From 0dd16ab99d7f9da30c8b63a68a8ffdf31d32949e Mon Sep 17 00:00:00 2001 From: Guilhem Date: Wed, 22 Jan 2025 12:51:14 +0100 Subject: [PATCH] feat (frontend): improve capture UI (#5051) * feat (frontend): improve capture UI * show schema diff * add accept reject form * add diff for first node input * move update schema button * change modified properties display * propagate change through nested components * allow modification of nested object in schema * reject nested changes * clean logic * clean code * clean * nit * fix first strep input * fix argument preview * clean * restore indentation for nested objects * ignore undefined field in diff computation * fix run button disabled update * nit * fix update JSON * fix deletion of nested components * dark mode * clean * replace dropdown with sidebar * only check type for compatibility * add shadow * disbale dnd on edit * auto-scroll within schema * fix nested not dnd object diff viewer * fix captures drawer * open edit tab on add new arg * change button label when schema is the same * ajust padding * fix arg update * fix oneof display * propagate change event through nested schema * handle oneOf * fix preview arg sync * handle s3 object * fix arg sync * update schema input compatibility * clean compatible * accept empty array items * clean * increase of schema args gap * fix nested schema update * allow number and int compatibility * reset args when modifying schema * open fields on add with addPropertyV2 --------- Co-authored-by: Ruben Fiszel --- frontend/src/lib/components/ArgInput.svelte | 1280 +++++++++-------- .../lib/components/EditableSchemaForm.svelte | 93 +- .../src/lib/components/FirstStepInputs.svelte | 4 +- .../lib/components/FlowPreviewContent.svelte | 91 +- .../src/lib/components/HistoricInputs.svelte | 2 + .../lib/components/SavedInputsPicker.svelte | 6 + frontend/src/lib/components/SchemaForm.svelte | 75 +- .../src/lib/components/SimpleEditor.svelte | 5 +- .../components/flows/content/FlowInput.svelte | 349 +++-- .../flows/content/FlowInputEditor.svelte | 49 +- .../meltComponents/SideBarTab.svelte | 69 + .../lib/components/schema/AddProperty.svelte | 4 +- .../components/schema/AddPropertyV2.svelte | 14 +- .../schema/EditableSchemaDrawer.svelte | 15 +- .../schema/EditableSchemaWrapper.svelte | 5 + .../components/schema/SchemaFormDND.svelte | 36 +- .../src/lib/components/schema/schemaUtils.ts | 217 +++ .../components/triggers/CaptureTable.svelte | 12 +- 18 files changed, 1447 insertions(+), 879 deletions(-) create mode 100644 frontend/src/lib/components/meltComponents/SideBarTab.svelte create mode 100644 frontend/src/lib/components/schema/schemaUtils.ts diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index 705da3e07c..c073eb2fcd 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -6,7 +6,7 @@ emptyString, getSchemaFromProperties } from '$lib/utils' - import { DollarSign, Pipette, Plus, X } from 'lucide-svelte' + import { DollarSign, Pipette, Plus, X, Check } from 'lucide-svelte' import { createEventDispatcher, onMount, tick } from 'svelte' import Multiselect from 'svelte-multiselect' import { fade } from 'svelte/transition' @@ -37,6 +37,7 @@ import { deepEqual } from 'fast-equals' import DynSelect from './DynSelect.svelte' import type { Script } from '$lib/gen' + import type { SchemaDiff } from '$lib/components/schema/schemaUtils' export let label: string = '' export let value: any @@ -96,6 +97,10 @@ | undefined = undefined export let otherArgs: Record = {} export let lightHeader = false + export let diffStatus: SchemaDiff | undefined = undefined + export let hideNested = false + export let nestedParent: { label: string; nestedParent: any | undefined } | undefined = undefined + export let nestedClasses = '' $: inputCat = computeInputCat(type, format, itemsType?.type, enum_, contentEncoding) @@ -347,464 +352,323 @@ /> -
-
- {#if displayHeader} - - {/if} +
+ {#if diffStatus && typeof diffStatus === 'object' && diffStatus.diff !== 'same'} +
+ + +
+ {/if} + {#if displayHeader} + + {/if} - {#if description} -
-
{description}
-
- {/if} + {#if description} +
+
{description}
+
+ {/if} -
- {#if inputCat == 'number'} - {#if extra['min'] != undefined && extra['max'] != undefined} - - {:else if extra['seconds'] !== undefined} - - {:else if extra?.currency} - - {:else} -
- { - ignoreValueUndefined = true - }} - class={valid - ? '' - : 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'} - placeholder={placeholder ?? defaultValue ?? ''} - bind:value - min={extra['min']} - max={extra['max']} - /> -
- {/if} - {:else if inputCat == 'boolean'} -
- { - e?.stopPropagation() - }} +
+ {#if inputCat == 'number'} + {#if extra['min'] != undefined && extra['max'] != undefined} + + {:else if extra['seconds'] !== undefined} + + {:else if extra?.currency} + + {:else} +
+ { + ignoreValueUndefined = true + }} class={valid ? '' : 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'} - bind:checked={value} + placeholder={placeholder ?? defaultValue ?? ''} + bind:value + min={extra['min']} + max={extra['max']} /> - {#if type == 'boolean' && value == undefined} -   Not set - {/if}
- {:else if inputCat == 'list' && !isListJson} -
-
- {#if Array.isArray(itemsType?.multiselect) && Array.isArray(value)} -
- { - dispatch('focus') - }} - /> -
- {:else if itemsType?.enum != undefined && Array.isArray(itemsType?.enum) && Array.isArray(value)} -
- { - dispatch('focus') - }} - /> -
- {:else} -
- {#key redraw} - {#if Array.isArray(value)} - {#each value ?? [] as v, i} - {#if i < itemsLimit} -
- {#if itemsType?.type == 'number'} - - {:else if itemsType?.type == 'string' && itemsType?.contentEncoding == 'base64'} - fileChanged(x, (val) => (value[i] = val))} - multiple={false} - /> - {:else if itemsType?.type == 'object' && itemsType?.resourceType === undefined && itemsType?.properties === undefined} - - {:else if Array.isArray(itemsType?.enum)} - { - dispatch('focus') - }} - on:blur={(e) => { - dispatch('blur') - }} - {defaultValue} - {valid} - {disabled} - {autofocus} - bind:value={v} - enum_={itemsType?.enum ?? []} - enumLabels={extra['enumLabels']} - /> - {:else if itemsType?.type == 'resource' && itemsType?.resourceType && resourceTypes?.includes(itemsType.resourceType)} - - {:else if itemsType?.type == 'resource'} - { - dispatch('focus') - }} - on:blur={(e) => { - dispatch('blur') - }} - code={JSON.stringify(v, null, 2)} - bind:value={v} - /> - {:else if itemsType?.type === 'object' && itemsType?.properties} -
- -
- {:else} - - {/if} - -
- {/if} - {/each} - {#if value.length > itemsLimit} - - {/if} - {/if} - {/key} -
-
- -
- {/if} -
-
- { - // 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' }} - /> -
-
- {:else if inputCat == 'dynselect'} - - {:else if inputCat == 'resource-object' && resourceTypes == undefined} - Loading resource types... - {:else if inputCat == 'resource-object' && (resourceTypes == undefined || (format.split('-').length > 1 && resourceTypes.includes(format.substring('resource-'.length))))} - { - defaultValue = null + {/if} + {:else if inputCat == 'boolean'} +
+ { + e?.stopPropagation() }} - {showSchemaExplorer} + {disabled} + class={valid + ? '' + : 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'} + bind:checked={value} /> - {:else if inputCat == 'resource-object' && format.split('-').length > 1 && format - .replace('resource-', '') - .replace('_', '') - .toLowerCase() == 's3object'} -
- - {#if s3FileUploadRawMode} - { - dispatch('focus') - }} - on:blur={(e) => { - dispatch('blur') - }} - code={JSON.stringify(value ?? defaultValue ?? { s3: '' }, null, 2)} - bind:value - /> - - {:else} - { - value = { - s3: evt.detail?.path ?? '', - filename: evt.detail?.filename ?? '' - } - }} - on:deletion={(evt) => { - value = { - s3: '' - } - }} - defaultValue={defaultValue?.s3} - /> - {/if} -
- {:else if inputCat == 'object' || inputCat == 'resource-object' || isListJson} - {#if oneOf && oneOf.length >= 2} -
- {#if oneOf && oneOf.length >= 2} - { - value = { label: oneOfSelected } - redraw += 1 - }} - > - {#each oneOf as obj} - - {/each} - - {#if oneOfSelected} - {@const objIdx = oneOf.findIndex((o) => o.title === oneOfSelected)} - {@const obj = oneOf[objIdx]} - {#if obj && obj.properties && Object.keys(obj.properties).length > 0} - {#key redraw} -
- {#if orderEditable} - { - if (oneOf && oneOf[objIdx]) { - const keys = e.detail - oneOf[objIdx].order = keys - } - }} - on:change - /> - {:else} - - {/if} -
- {/key} - {:else if disabled} -