Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
24851bd
feat: input block code editors with variable name change button
jankuca Oct 23, 2025
3aefe9d
add options to input blocks
jankuca Oct 23, 2025
378ccda
show input values in editors
jankuca Oct 23, 2025
0704de4
live update of input editor when value changes
jankuca Oct 23, 2025
66f832a
fix date values
jankuca Oct 23, 2025
bf199da
feat: make non-text input block editors readonly
jankuca Oct 23, 2025
e953e7b
fix date format
jankuca Oct 24, 2025
4ce5ae7
fix: prevent language change of input block editors
jankuca Oct 24, 2025
6d71fc0
feat: add Step option to slider inputs
jankuca Oct 24, 2025
28e205f
feat: add file picker button to file inputs
jankuca Oct 24, 2025
cf64ac0
add select input settings screen
jankuca Oct 24, 2025
62628b8
feat: add multi-select support to select inputs
jankuca Oct 24, 2025
a3fb707
fix: improve UX of multi-select
jankuca Oct 24, 2025
b04f5f2
fix empty value handling for multi-select inputs
jankuca Oct 24, 2025
9854628
feat: add warning comment to button blocks
jankuca Oct 24, 2025
4b1b962
fix ts issues: add localization for select box webview
jankuca Oct 24, 2025
06d2d7c
fix: normalize 'None' to null in select input parsing
jankuca Oct 24, 2025
131a377
fix: parse slider value as number instead of string
jankuca Oct 24, 2025
4b5b5b5
fix: use null instead of empty string for date-range parse failures
jankuca Oct 24, 2025
d056497
fix: localize tooltip strings and wrap switch-case declarations in bl…
jankuca Oct 24, 2025
5618b50
fix: replace deprecated onKeyPress with onKeyDown
jankuca Oct 24, 2025
e1a61cf
fix: add 'cancel' to WebviewMessage type union
jankuca Oct 24, 2025
8659ead
fix: remove duplicate SelectInputSettings interface
jankuca Oct 24, 2025
267aca6
fix: add accessibility improvements to SelectInputSettingsPanel
jankuca Oct 24, 2025
d0efc2d
fix: improve webview content loading and localization
jankuca Oct 24, 2025
8d4966a
fix: use updateCellMetadata to preserve cell outputs and attachments
jankuca Oct 24, 2025
43fe8af
fix: properly parse slider values as numbers in applyChangesToBlock
jankuca Oct 24, 2025
6a5982b
fix: update input-file status bar test to expect 3 items
jankuca Oct 24, 2025
09266cb
fix: resolve all lint issues
jankuca Oct 24, 2025
760fdd0
fix: remove invalid content parsing for variable names
jankuca Oct 24, 2025
3202b3a
refactor: remove dead input value parsing logic
jankuca Oct 24, 2025
8a9ede8
fix: handle lineCount=0
jankuca Oct 24, 2025
d1a6abc
add logs for failed reverts
jankuca Oct 24, 2025
07e59fe
remove unnecessary base logic
jankuca Oct 24, 2025
0f2cb39
simplify input converters
jankuca Oct 24, 2025
779448a
fix lang in text input tests
jankuca Oct 24, 2025
bae1759
remove outdated input value logic
jankuca Oct 24, 2025
9297504
add handling for start>end date ranges
jankuca Oct 24, 2025
c2c6f45
improve empty selection handling when not allowed
jankuca Oct 24, 2025
6b021cd
fix file input paths
jankuca Oct 24, 2025
3663f99
ease test
jankuca Oct 24, 2025
f41cd57
improve uri typing
jankuca Oct 24, 2025
378c9cf
handle special chars in input values
jankuca Oct 24, 2025
7c83d1e
delete unused html
jankuca Oct 24, 2025
a035cd8
add focus outlines
jankuca Oct 24, 2025
ebf8518
aria for inputs
jankuca Oct 24, 2025
99e86c3
tighten typing in converotrs
jankuca Oct 24, 2025
b7f2dee
refactor mock logger
jankuca Oct 24, 2025
3c421dd
refactor canConvert
jankuca Oct 24, 2025
f74b221
fix comment
jankuca Oct 24, 2025
c8ccc02
fix type
jankuca Oct 24, 2025
96738bc
void->undefined
jankuca Oct 24, 2025
eff2419
remove copyright
jankuca Oct 24, 2025
684e765
fix tests of numeric values
jankuca Oct 24, 2025
57e826a
show "Set variable name" when not set
jankuca Oct 24, 2025
7c3e74c
fix extension filter
jankuca Oct 24, 2025
c090db7
Fix date input timezone shifts in input block status bar
jankuca Oct 24, 2025
5913802
Fix accessibility issues in SelectInputSettingsPanel radio buttons
jankuca Oct 24, 2025
67c471b
fix css importing
jankuca Oct 24, 2025
97c5150
add tests for command handlers in the input status bar provider
jankuca Oct 24, 2025
459f6d6
remove unused imports
jankuca Oct 24, 2025
dfacb82
polish input converter tests
jankuca Oct 27, 2025
c5bb4ec
respect cancl token
jankuca Oct 27, 2025
2523fcd
fix: avoid max<min in sliders
jankuca Oct 27, 2025
9f6c5d3
localize file input block status bar
jankuca Oct 27, 2025
cc33ea3
fix: prevent invalid dates
jankuca Oct 27, 2025
2577c37
polish select variable metadata cleanup
jankuca Oct 27, 2025
f6567f3
replace event.data cast with type param
jankuca Oct 27, 2025
cbd0cc8
polish localization of select input settings webview
jankuca Oct 27, 2025
f478300
avoid dupe select box options
jankuca Oct 27, 2025
84f4404
add accessible labels to select input settings
jankuca Oct 27, 2025
18b3faf
polish localization of select input settings
jankuca Oct 27, 2025
51acc12
disposable in test
jankuca Oct 27, 2025
002e44a
reorganize imports in input block tests
jankuca Oct 27, 2025
bb7c4f5
fixup token
jankuca Oct 27, 2025
b4569fa
Merge remote-tracking branch 'origin/main' into jk/feat/input-block-ux
jankuca Oct 29, 2025
d948c28
empty function -> return undefined
jankuca Oct 29, 2025
e099928
fix package lock file
jankuca Oct 29, 2025
cec91d3
fix: fix default metadata logic
jankuca Oct 29, 2025
0ed9e29
test: add more tests for sql block status bar provider
jankuca Oct 29, 2025
a7868ab
localize select input strings
jankuca Oct 29, 2025
deaa5af
tighten typing of select box webview messages
jankuca Oct 29, 2025
57093c5
test: add more tests for sql statu sbar
jankuca Oct 29, 2025
e6dc2fd
test: add more tests for input block status bar provider
jankuca Oct 29, 2025
96f5af8
convert to typed error
jankuca Oct 29, 2025
6182bb7
remove generated copyright
jankuca Oct 29, 2025
d6cae8a
discriminated union for select box options
jankuca Oct 29, 2025
8e2404a
polish sql status bar test
jankuca Oct 29, 2025
9ae6a65
swap data and localization string sending
jankuca Oct 29, 2025
c089b87
Revert "discriminated union for select box options"
jankuca Oct 29, 2025
6e419f2
refactor: centralize error localization and improve error handling in…
jankuca Oct 29, 2025
d022716
improve test
jankuca Oct 29, 2025
7b8ceff
remove generated copyright header
jankuca Oct 29, 2025
ebd75c9
fix deduplication
jankuca Oct 29, 2025
9719a81
Fix promise handling in select input settings save failure
jankuca Oct 29, 2025
c37560c
fix select box option detection
jankuca Oct 29, 2025
ec12df9
fix dispose race conditions in select box settings webview
jankuca Oct 29, 2025
6bb525e
fix select box message type use
jankuca Oct 29, 2025
d70a96a
cancel token in the webview
jankuca Oct 29, 2025
18382fc
hide error cause from ui
jankuca Oct 29, 2025
727f55c
Merge branch 'main' into jk/feat/input-block-ux
jankuca Oct 29, 2025
826e91f
consolidate input block language options
jankuca Oct 30, 2025
e5bdfa7
fix text input block execution
jankuca Oct 30, 2025
f59aad4
Merge branch 'main' into jk/feat/input-block-ux
jankuca Oct 30, 2025
b15eb5b
Merge branch 'main' into jk/feat/input-block-ux
jankuca Oct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build/esbuild/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,11 @@ async function buildAll() {
path.join(extensionFolder, 'src', 'webviews', 'webview-side', 'integrations', 'index.tsx'),
path.join(extensionFolder, 'dist', 'webviews', 'webview-side', 'integrations', 'index.js'),
{ target: 'web', watch: watchAll }
),
build(
path.join(extensionFolder, 'src', 'webviews', 'webview-side', 'selectInputSettings', 'index.tsx'),
path.join(extensionFolder, 'dist', 'webviews', 'webview-side', 'selectInputSettings', 'index.js'),
{ target: 'web', watch: watchAll }
)
);

