mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 08:01:35 +00:00
feat(frontend): overhaul the whole flow UX
This commit is contained in:
@@ -65,6 +65,7 @@
|
||||
}
|
||||
|
||||
$: {
|
||||
error = ''
|
||||
if (inputCat === 'object') {
|
||||
evalValueToRaw()
|
||||
validateInput(pattern, value)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
faCode,
|
||||
faCube,
|
||||
faDollarSign,
|
||||
faEye,
|
||||
faRotate,
|
||||
faRotateLeft,
|
||||
faWallet
|
||||
@@ -232,7 +233,7 @@
|
||||
startIcon={{ icon: faDollarSign }}
|
||||
{iconOnly}
|
||||
>
|
||||
+Contextual Variable
|
||||
+Context Var
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
@@ -245,7 +246,7 @@
|
||||
startIcon={{ icon: faWallet }}
|
||||
{iconOnly}
|
||||
>
|
||||
+Variable
|
||||
+Var
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
@@ -270,9 +271,9 @@
|
||||
color="light"
|
||||
on:click={scriptPicker.openDrawer}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faCode }}
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
View Script
|
||||
Script
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -286,7 +287,7 @@
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faRotateLeft }}
|
||||
>
|
||||
Reset content
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -300,7 +301,7 @@
|
||||
startIcon={{ icon: faRotate }}
|
||||
>
|
||||
{#if !iconOnly}
|
||||
Reload assistants
|
||||
Assistant
|
||||
{/if}
|
||||
<span class="ml-1 -my-1">
|
||||
{#if lang == 'deno'}
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
| undefined = undefined
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="inline-flex flex-row items-center">
|
||||
<span class="font-semibold">
|
||||
{label}
|
||||
</span>
|
||||
<Required {required} class="!ml-0 pr-1" />
|
||||
|
||||
<span class="text-sm italic text-indigo-800">
|
||||
({type ?? 'any'}{contentEncoding && contentEncoding != ''
|
||||
? `, encoding: ${contentEncoding}`
|
||||
@@ -22,5 +24,4 @@
|
||||
? ` of ${itemsType?.type}s`
|
||||
: ''})</span
|
||||
>
|
||||
<Required {required} class="!ml-0" />
|
||||
</div>
|
||||
|
||||
@@ -10,17 +10,15 @@
|
||||
import type { PropPickerWrapperContext } from './flows/propPicker/PropPickerWrapper.svelte'
|
||||
import { codeToStaticTemplate, getDefaultExpr, isCodeInjection } from './flows/utils'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { Button } from './common'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faChain } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Button, ToggleButton, ToggleButtonGroup } from './common'
|
||||
import { faCode } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
export let schema: Schema
|
||||
export let arg: InputTransform | any
|
||||
export let argName: string
|
||||
export let extraLib: string = 'missing extraLib'
|
||||
export let inputCheck: boolean = true
|
||||
export let importPath: string | undefined = undefined
|
||||
export let previousModuleId: string | undefined
|
||||
|
||||
export let monaco: SimpleEditor | undefined = undefined
|
||||
let argInput: ArgInput | undefined = undefined
|
||||
@@ -44,7 +42,7 @@
|
||||
}
|
||||
|
||||
if (isCodeInjection(rawValue)) {
|
||||
arg.expr = getDefaultExpr(importPath, argName, `\`${rawValue}\``)
|
||||
arg.expr = getDefaultExpr(argName, previousModuleId, `\`${rawValue}\``)
|
||||
arg.type = 'javascript'
|
||||
propertyType = 'static'
|
||||
} else {
|
||||
@@ -66,14 +64,12 @@
|
||||
arg.value = `\$\{${rawValue}}`
|
||||
setPropertyType(arg.value)
|
||||
} else {
|
||||
arg.expr = getDefaultExpr(importPath, undefined, rawValue)
|
||||
arg.expr = getDefaultExpr(undefined, previousModuleId, rawValue)
|
||||
arg.type = 'javascript'
|
||||
propertyType = 'javascript'
|
||||
}
|
||||
}
|
||||
|
||||
$: checked = propertyType == 'javascript'
|
||||
|
||||
function onFocus() {
|
||||
if (isStaticTemplate(inputCat)) {
|
||||
focusProp(argName, 'append', (path) => {
|
||||
@@ -96,68 +92,85 @@
|
||||
</script>
|
||||
|
||||
{#if arg != undefined}
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<div class="flex items-center">
|
||||
<FieldHeader
|
||||
label={argName}
|
||||
format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
required={schema.required.includes(argName)}
|
||||
type={schema.properties[argName].type}
|
||||
itemsType={schema.properties[argName].items}
|
||||
/>
|
||||
<div class="flex flex-row justify-between items-center gap-x-1 mb-2">
|
||||
<div class="flex flex-row gap-4">
|
||||
<div class="flex items-center w-full">
|
||||
<FieldHeader
|
||||
label={argName}
|
||||
format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
required={schema.required.includes(argName)}
|
||||
type={schema.properties[argName].type}
|
||||
itemsType={schema.properties[argName].items}
|
||||
/>
|
||||
|
||||
{#if !checked && arg.type === 'javascript'}
|
||||
<span class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 py-0.5 rounded ml-2">
|
||||
{'${...}'}
|
||||
</span>
|
||||
{/if}
|
||||
{#if propertyType == 'static' && arg.type === 'javascript'}
|
||||
<span
|
||||
class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 py-0.5 rounded ml-2"
|
||||
>
|
||||
{'${...}'}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<ToggleButtonGroup
|
||||
bind:selected={propertyType}
|
||||
on:selected={(e) => {
|
||||
const staticTemplate = isStaticTemplate(inputCat)
|
||||
if (e.detail === 'javascript') {
|
||||
arg.expr = getDefaultExpr(
|
||||
argName,
|
||||
previousModuleId,
|
||||
staticTemplate ? `\`${arg.value ?? ''}\`` : arg.value
|
||||
)
|
||||
|
||||
arg.value = undefined
|
||||
propertyType = 'javascript'
|
||||
arg.type = 'javascript'
|
||||
} else {
|
||||
arg.value = staticTemplate ? codeToStaticTemplate(arg.expr) : undefined
|
||||
arg.expr = undefined
|
||||
propertyType = 'static'
|
||||
if (!isStaticTemplate) {
|
||||
arg.type = 'static'
|
||||
} else {
|
||||
setPropertyType(arg.value)
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<ToggleButton position="left" value="static" size="xs">Template</ToggleButton>
|
||||
{:else}
|
||||
<ToggleButton position="left" value="static" size="xs">Static</ToggleButton>
|
||||
{/if}
|
||||
|
||||
<ToggleButton position="right" value="javascript" startIcon={{ icon: faCode }} size="xs">
|
||||
Dynamic (JS)
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<!-- <InputsSpecEditor bind:appInputTransform={componentInputSpecs[inputSpecKey]} /> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row space-x-4 items-center">
|
||||
<Toggle
|
||||
bind:checked
|
||||
options={{
|
||||
right: 'Raw Javascript Editor'
|
||||
}}
|
||||
on:change={(e) => {
|
||||
const type = e.detail ? 'javascript' : 'static'
|
||||
const staticTemplate = isStaticTemplate(inputCat)
|
||||
if (type === 'javascript') {
|
||||
arg.expr = getDefaultExpr(
|
||||
importPath,
|
||||
argName,
|
||||
staticTemplate ? `\`${arg.value ?? ''}\`` : arg.value
|
||||
)
|
||||
|
||||
arg.value = undefined
|
||||
propertyType = 'javascript'
|
||||
} else {
|
||||
arg.value = staticTemplate ? codeToStaticTemplate(arg.expr) : undefined
|
||||
|
||||
arg.expr = undefined
|
||||
propertyType = 'static'
|
||||
}
|
||||
|
||||
arg.type = type
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="blue"
|
||||
size="sm"
|
||||
on:click={() => {
|
||||
focusProp(argName, 'connect', (path) => {
|
||||
connectProperty(path)
|
||||
return false
|
||||
})
|
||||
}}>Connect →</Button
|
||||
>
|
||||
<div>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="blue"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
focusProp(argName, 'connect', (path) => {
|
||||
connectProperty(path)
|
||||
return false
|
||||
})
|
||||
}}>Connect →</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-w-xs" />
|
||||
|
||||
{#if propertyType === undefined || !checked}
|
||||
{#if propertyType === undefined || propertyType == 'static'}
|
||||
<ArgInput
|
||||
bind:this={argInput}
|
||||
on:focus={onFocus}
|
||||
@@ -183,30 +196,27 @@
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else if checked}
|
||||
{#if arg.expr != undefined}
|
||||
<div class="border rounded p-2 mt-2 border-gray-300">
|
||||
<SimpleEditor
|
||||
bind:this={monaco}
|
||||
bind:code={arg.expr}
|
||||
{extraLib}
|
||||
lang="javascript"
|
||||
class="few-lines-editor"
|
||||
extraLibPath="file:///node_modules/@types/windmill@{importPath}/index.d.ts"
|
||||
shouldBindKey={false}
|
||||
on:focus={() => {
|
||||
focusProp(argName, 'insert', (path) => {
|
||||
monaco?.insertAtCursor(path)
|
||||
return false
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<DynamicInputHelpBox {importPath} />
|
||||
<div class="mb-2" />
|
||||
{/if}
|
||||
{:else if arg.expr != undefined}
|
||||
<div class="border rounded p-2 mt-2 border-gray-300">
|
||||
<SimpleEditor
|
||||
bind:this={monaco}
|
||||
bind:code={arg.expr}
|
||||
{extraLib}
|
||||
lang="javascript"
|
||||
shouldBindKey={false}
|
||||
on:focus={() => {
|
||||
focusProp(argName, 'insert', (path) => {
|
||||
monaco?.insertAtCursor(path)
|
||||
return false
|
||||
})
|
||||
}}
|
||||
autoHeight
|
||||
/>
|
||||
</div>
|
||||
<DynamicInputHelpBox />
|
||||
<div class="mb-2" />
|
||||
{:else}
|
||||
<p>Not recognized arg type {arg.type}</p>
|
||||
Not recognized input type {argName}
|
||||
{/if}
|
||||
{:else}
|
||||
<p class="text-sm text-gray-700">Arg at {argName} is undefined</p>
|
||||
|
||||
@@ -14,10 +14,8 @@
|
||||
export let editableSchema = false
|
||||
export let isValid: boolean = true
|
||||
export let extraLib: string = 'missing extraLib'
|
||||
export let importPath: string | undefined = undefined
|
||||
export let autofocus = false
|
||||
export let animateNew = false
|
||||
|
||||
export let previousModuleId: string | undefined = undefined
|
||||
let clazz: string = ''
|
||||
export { clazz as class }
|
||||
|
||||
@@ -46,12 +44,12 @@
|
||||
<div transition:slide|local>
|
||||
{#if inputTransform}
|
||||
<InputTransformForm
|
||||
{previousModuleId}
|
||||
bind:arg={args[argName]}
|
||||
bind:schema
|
||||
bind:argName
|
||||
bind:inputCheck={inputCheck[argName]}
|
||||
bind:extraLib
|
||||
bind:importPath
|
||||
/>
|
||||
{:else}
|
||||
<ArgInput
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { CompletedJob, Job, JobService } from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { emptySchema, scriptLangToEditorLang } from '$lib/utils'
|
||||
import { faPlay, faRotateRight } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faPlay } from '@fortawesome/free-solid-svg-icons'
|
||||
import Editor from './Editor.svelte'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import type { Preview } from '$lib/gen/models/Preview'
|
||||
@@ -16,7 +16,6 @@
|
||||
import { onMount } from 'svelte'
|
||||
import { Button, Kbd } from './common'
|
||||
import SplitPanesWrapper from './splitPanes/SplitPanesWrapper.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import WindmillIcon from './icons/WindmillIcon.svelte'
|
||||
|
||||
// Exported
|
||||
@@ -165,12 +164,7 @@
|
||||
<Pane size={40} minSize={10}>
|
||||
<Splitpanes horizontal>
|
||||
<Pane size={33}>
|
||||
<div class="w-full bg-gray-100 px-2 text-sm"
|
||||
>Preview <Tooltip>
|
||||
To recompute the input schema press <Kbd>Ctrl/Cmd</Kbd> + <Kbd>S</Kbd> or move the focus
|
||||
outside of the text editor
|
||||
</Tooltip></div
|
||||
>
|
||||
<div class="w-full bg-gray-100 px-2 text-sm">Preview</div>
|
||||
<div class="px-2">
|
||||
<div class="break-words relative font-sans">
|
||||
<SchemaForm {schema} bind:args bind:isValid />
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
export let formatAction: (() => void) | undefined = undefined
|
||||
export let automaticLayout = true
|
||||
export let extraLib: string = ''
|
||||
export let extraLibPath: string = ''
|
||||
export let shouldBindKey: boolean = true
|
||||
export let autoHeight = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let width = 0
|
||||
async function loadMonaco() {
|
||||
model = monaco.editor.createModel(code, lang, monaco.Uri.parse(uri))
|
||||
|
||||
@@ -115,6 +116,24 @@
|
||||
dispatch('change')
|
||||
})
|
||||
|
||||
if (autoHeight) {
|
||||
let ignoreEvent = false
|
||||
const updateHeight = () => {
|
||||
const contentHeight = Math.min(1000, editor.getContentHeight())
|
||||
if (divEl) {
|
||||
divEl.style.height = `${contentHeight}px`
|
||||
}
|
||||
try {
|
||||
ignoreEvent = true
|
||||
editor.layout({ width, height: contentHeight })
|
||||
} finally {
|
||||
ignoreEvent = false
|
||||
}
|
||||
}
|
||||
editor.onDidContentSizeChange(updateHeight)
|
||||
updateHeight()
|
||||
}
|
||||
|
||||
editor.onDidFocusEditorText(() => {
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () {
|
||||
code = getCode()
|
||||
@@ -133,13 +152,15 @@
|
||||
dispatch('blur')
|
||||
})
|
||||
|
||||
if (lang == 'javascript' && extraLib != '' && extraLibPath != '') {
|
||||
if (lang == 'javascript' && extraLib != '') {
|
||||
monaco.languages.typescript.javascriptDefaults.setExtraLibs([
|
||||
{
|
||||
content: extraLib,
|
||||
filePath: extraLibPath
|
||||
filePath: 'windmill.d.ts'
|
||||
}
|
||||
])
|
||||
} else {
|
||||
monaco.languages.typescript.javascriptDefaults.setExtraLibs([])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +178,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={divEl} class={$$props.class} />
|
||||
<div bind:this={divEl} class={$$props.class} bind:clientWidth={width} />
|
||||
|
||||
<style>
|
||||
.editor {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
value === $selected
|
||||
$selected?.startsWith(value)
|
||||
? 'border-b-2 border-gray-600 text-gray-800 '
|
||||
: 'hover:border-b-2 hover:border-gray-300 text-gray-500',
|
||||
'py-1 px-4 cursor-pointer transition-all ease-linear font-medium',
|
||||
|
||||
@@ -6,13 +6,15 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte'
|
||||
import { createEventDispatcher, setContext } from 'svelte'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export let selected: any
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const selectedContent = writable(selected)
|
||||
|
||||
$: $selectedContent && dispatch('selected', $selectedContent)
|
||||
setContext<ToggleButtonContext>('ToggleButtonGroup', {
|
||||
selected: selectedContent,
|
||||
select: (value: any) => {
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import Icon from 'svelte-awesome'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
export let importPath: string | undefined = undefined
|
||||
|
||||
$: opened = false
|
||||
</script>
|
||||
|
||||
@@ -27,37 +25,14 @@
|
||||
role="alert"
|
||||
id="dynamic-input-help-box"
|
||||
>
|
||||
<p class="font-bold">Dynamic arg help</p>
|
||||
<p>
|
||||
When a field is using the "Raw Javascript Editor", its value is computed dynamically as the
|
||||
evaluation of its corresponding javascript snippet.
|
||||
</p>
|
||||
That snippet can be a single line:
|
||||
<pre><code>last_result.myarg</code></pre>
|
||||
or a multiline:
|
||||
<pre
|
||||
><code
|
||||
>let x = 5;
|
||||
x + 2</code
|
||||
></pre
|
||||
>
|
||||
the last line must always be the final expression.
|
||||
<p>
|
||||
If it is multiline, the statement before the final expression <b
|
||||
>MUST END WITH ; and a newline</b
|
||||
>
|
||||
</p>
|
||||
The snippet can also be a string template:
|
||||
<code
|
||||
>`Hello ${params.name}, all your base ${previous_result.base_name} belong
|
||||
to us`</code
|
||||
>
|
||||
However, the last line must always be the final expression.
|
||||
<p>
|
||||
The snippet can use any javascript primitives, and the following flow specific objects and
|
||||
functions:
|
||||
</p>
|
||||
<ul class="ml-4">
|
||||
<li><b>result.id</b>: the result of step at id 'id'</li>
|
||||
<li><b>{'results.<id>'}</b>: the result of step at id 'id'</li>
|
||||
<li><b>flow_input</b>: the object containing the flow input arguments</li>
|
||||
<li><b>params</b>: the object containing the current step static values</li>
|
||||
<li>
|
||||
@@ -68,11 +43,5 @@ x + 2</code
|
||||
<b>resource(path)</b>: the function returning the resource at a given path as an object
|
||||
</li>
|
||||
</ul>
|
||||
<p>To re-enable editor assistance, import the helper functions types using:</p>
|
||||
<code>
|
||||
{`import { results, flow_input, variable, resource, params } from 'windmill${
|
||||
importPath ? `@${importPath}` : ''
|
||||
}'`}
|
||||
</code>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
const { previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
let editor: SimpleEditor | undefined = undefined
|
||||
|
||||
$: pickableProperties = getStepPropPicker(
|
||||
$: stepPropPicker = getStepPropPicker(
|
||||
$flowStateStore,
|
||||
parentModule,
|
||||
previousModule,
|
||||
@@ -31,7 +31,7 @@
|
||||
$previewArgs,
|
||||
false,
|
||||
true
|
||||
).pickableProperties
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col">
|
||||
@@ -47,7 +47,7 @@
|
||||
<div class="border w-full">
|
||||
<PropPickerWrapper
|
||||
notSelectable
|
||||
{pickableProperties}
|
||||
pickableProperties={stepPropPicker.pickableProperties}
|
||||
on:select={({ detail }) => {
|
||||
editor?.insertAtCursor(detail)
|
||||
}}
|
||||
@@ -58,6 +58,7 @@
|
||||
bind:code={branch.expr}
|
||||
class="small-editor"
|
||||
shouldBindKey={false}
|
||||
extraLib={stepPropPicker.extraLib}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
let editor: SimpleEditor | undefined = undefined
|
||||
let selected: string = 'early-stop'
|
||||
|
||||
$: pickableProperties = getStepPropPicker(
|
||||
$: stepPropPicker = getStepPropPicker(
|
||||
$flowStateStore,
|
||||
parentModule,
|
||||
previousModule,
|
||||
@@ -34,7 +34,7 @@
|
||||
$previewArgs,
|
||||
false,
|
||||
true
|
||||
).pickableProperties
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col">
|
||||
@@ -56,7 +56,7 @@
|
||||
<div class="border w-full">
|
||||
<PropPickerWrapper
|
||||
notSelectable
|
||||
{pickableProperties}
|
||||
pickableProperties={stepPropPicker.pickableProperties}
|
||||
on:select={({ detail }) => {
|
||||
editor?.insertAtCursor(detail)
|
||||
}}
|
||||
@@ -67,6 +67,7 @@
|
||||
bind:code={mod.value.iterator.expr}
|
||||
class="small-editor"
|
||||
shouldBindKey={false}
|
||||
extraLib={stepPropPicker.extraLib}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
|
||||
@@ -125,9 +125,9 @@
|
||||
<svelte:fragment slot="header">
|
||||
<FlowModuleHeader
|
||||
bind:module={flowModule}
|
||||
on:toggleSuspend={() => (selected = 'suspend')}
|
||||
on:toggleRetry={() => (selected = 'retries')}
|
||||
on:toggleStopAfterIf={() => (selected = 'early-stop')}
|
||||
on:toggleSuspend={() => (selected = 'advanced-suspend')}
|
||||
on:toggleRetry={() => (selected = 'advanced-retries')}
|
||||
on:toggleStopAfterIf={() => (selected = 'advanced-early-stop')}
|
||||
on:fork={async () => {
|
||||
const [module, state] = await fork(flowModule)
|
||||
flowModule = module
|
||||
@@ -194,19 +194,9 @@
|
||||
</Pane>
|
||||
<Pane size={50} minSize={20}>
|
||||
<Tabs bind:selected>
|
||||
<Tab value="inputs"
|
||||
><Tooltip>
|
||||
Move the focus outside of the text editor to recompute the inputs or press
|
||||
<Kbd>Ctrl/Cmd</Kbd> + <Kbd>S</Kbd>
|
||||
</Tooltip><span class="font-semibold">Step Input</span></Tab
|
||||
>
|
||||
<Tab value="inputs"><span class="font-semibold">Step Input</span></Tab>
|
||||
<Tab value="test"><span class="font-semibold text-md">Test this step</span></Tab>
|
||||
<Tab value="retries">Retries</Tab>
|
||||
{#if !$selectedId.includes('failure')}
|
||||
<Tab value="early-stop">Early Stop</Tab>
|
||||
<Tab value="suspend">Sleep/Suspend</Tab>
|
||||
<Tab value="same_worker">Same Worker/Shared dir</Tab>
|
||||
{/if}
|
||||
<Tab value="advanced">Advanced</Tab>
|
||||
</Tabs>
|
||||
<div class="h-[calc(100%-32px)]">
|
||||
{#if selected === 'inputs'}
|
||||
@@ -215,7 +205,7 @@
|
||||
<SchemaForm
|
||||
schema={$flowStateStore[$selectedId]?.schema ?? {}}
|
||||
inputTransform={true}
|
||||
importPath={$selectedId}
|
||||
previousModuleId={previousModule?.id}
|
||||
bind:args={flowModule.value.input_transforms}
|
||||
bind:extraLib={stepPropPicker.extraLib}
|
||||
/>
|
||||
@@ -227,27 +217,37 @@
|
||||
mod={flowModule}
|
||||
schema={$flowStateStore[$selectedId]?.schema ?? {}}
|
||||
/>
|
||||
{:else if selected === 'retries'}
|
||||
<FlowRetries bind:flowModule class="px-4 pb-4 h-full overflow-auto" />
|
||||
{:else if selected === 'early-stop'}
|
||||
<FlowModuleEarlyStop bind:flowModule class="px-4 pb-4 h-full overflow-auto" />
|
||||
{:else if selected === 'suspend'}
|
||||
<div class="px-4 pb-4 h-full overflow-auto">
|
||||
<FlowModuleSuspend previousModuleId={previousModule?.id} bind:flowModule />
|
||||
</div>
|
||||
{:else if selected === 'same_worker'}
|
||||
<div class="p-4 h-full overflow-auto">
|
||||
<Alert type="info" title="Share a directory using same worker">
|
||||
If same worker is set, all steps will be run on the same worker and will share
|
||||
the folder `/shared` to pass data between each other.
|
||||
</Alert>
|
||||
<Button
|
||||
btnClasses="mt-4"
|
||||
on:click={() => {
|
||||
$selectedId = 'settings-same-worker'
|
||||
}}>Set same worker in the flow settings</Button
|
||||
>
|
||||
</div>
|
||||
{:else if selected.startsWith('advanced')}
|
||||
<Tabs bind:selected>
|
||||
<Tab value="advanced-retries">Retries</Tab>
|
||||
{#if !$selectedId.includes('failure')}
|
||||
<Tab value="advanced-early-stop">Early Stop</Tab>
|
||||
<Tab value="advanced-suspend">Sleep/Suspend</Tab>
|
||||
<Tab value="advanced-same_worker">Same Worker/Shared dir</Tab>
|
||||
{/if}
|
||||
</Tabs>
|
||||
{#if selected === 'advanced-retries'}
|
||||
<FlowRetries bind:flowModule class="px-4 pb-4 h-full overflow-auto" />
|
||||
{:else if selected === 'advanced-early-stop'}
|
||||
<FlowModuleEarlyStop bind:flowModule class="px-4 pb-4 h-full overflow-auto" />
|
||||
{:else if selected === 'advanced-suspend'}
|
||||
<div class="px-4 pb-4 h-full overflow-auto">
|
||||
<FlowModuleSuspend previousModuleId={previousModule?.id} bind:flowModule />
|
||||
</div>
|
||||
{:else if selected === 'advanced-same_worker'}
|
||||
<div class="p-4 h-full overflow-auto">
|
||||
<Alert type="info" title="Share a directory using same worker">
|
||||
If same worker is set, all steps will be run on the same worker and will share
|
||||
the folder `/shared` to pass data between each other.
|
||||
</Alert>
|
||||
<Button
|
||||
btnClasses="mt-4"
|
||||
on:click={() => {
|
||||
$selectedId = 'settings-same-worker'
|
||||
}}>Set same worker in the flow settings</Button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
lang="javascript"
|
||||
bind:code={flowModule.stop_after_if.expr}
|
||||
class="small-editor"
|
||||
extraLib={`declare const result = ${JSON.stringify(result)};`}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
|
||||
@@ -107,7 +107,12 @@
|
||||
editor?.insertAtCursor(detail)
|
||||
}}
|
||||
>
|
||||
<InputTransformForm bind:arg={flowModule.sleep} argName="sleep" {schema} />
|
||||
<InputTransformForm
|
||||
bind:arg={flowModule.sleep}
|
||||
argName="sleep"
|
||||
{schema}
|
||||
{previousModuleId}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Badge } from '$lib/components/common'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
|
||||
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
|
||||
@@ -11,16 +12,17 @@
|
||||
let previewOpen = false
|
||||
let previewMode: 'upTo' | 'whole' = 'whole'
|
||||
|
||||
$: upToDisabled = [
|
||||
'settings',
|
||||
'settings-schedule',
|
||||
'settings-retries',
|
||||
'settings-same-worker',
|
||||
'settings-graph',
|
||||
'inputs',
|
||||
'schedules',
|
||||
'failure'
|
||||
].includes($selectedId)
|
||||
$: upToDisabled =
|
||||
[
|
||||
'settings',
|
||||
'settings-schedule',
|
||||
'settings-retries',
|
||||
'settings-same-worker',
|
||||
'settings-graph',
|
||||
'inputs',
|
||||
'schedules',
|
||||
'failure'
|
||||
].includes($selectedId) || $selectedId.includes('branch')
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row-reverse justify-between items-center gap-x-2">
|
||||
@@ -46,10 +48,10 @@
|
||||
}}
|
||||
endIcon={{ icon: faPlay }}
|
||||
>
|
||||
Test up to step {$selectedId
|
||||
.split('-')
|
||||
.map((x) => (Number.isNaN(Number(x)) ? x : Number(x) + 1))
|
||||
.join(' ')}
|
||||
Test up to
|
||||
<Badge baseClass="ml-1" color="indigo">
|
||||
{$selectedId}
|
||||
</Badge>
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -46,21 +46,24 @@
|
||||
</script>
|
||||
|
||||
{#if module.value.type === 'branchall'}
|
||||
<div class="flex text-xs">
|
||||
<div class="flex text-xs px-2">
|
||||
<div
|
||||
class="w-full space-y-2 flex flex-col border p-2 bg-gray-500 border-gray-600 bg-opacity-10 rounded-sm my-2 relative"
|
||||
class="w-full space-y-2 pb-2 flex flex-col border bg-gray-500 border-gray-600 bg-opacity-10 rounded-sm my-2 relative"
|
||||
>
|
||||
{#each module.value.branches ?? [] as branch, branchIndex (branchIndex)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
transition:slide|local
|
||||
on:click={() => {
|
||||
selectedBranch = branchIndex
|
||||
select(`${module.id}-branch-${branchIndex}`)
|
||||
}}
|
||||
class={classNames(
|
||||
'border w-full rounded-md p-2 bg-white text-sm cursor-pointer flex items-center relative module',
|
||||
`border-b ${
|
||||
branchIndex > 0 ? 'border-t' : ''
|
||||
} w-full p-2 bg-white border-gray-500 text-sm cursor-pointer flex items-center relative module`,
|
||||
$selectedId === `${module.id}-branch-${branchIndex}`
|
||||
? 'outline outline-offset-1 outline-2 outline-slate-900'
|
||||
? 'outline outline-2 outline-slate-900'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
@@ -85,10 +88,10 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FlowModuleSchemaMap bind:modules={branch.modules} color="indigo" />
|
||||
<FlowModuleSchemaMap bind:modules={branch.modules} />
|
||||
</div>
|
||||
{/each}
|
||||
<div class="overflow-clip">
|
||||
<div class="overflow-clip ml-2 mt-2">
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { deleteFlowStateById, emptyModule, idMutex } from '../flowStateUtils'
|
||||
import { emptyFlowModuleState } from '../utils'
|
||||
import { flowStateStore } from '../flowState'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
export let module: FlowModule
|
||||
|
||||
@@ -46,9 +47,9 @@
|
||||
</script>
|
||||
|
||||
{#if module.value.type === 'branchone'}
|
||||
<div class="flex text-xs">
|
||||
<div class="flex text-xs px-2">
|
||||
<div
|
||||
class="w-full space-y-2 flex flex-col border p-2 bg-gray-500 border-gray-600 bg-opacity-10 rounded-sm my-2 relative"
|
||||
class="w-full space-y-2 pb-2 flex flex-col border bg-gray-500 border-gray-600 bg-opacity-10 rounded-sm my-2 relative"
|
||||
>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
@@ -57,9 +58,9 @@
|
||||
select(`${module.id}-branch-default`)
|
||||
}}
|
||||
class={classNames(
|
||||
'border w-full rounded-md p-2 bg-white text-sm cursor-pointer flex items-center',
|
||||
`border-b w-full p-2 bg-white border-gray-500 text-sm cursor-pointer flex items-center relative module`,
|
||||
$selectedId === `${module.id}-branch-default`
|
||||
? 'outline outline-offset-1 outline-2 outline-slate-900'
|
||||
? 'outline outline-2 outline-slate-900'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
@@ -71,20 +72,21 @@
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<FlowModuleSchemaMap bind:modules={module.value.default} color="indigo" />
|
||||
<FlowModuleSchemaMap bind:modules={module.value.default} />
|
||||
</div>
|
||||
|
||||
{#each module.value.branches ?? [] as branch, branchIndex (branchIndex)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
transition:slide|local
|
||||
on:click={() => {
|
||||
selectedBranch = branchIndex + 1
|
||||
select(`${module.id}-branch-${branchIndex}`)
|
||||
}}
|
||||
class={classNames(
|
||||
'border w-full rounded-md p-2 bg-white text-sm cursor-pointer flex items-center mb-4 module relative',
|
||||
`border-b border-t w-full p-2 bg-white border-gray-500 text-sm cursor-pointer flex items-center relative module`,
|
||||
$selectedId === `${module.id}-branch-${branchIndex}`
|
||||
? 'outline outline-offset-1 outline-2 outline-slate-900'
|
||||
? 'outline outline-2 outline-slate-900'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
@@ -109,10 +111,10 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FlowModuleSchemaMap bind:modules={branch.modules} color="indigo" />
|
||||
<FlowModuleSchemaMap bind:modules={branch.modules} />
|
||||
</div>
|
||||
{/each}
|
||||
<div class="overflow-clip">
|
||||
<div class="overflow-clip ml-2">
|
||||
<Button
|
||||
btnClasses=""
|
||||
size="xs"
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
}
|
||||
}}
|
||||
class={classNames(
|
||||
'border rounded-md p-2 bg-white text-sm cursor-pointer flex flex-col overflow-x-hidden ',
|
||||
'border rounded-md p-2 bg-white text-sm border-gray-400 cursor-pointer flex flex-col overflow-x-hidden ',
|
||||
$selectedId.includes('failure') ? 'outline outline-offset-1 outline-2 outline-slate-900' : ''
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { getContext } from 'svelte'
|
||||
import FlowModuleSchemaItem from './FlowModuleSchemaItem.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faPen } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faFlagCheckered, faPen } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
const { select, selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
</script>
|
||||
@@ -15,8 +15,9 @@
|
||||
selected={$selectedId === 'inputs'}
|
||||
bold
|
||||
label="Flow Input"
|
||||
id={'flow_input'}
|
||||
>
|
||||
<div slot="icon">
|
||||
<Icon data={faPen} scale={0.8} />
|
||||
<Icon data={faFlagCheckered} scale={0.8} />
|
||||
</div>
|
||||
</FlowModuleSchemaItem>
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
<script lang="ts">
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import {
|
||||
faBed,
|
||||
faRepeat,
|
||||
faStop,
|
||||
faTimesCircle,
|
||||
faTrashAlt
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faBed, faRepeat, faStop, faTimesCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
export let color: 'blue' | 'orange' | 'indigo' = 'blue'
|
||||
export let isFirst: boolean = false
|
||||
export let isLast: boolean = false
|
||||
export let hasLine: boolean = true
|
||||
@@ -32,25 +25,11 @@
|
||||
<div class="flex relative" on:click>
|
||||
<div
|
||||
class={classNames(
|
||||
'flex mr-2 ml-0.5',
|
||||
'flex pl-6 ml-0.5',
|
||||
hasLine ? 'line' : '',
|
||||
isFirst ? 'justify-center items-start' : 'justify-center items-center'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'flex justify-center items-center w-6 h-6 border rounded-full text-xs font-bold',
|
||||
color === 'blue'
|
||||
? 'bg-blue-200 text-blue-800'
|
||||
: color === 'orange'
|
||||
? 'bg-orange-200 text-orange-800'
|
||||
: 'bg-blue-100 text-blue-600',
|
||||
''
|
||||
)}
|
||||
>
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
</div>
|
||||
/>
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full flex overflow-hidden rounded-sm cursor-pointer mr-2',
|
||||
@@ -76,8 +55,12 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between items-center w-full overflow-hidden border p-2 bg-white text-2xs module"
|
||||
class="flex justify-between items-center w-full overflow-hidden rounded-sm border border-gray-400 p-2 bg-white text-2xs module"
|
||||
>
|
||||
{#if $$slots.icon}
|
||||
<slot name="icon" />
|
||||
<span class="mr-2" />
|
||||
{/if}
|
||||
<div class="flex-1 truncate" class:font-bold={bold}>{label}</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
{#if id}
|
||||
@@ -106,7 +89,7 @@
|
||||
display: flex !important;
|
||||
}
|
||||
.line {
|
||||
background: repeating-linear-gradient(to bottom, transparent 0 4px, #bbb 4px 8px) 50%/1px 100%
|
||||
no-repeat;
|
||||
background: repeating-linear-gradient(to bottom, transparent 0 4px, rgb(120, 120, 120) 4px 8px)
|
||||
50%/1px 100% no-repeat;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
import FlowSettingsItem from './FlowSettingsItem.svelte'
|
||||
import FlowInputsItem from './FlowInputsItem.svelte'
|
||||
import InsertModuleButton from './InsertModuleButton.svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
export let root: boolean = false
|
||||
export let color: 'blue' | 'orange' | 'indigo' = 'blue'
|
||||
export let modules: FlowModule[]
|
||||
|
||||
let indexToRemove: number | undefined = undefined
|
||||
@@ -79,7 +79,7 @@
|
||||
<FlowSettingsItem />
|
||||
</div>
|
||||
{/if}
|
||||
<ul class="w-full flex-auto relative overflow-y-auto overflow-x-hidden px-2 py-1">
|
||||
<ul class="w-full flex-auto relative overflow-y-auto overflow-x-hidden {root ? 'px-2' : ''} py-1">
|
||||
{#if root}
|
||||
<li>
|
||||
<FlowInputsItem />
|
||||
@@ -87,21 +87,21 @@
|
||||
{/if}
|
||||
|
||||
{#each modules as mod, index (mod.id ?? index)}
|
||||
<MapItem
|
||||
{color}
|
||||
{index}
|
||||
bind:mod
|
||||
on:delete={(event) => {
|
||||
if (event.detail.detail.shiftKey || isEmptyFlowModule(mod)) {
|
||||
removeAtIndex(index)
|
||||
} else {
|
||||
indexToRemove = index
|
||||
}
|
||||
}}
|
||||
on:insert={() => {
|
||||
insertNewModuleAtIndex(index)
|
||||
}}
|
||||
/>
|
||||
<div transition:slide|local>
|
||||
<MapItem
|
||||
bind:mod
|
||||
on:delete={(event) => {
|
||||
if (event.detail.detail.shiftKey || isEmptyFlowModule(mod)) {
|
||||
removeAtIndex(index)
|
||||
} else {
|
||||
indexToRemove = index
|
||||
}
|
||||
}}
|
||||
on:insert={() => {
|
||||
insertNewModuleAtIndex(index)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<InsertModuleButton on:click={() => insertNewModuleAtIndex(modules.length)} />
|
||||
@@ -125,17 +125,3 @@
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.badge {
|
||||
@apply whitespace-nowrap text-sm font-medium border px-2.5 py-0.5 rounded cursor-pointer flex items-center;
|
||||
}
|
||||
|
||||
.badge-on {
|
||||
@apply bg-blue-100 text-blue-800 hover:bg-blue-200;
|
||||
}
|
||||
|
||||
.badge-off {
|
||||
@apply bg-gray-100 text-gray-800 hover:bg-gray-200;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
const { select, selectedId, schedule } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
$: settingsClass = classNames(
|
||||
'border w-full rounded-md p-2 bg-white text-sm cursor-pointer flex items-center',
|
||||
'border w-full rounded-md p-2 bg-white border-gray-400 text-sm cursor-pointer flex items-center',
|
||||
$selectedId === 'settings' ? 'outline outline-offset-1 outline-2 outline-slate-900' : ''
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -7,10 +7,17 @@
|
||||
import InsertModuleButton from './InsertModuleButton.svelte'
|
||||
import FlowBranchOneMap from './FlowBranchOneMap.svelte'
|
||||
import FlowBranchAllMap from './FlowBranchAllMap.svelte'
|
||||
import {
|
||||
faBuilding,
|
||||
faCode,
|
||||
faCodeBranch,
|
||||
faLongArrowDown,
|
||||
faQuestion,
|
||||
faRepeat
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
export let mod: FlowModule
|
||||
export let index: number
|
||||
export let color: 'blue' | 'orange' | 'indigo' = 'blue'
|
||||
|
||||
const { select, selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const dispatch = createEventDispatcher<{ delete: CustomEvent<MouseEvent>; insert: void }>()
|
||||
@@ -40,14 +47,14 @@
|
||||
{...itemProps}
|
||||
>
|
||||
<div slot="icon">
|
||||
<span>{index + 1}</span>
|
||||
<Icon data={faRepeat} scale={0.8} />
|
||||
</div>
|
||||
</FlowModuleSchemaItem>
|
||||
<div class="flex flex-row w-full">
|
||||
<div class="w-7 shrink-0 line" />
|
||||
<div class="grow my-1 overflow-auto">
|
||||
<div class="w-full">
|
||||
<FlowModuleSchemaMap bind:modules={mod.value.modules} color="orange" />
|
||||
<FlowModuleSchemaMap bind:modules={mod.value.modules} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,7 +70,7 @@
|
||||
label={mod.summary || 'Run one branch'}
|
||||
>
|
||||
<div slot="icon">
|
||||
<span>{index + 1}</span>
|
||||
<Icon data={faCodeBranch} scale={0.8} />
|
||||
</div>
|
||||
</FlowModuleSchemaItem>
|
||||
<FlowBranchOneMap bind:module={mod} />
|
||||
@@ -79,7 +86,7 @@
|
||||
label={mod.summary || 'Run all branches'}
|
||||
>
|
||||
<div slot="icon">
|
||||
<span>{index + 1}</span>
|
||||
<Icon data={faCodeBranch} scale={0.8} />
|
||||
</div>
|
||||
</FlowModuleSchemaItem>
|
||||
<FlowBranchAllMap bind:module={mod} />
|
||||
@@ -89,7 +96,6 @@
|
||||
<FlowModuleSchemaItem
|
||||
on:click={() => select(mod.id)}
|
||||
on:delete={onDelete}
|
||||
{color}
|
||||
deletable
|
||||
id={mod.id}
|
||||
{...itemProps}
|
||||
@@ -98,7 +104,13 @@
|
||||
(mod.value.type === 'rawscript' ? `Inline ${mod.value.language}` : 'To be defined')}
|
||||
>
|
||||
<div slot="icon">
|
||||
<span>{index + 1}</span>
|
||||
{#if mod.value.type === 'rawscript'}
|
||||
<Icon data={faCode} scale={0.8} />
|
||||
{:else if mod.value.type === 'identity'}
|
||||
<Icon data={faLongArrowDown} scale={0.8} />
|
||||
{:else if mod.value.type === 'script'}
|
||||
<Icon data={faBuilding} scale={0.8} />
|
||||
{/if}
|
||||
</div>
|
||||
</FlowModuleSchemaItem>
|
||||
</li>
|
||||
@@ -107,7 +119,7 @@
|
||||
|
||||
<style>
|
||||
.line {
|
||||
background: repeating-linear-gradient(to bottom, transparent 0 4px, #bbb 4px 8px) 50%/1px 100%
|
||||
no-repeat;
|
||||
background: repeating-linear-gradient(to bottom, transparent 0 4px, rgb(120, 120, 120) 4px 8px)
|
||||
50%/1px 100% no-repeat;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -137,28 +137,28 @@ export function buildExtraLib(flowInput: Record<string, any>, results: Record<st
|
||||
* get variable (including secret) at path
|
||||
* @param {string} path - path of the variable (e.g: g/all/pretty_secret)
|
||||
*/
|
||||
export function variable(path: string): string;
|
||||
declare function variable(path: string): string;
|
||||
|
||||
/**
|
||||
* get resource at path
|
||||
* @param {string} path - path of the resource (e.g: g/all/my_resource)
|
||||
*/
|
||||
export function resource(path: string): any;
|
||||
declare function resource(path: string): any;
|
||||
|
||||
/**
|
||||
* flow input as an object
|
||||
*/
|
||||
export const flow_input = ${JSON.stringify(flowInput)};
|
||||
declare const flow_input = ${JSON.stringify(flowInput)};
|
||||
|
||||
/**
|
||||
* static params of this same step
|
||||
*/
|
||||
export const params: any;
|
||||
declare const params: any;
|
||||
|
||||
/**
|
||||
* result by id
|
||||
*/
|
||||
export const results = ${JSON.stringify(results)};
|
||||
declare const results = ${JSON.stringify(results)};
|
||||
`
|
||||
|
||||
}
|
||||
|
||||
@@ -122,15 +122,11 @@ export function isCodeInjection(expr: string | undefined): boolean {
|
||||
}
|
||||
|
||||
export function getDefaultExpr(
|
||||
importPath: string | undefined = undefined,
|
||||
key: string = 'myfield',
|
||||
previousExpr?: string
|
||||
previousModuleId: string | undefined,
|
||||
previousExpr?: string,
|
||||
) {
|
||||
const expr = previousExpr ?? `results.${key}`
|
||||
return `import { results, flow_input, variable, resource, params } from 'windmill${importPath ? `@${importPath}` : ''
|
||||
}'
|
||||
|
||||
${expr}`
|
||||
return previousExpr ?? (previousModuleId ? `results.${previousModuleId}.${key}` : `flow_input.${key}`)
|
||||
}
|
||||
|
||||
export function jobsToResults(jobs: Job[]) {
|
||||
@@ -160,8 +156,6 @@ export function codeToStaticTemplate(code?: string): string | undefined {
|
||||
|
||||
const lines = code
|
||||
.split('\n')
|
||||
.slice(1)
|
||||
.filter((x) => x != '')
|
||||
|
||||
if (lines.length == 1) {
|
||||
const line = lines[0].trim()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Svelvet, { type Edge } from 'svelvet'
|
||||
import { sugiyama, dagStratify, decrossOpt, coordGreedy, coordCenter } from 'd3-dag'
|
||||
import type { FlowModule, RawScript } from '../../gen'
|
||||
import type { FlowModule, ForloopFlow, RawScript } from '../../gen'
|
||||
import {
|
||||
NODE,
|
||||
createIdGenerator,
|
||||
@@ -15,7 +15,7 @@
|
||||
type NestedNodes,
|
||||
type ModuleHost
|
||||
} from '.'
|
||||
import { truncate, truncateRev } from '$lib/utils'
|
||||
import { defaultIfEmptyString, truncate, truncateRev } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { numberToChars } from '../flows/utils'
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
function getConvertedFlowModule(
|
||||
module: FlowModule,
|
||||
parent: NestedNodes | undefined = undefined,
|
||||
parent: NestedNodes | string | undefined = undefined,
|
||||
edgeLabel: string | undefined = undefined
|
||||
): GraphItem | undefined {
|
||||
const type = module.value.type
|
||||
@@ -96,22 +96,18 @@
|
||||
edgeLabel
|
||||
)
|
||||
} else if (type === 'forloopflow') {
|
||||
const expr = module.value.iterator['expr']
|
||||
return flowModuleToLoop(
|
||||
module.value.modules,
|
||||
`For each item in: ${truncate(expr, 10)}`,
|
||||
parent
|
||||
)
|
||||
return flowModuleToLoop(module.value.modules, module, parent)
|
||||
} else if (type === 'branchone') {
|
||||
const branches = [module.value.default, ...module.value.branches.map((b) => b.modules)]
|
||||
return flowModuleToBranch(
|
||||
module,
|
||||
branches,
|
||||
['Default', ...module.value.branches.map((x) => `If ${truncateRev(x.expr, 20)}`)],
|
||||
parent
|
||||
)
|
||||
} else if (type === 'branchall') {
|
||||
const branches = module.value.branches.map((b) => b.modules)
|
||||
return flowModuleToBranch(branches, [], parent)
|
||||
return flowModuleToBranch(module, branches, [], parent)
|
||||
}
|
||||
return flowModuleToNode(
|
||||
parentIds,
|
||||
@@ -124,7 +120,10 @@
|
||||
)
|
||||
}
|
||||
|
||||
function getParentIds(items: NestedNodes | undefined = undefined): string[] {
|
||||
function getParentIds(items: string | NestedNodes | undefined = undefined): string[] {
|
||||
if (typeof items == 'string') {
|
||||
return [items]
|
||||
}
|
||||
const item = items?.at(-1) || nestedNodes.at(-1)
|
||||
if (!item) return []
|
||||
|
||||
@@ -197,37 +196,72 @@
|
||||
|
||||
function flowModuleToLoop(
|
||||
modules: FlowModule[],
|
||||
startLabel: string,
|
||||
parent: NestedNodes | undefined = undefined
|
||||
module: FlowModule,
|
||||
parent: NestedNodes | string | undefined = undefined
|
||||
): Loop {
|
||||
const value = module.value as ForloopFlow
|
||||
const expr = value.iterator.type == 'static' ? value.iterator.value : value.iterator.expr
|
||||
const loop: Loop = {
|
||||
type: 'loop',
|
||||
items: [createVirtualNode(getParentIds(parent), startLabel)]
|
||||
items: [
|
||||
flowModuleToNode(
|
||||
getParentIds(parent),
|
||||
module.id,
|
||||
module.summary || `For Loop: ${defaultIfEmptyString(expr ?? '', 'TBD')}`,
|
||||
'inline',
|
||||
module,
|
||||
undefined
|
||||
)
|
||||
]
|
||||
}
|
||||
modules.forEach((module) => {
|
||||
const item = getConvertedFlowModule(module, loop.items)
|
||||
item && loop.items.push(item)
|
||||
})
|
||||
loop.items.push(createVirtualNode(getParentIds(loop.items), "Collect iterations' results"))
|
||||
loop.items.push(
|
||||
createVirtualNode(
|
||||
getParentIds(loop.items),
|
||||
`Collect iterations' results of For Loop ${module.id}`
|
||||
)
|
||||
)
|
||||
return loop
|
||||
}
|
||||
|
||||
function flowModuleToBranch(
|
||||
module: FlowModule,
|
||||
branches: FlowModule[][],
|
||||
edgesLabel: string[],
|
||||
parent: NestedNodes | undefined = undefined
|
||||
parent: string | NestedNodes | undefined = undefined
|
||||
): Branch {
|
||||
const branch: Branch = {
|
||||
type: 'branch',
|
||||
node: flowModuleToNode(
|
||||
getParentIds(parent),
|
||||
module.id,
|
||||
module.summary || module.value.type == 'branchall'
|
||||
? 'Run all branches'
|
||||
: 'Run one branch given predicate',
|
||||
'inline',
|
||||
module,
|
||||
undefined
|
||||
),
|
||||
items: []
|
||||
}
|
||||
const branchParent = [branch.node.id.toString()]
|
||||
if (branches.length == 0) {
|
||||
branch.items.push([createVirtualNode(branchParent, 'No branches')])
|
||||
}
|
||||
branches.forEach((modules, i) => {
|
||||
const items: NestedNodes = []
|
||||
if (!modules.length) {
|
||||
items.push(createVirtualNode(getParentIds(parent), 'Empty branch', edgesLabel[i]))
|
||||
items.push(createVirtualNode(branchParent, 'Empty branch', edgesLabel[i]))
|
||||
} else {
|
||||
modules.forEach((module) => {
|
||||
const item = getConvertedFlowModule(module, items.length ? items : parent, edgesLabel[i])
|
||||
const item = getConvertedFlowModule(
|
||||
module,
|
||||
items.length ? items : branch.node.id.toString(),
|
||||
edgesLabel[i]
|
||||
)
|
||||
item && items.push(item)
|
||||
})
|
||||
}
|
||||
@@ -244,6 +278,7 @@
|
||||
} else if (isLoop(node)) {
|
||||
flattenNestedNodes(node.items, array)
|
||||
} else if (isBranch(node)) {
|
||||
array.push(node.node)
|
||||
node.items.forEach((item) => {
|
||||
flattenNestedNodes(item, array)
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@ export type Loop = {
|
||||
}
|
||||
|
||||
export type Branch = {
|
||||
node: Node,
|
||||
type: 'branch',
|
||||
items: NestedNodes[]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { truncate } from '$lib/utils'
|
||||
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Badge } from '../common'
|
||||
import { NEVER_TESTED_THIS_FAR } from '../flows/utils'
|
||||
import { getTypeAsString } from '../flows/utils'
|
||||
import { computeKey } from './utils'
|
||||
@@ -15,6 +16,7 @@
|
||||
export let collapsed = level == 3 || Array.isArray(json)
|
||||
export let rawKey = false
|
||||
export let topBrackets = false
|
||||
export let topLevelNode = false
|
||||
|
||||
const collapsedSymbol = '...'
|
||||
let keys: string | any[]
|
||||
@@ -43,17 +45,29 @@
|
||||
{#if keys.length > 0}
|
||||
<span class:hidden={collapsed}>
|
||||
{#if level != 0}
|
||||
<span class="cursor-pointer hover:bg-gray-200 px-1 rounded" on:click={collapse}> (-) </span>
|
||||
<span
|
||||
class="cursor-pointer border border-gray-300 hover:bg-gray-200 px-1 rounded"
|
||||
on:click={collapse}
|
||||
>
|
||||
-
|
||||
</span>
|
||||
{/if}
|
||||
{#if level == 0 && topBrackets}<span class="h-0">{openBracket}</span>{/if}
|
||||
<ul class="w-full">
|
||||
{#each keys as key, index}
|
||||
<li class="pt-1">
|
||||
<button
|
||||
on:click={() => selectProp(key)}
|
||||
class="key {pureViewer ? 'cursor-auto' : ''} font-normal rounded px-1 hover:bg-blue-100"
|
||||
>
|
||||
{!isArray ? key : index}:
|
||||
<button on:click={() => selectProp(key)}>
|
||||
{#if topLevelNode}
|
||||
<Badge baseClass="border border-blue-600" color="indigo">{key}</Badge>
|
||||
{:else}
|
||||
<span
|
||||
class="key {pureViewer
|
||||
? 'cursor-auto'
|
||||
: 'border border-gray-300'} font-normal rounded px-1 hover:bg-blue-100"
|
||||
>
|
||||
{!isArray ? key : index}</span
|
||||
>
|
||||
{/if}:
|
||||
</button>
|
||||
|
||||
{#if getTypeAsString(json[key]) === 'object'}
|
||||
@@ -69,7 +83,7 @@
|
||||
<button
|
||||
class="val {pureViewer
|
||||
? 'cursor-auto'
|
||||
: ''} rounded px-1 hover:bg-blue-100 {getTypeAsString(json[key])}"
|
||||
: ''} rounded hover:bg-blue-100 {getTypeAsString(json[key])}"
|
||||
on:click={() => selectProp(key)}
|
||||
>
|
||||
{#if json[key] === NEVER_TESTED_THIS_FAR}
|
||||
@@ -86,7 +100,11 @@
|
||||
</ul>
|
||||
{#if level == 0 && topBrackets}<span class="h-0">{closeBracket}</span>{/if}
|
||||
</span>
|
||||
<span class="cursor-pointer hover:bg-gray-200" class:hidden={!collapsed} on:click={collapse}>
|
||||
<span
|
||||
class="border border-blue-600 rounded px-1 cursor-pointer hover:bg-gray-200"
|
||||
class:hidden={!collapsed}
|
||||
on:click={collapse}
|
||||
>
|
||||
{openBracket}{collapsedSymbol}{closeBracket}
|
||||
</span>
|
||||
{#if !isLast && collapsed}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
{`Mode: ${$propPickerConfig?.insertionMode}`}
|
||||
</Badge>
|
||||
{:else}
|
||||
<Badge>← Select a step input</Badge>
|
||||
<Badge color="blue">← Select a step input</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -107,6 +107,7 @@
|
||||
<span class="font-bold text-sm">Previous Result</span>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
<ObjectViewer
|
||||
topLevelNode
|
||||
pureViewer={!$propPickerConfig}
|
||||
json={Object.fromEntries(
|
||||
Object.entries(resultByIdFiltered).filter(([k, v]) => k == previousId)
|
||||
@@ -121,6 +122,7 @@
|
||||
<span class="font-bold text-sm">All Results</span>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
<ObjectViewer
|
||||
topLevelNode
|
||||
pureViewer={!$propPickerConfig}
|
||||
collapsed={true}
|
||||
json={resultByIdFiltered}
|
||||
@@ -139,9 +141,10 @@
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
displayVariable = false
|
||||
}}>(-)</Button
|
||||
}}>-</Button
|
||||
>
|
||||
<ObjectViewer
|
||||
pureViewer={!$propPickerConfig}
|
||||
@@ -151,7 +154,7 @@
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
class="key font-normal rounded px-1 hover:bg-blue-100 !p-0"
|
||||
class="border border-blue-600 key font-normal rounded hover:bg-blue-100 px-1"
|
||||
on:click={async () => {
|
||||
await loadVariables()
|
||||
displayVariable = true
|
||||
@@ -164,10 +167,11 @@
|
||||
{#if displayResources}
|
||||
<Button
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
displayResources = false
|
||||
}}>(-)</Button
|
||||
}}>-</Button
|
||||
>
|
||||
<ObjectViewer
|
||||
pureViewer={!$propPickerConfig}
|
||||
@@ -177,7 +181,7 @@
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
class="key font-normal rounded px-1 hover:bg-blue-100 !p-0"
|
||||
class="border border-blue-600 px-1 key font-normal rounded hover:bg-blue-100"
|
||||
on:click={async () => {
|
||||
await loadResources()
|
||||
displayResources = true
|
||||
|
||||
Reference in New Issue
Block a user