mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 16:05:42 +00:00
clean reactivity
This commit is contained in:
@@ -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}
|
||||
<FlowPreviewButtons
|
||||
on:openTriggers={(e) => {
|
||||
select('triggers')
|
||||
select('Trigger')
|
||||
handleSelectTriggerFromKind(triggersState, triggersCount, initialPath, e.detail.kind)
|
||||
captureOn.set(true)
|
||||
showCaptureHint.set(true)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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<string, number>()
|
||||
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)
|
||||
</script>
|
||||
|
||||
{#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}
|
||||
|
||||
<NodeContextMenu
|
||||
selectedNodeIds={actualSelectionManager.selectedIds.filter(id => !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}
|
||||
>
|
||||
<SelectionBoundingBox
|
||||
|
||||
@@ -13,50 +13,15 @@ type NodePos = { position: { x: number; y: number } }
|
||||
|
||||
/**
|
||||
* Utility class for managing flow notes including regular and group notes
|
||||
* This is now a stateless utility that operates on passed note data
|
||||
*/
|
||||
export class NoteManager {
|
||||
private notes = $state<FlowNote[]>([])
|
||||
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 {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user