@dr2rai/raid-canvas 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/RaiBridge.d.ts +10 -3
- package/dist/RaiBridge.d.ts.map +1 -1
- package/dist/RaiBridge.js +288 -16
- package/dist/RaiBridge.js.map +1 -1
- package/dist/RaidCanvas.d.ts +10 -2
- package/dist/RaidCanvas.d.ts.map +1 -1
- package/dist/RaidCanvas.js +169 -27
- package/dist/RaidCanvas.js.map +1 -1
- package/dist/X6Shapes.d.ts +15 -1
- package/dist/X6Shapes.d.ts.map +1 -1
- package/dist/X6Shapes.js +196 -22
- package/dist/X6Shapes.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/RaiBridge.ts +332 -19
- package/src/RaidCanvas.tsx +183 -25
- package/src/X6Shapes.ts +204 -22
- package/src/index.ts +3 -0
- package/src/styles/aoaim-theme.css +21 -7
- package/src/types.ts +29 -1
package/src/RaidCanvas.tsx
CHANGED
|
@@ -16,12 +16,13 @@ import React, {
|
|
|
16
16
|
useCallback,
|
|
17
17
|
useImperativeHandle,
|
|
18
18
|
} from 'react';
|
|
19
|
-
import { Graph, Shape } from '@antv/x6';
|
|
19
|
+
import { Graph, Shape, Edge } from '@antv/x6';
|
|
20
20
|
import { RaiBridge } from './RaiBridge.js';
|
|
21
21
|
import {
|
|
22
22
|
registerAimShapes,
|
|
23
23
|
configureAimGraph,
|
|
24
24
|
createAimNode,
|
|
25
|
+
applyEdgeRouting,
|
|
25
26
|
CascaisPalette,
|
|
26
27
|
getDefaultNodeBounds,
|
|
27
28
|
getDefaultNodeName,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
getSemanticEdgeKind,
|
|
32
33
|
getSemanticEdgeStereotype,
|
|
33
34
|
} from './semanticRules.js';
|
|
34
|
-
import type { AimOntologyKind, RaidNodeData, RaidEdgeData } from './types.js';
|
|
35
|
+
import type { AimOntologyKind, AimRoutingMode, RaidNodeData, RaidEdgeData } from './types.js';
|
|
35
36
|
|
|
36
37
|
export interface RaidCanvasProps {
|
|
37
38
|
/** The raw SVG string carrying aim-* ontological attributes (preferred) */
|
|
@@ -40,6 +41,8 @@ export interface RaidCanvasProps {
|
|
|
40
41
|
svgContent?: string;
|
|
41
42
|
/** Whether the canvas allows dragging and editing, or behaves as a pan/zoom viewer */
|
|
42
43
|
readOnly?: boolean;
|
|
44
|
+
/** Default routing mode for edges ('manhattan', 'normal', 'smooth'). Default: 'manhattan' */
|
|
45
|
+
defaultRouting?: AimRoutingMode;
|
|
43
46
|
/** Optional CSS class name for the outer wrapper */
|
|
44
47
|
className?: string;
|
|
45
48
|
/** Optional inline styles for outer wrapper */
|
|
@@ -54,6 +57,8 @@ export interface RaidCanvasProps {
|
|
|
54
57
|
onSelectionChange?: (selectedIds: string[]) => void;
|
|
55
58
|
/** Callback fired when a stencil is dropped onto the canvas */
|
|
56
59
|
onDropStencil?: (kind: AimOntologyKind, point: { x: number; y: number }) => void;
|
|
60
|
+
/** Callback fired when the active routing mode changes (e.g. hydrated from SVG or switched by user) */
|
|
61
|
+
onRoutingModeChange?: (mode: AimRoutingMode) => void;
|
|
57
62
|
/** Whether to render built-in navigation controls (Zoom In/Out, Fit, Center, Reset). Default: true */
|
|
58
63
|
showToolbar?: boolean;
|
|
59
64
|
}
|
|
@@ -72,8 +77,12 @@ export interface RaidCanvasHandle {
|
|
|
72
77
|
) => string;
|
|
73
78
|
/** Update properties of an existing node (label, stereotype, dimensions, etc.) */
|
|
74
79
|
updateNode: (id: string, updates: Partial<RaidNodeData>) => void;
|
|
75
|
-
/** Update properties of an existing edge (kind, label, stereotype) */
|
|
80
|
+
/** Update properties of an existing edge (kind, label, stereotype, routing, ports) */
|
|
76
81
|
updateEdge: (id: string, updates: Partial<RaidEdgeData>) => void;
|
|
82
|
+
/** Set routing mode across the canvas or for new edges */
|
|
83
|
+
setRoutingMode: (mode: AimRoutingMode, applyToAllEdges?: boolean) => void;
|
|
84
|
+
/** Get the current active canvas routing mode */
|
|
85
|
+
getRoutingMode: () => AimRoutingMode;
|
|
77
86
|
/** Delete currently selected cell(s) */
|
|
78
87
|
deleteSelection: () => void;
|
|
79
88
|
/** Clear all cells on the canvas */
|
|
@@ -100,6 +109,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
100
109
|
svg,
|
|
101
110
|
svgContent,
|
|
102
111
|
readOnly = false,
|
|
112
|
+
defaultRouting = 'manhattan',
|
|
103
113
|
className,
|
|
104
114
|
style,
|
|
105
115
|
onChange,
|
|
@@ -107,6 +117,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
107
117
|
onSelect,
|
|
108
118
|
onSelectionChange,
|
|
109
119
|
onDropStencil,
|
|
120
|
+
onRoutingModeChange,
|
|
110
121
|
showToolbar = true,
|
|
111
122
|
},
|
|
112
123
|
ref,
|
|
@@ -118,6 +129,8 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
118
129
|
const isHydratingRef = useRef<boolean>(false);
|
|
119
130
|
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
120
131
|
const selectedCellIdRef = useRef<string | null>(null);
|
|
132
|
+
const clearEdgeToolsRef = useRef<(() => void) | null>(null);
|
|
133
|
+
const routingModeRef = useRef<AimRoutingMode>(defaultRouting);
|
|
121
134
|
|
|
122
135
|
const [zoomLevel, setZoomLevel] = useState<number>(100);
|
|
123
136
|
const [isDragOver, setIsDragOver] = useState<boolean>(false);
|
|
@@ -141,6 +154,9 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
141
154
|
const onDropStencilRef = useRef(onDropStencil);
|
|
142
155
|
onDropStencilRef.current = onDropStencil;
|
|
143
156
|
|
|
157
|
+
const onRoutingModeChangeRef = useRef(onRoutingModeChange);
|
|
158
|
+
onRoutingModeChangeRef.current = onRoutingModeChange;
|
|
159
|
+
|
|
144
160
|
const svgPropRef = useRef(activeSvg);
|
|
145
161
|
svgPropRef.current = activeSvg;
|
|
146
162
|
|
|
@@ -158,6 +174,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
158
174
|
const updatedSvg = bridgeRef.current.serializeToSvg(
|
|
159
175
|
graphRef.current,
|
|
160
176
|
currentBaseSvg,
|
|
177
|
+
{ routingMode: routingModeRef.current },
|
|
161
178
|
);
|
|
162
179
|
lastSerializedSvgRef.current = updatedSvg;
|
|
163
180
|
|
|
@@ -216,6 +233,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
216
233
|
return bridgeRef.current.serializeToSvg(
|
|
217
234
|
graphRef.current,
|
|
218
235
|
svgPropRef.current,
|
|
236
|
+
{ routingMode: routingModeRef.current },
|
|
219
237
|
);
|
|
220
238
|
},
|
|
221
239
|
addNode: (kind, x, y, customData) => {
|
|
@@ -299,8 +317,39 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
299
317
|
if (!edge || !edge.isEdge()) return;
|
|
300
318
|
|
|
301
319
|
const currentData = (edge.getData() ?? {}) as RaidEdgeData;
|
|
302
|
-
const
|
|
303
|
-
|
|
320
|
+
const mutableData: Record<string, unknown> = { ...currentData, ...updates };
|
|
321
|
+
|
|
322
|
+
if (updates.routing !== undefined) {
|
|
323
|
+
applyEdgeRouting(edge, updates.routing);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (updates.sourcePort !== undefined) {
|
|
327
|
+
const currentSource = edge.getSource() as { cell?: string };
|
|
328
|
+
if (currentSource.cell) {
|
|
329
|
+
if (updates.sourcePort === '' || updates.sourcePort === 'auto') {
|
|
330
|
+
edge.setSource({ cell: currentSource.cell });
|
|
331
|
+
delete mutableData.sourcePort;
|
|
332
|
+
} else {
|
|
333
|
+
edge.setSource({ cell: currentSource.cell, port: updates.sourcePort });
|
|
334
|
+
mutableData.sourcePort = updates.sourcePort;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (updates.targetPort !== undefined) {
|
|
340
|
+
const currentTarget = edge.getTarget() as { cell?: string };
|
|
341
|
+
if (currentTarget.cell) {
|
|
342
|
+
if (updates.targetPort === '' || updates.targetPort === 'auto') {
|
|
343
|
+
edge.setTarget({ cell: currentTarget.cell });
|
|
344
|
+
delete mutableData.targetPort;
|
|
345
|
+
} else {
|
|
346
|
+
edge.setTarget({ cell: currentTarget.cell, port: updates.targetPort });
|
|
347
|
+
mutableData.targetPort = updates.targetPort;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
edge.setData(mutableData as unknown as RaidEdgeData);
|
|
304
353
|
|
|
305
354
|
if (updates.label !== undefined || updates.stereotype !== undefined) {
|
|
306
355
|
const text = updates.label ?? updates.stereotype ?? '';
|
|
@@ -324,12 +373,29 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
324
373
|
|
|
325
374
|
triggerDebouncedChange();
|
|
326
375
|
},
|
|
376
|
+
setRoutingMode: (mode, applyToAll = true) => {
|
|
377
|
+
routingModeRef.current = mode;
|
|
378
|
+
if (graphRef.current) {
|
|
379
|
+
(graphRef.current as unknown as { _aimRoutingMode?: AimRoutingMode })._aimRoutingMode = mode;
|
|
380
|
+
}
|
|
381
|
+
onRoutingModeChangeRef.current?.(mode);
|
|
382
|
+
if (applyToAll && graphRef.current) {
|
|
383
|
+
for (const edge of graphRef.current.getEdges()) {
|
|
384
|
+
applyEdgeRouting(edge, mode);
|
|
385
|
+
const currentData = (edge.getData() ?? {}) as RaidEdgeData;
|
|
386
|
+
edge.setData({ ...currentData, routing: mode });
|
|
387
|
+
}
|
|
388
|
+
triggerDebouncedChange();
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
getRoutingMode: () => routingModeRef.current,
|
|
327
392
|
deleteSelection: () => {
|
|
328
393
|
const graph = graphRef.current;
|
|
329
394
|
if (!graph || readOnly) return;
|
|
330
395
|
if (selectedCellIdRef.current) {
|
|
331
396
|
const cell = graph.getCellById(selectedCellIdRef.current);
|
|
332
397
|
if (cell) {
|
|
398
|
+
clearEdgeToolsRef.current?.();
|
|
333
399
|
graph.removeCell(cell);
|
|
334
400
|
selectedCellIdRef.current = null;
|
|
335
401
|
onSelectRef.current?.(null);
|
|
@@ -455,9 +521,11 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
455
521
|
return validateSemanticConnection(sourceKind, targetKind);
|
|
456
522
|
},
|
|
457
523
|
createEdge() {
|
|
458
|
-
|
|
524
|
+
const edge = new Shape.Edge({
|
|
459
525
|
shape: 'aim-edge',
|
|
460
526
|
});
|
|
527
|
+
applyEdgeRouting(edge, routingModeRef.current);
|
|
528
|
+
return edge;
|
|
461
529
|
},
|
|
462
530
|
},
|
|
463
531
|
});
|
|
@@ -465,9 +533,66 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
465
533
|
configureAimGraph(graph);
|
|
466
534
|
graphRef.current = graph;
|
|
467
535
|
|
|
468
|
-
|
|
536
|
+
let activeToolEdge: Edge | null = null;
|
|
537
|
+
|
|
538
|
+
const clearEdgeTools = () => {
|
|
539
|
+
if (activeToolEdge) {
|
|
540
|
+
try {
|
|
541
|
+
activeToolEdge.removeTools();
|
|
542
|
+
} catch {
|
|
543
|
+
// Ignore if cell was already removed
|
|
544
|
+
}
|
|
545
|
+
activeToolEdge = null;
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
clearEdgeToolsRef.current = clearEdgeTools;
|
|
549
|
+
|
|
550
|
+
const setEdgeTools = (edge: Edge) => {
|
|
551
|
+
clearEdgeTools();
|
|
552
|
+
if (readOnly) return;
|
|
553
|
+
activeToolEdge = edge;
|
|
554
|
+
try {
|
|
555
|
+
edge.addTools([
|
|
556
|
+
{
|
|
557
|
+
name: 'source-arrowhead',
|
|
558
|
+
args: {
|
|
559
|
+
attrs: {
|
|
560
|
+
fill: CascaisPalette.NetGold,
|
|
561
|
+
stroke: '#FFFFFF',
|
|
562
|
+
strokeWidth: 2,
|
|
563
|
+
cursor: 'grab',
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
name: 'target-arrowhead',
|
|
569
|
+
args: {
|
|
570
|
+
attrs: {
|
|
571
|
+
fill: CascaisPalette.NetGold,
|
|
572
|
+
stroke: '#FFFFFF',
|
|
573
|
+
strokeWidth: 2,
|
|
574
|
+
cursor: 'grab',
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
name: 'vertices',
|
|
580
|
+
args: {
|
|
581
|
+
attrs: {
|
|
582
|
+
fill: CascaisPalette.WarmGraphite,
|
|
583
|
+
stroke: '#FFFFFF',
|
|
584
|
+
strokeWidth: 1.5,
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
]);
|
|
589
|
+
} catch {
|
|
590
|
+
// Fallback if tools fail to attach
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// Handle edge connections and re-connections with automatic semantic wiring
|
|
469
595
|
graph.on('edge:connected', ({ edge, isNew }) => {
|
|
470
|
-
if (!isNew) return;
|
|
471
596
|
const sourceCell = edge.getSourceCell();
|
|
472
597
|
const targetCell = edge.getTargetCell();
|
|
473
598
|
if (sourceCell?.isNode() && targetCell?.isNode()) {
|
|
@@ -479,33 +604,44 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
479
604
|
) as AimOntologyKind;
|
|
480
605
|
|
|
481
606
|
const edgeKind = getSemanticEdgeKind(sourceKind, targetKind);
|
|
482
|
-
const
|
|
607
|
+
const currentData = (edge.getData() ?? {}) as Partial<RaidEdgeData>;
|
|
608
|
+
const stereotype = currentData.stereotype ?? getSemanticEdgeStereotype(sourceKind, targetKind);
|
|
483
609
|
|
|
484
610
|
edge.setData({
|
|
611
|
+
...currentData,
|
|
485
612
|
id: edge.id,
|
|
486
613
|
kind: edgeKind,
|
|
487
614
|
sourceId: sourceCell.id,
|
|
488
615
|
targetId: targetCell.id,
|
|
489
616
|
sourcePort: edge.getSourcePortId(),
|
|
490
617
|
targetPort: edge.getTargetPortId(),
|
|
491
|
-
label: stereotype,
|
|
618
|
+
label: currentData.label ?? stereotype,
|
|
492
619
|
stereotype,
|
|
493
|
-
|
|
620
|
+
routing: currentData.routing ?? routingModeRef.current,
|
|
621
|
+
bendPoints: currentData.bendPoints ?? [],
|
|
494
622
|
});
|
|
495
623
|
|
|
496
|
-
if (
|
|
497
|
-
edge.
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
624
|
+
if (isNew) {
|
|
625
|
+
applyEdgeRouting(edge, routingModeRef.current);
|
|
626
|
+
if (stereotype) {
|
|
627
|
+
edge.setLabels([
|
|
628
|
+
{
|
|
629
|
+
attrs: {
|
|
630
|
+
text: {
|
|
631
|
+
text: stereotype,
|
|
632
|
+
fill: CascaisPalette.TextSecondary,
|
|
633
|
+
fontSize: 11,
|
|
634
|
+
},
|
|
504
635
|
},
|
|
636
|
+
position: 0.5,
|
|
505
637
|
},
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
638
|
+
]);
|
|
639
|
+
}
|
|
640
|
+
} else {
|
|
641
|
+
// Re-anchored: if edge was selected, re-attach tools
|
|
642
|
+
if (selectedCellIdRef.current === edge.id) {
|
|
643
|
+
setEdgeTools(edge);
|
|
644
|
+
}
|
|
509
645
|
}
|
|
510
646
|
}
|
|
511
647
|
triggerDebouncedChange();
|
|
@@ -515,8 +651,15 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
515
651
|
graph.on('node:change:position', triggerDebouncedChange);
|
|
516
652
|
graph.on('node:change:size', triggerDebouncedChange);
|
|
517
653
|
graph.on('edge:change:vertices', triggerDebouncedChange);
|
|
654
|
+
graph.on('edge:change:source', triggerDebouncedChange);
|
|
655
|
+
graph.on('edge:change:target', triggerDebouncedChange);
|
|
518
656
|
graph.on('cell:added', triggerDebouncedChange);
|
|
519
|
-
graph.on('cell:removed',
|
|
657
|
+
graph.on('cell:removed', ({ cell }) => {
|
|
658
|
+
if (cell === activeToolEdge) {
|
|
659
|
+
activeToolEdge = null;
|
|
660
|
+
}
|
|
661
|
+
triggerDebouncedChange();
|
|
662
|
+
});
|
|
520
663
|
|
|
521
664
|
// Zoom listener to keep toolbar indicator accurate
|
|
522
665
|
graph.on('scale', () => {
|
|
@@ -529,6 +672,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
529
672
|
selectedCellIdRef.current = id;
|
|
530
673
|
|
|
531
674
|
if (cell.isNode()) {
|
|
675
|
+
clearEdgeTools();
|
|
532
676
|
const data = (cell.getData() ?? {}) as Partial<RaidNodeData>;
|
|
533
677
|
const kind = (data.kind ?? 'act') as AimOntologyKind;
|
|
534
678
|
const label =
|
|
@@ -538,6 +682,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
538
682
|
id;
|
|
539
683
|
onSelectRef.current?.({ id, kind, label });
|
|
540
684
|
} else if (cell.isEdge()) {
|
|
685
|
+
setEdgeTools(cell);
|
|
541
686
|
const data = (cell.getData() ?? {}) as Partial<RaidEdgeData>;
|
|
542
687
|
const label =
|
|
543
688
|
data.label ??
|
|
@@ -550,6 +695,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
550
695
|
});
|
|
551
696
|
|
|
552
697
|
graph.on('blank:click', () => {
|
|
698
|
+
clearEdgeTools();
|
|
553
699
|
selectedCellIdRef.current = null;
|
|
554
700
|
onSelectRef.current?.(null);
|
|
555
701
|
onSelectionChangeRef.current?.([]);
|
|
@@ -572,6 +718,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
572
718
|
const cell = graphRef.current.getCellById(selectedCellIdRef.current);
|
|
573
719
|
if (cell) {
|
|
574
720
|
e.preventDefault();
|
|
721
|
+
clearEdgeTools();
|
|
575
722
|
graphRef.current.removeCell(cell);
|
|
576
723
|
selectedCellIdRef.current = null;
|
|
577
724
|
onSelectRef.current?.(null);
|
|
@@ -600,8 +747,13 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
600
747
|
if (svgPropRef.current) {
|
|
601
748
|
isHydratingRef.current = true;
|
|
602
749
|
try {
|
|
603
|
-
bridgeRef.current.hydrateFromSvg(svgPropRef.current, graph);
|
|
750
|
+
const metamodel = bridgeRef.current.hydrateFromSvg(svgPropRef.current, graph, { inferPorts: false });
|
|
604
751
|
lastSerializedSvgRef.current = svgPropRef.current;
|
|
752
|
+
if (metamodel.routing) {
|
|
753
|
+
routingModeRef.current = metamodel.routing;
|
|
754
|
+
(graph as unknown as { _aimRoutingMode?: AimRoutingMode })._aimRoutingMode = metamodel.routing;
|
|
755
|
+
onRoutingModeChangeRef.current?.(metamodel.routing);
|
|
756
|
+
}
|
|
605
757
|
graph.centerContent();
|
|
606
758
|
} catch (err) {
|
|
607
759
|
console.error('RaidCanvas hydration error:', err);
|
|
@@ -617,6 +769,7 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
617
769
|
}
|
|
618
770
|
window.removeEventListener('keydown', handleKeyDown);
|
|
619
771
|
resizeObserver.disconnect();
|
|
772
|
+
clearEdgeToolsRef.current = null;
|
|
620
773
|
graph.dispose();
|
|
621
774
|
graphRef.current = null;
|
|
622
775
|
};
|
|
@@ -636,8 +789,13 @@ export const RaidCanvas = React.forwardRef<RaidCanvasHandle, RaidCanvasProps>(
|
|
|
636
789
|
|
|
637
790
|
isHydratingRef.current = true;
|
|
638
791
|
try {
|
|
639
|
-
bridgeRef.current.hydrateFromSvg(activeSvg, graph);
|
|
792
|
+
const metamodel = bridgeRef.current.hydrateFromSvg(activeSvg, graph, { inferPorts: false });
|
|
640
793
|
lastSerializedSvgRef.current = activeSvg;
|
|
794
|
+
if (metamodel.routing) {
|
|
795
|
+
routingModeRef.current = metamodel.routing;
|
|
796
|
+
(graph as unknown as { _aimRoutingMode?: AimRoutingMode })._aimRoutingMode = metamodel.routing;
|
|
797
|
+
onRoutingModeChangeRef.current?.(metamodel.routing);
|
|
798
|
+
}
|
|
641
799
|
graph.centerContent();
|
|
642
800
|
} catch (err) {
|
|
643
801
|
console.error('RaidCanvas re-hydration error:', err);
|