Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 18 additions & 0 deletions src/messageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,24 @@ export type LocalizedMessages = {
integrationsRequiredField: string;
integrationsOptionalField: string;
integrationsUnnamedIntegration: string;
// Select input settings strings
selectInputSettingsTitle: string;
allowMultipleValues: string;
allowEmptyValue: string;
valueSourceTitle: string;
fromOptions: string;
fromOptionsDescription: string;
addOptionPlaceholder: string;
addButton: string;
fromVariable: string;
fromVariableDescription: string;
variablePlaceholder: string;
optionNameLabel: string;
variableNameLabel: string;
removeOptionAriaLabel: string;
saveButton: string;
cancelButton: string;
failedToSave: string;
};
// Map all messages to specific payloads
export class IInteractiveWindowMapping {
Expand Down
177 changes: 152 additions & 25 deletions src/notebooks/deepnote/converters/inputConverters.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NotebookCellData, NotebookCellKind } from 'vscode';
import { z } from 'zod';

import { logger } from '../../../platform/logging';
import type { BlockConverter } from './blockConverter';
import type { DeepnoteBlock } from '../../../platform/deepnote/deepnoteTypes';
import {
Expand All @@ -14,57 +15,69 @@ 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;
abstract getSupportedType(): string;
abstract defaultConfig(): z.infer<T>;

applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
/**
* Helper method to update block metadata with common logic.
* Clears block.content, parses schema, deletes DEEPNOTE_VSCODE_RAW_CONTENT_KEY,
* and merges metadata with updates.
*
* If metadata is missing or invalid, applies default config.
* Otherwise, preserves existing metadata and only applies updates.
*/
protected updateBlockMetadata(block: DeepnoteBlock, updates: Partial<z.infer<T>>): void {
block.content = '';

const config = this.schema().safeParse(parseJsonWithFallback(cell.value));
if (block.metadata != null) {
delete block.metadata[DEEPNOTE_VSCODE_RAW_CONTENT_KEY];
}

// Check if existing metadata is valid
const existingMetadata = this.schema().safeParse(block.metadata);
const hasValidMetadata =
existingMetadata.success && block.metadata != null && Object.keys(block.metadata).length > 0;

if (config.success !== true) {
if (hasValidMetadata) {
// Preserve existing metadata and only apply updates
block.metadata = {
...(block.metadata ?? {}),
[DEEPNOTE_VSCODE_RAW_CONTENT_KEY]: cell.value
...updates
};
} else {
// Apply defaults when metadata is missing or invalid
block.metadata = {
...this.defaultConfig(),
...updates
};
return;
}

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

block.metadata = {
...(block.metadata ?? {}),
...config.data
};
applyChangesToBlock(block: DeepnoteBlock, _cell: NotebookCellData): void {
// Default implementation: preserve existing metadata
// Readonly blocks (select, checkbox, date, date-range, button) use this default behavior
// Editable blocks override this method to update specific metadata fields
this.updateBlockMetadata(block, {});
}

canConvert(blockType: string): boolean {
return blockType.toLowerCase() === this.getSupportedType();
return this.getSupportedTypes().includes(blockType.toLowerCase());
}

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));
logger.error('Error parsing deepnote input metadata', deepnoteMetadataResult.error);
}

const configStr = deepnoteJupyterRawContentResult.success
? deepnoteJupyterRawContentResult.data
: deepnoteMetadataResult.success
? JSON.stringify(deepnoteMetadataResult.data, null, 2)
: JSON.stringify(this.defaultConfig(), null, 2);

const cell = new NotebookCellData(NotebookCellKind.Code, configStr, 'json');
// Default fallback: empty plaintext cell; subclasses render content/language
const cell = new NotebookCellData(NotebookCellKind.Code, '', 'plaintext');

return cell;
}
Expand All @@ -86,6 +99,21 @@ 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 {
// The cell value contains the text value
const value = cell.value;

this.updateBlockMetadata(block, {
deepnote_variable_value: value
});
}
}

export class InputTextareaBlockConverter extends BaseInputBlockConverter<typeof DeepnoteTextareaInputMetadataSchema> {
Expand All @@ -100,6 +128,21 @@ 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 {
// The cell value contains the text value
const value = cell.value;

this.updateBlockMetadata(block, {
deepnote_variable_value: value
});
}
}

export class InputSelectBlockConverter extends BaseInputBlockConverter<typeof DeepnoteSelectInputMetadataSchema> {
Expand All @@ -114,6 +157,15 @@ 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;
}

// Select blocks are readonly - edits are reverted by DeepnoteInputBlockEditProtection
// Uses base class applyChangesToBlock which preserves existing metadata
}

export class InputSliderBlockConverter extends BaseInputBlockConverter<typeof DeepnoteSliderInputMetadataSchema> {
Expand All @@ -128,6 +180,30 @@ 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 {
// Parse numeric value; fall back to existing/default
const str = cell.value.trim();
const parsed = Number(str);

const existingMetadata = this.schema().safeParse(block.metadata);

const existingValue = existingMetadata.success
? Number(existingMetadata.data.deepnote_variable_value)
: Number(this.defaultConfig().deepnote_variable_value);
const fallback = Number.isFinite(existingValue) ? existingValue : 0;
const value = Number.isFinite(parsed) ? parsed : fallback;

this.updateBlockMetadata(block, {
deepnote_variable_value: String(value)
});
}
}

export class InputCheckboxBlockConverter extends BaseInputBlockConverter<typeof DeepnoteCheckboxInputMetadataSchema> {
Expand All @@ -142,6 +218,15 @@ 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;
}

// Checkbox blocks are readonly - edits are reverted by DeepnoteInputBlockEditProtection
// Uses base class applyChangesToBlock which preserves existing metadata
}

export class InputDateBlockConverter extends BaseInputBlockConverter<typeof DeepnoteDateInputMetadataSchema> {
Expand All @@ -156,6 +241,15 @@ 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;
}

// Date blocks are readonly - edits are reverted by DeepnoteInputBlockEditProtection
// Uses base class applyChangesToBlock which preserves existing metadata
}

export class InputDateRangeBlockConverter extends BaseInputBlockConverter<typeof DeepnoteDateRangeInputMetadataSchema> {
Expand All @@ -170,6 +264,15 @@ 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;
}

// Date range blocks are readonly - edits are reverted by DeepnoteInputBlockEditProtection
// Uses base class applyChangesToBlock which preserves existing metadata
}

export class InputFileBlockConverter extends BaseInputBlockConverter<typeof DeepnoteFileInputMetadataSchema> {
Expand All @@ -184,6 +287,21 @@ 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 {
// Remove quotes from the cell value
const value = cell.value.trim().replace(/^["']|["']$/g, '');

this.updateBlockMetadata(block, {
deepnote_variable_value: value
});
}
}

export class ButtonBlockConverter extends BaseInputBlockConverter<typeof DeepnoteButtonMetadataSchema> {
Expand All @@ -198,4 +316,13 @@ 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;
}

// Button blocks don't store any value from the cell content
// Uses base class applyChangesToBlock which preserves existing metadata
}
Loading