Skip to content

Commit b133a6b

Browse files
committed
feat: Add support for new input blocks in Deepnote, including text, textarea, select, slider, checkbox, date, date range, file, and button blocks
1 parent 5381cdb commit b133a6b

File tree

5 files changed

+481
-3
lines changed

5 files changed

+481
-3
lines changed

package.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,60 @@
121121
"category": "Deepnote",
122122
"icon": "$(graph)"
123123
},
124+
{
125+
"command": "deepnote.addInputTextBlock",
126+
"title": "%deepnote.commands.addInputTextBlock.title%",
127+
"category": "Deepnote",
128+
"icon": "$(symbol-text)"
129+
},
130+
{
131+
"command": "deepnote.addInputTextareaBlock",
132+
"title": "%deepnote.commands.addInputTextareaBlock.title%",
133+
"category": "Deepnote",
134+
"icon": "$(note)"
135+
},
136+
{
137+
"command": "deepnote.addInputSelectBlock",
138+
"title": "%deepnote.commands.addInputSelectBlock.title%",
139+
"category": "Deepnote",
140+
"icon": "$(list-selection)"
141+
},
142+
{
143+
"command": "deepnote.addInputSliderBlock",
144+
"title": "%deepnote.commands.addInputSliderBlock.title%",
145+
"category": "Deepnote",
146+
"icon": "$(settings-more-action)"
147+
},
148+
{
149+
"command": "deepnote.addInputCheckboxBlock",
150+
"title": "%deepnote.commands.addInputCheckboxBlock.title%",
151+
"category": "Deepnote",
152+
"icon": "$(check)"
153+
},
154+
{
155+
"command": "deepnote.addInputDateBlock",
156+
"title": "%deepnote.commands.addInputDateBlock.title%",
157+
"category": "Deepnote",
158+
"icon": "$(calendar)"
159+
},
160+
{
161+
"command": "deepnote.addInputDateRangeBlock",
162+
"title": "%deepnote.commands.addInputDateRangeBlock.title%",
163+
"category": "Deepnote",
164+
"icon": "$(calendar)"
165+
},
166+
{
167+
"command": "deepnote.addInputFileBlock",
168+
"title": "%deepnote.commands.addInputFileBlock.title%",
169+
"category": "Deepnote",
170+
"icon": "$(file)"
171+
},
172+
{
173+
"command": "deepnote.addButtonBlock",
174+
"title": "%deepnote.commands.addButtonBlock.title%",
175+
"category": "Deepnote",
176+
"icon": "$(add)"
177+
},
124178
{
125179
"command": "dataScience.ClearCache",
126180
"title": "%jupyter.command.dataScience.clearCache.title%",

package.nls.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,15 @@
255255
"deepnote.commands.importJupyterNotebook.title": "Import Jupyter Notebook",
256256
"deepnote.commands.addSqlBlock.title": "Add SQL Block",
257257
"deepnote.commands.addBigNumberChartBlock.title": "Add Big Number Chart Block",
258+
"deepnote.commands.addInputTextBlock.title": "Add Input Text Block",
259+
"deepnote.commands.addInputTextareaBlock.title": "Add Input Textarea Block",
260+
"deepnote.commands.addInputSelectBlock.title": "Add Input Select Block",
261+
"deepnote.commands.addInputSliderBlock.title": "Add Input Slider Block",
262+
"deepnote.commands.addInputCheckboxBlock.title": "Add Input Checkbox Block",
263+
"deepnote.commands.addInputDateBlock.title": "Add Input Date Block",
264+
"deepnote.commands.addInputDateRangeBlock.title": "Add Input Date Range Block",
265+
"deepnote.commands.addInputFileBlock.title": "Add Input File Block",
266+
"deepnote.commands.addButtonBlock.title": "Add Button Block",
258267
"deepnote.views.explorer.name": "Explorer",
259268
"deepnote.views.explorer.welcome": "No Deepnote notebooks found in this workspace.",
260269
"deepnote.command.selectNotebook.title": "Select Notebook"

src/notebooks/deepnote/deepnoteNotebookCommandListener.ts

Lines changed: 172 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,119 @@
22
// Licensed under the MIT License.
33

44
import { injectable, inject } from 'inversify';
5-
import { commands, window, NotebookCellData, NotebookCellKind, NotebookEdit, NotebookRange } from 'vscode';
5+
import {
6+
commands,
7+
window,
8+
NotebookCellData,
9+
NotebookCellKind,
10+
NotebookEdit,
11+
NotebookRange,
12+
NotebookCell
13+
} from 'vscode';
614
import { IExtensionSyncActivationService } from '../../platform/activation/types';
715
import { IDisposableRegistry } from '../../platform/common/types';
816
import { Commands } from '../../platform/common/constants';
917
import { noop } from '../../platform/common/utils/misc';
1018
import { chainWithPendingUpdates } from '../../kernels/execution/notebookUpdater';
11-
import { DeepnoteBigNumberMetadataSchema } from './deepnoteSchemas';
19+
import {
20+
DeepnoteBigNumberMetadataSchema,
21+
DeepnoteTextInputMetadataSchema,
22+
DeepnoteTextareaInputMetadataSchema,
23+
DeepnoteSelectInputMetadataSchema,
24+
DeepnoteSliderInputMetadataSchema,
25+
DeepnoteCheckboxInputMetadataSchema,
26+
DeepnoteDateInputMetadataSchema,
27+
DeepnoteDateRangeInputMetadataSchema,
28+
DeepnoteFileInputMetadataSchema,
29+
DeepnoteButtonMetadataSchema
30+
} from './deepnoteSchemas';
31+
import z from 'zod';
32+
33+
type InputBlockType =
34+
| 'input-text'
35+
| 'input-textarea'
36+
| 'input-select'
37+
| 'input-slider'
38+
| 'input-checkbox'
39+
| 'input-date'
40+
| 'input-date-range'
41+
| 'input-file'
42+
| 'button';
43+
44+
export function getInputBlockMetadata(blockType: InputBlockType, variableName: string) {
45+
const defaultInput = {
46+
deepnote_variable_name: variableName
47+
};
48+
49+
switch (blockType) {
50+
case 'input-text':
51+
return DeepnoteTextInputMetadataSchema.parse(defaultInput);
52+
case 'input-textarea':
53+
return DeepnoteTextareaInputMetadataSchema.parse(defaultInput);
54+
case 'input-select':
55+
return DeepnoteSelectInputMetadataSchema.parse(defaultInput);
56+
case 'input-slider':
57+
return DeepnoteSliderInputMetadataSchema.parse(defaultInput);
58+
case 'input-checkbox':
59+
return DeepnoteCheckboxInputMetadataSchema.parse(defaultInput);
60+
case 'input-date':
61+
return DeepnoteDateInputMetadataSchema.parse(defaultInput);
62+
case 'input-date-range':
63+
return DeepnoteDateRangeInputMetadataSchema.parse(defaultInput);
64+
case 'input-file':
65+
return DeepnoteFileInputMetadataSchema.parse(defaultInput);
66+
case 'button':
67+
return DeepnoteButtonMetadataSchema.parse(defaultInput);
68+
default: {
69+
const exhaustiveCheck: never = blockType;
70+
throw new Error(`Unhandled block type: ${exhaustiveCheck satisfies never}`);
71+
}
72+
}
73+
}
74+
75+
export function safeParseDeepnoteVariableNameFromContentJson(content: string): string | undefined {
76+
try {
77+
return JSON.parse(content)['deepnote_variable_name'];
78+
} catch (error) {
79+
return undefined;
80+
}
81+
}
82+
83+
export function getNextDeepnoteVariableName(cells: NotebookCell[], prefix: 'df' | 'query' | 'input'): string {
84+
const deepnoteVariableNames = cells.reduce<string[]>((acc, cell) => {
85+
const contentValue = safeParseDeepnoteVariableNameFromContentJson(cell.document.getText());
86+
87+
if (contentValue != null) {
88+
acc.push(contentValue);
89+
}
90+
91+
const parsedMetadataValue = z.string().safeParse(cell.metadata.__deepnotePocket?.variableName);
92+
93+
if (parsedMetadataValue.success) {
94+
acc.push(parsedMetadataValue.data);
95+
}
96+
97+
return acc;
98+
}, []);
99+
100+
const maxDeepnoteVariableNamesSuffixNumber =
101+
deepnoteVariableNames.reduce<number | null>((acc, name) => {
102+
const m = name.match(new RegExp(`^${prefix}_(\\d+)$`));
103+
if (m == null) {
104+
return acc;
105+
}
106+
107+
const suffixNumber = parseInt(m[1]);
108+
109+
if (isNaN(suffixNumber)) {
110+
return acc;
111+
}
112+
113+
return acc == null || suffixNumber > acc ? suffixNumber : acc;
114+
}, null) ?? 0;
115+
116+
return `${prefix}_${maxDeepnoteVariableNamesSuffixNumber + 1}`;
117+
}
12118

13119
/**
14120
* Service responsible for registering and handling Deepnote-specific notebook commands.
@@ -29,6 +135,33 @@ export class DeepnoteNotebookCommandListener implements IExtensionSyncActivation
29135
this.disposableRegistry.push(
30136
commands.registerCommand(Commands.AddBigNumberChartBlock, () => this.addBigNumberChartBlock())
31137
);
138+
this.disposableRegistry.push(
139+
commands.registerCommand(Commands.AddInputTextBlock, () => this.addInputBlock('input-text'))
140+
);
141+
this.disposableRegistry.push(
142+
commands.registerCommand(Commands.AddInputTextareaBlock, () => this.addInputBlock('input-textarea'))
143+
);
144+
this.disposableRegistry.push(
145+
commands.registerCommand(Commands.AddInputSelectBlock, () => this.addInputBlock('input-select'))
146+
);
147+
this.disposableRegistry.push(
148+
commands.registerCommand(Commands.AddInputSliderBlock, () => this.addInputBlock('input-slider'))
149+
);
150+
this.disposableRegistry.push(
151+
commands.registerCommand(Commands.AddInputCheckboxBlock, () => this.addInputBlock('input-checkbox'))
152+
);
153+
this.disposableRegistry.push(
154+
commands.registerCommand(Commands.AddInputDateBlock, () => this.addInputBlock('input-date'))
155+
);
156+
this.disposableRegistry.push(
157+
commands.registerCommand(Commands.AddInputDateRangeBlock, () => this.addInputBlock('input-date-range'))
158+
);
159+
this.disposableRegistry.push(
160+
commands.registerCommand(Commands.AddInputFileBlock, () => this.addInputBlock('input-file'))
161+
);
162+
this.disposableRegistry.push(
163+
commands.registerCommand(Commands.AddButtonBlock, () => this.addInputBlock('button'))
164+
);
32165
}
33166

34167
private addSqlBlock(): void {
@@ -91,4 +224,41 @@ export class DeepnoteNotebookCommandListener implements IExtensionSyncActivation
91224
editor.selection = new NotebookRange(insertIndex, insertIndex + 1);
92225
}, noop);
93226
}
227+
228+
private addInputBlock(blockType: InputBlockType): void {
229+
const editor = window.activeNotebookEditor;
230+
if (!editor) {
231+
return;
232+
}
233+
const document = editor.notebook;
234+
const selection = editor.selection;
235+
const cells = editor.notebook.getCells();
236+
const deepnoteVariableName = getNextDeepnoteVariableName(cells, 'input');
237+
238+
// Determine the index where to insert the new cell (below current selection or at the end)
239+
const insertIndex = selection ? selection.end : document.cellCount;
240+
241+
// Get the appropriate schema and parse default metadata based on block type
242+
let defaultMetadata = getInputBlockMetadata(blockType, deepnoteVariableName);
243+
244+
const metadata = {
245+
__deepnotePocket: {
246+
type: blockType,
247+
...defaultMetadata
248+
}
249+
};
250+
251+
chainWithPendingUpdates(document, (edit) => {
252+
const newCell = new NotebookCellData(
253+
NotebookCellKind.Code,
254+
JSON.stringify(defaultMetadata, null, 2),
255+
'json'
256+
);
257+
newCell.metadata = metadata;
258+
const nbEdit = NotebookEdit.insertCells(insertIndex, [newCell]);
259+
edit.set(document.uri, [nbEdit]);
260+
}).then(() => {
261+
editor.selection = new NotebookRange(insertIndex, insertIndex + 1);
262+
}, noop);
263+
}
94264
}

0 commit comments

Comments
 (0)