@3plate/graph-core 0.1.4 → 0.1.5
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/index.cjs +1374 -297
- package/dist/index.d.cts +253 -15
- package/dist/index.d.ts +253 -15
- package/dist/index.js +1374 -297
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,70 @@ var import_immutable8 = require("immutable");
|
|
|
40
40
|
|
|
41
41
|
// src/graph/node.ts
|
|
42
42
|
var import_immutable = require("immutable");
|
|
43
|
+
|
|
44
|
+
// src/log.ts
|
|
45
|
+
var import_pino = __toESM(require("pino"), 1);
|
|
46
|
+
var resolvedLevel = typeof globalThis !== "undefined" && globalThis.__LOG_LEVEL || typeof process !== "undefined" && process.env?.LOG_LEVEL || "debug";
|
|
47
|
+
var browserOpts = { asObject: true };
|
|
48
|
+
browserOpts.transmit = {
|
|
49
|
+
level: resolvedLevel,
|
|
50
|
+
send: (level, log12) => {
|
|
51
|
+
try {
|
|
52
|
+
const endpoint = typeof globalThis !== "undefined" && globalThis.__LOG_INGEST_URL || typeof process !== "undefined" && process.env?.LOG_INGEST_URL || void 0;
|
|
53
|
+
if (!endpoint || typeof window === "undefined") {
|
|
54
|
+
try {
|
|
55
|
+
console.debug("[graph-core] transmit skipped", { endpoint, hasWindow: typeof window !== "undefined", level });
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const line = JSON.stringify({ level, ...log12, ts: Date.now() }) + "\n";
|
|
61
|
+
try {
|
|
62
|
+
console.debug("[graph-core] transmit sending", { endpoint, level, bytes: line.length });
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
fetch(endpoint, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
mode: "no-cors",
|
|
68
|
+
credentials: "omit",
|
|
69
|
+
body: line,
|
|
70
|
+
keepalive: true
|
|
71
|
+
}).then(() => {
|
|
72
|
+
try {
|
|
73
|
+
console.debug("[graph-core] transmit fetch ok");
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}).catch((err) => {
|
|
77
|
+
try {
|
|
78
|
+
console.debug("[graph-core] transmit fetch error", err?.message || err);
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
} catch (e) {
|
|
83
|
+
try {
|
|
84
|
+
console.debug("[graph-core] transmit error", e?.message || e);
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var base = (0, import_pino.default)({
|
|
91
|
+
level: resolvedLevel,
|
|
92
|
+
browser: browserOpts
|
|
93
|
+
});
|
|
94
|
+
function logger(module2) {
|
|
95
|
+
const child = base.child({ module: module2 });
|
|
96
|
+
return {
|
|
97
|
+
error: (msg, ...args) => child.error({ args }, msg),
|
|
98
|
+
warn: (msg, ...args) => child.warn({ args }, msg),
|
|
99
|
+
info: (msg, ...args) => child.info({ args }, msg),
|
|
100
|
+
debug: (msg, ...args) => child.debug({ args }, msg)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
var log = logger("core");
|
|
104
|
+
|
|
105
|
+
// src/graph/node.ts
|
|
106
|
+
var log2 = logger("node");
|
|
43
107
|
var defNodeData = {
|
|
44
108
|
id: "",
|
|
45
109
|
data: void 0,
|
|
@@ -48,7 +112,7 @@ var defNodeData = {
|
|
|
48
112
|
text: void 0,
|
|
49
113
|
type: void 0,
|
|
50
114
|
render: void 0,
|
|
51
|
-
ports: {
|
|
115
|
+
ports: {},
|
|
52
116
|
aligned: {},
|
|
53
117
|
edges: { in: (0, import_immutable.Set)(), out: (0, import_immutable.Set)() },
|
|
54
118
|
segs: { in: (0, import_immutable.Set)(), out: (0, import_immutable.Set)() },
|
|
@@ -82,10 +146,7 @@ var Node = class _Node extends (0, import_immutable.Record)(defNodeData) {
|
|
|
82
146
|
return this.isDummy ? this.id : _Node.key(this);
|
|
83
147
|
}
|
|
84
148
|
static key(node) {
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
static isDummyId(nodeId) {
|
|
88
|
-
return nodeId.startsWith(_Node.dummyPrefix);
|
|
149
|
+
return `k:${node.id}:${node.version}`;
|
|
89
150
|
}
|
|
90
151
|
static addNormal(g, data) {
|
|
91
152
|
const layer = g.layerAt(0);
|
|
@@ -95,7 +156,9 @@ var Node = class _Node extends (0, import_immutable.Record)(defNodeData) {
|
|
|
95
156
|
segs: { in: (0, import_immutable.Set)(), out: (0, import_immutable.Set)() },
|
|
96
157
|
aligned: {},
|
|
97
158
|
edgeIds: [],
|
|
98
|
-
layerId: layer.id
|
|
159
|
+
layerId: layer.id,
|
|
160
|
+
lpos: void 0,
|
|
161
|
+
pos: void 0
|
|
99
162
|
});
|
|
100
163
|
layer.addNode(g, node.id);
|
|
101
164
|
g.nodes.set(node.id, node);
|
|
@@ -158,6 +221,18 @@ var Node = class _Node extends (0, import_immutable.Record)(defNodeData) {
|
|
|
158
221
|
getLayer(g) {
|
|
159
222
|
return g.getLayer(this.layerId);
|
|
160
223
|
}
|
|
224
|
+
margin(g) {
|
|
225
|
+
return this.isDummy ? g.options.edgeSpacing - g.options.dummyNodeSize : g.options.nodeMargin;
|
|
226
|
+
}
|
|
227
|
+
marginWith(g, other) {
|
|
228
|
+
return Math.max(this.margin(g), other.margin(g));
|
|
229
|
+
}
|
|
230
|
+
width(g) {
|
|
231
|
+
return this.dims?.[g.w] ?? 0;
|
|
232
|
+
}
|
|
233
|
+
right(g) {
|
|
234
|
+
return this.lpos + this.width(g);
|
|
235
|
+
}
|
|
161
236
|
setIndex(g, index) {
|
|
162
237
|
if (this.index == index) return this;
|
|
163
238
|
return this.mut(g).set("index", index);
|
|
@@ -204,9 +279,22 @@ var Node = class _Node extends (0, import_immutable.Record)(defNodeData) {
|
|
|
204
279
|
return node;
|
|
205
280
|
}
|
|
206
281
|
delSelf(g) {
|
|
282
|
+
if (this.aligned.in)
|
|
283
|
+
g.getNode(this.aligned.in).setAligned(g, "out", void 0);
|
|
284
|
+
if (this.aligned.out)
|
|
285
|
+
g.getNode(this.aligned.out).setAligned(g, "in", void 0);
|
|
207
286
|
this.getLayer(g).delNode(g, this.id);
|
|
208
|
-
for (const
|
|
209
|
-
|
|
287
|
+
for (const edge of this.rels(g, "edges", "both"))
|
|
288
|
+
edge.delSelf(g);
|
|
289
|
+
const remainingSegIds = [...this.segs.in, ...this.segs.out];
|
|
290
|
+
if (remainingSegIds.length > 0) {
|
|
291
|
+
for (const segId of remainingSegIds) {
|
|
292
|
+
if (g.segs?.has?.(segId)) {
|
|
293
|
+
g.getSeg(segId).delSelf(g);
|
|
294
|
+
} else {
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
210
298
|
g.nodes.delete(this.id);
|
|
211
299
|
g.dirtyNodes.delete(this.id);
|
|
212
300
|
g.delNodes.add(this.id);
|
|
@@ -299,30 +387,7 @@ var Node = class _Node extends (0, import_immutable.Record)(defNodeData) {
|
|
|
299
387
|
|
|
300
388
|
// src/graph/edge.ts
|
|
301
389
|
var import_immutable2 = require("immutable");
|
|
302
|
-
|
|
303
|
-
// src/log.ts
|
|
304
|
-
var levels = {
|
|
305
|
-
error: 0,
|
|
306
|
-
warn: 1,
|
|
307
|
-
info: 2,
|
|
308
|
-
debug: 3
|
|
309
|
-
};
|
|
310
|
-
var currentLevel = "debug";
|
|
311
|
-
function shouldLog(level) {
|
|
312
|
-
return levels[level] <= levels[currentLevel];
|
|
313
|
-
}
|
|
314
|
-
function logger(module2) {
|
|
315
|
-
return {
|
|
316
|
-
error: (msg, ...args) => shouldLog("error") && console.error(`[${module2}] ${msg}`, ...args),
|
|
317
|
-
warn: (msg, ...args) => shouldLog("warn") && console.warn(`[${module2}] ${msg}`, ...args),
|
|
318
|
-
info: (msg, ...args) => shouldLog("info") && console.info(`[${module2}] ${msg}`, ...args),
|
|
319
|
-
debug: (msg, ...args) => shouldLog("debug") && console.debug(`[${module2}] ${msg}`, ...args)
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
var log = logger("core");
|
|
323
|
-
|
|
324
|
-
// src/graph/edge.ts
|
|
325
|
-
var log2 = logger("edge");
|
|
390
|
+
var log3 = logger("edge");
|
|
326
391
|
var defEdgeData = {
|
|
327
392
|
id: "",
|
|
328
393
|
data: null,
|
|
@@ -330,7 +395,6 @@ var defEdgeData = {
|
|
|
330
395
|
source: { id: "" },
|
|
331
396
|
target: { id: "" },
|
|
332
397
|
type: void 0,
|
|
333
|
-
style: void 0,
|
|
334
398
|
mutable: false,
|
|
335
399
|
segIds: []
|
|
336
400
|
};
|
|
@@ -411,7 +475,7 @@ var Edge = class _Edge extends (0, import_immutable2.Record)(defEdgeData) {
|
|
|
411
475
|
source = edge.source.id;
|
|
412
476
|
if (edge.source?.port)
|
|
413
477
|
source = `${source}.${edge.source.port}`;
|
|
414
|
-
const marker = edge.source?.marker
|
|
478
|
+
const marker = edge.source?.marker;
|
|
415
479
|
if (marker && marker != "none") source += `[${marker}]`;
|
|
416
480
|
source += "-";
|
|
417
481
|
}
|
|
@@ -421,7 +485,7 @@ var Edge = class _Edge extends (0, import_immutable2.Record)(defEdgeData) {
|
|
|
421
485
|
if (edge.target.port)
|
|
422
486
|
target = `${target}.${edge.target.port}`;
|
|
423
487
|
target = "-" + target;
|
|
424
|
-
const marker = edge.target?.marker ??
|
|
488
|
+
const marker = edge.target?.marker ?? "arrow";
|
|
425
489
|
if (marker && marker != "none") target += `[${marker}]`;
|
|
426
490
|
}
|
|
427
491
|
const type = edge.type || "";
|
|
@@ -463,7 +527,6 @@ var defSegData = {
|
|
|
463
527
|
source: { id: "" },
|
|
464
528
|
target: { id: "" },
|
|
465
529
|
type: void 0,
|
|
466
|
-
style: void 0,
|
|
467
530
|
edgeIds: (0, import_immutable3.Set)(),
|
|
468
531
|
trackPos: void 0,
|
|
469
532
|
svg: void 0,
|
|
@@ -494,7 +557,7 @@ var Seg = class _Seg extends (0, import_immutable3.Record)(defSegData) {
|
|
|
494
557
|
sameEnd(other, side) {
|
|
495
558
|
const mine = this[side];
|
|
496
559
|
const yours = other[side];
|
|
497
|
-
return mine.id === yours.id && mine.port === yours.port && mine.marker === yours.marker;
|
|
560
|
+
return mine.id === yours.id && mine.port === yours.port && mine.marker === yours.marker && this.type === other.type;
|
|
498
561
|
}
|
|
499
562
|
setPos(g, source, target) {
|
|
500
563
|
return this.mut(g).merge({
|
|
@@ -566,7 +629,7 @@ var Seg = class _Seg extends (0, import_immutable3.Record)(defSegData) {
|
|
|
566
629
|
|
|
567
630
|
// src/graph/layer.ts
|
|
568
631
|
var import_immutable4 = require("immutable");
|
|
569
|
-
var
|
|
632
|
+
var log4 = logger("layer");
|
|
570
633
|
var defLayerData = {
|
|
571
634
|
id: "",
|
|
572
635
|
index: 0,
|
|
@@ -591,7 +654,7 @@ var Layer = class extends (0, import_immutable4.Record)(defLayerData) {
|
|
|
591
654
|
mutable: false
|
|
592
655
|
}).asImmutable();
|
|
593
656
|
}
|
|
594
|
-
get
|
|
657
|
+
get nodeCount() {
|
|
595
658
|
return this.nodeIds.size;
|
|
596
659
|
}
|
|
597
660
|
*nodes(g) {
|
|
@@ -646,9 +709,8 @@ var Layer = class extends (0, import_immutable4.Record)(defLayerData) {
|
|
|
646
709
|
reindex(g, nodeId) {
|
|
647
710
|
if (!this.isSorted) return void 0;
|
|
648
711
|
const sorted = this.sorted.filter((id) => id != nodeId);
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
g.getNode(sorted[i]).setIndex(g, i);
|
|
712
|
+
for (const [i, id] of this.sorted.entries())
|
|
713
|
+
g.getNode(id).setIndex(g, i);
|
|
652
714
|
return sorted;
|
|
653
715
|
}
|
|
654
716
|
delNode(g, nodeId) {
|
|
@@ -820,12 +882,12 @@ var Cycles = class _Cycles {
|
|
|
820
882
|
|
|
821
883
|
// src/graph/services/dummy.ts
|
|
822
884
|
var import_immutable5 = require("immutable");
|
|
823
|
-
var
|
|
885
|
+
var log5 = logger("dummy");
|
|
824
886
|
var Dummy = class _Dummy {
|
|
825
887
|
static updateDummies(g) {
|
|
826
888
|
for (const edgeId of g.dirtyEdges) {
|
|
827
889
|
const edge = g.getEdge(edgeId);
|
|
828
|
-
const { type
|
|
890
|
+
const { type } = edge;
|
|
829
891
|
const sourceLayer = edge.sourceNode(g).layerIndex(g);
|
|
830
892
|
const targetLayer = edge.targetNode(g).layerIndex(g);
|
|
831
893
|
let segIndex = 0;
|
|
@@ -849,7 +911,7 @@ var Dummy = class _Dummy {
|
|
|
849
911
|
});
|
|
850
912
|
target = { id: dummy.id };
|
|
851
913
|
}
|
|
852
|
-
seg = Seg.add(g, { source, target, type,
|
|
914
|
+
seg = Seg.add(g, { source, target, type, edgeIds: (0, import_immutable5.Set)([edgeId]) });
|
|
853
915
|
segs.splice(segIndex, 0, seg.id);
|
|
854
916
|
changed = true;
|
|
855
917
|
} else if (segLayer < layerIndex || seg.source.id != source.id || seg.source.port != source.port || layerIndex == targetLayer && (seg.target.id != edge.target.id || seg.target.port != edge.target.port)) {
|
|
@@ -888,9 +950,8 @@ var Dummy = class _Dummy {
|
|
|
888
950
|
let layer = g.getLayer(layerId);
|
|
889
951
|
const groups = /* @__PURE__ */ new Map();
|
|
890
952
|
for (const nodeId of layer.nodeIds) {
|
|
891
|
-
if (!Node.isDummyId(nodeId)) continue;
|
|
892
953
|
const node = g.getNode(nodeId);
|
|
893
|
-
if (node.isMerged) continue;
|
|
954
|
+
if (!node.isDummy || node.isMerged) continue;
|
|
894
955
|
const edge = g.getEdge(node.edgeIds[0]);
|
|
895
956
|
const key = Edge.key(edge, "k:", side);
|
|
896
957
|
if (!groups.has(key)) groups.set(key, /* @__PURE__ */ new Set());
|
|
@@ -898,7 +959,7 @@ var Dummy = class _Dummy {
|
|
|
898
959
|
}
|
|
899
960
|
for (const [key, group] of groups) {
|
|
900
961
|
if (group.size == 1) continue;
|
|
901
|
-
const edgeIds = [...group].map((node) => node.
|
|
962
|
+
const edgeIds = [...group].map((node) => node.edgeIds[0]);
|
|
902
963
|
const dummy = Node.addDummy(g, { edgeIds, layerId, isMerged: true });
|
|
903
964
|
let seg;
|
|
904
965
|
for (const old of group) {
|
|
@@ -908,8 +969,9 @@ var Dummy = class _Dummy {
|
|
|
908
969
|
const example = g.getSeg(segId);
|
|
909
970
|
seg = Seg.add(g, {
|
|
910
971
|
...example,
|
|
911
|
-
edgeIds: (0, import_immutable5.Set)(
|
|
912
|
-
[
|
|
972
|
+
edgeIds: (0, import_immutable5.Set)(edgeIds),
|
|
973
|
+
[side]: { ...example[side] },
|
|
974
|
+
[altSide]: { ...example[altSide], id: dummy.id, port: void 0 }
|
|
913
975
|
});
|
|
914
976
|
}
|
|
915
977
|
edge = edge.replaceSegId(g, segId, seg.id);
|
|
@@ -921,12 +983,15 @@ var Dummy = class _Dummy {
|
|
|
921
983
|
const example = g.getSeg(segId);
|
|
922
984
|
const seg2 = Seg.add(g, {
|
|
923
985
|
...example,
|
|
924
|
-
edgeIds: (0, import_immutable5.Set)([old.
|
|
925
|
-
[
|
|
986
|
+
edgeIds: (0, import_immutable5.Set)([old.edgeIds[0]]),
|
|
987
|
+
[altSide]: { ...example[altSide] },
|
|
988
|
+
[side]: { ...example[side], id: dummy.id, port: void 0 }
|
|
926
989
|
});
|
|
927
990
|
edge = edge.replaceSegId(g, segId, seg2.id);
|
|
928
991
|
}
|
|
929
992
|
}
|
|
993
|
+
for (const old of group)
|
|
994
|
+
old.delSelf(g);
|
|
930
995
|
}
|
|
931
996
|
}
|
|
932
997
|
}
|
|
@@ -934,7 +999,7 @@ var Dummy = class _Dummy {
|
|
|
934
999
|
|
|
935
1000
|
// src/graph/services/layers.ts
|
|
936
1001
|
var import_immutable6 = require("immutable");
|
|
937
|
-
var
|
|
1002
|
+
var log6 = logger("layers");
|
|
938
1003
|
var Layers = class {
|
|
939
1004
|
static updateLayers(g) {
|
|
940
1005
|
const stack = [...g.dirtyNodes].map((id) => g.getNode(id)).filter((node) => !node.isDummy).sort((a, b) => b.layerIndex(g) - a.layerIndex(g)).map((node) => node.id);
|
|
@@ -997,7 +1062,7 @@ var Layers = class {
|
|
|
997
1062
|
|
|
998
1063
|
// src/graph/services/layout.ts
|
|
999
1064
|
var import_immutable7 = require("immutable");
|
|
1000
|
-
var
|
|
1065
|
+
var log7 = logger("layout");
|
|
1001
1066
|
var Layout = class _Layout {
|
|
1002
1067
|
static parentIndex(g, node) {
|
|
1003
1068
|
const parents = (0, import_immutable7.Seq)([...node.adjs(g, "segs", "in")]);
|
|
@@ -1014,8 +1079,8 @@ var Layout = class _Layout {
|
|
|
1014
1079
|
if (a.isDummy && !b.isDummy) return -1;
|
|
1015
1080
|
if (!a.isDummy && b.isDummy) return 1;
|
|
1016
1081
|
if (!a.isDummy) return a.id.localeCompare(b.id);
|
|
1017
|
-
const minA =
|
|
1018
|
-
const minB =
|
|
1082
|
+
const minA = (0, import_immutable7.Seq)(a.edgeIds).min();
|
|
1083
|
+
const minB = (0, import_immutable7.Seq)(b.edgeIds).min();
|
|
1019
1084
|
return minA.localeCompare(minB);
|
|
1020
1085
|
}
|
|
1021
1086
|
static positionNodes(g) {
|
|
@@ -1041,7 +1106,12 @@ var Layout = class _Layout {
|
|
|
1041
1106
|
let node = g.getNode(sorted[i]);
|
|
1042
1107
|
node = node.setIndex(g, i).setLayerPos(g, lpos);
|
|
1043
1108
|
const size = node.dims?.[g.w] ?? 0;
|
|
1044
|
-
|
|
1109
|
+
let margin = node.margin(g);
|
|
1110
|
+
if (i + 1 < sorted.length) {
|
|
1111
|
+
const next = g.getNode(sorted[i + 1]);
|
|
1112
|
+
margin = node.marginWith(g, next);
|
|
1113
|
+
}
|
|
1114
|
+
lpos += size + margin;
|
|
1045
1115
|
}
|
|
1046
1116
|
}
|
|
1047
1117
|
}
|
|
@@ -1073,7 +1143,7 @@ var Layout = class _Layout {
|
|
|
1073
1143
|
let iterations = 0;
|
|
1074
1144
|
while (true) {
|
|
1075
1145
|
if (++iterations > 10) {
|
|
1076
|
-
|
|
1146
|
+
log7.error(`alignNodes: infinite loop detected in layer ${layerId}`);
|
|
1077
1147
|
break;
|
|
1078
1148
|
}
|
|
1079
1149
|
let changed = false;
|
|
@@ -1133,7 +1203,7 @@ var Layout = class _Layout {
|
|
|
1133
1203
|
static anchorPos(g, seg, side) {
|
|
1134
1204
|
const nodeId = seg[side].id;
|
|
1135
1205
|
const node = g.getNode(nodeId);
|
|
1136
|
-
let p = {
|
|
1206
|
+
let p = { [g.x]: node.lpos, [g.y]: node.pos?.[g.y] ?? 0 };
|
|
1137
1207
|
let w = node.dims?.[g.w] ?? 0;
|
|
1138
1208
|
let h = node.dims?.[g.h] ?? 0;
|
|
1139
1209
|
if (node.isDummy)
|
|
@@ -1179,22 +1249,18 @@ var Layout = class _Layout {
|
|
|
1179
1249
|
}
|
|
1180
1250
|
static shiftNode(g, nodeId, alignId, dir, lpos, reverseMove, conservative) {
|
|
1181
1251
|
const node = g.getNode(nodeId);
|
|
1182
|
-
log6.debug(`shift ${nodeId} (at ${node.lpos}) to ${alignId} (at ${lpos})`);
|
|
1183
1252
|
if (!conservative)
|
|
1184
1253
|
_Layout.markAligned(g, nodeId, alignId, dir, lpos);
|
|
1185
|
-
const
|
|
1186
|
-
const nodeWidth = node.dims?.[g.w] ?? 0;
|
|
1187
|
-
const aMin = lpos - space, aMax = lpos + nodeWidth + space;
|
|
1254
|
+
const nodeRight = lpos + node.width(g);
|
|
1188
1255
|
repeat:
|
|
1189
1256
|
for (const otherId of node.getLayer(g).nodeIds) {
|
|
1190
1257
|
if (otherId == nodeId) continue;
|
|
1191
1258
|
const other = g.getNode(otherId);
|
|
1192
|
-
const
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
if (aMin < bMax && bMin < aMax) {
|
|
1259
|
+
const margin = node.marginWith(g, other);
|
|
1260
|
+
const gap = lpos < other.lpos ? other.lpos - nodeRight : lpos - other.right(g);
|
|
1261
|
+
if (gap < margin) {
|
|
1196
1262
|
if (conservative) return false;
|
|
1197
|
-
const safePos = reverseMove ?
|
|
1263
|
+
const safePos = reverseMove ? lpos - other.width(g) - margin : nodeRight + margin;
|
|
1198
1264
|
_Layout.shiftNode(g, otherId, void 0, dir, safePos, reverseMove, conservative);
|
|
1199
1265
|
continue repeat;
|
|
1200
1266
|
}
|
|
@@ -1243,11 +1309,12 @@ var Layout = class _Layout {
|
|
|
1243
1309
|
for (const layerId of g.layerList) {
|
|
1244
1310
|
const layer = g.getLayer(layerId);
|
|
1245
1311
|
if (layer.sorted.length < 2) continue;
|
|
1246
|
-
for (const nodeId of layer.sorted) {
|
|
1312
|
+
for (const [i, nodeId] of layer.sorted.entries()) {
|
|
1247
1313
|
const node = g.getNode(nodeId);
|
|
1248
1314
|
if (node.index == 0) continue;
|
|
1249
1315
|
let minGap = Infinity;
|
|
1250
1316
|
const stack = [];
|
|
1317
|
+
let maxMargin = 0;
|
|
1251
1318
|
for (const right of _Layout.aligned(g, nodeId, "both")) {
|
|
1252
1319
|
stack.push(right);
|
|
1253
1320
|
const leftId = _Layout.leftOf(g, right);
|
|
@@ -1256,8 +1323,10 @@ var Layout = class _Layout {
|
|
|
1256
1323
|
const leftWidth = left.dims?.[g.w] ?? 0;
|
|
1257
1324
|
const gap = right.lpos - left.lpos - leftWidth;
|
|
1258
1325
|
if (gap < minGap) minGap = gap;
|
|
1326
|
+
const margin = right.marginWith(g, left);
|
|
1327
|
+
if (margin > maxMargin) maxMargin = margin;
|
|
1259
1328
|
}
|
|
1260
|
-
const delta = minGap -
|
|
1329
|
+
const delta = minGap - maxMargin;
|
|
1261
1330
|
if (delta <= 0) continue;
|
|
1262
1331
|
anyChanged = true;
|
|
1263
1332
|
for (const right of stack)
|
|
@@ -1305,9 +1374,8 @@ var Layout = class _Layout {
|
|
|
1305
1374
|
};
|
|
1306
1375
|
|
|
1307
1376
|
// src/canvas/marker.tsx
|
|
1308
|
-
var import_marker = __toESM(require("./marker.css?raw"), 1);
|
|
1309
1377
|
var import_jsx_runtime = require("jsx-dom/jsx-runtime");
|
|
1310
|
-
function arrow(size,
|
|
1378
|
+
function arrow(size, reverse = false) {
|
|
1311
1379
|
const h = size / 1.5;
|
|
1312
1380
|
const w = size;
|
|
1313
1381
|
const ry = h / 2;
|
|
@@ -1316,7 +1384,7 @@ function arrow(size, classPrefix, reverse = false) {
|
|
|
1316
1384
|
"marker",
|
|
1317
1385
|
{
|
|
1318
1386
|
id: `g3p-marker-arrow${suffix}`,
|
|
1319
|
-
className:
|
|
1387
|
+
className: "g3p-marker g3p-marker-arrow",
|
|
1320
1388
|
markerWidth: size,
|
|
1321
1389
|
markerHeight: size,
|
|
1322
1390
|
refX: "2",
|
|
@@ -1327,7 +1395,7 @@ function arrow(size, classPrefix, reverse = false) {
|
|
|
1327
1395
|
}
|
|
1328
1396
|
);
|
|
1329
1397
|
}
|
|
1330
|
-
function circle(size,
|
|
1398
|
+
function circle(size, reverse = false) {
|
|
1331
1399
|
const r = size / 3;
|
|
1332
1400
|
const cy = size / 2;
|
|
1333
1401
|
const suffix = reverse ? "-reverse" : "";
|
|
@@ -1335,7 +1403,7 @@ function circle(size, classPrefix, reverse = false) {
|
|
|
1335
1403
|
"marker",
|
|
1336
1404
|
{
|
|
1337
1405
|
id: `g3p-marker-circle${suffix}`,
|
|
1338
|
-
className:
|
|
1406
|
+
className: "g3p-marker g3p-marker-circle",
|
|
1339
1407
|
markerWidth: size,
|
|
1340
1408
|
markerHeight: size,
|
|
1341
1409
|
refX: "2",
|
|
@@ -1346,7 +1414,7 @@ function circle(size, classPrefix, reverse = false) {
|
|
|
1346
1414
|
}
|
|
1347
1415
|
);
|
|
1348
1416
|
}
|
|
1349
|
-
function diamond(size,
|
|
1417
|
+
function diamond(size, reverse = false) {
|
|
1350
1418
|
const w = size * 0.7;
|
|
1351
1419
|
const h = size / 2;
|
|
1352
1420
|
const cy = size / 2;
|
|
@@ -1355,7 +1423,7 @@ function diamond(size, classPrefix, reverse = false) {
|
|
|
1355
1423
|
"marker",
|
|
1356
1424
|
{
|
|
1357
1425
|
id: `g3p-marker-diamond${suffix}`,
|
|
1358
|
-
className:
|
|
1426
|
+
className: "g3p-marker g3p-marker-diamond",
|
|
1359
1427
|
markerWidth: size,
|
|
1360
1428
|
markerHeight: size,
|
|
1361
1429
|
refX: "2",
|
|
@@ -1366,7 +1434,7 @@ function diamond(size, classPrefix, reverse = false) {
|
|
|
1366
1434
|
}
|
|
1367
1435
|
);
|
|
1368
1436
|
}
|
|
1369
|
-
function bar(size,
|
|
1437
|
+
function bar(size, reverse = false) {
|
|
1370
1438
|
const h = size * 0.6;
|
|
1371
1439
|
const cy = size / 2;
|
|
1372
1440
|
const suffix = reverse ? "-reverse" : "";
|
|
@@ -1374,7 +1442,7 @@ function bar(size, classPrefix, reverse = false) {
|
|
|
1374
1442
|
"marker",
|
|
1375
1443
|
{
|
|
1376
1444
|
id: `g3p-marker-bar${suffix}`,
|
|
1377
|
-
className:
|
|
1445
|
+
className: "g3p-marker g3p-marker-bar",
|
|
1378
1446
|
markerWidth: size,
|
|
1379
1447
|
markerHeight: size,
|
|
1380
1448
|
refX: "2",
|
|
@@ -1385,7 +1453,7 @@ function bar(size, classPrefix, reverse = false) {
|
|
|
1385
1453
|
}
|
|
1386
1454
|
);
|
|
1387
1455
|
}
|
|
1388
|
-
function none(size,
|
|
1456
|
+
function none(size, reverse = false) {
|
|
1389
1457
|
return void 0;
|
|
1390
1458
|
}
|
|
1391
1459
|
function normalize(data) {
|
|
@@ -1404,7 +1472,7 @@ var markerDefs = {
|
|
|
1404
1472
|
};
|
|
1405
1473
|
|
|
1406
1474
|
// src/graph/services/lines.ts
|
|
1407
|
-
var
|
|
1475
|
+
var log8 = logger("lines");
|
|
1408
1476
|
var Lines = class _Lines {
|
|
1409
1477
|
static layoutSeg(g, seg) {
|
|
1410
1478
|
const sourcePos = Layout.anchorPos(g, seg, "source");
|
|
@@ -1450,7 +1518,11 @@ var Lines = class _Lines {
|
|
|
1450
1518
|
validTrack = track;
|
|
1451
1519
|
break;
|
|
1452
1520
|
}
|
|
1453
|
-
|
|
1521
|
+
const minA = Math.min(seg.p1, seg.p2);
|
|
1522
|
+
const maxA = Math.max(seg.p1, seg.p2);
|
|
1523
|
+
const minB = Math.min(other.p1, other.p2);
|
|
1524
|
+
const maxB = Math.max(other.p1, other.p2);
|
|
1525
|
+
if (minA < maxB && minB < maxA) {
|
|
1454
1526
|
overlap = true;
|
|
1455
1527
|
break;
|
|
1456
1528
|
}
|
|
@@ -1465,6 +1537,21 @@ var Lines = class _Lines {
|
|
|
1465
1537
|
else
|
|
1466
1538
|
trackSet.push([seg]);
|
|
1467
1539
|
}
|
|
1540
|
+
const midpoint = (s) => (s.p1 + s.p2) / 2;
|
|
1541
|
+
const sortTracks = (tracks2, goingRight) => {
|
|
1542
|
+
tracks2.sort((a, b) => {
|
|
1543
|
+
const midA = Math.max(...a.map(midpoint));
|
|
1544
|
+
const midB = Math.max(...b.map(midpoint));
|
|
1545
|
+
return goingRight ? midB - midA : midA - midB;
|
|
1546
|
+
});
|
|
1547
|
+
};
|
|
1548
|
+
sortTracks(rightTracks, true);
|
|
1549
|
+
sortTracks(leftTracks, false);
|
|
1550
|
+
allTracks.sort((a, b) => {
|
|
1551
|
+
const avgA = a.reduce((sum, s) => sum + midpoint(s), 0) / a.length;
|
|
1552
|
+
const avgB = b.reduce((sum, s) => sum + midpoint(s), 0) / b.length;
|
|
1553
|
+
return avgB - avgA;
|
|
1554
|
+
});
|
|
1468
1555
|
const tracks = [];
|
|
1469
1556
|
const all = leftTracks.concat(rightTracks).concat(allTracks);
|
|
1470
1557
|
for (const track of all)
|
|
@@ -1635,6 +1722,7 @@ var Lines = class _Lines {
|
|
|
1635
1722
|
};
|
|
1636
1723
|
|
|
1637
1724
|
// src/graph/graph.ts
|
|
1725
|
+
var log9 = logger("graph");
|
|
1638
1726
|
var emptyChanges = {
|
|
1639
1727
|
addedNodes: [],
|
|
1640
1728
|
removedNodes: [],
|
|
@@ -1909,29 +1997,8 @@ var screenPos = (x, y) => ({ x, y });
|
|
|
1909
1997
|
var canvasPos = (x, y) => ({ x, y });
|
|
1910
1998
|
var graphPos = (x, y) => ({ x, y });
|
|
1911
1999
|
|
|
1912
|
-
// src/canvas/node.tsx
|
|
1913
|
-
var import_node3 = __toESM(require("./node.css?raw"), 1);
|
|
1914
|
-
|
|
1915
|
-
// src/canvas/styler.ts
|
|
1916
|
-
var injected = {};
|
|
1917
|
-
function styler(name, styles4, prefix) {
|
|
1918
|
-
if (prefix === "g3p" && !injected[name]) {
|
|
1919
|
-
const style = document.createElement("style");
|
|
1920
|
-
style.textContent = styles4;
|
|
1921
|
-
document.head.appendChild(style);
|
|
1922
|
-
injected[name] = true;
|
|
1923
|
-
}
|
|
1924
|
-
return (str, condition) => {
|
|
1925
|
-
if (!(condition ?? true)) return "";
|
|
1926
|
-
const parts = str.split(/\s+/);
|
|
1927
|
-
const fixed = parts.map((p) => `${prefix}-${name}-${p}`);
|
|
1928
|
-
return fixed.join(" ");
|
|
1929
|
-
};
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
2000
|
// src/canvas/node.tsx
|
|
1933
2001
|
var import_jsx_runtime2 = require("jsx-dom/jsx-runtime");
|
|
1934
|
-
var log8 = logger("canvas");
|
|
1935
2002
|
var Node2 = class {
|
|
1936
2003
|
selected;
|
|
1937
2004
|
hovered;
|
|
@@ -1939,7 +2006,6 @@ var Node2 = class {
|
|
|
1939
2006
|
content;
|
|
1940
2007
|
canvas;
|
|
1941
2008
|
data;
|
|
1942
|
-
classPrefix;
|
|
1943
2009
|
isDummy;
|
|
1944
2010
|
pos;
|
|
1945
2011
|
constructor(canvas, data, isDummy = false) {
|
|
@@ -1947,20 +2013,18 @@ var Node2 = class {
|
|
|
1947
2013
|
this.data = data;
|
|
1948
2014
|
this.selected = false;
|
|
1949
2015
|
this.hovered = false;
|
|
1950
|
-
this.classPrefix = canvas.classPrefix;
|
|
1951
2016
|
this.isDummy = isDummy;
|
|
1952
2017
|
if (this.isDummy) {
|
|
1953
2018
|
const size = canvas.dummyNodeSize;
|
|
1954
2019
|
} else {
|
|
1955
2020
|
const render = data.render ?? canvas.renderNode;
|
|
1956
|
-
this.content = this.renderContent(render(data.data));
|
|
2021
|
+
this.content = this.renderContent(render(data.data, data));
|
|
1957
2022
|
}
|
|
1958
2023
|
}
|
|
1959
2024
|
remove() {
|
|
1960
2025
|
this.container.remove();
|
|
1961
2026
|
}
|
|
1962
2027
|
append() {
|
|
1963
|
-
console.log("append", this);
|
|
1964
2028
|
this.canvas.group.appendChild(this.container);
|
|
1965
2029
|
}
|
|
1966
2030
|
needsContentSize() {
|
|
@@ -1969,19 +2033,6 @@ var Node2 = class {
|
|
|
1969
2033
|
needsContainerSize() {
|
|
1970
2034
|
return !this.isDummy;
|
|
1971
2035
|
}
|
|
1972
|
-
handleClick(e) {
|
|
1973
|
-
e.stopPropagation();
|
|
1974
|
-
}
|
|
1975
|
-
handleMouseEnter(e) {
|
|
1976
|
-
}
|
|
1977
|
-
handleMouseLeave(e) {
|
|
1978
|
-
}
|
|
1979
|
-
handleContextMenu(e) {
|
|
1980
|
-
}
|
|
1981
|
-
handleMouseDown(e) {
|
|
1982
|
-
}
|
|
1983
|
-
handleMouseUp(e) {
|
|
1984
|
-
}
|
|
1985
2036
|
setPos(pos) {
|
|
1986
2037
|
this.pos = pos;
|
|
1987
2038
|
const { x, y } = pos;
|
|
@@ -1998,7 +2049,6 @@ var Node2 = class {
|
|
|
1998
2049
|
return el;
|
|
1999
2050
|
}
|
|
2000
2051
|
renderContainer() {
|
|
2001
|
-
const c = styler("node", import_node3.default, this.classPrefix);
|
|
2002
2052
|
const hasPorts = this.hasPorts();
|
|
2003
2053
|
const inner = this.isDummy ? this.renderDummy() : this.renderForeign();
|
|
2004
2054
|
const nodeType = this.data?.type;
|
|
@@ -2006,14 +2056,8 @@ var Node2 = class {
|
|
|
2006
2056
|
this.container = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2007
2057
|
"g",
|
|
2008
2058
|
{
|
|
2009
|
-
className:
|
|
2010
|
-
|
|
2011
|
-
onMouseEnter: (e) => this.handleMouseEnter(e),
|
|
2012
|
-
onMouseLeave: (e) => this.handleMouseLeave(e),
|
|
2013
|
-
onContextMenu: (e) => this.handleContextMenu(e),
|
|
2014
|
-
onMouseDown: (e) => this.handleMouseDown(e),
|
|
2015
|
-
onMouseUp: (e) => this.handleMouseUp(e),
|
|
2016
|
-
style: { cursor: "pointer" },
|
|
2059
|
+
className: `g3p-node-container ${this.isDummy ? "g3p-node-dummy" : ""} ${typeClass}`.trim(),
|
|
2060
|
+
"data-node-id": this.data?.id,
|
|
2017
2061
|
children: inner
|
|
2018
2062
|
}
|
|
2019
2063
|
);
|
|
@@ -2023,7 +2067,6 @@ var Node2 = class {
|
|
|
2023
2067
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("foreignObject", { width: w, height: h, children: this.content });
|
|
2024
2068
|
}
|
|
2025
2069
|
renderDummy() {
|
|
2026
|
-
const c = styler("node", import_node3.default, this.classPrefix);
|
|
2027
2070
|
let w = this.canvas.dummyNodeSize;
|
|
2028
2071
|
let h = this.canvas.dummyNodeSize;
|
|
2029
2072
|
w /= 2;
|
|
@@ -2036,7 +2079,7 @@ var Node2 = class {
|
|
|
2036
2079
|
cy: h,
|
|
2037
2080
|
rx: w,
|
|
2038
2081
|
ry: h,
|
|
2039
|
-
className:
|
|
2082
|
+
className: "g3p-node-background"
|
|
2040
2083
|
}
|
|
2041
2084
|
),
|
|
2042
2085
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -2047,7 +2090,7 @@ var Node2 = class {
|
|
|
2047
2090
|
rx: w,
|
|
2048
2091
|
ry: h,
|
|
2049
2092
|
fill: "none",
|
|
2050
|
-
className:
|
|
2093
|
+
className: "g3p-node-border"
|
|
2051
2094
|
}
|
|
2052
2095
|
)
|
|
2053
2096
|
] });
|
|
@@ -2060,7 +2103,7 @@ var Node2 = class {
|
|
|
2060
2103
|
const ports = data.ports?.[dir];
|
|
2061
2104
|
if (!ports) continue;
|
|
2062
2105
|
for (const port of ports) {
|
|
2063
|
-
const el = this.content.querySelector(
|
|
2106
|
+
const el = this.content.querySelector(`.g3p-node-port[data-node-id="${data.id}"][data-port-id="${port.id}"]`);
|
|
2064
2107
|
if (!el) continue;
|
|
2065
2108
|
const portRect = el.getBoundingClientRect();
|
|
2066
2109
|
if (isVertical) {
|
|
@@ -2098,23 +2141,22 @@ var Node2 = class {
|
|
|
2098
2141
|
renderPortRow(dir, inout) {
|
|
2099
2142
|
const ports = this.data?.ports?.[dir];
|
|
2100
2143
|
if (!ports?.length) return null;
|
|
2101
|
-
const c = styler("node", import_node3.default, this.classPrefix);
|
|
2102
2144
|
const pos = this.getPortPosition(dir);
|
|
2103
2145
|
const isVertical = this.isVerticalOrientation();
|
|
2104
2146
|
const layoutClass = isVertical ? "row" : "col";
|
|
2105
2147
|
const rotateLabels = false;
|
|
2106
2148
|
const rotateClass = rotateLabels ? `port-rotated-${pos}` : "";
|
|
2107
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className:
|
|
2149
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: `g3p-node-ports g3p-node-ports-${layoutClass}`, children: ports.map((port) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2108
2150
|
"div",
|
|
2109
2151
|
{
|
|
2110
|
-
|
|
2111
|
-
|
|
2152
|
+
className: `g3p-node-port g3p-node-port-${inout}-${pos} ${rotateClass}`,
|
|
2153
|
+
"data-node-id": this.data.id,
|
|
2154
|
+
"data-port-id": port.id,
|
|
2112
2155
|
children: port.label ?? port.id
|
|
2113
2156
|
}
|
|
2114
2157
|
)) });
|
|
2115
2158
|
}
|
|
2116
2159
|
renderInsidePorts(el) {
|
|
2117
|
-
const c = styler("node", import_node3.default, this.classPrefix);
|
|
2118
2160
|
const isVertical = this.isVerticalOrientation();
|
|
2119
2161
|
const isReversed = this.isReversedOrientation();
|
|
2120
2162
|
let inPorts = this.renderPortRow("in", "in");
|
|
@@ -2122,14 +2164,13 @@ var Node2 = class {
|
|
|
2122
2164
|
if (!inPorts && !outPorts) return el;
|
|
2123
2165
|
if (isReversed) [inPorts, outPorts] = [outPorts, inPorts];
|
|
2124
2166
|
const wrapperClass = isVertical ? "v" : "h";
|
|
2125
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className:
|
|
2167
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `g3p-node-with-ports g3p-node-with-ports-${wrapperClass}`, children: [
|
|
2126
2168
|
inPorts,
|
|
2127
2169
|
el,
|
|
2128
2170
|
outPorts
|
|
2129
2171
|
] });
|
|
2130
2172
|
}
|
|
2131
2173
|
renderOutsidePorts(el) {
|
|
2132
|
-
const c = styler("node", import_node3.default, this.classPrefix);
|
|
2133
2174
|
const isVertical = this.isVerticalOrientation();
|
|
2134
2175
|
const isReversed = this.isReversedOrientation();
|
|
2135
2176
|
let inPorts = this.renderPortRow("in", "out");
|
|
@@ -2137,71 +2178,59 @@ var Node2 = class {
|
|
|
2137
2178
|
if (!inPorts && !outPorts) return el;
|
|
2138
2179
|
if (isReversed) [inPorts, outPorts] = [outPorts, inPorts];
|
|
2139
2180
|
const wrapperClass = isVertical ? "v" : "h";
|
|
2140
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className:
|
|
2181
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `g3p-node-with-ports g3p-node-with-ports-${wrapperClass}`, children: [
|
|
2141
2182
|
inPorts,
|
|
2142
2183
|
el,
|
|
2143
2184
|
outPorts
|
|
2144
2185
|
] });
|
|
2145
2186
|
}
|
|
2146
2187
|
renderBorder(el) {
|
|
2147
|
-
|
|
2148
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: c("border"), children: el });
|
|
2188
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "g3p-node-border", children: el });
|
|
2149
2189
|
}
|
|
2150
2190
|
};
|
|
2151
2191
|
|
|
2152
2192
|
// src/canvas/seg.tsx
|
|
2153
|
-
var import_seg3 = __toESM(require("./seg.css?raw"), 1);
|
|
2154
2193
|
var import_jsx_runtime3 = require("jsx-dom/jsx-runtime");
|
|
2155
|
-
var log9 = logger("canvas");
|
|
2156
2194
|
var Seg2 = class {
|
|
2157
2195
|
id;
|
|
2158
2196
|
selected;
|
|
2159
2197
|
hovered;
|
|
2160
2198
|
canvas;
|
|
2161
|
-
classPrefix;
|
|
2162
2199
|
type;
|
|
2163
2200
|
svg;
|
|
2164
2201
|
el;
|
|
2165
2202
|
source;
|
|
2166
2203
|
target;
|
|
2204
|
+
edgeIds;
|
|
2167
2205
|
constructor(canvas, data, g) {
|
|
2168
2206
|
this.id = data.id;
|
|
2169
2207
|
this.canvas = canvas;
|
|
2170
2208
|
this.selected = false;
|
|
2171
2209
|
this.hovered = false;
|
|
2172
2210
|
this.svg = data.svg;
|
|
2173
|
-
this.classPrefix = canvas.classPrefix;
|
|
2174
2211
|
this.source = { ...data.source, isDummy: data.sourceNode(g).isDummy };
|
|
2175
2212
|
this.target = { ...data.target, isDummy: data.targetNode(g).isDummy };
|
|
2176
2213
|
this.type = data.type;
|
|
2214
|
+
this.edgeIds = data.edgeIds.toArray();
|
|
2177
2215
|
this.el = this.render();
|
|
2178
2216
|
}
|
|
2179
|
-
handleClick(e) {
|
|
2180
|
-
e.stopPropagation();
|
|
2181
|
-
}
|
|
2182
|
-
handleMouseEnter(e) {
|
|
2183
|
-
}
|
|
2184
|
-
handleMouseLeave(e) {
|
|
2185
|
-
}
|
|
2186
|
-
handleContextMenu(e) {
|
|
2187
|
-
}
|
|
2188
2217
|
append() {
|
|
2189
2218
|
this.canvas.group.appendChild(this.el);
|
|
2190
2219
|
}
|
|
2191
2220
|
remove() {
|
|
2192
2221
|
this.el.remove();
|
|
2193
2222
|
}
|
|
2194
|
-
update(data) {
|
|
2223
|
+
update(data, g) {
|
|
2195
2224
|
this.svg = data.svg;
|
|
2196
2225
|
this.type = data.type;
|
|
2197
|
-
this.source = data.source;
|
|
2198
|
-
this.target = data.target;
|
|
2226
|
+
this.source = { ...data.source, isDummy: data.sourceNode(g).isDummy };
|
|
2227
|
+
this.target = { ...data.target, isDummy: data.targetNode(g).isDummy };
|
|
2228
|
+
this.edgeIds = data.edgeIds.toArray();
|
|
2199
2229
|
this.remove();
|
|
2200
2230
|
this.el = this.render();
|
|
2201
2231
|
this.append();
|
|
2202
2232
|
}
|
|
2203
2233
|
render() {
|
|
2204
|
-
const c = styler("seg", import_seg3.default, this.classPrefix);
|
|
2205
2234
|
let { source, target } = normalize(this);
|
|
2206
2235
|
if (this.source.isDummy) source = void 0;
|
|
2207
2236
|
if (this.target.isDummy) target = void 0;
|
|
@@ -2211,18 +2240,15 @@ var Seg2 = class {
|
|
|
2211
2240
|
{
|
|
2212
2241
|
ref: (el) => this.el = el,
|
|
2213
2242
|
id: `g3p-seg-${this.id}`,
|
|
2214
|
-
className:
|
|
2215
|
-
|
|
2216
|
-
onMouseEnter: this.handleMouseEnter.bind(this),
|
|
2217
|
-
onMouseLeave: this.handleMouseLeave.bind(this),
|
|
2218
|
-
onContextMenu: this.handleContextMenu.bind(this),
|
|
2243
|
+
className: `g3p-seg-container ${typeClass}`.trim(),
|
|
2244
|
+
"data-edge-id": this.id,
|
|
2219
2245
|
children: [
|
|
2220
2246
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2221
2247
|
"path",
|
|
2222
2248
|
{
|
|
2223
2249
|
d: this.svg,
|
|
2224
2250
|
fill: "none",
|
|
2225
|
-
className:
|
|
2251
|
+
className: "g3p-seg-line",
|
|
2226
2252
|
markerStart: source ? `url(#g3p-marker-${source}-reverse)` : void 0,
|
|
2227
2253
|
markerEnd: target ? `url(#g3p-marker-${target})` : void 0
|
|
2228
2254
|
}
|
|
@@ -2233,7 +2259,7 @@ var Seg2 = class {
|
|
|
2233
2259
|
d: this.svg,
|
|
2234
2260
|
stroke: "transparent",
|
|
2235
2261
|
fill: "none",
|
|
2236
|
-
className:
|
|
2262
|
+
className: "g3p-seg-hitbox",
|
|
2237
2263
|
style: { cursor: "pointer" }
|
|
2238
2264
|
}
|
|
2239
2265
|
)
|
|
@@ -2243,46 +2269,561 @@ var Seg2 = class {
|
|
|
2243
2269
|
}
|
|
2244
2270
|
};
|
|
2245
2271
|
|
|
2246
|
-
// src/canvas/
|
|
2247
|
-
var
|
|
2248
|
-
|
|
2272
|
+
// src/canvas/editMode.ts
|
|
2273
|
+
var EditMode = class {
|
|
2274
|
+
_state = { type: "idle" };
|
|
2275
|
+
_editable = false;
|
|
2276
|
+
get state() {
|
|
2277
|
+
return this._state;
|
|
2278
|
+
}
|
|
2279
|
+
get editable() {
|
|
2280
|
+
return this._editable;
|
|
2281
|
+
}
|
|
2282
|
+
set editable(value) {
|
|
2283
|
+
this._editable = value;
|
|
2284
|
+
if (!value) {
|
|
2285
|
+
this.reset();
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
get isIdle() {
|
|
2289
|
+
return this._state.type === "idle";
|
|
2290
|
+
}
|
|
2291
|
+
get isPanning() {
|
|
2292
|
+
return this._state.type === "panning";
|
|
2293
|
+
}
|
|
2294
|
+
get isCreatingEdge() {
|
|
2295
|
+
return this._state.type === "new-edge";
|
|
2296
|
+
}
|
|
2297
|
+
/** Start panning the canvas */
|
|
2298
|
+
startPan(startCanvas, startTransform) {
|
|
2299
|
+
this._state = { type: "panning", startCanvas, startTransform };
|
|
2300
|
+
}
|
|
2301
|
+
/** Start creating a new edge from a node or port */
|
|
2302
|
+
startNewEdge(id, startGraph, port) {
|
|
2303
|
+
if (!this._editable) return;
|
|
2304
|
+
this._state = {
|
|
2305
|
+
type: "new-edge",
|
|
2306
|
+
source: { id, port },
|
|
2307
|
+
startGraph,
|
|
2308
|
+
currentGraph: startGraph,
|
|
2309
|
+
target: null
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
/** Update the current position during new-edge mode */
|
|
2313
|
+
updateNewEdgePosition(currentGraph) {
|
|
2314
|
+
if (this._state.type === "new-edge") {
|
|
2315
|
+
this._state = { ...this._state, currentGraph };
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
/** Update the hover target during new-edge mode */
|
|
2319
|
+
setHoverTarget(target) {
|
|
2320
|
+
if (this._state.type === "new-edge") {
|
|
2321
|
+
this._state = { ...this._state, target };
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
/** Get the new-edge state if active */
|
|
2325
|
+
getNewEdgeState() {
|
|
2326
|
+
if (this._state.type === "new-edge") {
|
|
2327
|
+
return this._state;
|
|
2328
|
+
}
|
|
2329
|
+
return null;
|
|
2330
|
+
}
|
|
2331
|
+
/** Reset to idle state */
|
|
2332
|
+
reset() {
|
|
2333
|
+
this._state = { type: "idle" };
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2336
|
+
|
|
2337
|
+
// src/canvas/newEdge.tsx
|
|
2249
2338
|
var import_jsx_runtime4 = require("jsx-dom/jsx-runtime");
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2339
|
+
function renderNewEdge({ start, end }) {
|
|
2340
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { className: "g3p-new-edge-container", children: [
|
|
2341
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2342
|
+
"circle",
|
|
2343
|
+
{
|
|
2344
|
+
cx: start.x,
|
|
2345
|
+
cy: start.y,
|
|
2346
|
+
r: 4,
|
|
2347
|
+
className: "g3p-new-edge-origin"
|
|
2348
|
+
}
|
|
2349
|
+
),
|
|
2350
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2351
|
+
"line",
|
|
2352
|
+
{
|
|
2353
|
+
x1: start.x,
|
|
2354
|
+
y1: start.y,
|
|
2355
|
+
x2: end.x,
|
|
2356
|
+
y2: end.y,
|
|
2357
|
+
className: "g3p-new-edge-line"
|
|
2358
|
+
}
|
|
2359
|
+
),
|
|
2360
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2361
|
+
"circle",
|
|
2362
|
+
{
|
|
2363
|
+
cx: end.x,
|
|
2364
|
+
cy: end.y,
|
|
2365
|
+
r: 3,
|
|
2366
|
+
className: "g3p-new-edge-end"
|
|
2367
|
+
}
|
|
2368
|
+
)
|
|
2369
|
+
] });
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
// src/canvas/modal.tsx
|
|
2373
|
+
var import_jsx_runtime5 = require("jsx-dom/jsx-runtime");
|
|
2374
|
+
var Modal = class {
|
|
2375
|
+
container;
|
|
2376
|
+
overlay;
|
|
2377
|
+
dialog;
|
|
2378
|
+
onClose;
|
|
2379
|
+
mouseDownOnOverlay = false;
|
|
2380
|
+
constructor(options) {
|
|
2381
|
+
this.onClose = options.onClose;
|
|
2382
|
+
this.overlay = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2383
|
+
"div",
|
|
2384
|
+
{
|
|
2385
|
+
className: "g3p-modal-overlay",
|
|
2386
|
+
onMouseDown: (e) => {
|
|
2387
|
+
this.mouseDownOnOverlay = e.target === this.overlay;
|
|
2388
|
+
},
|
|
2389
|
+
onMouseUp: (e) => {
|
|
2390
|
+
if (this.mouseDownOnOverlay && e.target === this.overlay) this.close();
|
|
2391
|
+
this.mouseDownOnOverlay = false;
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
);
|
|
2395
|
+
this.dialog = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-dialog", children: [
|
|
2396
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-header", children: [
|
|
2397
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "g3p-modal-title", children: options.title }),
|
|
2398
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2399
|
+
"button",
|
|
2400
|
+
{
|
|
2401
|
+
className: "g3p-modal-close",
|
|
2402
|
+
onClick: () => this.close(),
|
|
2403
|
+
children: "\xD7"
|
|
2404
|
+
}
|
|
2405
|
+
)
|
|
2406
|
+
] }),
|
|
2407
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-body" }),
|
|
2408
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-footer" })
|
|
2409
|
+
] });
|
|
2410
|
+
if (options.position) {
|
|
2411
|
+
this.dialog.style.position = "absolute";
|
|
2412
|
+
this.dialog.style.left = `${options.position.x}px`;
|
|
2413
|
+
this.dialog.style.top = `${options.position.y}px`;
|
|
2414
|
+
this.dialog.style.transform = "translate(-50%, -50%)";
|
|
2415
|
+
}
|
|
2416
|
+
this.overlay.appendChild(this.dialog);
|
|
2417
|
+
this.container = this.overlay;
|
|
2418
|
+
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
2419
|
+
document.addEventListener("keydown", this.handleKeyDown);
|
|
2420
|
+
}
|
|
2421
|
+
handleKeyDown(e) {
|
|
2422
|
+
if (e.key === "Escape") {
|
|
2423
|
+
this.close();
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
get body() {
|
|
2427
|
+
return this.dialog.querySelector(".g3p-modal-body");
|
|
2428
|
+
}
|
|
2429
|
+
get footer() {
|
|
2430
|
+
return this.dialog.querySelector(".g3p-modal-footer");
|
|
2431
|
+
}
|
|
2432
|
+
show(parent) {
|
|
2433
|
+
parent.appendChild(this.container);
|
|
2434
|
+
const firstInput = this.dialog.querySelector("input, select, button");
|
|
2435
|
+
if (firstInput) firstInput.focus();
|
|
2436
|
+
}
|
|
2437
|
+
close() {
|
|
2438
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2439
|
+
this.container.remove();
|
|
2440
|
+
this.onClose();
|
|
2441
|
+
}
|
|
2265
2442
|
};
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2443
|
+
var NewNodeModal = class extends Modal {
|
|
2444
|
+
fieldInputs = /* @__PURE__ */ new Map();
|
|
2445
|
+
typeSelect;
|
|
2446
|
+
submitCallback;
|
|
2447
|
+
fields;
|
|
2448
|
+
constructor(options) {
|
|
2449
|
+
super({
|
|
2450
|
+
title: "New Node",
|
|
2451
|
+
onClose: () => options.onCancel?.()
|
|
2452
|
+
});
|
|
2453
|
+
this.submitCallback = options.onSubmit;
|
|
2454
|
+
this.fields = options.fields ?? /* @__PURE__ */ new Map([["title", "string"]]);
|
|
2455
|
+
this.renderBody(options.nodeTypes);
|
|
2456
|
+
this.renderFooter();
|
|
2457
|
+
}
|
|
2458
|
+
renderBody(nodeTypes) {
|
|
2459
|
+
this.body.innerHTML = "";
|
|
2460
|
+
for (const [name, type] of this.fields) {
|
|
2461
|
+
const label = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2462
|
+
const fieldGroup = this.renderField(name, label, type);
|
|
2463
|
+
this.body.appendChild(fieldGroup);
|
|
2277
2464
|
}
|
|
2278
|
-
if (
|
|
2279
|
-
|
|
2280
|
-
|
|
2465
|
+
if (nodeTypes && nodeTypes.length > 0) {
|
|
2466
|
+
const typeGroup = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2467
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: "Type" }),
|
|
2468
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2469
|
+
"select",
|
|
2470
|
+
{
|
|
2471
|
+
className: "g3p-modal-select",
|
|
2472
|
+
ref: (el) => this.typeSelect = el,
|
|
2473
|
+
children: [
|
|
2474
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", children: "Default" }),
|
|
2475
|
+
nodeTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, children: type }))
|
|
2476
|
+
]
|
|
2477
|
+
}
|
|
2478
|
+
)
|
|
2479
|
+
] });
|
|
2480
|
+
this.body.appendChild(typeGroup);
|
|
2281
2481
|
}
|
|
2282
2482
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2483
|
+
renderField(name, label, type) {
|
|
2484
|
+
if (type === "boolean") {
|
|
2485
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-field g3p-modal-field-checkbox", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "g3p-modal-label", children: [
|
|
2486
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2487
|
+
"input",
|
|
2488
|
+
{
|
|
2489
|
+
type: "checkbox",
|
|
2490
|
+
className: "g3p-modal-checkbox",
|
|
2491
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2492
|
+
}
|
|
2493
|
+
),
|
|
2494
|
+
label
|
|
2495
|
+
] }) });
|
|
2496
|
+
}
|
|
2497
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2498
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: label }),
|
|
2499
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2500
|
+
"input",
|
|
2501
|
+
{
|
|
2502
|
+
type: type === "number" ? "number" : "text",
|
|
2503
|
+
className: "g3p-modal-input",
|
|
2504
|
+
placeholder: `Enter ${label.toLowerCase()}`,
|
|
2505
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2506
|
+
}
|
|
2507
|
+
)
|
|
2508
|
+
] });
|
|
2509
|
+
}
|
|
2510
|
+
renderFooter() {
|
|
2511
|
+
this.footer.innerHTML = "";
|
|
2512
|
+
this.footer.appendChild(
|
|
2513
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-buttons", children: [
|
|
2514
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2515
|
+
"button",
|
|
2516
|
+
{
|
|
2517
|
+
className: "g3p-modal-btn g3p-modal-btn-secondary",
|
|
2518
|
+
onClick: () => this.close(),
|
|
2519
|
+
children: "Cancel"
|
|
2520
|
+
}
|
|
2521
|
+
),
|
|
2522
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2523
|
+
"button",
|
|
2524
|
+
{
|
|
2525
|
+
className: "g3p-modal-btn g3p-modal-btn-primary",
|
|
2526
|
+
onClick: () => this.submit(),
|
|
2527
|
+
children: "Create"
|
|
2528
|
+
}
|
|
2529
|
+
)
|
|
2530
|
+
] })
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2533
|
+
submit() {
|
|
2534
|
+
const data = {};
|
|
2535
|
+
for (const [name, type] of this.fields) {
|
|
2536
|
+
const input = this.fieldInputs.get(name);
|
|
2537
|
+
if (!input) continue;
|
|
2538
|
+
if (type === "boolean") {
|
|
2539
|
+
data[name] = input.checked;
|
|
2540
|
+
} else if (type === "number") {
|
|
2541
|
+
const val = input.value;
|
|
2542
|
+
if (val) data[name] = Number(val);
|
|
2543
|
+
} else {
|
|
2544
|
+
const val = input.value.trim();
|
|
2545
|
+
if (val) data[name] = val;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
if (Object.keys(data).length === 0) {
|
|
2549
|
+
const firstInput = this.fieldInputs.values().next().value;
|
|
2550
|
+
if (firstInput) firstInput.focus();
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (this.typeSelect?.value) {
|
|
2554
|
+
data.type = this.typeSelect.value;
|
|
2555
|
+
}
|
|
2556
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2557
|
+
this.container.remove();
|
|
2558
|
+
this.submitCallback(data);
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2561
|
+
var EditNodeModal = class extends Modal {
|
|
2562
|
+
fieldInputs = /* @__PURE__ */ new Map();
|
|
2563
|
+
typeSelect;
|
|
2564
|
+
node;
|
|
2565
|
+
fields;
|
|
2566
|
+
submitCallback;
|
|
2567
|
+
deleteCallback;
|
|
2568
|
+
constructor(options) {
|
|
2569
|
+
super({
|
|
2570
|
+
title: "Edit Node",
|
|
2571
|
+
position: options.position,
|
|
2572
|
+
onClose: () => options.onCancel?.()
|
|
2573
|
+
});
|
|
2574
|
+
this.node = options.node;
|
|
2575
|
+
this.submitCallback = options.onSubmit;
|
|
2576
|
+
this.deleteCallback = options.onDelete;
|
|
2577
|
+
this.fields = options.fields ?? /* @__PURE__ */ new Map([["title", "string"]]);
|
|
2578
|
+
if (!options.fields && !this.node.title)
|
|
2579
|
+
this.node = { ...this.node, title: this.node.id };
|
|
2580
|
+
this.renderBody(options.nodeTypes);
|
|
2581
|
+
this.renderFooter();
|
|
2582
|
+
}
|
|
2583
|
+
renderBody(nodeTypes) {
|
|
2584
|
+
console.log(`renderBody`, this.node);
|
|
2585
|
+
this.body.innerHTML = "";
|
|
2586
|
+
for (const [name, type] of this.fields) {
|
|
2587
|
+
const label = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2588
|
+
const currentValue = this.node[name];
|
|
2589
|
+
const fieldGroup = this.renderField(name, label, type, currentValue);
|
|
2590
|
+
this.body.appendChild(fieldGroup);
|
|
2591
|
+
}
|
|
2592
|
+
if (nodeTypes && nodeTypes.length > 0) {
|
|
2593
|
+
const currentType = this.node.type ?? "";
|
|
2594
|
+
const typeGroup = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2595
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: "Type" }),
|
|
2596
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2597
|
+
"select",
|
|
2598
|
+
{
|
|
2599
|
+
className: "g3p-modal-select",
|
|
2600
|
+
ref: (el) => this.typeSelect = el,
|
|
2601
|
+
children: [
|
|
2602
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", selected: !currentType, children: "Default" }),
|
|
2603
|
+
nodeTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, selected: type === currentType, children: type }))
|
|
2604
|
+
]
|
|
2605
|
+
}
|
|
2606
|
+
)
|
|
2607
|
+
] });
|
|
2608
|
+
this.body.appendChild(typeGroup);
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
renderField(name, label, type, currentValue) {
|
|
2612
|
+
if (type === "boolean") {
|
|
2613
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-field g3p-modal-field-checkbox", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "g3p-modal-label", children: [
|
|
2614
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2615
|
+
"input",
|
|
2616
|
+
{
|
|
2617
|
+
type: "checkbox",
|
|
2618
|
+
className: "g3p-modal-checkbox",
|
|
2619
|
+
checked: !!currentValue,
|
|
2620
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2621
|
+
}
|
|
2622
|
+
),
|
|
2623
|
+
label
|
|
2624
|
+
] }) });
|
|
2625
|
+
}
|
|
2626
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2627
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: label }),
|
|
2628
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2629
|
+
"input",
|
|
2630
|
+
{
|
|
2631
|
+
type: type === "number" ? "number" : "text",
|
|
2632
|
+
className: "g3p-modal-input",
|
|
2633
|
+
value: currentValue ?? "",
|
|
2634
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2635
|
+
}
|
|
2636
|
+
)
|
|
2637
|
+
] });
|
|
2638
|
+
}
|
|
2639
|
+
renderFooter() {
|
|
2640
|
+
this.footer.innerHTML = "";
|
|
2641
|
+
this.footer.appendChild(
|
|
2642
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-buttons", children: [
|
|
2643
|
+
this.deleteCallback && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2644
|
+
"button",
|
|
2645
|
+
{
|
|
2646
|
+
className: "g3p-modal-btn g3p-modal-btn-danger",
|
|
2647
|
+
onClick: () => this.delete(),
|
|
2648
|
+
children: "Delete"
|
|
2649
|
+
}
|
|
2650
|
+
),
|
|
2651
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-spacer" }),
|
|
2652
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2653
|
+
"button",
|
|
2654
|
+
{
|
|
2655
|
+
className: "g3p-modal-btn g3p-modal-btn-secondary",
|
|
2656
|
+
onClick: () => this.close(),
|
|
2657
|
+
children: "Cancel"
|
|
2658
|
+
}
|
|
2659
|
+
),
|
|
2660
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2661
|
+
"button",
|
|
2662
|
+
{
|
|
2663
|
+
className: "g3p-modal-btn g3p-modal-btn-primary",
|
|
2664
|
+
onClick: () => this.submit(),
|
|
2665
|
+
children: "Save"
|
|
2666
|
+
}
|
|
2667
|
+
)
|
|
2668
|
+
] })
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2671
|
+
submit() {
|
|
2672
|
+
const data = { ...this.node };
|
|
2673
|
+
for (const [name, type] of this.fields) {
|
|
2674
|
+
const input = this.fieldInputs.get(name);
|
|
2675
|
+
if (!input) continue;
|
|
2676
|
+
if (type === "boolean") {
|
|
2677
|
+
data[name] = input.checked;
|
|
2678
|
+
} else if (type === "number") {
|
|
2679
|
+
const val = input.value;
|
|
2680
|
+
data[name] = val ? Number(val) : void 0;
|
|
2681
|
+
} else {
|
|
2682
|
+
const val = input.value.trim();
|
|
2683
|
+
data[name] = val || void 0;
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
if (this.typeSelect) {
|
|
2687
|
+
data.type = this.typeSelect.value || void 0;
|
|
2688
|
+
}
|
|
2689
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2690
|
+
this.container.remove();
|
|
2691
|
+
this.submitCallback(data);
|
|
2692
|
+
}
|
|
2693
|
+
delete() {
|
|
2694
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2695
|
+
this.container.remove();
|
|
2696
|
+
this.deleteCallback?.();
|
|
2697
|
+
}
|
|
2698
|
+
};
|
|
2699
|
+
var EditEdgeModal = class _EditEdgeModal extends Modal {
|
|
2700
|
+
sourceMarkerSelect;
|
|
2701
|
+
targetMarkerSelect;
|
|
2702
|
+
typeSelect;
|
|
2703
|
+
edge;
|
|
2704
|
+
submitCallback;
|
|
2705
|
+
deleteCallback;
|
|
2706
|
+
static markerTypes = ["none", "arrow", "circle", "diamond", "bar"];
|
|
2707
|
+
constructor(options) {
|
|
2708
|
+
super({
|
|
2709
|
+
title: "Edit Edge",
|
|
2710
|
+
onClose: () => options.onCancel?.()
|
|
2711
|
+
});
|
|
2712
|
+
this.edge = options.edge;
|
|
2713
|
+
this.submitCallback = options.onSubmit;
|
|
2714
|
+
this.deleteCallback = options.onDelete;
|
|
2715
|
+
this.renderBody(options.edgeTypes);
|
|
2716
|
+
this.renderFooter();
|
|
2717
|
+
}
|
|
2718
|
+
renderBody(edgeTypes) {
|
|
2719
|
+
this.body.innerHTML = "";
|
|
2720
|
+
const currentSourceMarker = this.edge.source?.marker ?? "none";
|
|
2721
|
+
const currentTargetMarker = this.edge.target?.marker ?? "arrow";
|
|
2722
|
+
const sourceGroup = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2723
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: "Source Marker" }),
|
|
2724
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2725
|
+
"select",
|
|
2726
|
+
{
|
|
2727
|
+
className: "g3p-modal-select",
|
|
2728
|
+
ref: (el) => this.sourceMarkerSelect = el,
|
|
2729
|
+
children: _EditEdgeModal.markerTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, selected: type === currentSourceMarker, children: type }))
|
|
2730
|
+
}
|
|
2731
|
+
)
|
|
2732
|
+
] });
|
|
2733
|
+
this.body.appendChild(sourceGroup);
|
|
2734
|
+
const targetGroup = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2735
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: "Target Marker" }),
|
|
2736
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2737
|
+
"select",
|
|
2738
|
+
{
|
|
2739
|
+
className: "g3p-modal-select",
|
|
2740
|
+
ref: (el) => this.targetMarkerSelect = el,
|
|
2741
|
+
children: _EditEdgeModal.markerTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, selected: type === currentTargetMarker, children: type }))
|
|
2742
|
+
}
|
|
2743
|
+
)
|
|
2744
|
+
] });
|
|
2745
|
+
this.body.appendChild(targetGroup);
|
|
2746
|
+
if (edgeTypes && edgeTypes.length > 0) {
|
|
2747
|
+
const currentType = this.edge.type ?? "";
|
|
2748
|
+
const typeGroup = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2749
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: "Type" }),
|
|
2750
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2751
|
+
"select",
|
|
2752
|
+
{
|
|
2753
|
+
className: "g3p-modal-select",
|
|
2754
|
+
ref: (el) => this.typeSelect = el,
|
|
2755
|
+
children: [
|
|
2756
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", selected: !currentType, children: "Default" }),
|
|
2757
|
+
edgeTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, selected: type === currentType, children: type }))
|
|
2758
|
+
]
|
|
2759
|
+
}
|
|
2760
|
+
)
|
|
2761
|
+
] });
|
|
2762
|
+
this.body.appendChild(typeGroup);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
renderFooter() {
|
|
2766
|
+
this.footer.innerHTML = "";
|
|
2767
|
+
this.footer.appendChild(
|
|
2768
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-buttons", children: [
|
|
2769
|
+
this.deleteCallback && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2770
|
+
"button",
|
|
2771
|
+
{
|
|
2772
|
+
className: "g3p-modal-btn g3p-modal-btn-danger",
|
|
2773
|
+
onClick: () => this.delete(),
|
|
2774
|
+
children: "Delete"
|
|
2775
|
+
}
|
|
2776
|
+
),
|
|
2777
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-spacer" }),
|
|
2778
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2779
|
+
"button",
|
|
2780
|
+
{
|
|
2781
|
+
className: "g3p-modal-btn g3p-modal-btn-secondary",
|
|
2782
|
+
onClick: () => this.close(),
|
|
2783
|
+
children: "Cancel"
|
|
2784
|
+
}
|
|
2785
|
+
),
|
|
2786
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2787
|
+
"button",
|
|
2788
|
+
{
|
|
2789
|
+
className: "g3p-modal-btn g3p-modal-btn-primary",
|
|
2790
|
+
onClick: () => this.submit(),
|
|
2791
|
+
children: "Save"
|
|
2792
|
+
}
|
|
2793
|
+
)
|
|
2794
|
+
] })
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
submit() {
|
|
2798
|
+
const data = {
|
|
2799
|
+
...this.edge,
|
|
2800
|
+
source: {
|
|
2801
|
+
...this.edge.source,
|
|
2802
|
+
marker: this.sourceMarkerSelect.value === "none" ? void 0 : this.sourceMarkerSelect.value
|
|
2803
|
+
},
|
|
2804
|
+
target: {
|
|
2805
|
+
...this.edge.target,
|
|
2806
|
+
marker: this.targetMarkerSelect.value === "none" ? void 0 : this.targetMarkerSelect.value
|
|
2807
|
+
}
|
|
2808
|
+
};
|
|
2809
|
+
if (this.typeSelect) {
|
|
2810
|
+
data.type = this.typeSelect.value || void 0;
|
|
2811
|
+
}
|
|
2812
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2813
|
+
this.container.remove();
|
|
2814
|
+
this.submitCallback(data);
|
|
2815
|
+
}
|
|
2816
|
+
delete() {
|
|
2817
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2818
|
+
this.container.remove();
|
|
2819
|
+
this.deleteCallback?.();
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
|
|
2823
|
+
// src/canvas/canvas.tsx
|
|
2824
|
+
var import_styles = __toESM(require("./styles.css?raw"), 1);
|
|
2825
|
+
var import_jsx_runtime6 = require("jsx-dom/jsx-runtime");
|
|
2826
|
+
var log10 = logger("canvas");
|
|
2286
2827
|
var Canvas = class {
|
|
2287
2828
|
container;
|
|
2288
2829
|
root;
|
|
@@ -2295,19 +2836,26 @@ var Canvas = class {
|
|
|
2295
2836
|
curSegs;
|
|
2296
2837
|
updating;
|
|
2297
2838
|
// Pan-zoom state
|
|
2298
|
-
isPanning = false;
|
|
2299
|
-
panStart = null;
|
|
2300
|
-
transformStart = null;
|
|
2301
2839
|
panScale = null;
|
|
2302
2840
|
zoomControls;
|
|
2303
|
-
|
|
2841
|
+
// Edit mode state machine
|
|
2842
|
+
editMode;
|
|
2843
|
+
api;
|
|
2844
|
+
// New-edge visual element
|
|
2845
|
+
newEdgeEl;
|
|
2846
|
+
// Pending drag state (for double-click debounce)
|
|
2847
|
+
pendingDrag = null;
|
|
2848
|
+
constructor(api, options) {
|
|
2304
2849
|
Object.assign(this, options);
|
|
2850
|
+
this.api = api;
|
|
2305
2851
|
this.allNodes = /* @__PURE__ */ new Map();
|
|
2306
2852
|
this.curNodes = /* @__PURE__ */ new Map();
|
|
2307
2853
|
this.curSegs = /* @__PURE__ */ new Map();
|
|
2308
2854
|
this.updating = false;
|
|
2309
2855
|
this.bounds = { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } };
|
|
2310
2856
|
this.transform = { x: 0, y: 0, scale: 1 };
|
|
2857
|
+
this.editMode = new EditMode();
|
|
2858
|
+
this.editMode.editable = this.editable;
|
|
2311
2859
|
this.createMeasurementContainer();
|
|
2312
2860
|
this.createCanvasContainer();
|
|
2313
2861
|
if (this.panZoom) this.setupPanZoom();
|
|
@@ -2352,7 +2900,6 @@ var Canvas = class {
|
|
|
2352
2900
|
if (gnode.isDummy) {
|
|
2353
2901
|
node = new Node2(this, gnode, true);
|
|
2354
2902
|
node.renderContainer();
|
|
2355
|
-
node.setPos(gnode.pos);
|
|
2356
2903
|
this.allNodes.set(key, node);
|
|
2357
2904
|
} else {
|
|
2358
2905
|
if (!this.allNodes.has(key))
|
|
@@ -2361,6 +2908,7 @@ var Canvas = class {
|
|
|
2361
2908
|
}
|
|
2362
2909
|
this.curNodes.set(gnode.id, node);
|
|
2363
2910
|
node.append();
|
|
2911
|
+
node.setPos(gnode.pos);
|
|
2364
2912
|
}
|
|
2365
2913
|
updateNode(gnode) {
|
|
2366
2914
|
if (gnode.isDummy) throw new Error("dummy node cannot be updated");
|
|
@@ -2382,10 +2930,10 @@ var Canvas = class {
|
|
|
2382
2930
|
this.curSegs.set(gseg.id, seg);
|
|
2383
2931
|
seg.append();
|
|
2384
2932
|
}
|
|
2385
|
-
updateSeg(gseg) {
|
|
2933
|
+
updateSeg(gseg, g) {
|
|
2386
2934
|
const seg = this.curSegs.get(gseg.id);
|
|
2387
2935
|
if (!seg) throw new Error("seg not found");
|
|
2388
|
-
seg.update(gseg);
|
|
2936
|
+
seg.update(gseg, g);
|
|
2389
2937
|
}
|
|
2390
2938
|
deleteSeg(gseg) {
|
|
2391
2939
|
const seg = this.curSegs.get(gseg.id);
|
|
@@ -2405,18 +2953,82 @@ var Canvas = class {
|
|
|
2405
2953
|
for (const node of newNodes.values()) {
|
|
2406
2954
|
node.measure(isVertical);
|
|
2407
2955
|
const { id, version } = node.data;
|
|
2408
|
-
const key =
|
|
2956
|
+
const key = `k:${id}:${version}`;
|
|
2409
2957
|
this.allNodes.set(key, node);
|
|
2410
2958
|
node.renderContainer();
|
|
2411
2959
|
}
|
|
2412
2960
|
this.measurement.innerHTML = "";
|
|
2413
2961
|
return newNodes;
|
|
2414
2962
|
}
|
|
2963
|
+
// ========== Mouse event handlers ==========
|
|
2415
2964
|
onClick(e) {
|
|
2416
|
-
|
|
2965
|
+
const hit = this.hitTest(e.clientX, e.clientY);
|
|
2966
|
+
if (hit.type === "node") {
|
|
2967
|
+
this.api.handleClickNode(hit.node.data.id);
|
|
2968
|
+
} else if (hit.type === "edge") {
|
|
2969
|
+
this.api.handleClickEdge(hit.segId);
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
onDoubleClick(e) {
|
|
2973
|
+
if (this.pendingDrag) {
|
|
2974
|
+
window.clearTimeout(this.pendingDrag.timeout);
|
|
2975
|
+
this.pendingDrag = null;
|
|
2976
|
+
}
|
|
2977
|
+
if (!this.editMode.editable) return;
|
|
2978
|
+
const hit = this.hitTest(e.clientX, e.clientY);
|
|
2979
|
+
if (hit.type === "node") {
|
|
2980
|
+
if (hit.node.isDummy) return;
|
|
2981
|
+
this.api.handleEditNode(hit.node.data.id);
|
|
2982
|
+
} else if (hit.type === "edge") {
|
|
2983
|
+
this.api.handleEditEdge(hit.segId);
|
|
2984
|
+
} else {
|
|
2985
|
+
this.api.handleNewNode();
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
// ========== Built-in Modals ==========
|
|
2989
|
+
/** Show the new node modal */
|
|
2990
|
+
showNewNodeModal(callback) {
|
|
2991
|
+
const nodeTypes = Object.keys(this.nodeTypes);
|
|
2992
|
+
const fields = this.api.getNodeFields();
|
|
2993
|
+
const modal = new NewNodeModal({
|
|
2994
|
+
nodeTypes: nodeTypes.length > 0 ? nodeTypes : void 0,
|
|
2995
|
+
fields: fields.size > 0 ? fields : void 0,
|
|
2996
|
+
onSubmit: (data) => {
|
|
2997
|
+
callback(data);
|
|
2998
|
+
}
|
|
2999
|
+
});
|
|
3000
|
+
modal.show(document.body);
|
|
3001
|
+
}
|
|
3002
|
+
/** Show the edit node modal */
|
|
3003
|
+
showEditNodeModal(node, callback) {
|
|
3004
|
+
const nodeTypes = Object.keys(this.nodeTypes);
|
|
3005
|
+
const fields = this.api.getNodeFields();
|
|
3006
|
+
const modal = new EditNodeModal({
|
|
3007
|
+
node,
|
|
3008
|
+
nodeTypes: nodeTypes.length > 0 ? nodeTypes : void 0,
|
|
3009
|
+
fields: fields.size > 0 ? fields : void 0,
|
|
3010
|
+
onSubmit: (data) => {
|
|
3011
|
+
callback(data);
|
|
3012
|
+
},
|
|
3013
|
+
onDelete: () => {
|
|
3014
|
+
this.api.handleDeleteNode(node.id);
|
|
3015
|
+
}
|
|
3016
|
+
});
|
|
3017
|
+
modal.show(document.body);
|
|
3018
|
+
}
|
|
3019
|
+
/** Show the edit edge modal */
|
|
3020
|
+
showEditEdgeModal(edge, callback) {
|
|
3021
|
+
const modal = new EditEdgeModal({
|
|
3022
|
+
edge,
|
|
3023
|
+
edgeTypes: Object.keys(this.edgeTypes),
|
|
3024
|
+
onSubmit: callback,
|
|
3025
|
+
onDelete: () => {
|
|
3026
|
+
this.api.handleDeleteEdge(edge.id);
|
|
3027
|
+
}
|
|
3028
|
+
});
|
|
3029
|
+
modal.show(document.body);
|
|
2417
3030
|
}
|
|
2418
3031
|
onContextMenu(e) {
|
|
2419
|
-
console.log("context menu", e);
|
|
2420
3032
|
}
|
|
2421
3033
|
groupTransform() {
|
|
2422
3034
|
return `translate(${this.transform.x}, ${this.transform.y}) scale(${this.transform.scale})`;
|
|
@@ -2431,53 +3043,52 @@ var Canvas = class {
|
|
|
2431
3043
|
}
|
|
2432
3044
|
generateDynamicStyles() {
|
|
2433
3045
|
let css = "";
|
|
2434
|
-
|
|
2435
|
-
css += themeToCSS(this.theme, `.${prefix}-canvas-container`);
|
|
3046
|
+
css += themeToCSS(this.theme, `.g3p-canvas-container`);
|
|
2436
3047
|
for (const [type, vars] of Object.entries(this.nodeTypes)) {
|
|
2437
|
-
css += themeToCSS(vars,
|
|
3048
|
+
css += themeToCSS(vars, `.g3p-node-type-${type}`, "node");
|
|
2438
3049
|
}
|
|
2439
3050
|
for (const [type, vars] of Object.entries(this.edgeTypes)) {
|
|
2440
|
-
css += themeToCSS(vars,
|
|
3051
|
+
css += themeToCSS(vars, `.g3p-edge-type-${type}`);
|
|
2441
3052
|
}
|
|
2442
3053
|
return css;
|
|
2443
3054
|
}
|
|
2444
3055
|
createCanvasContainer() {
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
3056
|
+
if (!document.getElementById("g3p-styles")) {
|
|
3057
|
+
const baseStyleEl = document.createElement("style");
|
|
3058
|
+
baseStyleEl.id = "g3p-styles";
|
|
3059
|
+
baseStyleEl.textContent = import_styles.default;
|
|
3060
|
+
document.head.appendChild(baseStyleEl);
|
|
3061
|
+
}
|
|
2451
3062
|
const dynamicStyles = this.generateDynamicStyles();
|
|
2452
3063
|
if (dynamicStyles) {
|
|
2453
|
-
const
|
|
2454
|
-
|
|
2455
|
-
document.head.appendChild(
|
|
3064
|
+
const dynamicStyleEl = document.createElement("style");
|
|
3065
|
+
dynamicStyleEl.textContent = dynamicStyles;
|
|
3066
|
+
document.head.appendChild(dynamicStyleEl);
|
|
2456
3067
|
}
|
|
2457
|
-
const c = styler("canvas", import_canvas.default, this.classPrefix);
|
|
2458
3068
|
const colorModeClass = this.colorMode !== "system" ? `g3p-${this.colorMode}` : "";
|
|
2459
|
-
this.container = /* @__PURE__ */ (0,
|
|
3069
|
+
this.container = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2460
3070
|
"div",
|
|
2461
3071
|
{
|
|
2462
|
-
className:
|
|
3072
|
+
className: `g3p-canvas-container ${colorModeClass}`.trim(),
|
|
2463
3073
|
ref: (el) => this.container = el,
|
|
2464
3074
|
onContextMenu: this.onContextMenu.bind(this),
|
|
2465
|
-
children: /* @__PURE__ */ (0,
|
|
3075
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2466
3076
|
"svg",
|
|
2467
3077
|
{
|
|
2468
3078
|
ref: (el) => this.root = el,
|
|
2469
|
-
className:
|
|
3079
|
+
className: "g3p-canvas-root",
|
|
2470
3080
|
width: this.width,
|
|
2471
3081
|
height: this.height,
|
|
2472
3082
|
viewBox: this.viewBox(),
|
|
2473
3083
|
preserveAspectRatio: "xMidYMid meet",
|
|
2474
3084
|
onClick: this.onClick.bind(this),
|
|
3085
|
+
onDblClick: this.onDoubleClick.bind(this),
|
|
2475
3086
|
children: [
|
|
2476
|
-
/* @__PURE__ */ (0,
|
|
2477
|
-
Object.values(markerDefs).map((marker) => marker(this.markerSize,
|
|
2478
|
-
Object.values(markerDefs).map((marker) => marker(this.markerSize,
|
|
3087
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("defs", { children: [
|
|
3088
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, false)),
|
|
3089
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, true))
|
|
2479
3090
|
] }),
|
|
2480
|
-
/* @__PURE__ */ (0,
|
|
3091
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2481
3092
|
"g",
|
|
2482
3093
|
{
|
|
2483
3094
|
ref: (el) => this.group = el,
|
|
@@ -2496,14 +3107,34 @@ var Canvas = class {
|
|
|
2496
3107
|
this.container.addEventListener("mousedown", this.onMouseDown.bind(this));
|
|
2497
3108
|
document.addEventListener("mousemove", this.onMouseMove.bind(this));
|
|
2498
3109
|
document.addEventListener("mouseup", this.onMouseUp.bind(this));
|
|
3110
|
+
document.addEventListener("keydown", this.onKeyDown.bind(this));
|
|
2499
3111
|
this.createZoomControls();
|
|
2500
3112
|
}
|
|
3113
|
+
onKeyDown(e) {
|
|
3114
|
+
if (e.key === "Escape" && this.editMode.isCreatingEdge) {
|
|
3115
|
+
this.endNewEdge(true);
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
2501
3118
|
/** Convert screen coordinates to canvas-relative coordinates */
|
|
2502
3119
|
screenToCanvas(screen) {
|
|
2503
3120
|
const rect = this.container.getBoundingClientRect();
|
|
2504
3121
|
return canvasPos(screen.x - rect.left, screen.y - rect.top);
|
|
2505
3122
|
}
|
|
2506
|
-
/**
|
|
3123
|
+
/** Convert canvas coordinates to graph coordinates */
|
|
3124
|
+
canvasToGraph(canvas) {
|
|
3125
|
+
const vb = this.currentViewBox();
|
|
3126
|
+
const { scale, offsetX, offsetY } = this.getEffectiveScale();
|
|
3127
|
+
return graphPos(
|
|
3128
|
+
vb.x - offsetX + canvas.x * scale,
|
|
3129
|
+
vb.y - offsetY + canvas.y * scale
|
|
3130
|
+
);
|
|
3131
|
+
}
|
|
3132
|
+
/** Convert screen coordinates to graph coordinates */
|
|
3133
|
+
screenToGraph(screen) {
|
|
3134
|
+
const canvas = this.screenToCanvas(screen);
|
|
3135
|
+
return this.canvasToGraph(canvas);
|
|
3136
|
+
}
|
|
3137
|
+
/**
|
|
2507
3138
|
* Get the effective scale from canvas pixels to graph units,
|
|
2508
3139
|
* accounting for preserveAspectRatio="xMidYMid meet" which uses
|
|
2509
3140
|
* the smaller scale (to fit) and centers the content.
|
|
@@ -2520,15 +3151,6 @@ var Canvas = class {
|
|
|
2520
3151
|
const offsetY = (actualH - vb.h) / 2;
|
|
2521
3152
|
return { scale, offsetX, offsetY };
|
|
2522
3153
|
}
|
|
2523
|
-
/** Convert canvas coordinates to graph coordinates */
|
|
2524
|
-
canvasToGraph(canvas) {
|
|
2525
|
-
const vb = this.currentViewBox();
|
|
2526
|
-
const { scale, offsetX, offsetY } = this.getEffectiveScale();
|
|
2527
|
-
return graphPos(
|
|
2528
|
-
vb.x - offsetX + canvas.x * scale,
|
|
2529
|
-
vb.y - offsetY + canvas.y * scale
|
|
2530
|
-
);
|
|
2531
|
-
}
|
|
2532
3154
|
/** Get current viewBox as an object */
|
|
2533
3155
|
currentViewBox() {
|
|
2534
3156
|
const p = this.padding;
|
|
@@ -2563,28 +3185,67 @@ var Canvas = class {
|
|
|
2563
3185
|
onMouseDown(e) {
|
|
2564
3186
|
if (e.button !== 0) return;
|
|
2565
3187
|
if (e.target.closest(".g3p-zoom-controls")) return;
|
|
2566
|
-
this.
|
|
2567
|
-
this.
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
3188
|
+
const hit = this.hitTest(e.clientX, e.clientY);
|
|
3189
|
+
if (this.editMode.editable && (hit.type === "node" || hit.type === "port")) {
|
|
3190
|
+
const node = hit.node;
|
|
3191
|
+
if (node.isDummy) return;
|
|
3192
|
+
e.preventDefault();
|
|
3193
|
+
e.stopPropagation();
|
|
3194
|
+
const startGraph = this.screenToGraph(hit.center);
|
|
3195
|
+
const portId = hit.type === "port" ? hit.port : void 0;
|
|
3196
|
+
this.pendingDrag = {
|
|
3197
|
+
timeout: window.setTimeout(() => {
|
|
3198
|
+
if (this.pendingDrag) {
|
|
3199
|
+
this.startNewEdge(this.pendingDrag.nodeId, this.pendingDrag.startGraph, this.pendingDrag.portId);
|
|
3200
|
+
this.pendingDrag = null;
|
|
3201
|
+
}
|
|
3202
|
+
}, 200),
|
|
3203
|
+
nodeId: node.data.id,
|
|
3204
|
+
startGraph,
|
|
3205
|
+
portId
|
|
3206
|
+
};
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
if (hit.type === "canvas" || hit.type === "edge") {
|
|
3210
|
+
const startCanvas = this.screenToCanvas(screenPos(e.clientX, e.clientY));
|
|
3211
|
+
this.editMode.startPan(startCanvas, { ...this.transform });
|
|
3212
|
+
const { scale } = this.getEffectiveScale();
|
|
3213
|
+
this.panScale = { x: scale, y: scale };
|
|
3214
|
+
this.container.style.cursor = "grabbing";
|
|
3215
|
+
e.preventDefault();
|
|
3216
|
+
}
|
|
2573
3217
|
}
|
|
2574
3218
|
onMouseMove(e) {
|
|
2575
|
-
if (
|
|
3219
|
+
if (this.editMode.isCreatingEdge) {
|
|
3220
|
+
const screenCursor = screenPos(e.clientX, e.clientY);
|
|
3221
|
+
const canvasCursor = this.screenToCanvas(screenCursor);
|
|
3222
|
+
const graphCursor = this.canvasToGraph(canvasCursor);
|
|
3223
|
+
this.editMode.updateNewEdgePosition(graphCursor);
|
|
3224
|
+
this.updateNewEdgeVisual();
|
|
3225
|
+
this.detectHoverTarget(e.clientX, e.clientY);
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
3228
|
+
if (!this.editMode.isPanning || !this.panScale) return;
|
|
3229
|
+
const panState = this.editMode.state;
|
|
3230
|
+
if (panState.type !== "panning") return;
|
|
2576
3231
|
const current = this.screenToCanvas(screenPos(e.clientX, e.clientY));
|
|
2577
|
-
const dx = current.x -
|
|
2578
|
-
const dy = current.y -
|
|
2579
|
-
this.transform.x =
|
|
2580
|
-
this.transform.y =
|
|
3232
|
+
const dx = current.x - panState.startCanvas.x;
|
|
3233
|
+
const dy = current.y - panState.startCanvas.y;
|
|
3234
|
+
this.transform.x = panState.startTransform.x + dx * this.panScale.x;
|
|
3235
|
+
this.transform.y = panState.startTransform.y + dy * this.panScale.y;
|
|
2581
3236
|
this.applyTransform();
|
|
2582
3237
|
}
|
|
2583
3238
|
onMouseUp(e) {
|
|
2584
|
-
if (
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
3239
|
+
if (this.pendingDrag) {
|
|
3240
|
+
window.clearTimeout(this.pendingDrag.timeout);
|
|
3241
|
+
this.pendingDrag = null;
|
|
3242
|
+
}
|
|
3243
|
+
if (this.editMode.isCreatingEdge) {
|
|
3244
|
+
this.endNewEdge(false);
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
if (!this.editMode.isPanning) return;
|
|
3248
|
+
this.editMode.reset();
|
|
2588
3249
|
this.panScale = null;
|
|
2589
3250
|
this.container.style.cursor = "";
|
|
2590
3251
|
}
|
|
@@ -2594,12 +3255,11 @@ var Canvas = class {
|
|
|
2594
3255
|
this.updateZoomLevel();
|
|
2595
3256
|
}
|
|
2596
3257
|
createZoomControls() {
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
/* @__PURE__ */ (0,
|
|
2600
|
-
/* @__PURE__ */ (0,
|
|
2601
|
-
/* @__PURE__ */ (0,
|
|
2602
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: `${c("btn")} ${c("reset")}`, onClick: () => this.zoomReset(), children: "\u27F2" })
|
|
3258
|
+
this.zoomControls = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "g3p-zoom-controls", children: [
|
|
3259
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "g3p-zoom-btn", onClick: () => this.zoomIn(), children: "+" }),
|
|
3260
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "g3p-zoom-level", id: "g3p-zoom-level", children: "100%" }),
|
|
3261
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "g3p-zoom-btn", onClick: () => this.zoomOut(), children: "\u2212" }),
|
|
3262
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "g3p-zoom-btn g3p-zoom-reset", onClick: () => this.zoomReset(), children: "\u27F2" })
|
|
2603
3263
|
] });
|
|
2604
3264
|
this.container.appendChild(this.zoomControls);
|
|
2605
3265
|
}
|
|
@@ -2621,17 +3281,215 @@ var Canvas = class {
|
|
|
2621
3281
|
this.transform = { x: 0, y: 0, scale: 1 };
|
|
2622
3282
|
this.applyTransform();
|
|
2623
3283
|
}
|
|
3284
|
+
// ==================== New-Edge Mode ====================
|
|
3285
|
+
/** Start creating a new edge from a node */
|
|
3286
|
+
startNewEdge(sourceNodeId, startGraph, sourcePortId) {
|
|
3287
|
+
this.editMode.startNewEdge(sourceNodeId, startGraph, sourcePortId);
|
|
3288
|
+
this.updateNewEdgeVisual();
|
|
3289
|
+
this.container.style.cursor = "crosshair";
|
|
3290
|
+
}
|
|
3291
|
+
/** Update the new-edge visual during drag */
|
|
3292
|
+
updateNewEdgeVisual() {
|
|
3293
|
+
const state = this.editMode.getNewEdgeState();
|
|
3294
|
+
if (!state) {
|
|
3295
|
+
this.removeNewEdgeVisual();
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
if (this.newEdgeEl) {
|
|
3299
|
+
this.newEdgeEl.remove();
|
|
3300
|
+
}
|
|
3301
|
+
this.newEdgeEl = renderNewEdge({
|
|
3302
|
+
start: state.startGraph,
|
|
3303
|
+
end: state.currentGraph
|
|
3304
|
+
});
|
|
3305
|
+
this.group.appendChild(this.newEdgeEl);
|
|
3306
|
+
}
|
|
3307
|
+
/** Remove the new-edge visual */
|
|
3308
|
+
removeNewEdgeVisual() {
|
|
3309
|
+
if (this.newEdgeEl) {
|
|
3310
|
+
this.newEdgeEl.remove();
|
|
3311
|
+
this.newEdgeEl = void 0;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
/** Complete or cancel the new-edge creation */
|
|
3315
|
+
endNewEdge(cancelled = false) {
|
|
3316
|
+
const state = this.editMode.getNewEdgeState();
|
|
3317
|
+
if (!state) return;
|
|
3318
|
+
if (!cancelled) {
|
|
3319
|
+
const { target, source } = state;
|
|
3320
|
+
if (target?.type == "node") {
|
|
3321
|
+
this.api.handleAddEdge({ id: "", source, target });
|
|
3322
|
+
} else {
|
|
3323
|
+
this.api.handleNewNodeFrom(source);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
this.removeNewEdgeVisual();
|
|
3327
|
+
this.clearDropTargetHighlight();
|
|
3328
|
+
this.editMode.reset();
|
|
3329
|
+
this.container.style.cursor = "";
|
|
3330
|
+
}
|
|
3331
|
+
/** Find node data by internal ID */
|
|
3332
|
+
findNodeDataById(nodeId) {
|
|
3333
|
+
for (const node of this.curNodes.values()) {
|
|
3334
|
+
if (node.data?.id === nodeId) {
|
|
3335
|
+
return node.data.data;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
return null;
|
|
3339
|
+
}
|
|
3340
|
+
/** Set hover target for new-edge mode */
|
|
3341
|
+
setNewEdgeHoverTarget(id, port) {
|
|
3342
|
+
this.clearDropTargetHighlight();
|
|
3343
|
+
this.editMode.setHoverTarget({ type: "node", id, port });
|
|
3344
|
+
if (port) {
|
|
3345
|
+
const portEl = this.container?.querySelector(`.g3p-node-port[data-node-id="${id}"][data-port-id="${port}"]`);
|
|
3346
|
+
if (portEl) portEl.classList.add("g3p-drop-target");
|
|
3347
|
+
} else {
|
|
3348
|
+
const node = this.curNodes.get(id);
|
|
3349
|
+
if (node?.container) node.container.classList.add("g3p-drop-target");
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
/** Clear hover target for new-edge mode */
|
|
3353
|
+
clearNewEdgeHoverTarget() {
|
|
3354
|
+
this.clearDropTargetHighlight();
|
|
3355
|
+
this.editMode.setHoverTarget(null);
|
|
3356
|
+
}
|
|
3357
|
+
/** Remove drop target highlight from all elements */
|
|
3358
|
+
clearDropTargetHighlight() {
|
|
3359
|
+
for (const node of this.curNodes.values()) {
|
|
3360
|
+
node.container?.classList.remove("g3p-drop-target");
|
|
3361
|
+
}
|
|
3362
|
+
this.container?.querySelectorAll(".g3p-drop-target").forEach((el) => {
|
|
3363
|
+
el.classList.remove("g3p-drop-target");
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
/** Detect hover target during new-edge drag using elementFromPoint */
|
|
3367
|
+
detectHoverTarget(clientX, clientY) {
|
|
3368
|
+
if (this.newEdgeEl) {
|
|
3369
|
+
this.newEdgeEl.style.display = "none";
|
|
3370
|
+
}
|
|
3371
|
+
const el = document.elementFromPoint(clientX, clientY);
|
|
3372
|
+
if (this.newEdgeEl) {
|
|
3373
|
+
this.newEdgeEl.style.display = "";
|
|
3374
|
+
}
|
|
3375
|
+
if (!el) {
|
|
3376
|
+
this.clearNewEdgeHoverTarget();
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
const portEl = el.closest(".g3p-node-port");
|
|
3380
|
+
if (portEl) {
|
|
3381
|
+
const nodeId = portEl.getAttribute("data-node-id");
|
|
3382
|
+
const portId = portEl.getAttribute("data-port-id");
|
|
3383
|
+
if (nodeId && portId) {
|
|
3384
|
+
const node = this.curNodes.get(nodeId);
|
|
3385
|
+
if (node && !node.isDummy) {
|
|
3386
|
+
this.setNewEdgeHoverTarget(nodeId, portId);
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
const nodeEl = el.closest(".g3p-node-container");
|
|
3392
|
+
if (nodeEl) {
|
|
3393
|
+
const nodeId = nodeEl.getAttribute("data-node-id");
|
|
3394
|
+
if (nodeId) {
|
|
3395
|
+
const node = this.curNodes.get(nodeId);
|
|
3396
|
+
if (node && !node.isDummy) {
|
|
3397
|
+
this.setNewEdgeHoverTarget(node.data.id);
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
this.clearNewEdgeHoverTarget();
|
|
3403
|
+
}
|
|
3404
|
+
// ==================== Hit Testing ====================
|
|
3405
|
+
/** Result of a hit test */
|
|
3406
|
+
hitTest(clientX, clientY) {
|
|
3407
|
+
const el = document.elementFromPoint(clientX, clientY);
|
|
3408
|
+
if (!el) return { type: "canvas" };
|
|
3409
|
+
const getCenter = (el2) => {
|
|
3410
|
+
const rect = el2.getBoundingClientRect();
|
|
3411
|
+
return screenPos(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
3412
|
+
};
|
|
3413
|
+
const portEl = el.closest(".g3p-node-port");
|
|
3414
|
+
if (portEl) {
|
|
3415
|
+
const nodeId = portEl.getAttribute("data-node-id");
|
|
3416
|
+
const portId = portEl.getAttribute("data-port-id");
|
|
3417
|
+
if (nodeId && portId) {
|
|
3418
|
+
const center = getCenter(portEl);
|
|
3419
|
+
const node = this.curNodes.get(nodeId);
|
|
3420
|
+
if (node) {
|
|
3421
|
+
return { type: "port", node, port: portId, center };
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
const nodeEl = el.closest(".g3p-node-container");
|
|
3426
|
+
if (nodeEl) {
|
|
3427
|
+
const nodeId = nodeEl.getAttribute("data-node-id");
|
|
3428
|
+
if (nodeId) {
|
|
3429
|
+
const borderEl = el.closest(".g3p-node-border");
|
|
3430
|
+
const center = getCenter(borderEl ?? nodeEl);
|
|
3431
|
+
const node = this.curNodes.get(nodeId);
|
|
3432
|
+
if (node) {
|
|
3433
|
+
return { type: "node", node, center };
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
const edgeEl = el.closest(".g3p-seg-container");
|
|
3438
|
+
if (edgeEl) {
|
|
3439
|
+
const segId = edgeEl.getAttribute("data-edge-id");
|
|
3440
|
+
if (segId) {
|
|
3441
|
+
return { type: "edge", segId };
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
return { type: "canvas" };
|
|
3445
|
+
}
|
|
2624
3446
|
};
|
|
3447
|
+
var themeVarMap = {
|
|
3448
|
+
// Canvas
|
|
3449
|
+
bg: "--g3p-bg",
|
|
3450
|
+
shadow: "--g3p-shadow",
|
|
3451
|
+
// Node
|
|
3452
|
+
border: "--g3p-border",
|
|
3453
|
+
borderHover: "--g3p-border-hover",
|
|
3454
|
+
borderSelected: "--g3p-border-selected",
|
|
3455
|
+
text: "--g3p-text",
|
|
3456
|
+
textMuted: "--g3p-text-muted",
|
|
3457
|
+
// Port
|
|
3458
|
+
bgHover: "--g3p-port-bg-hover",
|
|
3459
|
+
// Edge
|
|
3460
|
+
color: "--g3p-edge-color"
|
|
3461
|
+
};
|
|
3462
|
+
function themeToCSS(theme, selector, prefix = "") {
|
|
3463
|
+
const entries = Object.entries(theme).filter(([_, v]) => v !== void 0);
|
|
3464
|
+
if (!entries.length) return "";
|
|
3465
|
+
let css = `${selector} {
|
|
3466
|
+
`;
|
|
3467
|
+
for (const [key, value] of entries) {
|
|
3468
|
+
let cssVar = themeVarMap[key];
|
|
3469
|
+
if (key === "bg" && prefix === "node") {
|
|
3470
|
+
cssVar = "--g3p-bg-node";
|
|
3471
|
+
} else if (key === "bg" && prefix === "port") {
|
|
3472
|
+
cssVar = "--g3p-port-bg";
|
|
3473
|
+
}
|
|
3474
|
+
if (cssVar) {
|
|
3475
|
+
css += ` ${cssVar}: ${value};
|
|
3476
|
+
`;
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
css += "}\n";
|
|
3480
|
+
return css;
|
|
3481
|
+
}
|
|
2625
3482
|
|
|
2626
3483
|
// src/canvas/render-node.tsx
|
|
2627
|
-
var
|
|
2628
|
-
function renderNode(node) {
|
|
3484
|
+
var import_jsx_runtime7 = require("jsx-dom/jsx-runtime");
|
|
3485
|
+
function renderNode(node, props) {
|
|
2629
3486
|
if (typeof node == "string") node = { id: node };
|
|
2630
|
-
const title = node?.title ?? node?.label ?? node?.name ?? node?.text ?? node?.id ?? "?";
|
|
3487
|
+
const title = node?.title ?? props?.title ?? node?.label ?? node?.name ?? node?.text ?? props?.text ?? node?.id ?? "?";
|
|
2631
3488
|
const detail = node?.detail ?? node?.description ?? node?.subtitle;
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
3489
|
+
console.log(`renderNode: ${node.id} ${title} ${detail}`);
|
|
3490
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "g3p-node-default", children: [
|
|
3491
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "g3p-node-title", children: title }),
|
|
3492
|
+
detail && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "g3p-node-detail", children: detail })
|
|
2635
3493
|
] });
|
|
2636
3494
|
}
|
|
2637
3495
|
|
|
@@ -2663,7 +3521,6 @@ function defaults() {
|
|
|
2663
3521
|
},
|
|
2664
3522
|
canvas: {
|
|
2665
3523
|
renderNode,
|
|
2666
|
-
classPrefix: "g3p",
|
|
2667
3524
|
width: "100%",
|
|
2668
3525
|
height: "100%",
|
|
2669
3526
|
padding: 20,
|
|
@@ -2740,22 +3597,30 @@ var API = class {
|
|
|
2740
3597
|
nodeIds;
|
|
2741
3598
|
edgeIds;
|
|
2742
3599
|
nodeVersions;
|
|
3600
|
+
nodeOverrides;
|
|
3601
|
+
edgeOverrides;
|
|
3602
|
+
nodeFields;
|
|
2743
3603
|
nextNodeId;
|
|
2744
3604
|
nextEdgeId;
|
|
3605
|
+
events;
|
|
2745
3606
|
root;
|
|
2746
3607
|
constructor(args) {
|
|
2747
3608
|
this.root = args.root;
|
|
2748
3609
|
this.options = applyDefaults(args.options);
|
|
2749
3610
|
let graph2 = new Graph({ options: this.options.graph });
|
|
2750
3611
|
this.state = { graph: graph2, update: null };
|
|
3612
|
+
this.events = args.events || {};
|
|
2751
3613
|
this.seq = [this.state];
|
|
2752
3614
|
this.index = 0;
|
|
2753
3615
|
this.nodeIds = /* @__PURE__ */ new Map();
|
|
2754
3616
|
this.edgeIds = /* @__PURE__ */ new Map();
|
|
2755
3617
|
this.nodeVersions = /* @__PURE__ */ new Map();
|
|
3618
|
+
this.nodeOverrides = /* @__PURE__ */ new Map();
|
|
3619
|
+
this.edgeOverrides = /* @__PURE__ */ new Map();
|
|
3620
|
+
this.nodeFields = /* @__PURE__ */ new Map();
|
|
2756
3621
|
this.nextNodeId = 1;
|
|
2757
3622
|
this.nextEdgeId = 1;
|
|
2758
|
-
this.canvas = new Canvas({
|
|
3623
|
+
this.canvas = new Canvas(this, {
|
|
2759
3624
|
...this.options.canvas,
|
|
2760
3625
|
dummyNodeSize: this.options.graph.dummyNodeSize,
|
|
2761
3626
|
orientation: this.options.graph.orientation
|
|
@@ -2768,6 +3633,22 @@ var API = class {
|
|
|
2768
3633
|
this.history = [];
|
|
2769
3634
|
}
|
|
2770
3635
|
}
|
|
3636
|
+
/** Current history index (0-based) */
|
|
3637
|
+
getHistoryIndex() {
|
|
3638
|
+
return this.index;
|
|
3639
|
+
}
|
|
3640
|
+
/** Current history length */
|
|
3641
|
+
getHistoryLength() {
|
|
3642
|
+
return this.seq.length;
|
|
3643
|
+
}
|
|
3644
|
+
/** Toggle canvas editable mode without re-creating the graph */
|
|
3645
|
+
setEditable(editable) {
|
|
3646
|
+
this.canvas.editMode.editable = editable;
|
|
3647
|
+
}
|
|
3648
|
+
get graph() {
|
|
3649
|
+
return this.state.graph;
|
|
3650
|
+
}
|
|
3651
|
+
/** Initialize the API */
|
|
2771
3652
|
async init() {
|
|
2772
3653
|
const root = document.getElementById(this.root);
|
|
2773
3654
|
if (!root) throw new Error("root element not found");
|
|
@@ -2775,6 +3656,7 @@ var API = class {
|
|
|
2775
3656
|
for (const update of this.history)
|
|
2776
3657
|
await this.applyUpdate(update);
|
|
2777
3658
|
}
|
|
3659
|
+
/** Navigate to a different state */
|
|
2778
3660
|
nav(nav) {
|
|
2779
3661
|
let newIndex;
|
|
2780
3662
|
switch (nav) {
|
|
@@ -2796,6 +3678,8 @@ var API = class {
|
|
|
2796
3678
|
this.applyDiff(this.index, newIndex);
|
|
2797
3679
|
this.index = newIndex;
|
|
2798
3680
|
this.state = this.seq[this.index];
|
|
3681
|
+
if (this.events.historyChange)
|
|
3682
|
+
this.events.historyChange(this.index, this.seq.length);
|
|
2799
3683
|
}
|
|
2800
3684
|
applyDiff(oldIndex, newIndex) {
|
|
2801
3685
|
const oldGraph = this.seq[oldIndex].graph;
|
|
@@ -2805,36 +3689,72 @@ var API = class {
|
|
|
2805
3689
|
if (!newNode) this.canvas.deleteNode(oldNode);
|
|
2806
3690
|
}
|
|
2807
3691
|
for (const newNode of newGraph.nodes.values()) {
|
|
2808
|
-
|
|
3692
|
+
const oldNode = oldGraph.nodes.get(newNode.id);
|
|
3693
|
+
if (!oldNode) {
|
|
3694
|
+
this.canvas.addNode(newNode);
|
|
3695
|
+
} else if (oldNode.key !== newNode.key) {
|
|
3696
|
+
this.canvas.deleteNode(oldNode);
|
|
3697
|
+
this.canvas.addNode(newNode);
|
|
3698
|
+
} else if (oldNode.pos !== newNode.pos) {
|
|
3699
|
+
this.canvas.getNode(newNode.key).setPos(newNode.pos);
|
|
3700
|
+
}
|
|
2809
3701
|
}
|
|
2810
3702
|
for (const oldSeg of oldGraph.segs.values()) {
|
|
2811
3703
|
const newSeg = newGraph.segs.get(oldSeg.id);
|
|
2812
|
-
if (!newSeg)
|
|
3704
|
+
if (!newSeg) {
|
|
2813
3705
|
this.canvas.deleteSeg(oldSeg);
|
|
2814
|
-
else if (oldSeg
|
|
2815
|
-
this.canvas.updateSeg(newSeg);
|
|
3706
|
+
} else if (oldSeg !== newSeg) {
|
|
3707
|
+
this.canvas.updateSeg(newSeg, newGraph);
|
|
3708
|
+
}
|
|
2816
3709
|
}
|
|
2817
3710
|
for (const newSeg of newGraph.segs.values()) {
|
|
2818
|
-
if (!oldGraph.segs.has(newSeg.id))
|
|
3711
|
+
if (!oldGraph.segs.has(newSeg.id)) {
|
|
2819
3712
|
this.canvas.addSeg(newSeg, newGraph);
|
|
3713
|
+
}
|
|
2820
3714
|
}
|
|
2821
3715
|
this.canvas.update();
|
|
2822
3716
|
}
|
|
3717
|
+
/** Add a node */
|
|
2823
3718
|
async addNode(node) {
|
|
2824
3719
|
await this.update((update) => update.addNode(node));
|
|
2825
3720
|
}
|
|
3721
|
+
/** Delete a node */
|
|
2826
3722
|
async deleteNode(node) {
|
|
2827
3723
|
await this.update((update) => update.deleteNode(node));
|
|
2828
3724
|
}
|
|
3725
|
+
/** Update a node */
|
|
2829
3726
|
async updateNode(node) {
|
|
2830
3727
|
await this.update((update) => update.updateNode(node));
|
|
2831
3728
|
}
|
|
3729
|
+
/** Add an edge */
|
|
2832
3730
|
async addEdge(edge) {
|
|
2833
3731
|
await this.update((update) => update.addEdge(edge));
|
|
2834
3732
|
}
|
|
3733
|
+
/** Delete an edge */
|
|
2835
3734
|
async deleteEdge(edge) {
|
|
2836
3735
|
await this.update((update) => update.deleteEdge(edge));
|
|
2837
3736
|
}
|
|
3737
|
+
/** Update an edge */
|
|
3738
|
+
async updateEdge(edge) {
|
|
3739
|
+
await this.update((update) => update.updateEdge(edge));
|
|
3740
|
+
}
|
|
3741
|
+
/** Perform a batch of updates */
|
|
3742
|
+
async update(callback) {
|
|
3743
|
+
const updater = new Updater();
|
|
3744
|
+
callback(updater);
|
|
3745
|
+
await this.applyUpdate(updater.update);
|
|
3746
|
+
}
|
|
3747
|
+
/** Rebuild the graph from scratch (removes all then re-adds all nodes/edges) */
|
|
3748
|
+
async rebuild() {
|
|
3749
|
+
const nodes = [...this.nodeIds.keys()];
|
|
3750
|
+
const edges = [...this.edgeIds.keys()];
|
|
3751
|
+
await this.update((updater) => {
|
|
3752
|
+
for (const edge of edges) updater.deleteEdge(edge);
|
|
3753
|
+
for (const node of nodes) updater.deleteNode(node);
|
|
3754
|
+
for (const node of nodes) updater.addNode(node);
|
|
3755
|
+
for (const edge of edges) updater.addEdge(edge);
|
|
3756
|
+
});
|
|
3757
|
+
}
|
|
2838
3758
|
async applyUpdate(update) {
|
|
2839
3759
|
log11.info("applyUpdate", update);
|
|
2840
3760
|
const nodes = await this.measureNodes(update);
|
|
@@ -2851,12 +3771,16 @@ var API = class {
|
|
|
2851
3771
|
this._addEdge(edge, mut);
|
|
2852
3772
|
for (const edge of update.updateEdges ?? [])
|
|
2853
3773
|
this._updateEdge(edge, mut);
|
|
3774
|
+
this.nodeOverrides.clear();
|
|
3775
|
+
this.edgeOverrides.clear();
|
|
2854
3776
|
});
|
|
2855
3777
|
this.state = { graph: graph2, update };
|
|
2856
3778
|
this.setNodePositions();
|
|
2857
3779
|
this.seq.splice(this.index + 1);
|
|
2858
3780
|
this.seq.push(this.state);
|
|
2859
3781
|
this.nav("last");
|
|
3782
|
+
if (this.events.historyChange)
|
|
3783
|
+
this.events.historyChange(this.index, this.seq.length);
|
|
2860
3784
|
}
|
|
2861
3785
|
setNodePositions() {
|
|
2862
3786
|
const { graph: graph2 } = this.state;
|
|
@@ -2866,11 +3790,6 @@ var API = class {
|
|
|
2866
3790
|
this.canvas.getNode(node.key).setPos(node.pos);
|
|
2867
3791
|
}
|
|
2868
3792
|
}
|
|
2869
|
-
async update(callback) {
|
|
2870
|
-
const updater = new Updater();
|
|
2871
|
-
callback(updater);
|
|
2872
|
-
await this.applyUpdate(updater.update);
|
|
2873
|
-
}
|
|
2874
3793
|
async measureNodes(update) {
|
|
2875
3794
|
const data = [];
|
|
2876
3795
|
for (const set of [update.updateNodes, update.addNodes])
|
|
@@ -2885,7 +3804,10 @@ var API = class {
|
|
|
2885
3804
|
else if (!data) throw new Error(`invalid node ${data}`);
|
|
2886
3805
|
else if (typeof data == "string") props = { id: data };
|
|
2887
3806
|
else if (typeof data == "object") props = data;
|
|
2888
|
-
else throw new Error(`invalid node ${data}`);
|
|
3807
|
+
else throw new Error(`invalid node ${JSON.stringify(data)}`);
|
|
3808
|
+
this.detectNodeFields(data);
|
|
3809
|
+
const overrides = this.nodeOverrides.get(data);
|
|
3810
|
+
if (overrides) props = { ...props, ...overrides };
|
|
2889
3811
|
let { id, title, text, type, render } = props;
|
|
2890
3812
|
id ??= this.getNodeId(data);
|
|
2891
3813
|
const ports = this.parsePorts(props.ports);
|
|
@@ -2895,6 +3817,21 @@ var API = class {
|
|
|
2895
3817
|
this.nodeVersions.set(data, version);
|
|
2896
3818
|
return { id, data, ports, title, text, type, render, version };
|
|
2897
3819
|
}
|
|
3820
|
+
detectNodeFields(data) {
|
|
3821
|
+
if (typeof data != "object" || !data) return;
|
|
3822
|
+
const skip = /* @__PURE__ */ new Set(["id", "ports", "render", "version"]);
|
|
3823
|
+
for (const [key, value] of Object.entries(data)) {
|
|
3824
|
+
if (skip.has(key)) continue;
|
|
3825
|
+
if (value === null || value === void 0) continue;
|
|
3826
|
+
const type = typeof value;
|
|
3827
|
+
if (type === "string" || type === "number" || type === "boolean") {
|
|
3828
|
+
this.nodeFields.set(key, type);
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
getNodeFields() {
|
|
3833
|
+
return this.nodeFields;
|
|
3834
|
+
}
|
|
2898
3835
|
parseEdge(data) {
|
|
2899
3836
|
const get = this.options.props.edge;
|
|
2900
3837
|
let props;
|
|
@@ -2903,8 +3840,10 @@ var API = class {
|
|
|
2903
3840
|
else if (typeof data == "string") props = this.parseStringEdge(data);
|
|
2904
3841
|
else if (typeof data == "object") props = data;
|
|
2905
3842
|
else throw new Error(`invalid edge ${data}`);
|
|
3843
|
+
const overrides = this.edgeOverrides.get(data);
|
|
3844
|
+
if (overrides) props = { ...props, ...overrides };
|
|
2906
3845
|
let { id, source, target, type } = props;
|
|
2907
|
-
id
|
|
3846
|
+
if (!id) id = this.getEdgeId(data);
|
|
2908
3847
|
source = this.parseEdgeEnd(source);
|
|
2909
3848
|
target = this.parseEdgeEnd(target);
|
|
2910
3849
|
const edge = { id, source, target, type, data };
|
|
@@ -2917,14 +3856,14 @@ var API = class {
|
|
|
2917
3856
|
const keys = Object.keys(end);
|
|
2918
3857
|
const pidx = keys.indexOf("port");
|
|
2919
3858
|
if (pidx != -1) {
|
|
2920
|
-
if (typeof end.port != "string") return end;
|
|
3859
|
+
if (end.port !== void 0 && typeof end.port != "string") return end;
|
|
2921
3860
|
keys.splice(pidx, 1);
|
|
2922
3861
|
}
|
|
2923
3862
|
if (keys.length != 1) return end;
|
|
2924
3863
|
if (keys[0] == "id") return end;
|
|
2925
3864
|
if (keys[0] != "node") return end;
|
|
2926
3865
|
const id = this.nodeIds.get(end.node);
|
|
2927
|
-
if (!id) throw new Error(`edge end
|
|
3866
|
+
if (!id) throw new Error(`edge end references unknown node ${end.node}`);
|
|
2928
3867
|
return { id, port: end.port };
|
|
2929
3868
|
}
|
|
2930
3869
|
throw new Error(`invalid edge end ${end}`);
|
|
@@ -2934,13 +3873,19 @@ var API = class {
|
|
|
2934
3873
|
return { source, target };
|
|
2935
3874
|
}
|
|
2936
3875
|
parsePorts(ports) {
|
|
2937
|
-
const fixed = {
|
|
3876
|
+
const fixed = {};
|
|
2938
3877
|
for (const key of ["in", "out"]) {
|
|
2939
3878
|
if (ports?.[key] && ports[key].length > 0)
|
|
2940
3879
|
fixed[key] = ports[key].map((port) => typeof port == "string" ? { id: port } : port);
|
|
2941
3880
|
}
|
|
2942
3881
|
return fixed;
|
|
2943
3882
|
}
|
|
3883
|
+
getNode(id) {
|
|
3884
|
+
return this.graph.getNode(id);
|
|
3885
|
+
}
|
|
3886
|
+
getEdge(id) {
|
|
3887
|
+
return this.graph.getEdge(id);
|
|
3888
|
+
}
|
|
2944
3889
|
getNodeId(node) {
|
|
2945
3890
|
let id = this.nodeIds.get(node);
|
|
2946
3891
|
if (!id) {
|
|
@@ -2960,7 +3905,6 @@ var API = class {
|
|
|
2960
3905
|
_addNode(node, mut) {
|
|
2961
3906
|
const { data, id: newId } = node.data;
|
|
2962
3907
|
const oldId = this.nodeIds.get(data);
|
|
2963
|
-
console.log("addNode", node, oldId, newId);
|
|
2964
3908
|
if (oldId && oldId != newId)
|
|
2965
3909
|
throw new Error(`node id of ${data} changed from ${oldId} to ${newId}`);
|
|
2966
3910
|
this.nodeIds.set(data, newId);
|
|
@@ -2968,36 +3912,169 @@ var API = class {
|
|
|
2968
3912
|
}
|
|
2969
3913
|
_removeNode(node, mut) {
|
|
2970
3914
|
const id = this.nodeIds.get(node);
|
|
2971
|
-
if (!id) throw new Error(`removing node ${node} which does not exist`);
|
|
3915
|
+
if (!id) throw new Error(`removing node ${JSON.stringify(node)} which does not exist`);
|
|
2972
3916
|
mut.removeNode({ id });
|
|
2973
3917
|
}
|
|
2974
3918
|
_updateNode(node, mut) {
|
|
2975
3919
|
const { data, id: newId } = node.data;
|
|
2976
3920
|
const oldId = this.nodeIds.get(data);
|
|
2977
|
-
if (!oldId) throw new Error(`updating unknown node ${node}`);
|
|
2978
|
-
if (oldId != newId) throw new Error(`node id changed from ${oldId} to ${newId}`);
|
|
3921
|
+
if (!oldId) throw new Error(`updating unknown node ${JSON.stringify(node)} `);
|
|
3922
|
+
if (oldId != newId) throw new Error(`node id changed from ${oldId} to ${newId} `);
|
|
2979
3923
|
mut.updateNode(node.data);
|
|
2980
3924
|
}
|
|
2981
3925
|
_addEdge(edge, mut) {
|
|
2982
3926
|
const data = this.parseEdge(edge);
|
|
2983
3927
|
const id = this.edgeIds.get(edge);
|
|
2984
3928
|
if (id && id != data.id)
|
|
2985
|
-
throw new Error(`edge id changed from ${id} to ${data.id}`);
|
|
3929
|
+
throw new Error(`edge id changed from ${id} to ${data.id} `);
|
|
2986
3930
|
this.edgeIds.set(edge, data.id);
|
|
2987
3931
|
mut.addEdge(data);
|
|
2988
3932
|
}
|
|
2989
3933
|
_removeEdge(edge, mut) {
|
|
2990
3934
|
const id = this.edgeIds.get(edge);
|
|
2991
|
-
if (!id) throw new Error(`removing edge ${edge} which does not exist`);
|
|
3935
|
+
if (!id) throw new Error(`removing edge ${JSON.stringify(edge)} which does not exist`);
|
|
2992
3936
|
mut.removeEdge(this.parseEdge(edge));
|
|
2993
3937
|
}
|
|
2994
3938
|
_updateEdge(edge, mut) {
|
|
2995
3939
|
const id = this.edgeIds.get(edge);
|
|
2996
|
-
if (!id) throw new Error(`updating unknown edge ${edge}`);
|
|
3940
|
+
if (!id) throw new Error(`updating unknown edge ${JSON.stringify(edge)} `);
|
|
2997
3941
|
const data = this.parseEdge(edge);
|
|
2998
|
-
if (data.id !== id) throw new Error(`edge id changed from ${id} to ${data.id}`);
|
|
3942
|
+
if (data.id !== id) throw new Error(`edge id changed from ${id} to ${data.id} `);
|
|
2999
3943
|
mut.updateEdge(data);
|
|
3000
3944
|
}
|
|
3945
|
+
// Event Handlers
|
|
3946
|
+
handleClickNode(id) {
|
|
3947
|
+
const handler = this.events.nodeClick;
|
|
3948
|
+
const node = this.graph.getNode(id);
|
|
3949
|
+
if (handler) handler(node.data);
|
|
3950
|
+
}
|
|
3951
|
+
handleClickEdge(id) {
|
|
3952
|
+
const handler = this.events.edgeClick;
|
|
3953
|
+
if (!handler) return;
|
|
3954
|
+
const seg = this.graph.getSeg(id);
|
|
3955
|
+
if (seg.edgeIds.size != 1) return;
|
|
3956
|
+
const edge = this.graph.getEdge(seg.edgeIds.values().next().value);
|
|
3957
|
+
handler(edge.data);
|
|
3958
|
+
}
|
|
3959
|
+
async handleNewNode() {
|
|
3960
|
+
const gotNode = async (node) => {
|
|
3961
|
+
await this.addNode(node);
|
|
3962
|
+
};
|
|
3963
|
+
if (this.events.newNode)
|
|
3964
|
+
this.events.newNode(gotNode);
|
|
3965
|
+
else
|
|
3966
|
+
this.canvas.showNewNodeModal(async (data) => {
|
|
3967
|
+
if (this.events.addNode)
|
|
3968
|
+
this.events.addNode(data, gotNode);
|
|
3969
|
+
else
|
|
3970
|
+
await gotNode(data);
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
async handleNewNodeFrom(source) {
|
|
3974
|
+
const gotNode = async (node) => {
|
|
3975
|
+
const gotEdge = async (edge) => {
|
|
3976
|
+
await this.update((u) => {
|
|
3977
|
+
u.addNode(node).addEdge(edge);
|
|
3978
|
+
});
|
|
3979
|
+
};
|
|
3980
|
+
const data = this.graph.getNode(source.id).data;
|
|
3981
|
+
const newEdge = {
|
|
3982
|
+
source: { node: data, port: source.port },
|
|
3983
|
+
target: { node }
|
|
3984
|
+
};
|
|
3985
|
+
if (this.events.addEdge)
|
|
3986
|
+
this.events.addEdge(newEdge, gotEdge);
|
|
3987
|
+
else
|
|
3988
|
+
await gotEdge(newEdge);
|
|
3989
|
+
};
|
|
3990
|
+
if (this.events.newNode)
|
|
3991
|
+
this.events.newNode(gotNode);
|
|
3992
|
+
else
|
|
3993
|
+
this.canvas.showNewNodeModal(async (data) => {
|
|
3994
|
+
if (this.events.addNode)
|
|
3995
|
+
this.events.addNode(data, gotNode);
|
|
3996
|
+
else
|
|
3997
|
+
await gotNode(data);
|
|
3998
|
+
});
|
|
3999
|
+
}
|
|
4000
|
+
async handleEditNode(id) {
|
|
4001
|
+
const node = this.graph.getNode(id);
|
|
4002
|
+
const gotNode = async (node2) => {
|
|
4003
|
+
if (node2) await this.updateNode(node2);
|
|
4004
|
+
};
|
|
4005
|
+
if (this.events.editNode)
|
|
4006
|
+
this.events.editNode(node.data, gotNode);
|
|
4007
|
+
else {
|
|
4008
|
+
this.canvas.showEditNodeModal(node, async (data) => {
|
|
4009
|
+
if (this.events.updateNode)
|
|
4010
|
+
this.events.updateNode(node.data, data, gotNode);
|
|
4011
|
+
else {
|
|
4012
|
+
this.nodeOverrides.set(node.data, data);
|
|
4013
|
+
await gotNode(node.data);
|
|
4014
|
+
}
|
|
4015
|
+
});
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
async handleEditEdge(id) {
|
|
4019
|
+
const seg = this.graph.getSeg(id);
|
|
4020
|
+
if (seg.edgeIds.size != 1) return;
|
|
4021
|
+
const edge = this.graph.getEdge(seg.edgeIds.values().next().value);
|
|
4022
|
+
const gotEdge = async (edge2) => {
|
|
4023
|
+
if (edge2) await this.updateEdge(edge2);
|
|
4024
|
+
};
|
|
4025
|
+
if (this.events.editEdge)
|
|
4026
|
+
this.events.editEdge(edge.data, gotEdge);
|
|
4027
|
+
else
|
|
4028
|
+
this.canvas.showEditEdgeModal(edge, async (data) => {
|
|
4029
|
+
const sourceNode = edge.sourceNode(this.graph);
|
|
4030
|
+
const targetNode = edge.targetNode(this.graph);
|
|
4031
|
+
const update = {
|
|
4032
|
+
source: { node: sourceNode.data, port: data.source.port, marker: data.source.marker },
|
|
4033
|
+
target: { node: targetNode.data, port: data.target.port, marker: data.target.marker }
|
|
4034
|
+
};
|
|
4035
|
+
if (this.events.updateEdge)
|
|
4036
|
+
this.events.updateEdge(edge.data, update, gotEdge);
|
|
4037
|
+
else {
|
|
4038
|
+
this.edgeOverrides.set(edge.data, {
|
|
4039
|
+
source: { id: sourceNode.id, port: data.source.port, marker: data.source.marker },
|
|
4040
|
+
target: { id: targetNode.id, port: data.target.port, marker: data.target.marker },
|
|
4041
|
+
type: data.type
|
|
4042
|
+
});
|
|
4043
|
+
await gotEdge(edge.data);
|
|
4044
|
+
}
|
|
4045
|
+
});
|
|
4046
|
+
}
|
|
4047
|
+
async handleAddEdge(data) {
|
|
4048
|
+
const gotEdge = async (edge) => {
|
|
4049
|
+
if (edge) await this.addEdge(edge);
|
|
4050
|
+
};
|
|
4051
|
+
const newEdge = {
|
|
4052
|
+
source: { node: this.graph.getNode(data.source.id).data, port: data.source.port, marker: data.source.marker },
|
|
4053
|
+
target: { node: this.graph.getNode(data.target.id).data, port: data.target.port, marker: data.target.marker }
|
|
4054
|
+
};
|
|
4055
|
+
if (this.events.addEdge)
|
|
4056
|
+
this.events.addEdge(newEdge, gotEdge);
|
|
4057
|
+
else
|
|
4058
|
+
await gotEdge(data);
|
|
4059
|
+
}
|
|
4060
|
+
async handleDeleteNode(id) {
|
|
4061
|
+
const node = this.getNode(id);
|
|
4062
|
+
if (this.events.removeNode)
|
|
4063
|
+
this.events.removeNode(node.data, async (remove) => {
|
|
4064
|
+
if (remove) await this.deleteNode(node.data);
|
|
4065
|
+
});
|
|
4066
|
+
else
|
|
4067
|
+
await this.deleteNode(node.data);
|
|
4068
|
+
}
|
|
4069
|
+
async handleDeleteEdge(id) {
|
|
4070
|
+
const edge = this.getEdge(id);
|
|
4071
|
+
if (this.events.removeEdge)
|
|
4072
|
+
this.events.removeEdge(edge.data, async (remove) => {
|
|
4073
|
+
if (remove) await this.deleteEdge(edge.data);
|
|
4074
|
+
});
|
|
4075
|
+
else
|
|
4076
|
+
await this.deleteEdge(edge.data);
|
|
4077
|
+
}
|
|
3001
4078
|
};
|
|
3002
4079
|
|
|
3003
4080
|
// src/index.ts
|