Files
windmill/frontend/src/lib/components/schema/PropertyEditor.svelte
T
3c99b3fdc7 feat: migrate to svelte5 + vite6 (#4813)
* runs on svelte 5

* Line component from svelte-chartjs

* Replaced all svelte-chartjs occurrences with custom wrapper

* Fix props mistake

* Fix illegal table structures

* self-closing-tags fix

* aria labels

* Fixed trivial warnings and errors

* @tanstack/svelte-table fix

* upgrade to vite 6

* svelte-kit sync before running svelte-check

* Remove on:clear which is actually on:removeAll and already handled by on:change

* fix worker tags not displaying in Autoscaling

* Try to fix svelte-kit sync not working during CI

* remove warnings

* Fix add flow page crashing

* access worldStore before assignment fix

* fix infinite recursions in App Editor

* Replaced JSON.stringify with proper deepEqual

* component mount api changed (no longer classes)

* fix ci errors

* Fix infinite loops in background runnable panel

* factored effect on deep equal logic in onObjChange

* fix "Add" not working in AgGrid Table

* Replaced legacy component.$set api

* Fix multiselect infinite value reaction

* Fix flow input fields resetting when opening their edit tab

* fix date input resetting when typing year

* Remove !p-0 affecting subgrid dotted borders

* fix missing debounceTemplate causing hundreds of updates

* Fix AgGrid action refreshes and disppearing

* resolve getItems generating random ids every rerun

* fix cannot access items before init

* fix sort lambda arguments being undefined

* Revert "Remove !p-0 affecting subgrid dotted borders"

This reverts commit c62809bb45d682a48376b071680645ed4e1c601b.

* fix input not updating in decision tree editor

* Update frontend/src/lib/components/schema/EditableSchemaWrapper.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* Re-added padding affecting subgrid dotted borders (#5479)

* remove !p-0 in preset components

* removed extra padding on accordion tabs subgrid

* Fix non-reactive SchemaForm

* dirty fix for the oneOf bug

* Fix warnings and update svelte-exmarkdown for svelte 5

* fix dnd not working

* don't mount component like objects

---------

Co-authored-by: Diego Imbert <diegoimbert@protonmail.com>
Co-authored-by: Diego Imbert <70353967+diegoimbert@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-04-02 21:33:50 +00:00

246 lines
7.4 KiB
Svelte

<script lang="ts">
import autosize from '$lib/autosize'
import { shouldDisplayPlaceholder } from '$lib/utils'
import { twMerge } from 'tailwind-merge'
import ArrayTypeNarrowing from '../ArrayTypeNarrowing.svelte'
import Label from '../Label.svelte'
import NumberTypeNarrowing from '../NumberTypeNarrowing.svelte'
import StringTypeNarrowing from '../StringTypeNarrowing.svelte'
import Tooltip from '../Tooltip.svelte'
import EditableSchemaForm from '../EditableSchemaForm.svelte'
import { deepEqual } from 'fast-equals'
import type { EnumType } from '$lib/common'
import type { SchemaProperty } from '$lib/common'
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import { createEventDispatcher, onMount } from 'svelte'
export let description: string = ''
export let format: string | undefined = undefined
export let contentEncoding: 'base64' | 'binary' | undefined = undefined
export let type: string | undefined = undefined
export let oneOf: SchemaProperty[] | undefined = undefined
export let pattern: undefined | string = undefined
export let enum_: EnumType = undefined
export let extra: Record<string, any> = {}
export let minW = true
export let customErrorMessage: string | undefined = undefined
export let title: string | undefined = undefined
export let placeholder: string | undefined = undefined
export let properties: Record<string, any> | undefined = undefined
export let isFlowInput: boolean = false
export let isAppInput: boolean = false
export let order: string[] | undefined = undefined
export let itemsType:
| {
type?: 'string' | 'number' | 'bytes' | 'object' | 'resource'
contentEncoding?: 'base64'
enum?: string[]
resourceType?: string
multiselect?: string[]
}
| undefined = undefined
const dispatch = createEventDispatcher()
let el: HTMLTextAreaElement | undefined = undefined
let oneOfSelected: string | undefined =
(oneOf && oneOf.length >= 2 && oneOf[0]['title']) || undefined
let oneOfSchemas: any[] | undefined = undefined
function updateOneOfSchemas(oneOf: SchemaProperty[] | undefined) {
if (oneOf && oneOf.length >= 2) {
oneOfSchemas = oneOf.map((obj) => {
return {
properties: obj.properties
? Object.fromEntries(Object.entries(obj.properties).filter(([k, v]) => k !== 'label'))
: {},
order: obj.order
}
})
}
}
$: updateOneOfSchemas(oneOf)
export function focus() {
el?.focus()
if (el) {
el.style.height = '5px'
el.style.height = el.scrollHeight + 50 + 'px'
}
}
function onKeyDown(e: KeyboardEvent) {
if ((e.ctrlKey || e.metaKey) && e.key == 'Enter') {
return
}
e.stopPropagation()
}
let schema = {
properties,
order
}
let initialExtra: any = structuredClone({ order: undefined, properties: undefined, ...extra })
let mounted = false
let firstOnContentChange = true
onMount(() => {
setTimeout(() => {
mounted = true
}, 500)
})
$: extra && mounted && onContentChange()
function onContentChange() {
if (firstOnContentChange) {
firstOnContentChange = false
return
}
if (!deepEqual(extra, initialExtra)) {
initialExtra = structuredClone(extra)
console.debug('property content updated')
dispatch('change')
}
}
$: (properties || order) && updateSchema()
function updateSchema() {
if (!deepEqual(schema.properties, properties) || !deepEqual(schema.order, order)) {
schema = {
properties,
order
}
console.debug('property schema updated')
dispatch('change')
}
}
</script>
<div class="flex flex-row items-center justify-between w-full gap-2">
<!-- svelte-ignore a11y-autofocus -->
<div class={twMerge('flex flex-col w-full', 'gap-4', minW ? 'min-w-[250px]' : '')}>
<slot name="typeeditor" />
<Label label="Description">
<textarea
use:autosize
rows="2"
bind:value={description}
on:keydown={onKeyDown}
placeholder="Field description"
></textarea>
</Label>
<Label label="Custom title" class="w-full">
<svelte:fragment slot="header">
<Tooltip light>Will be displayed in the UI instead of the field name.</Tooltip>
</svelte:fragment>
<input bind:value={title} on:keydown={onKeyDown} placeholder="Field title" />
</Label>
<Label label="Placeholder">
<svelte:fragment slot="header">
<Tooltip light>
Will be displayed in the input field when the field is empty. If not set, the default
value will be used. The placeholder is disabled depending on the field type, format, etc.
</Tooltip>
</svelte:fragment>
<textarea
placeholder="Enter a placeholder"
rows="1"
bind:value={placeholder}
on:change={() => dispatch('change')}
disabled={!shouldDisplayPlaceholder(type, format, enum_, contentEncoding, pattern, extra)}
></textarea>
</Label>
{#if type == 'array'}
<ArrayTypeNarrowing
originalType={extra['originalType']}
bind:itemsType
canEditResourceType={isFlowInput || isAppInput}
/>
{:else if type == 'string' || ['number', 'integer', 'object'].includes(type ?? '')}
<div>
<Label label="Field settings">
<div>
{#if type == 'string'}
<StringTypeNarrowing
bind:customErrorMessage
bind:format
bind:pattern
bind:enum_
bind:contentEncoding
bind:password={extra['password']}
bind:minRows={extra['minRows']}
bind:disableCreate={extra['disableCreate']}
bind:disableVariablePicker={extra['disableVariablePicker']}
bind:dateFormat={extra['dateFormat']}
bind:enumLabels={extra['enumLabels']}
originalType={extra['originalType']}
overrideAllowKindChange={isFlowInput || isAppInput}
/>
{:else if type == 'number' || type == 'integer'}
<NumberTypeNarrowing
bind:min={extra['min']}
bind:max={extra['max']}
bind:currency={extra['currency']}
bind:currencyLocale={extra['currencyLocale']}
/>
{:else if type == 'object' && oneOf && oneOf.length >= 2 && !isFlowInput && !isAppInput}
<ToggleButtonGroup bind:selected={oneOfSelected} class="mb-2" let:item>
{#each oneOf as obj}
<ToggleButton value={obj.title ?? ''} label={obj.title} {item} />
{/each}
</ToggleButtonGroup>
{#if oneOfSelected && oneOfSchemas}
{@const idx = oneOf.findIndex((obj) => obj.title === oneOfSelected)}
<div class="border">
<EditableSchemaForm
on:change
noPreview
bind:schema={oneOfSchemas[idx]}
uiOnly
jsonEnabled={false}
editTab="inputEditor"
/>
</div>
{/if}
{:else if type == 'object' && format?.startsWith('dynselect-')}
<div class="text-tertiary text-xs">No settings available for Dynamic Select</div>
{:else if type == 'object' && !format?.startsWith('resource-') && !isFlowInput && !isAppInput}
<div class="border">
<EditableSchemaForm
on:change
noPreview
bind:schema
uiOnly
jsonEnabled={false}
editTab="inputEditor"
/>
</div>
{:else}
<div class="text-tertiary text-xs">No settings available for this field type</div>
{/if}
</div>
</Label>
</div>
{/if}
<slot />
</div>
</div>
<style>
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none !important;
margin: 0;
}
</style>