Expand Down
284 changes: 267 additions & 17 deletions src/notebooks/deepnote/converters/inputConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
DeepnoteFileInputMetadataSchema,
DeepnoteButtonMetadataSchema
} from '../deepnoteSchemas';
import { parseJsonWithFallback } from '../dataConversionUtils';
import { DEEPNOTE_VSCODE_RAW_CONTENT_KEY } from './constants';
import { formatInputBlockCellContent } from '../inputBlockContentFormatter';

export abstract class BaseInputBlockConverter<T extends z.ZodObject> implements BlockConverter {
abstract schema(): T;
Expand All @@ -25,23 +25,21 @@ export abstract class BaseInputBlockConverter<T extends z.ZodObject> implements
applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
block.content = '';

const config = this.schema().safeParse(parseJsonWithFallback(cell.value));
// The cell value now contains just the variable name
const variableName = cell.value.trim();

if (config.success !== true) {
block.metadata = {
...(block.metadata ?? {}),
[DEEPNOTE_VSCODE_RAW_CONTENT_KEY]: cell.value
};
return;
}
// Preserve existing metadata and update only the variable name
const existingMetadata = this.schema().safeParse(block.metadata);
const baseMetadata = existingMetadata.success ? existingMetadata.data : this.defaultConfig();

if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

block.metadata = {
...(block.metadata ?? {}),
...config.data
...baseMetadata,
deepnote_variable_name: variableName
};
}

