Files
ConceptSketch/src/Graphviz.tsx
Claude Bot ed30804839 feature: implement cut and paste functionality (closes #8)
- Add CutStore (Zustand) to store cut node IDs, node data, edges, and source graph ID
- Update graphToDot() to render cut nodes with dashed grey style for visual indication
- Add 'Cut' action to node context menu; 'Paste' action to empty-area context menu
- Cut logic: captures selected nodes (or right-clicked node if none selected) plus
  all nodes reachable by outgoing edges from those nodes; clears selection afterwards
- Paste logic: adds cut nodes/edges to target graph, removes them from source graph
  in GraphsStore; pasting on the same graph as the cut source cancels the cut
- Graph re-renders automatically when cutNodeIds changes via useCutStore subscription
- Clear cut store on file load for consistency
- Add E2E tests covering: Cut menu visibility, Paste menu visibility, cut styling,
  linked-node inclusion, cross-subgraph paste, source-graph cleanup, same-graph cancel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 15:57:28 +00:00

31 lines
949 B
TypeScript

import type { GraphModel } from "./components/Graph";
export function graphToDot(g: GraphModel, cutNodeIds: Set<string> = new Set()): string {
// Directed graph, use neato layout so we can use pos attributes
const lines = [];
lines.push('digraph G {');
lines.push(' graph [splines=true, overlap=false, rankdir=LR];');
lines.push(' node [shape=rectangle, style=filled, fillcolor="white", fontsize=12];');
// nodes
for (const n of g.nodes) {
const attrs = [];
attrs.push(`label=\"${n.label}\"`);
attrs.push(`id=\"${n.id}\"`)
if (cutNodeIds.has(n.id)) {
attrs.push(`fillcolor="#d9d9d9"`, `style="filled,dashed"`);
}
lines.push(` \"${n.id}\" [${attrs.join(', ')}];`);
}
// edges
for (const e of g.edges) {
const attrs = [];
attrs.push(`id=\"${e.id}\"`)
lines.push(` \"${e.from}\" -> \"${e.to}\" [${attrs.join(', ')}];`);
}
// close
lines.push('}');
return lines.join('\n');
}