Files
windmill/frontend/src/lib/components/flows/content/FlowEditorDrawer.svelte
T
Ruben Fiszel 0d1cb818ee feat: preview and edit steps inside expanded subflows (#10520)
* 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
2026-08-04 20:40:35 +02:00

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 />