diff --git a/backend/migrations/20240930183601_add_preprocessor_kind.down.sql b/backend/migrations/20240930183601_add_preprocessor_kind.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20240930183601_add_preprocessor_kind.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20240930183601_add_preprocessor_kind.up.sql b/backend/migrations/20240930183601_add_preprocessor_kind.up.sql new file mode 100644 index 0000000000..d70dc181aa --- /dev/null +++ b/backend/migrations/20240930183601_add_preprocessor_kind.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TYPE SCRIPT_KIND ADD VALUE IF NOT EXISTS 'preprocessor'; \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 61554a5db8..b6ce8cd73c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -177,6 +177,11 @@ "svelte": "./package/components/icons/WindmillIcon.svelte", "default": "./package/components/icons/WindmillIcon.svelte" }, + "./components/icons/WindmillIcon2.svelte": { + "types": "./package/components/icons/WindmillIcon2.d.ts", + "svelte": "./package/components/icons/WindmillIcon2.svelte", + "default": "./package/components/icons/WindmillIcon2.svelte" + }, "./components/IconedResourceType.svelte": { "types": "./package/components/IconedResourceType.svelte.d.ts", "svelte": "./package/components/IconedResourceType.svelte", @@ -358,6 +363,9 @@ "components/icons/WindmillIcon.svelte": [ "./package/components/icons/WindmillIcon.svelte.d.ts" ], + "components/icons/WindmillIcon2.svelte": [ + "./package/components/icons/WindmillIcon2.svelte.d.ts" + ], "components/scriptEditor/LogPanel.svelte": [ "./package/components/scriptEditor/LogPanel.svelte.d.ts" ], diff --git a/frontend/src/lib/components/DefaultScripts.svelte b/frontend/src/lib/components/DefaultScripts.svelte index 4c6383c3fb..c53c590954 100644 --- a/frontend/src/lib/components/DefaultScripts.svelte +++ b/frontend/src/lib/components/DefaultScripts.svelte @@ -7,10 +7,14 @@ import DefaultScriptsInner from './DefaultScriptsInner.svelte' let drawer: Drawer + export let placement: 'left' | 'right' = 'left' + + export let size: 'xs3' | 'xs2' = 'xs2' + export let noText = false {#if $userStore?.is_admin || $userStore?.is_super_admin} - + @@ -19,8 +23,10 @@ on:click={drawer?.openDrawer} startIcon={{ icon: SettingsIcon }} color="light" - size="xs2" + {size} btnClasses="!text-tertiary" - variant="contained">defaults + {noText ? '' : 'defaults'} + {/if} diff --git a/frontend/src/lib/components/DefaultScriptsInner.svelte b/frontend/src/lib/components/DefaultScriptsInner.svelte index db0dfa2500..62809c18e3 100644 --- a/frontend/src/lib/components/DefaultScriptsInner.svelte +++ b/frontend/src/lib/components/DefaultScriptsInner.svelte @@ -6,6 +6,7 @@ import { defaultScriptLanguages } from '$lib/scripts' import Alert from './common/alert/Alert.svelte' + export let small = false $: langs = computeLangs($defaultScripts) function computeLangs(defaultScripts: WorkspaceDefaultScripts | undefined): Script['language'][] { @@ -31,24 +32,32 @@ } - + This setting is only available to admins and will affect all users in the workspace. -
+
{#each langs as lang, i (lang)}

{lang}

+ class="w-full p-2 rounded {small + ? '' + : 'border border-secondary'} grid grid-cols-3 items-center" + >

{lang}

{#if i > 0} - {/if} {#if i < langs.length - 1} - changePosition(i ?? 0, false)} + class={small ? 'mr-2 text-secondary text-sm' : 'text-lg mr-2'} + title="Move down">↓ {/if}
diff --git a/frontend/src/lib/components/Dev.svelte b/frontend/src/lib/components/Dev.svelte index 343291adb4..aa6a66b1d4 100644 --- a/frontend/src/lib/components/Dev.svelte +++ b/frontend/src/lib/components/Dev.svelte @@ -496,7 +496,8 @@ saveDraft: () => {}, initialPath: '', flowInputsStore: writable({}), - customUi: {} + customUi: {}, + insertButtonOpen: writable(false) }) $: updateFlow($flowStore) diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 5de1ecf251..f33841a4f6 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -394,6 +394,7 @@ selectedIdStore.set(selectedId) } + let insertButtonOpen = writable(false) setContext('FlowEditorContext', { selectedId: selectedIdStore, schedule: scheduleStore, @@ -408,7 +409,8 @@ saveDraft, initialPath, flowInputsStore: writable({}), - customUi + customUi, + insertButtonOpen }) async function loadSchedule() { @@ -461,20 +463,24 @@ } break case 'ArrowDown': { - let ids = generateIds() - let idx = ids.indexOf($selectedIdStore) - if (idx > -1 && idx < ids.length - 1) { - $selectedIdStore = ids[idx + 1] - event.preventDefault() + if (!$insertButtonOpen) { + let ids = generateIds() + let idx = ids.indexOf($selectedIdStore) + if (idx > -1 && idx < ids.length - 1) { + $selectedIdStore = ids[idx + 1] + event.preventDefault() + } } break } case 'ArrowUp': { - let ids = generateIds() - let idx = ids.indexOf($selectedIdStore) - if (idx > 0 && idx < ids.length) { - $selectedIdStore = ids[idx - 1] - event.preventDefault() + if (!$insertButtonOpen) { + let ids = generateIds() + let idx = ids.indexOf($selectedIdStore) + if (idx > 0 && idx < ids.length) { + $selectedIdStore = ids[idx - 1] + event.preventDefault() + } } break } @@ -561,6 +567,8 @@ kind: string app: string ask_id: number + id: number + version_id: number }[] } catch (err) { if (err.name !== 'CancelError') throw err @@ -890,7 +898,8 @@ const snakeKey = snakeCase(key) if ( schemaProperty && - (!$flowStore.schema || !(snakeKey in ($flowStore.schema.properties as any) ?? {})) // prevent overriding flow inputs + (!$flowStore.schema || + !(snakeKey in ($flowStore?.schema?.properties ?? ({} as any)))) // prevent overriding flow inputs ) { copilotFlowInputs[snakeKey] = schemaProperty if (schema?.required.includes(snakeKey)) { diff --git a/frontend/src/lib/components/Scrollable.svelte b/frontend/src/lib/components/Scrollable.svelte new file mode 100644 index 0000000000..ff8e370e86 --- /dev/null +++ b/frontend/src/lib/components/Scrollable.svelte @@ -0,0 +1,57 @@ + + +
+
+ +
+ {#if !isAtBottom && isScrollable} +
+ {/if} +
diff --git a/frontend/src/lib/components/ToggleHubWorkspaceQuick.svelte b/frontend/src/lib/components/ToggleHubWorkspaceQuick.svelte new file mode 100644 index 0000000000..c48a33a6b1 --- /dev/null +++ b/frontend/src/lib/components/ToggleHubWorkspaceQuick.svelte @@ -0,0 +1,15 @@ + + +
+ + + + + +
diff --git a/frontend/src/lib/components/common/menu/Menu.svelte b/frontend/src/lib/components/common/menu/Menu.svelte index 173eae2208..92ced7e4be 100644 --- a/frontend/src/lib/components/common/menu/Menu.svelte +++ b/frontend/src/lib/components/common/menu/Menu.svelte @@ -54,7 +54,8 @@ 'bottom-start': 'origin-top-left left-0', 'bottom-end': 'origin-top-right right-0', 'top-start': 'origin-bottom-left left-0 bottom-0', - 'top-end': 'origin-bottom-right right-0 bottom-0' + 'top-end': 'origin-bottom-right right-0 bottom-0', + 'top-center': 'origin-top-left -top-full left-1/2 transform -translate-x-1/2 -translate-y-full' } const dispatch = createEventDispatcher() diff --git a/frontend/src/lib/components/common/popup/Popup.svelte b/frontend/src/lib/components/common/popup/Popup.svelte index e2ccc22b74..eb5c15189f 100644 --- a/frontend/src/lib/components/common/popup/Popup.svelte +++ b/frontend/src/lib/components/common/popup/Popup.svelte @@ -9,12 +9,13 @@ } export let containerClasses: string = 'rounded-lg shadow-md border p-4 bg-surface' - + export let floatingClasses: string = '' const [floatingRef, floatingContent] = createFloatingActions(floatingConfig) export let blockOpen = false export let shouldUsePortal: boolean = true export let target: string | HTMLElement | undefined = undefined + export let noTransition = false @@ -24,22 +25,30 @@
-
- - +
+ {#if !noTransition} + + +
+ +
+
+
+ {:else} +
- + {/if}
diff --git a/frontend/src/lib/components/common/popup/PopupV2.svelte b/frontend/src/lib/components/common/popup/PopupV2.svelte new file mode 100644 index 0000000000..a70bc1c2be --- /dev/null +++ b/frontend/src/lib/components/common/popup/PopupV2.svelte @@ -0,0 +1,63 @@ + + +
+ +
+ + + {#if open} +
+
{ + if (acceptClickoutside) { + acceptClickoutside = false + open = false + } + }} + > + +
+
+ {/if} +
diff --git a/frontend/src/lib/components/copilot/RegexGen.svelte b/frontend/src/lib/components/copilot/RegexGen.svelte index 0817167bf5..b2c2199385 100644 --- a/frontend/src/lib/components/copilot/RegexGen.svelte +++ b/frontend/src/lib/components/copilot/RegexGen.svelte @@ -19,7 +19,7 @@ const dispatch = createEventDispatcher() async function onGenerate() { - if (funcDesc.length <= 0) { + if (funcDesc?.length <= 0) { return } savePrompt() @@ -122,7 +122,7 @@ bind:this={input} bind:value={funcDesc} on:keypress={({ key }) => { - if (key === 'Enter' && funcDesc.length > 0) { + if (key === 'Enter' && funcDesc?.length > 0) { close(input || null) onGenerate() } @@ -139,7 +139,7 @@ close(input || null) onGenerate() }} - disabled={funcDesc.length <= 0} + disabled={funcDesc?.length <= 0} iconOnly startIcon={{ icon: Wand2 }} /> diff --git a/frontend/src/lib/components/copilot/StepGen.svelte b/frontend/src/lib/components/copilot/StepGen.svelte index 8b2aa3605b..f2a7617b70 100644 --- a/frontend/src/lib/components/copilot/StepGen.svelte +++ b/frontend/src/lib/components/copilot/StepGen.svelte @@ -110,7 +110,7 @@ f={(x) => (emptyString(x.summary) ? x.path : x.summary + ' (' + x.path + ')')} /> -
+
{ - if (funcDesc.length > 2) { + if (funcDesc?.length > 2) { getHubCompletions(funcDesc) } else { hubCompletions = [] @@ -126,14 +126,14 @@ }} placeholder="Search {trigger ? 'triggers' : 'scripts'} or AI gen" /> - {#if funcDesc.length === 0} + {#if funcDesc?.length === 0} {/if}
- {#if !disableAi && funcDesc.length > 0} + {#if !disableAi && funcDesc?.length > 0}
{/if} - {#if funcDesc.length > 0 && filteredItems.length > 0} + {#if funcDesc?.length > 0 && filteredItems?.length > 0}

Workspace {trigger ? 'Triggers' : 'Scripts'}

    - {#each filteredItems.slice(0, 3) as item (item.path)} + {#each filteredItems?.slice(0, 3) ?? [] as item (item.path)}
  • +
  • + {/each} +
+ {:else} + {#each Array(10).fill(0) as _} + + {/each} + {/if} +
diff --git a/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte b/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte new file mode 100644 index 0000000000..894c8c52fd --- /dev/null +++ b/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte @@ -0,0 +1,455 @@ + + + +
+ {#if selectedKind != 'preprocessor'} + + {#if ['script', 'trigger', 'approval', 'preprocessor', 'failure'].includes(selectedKind)} + {#if (preFilter === 'all' && owners.length > 0) || preFilter === 'workspace'} + {#if preFilter !== 'workspace'} +
Workspace Folders
+ {/if} + + {#if owners.length > 0} + {#each owners as owner (owner)} +
+ +
+ {/each} + {:else} +
+ No items found. +
+ {/if} + {/if} + + {#if preFilter === 'hub' || preFilter === 'all'} + {#if preFilter == 'all'} +
Integrations
+ {/if} + + {/if} + {:else if selectedKind === 'flow'} + {#if owners.length > 0} + {#each owners as owner (owner)} +
+ +
+ {/each} + {/if} + {/if} +
+ {/if} + + {#if kind == 'script'} + {#each topLevelNodes as [label, kind], i (label)} + { + dispatch('new', { kind }) + }} + {label} + selected={selectedByKeyboard === i} + /> + {/each} + {/if} + + {#if inlineScripts?.length > 0} +
+
New {selectedKind != 'script' ? selectedKind + ' ' : ''}script
+ {#if $userStore?.is_admin || $userStore?.is_super_admin} + {#if !openScriptSettings} + + {/if} + {/if} +
+ {#if openScriptSettings} +
+ +
+ {/if} + {#each inlineScripts as [label, lang], i (lang)} + { + if (lang == 'docker') { + if (isCloudHosted()) { + sendUserToast( + 'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.', + true, + [ + { + label: 'Learn more', + callback: () => { + window.open('https://www.windmill.dev/docs/advanced/docker', '_blank') + } + } + ] + ) + return + } + } + + dispatch('new', { + kind: 'script', + inlineScript: { + language: lang == 'docker' ? 'bash' : lang, + kind: selectedKind, + subkind: + lang == 'docker' + ? 'docker' + : selectedKind == 'preprocessor' + ? 'preprocessor' + : 'flow', + summary + } + }) + }} + /> + {/each} + {/if} + + {#if !disableAi && funcDesc?.length > 0 && kind != 'failure' && kind != 'preprocessor' && (selectedKind == 'script' || selectedKind == 'trigger') && preFilter == 'all'} +
    +
  • { + lang = 'bun' + onGenerate() + }} + /> +
  • +
  • + { + lang = 'python3' + onGenerate() + }} + /> +
  • +
+ {/if} + + {#if (!selected || selected?.kind === 'owner') && (preFilter === 'workspace' || preFilter === 'all')} + {#if !selected && (preFilter !== 'workspace' || funcDesc?.length > 0)} +
Workspace
+ {/if} + + {/if} + + {#if selectedKind != 'preprocessor' && selectedKind != 'flow'} + {#if (!selected || selected?.kind === 'integrations') && (preFilter === 'hub' || preFilter === 'all')} + {#if !selected && preFilter !== 'hub'} +
Hub
+ {/if} + + {/if} + {/if} +
+
+ + diff --git a/frontend/src/lib/components/flows/content/GenAiQuick.svelte b/frontend/src/lib/components/flows/content/GenAiQuick.svelte new file mode 100644 index 0000000000..ef59d9d6d7 --- /dev/null +++ b/frontend/src/lib/components/flows/content/GenAiQuick.svelte @@ -0,0 +1,33 @@ + + + + diff --git a/frontend/src/lib/components/flows/map/FlowErrorHandlerItem.svelte b/frontend/src/lib/components/flows/map/FlowErrorHandlerItem.svelte index 9faf6d862a..fbae0cf433 100644 --- a/frontend/src/lib/components/flows/map/FlowErrorHandlerItem.svelte +++ b/frontend/src/lib/components/flows/map/FlowErrorHandlerItem.svelte @@ -3,37 +3,52 @@ import { getContext } from 'svelte' import { classNames, emptySchema } from '$lib/utils' import type { FlowModuleState } from '../flowState' - import Toggle from '$lib/components/Toggle.svelte' import { NEVER_TESTED_THIS_FAR } from '../models' import type { FlowCopilotContext } from '$lib/components/copilot/flow' import { fade } from 'svelte/transition' - import { Bug } from 'lucide-svelte' + import { Bug, X } from 'lucide-svelte' + import InsertModuleButton from '$lib/components/flows/map/InsertModuleButton.svelte' + import { createInlineScriptModule, pickScript } from '$lib/components/flows/flowStateUtils' + import type { FlowModule, RawScript } from '$lib/gen' + import { twMerge } from 'tailwind-merge' export let small: boolean const { selectedId, flowStateStore, flowStore } = getContext('FlowEditorContext') - function onToggle() { - if ($flowStore?.value?.failure_module) { - $flowStore.value.failure_module = undefined - // By default, we return to settings when disabling the failure module - $selectedId = 'settings-metadata' - } else { - const failureModule: FlowModuleState = { - schema: emptySchema(), - previewResult: NEVER_TESTED_THIS_FAR - } - - $flowStore.value.failure_module = { - id: 'failure', - value: { type: 'identity' } - } - $flowStateStore['failure'] = failureModule - - $selectedId = 'failure' - $flowStore = $flowStore + async function insertNewFailureModule( + inlineScript?: { + language: RawScript['language'] + subkind: 'pgsql' | 'flow' + }, + wsScript?: { path: string; summary: string; hash: string | undefined } + ) { + var module: FlowModule = { + id: 'failure', + value: { type: 'identity' } } + var state: FlowModuleState = { + schema: emptySchema(), + previewResult: NEVER_TESTED_THIS_FAR + } + + if (inlineScript) { + ;[module, state] = await createInlineScriptModule( + inlineScript.language, + 'failure', + inlineScript.subkind, + 'failure' + ) + } else if (wsScript) { + ;[module, state] = await pickScript(wsScript.path, wsScript.summary, module.id, wsScript.hash) + } + + $flowStore.value.failure_module = module + $flowStateStore[module.id] = state + + $selectedId = 'failure' + $flowStore = $flowStore } const { currentStepStore: copilotCurrentStepStore } = @@ -42,45 +57,71 @@ +
{ if ($copilotCurrentStepStore !== undefined) return if ($flowStore?.value?.failure_module) { $selectedId = 'failure' - } else { - onToggle() } }} - class={classNames( - 'z-10', - $copilotCurrentStepStore !== undefined ? 'border-gray-500/75' : 'cursor-pointer', - 'border transition-colors duration-[400ms] ease-linear rounded-sm px-2 py-1 bg-surface text-sm flex justify-between items-center flex-row overflow-x-hidden relative', - $selectedId?.includes('failure') ? 'outline outline-offset-1 outline-2 outline-slate-900 dark:outline-slate-900/0 dark:bg-surface-secondary dark:border-gray-400' : '' - )} - style={small ? 'min-width: 200px' : 'min-width: 275px'} > {#if $copilotCurrentStepStore !== undefined}
{/if} -
- - Error Handler +
+
-
- {#if Boolean($flowStore?.value?.failure_module)} - - {$flowStore.value.failure_module?.summary || - ($flowStore.value.failure_module?.value.type === 'rawscript' - ? `${$flowStore.value.failure_module?.value.language}` - : 'TBD')} - - {/if} -
- + {#if !$flowStore?.value?.failure_module} +
Error Handler
+ {:else} +
+ {$flowStore.value.failure_module?.summary || + ($flowStore.value.failure_module?.value.type === 'rawscript' + ? `${$flowStore.value.failure_module?.value.language}` + : 'TBD')} +
+ {/if} + + {#if !$flowStore?.value?.failure_module} + { + insertNewFailureModule(e.detail.inlineScript) + }} + on:pickScript={(e) => { + insertNewFailureModule(undefined, e.detail) + }} + kind="failure" + /> + {:else} + + {/if}
diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index bc2af7f82e..c2919e41d7 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -2,15 +2,17 @@ import type { FlowEditorContext } from '../types' import { createEventDispatcher, getContext, tick } from 'svelte' import { + createInlineScriptModule, createBranchAll, createBranches, createLoop, createWhileLoop, deleteFlowStateById, emptyModule, - pickScript + pickScript, + pickFlow } from '$lib/components/flows/flowStateUtils' - import type { FlowModule } from '$lib/gen' + import type { FlowModule, RawScript, Script } from '$lib/gen' import { emptyFlowModuleState, initFlowStepWarnings } from '../utils' import FlowSettingsItem from './FlowSettingsItem.svelte' import FlowConstantsItem from './FlowConstantsItem.svelte' @@ -31,8 +33,6 @@ import { tutorialInProgress } from '$lib/tutorialUtils' import FlowGraphV2 from '$lib/components/graph/FlowGraphV2.svelte' import { replaceId } from '../flowStore' - import { emptySchema } from '$lib/utils' - import { NEVER_TESTED_THIS_FAR } from '../models' export let modules: FlowModule[] | undefined export let sidebarSize: number | undefined = undefined @@ -61,12 +61,22 @@ | 'trigger' | 'approval' | 'end', - wsScript?: { path: string; summary: string; hash: string | undefined } + wsScript?: { path: string; summary: string; hash: string | undefined }, + wsFlow?: { path: string; summary: string }, + inlineScript?: { + language: RawScript['language'] + kind: Script['kind'] + subkind: 'pgsql' | 'flow' + id: string + summary?: string + } ): Promise { push(history, $flowStore) var module = emptyModule($flowStateStore, $flowStore, kind == 'flow') var state = emptyFlowModuleState() - if (wsScript) { + if (wsFlow) { + ;[module, state] = await pickFlow(wsFlow.path, wsFlow.summary, module.id) + } else if (wsScript) { ;[module, state] = await pickScript(wsScript.path, wsScript.summary, module.id, wsScript.hash) } else if (kind == 'forloop') { ;[module, state] = await createLoop( @@ -89,11 +99,49 @@ module.summary = 'Terminate flow' module.stop_after_if = { skip_if_stopped: false, expr: 'true' } } + if (inlineScript) { + const { language, kind, subkind } = inlineScript + ;[module, state] = await createInlineScriptModule( + language, + kind, + subkind, + module.id, + module.summary + ) + } if (!modules) return [module] modules.splice(index, 0, module) return modules } + async function insertNewPreprocessorModule( + inlineScript?: { + language: RawScript['language'] + subkind: 'pgsql' | 'flow' + }, + wsScript?: { path: string; summary: string; hash: string | undefined } + ) { + var module: FlowModule = { + id: 'preprocessor', + value: { type: 'identity' } + } + var state = emptyFlowModuleState() + + if (inlineScript) { + ;[module, state] = await createInlineScriptModule( + inlineScript.language, + 'script', + inlineScript.subkind, + 'preprocessor' + ) + } else if (wsScript) { + ;[module, state] = await pickScript(wsScript.path, wsScript.summary, module.id, wsScript.hash) + } + + $flowStore.value.preprocessor_module = module + $flowStateStore[module.id] = state + } + function removeAtId(modules: FlowModule[], id: string): FlowModule[] { const index = modules.findIndex((mod) => mod.id == id) if (index != -1) { @@ -311,22 +359,16 @@ $moving = undefined } else { if (detail.detail === 'preprocessor') { - const preprocessorModule = { - schema: emptySchema(), - previewResult: NEVER_TESTED_THIS_FAR - } - $flowStore.value.preprocessor_module = { - id: 'preprocessor', - value: { type: 'identity' } - } - $flowStateStore['preprocessor'] = preprocessorModule + insertNewPreprocessorModule(detail.inlineScript, detail.script) $selectedId = 'preprocessor' } else { await insertNewModuleAtIndex( detail.modules, detail.index ?? 0, - detail.detail, - detail.script + detail.kind, + detail.script, + detail.flow, + detail.inlineScript ) $selectedId = detail.modules[detail.index ?? 0].id } diff --git a/frontend/src/lib/components/flows/map/InsertModuleButton.svelte b/frontend/src/lib/components/flows/map/InsertModuleButton.svelte index 40cce9648f..351299ca8d 100644 --- a/frontend/src/lib/components/flows/map/InsertModuleButton.svelte +++ b/frontend/src/lib/components/flows/map/InsertModuleButton.svelte @@ -1,169 +1,200 @@ - - + + + + + + -
- + +
{ + e.stopPropagation() + }} + role="none" + > +
+ + {#if selectedKind != 'preprocessor' && selectedKind != 'flow'} + + {/if} +
- {#if funcDesc.length === 0} -
- - {#if customUi?.triggers != false && trigger} - - {/if} - - - - - - - - - {#if customUi?.flowNode != false} - - {/if} - {#if stop} - - {/if} -
- {/if} + /> + { + close(null) + dispatch('new', { kind: 'whileloop' }) + }} + /> + { + close(null) + dispatch('new', { kind: 'branchone' }) + }} + /> + { + close(null) + dispatch('new', { kind: 'branchall' }) + }} + /> +
+ {/if} + + { + close(null) + }} + on:new + on:pickScript + on:pickFlow + {preFilter} + {small} + /> +
- + diff --git a/frontend/src/lib/components/flows/map/InsertTriggerButton.svelte b/frontend/src/lib/components/flows/map/InsertTriggerButton.svelte deleted file mode 100644 index 35dc331161..0000000000 --- a/frontend/src/lib/components/flows/map/InsertTriggerButton.svelte +++ /dev/null @@ -1,52 +0,0 @@ - - - - - {#if !disableAi} - - {/if} - {#if funcDesc.length === 0} -
- -
- {/if} -
diff --git a/frontend/src/lib/components/flows/pickers/FlowScriptPickerQuick.svelte b/frontend/src/lib/components/flows/pickers/FlowScriptPickerQuick.svelte new file mode 100644 index 0000000000..2f4319e561 --- /dev/null +++ b/frontend/src/lib/components/flows/pickers/FlowScriptPickerQuick.svelte @@ -0,0 +1,56 @@ + + + + + diff --git a/frontend/src/lib/components/flows/pickers/FlowToplevelNode.svelte b/frontend/src/lib/components/flows/pickers/FlowToplevelNode.svelte new file mode 100644 index 0000000000..0248437963 --- /dev/null +++ b/frontend/src/lib/components/flows/pickers/FlowToplevelNode.svelte @@ -0,0 +1,22 @@ + + + + + diff --git a/frontend/src/lib/components/flows/pickers/PickHubScriptQuick.svelte b/frontend/src/lib/components/flows/pickers/PickHubScriptQuick.svelte new file mode 100644 index 0000000000..122e4a3125 --- /dev/null +++ b/frontend/src/lib/components/flows/pickers/PickHubScriptQuick.svelte @@ -0,0 +1,175 @@ + + + +{#if hubNotAvailable} +
+ Hub not available +
+{:else if loading} + {#each Array(15).fill(0) as _} + + {/each} +{:else if items.length > 0 && apps.length > 0} +
    + {#each items as item, index (item.path)} +
  • + +
  • + {/each} +
+ {#if items.length == 40} +
+ There are more items than being displayed. Refine your search. +
+ {/if} +{/if} diff --git a/frontend/src/lib/components/flows/pickers/TopLevelNode.svelte b/frontend/src/lib/components/flows/pickers/TopLevelNode.svelte new file mode 100644 index 0000000000..af70d38508 --- /dev/null +++ b/frontend/src/lib/components/flows/pickers/TopLevelNode.svelte @@ -0,0 +1,60 @@ + + + diff --git a/frontend/src/lib/components/flows/pickers/WorkspaceScriptPickerQuick.svelte b/frontend/src/lib/components/flows/pickers/WorkspaceScriptPickerQuick.svelte new file mode 100644 index 0000000000..04cc468d83 --- /dev/null +++ b/frontend/src/lib/components/flows/pickers/WorkspaceScriptPickerQuick.svelte @@ -0,0 +1,136 @@ + + + (emptyString(x.summary) ? x.path : x.summary + ' (' + x.path + ')')} +/> + + +{#if filteredItems} + {#if filteredItems.length == 0} +
+ {kind == 'flow' ? 'No flows found.' : 'No scripts found.'} +
+ {/if} +
    + {#each filteredWithOwner ?? [] as { path, hash, summary, marked }, index} +
  • + +
  • + {/each} +
+{:else} + {#each Array(10).fill(0) as _} + + {/each} +{/if} diff --git a/frontend/src/lib/components/flows/types.ts b/frontend/src/lib/components/flows/types.ts index ba8ce95c5b..53f81c5a0d 100644 --- a/frontend/src/lib/components/flows/types.ts +++ b/frontend/src/lib/components/flows/types.ts @@ -41,4 +41,5 @@ export type FlowEditorContext = { initialPath: string flowInputsStore: Writable customUi: FlowBuilderWhitelabelCustomUi + insertButtonOpen: Writable } diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index 318ccaaa74..e4609f6d28 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -48,8 +48,8 @@ export let selectedId: Writable = writable(undefined) export let insertable = false - export let moving: string | undefined = undefined export let scroll = false + export let moving: string | undefined = undefined // Download: display a top level button to open the graph in a new tab export let download = false @@ -266,6 +266,9 @@
{:else} { + window.dispatchEvent(new Event('focus')) + }} {nodes} {edges} {edgeTypes} diff --git a/frontend/src/lib/components/graph/graphBuilder.ts b/frontend/src/lib/components/graph/graphBuilder.ts index eecaffc7e3..3a77c22b2c 100644 --- a/frontend/src/lib/components/graph/graphBuilder.ts +++ b/frontend/src/lib/components/graph/graphBuilder.ts @@ -549,4 +549,5 @@ export default function graphBuilder( error: e } } + } diff --git a/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte b/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte index f27e89b8e0..584fb44b25 100644 --- a/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte +++ b/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte @@ -7,7 +7,6 @@ import type { Writable } from 'svelte/store' import type { GraphEventHandlers } from '../../graphBuilder' import { getStraightLinePath } from '../utils' - import InsertTriggerButton from '$lib/components/flows/map/InsertTriggerButton.svelte' import { twMerge } from 'tailwind-merge' export let sourceX: number @@ -47,33 +46,44 @@ const { useDataflow } = getContext<{ useDataflow: Writable }>('FlowGraphContext') - - let menuOpen = false {#if data?.insertable && !$useDataflow && !data?.moving}
{ - data?.eventHandlers.insert({ modules: data.modules, index: data.index, detail: e.detail }) - }} - on:insert={(e) => { + // console.log('new', e) data?.eventHandlers.insert({ modules: data.modules, index: data.index, - script: e.detail, - detail: 'script' + kind: e.detail.kind, + inlineScript: e.detail.inlineScript + }) + }} + on:pickScript={(e) => { + // console.log('pickScript', e) + data?.eventHandlers.insert({ + modules: data.modules, + index: data.index, + script: e.detail + }) + }} + on:pickFlow={(e) => { + // console.log('pickFlow', e) + data?.eventHandlers.insert({ + modules: data.modules, + index: data.index, + flow: e.detail }) }} - bind:open={menuOpen} />
{#if data.enableTrigger} @@ -81,23 +91,34 @@ class="edgeButtonContainer nodrag nopan" style:transform="translate(100%, 50%) translate({sourceX}px,{sourceY + 2}px)" > - { + // console.log('new', e) data?.eventHandlers.insert({ modules: data.modules, index: data.index, - detail: e.detail + kind: e.detail.kind, + inlineScript: e.detail.inlineScript }) }} - on:insert={(e) => { + on:pickScript={(e) => { + // console.log('pickScript', e) data?.eventHandlers.insert({ modules: data.modules, index: data.index, - script: e.detail, - detail: 'script' + script: e.detail }) }} + on:pickFlow={(e) => { + // console.log('pickFlow', e) + data?.eventHandlers.insert({ + modules: data.modules, + index: data.index, + flow: e.detail + }) + }} + kind="trigger" index={data?.index ?? 0} modules={data?.modules ?? []} /> diff --git a/frontend/src/lib/components/graph/renderers/nodes/InputNode.svelte b/frontend/src/lib/components/graph/renderers/nodes/InputNode.svelte index 0cacdbfc47..7690fae71d 100644 --- a/frontend/src/lib/components/graph/renderers/nodes/InputNode.svelte +++ b/frontend/src/lib/components/graph/renderers/nodes/InputNode.svelte @@ -1,21 +1,24 @@ + +{#if Array.isArray(filters) && filters.length > 0} + {#each filters as filter (filter)} +
+ +
+ {/each} +{/if} diff --git a/frontend/src/lib/components/icons/WindmillIcon2.svelte b/frontend/src/lib/components/icons/WindmillIcon2.svelte new file mode 100644 index 0000000000..568e4cb05f --- /dev/null +++ b/frontend/src/lib/components/icons/WindmillIcon2.svelte @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/index.ts b/frontend/src/lib/components/icons/index.ts index 78ec4769d2..8fb92470d8 100644 --- a/frontend/src/lib/components/icons/index.ts +++ b/frontend/src/lib/components/icons/index.ts @@ -18,6 +18,7 @@ import S3Icon from './S3Icon.svelte' import Slack from './Slack.svelte' import TogglIcon from './TogglIcon.svelte' import WindmillIcon from './WindmillIcon.svelte' +import WindmillIcon2 from './WindmillIcon2.svelte' import MailchimpIcon from './MailchimpIcon.svelte' import SendgridIcon from './SendgridIcon.svelte' import SendflakeIcon from './SendflakeIcon.svelte' @@ -211,6 +212,7 @@ export { Slack, TogglIcon, WindmillIcon, + WindmillIcon2, MailchimpIcon, SendgridIcon, LinkedinIcon, diff --git a/frontend/src/lib/script_helpers.ts b/frontend/src/lib/script_helpers.ts index 3dcade90c0..d0e7e26570 100644 --- a/frontend/src/lib/script_helpers.ts +++ b/frontend/src/lib/script_helpers.ts @@ -350,6 +350,27 @@ export async function main() { } ` +export const BUN_INIT_CODE_TRIGGER = `import * as wmill from "windmill-client" + +export async function main() { + + // A common trigger script would follow this pattern: + // 1. Get the last saved state + // const state = await wmill.getState() + // 2. Get the actual state from the external service + // const newState = await (await fetch('https://hacker-news.firebaseio.com/v0/topstories.json')).json() + // 3. Compare the two states and update the internal state + // await wmill.setState(newState) + // 4. Return the new rows + // return range from (state to newState) + + return [1,2,3] + + // In subsequent scripts, you may refer to each row/value returned by the trigger script using + // 'flow_input.iter.value' +} +` + export const GO_INIT_CODE_TRIGGER = `package inner import ( @@ -715,7 +736,9 @@ export function initialCode( } else if (language == 'ansible') { return ANSIBLE_PLAYBOOK_INIT_CODE } else if (language == 'bun' || language == 'bunnative') { - if (language == 'bunnative' || subkind === 'bunnative') { + if (kind == 'trigger') { + return BUN_INIT_CODE_TRIGGER + } else if (language == 'bunnative' || subkind === 'bunnative') { return BUNNATIVE_INIT_CODE } else if (kind === 'approval') { return BUN_INIT_CODE_APPROVAL @@ -723,8 +746,7 @@ export function initialCode( return BUN_FAILURE_MODULE_CODE } else if (subkind === 'preprocessor') { return BUN_PREPROCESSOR_MODULE_CODE - } - if (subkind === 'flow') { + } else if (subkind === 'flow') { return BUN_INIT_CODE_CLEAR } diff --git a/frontend/src/routes/flows/dev/+page.svelte b/frontend/src/routes/flows/dev/+page.svelte index 6cd02642a4..3f12f3f5a7 100644 --- a/frontend/src/routes/flows/dev/+page.svelte +++ b/frontend/src/routes/flows/dev/+page.svelte @@ -97,7 +97,8 @@ saveDraft: () => {}, initialPath: '', flowInputsStore: writable({}), - customUi: {} + customUi: {}, + insertButtonOpen: writable(false) }) type LastEdit = {