mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 00:04:10 +00:00
feature(frontend): Prop picker panel (#605)
* feature(frontend): WIP Prop picker v2 * feature(frontend): add PropPicker context * feature(frontend): prop picker working * feature(frontend): restore connect button * feature(frontend): Fix wording + fix height to avoid content shift * feature(frontend): Fix upTo preview + adapt style * feature(frontend): Preview results cleanup * feature(frontend): Remove test logs * feature(frontend): Revert wrong changes * feature(frontend): Restore shared preview args * feature(frontend): Reduce ObjectViewer font size + remove useless space * feature(frontend): Wording * feature(frontend): Fix Split panel scrolling issues
This commit is contained in:
@@ -140,6 +140,7 @@
|
||||
|
||||
const selectedIdStore = writable<string>('settings')
|
||||
const scheduleStore = writable<Schedule>(undefined)
|
||||
const previewArgsStore = writable<Record<string, any>>(undefined)
|
||||
|
||||
function select(selectedId: string) {
|
||||
selectedIdStore.set(selectedId)
|
||||
@@ -149,7 +150,8 @@
|
||||
selectedId: selectedIdStore,
|
||||
schedule: scheduleStore,
|
||||
select,
|
||||
path: initialPath
|
||||
path: initialPath,
|
||||
previewArgs: previewArgsStore
|
||||
})
|
||||
|
||||
$: {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
export let flow: Flow
|
||||
export let schema: Schema
|
||||
|
||||
const [i, j] = indexes.split('-').map(Number)
|
||||
const [i, j] = indexes.split('-').filter(Boolean).map(Number)
|
||||
|
||||
let stepArgs: Record<string, any> = {}
|
||||
let jobId: string
|
||||
|
||||
@@ -11,18 +11,16 @@
|
||||
import { flowStore } from './flows/flowStore'
|
||||
import type { FlowEditorContext } from './flows/types'
|
||||
import { runFlowPreview } from './flows/utils'
|
||||
import FlowStatusViewer from './FlowStatusViewer.svelte'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
|
||||
import FlowStatusViewer from '../components/FlowStatusViewer.svelte'
|
||||
export let previewMode: 'upTo' | 'whole'
|
||||
|
||||
let args: Record<string, any> = {}
|
||||
|
||||
let jobId: string | undefined = undefined
|
||||
let isValid: boolean = false
|
||||
let intervalState: 'idle' | 'canceled' | 'done' | 'running' = 'idle'
|
||||
|
||||
const { selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const { selectedId, previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
function extractFlow(previewMode: 'upTo' | 'whole') {
|
||||
if (previewMode === 'whole') {
|
||||
@@ -104,7 +102,7 @@
|
||||
</div>
|
||||
<div class="pb-4 h-full max-h-1/2 overflow-auto">
|
||||
<div class="mt-4">
|
||||
<SchemaForm schema={$flowStore.schema} bind:isValid bind:args />
|
||||
<SchemaForm schema={$flowStore.schema} bind:isValid bind:args={$previewArgs} />
|
||||
</div>
|
||||
</div>
|
||||
{#if intervalState === 'running'}
|
||||
@@ -128,7 +126,12 @@
|
||||
Cancel
|
||||
</Button>
|
||||
{:else}
|
||||
<Button disabled={!isValid} class="blue-button" on:click={() => runPreview(args)} size="md">
|
||||
<Button
|
||||
disabled={!isValid}
|
||||
class="blue-button"
|
||||
on:click={() => runPreview($previewArgs)}
|
||||
size="md"
|
||||
>
|
||||
{`Run${intervalState === 'done' ? ' again' : ''}`}
|
||||
</Button>
|
||||
{/if}
|
||||
@@ -140,9 +143,11 @@
|
||||
on:jobsLoaded={(e) => {
|
||||
intervalState = 'done'
|
||||
const [parentIndex] = $selectedId.split('-')
|
||||
const configIndex =
|
||||
previewMode === 'upTo' ? Number(parentIndex) : $flowStateStore.modules.length
|
||||
mapJobResultsToFlowState(e.detail, 'upto', configIndex, undefined)
|
||||
|
||||
const upToIndex =
|
||||
previewMode === 'upTo' ? Number(parentIndex) + 1 : $flowStateStore.modules.length
|
||||
|
||||
mapJobResultsToFlowState(e.detail, 'upto', upToIndex, undefined)
|
||||
}}
|
||||
root={true}
|
||||
/>
|
||||
|
||||
@@ -127,5 +127,5 @@
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
No script selected
|
||||
Job starting...
|
||||
{/if}
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { InputTransform } from '$lib/gen'
|
||||
import type { InputCat } from '$lib/utils'
|
||||
import { faChain } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Button, Tooltip } from 'flowbite-svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import DynamicInputHelpBox from './flows/content/DynamicInputHelpBox.svelte'
|
||||
import type { PropPickerWrapperContext } from './flows/propPicker/PropPickerWrapper.svelte'
|
||||
import { codeToStaticTemplate, getDefaultExpr, isCodeInjection } from './flows/utils'
|
||||
import OverlayPropertyPicker from './propertyPicker/OverlayPropertyPicker.svelte'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { Button, Tooltip } from 'flowbite-svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faChain } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
export let schema: Schema
|
||||
export let arg: InputTransform | any
|
||||
@@ -19,9 +21,7 @@
|
||||
export let extraLib: string = 'missing extraLib'
|
||||
export let inputCheck: { [id: string]: boolean }
|
||||
export let importPath: string | undefined = undefined
|
||||
export let pickableProperties: Object | undefined = undefined
|
||||
|
||||
let overlays: { [id: string]: OverlayPropertyPicker } = {}
|
||||
let monacos: { [id: string]: SimpleEditor } = {}
|
||||
|
||||
let inputCats: { [id: string]: InputCat } = {}
|
||||
@@ -60,16 +60,6 @@
|
||||
return inputCat === 'string' || inputCat === 'sql'
|
||||
}
|
||||
|
||||
function focusProp(argName: string, monacoEditor: boolean = false) {
|
||||
Object.keys(overlays).forEach((k) => {
|
||||
if (k == argName && (isStaticTemplate(inputCats[argName]) || monacoEditor)) {
|
||||
overlays[k].focus()
|
||||
} else {
|
||||
overlays[k].unfocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function connectProperty(argName: string, rawValue: string) {
|
||||
if (isStaticTemplate(inputCats[argName])) {
|
||||
arg.value = `\$\{${rawValue}}`
|
||||
@@ -86,6 +76,8 @@
|
||||
}
|
||||
|
||||
$: checked = propertyType == 'javascript'
|
||||
|
||||
const { focusProp } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||
</script>
|
||||
|
||||
{#if arg != undefined}
|
||||
@@ -137,14 +129,10 @@
|
||||
<div class="max-w-xs" />
|
||||
|
||||
{#if propertyType === undefined || !checked}
|
||||
<OverlayPropertyPicker
|
||||
{pickableProperties}
|
||||
bind:this={overlays[argName]}
|
||||
on:select={({ detail }) => {
|
||||
if (detail.pickerVariation === 'connect') {
|
||||
connectProperty(argName, detail.propPath)
|
||||
} else {
|
||||
const toAppend = `\$\{${detail.propPath}}`
|
||||
<ArgInput
|
||||
on:focus={() => {
|
||||
focusProp(argName, 'append', (path) => {
|
||||
const toAppend = `\$\{${path}}`
|
||||
arg.value = `${arg.value ?? ''}${toAppend}`
|
||||
if (monacos[argName]) {
|
||||
monacos[argName].setCode(arg.value)
|
||||
@@ -152,66 +140,64 @@
|
||||
if (isStaticTemplate(inputCats[argName])) {
|
||||
setPropertyType(arg.value)
|
||||
}
|
||||
})
|
||||
}}
|
||||
label={argName}
|
||||
bind:editor={monacos[argName]}
|
||||
bind:description={schema.properties[argName].description}
|
||||
bind:value={arg.value}
|
||||
type={schema.properties[argName].type}
|
||||
required={schema.required.includes(argName)}
|
||||
bind:pattern={schema.properties[argName].pattern}
|
||||
bind:valid={inputCheck[argName]}
|
||||
defaultValue={schema.properties[argName].default}
|
||||
bind:enum_={schema.properties[argName].enum}
|
||||
bind:format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
bind:itemsType={schema.properties[argName].items}
|
||||
properties={schema.properties[argName].properties}
|
||||
displayHeader={false}
|
||||
bind:inputCat={inputCats[argName]}
|
||||
on:input={(e) => {
|
||||
if (isStaticTemplate(inputCats[argName])) {
|
||||
setPropertyType(e.detail.rawValue)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ArgInput
|
||||
on:focus={() => focusProp(argName)}
|
||||
label={argName}
|
||||
bind:editor={monacos[argName]}
|
||||
bind:description={schema.properties[argName].description}
|
||||
bind:value={arg.value}
|
||||
type={schema.properties[argName].type}
|
||||
required={schema.required.includes(argName)}
|
||||
bind:pattern={schema.properties[argName].pattern}
|
||||
bind:valid={inputCheck[argName]}
|
||||
defaultValue={schema.properties[argName].default}
|
||||
bind:enum_={schema.properties[argName].enum}
|
||||
bind:format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
bind:itemsType={schema.properties[argName].items}
|
||||
properties={schema.properties[argName].properties}
|
||||
displayHeader={false}
|
||||
bind:inputCat={inputCats[argName]}
|
||||
on:input={(e) => {
|
||||
if (isStaticTemplate(inputCats[argName])) {
|
||||
setPropertyType(e.detail.rawValue)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div slot="actions">
|
||||
<div on:click={() => overlays[argName]?.focus('connect')}>
|
||||
<Tooltip placement="bottom" content="Input connect">
|
||||
<Button size="sm" class="blue-button h-8">
|
||||
<Icon data={faChain} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<div
|
||||
on:click={() => {
|
||||
focusProp(argName, 'connect', (path) => {
|
||||
connectProperty(argName, path)
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Tooltip placement="bottom" content="Input connect">
|
||||
<Button size="sm" class="blue-button h-8">
|
||||
<Icon data={faChain} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</ArgInput>
|
||||
</OverlayPropertyPicker>
|
||||
</div>
|
||||
</ArgInput>
|
||||
{:else if checked}
|
||||
{#if arg.expr != undefined}
|
||||
<OverlayPropertyPicker
|
||||
bind:this={overlays[argName]}
|
||||
{pickableProperties}
|
||||
on:select={(event) => {
|
||||
monacos[argName].insertAtCursor(event.detail.propPath)
|
||||
}}
|
||||
>
|
||||
<div class="border rounded p-2 mt-2 border-gray-300">
|
||||
<SimpleEditor
|
||||
bind:this={monacos[argName]}
|
||||
on:focus={() => focusProp(argName, true)}
|
||||
bind:code={arg.expr}
|
||||
lang="javascript"
|
||||
class="few-lines-editor"
|
||||
{extraLib}
|
||||
extraLibPath="file:///node_modules/@types/windmill@{importPath}/index.d.ts"
|
||||
shouldBindKey={false}
|
||||
/>
|
||||
</div>
|
||||
</OverlayPropertyPicker>
|
||||
<div class="border rounded p-2 mt-2 border-gray-300">
|
||||
<SimpleEditor
|
||||
bind:this={monacos[argName]}
|
||||
on:focus={() => {
|
||||
focusProp(argName, 'insert', (path) => {
|
||||
monacos[argName].insertAtCursor(path)
|
||||
})
|
||||
}}
|
||||
bind:code={arg.expr}
|
||||
lang="javascript"
|
||||
class="few-lines-editor"
|
||||
{extraLib}
|
||||
extraLibPath="file:///node_modules/@types/windmill@{importPath}/index.d.ts"
|
||||
shouldBindKey={false}
|
||||
/>
|
||||
</div>
|
||||
<DynamicInputHelpBox {importPath} />
|
||||
{/if}
|
||||
{:else}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { InputTransform } from '$lib/gen'
|
||||
import { allTrue } from '$lib/utils'
|
||||
import { getContext, hasContext } from 'svelte'
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
import type { PropPickerWrapperContext } from './flows/propPicker/PropPickerWrapper.svelte'
|
||||
import InputTransformForm from './InputTransformForm.svelte'
|
||||
|
||||
export let inputTransform = false
|
||||
@@ -10,7 +12,6 @@
|
||||
export let args: Record<string, InputTransform | any> = {}
|
||||
export let editableSchema = false
|
||||
export let isValid: boolean = true
|
||||
export let pickableProperties: Object | undefined = undefined
|
||||
export let extraLib: string = 'missing extraLib'
|
||||
export let importPath: string | undefined = undefined
|
||||
|
||||
@@ -31,6 +32,16 @@
|
||||
}
|
||||
|
||||
$: schema?.properties && removeExtraKey()
|
||||
|
||||
function getFocusFunction() {
|
||||
if (hasContext('PropPickerWrapper')) {
|
||||
const { focus } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||
return focus
|
||||
}
|
||||
return (value: string | undefined) => {}
|
||||
}
|
||||
|
||||
const focus = getFocusFunction()
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
@@ -42,7 +53,6 @@
|
||||
bind:schema
|
||||
bind:argName
|
||||
bind:inputCheck
|
||||
bind:pickableProperties
|
||||
bind:extraLib
|
||||
bind:importPath
|
||||
/>
|
||||
@@ -61,6 +71,9 @@
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
properties={schema.properties[argName].properties}
|
||||
bind:itemsType={schema.properties[argName].items}
|
||||
on:focus={() => {
|
||||
focus && focus(argName)
|
||||
}}
|
||||
{editableSchema}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="border-b border-gray-200 flex flex-row mb-4">
|
||||
<div class="border-b border-gray-200 flex flex-row">
|
||||
<slot />
|
||||
</div>
|
||||
<slot name="content" />
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { getContext } from 'svelte'
|
||||
import type { ToggleButtonContext } from './ToggleButtonGroup.svelte'
|
||||
|
||||
export let value: string
|
||||
export let position: 'left' | 'center' | 'right'
|
||||
|
||||
const { select, selected } = getContext<ToggleButtonContext>('ToggleButtonGroup')
|
||||
</script>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => select(value)}
|
||||
class={classNames(
|
||||
'py-1 px-2 text-sm font-medium border-gray-200 ',
|
||||
position === 'left' ? 'rounded-l-lg border' : '',
|
||||
position === 'center' ? 'border-t border-b' : '',
|
||||
position === 'right' ? 'rounded-r-md border border-l-0' : '',
|
||||
$selected.includes(value) ? 'text-white bg-gray-900 ' : 'text-gray-900 bg-white '
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script context="module" lang="ts">
|
||||
export type ToggleButtonContext = {
|
||||
selected: Writable<string[]>
|
||||
select: (value: string) => void
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export let exclusive: boolean = true
|
||||
export let selected: string[] = []
|
||||
|
||||
const selectedContent = writable(selected)
|
||||
|
||||
setContext<ToggleButtonContext>('ToggleButtonGroup', {
|
||||
selected: selectedContent,
|
||||
select: (value: string) => {
|
||||
if (exclusive) {
|
||||
selectedContent.set([value])
|
||||
} else {
|
||||
selectedContent.update((selected) => {
|
||||
const index = selected.findIndex((val: string) => val === value)
|
||||
if (index !== -1) {
|
||||
selected.splice(index, 1)
|
||||
return selected
|
||||
} else {
|
||||
return [value, ...selected]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="inline-flex rounded-md shadow-sm" role="group">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import FlowModuleWrapper from './FlowModuleWrapper.svelte'
|
||||
import FlowSchedules from './FlowSchedules.svelte'
|
||||
import FlowSettings from './FlowSettings.svelte'
|
||||
import FlowInput from './FlowInput.svelte'
|
||||
import FlowLoop from './FlowLoop.svelte'
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
import FlowModuleHeader from './FlowModuleHeader.svelte'
|
||||
import { flowStateStore, type FlowModuleSchema } from '../flowState'
|
||||
import { scriptLangToEditorLang } from '$lib/utils'
|
||||
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import FlowModuleAdvancedSettings from './FlowModuleAdvancedSettings.svelte'
|
||||
|
||||
export let indexes: string
|
||||
@@ -97,7 +97,6 @@
|
||||
shouldDisableLoopCreation={indexes.length > 1 || parentIndex == 0}
|
||||
on:loop={() => {
|
||||
applyCreateLoop()
|
||||
|
||||
select(['loop', $selectedId].join('-'))
|
||||
}}
|
||||
on:pick={(e) => apply(pickScript, e.detail.path)}
|
||||
@@ -119,6 +118,8 @@
|
||||
<VSplitPane
|
||||
topPanelSize={flowModule.value.type === 'rawscript' ? '50%' : '0%'}
|
||||
downPanelSize={flowModule.value.type === 'rawscript' ? '50%' : '100%'}
|
||||
minTopPaneSize="20%"
|
||||
minDownPaneSize="20%"
|
||||
>
|
||||
<top slot="top">
|
||||
{#if flowModule.value.type === 'rawscript'}
|
||||
@@ -137,46 +138,36 @@
|
||||
{/if}
|
||||
</top>
|
||||
|
||||
<down slot="down">
|
||||
<down slot="down" class="flex flex-col flex-1 h-full">
|
||||
<Tabs selected="inputs">
|
||||
<Tab value="inputs">Inputs</Tab>
|
||||
<Tab value="preview">Test</Tab>
|
||||
<Tab value="test">Test</Tab>
|
||||
<Tab value="advanced">Advanced</Tab>
|
||||
|
||||
<svelte:fragment slot="content">
|
||||
<div class="h-full pb-16 overflow-y-scroll bg-white">
|
||||
<div class="p-4 overflow-hidden">
|
||||
<TabContent value="inputs">
|
||||
<div class="w-11/12">
|
||||
<SchemaForm
|
||||
{schema}
|
||||
inputTransform={true}
|
||||
importPath={indexes}
|
||||
bind:pickableProperties={stepPropPicker.pickableProperties}
|
||||
bind:args={flowModule.input_transforms}
|
||||
bind:extraLib={stepPropPicker.extraLib}
|
||||
/>
|
||||
</div>
|
||||
</TabContent>
|
||||
<TabContent value="preview">
|
||||
<div class="overflow-hidden bg-white" style="height:calc(100% - 32px);">
|
||||
<TabContent value="inputs" class="flex flex-col flex-1 h-full">
|
||||
<PropPickerWrapper bind:pickableProperties={stepPropPicker.pickableProperties}>
|
||||
<SchemaForm
|
||||
{schema}
|
||||
inputTransform={true}
|
||||
importPath={indexes}
|
||||
bind:args={flowModule.input_transforms}
|
||||
bind:extraLib={stepPropPicker.extraLib}
|
||||
/>
|
||||
</PropPickerWrapper>
|
||||
</TabContent>
|
||||
<TabContent value="test" class="flex flex-col flex-1 h-full">
|
||||
<div class="p-4 overflow-y-auto">
|
||||
<FlowPreview flow={$flowStore} {indexes} {schema} />
|
||||
</TabContent>
|
||||
<TabContent value="settings">
|
||||
{#if ('path' in flowModule.value && flowModule.value.path) || ('language' in flowModule.value && flowModule.value.language)}
|
||||
<input
|
||||
on:click|stopPropagation={() => undefined}
|
||||
class="overflow-x-auto"
|
||||
type="text"
|
||||
bind:value={flowModule.summary}
|
||||
placeholder="Summary"
|
||||
/>
|
||||
{/if}
|
||||
</TabContent>
|
||||
</div>
|
||||
</TabContent>
|
||||
|
||||
<TabContent value="advanced">
|
||||
<TabContent value="advanced" class="flex flex-col flex-1 h-full">
|
||||
<div class="p-4 overflow-y-auto">
|
||||
<FlowModuleAdvancedSettings bind:flowModule />
|
||||
</TabContent>
|
||||
</div>
|
||||
</div>
|
||||
</TabContent>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import FlowModule from './FlowModule.svelte'
|
||||
|
||||
const { selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const { selectedId, previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
function selectedIdToIndexes(selectedId: string): number[] {
|
||||
return selectedId.split('-').map(Number)
|
||||
@@ -17,8 +17,8 @@
|
||||
{#each $flowStateStore.modules[parentIndex].childFlowModules ?? [] as fa, index}
|
||||
{#if index === childIndex}
|
||||
<FlowModule
|
||||
args={previewArgs}
|
||||
indexes={$selectedId}
|
||||
args={{}}
|
||||
bind:flowModule={fa.flowModule}
|
||||
bind:schema={fa.schema}
|
||||
bind:childFlowModules={fa.childFlowModules}
|
||||
@@ -33,8 +33,8 @@
|
||||
{#each $flowStateStore.modules ?? [] as fa, index}
|
||||
{#if index === parentIndex}
|
||||
<FlowModule
|
||||
args={$previewArgs}
|
||||
indexes={$selectedId}
|
||||
args={{}}
|
||||
bind:flowModule={fa.flowModule}
|
||||
bind:schema={fa.schema}
|
||||
bind:childFlowModules={fa.childFlowModules}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<Tab value="configuration">Configuration</Tab>
|
||||
<Tab value="schedule">Schedule</Tab>
|
||||
<svelte:fragment slot="content">
|
||||
<TabContent value="configuration" class="px-4">
|
||||
<TabContent value="configuration" class="p-4">
|
||||
<Path bind:path={$flowStore.path} initialPath={path} namePlaceholder="my_flow" kind="flow">
|
||||
<div slot="ownerToolkit">
|
||||
Flow permissions depend on their path. Select the group <span class="font-mono"
|
||||
@@ -42,7 +42,7 @@
|
||||
/>
|
||||
</label>
|
||||
</TabContent>
|
||||
<TabContent value="schedule" class="px-4">
|
||||
<TabContent value="schedule" class="p-4">
|
||||
<FlowSchedules />
|
||||
</TabContent>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import {
|
||||
CompletedJob,
|
||||
Job,
|
||||
Script,
|
||||
ScriptService,
|
||||
type Flow,
|
||||
type FlowModule,
|
||||
type RawScript
|
||||
} from '$lib/gen'
|
||||
import { CompletedJob, Job, Script, ScriptService, type FlowModule, type RawScript } from '$lib/gen'
|
||||
import { initialCode } from '$lib/script_helpers'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import {
|
||||
@@ -208,10 +200,11 @@ export function getStepPropPicker(
|
||||
args: Record<string, any>
|
||||
): StepPropPicker {
|
||||
const isInsideLoop: boolean = indexes.length > 1
|
||||
const [parentIndex] = indexes
|
||||
const [parentIndex, childIndex] = indexes
|
||||
|
||||
const flowInput = schemaToObject(flowInputSchema, args)
|
||||
const results = getPreviousResults(flowState.modules, parentIndex)
|
||||
|
||||
const lastResult = results.length > 0 ? results[results.length - 1] : undefined
|
||||
|
||||
if (isInsideLoop) {
|
||||
@@ -281,25 +274,30 @@ export type JobResult = {
|
||||
export function mapJobResultsToFlowState(
|
||||
jobs: JobResult,
|
||||
config: 'upto' | 'justthis',
|
||||
i: number,
|
||||
parentIndex: number,
|
||||
j: number | undefined
|
||||
): void {
|
||||
if (!Array.isArray(jobs.innerJobs) || jobs.innerJobs.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (config === 'justthis') {
|
||||
const job = jobs.job as CompletedJob
|
||||
|
||||
flowStateStore.update((flowState: FlowState) => {
|
||||
if (j) {
|
||||
flowState[i].childFlowModules[j].previewResult = job.result
|
||||
} else {
|
||||
flowState[i].previewResult = job.result
|
||||
if (flowState.modules) {
|
||||
const childFlowModules = flowState.modules[parentIndex].childFlowModules
|
||||
if (j && childFlowModules) {
|
||||
childFlowModules[j].previewResult = job.result
|
||||
flowState.modules[parentIndex].childFlowModules = childFlowModules
|
||||
} else {
|
||||
flowState.modules[parentIndex].previewResult = job.result
|
||||
}
|
||||
}
|
||||
|
||||
return flowState
|
||||
})
|
||||
} else {
|
||||
if (!Array.isArray(jobs.innerJobs) || jobs.innerJobs.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const results = jobs.innerJobs.map(({ job, loopJobs }) => {
|
||||
if (Array.isArray(loopJobs) && loopJobs.length > 0) {
|
||||
return loopJobs.map(({ job }) => {
|
||||
@@ -315,12 +313,12 @@ export function mapJobResultsToFlowState(
|
||||
})
|
||||
|
||||
flowStateStore.update((flowState: FlowState) => {
|
||||
if (!Array.isArray(flowState)) {
|
||||
if (!Array.isArray(flowState.modules)) {
|
||||
return flowState
|
||||
}
|
||||
|
||||
const modules = flowState.modules.map((flowModuleSchema: FlowModuleSchema, index) => {
|
||||
if (index <= i) {
|
||||
const modules = flowState.modules.map((flowModuleSchema: FlowModuleSchema, index: number) => {
|
||||
if (index <= parentIndex) {
|
||||
flowModuleSchema.previewResult = results[index]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<script context="module" lang="ts">
|
||||
type InsertionMode = 'append' | 'connect' | 'insert'
|
||||
|
||||
type SelectCallback = (path: string) => void
|
||||
|
||||
type PropPickerConfig = {
|
||||
insertionMode: InsertionMode
|
||||
propName: string
|
||||
onSelect: SelectCallback
|
||||
}
|
||||
|
||||
export type PropPickerWrapperContext = {
|
||||
propPickerConfig: Writable<PropPickerConfig | undefined>
|
||||
focusProp: (propName: string, insertionMode: InsertionMode, onSelect: SelectCallback) => void
|
||||
clearFocus: () => void
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import PropPicker from '$lib/components/propertyPicker/PropPicker.svelte'
|
||||
import { setContext } from 'svelte'
|
||||
import { HSplitPane } from 'svelte-split-pane'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export let pickableProperties: Object = {}
|
||||
|
||||
const propPickerConfig = writable<PropPickerConfig | undefined>(undefined)
|
||||
|
||||
setContext<PropPickerWrapperContext>('PropPickerWrapper', {
|
||||
propPickerConfig,
|
||||
focusProp: (propName, insertionMode, onSelect) => {
|
||||
propPickerConfig.set({
|
||||
propName,
|
||||
insertionMode,
|
||||
onSelect
|
||||
})
|
||||
},
|
||||
clearFocus: () => {
|
||||
propPickerConfig.set(undefined)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<HSplitPane leftPaneSize="50%" rightPaneSize="50%" minLeftPaneSize="20%" minRightPaneSize="20%">
|
||||
<left slot="left" class="relative">
|
||||
<div class=" overflow-auto h-full p-4">
|
||||
<slot />
|
||||
</div>
|
||||
</left>
|
||||
<right slot="right">
|
||||
<div class="overflow-auto h-full">
|
||||
<PropPicker
|
||||
{pickableProperties}
|
||||
on:select={({ detail }) => {
|
||||
$propPickerConfig?.onSelect(detail)
|
||||
propPickerConfig.set(undefined)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</right>
|
||||
</HSplitPane>
|
||||
@@ -6,4 +6,5 @@ export type FlowEditorContext = {
|
||||
select: (id: string) => void
|
||||
schedule: Writable<Schedule>
|
||||
path: string
|
||||
previewArgs: Writable<Record<string, any>>
|
||||
}
|
||||
|
||||
@@ -39,15 +39,19 @@
|
||||
|
||||
{#if keys.length > 0}
|
||||
<span class:hidden={collapsed}>
|
||||
{#if level != 0}<span class="cursor-pointer hover:bg-slate-200 px-1 rounded" on:click={collapse}
|
||||
>(-)</span
|
||||
>{/if}
|
||||
{#if level != 0}<span
|
||||
class="cursor-pointer hover:bg-slate-200 px-1 rounded"
|
||||
on:click={collapse}
|
||||
>
|
||||
(-)
|
||||
</span>
|
||||
{/if}
|
||||
<ul class="w-full">
|
||||
{#each keys as key, index}
|
||||
<li class="pt-1">
|
||||
<button on:click={() => selectProp(key)} class="key rounded px-1 hover:bg-sky-100"
|
||||
>{!isArray ? key : index}:</button
|
||||
>
|
||||
<button on:click={() => selectProp(key)} class="key rounded px-1 hover:bg-sky-100">
|
||||
{!isArray ? key : index}:
|
||||
</button>
|
||||
|
||||
{#if getTypeAsString(json[key]) === 'object'}
|
||||
<svelte:self
|
||||
@@ -90,6 +94,7 @@
|
||||
padding-left: 1rem;
|
||||
border-left: 1px dotted lightgray;
|
||||
@apply text-black;
|
||||
@apply text-sm;
|
||||
}
|
||||
|
||||
.val {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createPopperActions } from 'svelte-popperjs'
|
||||
import PropPicker from './PropPicker.svelte'
|
||||
|
||||
const [popperRef, popperContent] = createPopperActions({
|
||||
placement: 'bottom',
|
||||
strategy: 'fixed'
|
||||
})
|
||||
|
||||
export let pickableProperties: Object | undefined
|
||||
let isOpen = false
|
||||
let isFocused = false
|
||||
|
||||
let timeout: NodeJS.Timeout
|
||||
|
||||
type PickerVariation = 'append' | 'connect'
|
||||
let pickerVariation: PickerVariation = 'append'
|
||||
|
||||
export function unfocus() {
|
||||
isFocused = false
|
||||
close()
|
||||
}
|
||||
export function focus(newPickerVariation?: PickerVariation) {
|
||||
if (newPickerVariation) {
|
||||
pickerVariation = newPickerVariation
|
||||
}
|
||||
isFocused = true
|
||||
open()
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (isFocused) {
|
||||
clearTimeout(timeout)
|
||||
!isOpen && (isOpen = true)
|
||||
}
|
||||
}
|
||||
function close() {
|
||||
if (pickerVariation === 'append') {
|
||||
timeout = setTimeout(() => (isOpen = false), 50)
|
||||
}
|
||||
}
|
||||
|
||||
function closePropertyPicker() {
|
||||
timeout = setTimeout(() => (isOpen = false), 50)
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div use:popperRef on:click={open} on:input={open} on:mouseleave={close}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
{#if isOpen}
|
||||
<div class="content" use:popperContent on:mouseenter={open} on:mouseleave={closePropertyPicker}>
|
||||
<PropPicker
|
||||
bind:pickableProperties
|
||||
on:select={(event) => {
|
||||
dispatch('select', { propPath: event.detail, pickerVariation })
|
||||
isOpen = false
|
||||
pickerVariation = 'append'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
@apply drop-shadow-xl;
|
||||
@apply w-full;
|
||||
@apply max-w-4xl;
|
||||
@apply px-6;
|
||||
@apply z-50;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { faClose } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import type { PropPickerWrapperContext } from '../flows/propPicker/PropPickerWrapper.svelte'
|
||||
|
||||
import ObjectViewer from './ObjectViewer.svelte'
|
||||
import { keepByKey } from './utils'
|
||||
|
||||
@@ -7,20 +12,44 @@
|
||||
const EMPTY_STRING = ''
|
||||
let search = ''
|
||||
|
||||
const { propPickerConfig, clearFocus } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||
|
||||
$: propsFiltered =
|
||||
search === EMPTY_STRING ? pickableProperties : keepByKey(pickableProperties, search)
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="relative p-3 bg-white rounded-lg border border-gray-200 shadow-md max-h-max overflow-y-scroll"
|
||||
>
|
||||
<div class="overflow-y-auto max-h-96">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={search}
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg block p-2 mb-2"
|
||||
placeholder="Search prop..."
|
||||
/>
|
||||
<ObjectViewer json={propsFiltered} on:select />
|
||||
<div class="px-4 pt-4">
|
||||
<div class="flex justify-between items-center h-8">
|
||||
<span class="font-bold text-sm">Context</span>
|
||||
<div class="flex space-x-2 items-center">
|
||||
{#if $propPickerConfig}
|
||||
<span
|
||||
class="flex items-center bg-blue-100 text-blue-800 text-xs font-semibold px-2 py-1 rounded dark:bg-green-200 dark:text-green-900"
|
||||
>
|
||||
{`Selected input: ${$propPickerConfig?.propName}`}
|
||||
</span>
|
||||
<span
|
||||
class="flex items-center bg-blue-100 text-blue-800 text-xs font-semibold px-2 py-1 rounded dark:bg-green-200 dark:text-green-900"
|
||||
>
|
||||
{`Mode: ${$propPickerConfig?.insertionMode}`}
|
||||
</span>
|
||||
<button
|
||||
class="border px-2 py-1 text-xs rounded-md flex items-center hover:bg-gray-50 hover:text-gray-900"
|
||||
on:click={() => clearFocus()}
|
||||
>
|
||||
<Icon data={faClose} class="mr-2" scale={0.8} />
|
||||
Deselect
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={search}
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg block p-2 mb-2"
|
||||
placeholder="Search prop..."
|
||||
/>
|
||||
</div>
|
||||
<div class="overflow-y-auto mb-8">
|
||||
<ObjectViewer json={propsFiltered} on:select />
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<Tab value="last_save"><span class="text-xs">Last save</span></Tab>
|
||||
|
||||
<svelte:fragment slot="content">
|
||||
<div class="px-4">
|
||||
<div class="p-4">
|
||||
<TabContent value="logs">
|
||||
<pre
|
||||
class="whitespace-pre-wrap break-all relative h-full bg-gray-50 text-xs p-2">{#if previewJob && previewJob.logs}{previewJob.logs}
|
||||
|
||||
@@ -54,8 +54,9 @@ export function displayDate(dateString: string | undefined): string {
|
||||
if (date.toString() === 'Invalid Date') {
|
||||
return ''
|
||||
} else {
|
||||
return `${date.getFullYear()}/${date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString()}`
|
||||
return `${date.getFullYear()}/${
|
||||
date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString()}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,7 +562,6 @@ export function classNames(...classes: string[]): string {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
|
||||
export function scriptLangToEditorLang(lang: Script.language): 'typescript' | 'python' | 'go' {
|
||||
if (lang == 'deno') {
|
||||
return 'typescript'
|
||||
@@ -570,4 +570,4 @@ export function scriptLangToEditorLang(lang: Script.language): 'typescript' | 'p
|
||||
} else {
|
||||
return lang
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user