mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 00:03:07 +00:00
fix app editor reactivity
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user