diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index ac381bb6b2..319a00e3b4 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -53,7 +53,6 @@ import SubflowBound from './renderers/nodes/SubflowBound.svelte' import ViewportResizer from './ViewportResizer.svelte' import ViewportSynchronizer from './ViewportSynchronizer.svelte' - import InitialViewportFitter from './InitialViewportFitter.svelte' import AssetNode, { computeAssetNodes } from './renderers/nodes/AssetNode.svelte' import AssetsOverflowedNode from './renderers/nodes/AssetsOverflowedNode.svelte' import type { FlowGraphAssetContext } from '../flows/types' @@ -248,12 +247,33 @@ const noteEditorContext = getNoteEditorContext() + // Function to calculate extra gap needed for notes below the lowest flow nodes + function calculateNoteGap(notes: FlowNote[] | undefined): number { + if (!notes || notes.length === 0) { + return 0 + } + let maxNoteBelowGap = 0 + + notes.forEach((note) => { + if (note.position?.y && note.position.y < 0) { + maxNoteBelowGap = Math.max(maxNoteBelowGap, -note.position.y) + } + }) + + return maxNoteBelowGap + } + + // Calculate note gap based on current nodes and notes + const topPadding = editMode ? 100 : 24 + const yOffset = calculateNoteGap(notes) + topPadding + setGraphContext({ selectionManager: selectionManager, useDataflow, showAssets, noteManager, - clearFlowSelection + clearFlowSelection, + yOffset } as any) if (triggerContext && allowSimplifiedPoll) { @@ -438,8 +458,20 @@ let height = $state(0) - // Counter to trigger initial viewport fit after first flow build - let initialBuildTrigger = $state(0) + // Derived nodes with yOffset applied to all nodes uniformly + const nodesWithOffset = $derived.by(() => { + return nodes.map((node) => { + if ( + node.type !== 'asset' && + node.type !== 'assetsOverflowed' && + node.type !== 'newAiTool' && + node.type !== 'aiTool' + ) { + return { ...node, position: { ...node.position, y: node.position.y + yOffset } } + } + return node + }) + }) // Note feature state @@ -562,7 +594,13 @@ ] await tick() - height = Math.max(...nodes.map((n) => n.position.y + NODE.height + 100), minHeight) + if (nodes.length === 0) { + height = minHeight + } else { + const minY = Math.min(...nodes.map((n) => n.position.y)) + const maxBottom = Math.max(...nodes.map((n) => n.position.y + NODE.height + 100)) + height = Math.max(maxBottom - minY, minHeight) + } } const nodeTypes = { @@ -660,10 +698,6 @@ ;[graph, allowSimplifiedPoll, $showAssets, showNotes, noteManager.renderCount] untrack(async () => { await updateStores() - // Trigger initial viewport fit after first build - if (initialBuildTrigger === 0 && nodes.length > 0) { - initialBuildTrigger = 1 - } }) }) @@ -809,7 +843,6 @@ bind:this={viewportSynchronizer} /> {/if} - { @@ -822,13 +855,17 @@ onnodedragstop={(event) => { const node = event.targetNode if (node && node.type === 'note') { - onNotePositionUpdate?.(node.id, node.position) + const positionWithOffset = { + x: node.position.x, + y: node.position.y - yOffset + } + onNotePositionUpdate?.(node.id, positionWithOffset) } }} onmove={(event, viewport) => { viewportSynchronizer?.handleLocalViewportChange(event, viewport) }} - {nodes} + nodes={nodesWithOffset} {edges} {edgeTypes} {nodeTypes} @@ -855,7 +892,7 @@
{#if noteMode} - + {/if} {#if multiSelectEnabled} @@ -865,7 +902,10 @@ !id.startsWith('Settings') && !id.startsWith('Trigger') && !id.startsWith('Result') )} > - + {/if} diff --git a/frontend/src/lib/components/graph/InitialViewportFitter.svelte b/frontend/src/lib/components/graph/InitialViewportFitter.svelte deleted file mode 100644 index ffec0bb00c..0000000000 --- a/frontend/src/lib/components/graph/InitialViewportFitter.svelte +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/frontend/src/lib/components/graph/NoteTool.svelte b/frontend/src/lib/components/graph/NoteTool.svelte index abdc460a77..ad41bd974a 100644 --- a/frontend/src/lib/components/graph/NoteTool.svelte +++ b/frontend/src/lib/components/graph/NoteTool.svelte @@ -7,9 +7,10 @@ interface Props { exitNoteMode?: () => void + yOffset: number } - let { exitNoteMode }: Props = $props() + let { exitNoteMode, yOffset }: Props = $props() // Get NoteEditor context for direct note creation const noteEditorContext = getNoteEditorContext() @@ -61,10 +62,14 @@ y: endPosition.y + rect.top } - const position = screenToFlowPosition({ + const flowPosition = screenToFlowPosition({ x: Math.min(absoluteStartPosition.x, absoluteEndPosition.x), y: Math.min(absoluteStartPosition.y, absoluteEndPosition.y) }) + const position = { + x: flowPosition.x, + y: flowPosition.y - (yOffset || 0) + } const zoom = getViewport().zoom const size = { @@ -155,10 +160,14 @@ onpointerup={onPointerUp} oncontextmenu={(e) => { // Capture the position when context menu is triggered - contextMenuPosition = screenToFlowPosition({ + const flowPosition = screenToFlowPosition({ x: e.clientX, y: e.clientY }) + contextMenuPosition = { + x: flowPosition.x, + y: flowPosition.y - yOffset + } }} role="button" tabindex="0" diff --git a/frontend/src/lib/components/graph/PaneContextMenu.svelte b/frontend/src/lib/components/graph/PaneContextMenu.svelte index 7201cfe66b..b6b6b49ac9 100644 --- a/frontend/src/lib/components/graph/PaneContextMenu.svelte +++ b/frontend/src/lib/components/graph/PaneContextMenu.svelte @@ -9,6 +9,7 @@ CONTEXT_MENU_ITEM_BASE_CLASS, CONTEXT_MENU_ITEM_HOVER_CLASS } from '../common/contextmenu/contextMenuStyles' + import { getGraphContext } from './graphContext' interface Props { editMode?: boolean @@ -19,6 +20,8 @@ const { screenToFlowPosition } = useSvelteFlow() const noteEditorContext = getNoteEditorContext() + const graphContext = getGraphContext() + let contextMenuVisible = $state(false) let contextMenuPosition = $state<{ x: number; y: number }>({ x: 0, y: 0 }) let pendingFlowPosition = $state<{ x: number; y: number } | null>(null) @@ -51,7 +54,10 @@ if (noteEditorContext?.noteEditor && pendingFlowPosition) { noteEditorContext.noteEditor.addNote({ text: '### Free note\nDouble click to edit me', - position: pendingFlowPosition, + position: { + x: pendingFlowPosition.x, + y: pendingFlowPosition.y - (graphContext?.yOffset || 0) + }, size: { width: 300, height: 200 }, color: DEFAULT_NOTE_COLOR, type: 'free', diff --git a/frontend/src/lib/components/graph/graphContext.ts b/frontend/src/lib/components/graph/graphContext.ts index d4b3a6b556..176fc5d3bf 100644 --- a/frontend/src/lib/components/graph/graphContext.ts +++ b/frontend/src/lib/components/graph/graphContext.ts @@ -9,6 +9,7 @@ export type GraphContext = { showAssets: Writable noteManager?: NoteManager clearFlowSelection?: () => void + yOffset?: number } const graphContextKey = 'FlowGraphContext'