Skip to content

Commit 4e20444

Browse files
Merge pull request #347 from CrewForm/fix/canvas-bugs
fix: canvas copy/paste, note persistence, node blur
2 parents 01dba3c + 72de819 commit 4e20444

3 files changed

Lines changed: 69 additions & 17 deletions

File tree

src/components/workflow/useCanvasClipboard.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,18 @@ export function useCanvasClipboard({
9999
// Auto-connect pasted nodes based on team mode
100100
const modeEdges = createModeEdges(newNodes, nodes, teamMode, layoutDirection, timestamp)
101101

102-
setNodes((currentNodes) => {
103-
setEdges((currentEdges) => {
104-
pushState(currentNodes, currentEdges)
105-
// Deselect existing nodes
106-
const deselected = currentNodes.map((n) => ({ ...n, selected: false }))
107-
const allNodes = [...deselected, ...newNodes]
108-
const allEdges = [...currentEdges, ...newEdges, ...modeEdges]
109-
setNodes(allNodes)
110-
void saveGraph(allNodes, allEdges)
111-
return allEdges
112-
})
113-
return currentNodes
114-
})
115-
}, [nodes, setNodes, setEdges, saveGraph, pushState, teamMode, layoutDirection])
102+
// Snapshot for undo
103+
pushState(nodes, edges)
104+
105+
// Deselect existing, add new nodes
106+
const deselectedNodes = nodes.map((n) => ({ ...n, selected: false }))
107+
const allNodes = [...deselectedNodes, ...newNodes]
108+
const allEdges = [...edges, ...newEdges, ...modeEdges]
109+
110+
setNodes(allNodes)
111+
setEdges(allEdges)
112+
void saveGraph(allNodes, allEdges)
113+
}, [nodes, edges, setNodes, setEdges, saveGraph, pushState, teamMode, layoutDirection])
116114

117115
return { copy, paste, hasClipboard: () => clipboardRef.current !== null }
118116
}

src/components/workflow/useWorkflowGraph.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
CollaborationConfig,
2121
} from '@/types'
2222
import type { AgentNodeData } from './nodes/AgentNode'
23+
import type { NoteNodeData, NoteColor } from './nodes/NoteNode'
2324

2425
const Y_SPACING = 120
2526
const X_CENTER = 300
@@ -225,6 +226,10 @@ function pipelineToGraph(config: PipelineConfig, agents: Agent[]): { nodes: Node
225226
markerEnd: ARROW_MARKER,
226227
})
227228

229+
// Restore sticky notes from config
230+
const noteNodes = restoreNoteNodes(config as unknown as Record<string, unknown>)
231+
nodes.push(...noteNodes)
232+
228233
return { nodes, edges }
229234
}
230235

@@ -312,6 +317,10 @@ function orchestratorToGraph(config: OrchestratorConfig, agents: Agent[]): { nod
312317
markerEnd: ARROW_MARKER_PURPLE,
313318
})
314319

320+
// Restore sticky notes from config
321+
const noteNodes = restoreNoteNodes(config as unknown as Record<string, unknown>)
322+
nodes.push(...noteNodes)
323+
315324
return { nodes, edges }
316325
}
317326

@@ -355,6 +364,10 @@ function collaborationToGraph(config: CollaborationConfig, agents: Agent[]): { n
355364
}
356365
}
357366

367+
// Restore sticky notes from config
368+
const noteNodes = restoreNoteNodes(config as unknown as Record<string, unknown>)
369+
nodes.push(...noteNodes)
370+
358371
return { nodes, edges }
359372
}
360373

@@ -424,11 +437,49 @@ export function validateConfig(
424437
function extractNodePositions(nodes: Node[]): Record<string, { x: number; y: number }> {
425438
const positions: Record<string, { x: number; y: number }> = {}
426439
for (const node of nodes) {
440+
// Exclude note nodes — they are persisted separately in _canvas_notes
441+
if (node.type === 'noteNode') continue
427442
positions[node.id] = { x: node.position.x, y: node.position.y }
428443
}
429444
return positions
430445
}
431446

447+
// ─── Note extraction / restoration ───────────────────────────────────────────
448+
449+
interface CanvasNote {
450+
id: string
451+
content: string
452+
color: NoteColor
453+
x: number
454+
y: number
455+
}
456+
457+
function extractCanvasNotes(nodes: Node[]): CanvasNote[] {
458+
return nodes
459+
.filter((n) => n.type === 'noteNode')
460+
.map((n) => {
461+
const data = n.data as unknown as NoteNodeData
462+
return {
463+
id: n.id,
464+
content: data.content,
465+
color: data.color ?? 'yellow',
466+
x: n.position.x,
467+
y: n.position.y,
468+
}
469+
})
470+
}
471+
472+
function restoreNoteNodes(config: Record<string, unknown>): Node[] {
473+
const notes = (config._canvas_notes ?? []) as CanvasNote[]
474+
return notes.map((note) => ({
475+
id: note.id,
476+
type: 'noteNode' as const,
477+
position: { x: note.x, y: note.y },
478+
data: { content: note.content, color: note.color },
479+
draggable: true,
480+
}))
481+
}
482+
432483
// ─── Graph → Config (reverse) ────────────────────────────────────────────────
433484

434485
/**
@@ -677,6 +728,8 @@ export function graphToConfig(
677728
if (layoutDirection) {
678729
(config as unknown as Record<string, unknown>).layout_direction = layoutDirection
679730
}
731+
// Persist sticky notes
732+
(config as unknown as Record<string, unknown>)._canvas_notes = extractCanvasNotes(nodes)
680733
return config
681734
}
682735

src/components/workflow/workflow.css

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,10 @@
116116
═══════════════════════════════════════════════════════════════════════════════ */
117117

118118
.workflow-glass-node {
119-
backdrop-filter: blur(12px);
120-
-webkit-backdrop-filter: blur(12px);
121-
background: rgba(26, 26, 26, 0.75);
119+
backdrop-filter: blur(4px);
120+
-webkit-backdrop-filter: blur(4px);
121+
background: rgba(26, 26, 26, 0.85);
122+
will-change: transform;
122123
box-shadow:
123124
0 4px 16px rgba(0, 0, 0, 0.3),
124125
0 0 0 1px rgba(255, 255, 255, 0.04) inset;

0 commit comments

Comments
 (0)