diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 8934dd91bb..55ea15bcad 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -1051,7 +1051,7 @@ variant="subtle" size="xs" on:click={async () => { - select('triggers') + select('Trigger') const selected = primaryScheduleIndex ?? scheduleIndex if (selected) { triggersState.selectedTriggerIndex = selected @@ -1144,7 +1144,7 @@ {/if} { - select('triggers') + select('Trigger') handleSelectTriggerFromKind(triggersState, triggersCount, initialPath, e.detail.kind) captureOn.set(true) showCaptureHint.set(true) diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index d480240a63..82de07f459 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -426,10 +426,7 @@ maxHeight={minHeight} modules={flowStore.val.value.modules} {noteMode} - notes={flowStore.val.value?.notes || []} - onNotesChange={(newNotes) => { - flowStore.val.value.notes = newNotes - }} + bind:notes={flowStore.val.value.notes} preprocessorModule={flowStore.val.value?.preprocessor_module} {selectionManager} {workspace} diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index f2c1cde5d0..7320b81f9a 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -121,7 +121,6 @@ notes?: FlowNote[] chatInputEnabled?: boolean multiSelectEnabled?: boolean - onNotesChange?: (notes: FlowNote[]) => void onDelete?: (id: string) => void onInsert?: (detail: { sourceId?: string @@ -210,8 +209,7 @@ suspendStatus = {}, flowHasChanged = false, noteMode = false, - notes = [], - onNotesChange = undefined, + notes = $bindable(), exitNoteMode = undefined, chatInputEnabled = false, sharedViewport = undefined, @@ -220,15 +218,8 @@ multiSelectEnabled = false }: Props = $props() - // Initialize note manager - const noteManager = new NoteManager(notes) - noteManager.setOnNotesChangeCallback(onNotesChange || (() => {})) - noteManager.setUpdateStoresCallback(updateStores) - - // Update note manager when notes prop changes - $effect(() => { - noteManager.setNotes(notes) - }) + // Initialize note manager (now stateless) + const noteManager = new NoteManager() // Selection manager - create one if not provided let actualSelectionManager = selectionManager || new SelectionManager() @@ -345,19 +336,23 @@ // First, collect all nodes that need spacing above them const spacingMap = new Map() for (const node of initialNodes) { - const groupNoteHeight = noteManager.getGroupNoteHeightForNode(node.id, initialNodes) + const groupNoteHeight = noteManager.getGroupNoteHeightForNode( + notes ?? [], + node.id, + initialNodes + ) if (groupNoteHeight > 0) { spacingMap.set(node.id, groupNoteHeight) } } // Apply spacing - move nodes down by the cumulative spacing above them - const adjustedNodes = initialNodes.map(node => { + const adjustedNodes = initialNodes.map((node) => { let totalSpacingAbove = 0 // Calculate total spacing needed above this node from all group notes above it for (const [spacingNodeId, spacing] of spacingMap) { - const spacingNode = initialNodes.find(n => n.id === spacingNodeId) + const spacingNode = initialNodes.find((n) => n.id === spacingNodeId) if (spacingNode && spacingNode.position.y <= node.position.y) { totalSpacingAbove += spacing } @@ -475,32 +470,24 @@ function onNoteAdded(newNoteFromTool: any) { // Add the note to our separate notes array if a note was created - if (newNoteFromTool && onNotesChange) { - const newNote: FlowNote = { + if (newNoteFromTool) { + const newNote = { id: `note-${nextNoteId}`, text: '', position: newNoteFromTool.position, size: newNoteFromTool.size || { width: 200, height: 100 }, color: DEFAULT_NOTE_COLOR } - noteManager.addNote(newNote) + notes = noteManager.addNote(notes ?? [], newNote) nextNoteId += 1 } exitNoteMode?.() - updateStores() } - - - function handleCreateGroupNote(selectedNodeIds: string[]) { - noteManager.createGroupNote(selectedNodeIds) - updateStores() + notes = noteManager.createGroupNote(notes ?? [], selectedNodeIds) } - - - async function updateStores() { if (graph.error) { return @@ -540,7 +527,9 @@ nodes = [ ...finalNodes, - ...noteManager.convertToNodes(finalNodes) + ...noteManager.convertToNodes(notes ?? [], finalNodes, (newNotes) => { + notes = newNotes + }) ] edges = [ ...(assetNodesResult?.newAssetEdges ?? []), @@ -640,7 +629,7 @@ }) $effect(() => { - ;[graph, allowSimplifiedPoll, $showAssets] + ;[graph, allowSimplifiedPoll, $showAssets, notes] untrack(() => updateStores()) }) @@ -718,7 +707,7 @@ viewportSynchronizer?.zoomOut() } - $inspect('dbg modules', modules, nodes) + $inspect('dbg notes & nodes', notes, nodes) {#if insertable} @@ -762,7 +751,7 @@ onnodedragstop={(event) => { const node = event.targetNode if (node && node.type === 'note') { - noteManager.updatePosition(node.id, node.position) + notes = noteManager.updatePosition(notes ?? [], node.id, node.position) } }} onmove={(event, viewport) => { @@ -792,7 +781,10 @@ {/if} !id.startsWith('Settings') && !id.startsWith('Trigger') && !id.startsWith('Result'))} + selectedNodeIds={actualSelectionManager.selectedIds.filter( + (id) => + !id.startsWith('Settings') && !id.startsWith('Trigger') && !id.startsWith('Result') + )} onCreateGroupNote={handleCreateGroupNote} > ([]) - private onNotesChangeCallback?: (notes: FlowNote[]) => void - private updateStoresCallback?: () => void - - constructor(initialNotes: FlowNote[] = []) { - this.notes = initialNotes - } - - /** - * Set the callback function to be called when notes change - */ - setOnNotesChangeCallback(callback: (notes: FlowNote[]) => void) { - this.onNotesChangeCallback = callback - } - - /** - * Set the callback function to be called when stores need updating - */ - setUpdateStoresCallback(callback: () => void) { - this.updateStoresCallback = callback - } - - /** - * Get current notes array - */ - getNotes(): FlowNote[] { - return this.notes - } - - /** - * Update notes array - */ - setNotes(newNotes: FlowNote[]) { - this.notes = newNotes - } + constructor() {} /** * Add a new note from the note tool */ - addNote(newNoteFromTool: any) { - if (!this.onNotesChangeCallback) return - + addNote(notes: FlowNote[], newNoteFromTool: any): FlowNote[] { // Add the note to our separate notes array if a note was created if (newNoteFromTool) { const newNote: FlowNote = { @@ -66,99 +31,89 @@ export class NoteManager { size: { width: newNoteFromTool.width || 300, height: newNoteFromTool.height || 100 }, color: newNoteFromTool.data?.color || 'yellow' } - this.onNotesChangeCallback([...this.notes, newNote]) + return [...notes, newNote] } + return notes } /** * Update note text */ - updateText(noteId: string, text: string) { - if (!this.onNotesChangeCallback) return - this.onNotesChangeCallback( - this.notes.map((note) => (note.id === noteId ? { ...note, text } : note)) - ) - this.updateStoresCallback?.() + updateText(notes: FlowNote[], noteId: string, text: string): FlowNote[] { + return notes.map((note) => (note.id === noteId ? { ...note, text } : note)) } /** * Delete a note */ - delete(noteId: string) { - if (!this.onNotesChangeCallback) return - this.onNotesChangeCallback(this.notes.filter((note) => note.id !== noteId)) - this.updateStoresCallback?.() + delete(notes: FlowNote[], noteId: string): FlowNote[] { + return notes.filter((note) => note.id !== noteId) } /** * Update note position */ - updatePosition(noteId: string, position: { x: number; y: number }) { - if (!this.onNotesChangeCallback) return - this.onNotesChangeCallback( - this.notes.map((note) => (note.id === noteId ? { ...note, position } : note)) - ) + updatePosition( + notes: FlowNote[], + noteId: string, + position: { x: number; y: number } + ): FlowNote[] { + return notes.map((note) => (note.id === noteId ? { ...note, position } : note)) } /** * Update note size */ - updateSize(noteId: string, size: { width: number; height: number }) { - if (!this.onNotesChangeCallback) return - this.onNotesChangeCallback( - this.notes.map((note) => (note.id === noteId ? { ...note, size } : note)) - ) + updateSize( + notes: FlowNote[], + noteId: string, + size: { width: number; height: number } + ): FlowNote[] { + return notes.map((note) => (note.id === noteId ? { ...note, size } : note)) } /** * Update note color */ - updateColor(noteId: string, color: NoteColor) { - if (!this.onNotesChangeCallback) return - this.onNotesChangeCallback( - this.notes.map((note) => (note.id === noteId ? { ...note, color } : note)) - ) - this.updateStoresCallback?.() + updateColor(notes: FlowNote[], noteId: string, color: NoteColor): FlowNote[] { + return notes.map((note) => (note.id === noteId ? { ...note, color } : note)) } /** * Update note lock state */ - updateLock(noteId: string, locked: boolean) { - if (!this.onNotesChangeCallback) return - this.onNotesChangeCallback( - this.notes.map((note) => (note.id === noteId ? { ...note, locked } as any : note)) - ) - this.updateStoresCallback?.() + updateLock(notes: FlowNote[], noteId: string, locked: boolean): FlowNote[] { + return notes.map((note) => (note.id === noteId ? ({ ...note, locked } as any) : note)) } /** * Create a group note from selected node IDs */ - createGroupNote(selectedNodeIds: string[]) { - if (selectedNodeIds.length === 0 || !this.onNotesChangeCallback) return + createGroupNote(notes: FlowNote[], selectedNodeIds: string[]): FlowNote[] { + if (selectedNodeIds.length === 0) return notes try { const groupNote = createGroupNote(selectedNodeIds) // For now, we need to store group notes as FlowNote format with additional properties // We'll add dummy position/size that will be calculated dynamically in convertNotesToNodes - const lockedGroupNote = { + const newGroupNote = { ...groupNote, position: { x: 0, y: 0 }, // Dummy values, will be calculated dynamically size: { width: 300, height: 100 }, // Dummy values, will be calculated dynamically - locked: true, + locked: false, // Group notes are not locked, just not movable/resizable isGroupNote: true, containedNodeIds: groupNote.containedNodeIds, type: 'group' } as FlowNote & { - locked: boolean; - isGroupNote: boolean; - containedNodeIds: string[]; - type: string; + locked: boolean + isGroupNote: boolean + containedNodeIds: string[] + type: string } - this.onNotesChangeCallback([...this.notes, lockedGroupNote]) + return [...notes, newGroupNote] } catch (error) { console.error('Failed to create group note:', error) + return notes } } @@ -166,12 +121,16 @@ export class NoteManager { * Helper function to determine if a node needs additional spacing above it for group notes. * Returns the height needed above the node. */ - getGroupNoteHeightForNode(nodeId: string, layoutedNodes: (NodeDep & NodePos)[]): number { - for (const note of this.notes) { + getGroupNoteHeightForNode( + notes: FlowNote[], + nodeId: string, + layoutedNodes: (NodeDep & NodePos)[] + ): number { + for (const note of notes) { const extendedNote = convertToExtendedNote(note as any) if (isGroupNote(extendedNote) && extendedNote.containedNodeIds.includes(nodeId)) { // Find the topmost node in this group by Y position - const containedNodes = layoutedNodes.filter(node => + const containedNodes = layoutedNodes.filter((node) => extendedNote.containedNodeIds.includes(node.id) ) @@ -193,8 +152,12 @@ export class NoteManager { /** * Convert notes to SvelteFlow nodes */ - convertToNodes(currentNodes: Node[]): Node[] { - return this.notes.map((note) => { + convertToNodes( + notes: FlowNote[], + currentNodes: Node[], + onNotesChange: (notes: FlowNote[]) => void + ): Node[] { + return notes.map((note) => { const extendedNote = convertToExtendedNote(note as any) if (isGroupNote(extendedNote)) { @@ -208,20 +171,35 @@ export class NoteManager { data: { text: extendedNote.text, color: extendedNote.color, - locked: (note as any).locked || true, // Group notes are locked by default + locked: false, // Group notes are not locked - they can be edited isGroupNote: true, containedNodeIds: extendedNote.containedNodeIds, - onUpdate: (text: string) => this.updateText(extendedNote.id, text), - onDelete: () => this.delete(extendedNote.id), - onColorChange: (color: NoteColor) => this.updateColor(extendedNote.id, color), - onSizeChange: (size: { width: number; height: number }) => this.updateSize(extendedNote.id, size), - onLockToggle: (locked: boolean) => this.updateLock(extendedNote.id, locked) + onUpdate: (text: string) => { + const newNotes = this.updateText(notes, extendedNote.id, text) + onNotesChange(newNotes) + }, + onDelete: () => { + const newNotes = this.delete(notes, extendedNote.id) + onNotesChange(newNotes) + }, + onColorChange: (color: NoteColor) => { + const newNotes = this.updateColor(notes, extendedNote.id, color) + onNotesChange(newNotes) + }, + onSizeChange: (size: { width: number; height: number }) => { + const newNotes = this.updateSize(notes, extendedNote.id, size) + onNotesChange(newNotes) + }, + onLockToggle: (locked: boolean) => { + const newNotes = this.updateLock(notes, extendedNote.id, locked) + onNotesChange(newNotes) + } }, style: `width: ${bounds.size.width}px; height: ${bounds.size.height}px;`, width: bounds.size.width, height: bounds.size.height, zIndex: -2000, - draggable: !(note as any).locked, // Don't allow dragging locked notes + draggable: false, // Group notes cannot be moved - position is determined by contained nodes selectable: true } } else { @@ -235,11 +213,26 @@ export class NoteManager { color: extendedNote.color, locked: (note as any).locked || false, isGroupNote: false, - onUpdate: (text: string) => this.updateText(extendedNote.id, text), - onDelete: () => this.delete(extendedNote.id), - onColorChange: (color: NoteColor) => this.updateColor(extendedNote.id, color), - onSizeChange: (size: { width: number; height: number }) => this.updateSize(extendedNote.id, size), - onLockToggle: (locked: boolean) => this.updateLock(extendedNote.id, locked) + onUpdate: (text: string) => { + const newNotes = this.updateText(notes, extendedNote.id, text) + onNotesChange(newNotes) + }, + onDelete: () => { + const newNotes = this.delete(notes, extendedNote.id) + onNotesChange(newNotes) + }, + onColorChange: (color: NoteColor) => { + const newNotes = this.updateColor(notes, extendedNote.id, color) + onNotesChange(newNotes) + }, + onSizeChange: (size: { width: number; height: number }) => { + const newNotes = this.updateSize(notes, extendedNote.id, size) + onNotesChange(newNotes) + }, + onLockToggle: (locked: boolean) => { + const newNotes = this.updateLock(notes, extendedNote.id, locked) + onNotesChange(newNotes) + } }, style: `width: ${extendedNote.size.width}px; height: ${extendedNote.size.height}px;`, width: extendedNote.size.width, @@ -251,4 +244,4 @@ export class NoteManager { } }) } -} \ No newline at end of file +}