Expand All @@ -50,21 +48,20 @@ export abstract class BaseInputBlockConverter<T extends z.ZodObject> implements
}

convertToCell(block: DeepnoteBlock): NotebookCellData {
const deepnoteJupyterRawContentResult = z.string().safeParse(block.metadata?.[DEEPNOTE_VSCODE_RAW_CONTENT_KEY]);
const deepnoteMetadataResult = this.schema().safeParse(block.metadata);

if (deepnoteMetadataResult.error != null) {
console.error('Error parsing deepnote input metadata:', deepnoteMetadataResult.error);
console.debug('Metadata:', JSON.stringify(block.metadata));
}

const configStr = deepnoteJupyterRawContentResult.success
? deepnoteJupyterRawContentResult.data
: deepnoteMetadataResult.success
? JSON.stringify(deepnoteMetadataResult.data, null, 2)
: JSON.stringify(this.defaultConfig(), null, 2);
// Extract the variable name from metadata
const variableName = deepnoteMetadataResult.success
? (deepnoteMetadataResult.data as { deepnote_variable_name?: string }).deepnote_variable_name || ''
: '';

const cell = new NotebookCellData(NotebookCellKind.Code, configStr, 'json');
// Create a code cell with Python language showing just the variable name
const cell = new NotebookCellData(NotebookCellKind.Code, `# ${variableName}`, 'python');

return cell;
}
Expand All @@ -86,6 +83,32 @@ export class InputTextBlockConverter extends BaseInputBlockConverter<typeof Deep
defaultConfig() {
return this.DEFAULT_INPUT_TEXT_CONFIG;
}

override convertToCell(block: DeepnoteBlock): NotebookCellData {
const cellValue = formatInputBlockCellContent('input-text', block.metadata ?? {});
const cell = new NotebookCellData(NotebookCellKind.Code, cellValue, 'plaintext');
return cell;
}

override applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
block.content = '';

// The cell value contains the text value
const value = cell.value;

const existingMetadata = this.schema().safeParse(block.metadata);
const baseMetadata = existingMetadata.success ? existingMetadata.data : this.defaultConfig();

if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

block.metadata = {
...(block.metadata ?? {}),
...baseMetadata,
deepnote_variable_value: value
};
}
}

