mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 16:02:24 +00:00
feat(frontend): refactor inline script (#1480)
* feat(frontend): refactor inline script wip * feat(frontend): refactor inline script wip * feat(frontend): clean up * feat(frontend): fix run configuration for triggerable * feat(frontend): fix autoRefresh issues * feat(frontend): fix autoRefresh issues * feat(frontend): modify actions + remove reactivity * feat(frontend): wip * feat(frontend): fix RecomputeAllComponnet * feat(frontend): add intermediate state when the language has not yet been selected * feat(frontend): fix connection * feat(frontend): fix wording * feat(frontend): fix bg trigers list * feat(frontend): restore * feat(frontend): restore * feat(frontend): add missing migration
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
import { classNames, defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { Bug } from 'lucide-svelte'
|
||||
import { createEventDispatcher, getContext, onDestroy } from 'svelte'
|
||||
import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte'
|
||||
import type { AppInputs, Runnable } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext, CancelablePromise, InlineScript } from '../../types'
|
||||
@@ -60,38 +60,6 @@
|
||||
|
||||
let donePromise: (() => void) | undefined = undefined
|
||||
|
||||
const cancellableRun: (inlineScript?: InlineScript) => CancelablePromise<void> = (
|
||||
inlineScript?: InlineScript
|
||||
) => {
|
||||
let rejectCb: (err: Error) => void
|
||||
let p: Partial<CancelablePromise<void>> = new Promise<void>((resolve, reject) => {
|
||||
rejectCb = reject
|
||||
donePromise = resolve
|
||||
executeComponent(true, inlineScript).catch(reject)
|
||||
})
|
||||
p.cancel = () => {
|
||||
testJobLoader?.cancelJob()
|
||||
loading = false
|
||||
rejectCb(new Error('Canceled'))
|
||||
}
|
||||
|
||||
return p as CancelablePromise<void>
|
||||
}
|
||||
|
||||
$runnableComponents[id] = {
|
||||
autoRefresh: autoRefresh && recomputableByRefreshButton,
|
||||
refreshOnStart,
|
||||
cb: cancellableRun
|
||||
}
|
||||
|
||||
if (!$initialized.initializedComponents.includes(id)) {
|
||||
$initialized.initializedComponents = [...$initialized.initializedComponents, id]
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
$initialized.initializedComponents = $initialized.initializedComponents.filter((c) => c !== id)
|
||||
})
|
||||
|
||||
$runnableComponents = $runnableComponents
|
||||
|
||||
let args: Record<string, any> | undefined = undefined
|
||||
@@ -189,8 +157,6 @@
|
||||
}
|
||||
|
||||
async function executeComponent(noToast = false, inlineScriptOverride?: InlineScript) {
|
||||
console.debug('execute', id)
|
||||
|
||||
if (runnable?.type === 'runnableByName' && runnable.inlineScript?.language === 'frontend') {
|
||||
loading = true
|
||||
try {
|
||||
@@ -234,6 +200,11 @@
|
||||
return
|
||||
}
|
||||
|
||||
if (!testJobLoader) {
|
||||
console.warn('No test job loader')
|
||||
return
|
||||
}
|
||||
|
||||
loading = true
|
||||
|
||||
try {
|
||||
@@ -359,6 +330,40 @@
|
||||
const event = e as unknown as PointerEvent
|
||||
!$connectingInput.opened && selectId(event, id, selectedComponent, $app)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const cancellableRun: (inlineScript?: InlineScript) => CancelablePromise<void> = (
|
||||
inlineScript?: InlineScript
|
||||
) => {
|
||||
let rejectCb: (err: Error) => void
|
||||
let p: Partial<CancelablePromise<void>> = new Promise<void>((resolve, reject) => {
|
||||
rejectCb = reject
|
||||
donePromise = resolve
|
||||
executeComponent(true, inlineScript).catch(reject)
|
||||
})
|
||||
p.cancel = () => {
|
||||
testJobLoader?.cancelJob()
|
||||
loading = false
|
||||
rejectCb(new Error('Canceled'))
|
||||
}
|
||||
|
||||
return p as CancelablePromise<void>
|
||||
}
|
||||
|
||||
$runnableComponents[id] = {
|
||||
autoRefresh: autoRefresh && recomputableByRefreshButton,
|
||||
refreshOnStart,
|
||||
cb: cancellableRun
|
||||
}
|
||||
|
||||
if (!$initialized.initializedComponents.includes(id)) {
|
||||
$initialized.initializedComponents = [...$initialized.initializedComponents, id]
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
$initialized.initializedComponents = $initialized.initializedComponents.filter((c) => c !== id)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#each Object.entries(fields ?? {}) as [key, v] (key)}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import InitializeComponent from './InitializeComponent.svelte'
|
||||
|
||||
export let componentInput: AppInput | undefined
|
||||
|
||||
export let id: string
|
||||
export let result: any = undefined
|
||||
export let initializing: boolean = true
|
||||
@@ -112,7 +113,11 @@
|
||||
bind:result
|
||||
runnable={componentInput.runnable}
|
||||
transformer={componentInput.transformer}
|
||||
{autoRefresh}
|
||||
autoRefresh={autoRefresh !== undefined
|
||||
? autoRefresh
|
||||
: componentInput?.type === 'runnable'
|
||||
? componentInput.autoRefresh
|
||||
: false}
|
||||
bind:recomputeOnInputChanged={componentInput.recomputeOnInputChanged}
|
||||
{id}
|
||||
{extraQueryParams}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { App, AppViewerContext } from '../types'
|
||||
import { allItems } from '../utils'
|
||||
@@ -8,7 +6,7 @@
|
||||
import PanelSection from './settingsPanel/common/PanelSection.svelte'
|
||||
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
|
||||
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
|
||||
import BackgroundScriptTriggerList from './settingsPanel/triggerLists/BackgroundScriptTriggerList.svelte'
|
||||
import BackgroundScriptSettings from './settingsPanel/script/BackgroundScriptSettings.svelte'
|
||||
|
||||
const { selectedComponent, app, stateId } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -54,6 +52,22 @@
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
$: {
|
||||
if (hiddenInlineScript && hiddenInlineScript?.script.recomputeOnInputChanged === undefined) {
|
||||
hiddenInlineScript.script.recomputeOnInputChanged = true
|
||||
}
|
||||
|
||||
//TODO: remove after migration is done
|
||||
if (
|
||||
hiddenInlineScript &&
|
||||
hiddenInlineScript?.script.doNotRecomputeOnInputChanged != undefined
|
||||
) {
|
||||
hiddenInlineScript.script.recomputeOnInputChanged =
|
||||
!hiddenInlineScript.script.doNotRecomputeOnInputChanged
|
||||
hiddenInlineScript.script.doNotRecomputeOnInputChanged = undefined
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if componentSettings}
|
||||
@@ -82,34 +96,12 @@
|
||||
/>
|
||||
{/key}
|
||||
{:else if hiddenInlineScript}
|
||||
<div class="min-h-full flex flex-col divide-y">
|
||||
<PanelSection title={`Configuration`}>
|
||||
<div class="flex items-center">
|
||||
<Toggle
|
||||
bind:checked={hiddenInlineScript.script.autoRefresh}
|
||||
options={{ right: 'Run on start and app refresh' }}
|
||||
/>
|
||||
<Tooltip>
|
||||
You may want to disable this so that the background script is only triggered by changes to
|
||||
other values or triggered by another computation on a button (See 'Recompute Others')
|
||||
</Tooltip>
|
||||
</div>
|
||||
</PanelSection>
|
||||
<BackgroundScriptSettings
|
||||
bind:script={hiddenInlineScript.script}
|
||||
id={`bg_${hiddenInlineScript.index}`}
|
||||
/>
|
||||
|
||||
<div class="p-2">
|
||||
{#if hiddenInlineScript.script.inlineScript}
|
||||
<BackgroundScriptTriggerList
|
||||
fields={hiddenInlineScript.script.fields}
|
||||
autoRefresh={hiddenInlineScript.script.autoRefresh}
|
||||
id={`bg_${hiddenInlineScript.index}`}
|
||||
bind:recomputeOnInputChanged={hiddenInlineScript.script.recomputeOnInputChanged}
|
||||
bind:doNotRecomputeOnInputChanged={hiddenInlineScript.script.doNotRecomputeOnInputChanged}
|
||||
bind:inlineScript={hiddenInlineScript.script.inlineScript}
|
||||
/>
|
||||
{:else}
|
||||
<span class="text-gray-600 text-xs">No script defined</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
{#if Object.keys(hiddenInlineScript.script.fields).length > 0}
|
||||
<PanelSection title={`Inputs`}>
|
||||
{#key $stateId}
|
||||
@@ -122,6 +114,13 @@
|
||||
{/key}
|
||||
</PanelSection>
|
||||
{/if}
|
||||
|
||||
{#if hiddenInlineScript.script.inlineScript?.language === 'frontend'}
|
||||
<PanelSection title={`Inputs`}>
|
||||
<div class="text-xs"> Frontend cannot have inputs </div>
|
||||
</PanelSection>
|
||||
{/if}
|
||||
|
||||
<div class="grow shrink" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { faClose } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { ResultAppInput } from '../../inputType'
|
||||
import type { AppEditorContext, AppViewerContext } from '../../types'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { clearResultAppInput } from '../../utils'
|
||||
import type { AppComponent } from '../component'
|
||||
import ComponentTriggerList from './triggerLists/ComponentTriggerList.svelte'
|
||||
import ComponentScriptSettings, { type ActionType } from './script/ComponentScriptSettings.svelte'
|
||||
import { ExternalLink, X } from 'lucide-svelte'
|
||||
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { selectedComponentInEditor } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
export let appInput: ResultAppInput
|
||||
export let appComponent: AppComponent
|
||||
|
||||
$: if (appInput.autoRefresh === undefined) {
|
||||
appInput.autoRefresh = true
|
||||
}
|
||||
|
||||
function detach() {
|
||||
if (appInput.runnable?.type === 'runnableByName' && appInput.runnable.inlineScript) {
|
||||
$app.unusedInlineScripts.push({
|
||||
@@ -41,97 +41,40 @@
|
||||
appInput.doNotRecomputeOnInputChanged = undefined
|
||||
}
|
||||
}
|
||||
|
||||
$: hasScript =
|
||||
appInput?.runnable?.type === 'runnableByPath' ||
|
||||
(appInput?.runnable?.type === 'runnableByName' && appInput.runnable?.inlineScript !== undefined)
|
||||
|
||||
function getActions(hasScript: boolean): ActionType[] {
|
||||
if (hasScript) {
|
||||
return [
|
||||
{
|
||||
label: 'Detach',
|
||||
icon: ExternalLink,
|
||||
color: 'light',
|
||||
callback: detach
|
||||
},
|
||||
{
|
||||
label: 'Clear',
|
||||
icon: X,
|
||||
color: 'red',
|
||||
callback: clear
|
||||
}
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
label: 'Clear',
|
||||
icon: X,
|
||||
color: 'red',
|
||||
callback: clear
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
$: actions = getActions(hasScript)
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between w-full items-center gap-1">
|
||||
<span class="text-xs font-semibold truncate">
|
||||
{#if appInput.runnable?.type === 'runnableByName'}
|
||||
{appInput.runnable.name}
|
||||
{:else if appInput.runnable?.type === 'runnableByPath'}
|
||||
{appInput.runnable.path}
|
||||
{/if}
|
||||
</span>
|
||||
<div class="flex gap-1 justify-center">
|
||||
{#if appInput.runnable?.type === 'runnableByName' && appInput.runnable.inlineScript}
|
||||
<Button size="xs" color="light" variant="border" on:click={detach}>
|
||||
Detach
|
||||
<Tooltip>
|
||||
Detaching an inline script keep it for later to be reused by another component
|
||||
</Tooltip>
|
||||
</Button>
|
||||
{/if}
|
||||
<Button size="xs" color="red" variant="border" startIcon={{ icon: faClose }} on:click={clear}>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="w-full"><div class="mx-auto w-0">↓</div></div>
|
||||
<div class="flex gap-1 justify-between items-center">
|
||||
<span class="text-xs font-semibold truncate">
|
||||
Transformer
|
||||
<Tooltip>
|
||||
A transformer is an optional frontend script that is executed right after the component's
|
||||
script whose purpose is to do lightweight transformation in the browser. It takes the
|
||||
previous computation's result as `result`
|
||||
</Tooltip>
|
||||
</span>
|
||||
<div class="flex gap-1">
|
||||
{#if !appInput.transformer}
|
||||
<div>
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
appInput.transformer = {
|
||||
language: 'frontend',
|
||||
content: 'return result'
|
||||
}
|
||||
$selectedComponentInEditor = appComponent.id + '_transformer'
|
||||
}}
|
||||
>
|
||||
Add Transformer
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Button
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="border"
|
||||
startIcon={{ icon: faClose }}
|
||||
on:click={() => {
|
||||
appInput.transformer = undefined
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
{/if}
|
||||
</div></div
|
||||
>
|
||||
</div>
|
||||
|
||||
{#if !['buttoncomponent', 'formbuttoncomponent', 'formcomponent'].includes(appComponent.type)}
|
||||
<div class="flex items-center">
|
||||
<Toggle
|
||||
size="xs"
|
||||
bind:checked={appInput.recomputeOnInputChanged}
|
||||
options={{ right: 'recompute on any input changes' }}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if appInput.runnable?.type === 'runnableByName'}
|
||||
<div>
|
||||
<ComponentTriggerList
|
||||
bind:runnable={appInput.runnable}
|
||||
{appComponent}
|
||||
fields={appInput.fields}
|
||||
recomputeOnInputChanged={appInput.recomputeOnInputChanged}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if appInput.runnable?.type === 'runnableByName' && !appInput.runnable.inlineScript}
|
||||
<span class="text-xs text-gray-500">
|
||||
Please configure the language in the inline script panel
|
||||
</span>
|
||||
{/if}
|
||||
<ComponentScriptSettings bind:appInput bind:appComponent {hasScript} {actions} />
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import ScriptSettingHeader from './shared/ScriptSettingHeader.svelte'
|
||||
import type { AppViewerContext, HiddenInlineScript } from '$lib/components/apps/types'
|
||||
import ScriptRunConfiguration from './shared/ScriptRunConfiguration.svelte'
|
||||
import BackgroundScriptTriggerBy from './shared/BackgroundScriptTriggerBy.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
export let script: HiddenInlineScript
|
||||
export let id: string
|
||||
|
||||
const { runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
|
||||
</script>
|
||||
|
||||
<div class={'border-y border-gray-200 divide-y'}>
|
||||
<ScriptSettingHeader name={script.name} />
|
||||
<ScriptRunConfiguration
|
||||
bind:autoRefresh={script.autoRefresh}
|
||||
bind:recomputeOnInputChanged={script.recomputeOnInputChanged}
|
||||
canConfigureRecomputeOnInputChanged={script.inlineScript?.language !== 'frontend'}
|
||||
on:updateAutoRefresh={() => {
|
||||
const autoRefresh = !script.autoRefresh
|
||||
if ($runnableComponents?.[id]?.autoRefresh !== autoRefresh && autoRefresh !== undefined) {
|
||||
$runnableComponents[id] = {
|
||||
...$runnableComponents[id],
|
||||
autoRefresh
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<BackgroundScriptTriggerBy bind:script recomputeOnInputChanged={script.recomputeOnInputChanged} />
|
||||
</div>
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<script lang="ts" context="module">
|
||||
export type ActionType = {
|
||||
label: string
|
||||
icon: any
|
||||
color: ButtonType.Color
|
||||
callback: () => void
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { ResultAppInput } from '$lib/components/apps/inputType'
|
||||
import type { ButtonType } from '$lib/components/common/button/model'
|
||||
import { isTriggerable } from './utils'
|
||||
|
||||
import type { AppComponent } from '../../component'
|
||||
import ScriptTransformer from './shared/ScriptTransformer.svelte'
|
||||
import ScriptRunConfiguration from './shared/ScriptRunConfiguration.svelte'
|
||||
import ComponentScriptTriggerBy from './shared/ComponentScriptTriggerBy.svelte'
|
||||
import ScriptSettingHeader from './shared/ScriptSettingHeader.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '$lib/components/apps/types'
|
||||
import ScriptSettingsSection from './shared/ScriptSettingsSection.svelte'
|
||||
|
||||
export let appInput: ResultAppInput
|
||||
export let appComponent: AppComponent
|
||||
export let hasScript: boolean
|
||||
|
||||
let runnable = appInput.runnable
|
||||
|
||||
const { runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
|
||||
export let actions: ActionType[] = []
|
||||
</script>
|
||||
|
||||
<div class={'border border-gray-200 divide-y'}>
|
||||
<ScriptSettingHeader
|
||||
name={runnable?.type === 'runnableByName'
|
||||
? runnable.name
|
||||
: runnable?.type === 'runnableByPath'
|
||||
? runnable.path
|
||||
: ''}
|
||||
{actions}
|
||||
/>
|
||||
|
||||
{#if hasScript}
|
||||
<ScriptTransformer bind:appInput bind:appComponent />
|
||||
<ScriptRunConfiguration
|
||||
canConfigureRecomputeOnInputChanged={!isTriggerable(appComponent.type)}
|
||||
canConfigureRunOnStart={!isTriggerable(appComponent.type)}
|
||||
bind:autoRefresh={appInput.autoRefresh}
|
||||
bind:recomputeOnInputChanged={appInput.recomputeOnInputChanged}
|
||||
on:updateAutoRefresh={() => {
|
||||
const autoRefresh = !(
|
||||
appComponent.componentInput?.type === 'runnable' &&
|
||||
appComponent?.componentInput?.autoRefresh
|
||||
)
|
||||
|
||||
if (
|
||||
appComponent.componentInput?.type === 'runnable' &&
|
||||
$runnableComponents?.[appComponent.id]?.autoRefresh !== autoRefresh &&
|
||||
!isTriggerable(appComponent.type) &&
|
||||
autoRefresh !== undefined
|
||||
) {
|
||||
$runnableComponents[appComponent.id] = {
|
||||
...$runnableComponents[appComponent.id],
|
||||
autoRefresh
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ComponentScriptTriggerBy {appComponent} {appInput} />
|
||||
{:else}
|
||||
<ScriptSettingsSection title="Language selection">
|
||||
<div class="text-xs"> Please configure the language in the inline script panel </div>
|
||||
</ScriptSettingsSection>
|
||||
{/if}
|
||||
</div>
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
<script lang="ts">
|
||||
import type { AppViewerContext, HiddenInlineScript } from '$lib/components/apps/types'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { Plus, X } from 'lucide-svelte'
|
||||
import ScriptSettingsSection from './ScriptSettingsSection.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { InputConnection } from '$lib/components/apps/inputType'
|
||||
import { getDependencies } from '../utils'
|
||||
|
||||
export let script: HiddenInlineScript
|
||||
export let recomputeOnInputChanged: boolean | undefined = undefined
|
||||
|
||||
$: isFrontend = script.inlineScript?.language === 'frontend'
|
||||
$: triggerEvents = script.autoRefresh ? ['start', 'refresh'] : []
|
||||
$: changeEvents = isFrontend
|
||||
? script.inlineScript?.refreshOn
|
||||
? script.inlineScript.refreshOn.map((x) => `${x.id} - ${x.key}`)
|
||||
: []
|
||||
: getDependencies(script.fields)
|
||||
|
||||
$: hasNoTriggers =
|
||||
triggerEvents.length === 0 && (changeEvents.length === 0 || !recomputeOnInputChanged)
|
||||
|
||||
const badgeClass = 'inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium border'
|
||||
const colors = {
|
||||
green: 'text-green-800 border-green-600 bg-green-100',
|
||||
indigo: 'text-indigo-800 border-indigo-600 bg-indigo-100',
|
||||
blue: 'text-blue-800 border-blue-600 bg-blue-100'
|
||||
}
|
||||
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
function applyConnection(connection: InputConnection) {
|
||||
const refresh = {
|
||||
id: connection.componentId,
|
||||
key: connection.path
|
||||
}
|
||||
|
||||
if (!script.inlineScript) {
|
||||
return
|
||||
}
|
||||
|
||||
if (script.inlineScript.refreshOn?.find((y) => y.id === refresh.id && y.key === refresh.key)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!script.inlineScript.refreshOn) {
|
||||
script.inlineScript.refreshOn = [refresh]
|
||||
} else {
|
||||
script.inlineScript.refreshOn.push(refresh)
|
||||
}
|
||||
|
||||
script.inlineScript = JSON.parse(JSON.stringify(script.inlineScript))
|
||||
$app = $app
|
||||
}
|
||||
</script>
|
||||
|
||||
<ScriptSettingsSection title="Triggers">
|
||||
{#if isFrontend}
|
||||
<div class="flex mb-4">
|
||||
<Button
|
||||
size="xs2"
|
||||
color="dark"
|
||||
on:click={() => {
|
||||
$connectingInput = {
|
||||
opened: true,
|
||||
input: undefined,
|
||||
hoveredComponent: undefined,
|
||||
onConnect: applyConnection
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
<Plus size={14} />
|
||||
|
||||
Add dependency
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if hasNoTriggers}
|
||||
<Alert type="warning" title="No triggers" size="xs">
|
||||
This script has no triggers. It will never run.
|
||||
</Alert>
|
||||
{:else}
|
||||
{#if triggerEvents.length > 0}
|
||||
<div class="text-xs font-semibold text-slate-800 mb-1">Events</div>
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each triggerEvents as triggerEvent}
|
||||
<span class={classNames(badgeClass, colors['green'])}>{triggerEvent}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if changeEvents.length > 0 && (recomputeOnInputChanged || isFrontend)}
|
||||
<div class="text-xs font-semibold text-slate-800 mb-1 mt-2">Change on value</div>
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each changeEvents as changeEvent}
|
||||
<span class={classNames(badgeClass, colors['blue'])}>
|
||||
{changeEvent}
|
||||
{#if isFrontend}
|
||||
<button
|
||||
class="bg-blue-300 ml-2 p-0.5 rounded-md hover:bg-blue-400 cursor-pointer"
|
||||
on:click={() => {
|
||||
if (script.inlineScript?.refreshOn) {
|
||||
script.inlineScript.refreshOn = script.inlineScript.refreshOn.filter(
|
||||
(x) => `${x.id} - ${x.key}` !== changeEvent
|
||||
)
|
||||
script.inlineScript = JSON.parse(JSON.stringify(script.inlineScript))
|
||||
}
|
||||
}}
|
||||
>
|
||||
<X size="14" />
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
<!-- delete button -->
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</ScriptSettingsSection>
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import type { ResultAppInput } from '$lib/components/apps/inputType'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import type { AppComponent } from '../../../component'
|
||||
import { getAllTriggerEvents, isTriggerable, getDependencies } from '../utils'
|
||||
import ScriptSettingsSection from './ScriptSettingsSection.svelte'
|
||||
|
||||
export let appComponent: AppComponent
|
||||
export let appInput: ResultAppInput
|
||||
|
||||
$: triggerEvents = getAllTriggerEvents(appComponent, appInput.autoRefresh)
|
||||
$: changeEvents = getDependencies(appInput.fields)
|
||||
$: hasNoTriggers =
|
||||
triggerEvents.length === 0 && (changeEvents.length === 0 || !appInput.recomputeOnInputChanged)
|
||||
|
||||
const badgeClass = 'inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium border'
|
||||
const colors = {
|
||||
green: 'text-green-800 border-green-600 bg-green-100',
|
||||
indigo: 'text-indigo-800 border-indigo-600 bg-indigo-100',
|
||||
blue: 'text-blue-800 border-blue-600 bg-blue-100'
|
||||
}
|
||||
</script>
|
||||
|
||||
<ScriptSettingsSection title="Triggers">
|
||||
{#if hasNoTriggers}
|
||||
<Alert type="warning" title="No triggers" size="xs">
|
||||
This script has no triggers. It will never run.
|
||||
</Alert>
|
||||
{:else}
|
||||
{#if triggerEvents.length > 0}
|
||||
<div class="text-xs font-semibold text-slate-800 mb-1">Events</div>
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each triggerEvents as triggerEvent}
|
||||
<span class={classNames(badgeClass, colors['green'])}>{triggerEvent}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if changeEvents.length > 0 && appInput.recomputeOnInputChanged && !isTriggerable(appComponent.type)}
|
||||
<div class="text-xs font-semibold text-slate-800 mb-1 mt-2">Change on value</div>
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each changeEvents as changeEvent}
|
||||
<span class={classNames(badgeClass, colors['blue'])}>{changeEvent}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</ScriptSettingsSection>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<script lang="ts">
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ScriptSettingsSection from './ScriptSettingsSection.svelte'
|
||||
|
||||
export let autoRefresh: boolean | undefined = false
|
||||
export let recomputeOnInputChanged: boolean | undefined = false
|
||||
export let canConfigureRecomputeOnInputChanged: boolean = true
|
||||
export let canConfigureRunOnStart: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
{#if canConfigureRecomputeOnInputChanged || canConfigureRunOnStart}
|
||||
<ScriptSettingsSection title="Run configuration">
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if autoRefresh !== undefined && canConfigureRunOnStart}
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex flex-row items-center gap-2 text-xs">
|
||||
Run on start and app refresh
|
||||
<Tooltip>
|
||||
You may want to disable this so that the background script is only triggered by
|
||||
changes to other values or triggered by another computation on a button (See
|
||||
'Recompute Others')
|
||||
</Tooltip>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={autoRefresh}
|
||||
class="!w-4 !h-4 !rounded-sm"
|
||||
on:click={() => {
|
||||
dispatch('updateAutoRefresh')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if recomputeOnInputChanged !== undefined && canConfigureRecomputeOnInputChanged}
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex flex-row items-center gap-2 text-xs">
|
||||
Recompute on any input changes
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={recomputeOnInputChanged}
|
||||
class="!w-4 !h-4 !rounded-sm"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</ScriptSettingsSection>
|
||||
{:else}
|
||||
<ScriptSettingsSection title="Run configuration">
|
||||
<div class="text-xs"> Triggerable component runs only when an interaction happens. </div>
|
||||
</ScriptSettingsSection>
|
||||
{/if}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { FunctionSquare } from 'lucide-svelte'
|
||||
import type { ActionType } from '../ComponentScriptSettings.svelte'
|
||||
import ScriptSettingsActions from './ScriptSettingsActions.svelte'
|
||||
|
||||
export let name: string
|
||||
export let actions: ActionType[] = []
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row p-2 border-gray-200 justify-between bg-blue-50/60">
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<FunctionSquare size={16} color="blue" />
|
||||
<span class="text-xs font-semibold truncate">
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ScriptSettingsActions {actions} />
|
||||
</div>
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import type { ButtonType } from '$lib/components/common/button/model'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
|
||||
type ActionType = {
|
||||
label: string
|
||||
icon: any
|
||||
color: ButtonType.Color
|
||||
callback: () => void
|
||||
}
|
||||
|
||||
export let actions: ActionType[] = []
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row gap-1 justify-end">
|
||||
{#each actions as action, index (index)}
|
||||
<Popover notClickable disappearTimeout={0}>
|
||||
<svelte:fragment slot="text">
|
||||
{action.label}
|
||||
</svelte:fragment>
|
||||
<Button color={action.color} on:click={action.callback} size="xs2" variant="border">
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
{#if action.icon}
|
||||
<svelte:component this={action.icon} size={12} />
|
||||
{/if}
|
||||
</div>
|
||||
</Button>
|
||||
</Popover>
|
||||
{/each}
|
||||
</div>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
|
||||
export let title: string
|
||||
export let tooltip: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<div class="p-2">
|
||||
<div class="mb-2 text-sm font-semibold justify-between flex flex-row items-center">
|
||||
{title}
|
||||
|
||||
{#if tooltip}
|
||||
<Tooltip>
|
||||
{tooltip}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import type { ResultAppInput } from '$lib/components/apps/inputType'
|
||||
import type { AppEditorContext } from '$lib/components/apps/types'
|
||||
import { getContext } from 'svelte'
|
||||
import ScriptSettingsSection from './ScriptSettingsSection.svelte'
|
||||
import type { AppComponent } from '../../../component'
|
||||
import { Button } from '$lib/components/common'
|
||||
|
||||
const { selectedComponentInEditor } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
export let appInput: ResultAppInput
|
||||
export let appComponent: AppComponent
|
||||
|
||||
$: checked = Boolean(appInput.transformer)
|
||||
</script>
|
||||
|
||||
<ScriptSettingsSection
|
||||
title="Transformer"
|
||||
tooltip={"A transformer is an optional frontend script that is executed right after the component's script whose purpose is to do lightweight transformation in the browser. It takes the previous computation's result as `result`"}
|
||||
>
|
||||
<div class="flex gap-1 justify-between items-center">
|
||||
<Button
|
||||
size="xs"
|
||||
color={checked ? 'red' : 'light'}
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
if (appInput.transformer) {
|
||||
appInput.transformer = undefined
|
||||
} else {
|
||||
appInput.transformer = {
|
||||
language: 'frontend',
|
||||
content: 'return result'
|
||||
}
|
||||
$selectedComponentInEditor = appComponent.id + '_transformer'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{checked ? 'Remove' : 'Add a transformer'}
|
||||
</Button>
|
||||
</div>
|
||||
</ScriptSettingsSection>
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { Runnable } from '$lib/components/apps/inputType'
|
||||
import type { AppComponent } from '../../component'
|
||||
import type {
|
||||
StaticAppInput,
|
||||
ConnectedAppInput,
|
||||
RowAppInput,
|
||||
UserAppInput
|
||||
} from '$lib/components/apps/inputType'
|
||||
|
||||
export function getDependencies(
|
||||
fields:
|
||||
| Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
| undefined
|
||||
): string[] {
|
||||
let dependencies: string[] = []
|
||||
|
||||
if (!fields) return dependencies
|
||||
|
||||
Object.values(fields).forEach((field) => {
|
||||
if (field.type === 'connected' && dependencies && field.connection) {
|
||||
dependencies.push(`${field.connection.componentId} - ${field.connection.path}`)
|
||||
}
|
||||
})
|
||||
return dependencies
|
||||
}
|
||||
|
||||
export function isFrontend(runnable: Runnable): boolean {
|
||||
return runnable?.type === 'runnableByName' && runnable.inlineScript?.language === 'frontend'
|
||||
}
|
||||
|
||||
export function isTriggerable(componentType: string): boolean {
|
||||
return ['buttoncomponent', 'formbuttoncomponent', 'formcomponent'].includes(componentType)
|
||||
}
|
||||
|
||||
export function isTriggerOnAppLoad(appComponent: AppComponent): boolean {
|
||||
return Boolean(
|
||||
appComponent?.configuration?.triggerOnAppLoad != undefined &&
|
||||
appComponent.configuration.triggerOnAppLoad.type == 'static' &&
|
||||
appComponent.configuration.triggerOnAppLoad.value
|
||||
)
|
||||
}
|
||||
|
||||
export function getAllTriggerEvents(
|
||||
appComponent: AppComponent,
|
||||
autoRefresh: boolean | undefined
|
||||
): string[] {
|
||||
const events: string[] = []
|
||||
const triggerOnAppLoad = isTriggerOnAppLoad(appComponent)
|
||||
const isTriggerableComponent = isTriggerable(appComponent.type)
|
||||
|
||||
if (isTriggerableComponent) {
|
||||
events.push('click')
|
||||
|
||||
if (triggerOnAppLoad) {
|
||||
events.push('start')
|
||||
}
|
||||
} else if (autoRefresh) {
|
||||
events.push('start')
|
||||
events.push('refresh')
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type {
|
||||
ConnectedAppInput,
|
||||
RowAppInput,
|
||||
StaticAppInput,
|
||||
UserAppInput
|
||||
} from '$lib/components/apps/inputType'
|
||||
import type { InlineScript } from '$lib/components/apps/types'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
|
||||
import TriggerBadgesList from './TriggerBadgesList.svelte'
|
||||
import { getDependencies } from './triggerListUtils'
|
||||
|
||||
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
export let autoRefresh: boolean = false
|
||||
export let id: string
|
||||
export let inlineScript: InlineScript
|
||||
export let recomputeOnInputChanged: boolean | undefined = true
|
||||
export let doNotRecomputeOnInputChanged: undefined | boolean = undefined
|
||||
|
||||
//TODO: remove after migration is done
|
||||
$: {
|
||||
if (doNotRecomputeOnInputChanged != undefined) {
|
||||
recomputeOnInputChanged = !doNotRecomputeOnInputChanged
|
||||
doNotRecomputeOnInputChanged = undefined
|
||||
}
|
||||
|
||||
if (recomputeOnInputChanged == undefined) {
|
||||
recomputeOnInputChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
$: dependencies = getDependencies(fields)
|
||||
</script>
|
||||
|
||||
{#if inlineScript.language !== 'frontend'}
|
||||
<div class="flex items-center px-1">
|
||||
<Toggle
|
||||
size="xs"
|
||||
bind:checked={recomputeOnInputChanged}
|
||||
options={{ right: 'recompute on any input changes' }}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<TriggerBadgesList
|
||||
bind:inlineScript
|
||||
{id}
|
||||
inputDependencies={dependencies}
|
||||
onLoad={autoRefresh}
|
||||
{recomputeOnInputChanged}
|
||||
/>
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type {
|
||||
ConnectedAppInput,
|
||||
RowAppInput,
|
||||
RunnableByName,
|
||||
StaticAppInput,
|
||||
UserAppInput
|
||||
} from '$lib/components/apps/inputType'
|
||||
import type { AppViewerContext } from '$lib/components/apps/types'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppComponent } from '../../component'
|
||||
import TriggerBadgesList from './TriggerBadgesList.svelte'
|
||||
import { getDependencies } from './triggerListUtils'
|
||||
|
||||
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
export let appComponent: AppComponent
|
||||
export let runnable: RunnableByName
|
||||
export let recomputeOnInputChanged: boolean = false
|
||||
|
||||
const onClick = ['buttoncomponent', 'formbuttoncomponent', 'formcomponent'].includes(
|
||||
appComponent.type
|
||||
)
|
||||
|
||||
$: onLoad =
|
||||
!onClick ||
|
||||
(appComponent?.configuration?.triggerOnAppLoad != undefined &&
|
||||
appComponent.configuration.triggerOnAppLoad.type == 'static' &&
|
||||
appComponent.configuration.triggerOnAppLoad.value)
|
||||
</script>
|
||||
|
||||
<TriggerBadgesList
|
||||
inputDependencies={onClick ? [] : getDependencies(fields)}
|
||||
bind:inlineScript={runnable.inlineScript}
|
||||
{onLoad}
|
||||
{recomputeOnInputChanged}
|
||||
id={$selectedComponent?.[0]}
|
||||
shouldHideAddDependencyButton={[
|
||||
'buttoncomponent',
|
||||
'formcomponent',
|
||||
'formbuttoncomponent'
|
||||
].includes(appComponent.type)}
|
||||
{onClick}
|
||||
/>
|
||||
-174
@@ -1,174 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { AppViewerContext, InlineScript } from '$lib/components/apps/types'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { Plus, X } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { getAllRecomputeIdsForComponent } from '../../appUtils'
|
||||
|
||||
export let inputDependencies: string[] = []
|
||||
export let inlineScript: InlineScript | undefined
|
||||
export let onClick: boolean = false
|
||||
export let onLoad: boolean = false
|
||||
export let id: string | undefined = undefined
|
||||
export let recomputeOnInputChanged: boolean = false
|
||||
export let shouldHideAddDependencyButton: boolean = false
|
||||
|
||||
const colors = {
|
||||
green: 'text-green-800 border-green-600 bg-green-100',
|
||||
indigo: 'text-indigo-800 border-indigo-600 bg-indigo-100',
|
||||
blue: 'text-blue-800 border-blue-600 bg-blue-100'
|
||||
}
|
||||
|
||||
let badgeClass = 'inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium border'
|
||||
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let addingDependency: boolean = false
|
||||
|
||||
function applyConnection() {
|
||||
if (!$connectingInput.opened && $connectingInput.input !== undefined && addingDependency) {
|
||||
if ($connectingInput.input.connection) {
|
||||
const x = {
|
||||
id: $connectingInput.input.connection.componentId,
|
||||
key: $connectingInput.input.connection.path
|
||||
}
|
||||
|
||||
if (!inlineScript) {
|
||||
return
|
||||
}
|
||||
|
||||
if (inlineScript.refreshOn?.find((y) => y.id === x.id && y.key === x.key)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!inlineScript.refreshOn) {
|
||||
inlineScript.refreshOn = [x]
|
||||
} else {
|
||||
inlineScript.refreshOn.push(x)
|
||||
}
|
||||
|
||||
inlineScript = JSON.parse(JSON.stringify(inlineScript))
|
||||
|
||||
addingDependency = false
|
||||
}
|
||||
|
||||
$connectingInput = {
|
||||
opened: false,
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
$app = $app
|
||||
}
|
||||
}
|
||||
|
||||
$: frontendDependencies =
|
||||
inlineScript?.language === 'frontend'
|
||||
? inlineScript?.refreshOn?.map((x) => `${x.id} - ${x.key}`) ?? []
|
||||
: undefined
|
||||
|
||||
$: $connectingInput && applyConnection()
|
||||
|
||||
$: recomputedBadges = getAllRecomputeIdsForComponent($app, id)
|
||||
|
||||
function deleteDep(index: number) {
|
||||
if (inlineScript) {
|
||||
inlineScript.refreshOn?.splice(index, 1)
|
||||
}
|
||||
inlineScript = inlineScript
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex w-full flex-col items-start gap-2 mt-2 mb-1">
|
||||
{#if recomputedBadges.length === 0 && !onLoad && !onClick && inputDependencies?.length === 0 && !frontendDependencies}
|
||||
<p class="text-xs font-semibold text-slate-800">
|
||||
This script has no triggers. It will never run.
|
||||
</p>
|
||||
{:else}
|
||||
<div class="text-sm font-semibold text-gray-800">Triggered by</div>
|
||||
|
||||
{#if onLoad || onClick}
|
||||
<div class="w-full">
|
||||
<div class="text-xs font-semibold text-slate-800 mb-1">Events</div>
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#if onLoad}
|
||||
<span class={classNames(badgeClass, colors['green'])}>Start</span>
|
||||
<span class={classNames(badgeClass, colors['green'])}>Refresh</span>
|
||||
{/if}
|
||||
{#if onClick}
|
||||
<span class={classNames(badgeClass, colors['green'])}>Click</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if inputDependencies.length > 0 && (recomputeOnInputChanged ?? true)}
|
||||
<div class="w-full">
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<div class="text-xs font-semibold text-slate-800">Change on values</div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each inputDependencies as label}
|
||||
<span class={classNames(badgeClass, colors['blue'])}>
|
||||
{label}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if recomputedBadges?.length > 0}
|
||||
<div class="w-full">
|
||||
<div class="text-xs font-semibold text-slate-800 mb-1">Computation of</div>
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each recomputedBadges as badge}
|
||||
<span class={classNames(badgeClass, colors['indigo'])}>{badge}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if frontendDependencies && recomputeOnInputChanged}
|
||||
<div class="w-full">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-xs font-semibold text-slate-800 mb-1">Change on values</div>
|
||||
{#if inlineScript?.language === 'frontend' && !shouldHideAddDependencyButton}
|
||||
<Button
|
||||
variant="border"
|
||||
size="xs"
|
||||
color="light"
|
||||
btnClasses="!px-1 !py-0.5"
|
||||
on:click={() => {
|
||||
addingDependency = true
|
||||
$connectingInput = {
|
||||
opened: true,
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
Add dependency
|
||||
<Plus size={14} />
|
||||
</div>
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 flex-wrap mt-2">
|
||||
{#each frontendDependencies as label, index}
|
||||
<span class={classNames(badgeClass, colors['blue'])}>
|
||||
{label}
|
||||
<button
|
||||
on:click={() => deleteDep(index)}
|
||||
class="bg-blue-300 cursor-pointer hover:bg-blue-400 ml-1 rounded-md"
|
||||
>
|
||||
<X size={18} class="p-0.5" />
|
||||
</button>
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import type {
|
||||
StaticAppInput,
|
||||
ConnectedAppInput,
|
||||
RowAppInput,
|
||||
UserAppInput
|
||||
} from '$lib/components/apps/inputType'
|
||||
|
||||
export function getDependencies(
|
||||
fields:
|
||||
| Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
| undefined
|
||||
): string[] {
|
||||
let dependencies: string[] = []
|
||||
|
||||
if (!fields) return dependencies
|
||||
|
||||
Object.values(fields).forEach((field) => {
|
||||
if (field.type === 'connected' && dependencies && field.connection) {
|
||||
dependencies.push(`${field.connection.componentId} - ${field.connection.path}`)
|
||||
}
|
||||
})
|
||||
return dependencies
|
||||
}
|
||||
@@ -92,6 +92,7 @@ export type ResultInput = {
|
||||
// kept for migration purposes
|
||||
doNotRecomputeOnInputChanged?: boolean
|
||||
recomputeOnInputChanged?: boolean
|
||||
autoRefresh?: boolean
|
||||
}
|
||||
|
||||
type AppInputSpec<T extends InputType, U, V extends InputType = never> = (
|
||||
|
||||
@@ -103,6 +103,17 @@ export type InlineScript = {
|
||||
|
||||
export type AppCssItemName = 'viewer' | 'grid' | AppComponent['type']
|
||||
|
||||
export type HiddenInlineScript = {
|
||||
name: string
|
||||
inlineScript: InlineScript | undefined
|
||||
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
autoRefresh?: boolean
|
||||
//deprecated and to be removed after migration
|
||||
doNotRecomputeOnInputChanged?: boolean
|
||||
recomputeOnInputChanged?: boolean
|
||||
noBackendValue?: any
|
||||
}
|
||||
|
||||
export type App = {
|
||||
grid: GridItem[]
|
||||
fullscreen: boolean
|
||||
@@ -111,16 +122,7 @@ export type App = {
|
||||
name: string
|
||||
inlineScript: InlineScript
|
||||
}>
|
||||
hiddenInlineScripts: Array<{
|
||||
name: string
|
||||
inlineScript: InlineScript | undefined
|
||||
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
autoRefresh?: boolean
|
||||
//deprecated and to be removed after migration
|
||||
doNotRecomputeOnInputChanged?: boolean
|
||||
recomputeOnInputChanged?: boolean
|
||||
noBackendValue?: any
|
||||
}>
|
||||
hiddenInlineScripts: Array<HiddenInlineScript>
|
||||
css?: Partial<Record<AppCssItemName, Record<string, ComponentCssProperty>>>
|
||||
subgrids?: Record<string, GridItem[]>
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
</MenuButton>
|
||||
<Transition
|
||||
show={open}
|
||||
enter="transition ease-out duration-75"
|
||||
enter="transition ease-out duration-[25ms]"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leave="transition ease-in duration-[25ms]"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user