mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 08:02:40 +00:00
feat: eval preview for apps
This commit is contained in:
@@ -7,7 +7,13 @@
|
||||
TemplateV2Input,
|
||||
UploadAppInput
|
||||
} from '../../inputType'
|
||||
import type { AppViewerContext, GroupContext, ListContext, RichConfiguration } from '../../types'
|
||||
import type {
|
||||
AppEditorContext,
|
||||
AppViewerContext,
|
||||
GroupContext,
|
||||
ListContext,
|
||||
RichConfiguration
|
||||
} from '../../types'
|
||||
import { accessPropertyByPath } from '../../utils'
|
||||
import { computeGlobalContext, eval_like } from './eval'
|
||||
import deepEqualWithOrderedArray from './deepEqualWithOrderedArray'
|
||||
@@ -21,8 +27,10 @@
|
||||
export let id: string | undefined = undefined
|
||||
export let error: string = ''
|
||||
export let key: string = ''
|
||||
export let field: string = key
|
||||
|
||||
const { componentControl, runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { evalPreview } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
const iterContext = getContext<ListContext>('ListWrapperContext')
|
||||
const rowContext = getContext<ListContext>('RowWrapperContext')
|
||||
@@ -138,6 +146,12 @@
|
||||
|
||||
const debounceEval = async () => {
|
||||
let nvalue = await evalExpr(lastInput as EvalAppInput)
|
||||
if (field) {
|
||||
evalPreview.update((x) => {
|
||||
x[`${id}.${field}`] = nvalue
|
||||
return x
|
||||
})
|
||||
}
|
||||
if (!deepEqual(nvalue, value)) {
|
||||
if (
|
||||
typeof nvalue == 'string' ||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
{#each Object.keys(configuration.configuration?.[choice] ?? {}) as nestedKey (nestedKey)}
|
||||
{#if resolvedConfig.configuration?.[choice] != undefined}
|
||||
<InputValue
|
||||
field={key}
|
||||
key={key + choice + nestedKey + extraKey}
|
||||
{id}
|
||||
input={configuration?.configuration?.[choice]?.[nestedKey]}
|
||||
@@ -30,5 +31,11 @@
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}
|
||||
<InputValue key={key + extraKey} {id} input={configuration} bind:value={resolvedConfig} />
|
||||
<InputValue
|
||||
field={key}
|
||||
key={key + extraKey}
|
||||
{id}
|
||||
input={configuration}
|
||||
bind:value={resolvedConfig}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
{@const property = customCss[key]}
|
||||
{#if property?.evalClass}
|
||||
<InputValue
|
||||
field={key}
|
||||
key={key + extraKey + 'css'}
|
||||
{id}
|
||||
bind:value={evalClassValue}
|
||||
@@ -65,6 +66,7 @@
|
||||
{@const property = componentStyle[key]}
|
||||
{#if property?.evalClass}
|
||||
<InputValue
|
||||
field={key}
|
||||
key={key + extraKey + 'cssGlobal'}
|
||||
{id}
|
||||
bind:value={evalClassValueGlobal}
|
||||
|
||||
@@ -162,6 +162,7 @@
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
yTop,
|
||||
evalPreview: writable({}),
|
||||
componentActive,
|
||||
dndItem: writable({}),
|
||||
refreshComponents: writable(undefined),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import InputsSpecEditor from '../settingsPanel/InputsSpecEditor.svelte'
|
||||
|
||||
export let evalClass: RichConfiguration
|
||||
export let key: string
|
||||
|
||||
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -26,6 +27,6 @@
|
||||
selectOptions={undefined}
|
||||
subFieldType={undefined}
|
||||
allowTypeChange={false}
|
||||
key=""
|
||||
{key}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -246,7 +246,7 @@
|
||||
/>
|
||||
|
||||
{#if value?.evalClass && dynamicClass}
|
||||
<CssEval bind:evalClass={value.evalClass} />
|
||||
<CssEval key={name} bind:evalClass={value.evalClass} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -221,14 +221,16 @@
|
||||
<Button size="xs" color="light" startIcon={{ icon: Plus }} on:click={() => addElementByType()}>
|
||||
Add
|
||||
</Button>
|
||||
|
||||
{#if subFieldType === 'table-column'}
|
||||
{#if subFieldType === 'table-column' || subFieldType == 'ag-grid'}
|
||||
<QuickAddColumn
|
||||
columns={componentInput.value?.map((item) => item.field)}
|
||||
on:add={({ detail }) => {
|
||||
if (!componentInput.value) componentInput.value = []
|
||||
|
||||
componentInput.value.push({ field: detail, headerName: detail, type: 'text' })
|
||||
if (subFieldType === 'table-column') {
|
||||
componentInput.value.push({ field: detail, headerName: detail, type: 'text' })
|
||||
} else if (subFieldType === 'ag-grid') {
|
||||
componentInput.value.push({ field: detail, headerName: detail, flex: 1 })
|
||||
}
|
||||
componentInput = componentInput
|
||||
|
||||
if (componentInput.value) {
|
||||
|
||||
@@ -263,6 +263,7 @@
|
||||
/>
|
||||
{:else if componentSettings.item.data.componentInput.type === 'evalv2' && component.componentInput !== undefined}
|
||||
<EvalV2InputEditor
|
||||
field="nonrunnable"
|
||||
bind:this={evalV2editor}
|
||||
id={component.id}
|
||||
bind:componentInput={componentSettings.item.data.componentInput}
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
{:else if componentInput?.type === 'eval'}
|
||||
<EvalInputEditor {id} bind:componentInput />
|
||||
{:else if componentInput?.type === 'evalv2'}
|
||||
<EvalV2InputEditor bind:this={evalV2editor} {id} bind:componentInput />
|
||||
<EvalV2InputEditor field={key} bind:this={evalV2editor} {id} bind:componentInput />
|
||||
{:else if componentInput?.type === 'upload'}
|
||||
<UploadInputEditor bind:componentInput {fileUpload} />
|
||||
{:else if componentInput?.type === 'user'}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
function updateRemainingColumns(result: any[], columns: string[]) {
|
||||
if (result?.length > 0) {
|
||||
if (Array.isArray(result) && result?.length > 0) {
|
||||
const allKeysSet: Set<string> = result.reduce((acc, obj) => {
|
||||
Object.keys(obj).forEach((key) => acc.add(key))
|
||||
return acc
|
||||
|
||||
+26
-1
@@ -2,17 +2,21 @@
|
||||
import type { EvalV2AppInput } from '../../../inputType'
|
||||
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '$lib/components/apps/types'
|
||||
import type { AppEditorContext, AppViewerContext } from '$lib/components/apps/types'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import { buildExtraLib } from '$lib/components/apps/utils'
|
||||
import { inferDeps } from '../../appUtilsInfer'
|
||||
import { Maximize2 } from 'lucide-svelte'
|
||||
import { Drawer } from '$lib/components/common'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { json } from 'svelte-highlight/languages'
|
||||
|
||||
export let componentInput: EvalV2AppInput | undefined
|
||||
export let id: string
|
||||
export let field: string
|
||||
|
||||
const { onchange, worldStore, state, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { evalPreview } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let editor: SimpleEditor
|
||||
export function setCode(code: string) {
|
||||
@@ -43,6 +47,7 @@
|
||||
}
|
||||
|
||||
let fullscreen = false
|
||||
let focus = false
|
||||
</script>
|
||||
|
||||
{#if componentInput?.type === 'evalv2'}
|
||||
@@ -75,6 +80,12 @@
|
||||
shouldBindKey={false}
|
||||
{extraLib}
|
||||
autoHeight
|
||||
on:focus={() => {
|
||||
focus = true
|
||||
}}
|
||||
on:blur={() => {
|
||||
focus = false
|
||||
}}
|
||||
on:change={async (e) => {
|
||||
if (onchange) {
|
||||
onchange()
|
||||
@@ -86,10 +97,24 @@
|
||||
class="border bg-surface absolute top-0.5 right-2 p-0.5"
|
||||
on:click={() => (fullscreen = true)}><Maximize2 size={12} /></button
|
||||
>
|
||||
{#if focus}
|
||||
<div class="relative w-full">
|
||||
<div
|
||||
class="p-1 !text-2xs absolute rounded-b border-b border-r border-l bg-surface w-full z-[5000] overflow-auto"
|
||||
>
|
||||
<Highlight
|
||||
language={json}
|
||||
code={JSON.stringify($evalPreview[`${id}.${field}`] ?? null, null, 4)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<pre class="text-small border px-2">{componentInput.expr}</pre>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- <div class="relative">
|
||||
<div class="absolute top-0 left-0 z-[1000]"> -->
|
||||
|
||||
{#if componentInput?.expr && componentInput.expr != '' && componentInput.expr
|
||||
.trim()
|
||||
|
||||
@@ -259,6 +259,7 @@ export type AppViewerContext = {
|
||||
|
||||
export type AppEditorContext = {
|
||||
yTop: Writable<number>
|
||||
evalPreview: Writable<Record<string, any>>
|
||||
componentActive: Writable<boolean>
|
||||
dndItem: Writable<Record<string, (x: number, y: number, topY: number) => void>>
|
||||
refreshComponents: Writable<(() => void) | undefined>
|
||||
|
||||
@@ -183,7 +183,7 @@ declare async function goto(path: string, newTab?: boolean): Promise<void>;
|
||||
* @param id component's id
|
||||
* @param index index of the tab to set
|
||||
*/
|
||||
declare function setTab(id: string, index: string): void;
|
||||
declare function setTab(id: string, index: number): void;
|
||||
|
||||
/** recompute a component's runnable or background runnable
|
||||
* @param id component's id
|
||||
@@ -247,7 +247,7 @@ declare const state: ${JSON.stringify(state)};
|
||||
declare const iter: {index: number, value: any};
|
||||
|
||||
/** The row within the context of a table */
|
||||
declare const row: Record<string, any>;
|
||||
declare const row: {index: number, value: Record<string, any>, disabled: boolean};
|
||||
|
||||
/** The group fields within the context of a container's group */
|
||||
declare const group: Record<string, any>;
|
||||
|
||||
Reference in New Issue
Block a user