export class InputTextareaBlockConverter extends BaseInputBlockConverter<typeof DeepnoteTextareaInputMetadataSchema> {
Expand All @@ -100,6 +123,32 @@ export class InputTextareaBlockConverter extends BaseInputBlockConverter<typeof
defaultConfig() {
return this.DEFAULT_INPUT_TEXTAREA_CONFIG;
}

override convertToCell(block: DeepnoteBlock): NotebookCellData {
const cellValue = formatInputBlockCellContent('input-textarea', block.metadata ?? {});
const cell = new NotebookCellData(NotebookCellKind.Code, cellValue, 'plaintext');
return cell;
}

override applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
block.content = '';

// The cell value contains the text value
const value = cell.value;

const existingMetadata = this.schema().safeParse(block.metadata);
const baseMetadata = existingMetadata.success ? existingMetadata.data : this.defaultConfig();

if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

block.metadata = {
...(block.metadata ?? {}),
...baseMetadata,
deepnote_variable_value: value
};
}
}

export class InputSelectBlockConverter extends BaseInputBlockConverter<typeof DeepnoteSelectInputMetadataSchema> {
Expand All @@ -114,6 +163,46 @@ export class InputSelectBlockConverter extends BaseInputBlockConverter<typeof De
defaultConfig() {
return this.DEFAULT_INPUT_SELECT_CONFIG;
}

override convertToCell(block: DeepnoteBlock): NotebookCellData {
const cellValue = formatInputBlockCellContent('input-select', block.metadata ?? {});
const cell = new NotebookCellData(NotebookCellKind.Code, cellValue, 'python');
return cell;
}

override applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
block.content = '';

// Parse the cell value to extract the selection
const cellValue = cell.value.trim();
let value: string | string[];

if (cellValue.startsWith('[') && cellValue.endsWith(']')) {
// Multi-select: parse array
const arrayContent = cellValue.slice(1, -1);
value = arrayContent
.split(',')
.map((v) => v.trim())
.filter((v) => v)
.map((v) => v.replace(/^["']|["']$/g, ''));
} else {
// Single select: remove quotes
value = cellValue.replace(/^["']|["']$/g, '');
}

const existingMetadata = this.schema().safeParse(block.metadata);
const baseMetadata = existingMetadata.success ? existingMetadata.data : this.defaultConfig();

if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

block.metadata = {
...(block.metadata ?? {}),
...baseMetadata,
deepnote_variable_value: value
};
}
}

export class InputSliderBlockConverter extends BaseInputBlockConverter<typeof DeepnoteSliderInputMetadataSchema> {
Expand All @@ -128,6 +217,32 @@ export class InputSliderBlockConverter extends BaseInputBlockConverter<typeof De
defaultConfig() {
return this.DEFAULT_INPUT_SLIDER_CONFIG;
}

override convertToCell(block: DeepnoteBlock): NotebookCellData {
const cellValue = formatInputBlockCellContent('input-slider', block.metadata ?? {});
const cell = new NotebookCellData(NotebookCellKind.Code, cellValue, 'python');
return cell;
}

override applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
block.content = '';

// The cell value contains the numeric value as a string
const value = cell.value.trim();

const existingMetadata = this.schema().safeParse(block.metadata);
const baseMetadata = existingMetadata.success ? existingMetadata.data : this.defaultConfig();

if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

block.metadata = {
...(block.metadata ?? {}),
...baseMetadata,
deepnote_variable_value: value
};
}
}

export class InputCheckboxBlockConverter extends BaseInputBlockConverter<typeof DeepnoteCheckboxInputMetadataSchema> {
Expand All @@ -142,6 +257,33 @@ export class InputCheckboxBlockConverter extends BaseInputBlockConverter<typeof
defaultConfig() {
return this.DEFAULT_INPUT_CHECKBOX_CONFIG;
}

override convertToCell(block: DeepnoteBlock): NotebookCellData {
const cellValue = formatInputBlockCellContent('input-checkbox', block.metadata ?? {});
const cell = new NotebookCellData(NotebookCellKind.Code, cellValue, 'python');
return cell;
}

override applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
block.content = '';

// Parse the cell value to get boolean
const cellValue = cell.value.trim();
const value = cellValue === 'True' || cellValue === 'true';

const existingMetadata = this.schema().safeParse(block.metadata);
const baseMetadata = existingMetadata.success ? existingMetadata.data : this.defaultConfig();

if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

block.metadata = {
...(block.metadata ?? {}),
...baseMetadata,
deepnote_variable_value: value
};
}
}

export class InputDateBlockConverter extends BaseInputBlockConverter<typeof DeepnoteDateInputMetadataSchema> {
Expand All @@ -156,6 +298,32 @@ export class InputDateBlockConverter extends BaseInputBlockConverter<typeof Deep
defaultConfig() {
return this.DEFAULT_INPUT_DATE_CONFIG;
}

override convertToCell(block: DeepnoteBlock): NotebookCellData {
const cellValue = formatInputBlockCellContent('input-date', block.metadata ?? {});
const cell = new NotebookCellData(NotebookCellKind.Code, cellValue, 'python');
return cell;
}

override applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
block.content = '';

// Remove quotes from the cell value
const value = cell.value.trim().replace(/^["']|["']$/g, '');

const existingMetadata = this.schema().safeParse(block.metadata);
const baseMetadata = existingMetadata.success ? existingMetadata.data : this.defaultConfig();

if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

block.metadata = {
...(block.metadata ?? {}),
...baseMetadata,
deepnote_variable_value: value
};
}
}

export class InputDateRangeBlockConverter extends BaseInputBlockConverter<typeof DeepnoteDateRangeInputMetadataSchema> {
Expand All @@ -170,6 +338,39 @@ export class InputDateRangeBlockConverter extends BaseInputBlockConverter<typeof
defaultConfig() {
return this.DEFAULT_INPUT_DATE_RANGE_CONFIG;
}

override convertToCell(block: DeepnoteBlock): NotebookCellData {
const cellValue = formatInputBlockCellContent('input-date-range', block.metadata ?? {});
const cell = new NotebookCellData(NotebookCellKind.Code, cellValue, 'python');
return cell;
}

override applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
block.content = '';

// Parse the cell value to extract the date range
const cellValue = cell.value.trim();
let value: [string, string] | string = '';

// Try to parse as tuple
const tupleMatch = cellValue.match(/\(\s*["']([^"']*)["']\s*,\s*["']([^"']*)["']\s*\)/);
if (tupleMatch) {
value = [tupleMatch[1], tupleMatch[2]];
}

const existingMetadata = this.schema().safeParse(block.metadata);
const baseMetadata = existingMetadata.success ? existingMetadata.data : this.defaultConfig();

if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

block.metadata = {
...(block.metadata ?? {}),
...baseMetadata,
deepnote_variable_value: value
};
}
}

export class InputFileBlockConverter extends BaseInputBlockConverter<typeof DeepnoteFileInputMetadataSchema> {
Expand All @@ -184,6 +385,32 @@ export class InputFileBlockConverter extends BaseInputBlockConverter<typeof Deep
defaultConfig() {
return this.DEFAULT_INPUT_FILE_CONFIG;
}

override convertToCell(block: DeepnoteBlock): NotebookCellData {
const cellValue = formatInputBlockCellContent('input-file', block.metadata ?? {});
const cell = new NotebookCellData(NotebookCellKind.Code, cellValue, 'python');
return cell;
}

override applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
block.content = '';

// Remove quotes from the cell value
const value = cell.value.trim().replace(/^["']|["']$/g, '');

const existingMetadata = this.schema().safeParse(block.metadata);
const baseMetadata = existingMetadata.success ? existingMetadata.data : this.defaultConfig();

if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

block.metadata = {
...(block.metadata ?? {}),
...baseMetadata,
deepnote_variable_value: value
};
}
}

export class ButtonBlockConverter extends BaseInputBlockConverter<typeof DeepnoteButtonMetadataSchema> {
Expand All @@ -198,4 +425,27 @@ export class ButtonBlockConverter extends BaseInputBlockConverter<typeof Deepnot
defaultConfig() {
return this.DEFAULT_BUTTON_CONFIG;
}

override convertToCell(block: DeepnoteBlock): NotebookCellData {
const cellValue = formatInputBlockCellContent('button', block.metadata ?? {});
const cell = new NotebookCellData(NotebookCellKind.Code, cellValue, 'python');
return cell;
}

override applyChangesToBlock(block: DeepnoteBlock, _cell: NotebookCellData): void {
block.content = '';

// Button blocks don't store any value from the cell content
const existingMetadata = this.schema().safeParse(block.metadata);
const baseMetadata = existingMetadata.success ? existingMetadata.data : this.defaultConfig();

if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

block.metadata = {
...(block.metadata ?? {}),
...baseMetadata
};
}
}
Loading
Loading