diff --git a/frontend/src/lib/components/Dev.svelte b/frontend/src/lib/components/Dev.svelte index 9be5e33a75..918ccd1f3e 100644 --- a/frontend/src/lib/components/Dev.svelte +++ b/frontend/src/lib/components/Dev.svelte @@ -476,7 +476,7 @@ let ids = dfs(flowStore.val.value.modules ?? [], (m) => m.id) flowStateStore.val = Object.fromEntries(ids.map((k) => [k, {}])) } catch (e) {} - inferModuleArgs(selectionManager.getSelectedId()!) + inferModuleArgs(selectedId!) } } catch (e) { console.error('issue setting new flowstore', e) @@ -620,7 +620,7 @@ flowStore.val && untrack(() => updateFlow(flowStore.val)) }) $effect(() => { - selectionManager.getSelectedId() && untrack(() => inferModuleArgs(selectionManager.getSelectedId()!)) + selectedId && untrack(() => inferModuleArgs(selectedId!)) }) let localModuleStates: Record = $state({}) @@ -642,7 +642,7 @@ job.success && flowPreviewButtons?.getPreviewMode() === 'whole' ) { - if (flowModuleSchemaMap?.isNodeVisible('result') && selectionManager.getSelectedId() !== 'Result') { + if (flowModuleSchemaMap?.isNodeVisible('result') && selectedId !== 'Result') { outputPickerOpenFns['Result']?.() } } else { @@ -667,6 +667,8 @@ } const flowHasChanged = $derived(flowPreviewContent?.flowHasChanged()) + + const selectedId = $derived(selectionManager.getSelectedId()) diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index f23523cac5..cea29e4c9f 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -564,7 +564,7 @@ encodeState({ flow: flowStore.val, path: $pathStore, - selectedId: selectionManager.getSelectedId(), + selectedId: selectedIdStore, draft_triggers: triggersState.getDraftTriggersSnapshot(), selected_trigger: triggersState.getSelectedTriggerSnapshot(), loadedFromHistory: { @@ -580,6 +580,7 @@ } const selectionManager = new SelectionManager() + const selectedIdStore = $derived(selectionManager.getSelectedId()) // Initialize with selected id if provided if (selectedId) { selectionManager.selectId(selectedId) @@ -588,7 +589,7 @@ } export function getSelectedId() { - return selectionManager.getSelectedId() + return selectedIdStore } const previewArgsStore = $state({ val: initialArgs }) @@ -721,7 +722,7 @@ case 'ArrowDown': { if (!$insertButtonOpen && !flowPreviewButtons?.getPreviewOpen()) { let ids = generateIds() - let idx = ids.indexOf(selectionManager.getSelectedId()!) + let idx = ids.indexOf(selectedIdStore!) if (idx > -1 && idx < ids.length - 1) { selectionManager.selectId(ids[idx + 1]) event.preventDefault() @@ -732,7 +733,7 @@ case 'ArrowUp': { if (!$insertButtonOpen && !flowPreviewButtons?.getPreviewOpen()) { let ids = generateIds() - let idx = ids.indexOf(selectionManager.getSelectedId()!) + let idx = ids.indexOf(selectedIdStore!) if (idx > 0 && idx < ids.length) { selectionManager.selectId(ids[idx - 1]) event.preventDefault() @@ -881,7 +882,7 @@ setContext('customUi', customUi) }) $effect.pre(() => { - if (flowStore.val || selectionManager.getSelectedId()) { + if (flowStore.val || selectedIdStore) { readFieldsRecursively(flowStore.val) untrack(() => saveSessionDraft()) } @@ -945,7 +946,7 @@ job.success && flowPreviewButtons?.getPreviewMode() === 'whole' ) { - if (flowEditor?.isNodeVisible('result') && selectionManager.getSelectedId() !== 'Result') { + if (flowEditor?.isNodeVisible('result') && selectedIdStore !== 'Result') { outputPickerOpenFns['Result']?.() } } else { diff --git a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte index f2496cb0bf..89dfce0574 100644 --- a/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte +++ b/frontend/src/lib/components/copilot/chat/flow/FlowAIChat.svelte @@ -27,6 +27,7 @@ const { flowStore, flowStateStore, selectionManager, currentEditor } = getContext('FlowEditorContext') + const selectedId = $derived(selectionManager.getSelectedId()) const { exprsToSet } = getContext('FlowCopilotContext') ?? {} @@ -84,7 +85,7 @@ const flow = $state.snapshot(flowStore).val return { flow, - selectedId: selectionManager.getSelectedId()! + selectedId: selectedId! } }, // flow apply/reject @@ -382,7 +383,7 @@ value: match[2].trim() })) - if (id === selectionManager.getSelectedId()!) { + if (id === selectedId!) { exprsToSet?.set({}) const argsToUpdate = {} for (const { input, value } of parsedInputs) { @@ -611,7 +612,7 @@ $effect(() => { const cleanup = aiChatManager.listenForSelectedIdChanges( - selectionManager.getSelectedId(), + selectedId, flowStore.val, flowStateStore.val, $currentEditor @@ -626,7 +627,6 @@ // Automatically show revert review when selecting a rawscript module with pending changes $effect(() => { - const selectedId = selectionManager.getSelectedId() if ( $currentEditor?.type === 'script' && selectedId && diff --git a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte index d5b4be0a3c..b370d4c184 100644 --- a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte +++ b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte @@ -66,6 +66,8 @@ flowInputEditorState } = getContext('FlowEditorContext') + const selectedId = $derived(selectionManager.getSelectedId()) + const { showCaptureHint, triggersState, triggersCount } = getContext('TriggerContext') function checkDup(modules: FlowModule[]): string | undefined { @@ -96,9 +98,9 @@ {/each} -{:else if selectionManager.getSelectedId()?.startsWith('settings')} +{:else if selectedId?.startsWith('settings')} -{:else if selectionManager.getSelectedId() === 'Input'} +{:else if selectedId === 'Input'} -{:else if selectionManager.getSelectedId() === 'Result'} +{:else if selectedId === 'Result'} -{:else if selectionManager.getSelectedId() === 'constants'} +{:else if selectedId === 'constants'} -{:else if selectionManager.getSelectedId() === 'failure'} +{:else if selectedId === 'failure'} -{:else if selectionManager.getSelectedId() === 'preprocessor'} +{:else if selectedId === 'preprocessor'} -{:else if selectionManager.getSelectedId() === 'Trigger'} +{:else if selectedId === 'Trigger'} { @@ -153,7 +155,7 @@ schema={flowStore.val.schema} {onDeployTrigger} /> -{:else if selectionManager.getSelectedId()?.startsWith('subflow:')} +{:else if selectedId?.startsWith('subflow:')}
Selected step is witin an expanded subflow and is not directly editable in the flow editor
@@ -162,7 +164,7 @@ {#if dup}
There are duplicate modules in the flow at id: {dup}
{:else} - {#key selectionManager.getSelectedId()} + {#key selectedId} {#each flowStore.val.value.modules as flowModule, index (flowModule.id ?? index)} ('FlowEditorContext') + const selectedId = $derived(selectionManager.getSelectedId()) + interface Props { flowModule: FlowModule failureModule?: boolean @@ -215,7 +217,7 @@ let stepHistoryLoader = getStepHistoryLoaderContext() function onSelectedIdChange() { - if (!flowStateStore?.val?.[selectionManager.getSelectedId()!]?.schema && flowModule) { + if (!flowStateStore?.val?.[selectedId!]?.schema && flowModule) { reload(flowModule) } } @@ -252,7 +254,7 @@ ) $effect.pre(() => { - selectionManager.getSelectedId() && untrack(() => onSelectedIdChange()) + selectedId && untrack(() => onSelectedIdChange()) }) let parentLoop = $derived( flowStore.val && flowModule ? checkIfParentLoop(flowStore.val, flowModule.id) : undefined @@ -404,7 +406,7 @@ on:createScriptFromInlineScript={async () => { const [module, state] = await createScriptFromInlineScript( flowModule, - selectionManager.getSelectedId()!, + selectedId!, flowStateStore.val[flowModule.id].schema, $pathStore ) @@ -468,7 +470,7 @@ automaticLayout={true} cmdEnterAction={async () => { selected = 'test' - if (selectionManager.getSelectedId() == flowModule.id) { + if (selectedId == flowModule.id) { if (flowModule.value.type === 'rawscript' && editor) { flowModule.value.content = editor.getCode() } @@ -578,8 +580,7 @@ class="px-2 xl:px-4" bind:this={inputTransformSchemaForm} pickableProperties={stepPropPicker.pickableProperties} - schema={flowStateStore.val[selectionManager.getSelectedId()!]?.schema ?? - {}} + schema={flowStateStore.val[selectedId!]?.schema ?? {}} previousModuleId={previousModule?.id} bind:args={ () => { @@ -610,7 +611,7 @@ bind:this={modulePreview} mod={flowModule} {noEditor} - schema={flowStateStore.val[selectionManager.getSelectedId()!]?.schema ?? {}} + schema={flowStateStore.val[selectedId!]?.schema ?? {}} bind:testJob bind:testIsLoading bind:scriptProgress @@ -624,7 +625,7 @@ active={flowModule.retry !== undefined} label="Retries" /> - {#if !selectionManager.getSelectedId()?.includes('failure')} + {#if !selectedId?.includes('failure')} ('FlowEditorContext') + const selectedId = $derived(selectionManager.getSelectedId()) const { triggersState, triggersCount } = getContext('TriggerContext') @@ -113,7 +114,7 @@ } -{#if flowModule.id === selectionManager.getSelectedId()!} +{#if flowModule.id === selectedId!} {#if flowModule.value.type === 'forloopflow'} {:else if flowModule.value.type === 'whileloopflow'} @@ -123,13 +124,13 @@ {:else if flowModule.value.type === 'branchall'} {:else if flowModule.value.type === 'identity'} - {#if selectionManager.getSelectedId() == 'failure'} + {#if selectedId == 'failure'}
If defined, the error handler will take the error as input.
- {:else if selectionManager.getSelectedId() == 'preprocessor'} + {:else if selectedId == 'preprocessor'}
{ const { path, summary, kind, hash } = detail createModuleFromScript(path, summary, kind, hash) @@ -187,8 +188,8 @@ flowModule = module flowStateStore.val[module.id] = state }} - failureModule={selectionManager.getSelectedId() === 'failure'} - preprocessorModule={selectionManager.getSelectedId() === 'preprocessor'} + failureModule={selectedId === 'failure'} + preprocessorModule={selectedId === 'preprocessor'} /> {/if} {:else if flowModule.value.type === 'rawscript' || flowModule.value.type === 'script' || flowModule.value.type === 'flow' || flowModule.value.type === 'aiagent'} @@ -197,8 +198,8 @@ bind:flowModule {parentModule} {previousModule} - failureModule={selectionManager.getSelectedId() === 'failure'} - preprocessorModule={selectionManager.getSelectedId() === 'preprocessor'} + failureModule={selectedId === 'failure'} + preprocessorModule={selectedId === 'preprocessor'} {scriptKind} {scriptTemplate} {enableAi} @@ -225,7 +226,7 @@ /> {/each} {:else if flowModule.value.type === 'branchone'} - {#if selectionManager.getSelectedId() === `${flowModule?.id}-branch-default`} + {#if selectedId === `${flowModule?.id}-branch-default`}

Default branch

Nothing to configure, this is the default branch if none of the predicates are met. @@ -247,7 +248,7 @@ {/each} {/if} {#each flowModule.value.branches as branch, branchIndex (branchIndex)} - {#if selectionManager.getSelectedId() === `${flowModule?.id}-branch-${branchIndex}`} + {#if selectedId === `${flowModule?.id}-branch-${branchIndex}`} {:else} {#each branch.modules as _, index} @@ -295,7 +296,7 @@ {/each} {:else if flowModule.value.type === 'aiagent'} {#each flowModule.value.tools as tool, toolIndex (toolIndex)} - {#if selectionManager.getSelectedId() === tool.id} + {#if selectedId === tool.id} ('FlowEditorContext') + const selectedId = $derived(selectionManager.getSelectedId())
@@ -41,7 +42,7 @@ unifiedSize="sm" wrapperClasses="min-w-36" startIcon={{ icon: Settings }} - selected={selectionManager.getSelectedId()?.startsWith('settings')} + selected={selectedId?.startsWith('settings')} variant="default" title="Settings" onClick={() => selectionManager.selectId('settings')} @@ -64,7 +65,7 @@ wrapperClasses="h-full" unifiedSize="sm" startIcon={{ icon: DollarSign }} - selected={selectionManager.getSelectedId() === 'constants'} + selected={selectedId === 'constants'} variant="default" iconOnly onClick={() => selectionManager.selectId('constants')} diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index f3a8c46650..34178f4680 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -179,7 +179,7 @@ testModuleStates = undefined, moduleActions = undefined, inputSchemaModified = undefined, - selectionManager = undefined, + selectionManager: selectionManagerProp = undefined, path = undefined, newFlow = false, insertable = false, @@ -234,10 +234,11 @@ let noteTextHeights = $state>({}) // Selection manager - create one if not provided - let actualSelectionManager = selectionManager || new SelectionManager() + let selectionManager = selectionManagerProp || new SelectionManager() + const selectedId = $derived(selectionManager.getSelectedId()) setGraphContext({ - selectionManager: actualSelectionManager, + selectionManager: selectionManager, useDataflow, showAssets }) @@ -365,7 +366,7 @@ let eventHandler = { deleteBranch: (detail, label) => { - actualSelectionManager.selectId(label) + selectionManager.selectId(label) onDeleteBranch?.(detail) }, insert: (detail) => { @@ -375,7 +376,7 @@ if (!notSelectable) { // TODO: Handle Ctrl/Cmd and Shift modifiers when node-level click events are available // For now, normal click behavior - actualSelectionManager.selectId(modId) + selectionManager.selectId(modId) onSelect?.(modId) } }, @@ -452,7 +453,7 @@ // Keyboard event handling function handleKeyDown(event: KeyboardEvent) { - actualSelectionManager.handleKeyDown(event, nodes) + selectionManager.handleKeyDown(event, nodes) } function handleKeyUp(_event: KeyboardEvent) { @@ -577,7 +578,7 @@ testModuleStates: untrack(() => testModuleStates), moduleActions: untrack(() => moduleActions), inputSchemaModified: untrack(() => inputSchemaModified), - selectedId: untrack(() => actualSelectionManager.getSelectedId()), + selectedId: untrack(() => selectedId), path, newFlow, cache, @@ -599,7 +600,7 @@ eventHandler, success, $useDataflow, - untrack(() => actualSelectionManager.getSelectedId()), + untrack(() => selectedId), moving, simplifiableFlow, triggerNode ? path : undefined, @@ -642,14 +643,14 @@ }) let showDataflow = $derived( - actualSelectionManager.getSelectedId() !== undefined && - actualSelectionManager.getSelectedId() !== null && - !actualSelectionManager.getSelectedId()?.startsWith('constants') && - !actualSelectionManager.getSelectedId()?.startsWith('settings') && - actualSelectionManager.getSelectedId() !== 'failure' && - actualSelectionManager.getSelectedId() !== 'preprocessor' && - actualSelectionManager.getSelectedId() !== 'Result' && - actualSelectionManager.getSelectedId() !== 'Trigger' + selectedId !== undefined && + selectedId !== null && + !selectedId?.startsWith('constants') && + !selectedId?.startsWith('settings') && + selectedId !== 'failure' && + selectedId !== 'preprocessor' && + selectedId !== 'Result' && + selectedId !== 'Trigger' ) let debouncedWidth: number | undefined = $state(undefined) let timeout: number | undefined = $state(undefined) @@ -721,8 +722,8 @@ { document.dispatchEvent(new Event('focus')) - if (actualSelectionManager.mode === 'normal') { - actualSelectionManager.clearSelection() + if (selectionManager.mode === 'normal') { + selectionManager.clearSelection() } }} onnodedragstop={(event) => { @@ -759,22 +760,20 @@ {#if multiSelectEnabled} !id.startsWith('Settings') && !id.startsWith('Trigger') && !id.startsWith('Result') )} > - actualSelectionManager.selectedIds.includes(node.id) - )} + selectedNodes={nodes.filter((node) => selectionManager.selectedIds.includes(node.id))} /> - actualSelectionManager.selectNodes(nodeIds, addToExisting, modules, nodes)} + selectionManager.selectNodes(nodeIds, addToExisting, modules, nodes)} {nodes} /> {/if} @@ -789,19 +788,19 @@
{ - actualSelectionManager.mode = - actualSelectionManager.mode === 'normal' ? 'rect-select' : 'normal' + selectionManager.mode = + selectionManager.mode === 'normal' ? 'rect-select' : 'normal' }} title="Toggle rectangle selection" - class={actualSelectionManager.mode === 'rect-select' + class={selectionManager.mode === 'rect-select' ? 'text-accent !bg-surface-selected' : ''} > - {#if actualSelectionManager.selectedIds.length > 0} + {#if selectionManager.selectedIds.length > 0} {actualSelectionManager.selectedIds.length} selected{selectionManager.selectedIds.length} selected {/if}