mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 08:02:26 +00:00
fix reactivity for static inputs
This commit is contained in:
@@ -75,8 +75,6 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
{initialValue}
|
||||
{value}
|
||||
<div class="flex flex-row gap-x-1 w-full">
|
||||
<Select
|
||||
value={valueSelect}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { goto } from '$app/navigation'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import { configurationKeys } from '../../editor/component'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -35,37 +36,52 @@
|
||||
$componentControl[id] = controls
|
||||
}
|
||||
|
||||
let labelValue: string
|
||||
let color: ButtonType.Color
|
||||
let size: ButtonType.Size
|
||||
let runnableComponent: RunnableComponent
|
||||
let disabled: boolean | undefined = undefined
|
||||
let fillContainer: boolean | undefined = undefined
|
||||
let gotoUrl: string | undefined = undefined
|
||||
let gotoNewTab: boolean | undefined = undefined
|
||||
|
||||
// let labelValue: string
|
||||
// let color: ButtonType.Color
|
||||
// let size: ButtonType.Size
|
||||
// let disabled: boolean | undefined = undefined
|
||||
// let fillContainer: boolean | undefined = undefined
|
||||
// let gotoUrl: string | undefined = undefined
|
||||
// let gotoNewTab: boolean | undefined = undefined
|
||||
|
||||
let isLoading: boolean = false
|
||||
let ownClick: boolean = false
|
||||
let triggerOnAppLoad = false
|
||||
|
||||
let beforeIcon: undefined | string = undefined
|
||||
let afterIcon: undefined | string = undefined
|
||||
// let beforeIcon: undefined | string = undefined
|
||||
// let afterIcon: undefined | string = undefined
|
||||
|
||||
let resolvedConfig = {} as {
|
||||
beforeIcon?: string
|
||||
afterIcon?: string
|
||||
label?: string
|
||||
color?: ButtonType.Color
|
||||
size?: ButtonType.Size
|
||||
disabled?: boolean
|
||||
fillContainer?: boolean
|
||||
gotoUrl?: string
|
||||
gotoNewTab?: boolean
|
||||
triggerOnAppLoad?: boolean
|
||||
}
|
||||
|
||||
$: initializing = resolvedConfig?.label == undefined
|
||||
|
||||
let beforeIconComponent: any
|
||||
let afterIconComponent: any
|
||||
|
||||
$: beforeIcon && handleBeforeIcon()
|
||||
$: afterIcon && handleAfterIcon()
|
||||
$: resolvedConfig.beforeIcon && handleBeforeIcon()
|
||||
$: resolvedConfig.afterIcon && handleAfterIcon()
|
||||
|
||||
async function handleBeforeIcon() {
|
||||
if (beforeIcon) {
|
||||
beforeIconComponent = await loadIcon(beforeIcon)
|
||||
if (resolvedConfig.beforeIcon) {
|
||||
beforeIconComponent = await loadIcon(resolvedConfig.beforeIcon)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAfterIcon() {
|
||||
if (afterIcon) {
|
||||
afterIconComponent = await loadIcon(afterIcon)
|
||||
if (resolvedConfig.afterIcon) {
|
||||
afterIconComponent = await loadIcon(resolvedConfig.afterIcon)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +90,7 @@
|
||||
loading: false
|
||||
})
|
||||
|
||||
$: triggerOnAppLoad && runnableComponent?.runComponent()
|
||||
$: resolvedConfig.triggerOnAppLoad && runnableComponent?.runComponent()
|
||||
|
||||
$: if (outputs?.loading != undefined) {
|
||||
outputs.loading.set(false, true)
|
||||
@@ -107,11 +123,11 @@
|
||||
ownClick = true
|
||||
|
||||
if (!runnableComponent) {
|
||||
if (gotoUrl) {
|
||||
if (gotoNewTab) {
|
||||
window.open(gotoUrl, '_blank')
|
||||
if (resolvedConfig.gotoUrl) {
|
||||
if (resolvedConfig.gotoNewTab) {
|
||||
window.open(resolvedConfig.gotoUrl, '_blank')
|
||||
} else {
|
||||
goto(gotoUrl)
|
||||
goto(resolvedConfig.gotoUrl)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -120,28 +136,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue
|
||||
on:done={() => initializing && (initializing = false)}
|
||||
{id}
|
||||
input={configuration.label}
|
||||
bind:value={labelValue}
|
||||
/>
|
||||
<InputValue {id} input={configuration.goto} bind:value={gotoUrl} />
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
<InputValue {id} input={configuration.beforeIcon} bind:value={beforeIcon} />
|
||||
<InputValue {id} input={configuration.afterIcon} bind:value={afterIcon} />
|
||||
<InputValue {id} input={configuration.triggerOnAppLoad} bind:value={triggerOnAppLoad} />
|
||||
|
||||
<InputValue
|
||||
{id}
|
||||
input={configuration.disabled}
|
||||
extraContext={extraQueryParams}
|
||||
bind:value={disabled}
|
||||
bind:error={errors.disabled}
|
||||
/>
|
||||
<InputValue {id} input={configuration.fillContainer} bind:value={fillContainer} />
|
||||
<InputValue {id} input={configuration.gotoNewTab} bind:value={gotoNewTab} />
|
||||
{#each configurationKeys['buttoncomponent'] as key (key)}
|
||||
<InputValue {key} {id} input={configuration[key]} bind:value={resolvedConfig[key]} />
|
||||
{/each}
|
||||
|
||||
<RunnableWrapper
|
||||
{recomputeIds}
|
||||
@@ -150,8 +147,8 @@
|
||||
{id}
|
||||
{extraQueryParams}
|
||||
autoRefresh={false}
|
||||
goto={gotoUrl}
|
||||
{gotoNewTab}
|
||||
goto={resolvedConfig.gotoUrl}
|
||||
gotoNewTab={resolvedConfig.gotoNewTab}
|
||||
{render}
|
||||
>
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
@@ -162,25 +159,25 @@
|
||||
btnClasses={twMerge(
|
||||
$app.css?.['buttoncomponent']?.['button']?.class,
|
||||
customCss?.button.class,
|
||||
fillContainer ? 'w-full h-full' : ''
|
||||
resolvedConfig.fillContainer ? 'w-full h-full' : ''
|
||||
)}
|
||||
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button.style].join(';')}
|
||||
{disabled}
|
||||
disabled={resolvedConfig.disabled}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={handleClick}
|
||||
{size}
|
||||
{color}
|
||||
size={resolvedConfig.size}
|
||||
color={resolvedConfig.color}
|
||||
{loading}
|
||||
>
|
||||
<span class="truncate inline-flex gap-2 items-center">
|
||||
{#if beforeIcon && beforeIconComponent}
|
||||
{#if resolvedConfig.beforeIcon && beforeIconComponent}
|
||||
<svelte:component this={beforeIconComponent} size={14} />
|
||||
{/if}
|
||||
<div>{labelValue}</div>
|
||||
{#if afterIcon && afterIconComponent}
|
||||
<div>{resolvedConfig.label}</div>
|
||||
{#if resolvedConfig.afterIcon && afterIconComponent}
|
||||
<svelte:component this={afterIconComponent} size={14} />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
export let extraContext: Record<string, any> = {}
|
||||
export let key: string = ''
|
||||
|
||||
$: console.log(value)
|
||||
const { componentControl } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@@ -26,6 +27,7 @@
|
||||
|
||||
let lastInput = input ? JSON.parse(JSON.stringify(input)) : undefined
|
||||
|
||||
$: console.log(input)
|
||||
$: if (input && !deepEqual(input, lastInput)) {
|
||||
lastInput = JSON.parse(JSON.stringify(input))
|
||||
// Needed because of file uploads
|
||||
@@ -63,6 +65,7 @@
|
||||
debounce(async () => (value = await evalExpr(lastInput)))
|
||||
|
||||
async function handleConnection() {
|
||||
console.log('handleConnection', lastInput)
|
||||
if (lastInput.type === 'connected') {
|
||||
$worldStore?.connect<any>(lastInput, onValueChange, `${id}-${key}`)
|
||||
} else if (lastInput.type === 'static' || lastInput.type == 'template') {
|
||||
|
||||
@@ -1586,3 +1586,27 @@ Hello \${ctx.username}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const configurationKeys: Record<AppComponent['type'], string[]> = Object.fromEntries(
|
||||
Object.entries(components)
|
||||
.map(([k, v]) => [
|
||||
k,
|
||||
[
|
||||
...new Set(
|
||||
Object.entries(v.data.configuration).flatMap(([k, v]) => {
|
||||
if (!v.ctype) {
|
||||
return [k]
|
||||
} else if (v.ctype == 'oneOf') {
|
||||
return [
|
||||
k,
|
||||
...Object.values(v.configuration ?? {}).flatMap((v) => Object.keys(v ?? {}))
|
||||
]
|
||||
} else if (v.ctype == 'group') {
|
||||
return Object.keys(v.configuration ?? {})
|
||||
}
|
||||
})
|
||||
)
|
||||
]
|
||||
])
|
||||
.filter(([k, v]) => v != undefined)
|
||||
)
|
||||
|
||||
@@ -6,31 +6,16 @@
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
|
||||
import type {
|
||||
ConnectedAppInput,
|
||||
EvalAppInput,
|
||||
RowAppInput,
|
||||
StaticAppInput,
|
||||
UploadAppInput,
|
||||
UserAppInput
|
||||
} from '../../inputType'
|
||||
import ConnectedInputEditor from './inputEditor/ConnectedInputEditor.svelte'
|
||||
import EvalInputEditor from './inputEditor/EvalInputEditor.svelte'
|
||||
import RowInputEditor from './inputEditor/RowInputEditor.svelte'
|
||||
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
|
||||
import UploadInputEditor from './inputEditor/UploadInputEditor.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import type { AppViewerContext, RichConfiguration } from '../../types'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: (
|
||||
| StaticAppInput
|
||||
| ConnectedAppInput
|
||||
| UserAppInput
|
||||
| RowAppInput
|
||||
| EvalAppInput
|
||||
| UploadAppInput
|
||||
) & { tooltip?: string; onlyStatic?: boolean }
|
||||
export let componentInput: RichConfiguration
|
||||
export let key: string
|
||||
export let hasRows: boolean = false
|
||||
export let userInputEnabled: boolean = false
|
||||
@@ -40,99 +25,101 @@
|
||||
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
|
||||
</script>
|
||||
|
||||
{#if !(resourceOnly && (componentInput.fieldType !== 'object' || !componentInput.format?.startsWith('resource-')))}
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex justify-between items-end gap-1">
|
||||
<span class="text-sm font-semibold truncate">
|
||||
{shouldCapitalize ? capitalize(addWhitespaceBeforeCapitals(key)) : key}
|
||||
{#if componentInput.tooltip}
|
||||
<Tooltip>
|
||||
{componentInput.tooltip}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</span>
|
||||
{#if componentInput.ctype == undefined}
|
||||
{#if !(resourceOnly && (componentInput.fieldType !== 'object' || !componentInput.format?.startsWith('resource-')))}
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex justify-between items-end gap-1">
|
||||
<span class="text-sm font-semibold truncate">
|
||||
{shouldCapitalize ? capitalize(addWhitespaceBeforeCapitals(key)) : key}
|
||||
{#if componentInput.tooltip}
|
||||
<Tooltip>
|
||||
{componentInput.tooltip}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
|
||||
<Badge color="blue">
|
||||
{componentInput.fieldType === 'array' && componentInput.subFieldType
|
||||
? `${capitalize(fieldTypeToTsType(componentInput.subFieldType))}[]`
|
||||
: capitalize(fieldTypeToTsType(componentInput.fieldType))}
|
||||
</Badge>
|
||||
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
|
||||
<Badge color="blue">
|
||||
{componentInput.fieldType === 'array' && componentInput.subFieldType
|
||||
? `${capitalize(fieldTypeToTsType(componentInput.subFieldType))}[]`
|
||||
: capitalize(fieldTypeToTsType(componentInput.fieldType))}
|
||||
</Badge>
|
||||
|
||||
{#if !componentInput.onlyStatic && componentInput.type != 'eval'}
|
||||
<ToggleButtonGroup
|
||||
bind:selected={componentInput.type}
|
||||
on:selected={(e) => {
|
||||
if (e.detail == 'connected' && !componentInput['connection']) {
|
||||
$connectingInput = {
|
||||
opened: true,
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
{#if !componentInput.onlyStatic && componentInput.type != 'eval'}
|
||||
<ToggleButtonGroup
|
||||
bind:selected={componentInput.type}
|
||||
on:selected={(e) => {
|
||||
if (e.detail == 'connected' && !componentInput['connection']) {
|
||||
$connectingInput = {
|
||||
opened: true,
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Popover placement="bottom" notClickable disapperTimoout={0}>
|
||||
<ToggleButton
|
||||
position="left"
|
||||
value="static"
|
||||
startIcon={{ icon: faPen }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
/>
|
||||
<svelte:fragment slot="text">Static</svelte:fragment>
|
||||
</Popover>
|
||||
{#if userInputEnabled && !componentInput.format?.startsWith('resource-')}
|
||||
}}
|
||||
>
|
||||
<Popover placement="bottom" notClickable disapperTimoout={0}>
|
||||
<ToggleButton
|
||||
position="center"
|
||||
value="user"
|
||||
startIcon={{ icon: faUser }}
|
||||
position="left"
|
||||
value="static"
|
||||
startIcon={{ icon: faPen }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
/>
|
||||
<svelte:fragment slot="text">User Input</svelte:fragment>
|
||||
<svelte:fragment slot="text">Static</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if 'fileUpload' in componentInput}
|
||||
{#if userInputEnabled && !componentInput.format?.startsWith('resource-')}
|
||||
<Popover placement="bottom" notClickable disapperTimoout={0}>
|
||||
<ToggleButton
|
||||
position="center"
|
||||
value="user"
|
||||
startIcon={{ icon: faUser }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
/>
|
||||
<svelte:fragment slot="text">User Input</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if 'fileUpload' in componentInput}
|
||||
<Popover placement="bottom" notClickable disapperTimoout={0}>
|
||||
<ToggleButton
|
||||
position="center"
|
||||
value="upload"
|
||||
startIcon={{ icon: faUpload }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
/>
|
||||
<svelte:fragment slot="text">Upload</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
<Popover placement="bottom" notClickable disapperTimoout={0}>
|
||||
<ToggleButton
|
||||
position="center"
|
||||
value="upload"
|
||||
startIcon={{ icon: faUpload }}
|
||||
position="right"
|
||||
value="connected"
|
||||
startIcon={{ icon: faArrowRight }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
/>
|
||||
<svelte:fragment slot="text">Upload</svelte:fragment>
|
||||
<svelte:fragment slot="text">Connect</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
<Popover placement="bottom" notClickable disapperTimoout={0}>
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="connected"
|
||||
startIcon={{ icon: faArrowRight }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
/>
|
||||
<svelte:fragment slot="text">Connect</svelte:fragment>
|
||||
</Popover>
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if componentInput.type === 'connected'}
|
||||
<ConnectedInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'row'}
|
||||
<RowInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'static'}
|
||||
<StaticInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'eval'}
|
||||
<EvalInputEditor {hasRows} {id} bind:componentInput />
|
||||
{:else if componentInput.type === 'upload'}
|
||||
<UploadInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'user'}
|
||||
<span class="text-2xs italic text-gray-6f00">Field's value is set by the user</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if componentInput.type === 'connected'}
|
||||
<ConnectedInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'row'}
|
||||
<RowInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'static'}
|
||||
<StaticInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'eval'}
|
||||
<EvalInputEditor {hasRows} {id} bind:componentInput />
|
||||
{:else if componentInput.type === 'upload'}
|
||||
<UploadInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'user'}
|
||||
<span class="text-2xs italic text-gray-600">Field's value is set by the user</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
import InputsSpecEditor from './InputsSpecEditor.svelte'
|
||||
|
||||
export let id: string
|
||||
export let inputSpecs:
|
||||
| RichConfigurations
|
||||
| Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
export let inputSpecs: RichConfigurations
|
||||
export let userInputEnabled: boolean = false
|
||||
export let shouldCapitalize: boolean = true
|
||||
export let rowColumns = false
|
||||
@@ -20,11 +18,11 @@
|
||||
|
||||
{#if inputSpecs}
|
||||
<div class="w-full flex flex-col gap-4">
|
||||
{#each Object.entries(inputSpecs) as [k, v] (k)}
|
||||
{#if v.ctype == undefined}
|
||||
{#each Object.keys(inputSpecs) as k}
|
||||
{#if inputSpecs[k].ctype == undefined}
|
||||
<InputsSpecEditor
|
||||
key={k}
|
||||
bind:componentInput={v}
|
||||
bind:componentInput={inputSpecs[k]}
|
||||
{id}
|
||||
{userInputEnabled}
|
||||
{shouldCapitalize}
|
||||
|
||||
@@ -43,7 +43,11 @@ export type Configuration = GeneralAppInput &
|
||||
|
||||
export type RichConfiguration =
|
||||
| (Configuration & { ctype?: undefined })
|
||||
| { ctype: 'oneOf'; selected: string; configuration: Record<string, Configuration> }
|
||||
| {
|
||||
ctype: 'oneOf'
|
||||
selected: string
|
||||
configuration: Record<string, Record<string, Configuration>>
|
||||
}
|
||||
| { ctype: 'group'; title: string; configuration: Record<string, Configuration> }
|
||||
|
||||
export type RichConfigurations = Record<string, RichConfiguration>
|
||||
|
||||
@@ -122,8 +122,8 @@
|
||||
ts = date.toISOString()
|
||||
}
|
||||
const newJobs = await fetchJobs(maxTs, minTs ?? ts)
|
||||
if (newJobs && newJobs.length > 0) {
|
||||
const oldJobs = jobs.map((x) => x.id)
|
||||
if (newJobs && newJobs.length > 0 && jobs) {
|
||||
const oldJobs = jobs?.map((x) => x.id)
|
||||
jobs = newJobs.filter((x) => !oldJobs.includes(x.id)).concat(jobs)
|
||||
newJobs
|
||||
.filter((x) => oldJobs.includes(x.id))
|
||||
|
||||
Reference in New Issue
Block a user