mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-22 08:03:38 +00:00
* feat: preview and edit expanded subflow steps in the flow editor * fix: hide subflow edit button when no flow editor drawer is available * fix: address review findings on expanded subflow step panel * fix: base-prefix subflow links and bound the expanded subflow module cache * fix: do not let a pre-deploy response repopulate the invalidated subflow cache * fix: guard expanded subflow reloads against collapse and encode workspace in link * fix: commit an expanded subflow reload only onto the expansion it fetched for
140 lines
4.0 KiB
Svelte
140 lines
4.0 KiB
Svelte
<script lang="ts">
|
|
import { Drawer, DrawerContent } from '$lib/components/common'
|
|
import FlowBuilder from '$lib/components/FlowBuilder.svelte'
|
|
import DiffDrawer from '$lib/components/DiffDrawer.svelte'
|
|
import { FlowService, type Flow } from '$lib/gen'
|
|
import { workspaceStore } from '$lib/stores'
|
|
import { ExternalLink, Loader2 } from 'lucide-svelte'
|
|
import Button from '$lib/components/common/button/Button.svelte'
|
|
import { emptySchema, type StateStore } from '$lib/utils'
|
|
import { createEventDispatcher, getContext } from 'svelte'
|
|
import { fade } from 'svelte/transition'
|
|
import { initFlow } from '$lib/components/flows/flowStore.svelte'
|
|
import type { FlowState } from '$lib/components/flows/flowState'
|
|
import type { FlowEditorContext } from '../types'
|
|
import { base } from '$app/paths'
|
|
|
|
let flowEditorDrawer: Drawer | undefined = $state()
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
|
|
let opWs = $derived(flowEditorContext?.opWorkspace?.() ?? $workspaceStore)
|
|
|
|
export async function openDrawer(path: string, cb: () => void, stepId?: string): Promise<void> {
|
|
flowPath = path
|
|
flow = undefined
|
|
loading = true
|
|
callback = cb
|
|
selectedStepId = stepId
|
|
flowEditorDrawer?.openDrawer?.()
|
|
|
|
try {
|
|
const backendFlow = await FlowService.getFlowByPath({
|
|
workspace: opWs!,
|
|
path
|
|
})
|
|
|
|
savedFlow = structuredClone(backendFlow) as Flow
|
|
|
|
flow = backendFlow
|
|
|
|
await initFlow(flow, flowStore, flowStateStore, opWs)
|
|
loading = false
|
|
} catch (error: any) {
|
|
console.error('Failed to load flow:', error)
|
|
loading = false
|
|
}
|
|
}
|
|
|
|
let callback: (() => void) | undefined = undefined
|
|
let flowPath: string = $state('')
|
|
let selectedStepId: string | undefined = $state(undefined)
|
|
let flow: Flow | undefined = $state(undefined)
|
|
let savedFlow: Flow | undefined = $state(undefined)
|
|
let loading = $state(true)
|
|
|
|
const flowStore: StateStore<Flow> = $state({
|
|
val: {
|
|
summary: '',
|
|
value: { modules: [] },
|
|
path: '',
|
|
edited_at: '',
|
|
edited_by: '',
|
|
archived: false,
|
|
extra_perms: {},
|
|
schema: emptySchema()
|
|
}
|
|
})
|
|
const flowStateStore: StateStore<FlowState> = $state({ val: {} })
|
|
|
|
let diffDrawer: DiffDrawer | undefined = $state()
|
|
let flowBuilder: FlowBuilder | undefined = $state(undefined)
|
|
</script>
|
|
|
|
<Drawer bind:this={flowEditorDrawer} size="100%">
|
|
<DrawerContent
|
|
title="Flow Editor - {flowPath}"
|
|
noPadding
|
|
fullScreen
|
|
on:close={() => {
|
|
flowEditorDrawer?.closeDrawer()
|
|
}}
|
|
>
|
|
{#if loading}
|
|
<div
|
|
out:fade={{ duration: 200 }}
|
|
class="absolute inset-0 center-center flex-col bg-surface text-primary border"
|
|
>
|
|
<Loader2 class="animate-spin" size={16} />
|
|
<span class="text-xs mt-1">Loading flow...</span>
|
|
</div>
|
|
{:else if flow}
|
|
<FlowBuilder
|
|
bind:this={flowBuilder}
|
|
{flowStore}
|
|
{flowStateStore}
|
|
initialPath={flowPath}
|
|
autosaveWorkspace={opWs}
|
|
newFlow={false}
|
|
selectedId={selectedStepId ?? 'settings-metadata'}
|
|
loading={false}
|
|
bind:savedFlow
|
|
{diffDrawer}
|
|
customUi={{ topBar: { draft: false, extraDeployOptions: false } }}
|
|
onDeploy={() => {
|
|
callback?.()
|
|
dispatch('save')
|
|
flowEditorDrawer?.closeDrawer()
|
|
}}
|
|
onDetails={() => {
|
|
callback?.()
|
|
dispatch('save')
|
|
flowEditorDrawer?.closeDrawer()
|
|
}}
|
|
/>
|
|
{:else}
|
|
<div class="flex items-center justify-center h-full">
|
|
<span class="text-red-500">Failed to load flow</span>
|
|
</div>
|
|
{/if}
|
|
{#snippet actions()}
|
|
<Button
|
|
variant="default"
|
|
on:click={() => {
|
|
// Follow the drawer's own selection, which the user may have moved since it opened.
|
|
const stepId = flowBuilder?.getSelectedId() ?? selectedStepId
|
|
const selected = stepId ? `?selected=${encodeURIComponent(stepId)}` : ''
|
|
window.open(`${base}/flows/edit/${flowPath}${selected}`, '_blank', 'noopener,noreferrer')
|
|
flowEditorDrawer?.closeDrawer()
|
|
}}
|
|
startIcon={{ icon: ExternalLink }}
|
|
>
|
|
Edit in new tab & close drawer
|
|
</Button>
|
|
{/snippet}
|
|
</DrawerContent>
|
|
</Drawer>
|
|
|
|
<DiffDrawer bind:this={diffDrawer} isFlow />
|