fix app editor reactivity

This commit is contained in:
Ruben Fiszel
2025-06-15 03:59:37 +02:00
parent 499fe7e7fe
commit 41219e1f84
8 changed files with 333 additions and 244 deletions
@@ -17,22 +17,32 @@
import ResolveConfig from '../helpers/ResolveConfig.svelte'
import { userStore } from '$lib/stores'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'displaycomponent'> | undefined = undefined
export let render: boolean
export let configuration: RichConfigurations
interface Props {
id: string
componentInput: AppInput | undefined
initializing?: boolean | undefined
customCss?: ComponentCustomCSS<'displaycomponent'> | undefined
render: boolean
configuration: RichConfigurations
}
let {
id,
componentInput,
initializing = $bindable(undefined),
customCss = undefined,
render,
configuration
}: Props = $props()
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
const { app, worldStore, componentControl, workspace, appPath } =
getContext<AppViewerContext>('AppViewerContext')
let result: any = undefined
let result: any = $state(undefined)
const resolvedConfig = initConfig(
components['displaycomponent'].initialData.configuration,
configuration
const resolvedConfig = $state(
initConfig(components['displaycomponent'].initialData.configuration, configuration)
)
$componentControl[id] = {
@@ -46,7 +56,7 @@
loading: false
})
let css = initCss($app.css?.displaycomponent, customCss)
let css = $state(initCss($app.css?.displaycomponent, customCss))
</script>
{#each Object.keys(components['displaycomponent'].initialData.configuration) as key (key)}
@@ -1,11 +1,10 @@
<script lang="ts">
import { createEventDispatcher, getContext, onDestroy, tick } from 'svelte'
import { createEventDispatcher, getContext, tick, untrack } from 'svelte'
import { get } from 'svelte/store'
import type {
AppInput,
EvalAppInput,
EvalV2AppInput,
TemplateV2Input,
UploadAppInput,
UploadS3AppInput
} from '../../inputType'
@@ -18,7 +17,6 @@
} from '../../types'
import { accessPropertyByPath } from '../../utils'
import { computeGlobalContext, eval_like } from './eval'
import deepEqualWithOrderedArray from './deepEqualWithOrderedArray'
import { deepEqual } from 'fast-equals'
import { deepMergeWithPriority, isCodeInjection } from '$lib/utils'
import sum from 'hash-sum'
@@ -26,14 +24,27 @@
type T = string | number | boolean | Record<string | number, any> | undefined
export let input: AppInput | RichConfiguration
export let value: T
export let id: string | undefined = undefined
export let error: string = ''
export let key: string = ''
export let field: string = key
export let onDemandOnly: boolean = false
export let exportValueFunction: boolean = false
interface Props {
input: AppInput | RichConfiguration
value: T
id?: string | undefined
error?: string
key?: string
field?: string
onDemandOnly?: boolean
exportValueFunction?: boolean
}
let {
input,
value = $bindable(),
id = undefined,
error = $bindable(''),
key = '',
field = key,
onDemandOnly = false,
exportValueFunction = false
}: Props = $props()
const { componentControl, runnableComponents, recomputeAllContext } =
getContext<AppViewerContext>('AppViewerContext')
@@ -49,33 +60,6 @@
let groupStore = groupContext?.context
$: fullContext = {
iter: iterContext ? $iterContext : undefined,
row: rowContext ? $rowContext : undefined,
group: groupStore ? $groupStore : undefined
}
$: lastInput?.type == 'evalv2' &&
!onDemandOnly &&
(fullContext.iter != undefined ||
fullContext.row != undefined ||
fullContext.group != undefined) &&
lastInput.connections?.some(
(x) => x.componentId == 'row' || x.componentId == 'iter' || x.componentId == 'group'
) &&
debounceEval()
$: lastInput &&
lastInput.type == 'templatev2' &&
isCodeInjection(lastInput.eval) &&
(fullContext.iter != undefined ||
fullContext.row != undefined ||
fullContext.group != undefined) &&
lastInput.connections?.some(
(x) => x.componentId == 'row' || x.componentId == 'iter' || x.componentId == 'group'
) &&
debounceTemplate()
const dispatch = createEventDispatcher()
const dispatchIfMounted = createDispatcherIfMounted(dispatch)
@@ -85,30 +69,15 @@
dispatch('done')
}
let lastInput: AppInput | undefined = input ? JSON.parse(JSON.stringify(input)) : undefined
onDestroy(() => (lastInput = undefined))
$: if (input && !deepEqualWithOrderedArray(input, lastInput)) {
lastInput = JSON.parse(JSON.stringify(input))
// Needed because of file uploads
if (lastInput && input?.['value'] instanceof ArrayBuffer) {
// @ts-ignore
lastInput.value = input?.['value']
}
}
const { worldStore, state: stateStore, mode } = getContext<AppViewerContext>('AppViewerContext')
$: stateId = $worldStore?.stateId
let timeout: NodeJS.Timeout | undefined = undefined
let firstDebounce = true
const debounce_ms = 50
export async function computeExpr(args?: Record<string, any>) {
return await evalExpr(lastInput as EvalAppInput, args)
return await evalExpr(input as EvalAppInput, args)
}
function debounce(cb: () => Promise<void>) {
@@ -135,28 +104,19 @@
timeout = setTimeout(cb, 50)
}
$: lastInput && $worldStore && debounce(handleConnection)
const debounceTemplate = async () => {
let nvalue = await getValue(lastInput as EvalAppInput)
let nvalue = await getValue(input as EvalAppInput)
if (!deepEqual(nvalue, value)) {
// console.log('template')
value = nvalue
}
}
$: lastInput &&
lastInput.type == 'template' &&
isCodeInjection(lastInput.eval) &&
$stateId &&
$stateStore &&
debounce(debounceTemplate)
let lastExprHash: any = undefined
const debounceEval = async (s?: string) => {
let args = s == 'exprChanged' ? { file: { name: 'example.png' } } : undefined
let nvalue = await evalExpr(lastInput as EvalAppInput, args)
let nvalue = await evalExpr(input as EvalAppInput, args)
if (field) {
editorContext?.evalPreview.update((x) => {
@@ -175,16 +135,11 @@
}
}
$: lastInput && lastInput.type == 'eval' && $stateId && $stateStore && debounce2(debounceEval)
$: lastInput?.type == 'evalv2' && lastInput.expr && debounceEval('exprChanged')
$: lastInput?.type == 'templatev2' && lastInput.eval && debounceTemplate()
async function handleConnection() {
// console.log('handleCon')
if (lastInput?.type === 'connected') {
if (lastInput.connection) {
const { path, componentId } = lastInput.connection
if (input?.type === 'connected') {
if (input.connection) {
const { path, componentId } = input.connection
const [p] = path ? path.split('.')[0].split('[') : [undefined]
if (p) {
const skey = `${id}-${key}-${rowContext ? $rowContext.index : 0}-${
@@ -197,25 +152,24 @@
previousConnectedValue
)
} else {
console.debug('path was invalid for connection', lastInput.connection)
console.debug('path was invalid for connection', input.connection)
}
}
} else if (lastInput?.type === 'static' || lastInput?.type == 'template') {
} else if (input?.type === 'static' || input?.type == 'template') {
await debounceTemplate()
} else if (lastInput?.type == 'eval') {
value = await evalExpr(lastInput as EvalAppInput)
} else if (lastInput?.type == 'evalv2') {
} else if (input?.type == 'eval') {
value = await evalExpr(input as EvalAppInput)
} else if (input?.type == 'evalv2') {
// console.log('evalv2', onDemandOnly, field)
if (onDemandOnly && exportValueFunction) {
value = (args?: any) => {
return evalExpr(lastInput as EvalV2AppInput, args)
return evalExpr(input as EvalV2AppInput, args)
}
return
}
const skey = `${id}-${key}-${rowContext ? $rowContext.index : 0}-${
iterContext ? $iterContext.index : 0
}`
const input = lastInput as EvalV2AppInput
for (const c of input.connections ?? []) {
const previousValueKey = `${c.componentId}-${c.id}`
$worldStore?.connect<any>(
@@ -225,8 +179,7 @@
previousConnectedValues[previousValueKey]
)
}
} else if (lastInput?.type == 'templatev2') {
const input = lastInput as TemplateV2Input
} else if (input?.type == 'templatev2') {
const skey = `${id}-${key}-${rowContext ? $rowContext.index : 0}-${
iterContext ? $iterContext.index : 0
}`
@@ -239,10 +192,10 @@
previousConnectedValues[previousValueKey]
)
}
} else if (lastInput?.type == 'upload') {
value = (lastInput as UploadAppInput).value
} else if (lastInput?.type == 'uploadS3') {
value = (lastInput as UploadS3AppInput).value
} else if (input?.type == 'upload') {
value = (input as UploadAppInput).value
} else if (input?.type == 'uploadS3') {
value = (input as UploadS3AppInput).value
} else {
value = undefined
}
@@ -330,11 +283,8 @@
function onValueChange(newValue: any, force?: boolean): void {
if (iterContext && $iterContext.disabled) return
if (
lastInput?.type === 'connected' &&
((newValue !== undefined && newValue !== null) || force)
) {
const { connection } = lastInput
if (input?.type === 'connected' && ((newValue !== undefined && newValue !== null) || force)) {
const { connection } = input
if (!connection) {
// No connection
return
@@ -358,4 +308,61 @@
// TODO: handle disconnect
}
}
let fullContext = $derived({
iter: iterContext ? $iterContext : undefined,
row: rowContext ? $rowContext : undefined,
group: groupStore ? $groupStore : undefined
})
$effect.pre(() => {
input?.type == 'evalv2' &&
!onDemandOnly &&
(fullContext.iter != undefined ||
fullContext.row != undefined ||
fullContext.group != undefined) &&
input.connections?.some(
(x) => x.componentId == 'row' || x.componentId == 'iter' || x.componentId == 'group'
) &&
untrack(() => debounceEval())
})
$effect.pre(() => {
input &&
input.type == 'templatev2' &&
isCodeInjection(input.eval) &&
(fullContext.iter != undefined ||
fullContext.row != undefined ||
fullContext.group != undefined) &&
input.connections?.some(
(x) => x.componentId == 'row' || x.componentId == 'iter' || x.componentId == 'group'
) &&
untrack(() => debounceTemplate())
})
let stateId = $derived($worldStore?.stateId)
$effect.pre(() => {
input?.type == 'static' && input.value
input && $worldStore && untrack(() => debounce(handleConnection))
})
$effect.pre(() => {
input &&
input.type == 'template' &&
isCodeInjection(input.eval) &&
$stateId &&
$stateStore &&
untrack(() => debounce(debounceTemplate))
})
$effect.pre(() => {
input &&
input.type == 'eval' &&
$stateId &&
$stateStore &&
untrack(() => debounce2(debounceEval))
})
$effect.pre(() => {
input?.type == 'evalv2' && input.expr && untrack(() => debounceEval('exprChanged'))
})
$effect.pre(() => {
input?.type == 'templatev2' && input.eval && untrack(() => debounceTemplate())
})
</script>
<!-- {JSON.stringify(input)} -->
@@ -1,5 +1,5 @@
<script lang="ts">
import { getContext } from 'svelte'
import { getContext, untrack } from 'svelte'
import type { AppInput, InputConnectionEval } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext } from '../../types'
@@ -7,26 +7,41 @@
import InitializeComponent from './InitializeComponent.svelte'
import RefreshIndicator from './RefreshIndicator.svelte'
export let componentInput: AppInput
export let id: string
export let result: any
export let render: boolean
export let hasChildrens: boolean
export let noInitialize
interface Props {
componentInput: AppInput
id: string
result: any
render: boolean
hasChildrens: boolean
noInitialize: any
children?: import('svelte').Snippet
}
let {
componentInput,
id,
result = $bindable(),
render,
hasChildrens,
noInitialize,
children
}: Props = $props()
// Sync the result to the output
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
$: outputs = $worldStore?.outputsById?.[id] as {
result: Output<any>
}
let outputs = $derived(
$worldStore?.outputsById?.[id] as {
result: Output<any>
}
)
function setOutput(v: any) {
// console.log('setnr', id)
outputs?.result?.set(v, true)
}
let loading: boolean = false
let loading: boolean = $state(false)
let loadingStates: { [key: string]: boolean } = {}
let subscriptions: Array<() => void> = []
@@ -62,11 +77,15 @@
})
}
$: componentInput.type === 'evalv2' &&
componentInput.connections &&
builtSubscriptions(componentInput.connections)
$effect.pre(() => {
componentInput.type === 'evalv2' &&
componentInput.connections &&
untrack(() => builtSubscriptions(componentInput.connections))
})
$: result != undefined && outputs && setOutput(result)
$effect.pre(() => {
result != undefined && outputs && untrack(() => setOutput(result))
})
</script>
{#if !noInitialize}
@@ -79,7 +98,7 @@
{#if render || hasChildrens}
<div class={render ? 'h-full w-full' : 'invisible h-0 overflow-hidden'}>
<slot />
{@render children?.()}
<div class="flex absolute top-1 right-1 z-50 app-component-refresh-btn">
<RefreshIndicator {loading} />
</div>
@@ -100,7 +100,8 @@
migrateApp(app)
const appStore = writable<App>(app)
const stateApp = $state(app)
const appStore = writable<App>(stateApp)
const selectedComponent = writable<string[] | undefined>(undefined)
// $: selectedComponent.subscribe((s) => {
@@ -1,5 +1,5 @@
<script lang="ts">
import { getContext } from 'svelte'
import { getContext, untrack } from 'svelte'
import type { AppEditorContext, AppViewerContext } from '../types'
import { gridColumns, isFixed, toggleFixed } from '../gridUtils'
import { twMerge } from 'tailwind-merge'
@@ -29,7 +29,11 @@
import Popover from '$lib/components/Popover.svelte'
import type { Policy } from '$lib/gen'
export let policy: Policy
interface Props {
policy: Policy
}
let { policy }: Props = $props()
const {
selectedComponent,
@@ -46,13 +50,17 @@
const { history, componentActive } = getContext<AppEditorContext>('AppEditorContext')
let previousSelectedIds: string[] | undefined = undefined
$: if (!deepEqual(previousSelectedIds, $selectedComponent)) {
previousSelectedIds = $selectedComponent
$allIdsInPath = ($selectedComponent ?? [])
.flatMap((id) => dfs($app.grid, id, $app.subgrids ?? {}))
.filter((x) => x != undefined) as string[]
}
let previousSelectedIds: string[] | undefined = $state(undefined)
$effect(() => {
if (!deepEqual(previousSelectedIds, $selectedComponent)) {
untrack(() => {
previousSelectedIds = $selectedComponent
$allIdsInPath = ($selectedComponent ?? [])
.flatMap((id) => dfs($app.grid, id, $app.subgrids ?? {}))
.filter((x) => x != undefined) as string[]
})
}
})
function handleLock(id: string) {
const gridItem = findGridItem($app, id)
@@ -138,15 +146,17 @@
<span class="!text-2xs text-tertiary inline-flex gap-1 items-center"
><Loader2 size={10} class="animate-spin" /> {$bgRuns.length}
</span>
<span slot="text"
><div class="flex flex-col">
{#each $bgRuns as bgRun}
<div class="flex gap-2 items-center">
<div class="text-2xs text-tertiary">{bgRun}</div>
</div>
{/each}
</div></span
>
{#snippet text()}
<span
><div class="flex flex-col">
{#each $bgRuns as bgRun}
<div class="flex gap-2 items-center">
<div class="text-2xs text-tertiary">{bgRun}</div>
</div>
{/each}
</div></span
>
{/snippet}
</Popover>
{:else}
<span class="w-9"></span>
@@ -167,7 +177,7 @@
</div>
</div>
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
style={$app.css?.['app']?.['grid']?.style}
class={twMerge(
@@ -175,7 +185,7 @@
$app.css?.['app']?.['grid']?.class ?? '',
'wm-app-grid !static h-full w-full'
)}
on:pointerdown={() => {
onpointerdown={() => {
$selectedComponent = undefined
$focusedGrid = undefined
}}
@@ -194,10 +204,6 @@
$app.grid = e.detail
}}
root
let:dataItem
let:overlapped
let:moveMode
let:componentDraggedId
on:dropped={(e) => {
const { id, overlapped, x, y } = e.detail
@@ -224,55 +230,57 @@
}}
disableMove={!!$connectingInput.opened}
>
<ComponentWrapper
id={dataItem.id}
type={dataItem.data.type}
class={classNames(
'h-full w-full center-center outline outline-surface-secondary',
Boolean($selectedComponent?.includes(dataItem.id)) ? 'active-grid-item' : ''
)}
>
<GridEditorMenu
{#snippet children({ dataItem, overlapped, moveMode, componentDraggedId })}
<ComponentWrapper
id={dataItem.id}
on:expand={() => {
push(history, $app)
$selectedComponent = [dataItem.id]
expandGriditem($app.grid, dataItem.id, $breakpoint)
$app = $app
}}
on:lock={() => {
handleLock(dataItem.id)
}}
on:fillHeight={() => {
handleFillHeight(dataItem.id)
}}
locked={isFixed(dataItem)}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
type={dataItem.data.type}
class={classNames(
'h-full w-full center-center outline outline-surface-secondary',
Boolean($selectedComponent?.includes(dataItem.id)) ? 'active-grid-item' : ''
)}
>
<Component
render={true}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
on:lock={() => {
handleLock(dataItem.id)
}}
on:fillHeight={() => {
handleFillHeight(dataItem.id)
}}
<GridEditorMenu
id={dataItem.id}
on:expand={() => {
push(history, $app)
$selectedComponent = [dataItem.id]
expandGriditem($app.grid, dataItem.id, $breakpoint)
$app = $app
}}
{overlapped}
{moveMode}
{componentDraggedId}
/>
</GridEditorMenu>
</ComponentWrapper>
on:lock={() => {
handleLock(dataItem.id)
}}
on:fillHeight={() => {
handleFillHeight(dataItem.id)
}}
locked={isFixed(dataItem)}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
>
<Component
render={true}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
on:lock={() => {
handleLock(dataItem.id)
}}
on:fillHeight={() => {
handleFillHeight(dataItem.id)
}}
on:expand={() => {
push(history, $app)
$selectedComponent = [dataItem.id]
expandGriditem($app.grid, dataItem.id, $breakpoint)
$app = $app
}}
{overlapped}
{moveMode}
{componentDraggedId}
/>
</GridEditorMenu>
</ComponentWrapper>
{/snippet}
</Grid>
</div>
</div>
@@ -75,12 +75,23 @@
import type { AppComponent } from './components'
import { Button } from '$lib/components/common'
export let component: AppComponent
export let render: boolean
export let componentContainerHeight: number
export let errorHandledByComponent: boolean
export let inlineEditorOpened: boolean
export let initializing: boolean | undefined = undefined
interface Props {
component: AppComponent
render: boolean
componentContainerHeight: number
errorHandledByComponent: boolean
inlineEditorOpened: boolean
initializing?: boolean | undefined
}
let {
component,
render,
componentContainerHeight,
errorHandledByComponent = $bindable(),
inlineEditorOpened = $bindable(),
initializing = $bindable(undefined)
}: Props = $props()
</script>
<svelte:boundary>
@@ -797,7 +808,9 @@
>{error}</pre
>
<div class="flex mt-4">
<Button wrapperClasses="border rounded !border-gray-400" color="dark" on:click={reset}>reset</Button>
<Button wrapperClasses="border rounded !border-gray-400" color="dark" on:click={reset}
>reset</Button
>
</div>
</div>
{/snippet}
@@ -46,11 +46,19 @@
import { twMerge } from 'tailwind-merge'
import Popover from '$lib/components/Popover.svelte'
export let componentSettings: { item: GridItem; parent: string | undefined } | undefined =
undefined
export let onDelete: (() => void) | undefined = undefined
export let noGrid = false
export let duplicateMoveAllowed = true
interface Props {
componentSettings?: { item: GridItem; parent: string | undefined } | undefined
onDelete?: (() => void) | undefined
noGrid?: boolean
duplicateMoveAllowed?: boolean
}
let {
componentSettings = $bindable(undefined),
onDelete = undefined,
noGrid = false,
duplicateMoveAllowed = true
}: Props = $props()
const {
app,
@@ -108,10 +116,10 @@
let viewCssOptions = false
$: extraLib =
let extraLib = $derived(
(componentSettings?.item?.data?.componentInput?.type === 'template' ||
componentSettings?.item?.data?.componentInput?.type === 'templatev2') &&
$worldStore
$worldStore
? buildExtraLib(
$worldStore?.outputsById ?? {},
componentSettings?.item?.data?.id,
@@ -119,6 +127,7 @@
false
)
: undefined
)
// `
// /** The current's app state */
@@ -144,7 +153,7 @@
? isTriggerable(componentSettings?.item.data.type)
: false
let evalV2editor: EvalV2InputEditor | undefined = undefined
let evalV2editor: EvalV2InputEditor | undefined = $state(undefined)
function transformToFrontend() {
if (componentSettings?.item.data.componentInput) {
@@ -173,7 +182,7 @@
}
</script>
<svelte:window on:keydown={keydown} />
<svelte:window onkeydown={keydown} />
{#if componentSettings?.item?.id && isTableAction(componentSettings?.item?.id, $app)}
<div
@@ -181,9 +190,9 @@
>
<div class="flex flex-row items-center gap-2">
<Popover>
<svelte:fragment slot="text">
{#snippet text()}
<div class="flex flex-row gap-1"> Back to table component </div>
</svelte:fragment>
{/snippet}
<Button
iconOnly
startIcon={{
@@ -238,7 +247,7 @@
: 'Data source'}
id={'component-input'}
>
<svelte:fragment slot="action">
{#snippet action()}
<div class="flex flex-row gap-1 justify-center items-center">
<DocLink
docLink={'https://www.windmill.dev/docs/apps/app-runnable-panel#creating-a-runnable'}
@@ -252,7 +261,7 @@
{`${component.id}`}
</div>
</div>
</svelte:fragment>
{/snippet}
{#if componentSettings.item.data.componentInput}
<ComponentInputTypeEditor
@@ -314,7 +323,7 @@
id={component.id}
bind:componentInput={componentSettings.item.data.componentInput}
/>
<a class="text-2xs" on:click={transformToFrontend} href={undefined}>
<a class="text-2xs" onclick={transformToFrontend} href={undefined}>
transform to a frontend script
</a>
{:else if componentSettings.item.data.componentInput?.type === 'runnable' && component.componentInput !== undefined}
@@ -470,17 +479,19 @@
{#if Object.keys(ccomponents[component.type]?.customCss ?? {}).length > 0}
<PanelSection title="Styling">
<div slot="action" class="flex justify-end flex-wrap gap-1">
<Button
color="light"
size="xs"
variant="border"
startIcon={{ icon: ChevronLeft }}
on:click={() => secondaryMenuLeft.toggle(StylePanel, { type: 'style' })}
>
Show
</Button>
</div>
{#snippet action()}
<div class="flex justify-end flex-wrap gap-1">
<Button
color="light"
size="xs"
variant="border"
startIcon={{ icon: ChevronLeft }}
on:click={() => secondaryMenuLeft.toggle(StylePanel, { type: 'style' })}
>
Show
</Button>
</div>
{/snippet}
<div class="flex gap-2 items-center flex-wrap">
<div class="!text-2xs">Full height</div>
{#if componentSettings?.item?.[12]?.fullHeight !== undefined}
@@ -525,20 +536,22 @@
{#if duplicateMoveAllowed}
<PanelSection title="Copy/Move">
<div slot="action">
<Button
size="xs"
color="red"
variant="border"
on:click={removeGridElement}
shortCut={{
key: isMac() ? getModifierKey() + 'Del' : 'Del',
withoutModifier: true
}}
>
Delete
</Button>
</div>
{#snippet action()}
<div>
<Button
size="xs"
color="red"
variant="border"
on:click={removeGridElement}
shortCut={{
key: isMac() ? getModifierKey() + 'Del' : 'Del',
withoutModifier: true
}}
>
Delete
</Button>
</div>
{/snippet}
<div class="overflow-auto grid grid-cols-2 gap-1 text-tertiary">
<div>
@@ -7,21 +7,39 @@
import OneOfInputSpecsEditor from './OneOfInputSpecsEditor.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
export let id: string
export let inputSpecs: RichConfigurations
export let inputSpecsConfiguration: RichConfigurations | undefined = undefined
export let userInputEnabled: boolean = false
export let shouldCapitalize: boolean = true
export let resourceOnly = false
export let displayType = false
export let deletable = false
export let acceptSelf: boolean = false
export let recomputeOnInputChanged = true
export let showOnDemandOnlyToggle = false
export let securedContext = false
export let overridenByComponent: string[] = []
interface Props {
id: string
inputSpecs: RichConfigurations
inputSpecsConfiguration?: RichConfigurations | undefined
userInputEnabled?: boolean
shouldCapitalize?: boolean
resourceOnly?: boolean
displayType?: boolean
deletable?: boolean
acceptSelf?: boolean
recomputeOnInputChanged?: boolean
showOnDemandOnlyToggle?: boolean
securedContext?: boolean
overridenByComponent?: string[]
}
$: finalInputSpecsConfiguration = inputSpecsConfiguration ?? inputSpecs
let {
id,
inputSpecs = $bindable(),
inputSpecsConfiguration = undefined,
userInputEnabled = false,
shouldCapitalize = true,
resourceOnly = false,
displayType = false,
deletable = false,
acceptSelf = false,
recomputeOnInputChanged = true,
showOnDemandOnlyToggle = false,
securedContext = false,
overridenByComponent = []
}: Props = $props()
let finalInputSpecsConfiguration = $derived(inputSpecsConfiguration ?? inputSpecs)
const dispatch = createEventDispatcher()