From c394a47aba3b3334e79eaa9fc4d7b134015057eb Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Sat, 27 Sep 2025 23:52:13 +0200 Subject: [PATCH] feat: UX improvements (all inputs) * Monaco transparent bg * started improving input transform form * always show static/f selector * fix connecting btn changing size * pretty Fill Inputs button * ResizeTransitionWrapper * Prettier TemplateEditor * prevent double onpointerdown when clicking button to close * text hint * force focus border for TemplateEditor * styling in js mode * update select style * fix jittery fake monaco placeholder * select nits * aiproviderpicker + nits * smaller ${...} badge * no-default-style * select dropdown slide * nit * Refresh button in flow picker quick * jsonEditor pretty * ai provider toggle button more * change resource edit button pos * ResourcePicker Add and Refresh btn * fix scrollbar * Fix FileInput and S3 Arg Input * fix textarea styling * nicer refresh button in Test This Step * fix togglebutton border in darkmode * rounded nit * Fix multiselect styling * Prevent crash when selecting dyn-multiselect * missing $derived and $state => reactivity issue when switching between DynSelect and DynMultiselect * forgot $effect.pre * fix nested objects * nits * prettier json toggle and array inputs * array input nits * nit * fix json toggle appearing in fileinputs * nit * started updating PropertyEditor * (stash) fix select dropdown animation teleporting from bottom to top * nit * nit * resize transition in schemaform * nit * nit typo * nit enableFlyTransition * shadow nit * small consistency changes * user setting nit * resize transition in module preview form * more space * nit readability on hover * DateTimeInput new style * nit fix * remove yPadding in template and simple editor * nits * Revert "remove yPadding in template and simple editor" This reverts commit 8f27c8d0b8b9ef858e10d7cdd79f71a0715b87e7. * nit * Fix proppicker border * fix inconsistent spacing btw arginput and input transform form field headers * consistent add item button * nit * s3 settings nits * RunsFilter fix * gray ${...} badge * border fix darkmode * nit * nit app editor consistency * fix step input gen style * nit fix * nits * toggle border * nit toggle button * nit font-medium * nit font-medium * nit font-medium * nit font-medium * nit --------- Co-authored-by: Ruben Fiszel --- .../lib/components/AIProviderPicker.svelte | 33 +- frontend/src/lib/components/ArgEnum.svelte | 7 +- frontend/src/lib/components/ArgInput.svelte | 416 ++++++++++-------- .../lib/components/ArrayTypeNarrowing.svelte | 70 +-- .../src/lib/components/DateTimeInput.svelte | 12 +- .../lib/components/EditableSchemaForm.svelte | 10 +- .../components/FakeMonacoPlaceHolder.svelte | 6 +- .../src/lib/components/FieldHeader.svelte | 12 +- .../src/lib/components/FlowPlugConnect.svelte | 10 +- .../src/lib/components/FolderEditor.svelte | 12 +- .../src/lib/components/GroupEditor.svelte | 294 +++++++------ .../lib/components/InputTransformForm.svelte | 158 ++++--- .../InputTransformSchemaForm.svelte | 9 +- frontend/src/lib/components/JsonEditor.svelte | 18 +- frontend/src/lib/components/JsonInputs.svelte | 2 +- .../lib/components/ModulePreviewForm.svelte | 41 +- .../lib/components/NumberTypeNarrowing.svelte | 48 +- frontend/src/lib/components/Path.svelte | 10 +- .../src/lib/components/ResourcePicker.svelte | 157 ++++--- .../lib/components/S3ArrayHelperButton.svelte | 24 +- .../src/lib/components/S3FilePicker.svelte | 2 +- frontend/src/lib/components/SchemaForm.svelte | 26 +- .../src/lib/components/ScriptEditor.svelte | 2 +- frontend/src/lib/components/ShareModal.svelte | 8 +- .../src/lib/components/SimpleEditor.svelte | 14 +- .../lib/components/StringTypeNarrowing.svelte | 6 +- .../components/SuperadminSettingsInner.svelte | 6 +- .../src/lib/components/TemplateEditor.svelte | 28 +- frontend/src/lib/components/Toggle.svelte | 24 +- .../src/lib/components/WorkerGroup.svelte | 16 +- .../inputs/currency/CurrencyInput.svelte | 3 +- .../components/apps/editor/GridEditor.svelte | 1 - .../settingsPanel/InputsSpecEditor.svelte | 11 +- .../common/ResizeTransitionWrapper.svelte | 55 +++ .../components/common/button/Button.svelte | 3 +- .../common/button/ConnectionButton.svelte | 4 + .../calendarPicker/CalendarPicker.svelte | 2 +- .../common/fileInput/FileInput.svelte | 13 +- .../common/fileUpload/S3ArgInput.svelte | 22 +- .../components/common/popup/PopupV2.svelte | 1 + .../toggleButton-v2/ToggleButton.svelte | 72 ++- .../toggleButton-v2/ToggleButtonGroup.svelte | 2 +- .../toggleButton-v2/ToggleButtonMore.svelte | 8 +- .../components/copilot/StepInputGen.svelte | 18 +- .../components/copilot/StepInputsGen.svelte | 10 +- .../flows/content/DynamicInputHelpBox.svelte | 9 +- .../flows/content/FlowModuleComponent.svelte | 15 +- .../flows/propPicker/PropPickerWrapper.svelte | 17 +- .../src/lib/components/runs/RunOption.svelte | 4 +- .../src/lib/components/runs/RunsFilter.svelte | 17 +- .../schema/FlowPropertyEditor.svelte | 6 +- .../components/schema/PropertyEditor.svelte | 49 +-- .../components/schema/SchemaFormDND.svelte | 7 +- .../components/select/DraggableTags.svelte | 4 +- .../lib/components/select/MultiSelect.svelte | 24 +- .../src/lib/components/select/Select.svelte | 29 +- .../components/select/SelectDropdown.svelte | 96 ++-- .../components/settings/CreateToken.svelte | 145 +++--- .../settings/WorkspaceUserSettings.svelte | 45 +- .../components/text_input/TextInput.svelte | 55 +++ .../components/triggers/TriggersEditor.svelte | 2 +- frontend/src/lib/components/vscode.ts | 9 +- .../workspaceSettings/AISettings.svelte | 7 +- .../workspaceSettings/CreateWorkspace.svelte | 4 +- .../workspaceSettings/DucklakeSettings.svelte | 10 +- .../workspaceSettings/StorageSettings.svelte | 49 +-- .../(logged)/runs/[...path]/+page.svelte | 6 +- frontend/tailwind.config.cjs | 12 +- 68 files changed, 1378 insertions(+), 949 deletions(-) create mode 100644 frontend/src/lib/components/common/ResizeTransitionWrapper.svelte create mode 100644 frontend/src/lib/components/text_input/TextInput.svelte diff --git a/frontend/src/lib/components/AIProviderPicker.svelte b/frontend/src/lib/components/AIProviderPicker.svelte index b32453aa8f..c9336f8627 100644 --- a/frontend/src/lib/components/AIProviderPicker.svelte +++ b/frontend/src/lib/components/AIProviderPicker.svelte @@ -8,6 +8,7 @@ import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte' import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte' import ResourcePicker from './ResourcePicker.svelte' + import ToggleButtonMore from './common/toggleButton-v2/ToggleButtonMore.svelte' interface ProviderValue { kind?: AIProvider @@ -154,22 +155,36 @@ } -
+
-
- +
+ {#snippet children({ item })} - {#each providerOptions as option} - + {#each providerOptions.slice(0, 3) as option} + {/each} + value?.kind, (v) => v && onProviderChange(v)} + /> {/snippet}
-
+
-

resource

+

resource

resourceValueToPath(value?.resource), @@ -183,12 +198,13 @@ disabled={disabled || !value?.kind} placeholder="Select resource" selectFirst={true} + selectInputClass="!bg-surface" />
-

model

+

model

dispatch('focus')} onBlur={() => dispatch('blur')} + inputClass={selectClass} />
diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index 843281774f..ace4f6cbed 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -46,6 +46,7 @@ import { workspaceStore } from '$lib/stores' import { getJsonSchemaFromResource } from './schema/jsonSchemaResource.svelte' import AIProviderPicker from './AIProviderPicker.svelte' + import TextInput, { inputBaseClass, inputBorderClass } from './text_input/TextInput.svelte' interface Props { label?: string @@ -71,6 +72,7 @@ properties?: { [name: string]: SchemaProperty } } | undefined + lightHeaderFont?: boolean displayHeader?: boolean properties?: { [name: string]: SchemaProperty } | undefined nestedRequired?: string[] | undefined @@ -120,6 +122,8 @@ | undefined workspace?: string | undefined actions?: import('svelte').Snippet + innerBottomSnippet?: import('svelte').Snippet + fieldHeaderActions?: import('svelte').Snippet } let { @@ -133,7 +137,7 @@ oneOf = $bindable(undefined), required = false, pattern = $bindable(undefined), - valid = $bindable(undefined), + valid = $bindable(undefined), // Note : this should not exist, valid and error should be one coherent state enum_ = $bindable(undefined), disabled = false, itemsType = $bindable(undefined), @@ -176,7 +180,10 @@ appPath = undefined, computeS3ForceViewerPolicies = undefined, workspace = undefined, - actions + actions, + innerBottomSnippet, + fieldHeaderActions, + lightHeaderFont = false }: Props = $props() $effect(() => { @@ -468,6 +475,11 @@ let { debounced, clearDebounce } = debounce(() => compareValues(value), 50) let inputCat = $derived(computeInputCat(type, format, itemsType?.type, enum_, contentEncoding)) + let displayJsonToggleHeader = $derived( + displayHeader && + inputCat === 'list' && + !(itemsType?.resourceType === 's3_object' || itemsType?.resourceType === 's3object') + ) $effect(() => { oneOf && untrack(() => updateOneOfSelected(oneOf)) }) @@ -518,10 +530,10 @@ {#snippet variableInput()} {#if variableEditor} -
+
{#if value && typeof value == 'string' && value?.startsWith('$var:')} Linked to variable {/if} @@ -587,24 +599,43 @@
{/if} {#if displayHeader} - - {/if} +
+ +
+ {#if displayJsonToggleHeader} + { + // Once the user has changed the input type, we should not change it back automatically + if (!hasIsListJsonChanged) { + hasIsListJsonChanged = true + } - {#if description} -
-
{description}
+ evalValueToRaw() + isListJson = !isListJson + }} + checked={isListJson} + textClass="text-secondary" + size="xs" + options={{ left: 'json' }} + /> + {/if} + {@render fieldHeaderActions?.()} +
{/if} @@ -629,7 +660,22 @@ /> {:else}
- (ignoreValueUndefined = true), + placeholder: placeholder ?? defaultValue ?? '', + min: extra['min'], + max: extra['max'] + }} + {error} + bind:value + /> +
{/if} {:else if inputCat == 'boolean'} @@ -679,6 +725,7 @@ bind:editor {appPath} {computeS3ForceViewerPolicies} + bottom={innerBottomSnippet} /> {:else if inputCat == 'object' && format == 'json-schema'} {#await import('$lib/components/EditableSchemaForm.svelte')} @@ -722,8 +769,9 @@ {/await} {:else} -
+
{:else} -
+
{#key redraw} {#if Array.isArray(value)} {#each value ?? [] as v, i} {#if i < itemsLimit} -
+
+ {#snippet deleteItemBtn()} + + {/snippet} {#if itemsType?.type == 'number'} - + +
+ {@render deleteItemBtn()} +
{:else if itemsType?.type == 'string' && itemsType?.contentEncoding == 'base64'} fileChanged(x, (val) => (value[i] = val))} multiple={false} /> + {@render deleteItemBtn()} {:else if itemsType?.type == 'object' && itemsType?.resourceType === undefined && itemsType?.properties === undefined && !(format?.startsWith('resource-') && resourceTypes?.includes(format.split('-')[1]))} {#await import('$lib/components/JsonEditor.svelte')} @@ -792,6 +861,7 @@ bind:value={value[i]} /> {/await} + {@render deleteItemBtn()} {:else if Array.isArray(itemsType?.enum)} + {@render deleteItemBtn()} {:else if (itemsType?.type == 'resource' && itemsType?.resourceType && resourceTypes?.includes(itemsType.resourceType)) || (format?.startsWith('resource-') && resourceTypes?.includes(format.split('-')[1]))} {@const resourceFormat = itemsType?.type == 'resource' && @@ -821,6 +892,7 @@ format={resourceFormat} defaultValue={undefined} /> + {@render deleteItemBtn()} {:else if itemsType?.type == 'resource'} {#await import('$lib/components/JsonEditor.svelte')} @@ -837,9 +909,11 @@ bind:value={value[i]} /> {/await} + {@render deleteItemBtn()} {:else if itemsType?.type === 'object' && itemsType?.properties}
+ {@render deleteItemBtn()} {:else} - + +
+ {@render deleteItemBtn()} +
{/if} -
{/if} {/each} @@ -874,7 +945,7 @@ {#if value.startsWith('$res:')} {@render resourceInput()} {:else} -
+
Invalid string value: "{value}", expected array. Click add item to turn it into an array.
@@ -882,59 +953,58 @@ {/if} {/key}
-
- -
+ {/if}
-
- { - // Once the user has changed the input type, we should not change it back automatically - if (!hasIsListJsonChanged) { - hasIsListJsonChanged = true - } + {#if !displayHeader} +
+ { + // Once the user has changed the input type, we should not change it back automatically + if (!hasIsListJsonChanged) { + hasIsListJsonChanged = true + } - evalValueToRaw() - isListJson = !isListJson - }} - checked={isListJson} - textClass="text-secondary" - size="xs" - options={{ right: 'json' }} - /> -
+ evalValueToRaw() + isListJson = !isListJson + }} + checked={isListJson} + textClass="text-secondary" + size="xs" + options={{ left: 'json' }} + /> +
+ {/if}
{:else if inputCat == 'dynamic'} @@ -957,7 +1027,7 @@ /> {:else if inputCat == 'object' || inputCat == 'resource-object' || isListJson} {#if oneOf && oneOf.length >= 2} -
+
{#if oneOf && oneOf.length >= 2} 0} {#key redraw} -
- {#if orderEditable} - ({ - properties: obj.properties ?? {}, - order: obj.order, - $schema: '', - required: obj.required ?? [], - type: 'object' - }), - () => { - dispatch('nestedChange') - } - } - bind:args={value} - hiddenArgs={[ - oneOf?.find((o) => Object.keys(o.properties ?? {}).includes('kind')) - ? 'kind' - : 'label' - ]} - on:reorder={(e) => { - if (oneOf && oneOf[objIdx]) { - const keys = e.detail - oneOf[objIdx].order = keys - } - }} - on:nestedChange - {shouldDispatchChanges} - /> - {:else} - ({ + properties: obj.properties ?? {}, order: obj.order, $schema: '', required: obj.required ?? [], type: 'object' - }} - bind:args={ - () => value, - (v) => { - value = { ...v, [tagKey]: oneOfSelected } - } - } - {shouldDispatchChanges} - on:change={() => { + }), + () => { dispatch('nestedChange') - }} - on:nestedChange - /> - {/if} -
+ } + } + bind:args={value} + hiddenArgs={[ + oneOf?.find((o) => Object.keys(o.properties ?? {}).includes('kind')) + ? 'kind' + : 'label' + ]} + on:reorder={(e) => { + if (oneOf && oneOf[objIdx]) { + const keys = e.detail + oneOf[objIdx].order = keys + } + }} + on:nestedChange + {shouldDispatchChanges} + /> + {:else} + value, + (v) => { + value = { ...v, [tagKey]: oneOfSelected } + } + } + {shouldDispatchChanges} + on:change={() => { + dispatch('nestedChange') + }} + on:nestedChange + /> + {/if} {/key} {:else if disabled} @@ -1107,9 +1177,10 @@ {/if}
{:else if properties && Object.keys(properties).length > 0 && inputCat !== 'list'} -
+
{#if orderEditable} {:else} {/await} {/if} - {#if inputCat == 'list'} -
+ {#if inputCat == 'list' && !displayHeader} +
{ isListJson = !isListJson @@ -1199,7 +1271,7 @@ checked={isListJson} textClass="text-secondary" size="xs" - options={{ right: 'json' }} + options={{ left: 'json' }} />
{/if} @@ -1220,6 +1292,7 @@ dispatch('blur') }} enumLabels={extra['enumLabels']} + selectClass="min-h-10" />
{:else if inputCat == 'date'} @@ -1328,12 +1401,7 @@ use:autosize onkeydown={onKeyDown} {disabled} - class={twMerge( - 'w-full', - valid - ? '' - : 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-3' - )} + class={twMerge('w-full', inputBaseClass, inputBorderClass({ error: !!error }))} placeholder={placeholder ?? defaultValue ?? ''} bind:value > @@ -1344,7 +1412,7 @@
- {#if !compact || (error && error != '')} -
- {#if disabled || error === ''} -   - {:else} - {error} - {/if} + {#if description} +
+
{description}
+ {/if} + + {#if !compact || (error && error != '')} + {#if disabled || error === ''} +   + {:else} +
+ {error} +
+ {/if} {:else if !noMargin}
{/if} @@ -1381,10 +1455,4 @@ -webkit-appearance: none !important; margin: 0; } - - /* Firefox */ - input[type='number'] { - -moz-appearance: textfield !important; - appearance: textfield !important; - } diff --git a/frontend/src/lib/components/ArrayTypeNarrowing.svelte b/frontend/src/lib/components/ArrayTypeNarrowing.svelte index 86b4fb6c10..0a27ddf228 100644 --- a/frontend/src/lib/components/ArrayTypeNarrowing.svelte +++ b/frontend/src/lib/components/ArrayTypeNarrowing.svelte @@ -10,6 +10,7 @@ import type { SchemaProperty } from '$lib/common' import Toggle from './Toggle.svelte' import { tick } from 'svelte' + import Select from './select/Select.svelte' interface Props { canEditResourceType?: boolean @@ -54,39 +55,44 @@ {#if canEditResourceType || originalType == 'string[]' || originalType == 'object[]'} {:else if itemsType?.resourceType}