@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.js
CHANGED
|
@@ -3,6 +3,70 @@ import { Map as IMap, List as IList, Set as ISet6 } from "immutable";
|
|
|
3
3
|
|
|
4
4
|
// src/graph/node.ts
|
|
5
5
|
import { Record, Set as ISet } from "immutable";
|
|
6
|
+
|
|
7
|
+
// src/log.ts
|
|
8
|
+
import pino from "pino";
|
|
9
|
+
var resolvedLevel = typeof globalThis !== "undefined" && globalThis.__LOG_LEVEL || typeof process !== "undefined" && process.env?.LOG_LEVEL || "debug";
|
|
10
|
+
var browserOpts = { asObject: true };
|
|
11
|
+
browserOpts.transmit = {
|
|
12
|
+
level: resolvedLevel,
|
|
13
|
+
send: (level, log12) => {
|
|
14
|
+
try {
|
|
15
|
+
const endpoint = typeof globalThis !== "undefined" && globalThis.__LOG_INGEST_URL || typeof process !== "undefined" && process.env?.LOG_INGEST_URL || void 0;
|
|
16
|
+
if (!endpoint || typeof window === "undefined") {
|
|
17
|
+
try {
|
|
18
|
+
console.debug("[graph-core] transmit skipped", { endpoint, hasWindow: typeof window !== "undefined", level });
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const line = JSON.stringify({ level, ...log12, ts: Date.now() }) + "\n";
|
|
24
|
+
try {
|
|
25
|
+
console.debug("[graph-core] transmit sending", { endpoint, level, bytes: line.length });
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
fetch(endpoint, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
mode: "no-cors",
|
|
31
|
+
credentials: "omit",
|
|
32
|
+
body: line,
|
|
33
|
+
keepalive: true
|
|
34
|
+
}).then(() => {
|
|
35
|
+
try {
|
|
36
|
+
console.debug("[graph-core] transmit fetch ok");
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}).catch((err) => {
|
|
40
|
+
try {
|
|
41
|
+
console.debug("[graph-core] transmit fetch error", err?.message || err);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
} catch (e) {
|
|
46
|
+
try {
|
|
47
|
+
console.debug("[graph-core] transmit error", e?.message || e);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var base = pino({
|
|
54
|
+
level: resolvedLevel,
|
|
55
|
+
browser: browserOpts
|
|
56
|
+
});
|
|
57
|
+
function logger(module) {
|
|
58
|
+
const child = base.child({ module });
|
|
59
|
+
return {
|
|
60
|
+
error: (msg, ...args) => child.error({ args }, msg),
|
|
61
|
+
warn: (msg, ...args) => child.warn({ args }, msg),
|
|
62
|
+
info: (msg, ...args) => child.info({ args }, msg),
|
|
63
|
+
debug: (msg, ...args) => child.debug({ args }, msg)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
var log = logger("core");
|
|
67
|
+
|
|
68
|
+
// src/graph/node.ts
|
|
69
|
+
var log2 = logger("node");
|
|
6
70
|
var defNodeData = {
|
|
7
71
|
id: "",
|
|
8
72
|
data: void 0,
|
|
@@ -11,7 +75,7 @@ var defNodeData = {
|
|
|
11
75
|
text: void 0,
|
|
12
76
|
type: void 0,
|
|
13
77
|
render: void 0,
|
|
14
|
-
ports: {
|
|
78
|
+
ports: {},
|
|
15
79
|
aligned: {},
|
|
16
80
|
edges: { in: ISet(), out: ISet() },
|
|
17
81
|
segs: { in: ISet(), out: ISet() },
|
|
@@ -45,10 +109,7 @@ var Node = class _Node extends Record(defNodeData) {
|
|
|
45
109
|
return this.isDummy ? this.id : _Node.key(this);
|
|
46
110
|
}
|
|
47
111
|
static key(node) {
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
static isDummyId(nodeId) {
|
|
51
|
-
return nodeId.startsWith(_Node.dummyPrefix);
|
|
112
|
+
return `k:${node.id}:${node.version}`;
|
|
52
113
|
}
|
|
53
114
|
static addNormal(g, data) {
|
|
54
115
|
const layer = g.layerAt(0);
|
|
@@ -58,7 +119,9 @@ var Node = class _Node extends Record(defNodeData) {
|
|
|
58
119
|
segs: { in: ISet(), out: ISet() },
|
|
59
120
|
aligned: {},
|
|
60
121
|
edgeIds: [],
|
|
61
|
-
layerId: layer.id
|
|
122
|
+
layerId: layer.id,
|
|
123
|
+
lpos: void 0,
|
|
124
|
+
pos: void 0
|
|
62
125
|
});
|
|
63
126
|
layer.addNode(g, node.id);
|
|
64
127
|
g.nodes.set(node.id, node);
|
|
@@ -121,6 +184,18 @@ var Node = class _Node extends Record(defNodeData) {
|
|
|
121
184
|
getLayer(g) {
|
|
122
185
|
return g.getLayer(this.layerId);
|
|
123
186
|
}
|
|
187
|
+
margin(g) {
|
|
188
|
+
return this.isDummy ? g.options.edgeSpacing - g.options.dummyNodeSize : g.options.nodeMargin;
|
|
189
|
+
}
|
|
190
|
+
marginWith(g, other) {
|
|
191
|
+
return Math.max(this.margin(g), other.margin(g));
|
|
192
|
+
}
|
|
193
|
+
width(g) {
|
|
194
|
+
return this.dims?.[g.w] ?? 0;
|
|
195
|
+
}
|
|
196
|
+
right(g) {
|
|
197
|
+
return this.lpos + this.width(g);
|
|
198
|
+
}
|
|
124
199
|
setIndex(g, index) {
|
|
125
200
|
if (this.index == index) return this;
|
|
126
201
|
return this.mut(g).set("index", index);
|
|
@@ -167,9 +242,22 @@ var Node = class _Node extends Record(defNodeData) {
|
|
|
167
242
|
return node;
|
|
168
243
|
}
|
|
169
244
|
delSelf(g) {
|
|
245
|
+
if (this.aligned.in)
|
|
246
|
+
g.getNode(this.aligned.in).setAligned(g, "out", void 0);
|
|
247
|
+
if (this.aligned.out)
|
|
248
|
+
g.getNode(this.aligned.out).setAligned(g, "in", void 0);
|
|
170
249
|
this.getLayer(g).delNode(g, this.id);
|
|
171
|
-
for (const
|
|
172
|
-
|
|
250
|
+
for (const edge of this.rels(g, "edges", "both"))
|
|
251
|
+
edge.delSelf(g);
|
|
252
|
+
const remainingSegIds = [...this.segs.in, ...this.segs.out];
|
|
253
|
+
if (remainingSegIds.length > 0) {
|
|
254
|
+
for (const segId of remainingSegIds) {
|
|
255
|
+
if (g.segs?.has?.(segId)) {
|
|
256
|
+
g.getSeg(segId).delSelf(g);
|
|
257
|
+
} else {
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
173
261
|
g.nodes.delete(this.id);
|
|
174
262
|
g.dirtyNodes.delete(this.id);
|
|
175
263
|
g.delNodes.add(this.id);
|
|
@@ -262,30 +350,7 @@ var Node = class _Node extends Record(defNodeData) {
|
|
|
262
350
|
|
|
263
351
|
// src/graph/edge.ts
|
|
264
352
|
import { Record as Record2 } from "immutable";
|
|
265
|
-
|
|
266
|
-
// src/log.ts
|
|
267
|
-
var levels = {
|
|
268
|
-
error: 0,
|
|
269
|
-
warn: 1,
|
|
270
|
-
info: 2,
|
|
271
|
-
debug: 3
|
|
272
|
-
};
|
|
273
|
-
var currentLevel = "debug";
|
|
274
|
-
function shouldLog(level) {
|
|
275
|
-
return levels[level] <= levels[currentLevel];
|
|
276
|
-
}
|
|
277
|
-
function logger(module) {
|
|
278
|
-
return {
|
|
279
|
-
error: (msg, ...args) => shouldLog("error") && console.error(`[${module}] ${msg}`, ...args),
|
|
280
|
-
warn: (msg, ...args) => shouldLog("warn") && console.warn(`[${module}] ${msg}`, ...args),
|
|
281
|
-
info: (msg, ...args) => shouldLog("info") && console.info(`[${module}] ${msg}`, ...args),
|
|
282
|
-
debug: (msg, ...args) => shouldLog("debug") && console.debug(`[${module}] ${msg}`, ...args)
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
var log = logger("core");
|
|
286
|
-
|
|
287
|
-
// src/graph/edge.ts
|
|
288
|
-
var log2 = logger("edge");
|
|
353
|
+
var log3 = logger("edge");
|
|
289
354
|
var defEdgeData = {
|
|
290
355
|
id: "",
|
|
291
356
|
data: null,
|
|
@@ -293,7 +358,6 @@ var defEdgeData = {
|
|
|
293
358
|
source: { id: "" },
|
|
294
359
|
target: { id: "" },
|
|
295
360
|
type: void 0,
|
|
296
|
-
style: void 0,
|
|
297
361
|
mutable: false,
|
|
298
362
|
segIds: []
|
|
299
363
|
};
|
|
@@ -374,7 +438,7 @@ var Edge = class _Edge extends Record2(defEdgeData) {
|
|
|
374
438
|
source = edge.source.id;
|
|
375
439
|
if (edge.source?.port)
|
|
376
440
|
source = `${source}.${edge.source.port}`;
|
|
377
|
-
const marker = edge.source?.marker
|
|
441
|
+
const marker = edge.source?.marker;
|
|
378
442
|
if (marker && marker != "none") source += `[${marker}]`;
|
|
379
443
|
source += "-";
|
|
380
444
|
}
|
|
@@ -384,7 +448,7 @@ var Edge = class _Edge extends Record2(defEdgeData) {
|
|
|
384
448
|
if (edge.target.port)
|
|
385
449
|
target = `${target}.${edge.target.port}`;
|
|
386
450
|
target = "-" + target;
|
|
387
|
-
const marker = edge.target?.marker ??
|
|
451
|
+
const marker = edge.target?.marker ?? "arrow";
|
|
388
452
|
if (marker && marker != "none") target += `[${marker}]`;
|
|
389
453
|
}
|
|
390
454
|
const type = edge.type || "";
|
|
@@ -426,7 +490,6 @@ var defSegData = {
|
|
|
426
490
|
source: { id: "" },
|
|
427
491
|
target: { id: "" },
|
|
428
492
|
type: void 0,
|
|
429
|
-
style: void 0,
|
|
430
493
|
edgeIds: ISet2(),
|
|
431
494
|
trackPos: void 0,
|
|
432
495
|
svg: void 0,
|
|
@@ -457,7 +520,7 @@ var Seg = class _Seg extends Record3(defSegData) {
|
|
|
457
520
|
sameEnd(other, side) {
|
|
458
521
|
const mine = this[side];
|
|
459
522
|
const yours = other[side];
|
|
460
|
-
return mine.id === yours.id && mine.port === yours.port && mine.marker === yours.marker;
|
|
523
|
+
return mine.id === yours.id && mine.port === yours.port && mine.marker === yours.marker && this.type === other.type;
|
|
461
524
|
}
|
|
462
525
|
setPos(g, source, target) {
|
|
463
526
|
return this.mut(g).merge({
|
|
@@ -529,7 +592,7 @@ var Seg = class _Seg extends Record3(defSegData) {
|
|
|
529
592
|
|
|
530
593
|
// src/graph/layer.ts
|
|
531
594
|
import { Record as Record4, Set as ISet3 } from "immutable";
|
|
532
|
-
var
|
|
595
|
+
var log4 = logger("layer");
|
|
533
596
|
var defLayerData = {
|
|
534
597
|
id: "",
|
|
535
598
|
index: 0,
|
|
@@ -554,7 +617,7 @@ var Layer = class extends Record4(defLayerData) {
|
|
|
554
617
|
mutable: false
|
|
555
618
|
}).asImmutable();
|
|
556
619
|
}
|
|
557
|
-
get
|
|
620
|
+
get nodeCount() {
|
|
558
621
|
return this.nodeIds.size;
|
|
559
622
|
}
|
|
560
623
|
*nodes(g) {
|
|
@@ -609,9 +672,8 @@ var Layer = class extends Record4(defLayerData) {
|
|
|
609
672
|
reindex(g, nodeId) {
|
|
610
673
|
if (!this.isSorted) return void 0;
|
|
611
674
|
const sorted = this.sorted.filter((id) => id != nodeId);
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
g.getNode(sorted[i]).setIndex(g, i);
|
|
675
|
+
for (const [i, id] of this.sorted.entries())
|
|
676
|
+
g.getNode(id).setIndex(g, i);
|
|
615
677
|
return sorted;
|
|
616
678
|
}
|
|
617
679
|
delNode(g, nodeId) {
|
|
@@ -783,12 +845,12 @@ var Cycles = class _Cycles {
|
|
|
783
845
|
|
|
784
846
|
// src/graph/services/dummy.ts
|
|
785
847
|
import { Set as ISet4 } from "immutable";
|
|
786
|
-
var
|
|
848
|
+
var log5 = logger("dummy");
|
|
787
849
|
var Dummy = class _Dummy {
|
|
788
850
|
static updateDummies(g) {
|
|
789
851
|
for (const edgeId of g.dirtyEdges) {
|
|
790
852
|
const edge = g.getEdge(edgeId);
|
|
791
|
-
const { type
|
|
853
|
+
const { type } = edge;
|
|
792
854
|
const sourceLayer = edge.sourceNode(g).layerIndex(g);
|
|
793
855
|
const targetLayer = edge.targetNode(g).layerIndex(g);
|
|
794
856
|
let segIndex = 0;
|
|
@@ -812,7 +874,7 @@ var Dummy = class _Dummy {
|
|
|
812
874
|
});
|
|
813
875
|
target = { id: dummy.id };
|
|
814
876
|
}
|
|
815
|
-
seg = Seg.add(g, { source, target, type,
|
|
877
|
+
seg = Seg.add(g, { source, target, type, edgeIds: ISet4([edgeId]) });
|
|
816
878
|
segs.splice(segIndex, 0, seg.id);
|
|
817
879
|
changed = true;
|
|
818
880
|
} 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)) {
|
|
@@ -851,9 +913,8 @@ var Dummy = class _Dummy {
|
|
|
851
913
|
let layer = g.getLayer(layerId);
|
|
852
914
|
const groups = /* @__PURE__ */ new Map();
|
|
853
915
|
for (const nodeId of layer.nodeIds) {
|
|
854
|
-
if (!Node.isDummyId(nodeId)) continue;
|
|
855
916
|
const node = g.getNode(nodeId);
|
|
856
|
-
if (node.isMerged) continue;
|
|
917
|
+
if (!node.isDummy || node.isMerged) continue;
|
|
857
918
|
const edge = g.getEdge(node.edgeIds[0]);
|
|
858
919
|
const key = Edge.key(edge, "k:", side);
|
|
859
920
|
if (!groups.has(key)) groups.set(key, /* @__PURE__ */ new Set());
|
|
@@ -861,7 +922,7 @@ var Dummy = class _Dummy {
|
|
|
861
922
|
}
|
|
862
923
|
for (const [key, group] of groups) {
|
|
863
924
|
if (group.size == 1) continue;
|
|
864
|
-
const edgeIds = [...group].map((node) => node.
|
|
925
|
+
const edgeIds = [...group].map((node) => node.edgeIds[0]);
|
|
865
926
|
const dummy = Node.addDummy(g, { edgeIds, layerId, isMerged: true });
|
|
866
927
|
let seg;
|
|
867
928
|
for (const old of group) {
|
|
@@ -871,8 +932,9 @@ var Dummy = class _Dummy {
|
|
|
871
932
|
const example = g.getSeg(segId);
|
|
872
933
|
seg = Seg.add(g, {
|
|
873
934
|
...example,
|
|
874
|
-
edgeIds: ISet4(
|
|
875
|
-
[
|
|
935
|
+
edgeIds: ISet4(edgeIds),
|
|
936
|
+
[side]: { ...example[side] },
|
|
937
|
+
[altSide]: { ...example[altSide], id: dummy.id, port: void 0 }
|
|
876
938
|
});
|
|
877
939
|
}
|
|
878
940
|
edge = edge.replaceSegId(g, segId, seg.id);
|
|
@@ -884,12 +946,15 @@ var Dummy = class _Dummy {
|
|
|
884
946
|
const example = g.getSeg(segId);
|
|
885
947
|
const seg2 = Seg.add(g, {
|
|
886
948
|
...example,
|
|
887
|
-
edgeIds: ISet4([old.
|
|
888
|
-
[
|
|
949
|
+
edgeIds: ISet4([old.edgeIds[0]]),
|
|
950
|
+
[altSide]: { ...example[altSide] },
|
|
951
|
+
[side]: { ...example[side], id: dummy.id, port: void 0 }
|
|
889
952
|
});
|
|
890
953
|
edge = edge.replaceSegId(g, segId, seg2.id);
|
|
891
954
|
}
|
|
892
955
|
}
|
|
956
|
+
for (const old of group)
|
|
957
|
+
old.delSelf(g);
|
|
893
958
|
}
|
|
894
959
|
}
|
|
895
960
|
}
|
|
@@ -897,7 +962,7 @@ var Dummy = class _Dummy {
|
|
|
897
962
|
|
|
898
963
|
// src/graph/services/layers.ts
|
|
899
964
|
import { Seq } from "immutable";
|
|
900
|
-
var
|
|
965
|
+
var log6 = logger("layers");
|
|
901
966
|
var Layers = class {
|
|
902
967
|
static updateLayers(g) {
|
|
903
968
|
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);
|
|
@@ -960,7 +1025,7 @@ var Layers = class {
|
|
|
960
1025
|
|
|
961
1026
|
// src/graph/services/layout.ts
|
|
962
1027
|
import { Seq as Seq2 } from "immutable";
|
|
963
|
-
var
|
|
1028
|
+
var log7 = logger("layout");
|
|
964
1029
|
var Layout = class _Layout {
|
|
965
1030
|
static parentIndex(g, node) {
|
|
966
1031
|
const parents = Seq2([...node.adjs(g, "segs", "in")]);
|
|
@@ -977,8 +1042,8 @@ var Layout = class _Layout {
|
|
|
977
1042
|
if (a.isDummy && !b.isDummy) return -1;
|
|
978
1043
|
if (!a.isDummy && b.isDummy) return 1;
|
|
979
1044
|
if (!a.isDummy) return a.id.localeCompare(b.id);
|
|
980
|
-
const minA =
|
|
981
|
-
const minB =
|
|
1045
|
+
const minA = Seq2(a.edgeIds).min();
|
|
1046
|
+
const minB = Seq2(b.edgeIds).min();
|
|
982
1047
|
return minA.localeCompare(minB);
|
|
983
1048
|
}
|
|
984
1049
|
static positionNodes(g) {
|
|
@@ -1004,7 +1069,12 @@ var Layout = class _Layout {
|
|
|
1004
1069
|
let node = g.getNode(sorted[i]);
|
|
1005
1070
|
node = node.setIndex(g, i).setLayerPos(g, lpos);
|
|
1006
1071
|
const size = node.dims?.[g.w] ?? 0;
|
|
1007
|
-
|
|
1072
|
+
let margin = node.margin(g);
|
|
1073
|
+
if (i + 1 < sorted.length) {
|
|
1074
|
+
const next = g.getNode(sorted[i + 1]);
|
|
1075
|
+
margin = node.marginWith(g, next);
|
|
1076
|
+
}
|
|
1077
|
+
lpos += size + margin;
|
|
1008
1078
|
}
|
|
1009
1079
|
}
|
|
1010
1080
|
}
|
|
@@ -1036,7 +1106,7 @@ var Layout = class _Layout {
|
|
|
1036
1106
|
let iterations = 0;
|
|
1037
1107
|
while (true) {
|
|
1038
1108
|
if (++iterations > 10) {
|
|
1039
|
-
|
|
1109
|
+
log7.error(`alignNodes: infinite loop detected in layer ${layerId}`);
|
|
1040
1110
|
break;
|
|
1041
1111
|
}
|
|
1042
1112
|
let changed = false;
|
|
@@ -1096,7 +1166,7 @@ var Layout = class _Layout {
|
|
|
1096
1166
|
static anchorPos(g, seg, side) {
|
|
1097
1167
|
const nodeId = seg[side].id;
|
|
1098
1168
|
const node = g.getNode(nodeId);
|
|
1099
|
-
let p = {
|
|
1169
|
+
let p = { [g.x]: node.lpos, [g.y]: node.pos?.[g.y] ?? 0 };
|
|
1100
1170
|
let w = node.dims?.[g.w] ?? 0;
|
|
1101
1171
|
let h = node.dims?.[g.h] ?? 0;
|
|
1102
1172
|
if (node.isDummy)
|
|
@@ -1142,22 +1212,18 @@ var Layout = class _Layout {
|
|
|
1142
1212
|
}
|
|
1143
1213
|
static shiftNode(g, nodeId, alignId, dir, lpos, reverseMove, conservative) {
|
|
1144
1214
|
const node = g.getNode(nodeId);
|
|
1145
|
-
log6.debug(`shift ${nodeId} (at ${node.lpos}) to ${alignId} (at ${lpos})`);
|
|
1146
1215
|
if (!conservative)
|
|
1147
1216
|
_Layout.markAligned(g, nodeId, alignId, dir, lpos);
|
|
1148
|
-
const
|
|
1149
|
-
const nodeWidth = node.dims?.[g.w] ?? 0;
|
|
1150
|
-
const aMin = lpos - space, aMax = lpos + nodeWidth + space;
|
|
1217
|
+
const nodeRight = lpos + node.width(g);
|
|
1151
1218
|
repeat:
|
|
1152
1219
|
for (const otherId of node.getLayer(g).nodeIds) {
|
|
1153
1220
|
if (otherId == nodeId) continue;
|
|
1154
1221
|
const other = g.getNode(otherId);
|
|
1155
|
-
const
|
|
1156
|
-
const
|
|
1157
|
-
|
|
1158
|
-
if (aMin < bMax && bMin < aMax) {
|
|
1222
|
+
const margin = node.marginWith(g, other);
|
|
1223
|
+
const gap = lpos < other.lpos ? other.lpos - nodeRight : lpos - other.right(g);
|
|
1224
|
+
if (gap < margin) {
|
|
1159
1225
|
if (conservative) return false;
|
|
1160
|
-
const safePos = reverseMove ?
|
|
1226
|
+
const safePos = reverseMove ? lpos - other.width(g) - margin : nodeRight + margin;
|
|
1161
1227
|
_Layout.shiftNode(g, otherId, void 0, dir, safePos, reverseMove, conservative);
|
|
1162
1228
|
continue repeat;
|
|
1163
1229
|
}
|
|
@@ -1206,11 +1272,12 @@ var Layout = class _Layout {
|
|
|
1206
1272
|
for (const layerId of g.layerList) {
|
|
1207
1273
|
const layer = g.getLayer(layerId);
|
|
1208
1274
|
if (layer.sorted.length < 2) continue;
|
|
1209
|
-
for (const nodeId of layer.sorted) {
|
|
1275
|
+
for (const [i, nodeId] of layer.sorted.entries()) {
|
|
1210
1276
|
const node = g.getNode(nodeId);
|
|
1211
1277
|
if (node.index == 0) continue;
|
|
1212
1278
|
let minGap = Infinity;
|
|
1213
1279
|
const stack = [];
|
|
1280
|
+
let maxMargin = 0;
|
|
1214
1281
|
for (const right of _Layout.aligned(g, nodeId, "both")) {
|
|
1215
1282
|
stack.push(right);
|
|
1216
1283
|
const leftId = _Layout.leftOf(g, right);
|
|
@@ -1219,8 +1286,10 @@ var Layout = class _Layout {
|
|
|
1219
1286
|
const leftWidth = left.dims?.[g.w] ?? 0;
|
|
1220
1287
|
const gap = right.lpos - left.lpos - leftWidth;
|
|
1221
1288
|
if (gap < minGap) minGap = gap;
|
|
1289
|
+
const margin = right.marginWith(g, left);
|
|
1290
|
+
if (margin > maxMargin) maxMargin = margin;
|
|
1222
1291
|
}
|
|
1223
|
-
const delta = minGap -
|
|
1292
|
+
const delta = minGap - maxMargin;
|
|
1224
1293
|
if (delta <= 0) continue;
|
|
1225
1294
|
anyChanged = true;
|
|
1226
1295
|
for (const right of stack)
|
|
@@ -1268,9 +1337,8 @@ var Layout = class _Layout {
|
|
|
1268
1337
|
};
|
|
1269
1338
|
|
|
1270
1339
|
// src/canvas/marker.tsx
|
|
1271
|
-
import { default as default2 } from "./marker.css?raw";
|
|
1272
1340
|
import { jsx } from "jsx-dom/jsx-runtime";
|
|
1273
|
-
function arrow(size,
|
|
1341
|
+
function arrow(size, reverse = false) {
|
|
1274
1342
|
const h = size / 1.5;
|
|
1275
1343
|
const w = size;
|
|
1276
1344
|
const ry = h / 2;
|
|
@@ -1279,7 +1347,7 @@ function arrow(size, classPrefix, reverse = false) {
|
|
|
1279
1347
|
"marker",
|
|
1280
1348
|
{
|
|
1281
1349
|
id: `g3p-marker-arrow${suffix}`,
|
|
1282
|
-
className:
|
|
1350
|
+
className: "g3p-marker g3p-marker-arrow",
|
|
1283
1351
|
markerWidth: size,
|
|
1284
1352
|
markerHeight: size,
|
|
1285
1353
|
refX: "2",
|
|
@@ -1290,7 +1358,7 @@ function arrow(size, classPrefix, reverse = false) {
|
|
|
1290
1358
|
}
|
|
1291
1359
|
);
|
|
1292
1360
|
}
|
|
1293
|
-
function circle(size,
|
|
1361
|
+
function circle(size, reverse = false) {
|
|
1294
1362
|
const r = size / 3;
|
|
1295
1363
|
const cy = size / 2;
|
|
1296
1364
|
const suffix = reverse ? "-reverse" : "";
|
|
@@ -1298,7 +1366,7 @@ function circle(size, classPrefix, reverse = false) {
|
|
|
1298
1366
|
"marker",
|
|
1299
1367
|
{
|
|
1300
1368
|
id: `g3p-marker-circle${suffix}`,
|
|
1301
|
-
className:
|
|
1369
|
+
className: "g3p-marker g3p-marker-circle",
|
|
1302
1370
|
markerWidth: size,
|
|
1303
1371
|
markerHeight: size,
|
|
1304
1372
|
refX: "2",
|
|
@@ -1309,7 +1377,7 @@ function circle(size, classPrefix, reverse = false) {
|
|
|
1309
1377
|
}
|
|
1310
1378
|
);
|
|
1311
1379
|
}
|
|
1312
|
-
function diamond(size,
|
|
1380
|
+
function diamond(size, reverse = false) {
|
|
1313
1381
|
const w = size * 0.7;
|
|
1314
1382
|
const h = size / 2;
|
|
1315
1383
|
const cy = size / 2;
|
|
@@ -1318,7 +1386,7 @@ function diamond(size, classPrefix, reverse = false) {
|
|
|
1318
1386
|
"marker",
|
|
1319
1387
|
{
|
|
1320
1388
|
id: `g3p-marker-diamond${suffix}`,
|
|
1321
|
-
className:
|
|
1389
|
+
className: "g3p-marker g3p-marker-diamond",
|
|
1322
1390
|
markerWidth: size,
|
|
1323
1391
|
markerHeight: size,
|
|
1324
1392
|
refX: "2",
|
|
@@ -1329,7 +1397,7 @@ function diamond(size, classPrefix, reverse = false) {
|
|
|
1329
1397
|
}
|
|
1330
1398
|
);
|
|
1331
1399
|
}
|
|
1332
|
-
function bar(size,
|
|
1400
|
+
function bar(size, reverse = false) {
|
|
1333
1401
|
const h = size * 0.6;
|
|
1334
1402
|
const cy = size / 2;
|
|
1335
1403
|
const suffix = reverse ? "-reverse" : "";
|
|
@@ -1337,7 +1405,7 @@ function bar(size, classPrefix, reverse = false) {
|
|
|
1337
1405
|
"marker",
|
|
1338
1406
|
{
|
|
1339
1407
|
id: `g3p-marker-bar${suffix}`,
|
|
1340
|
-
className:
|
|
1408
|
+
className: "g3p-marker g3p-marker-bar",
|
|
1341
1409
|
markerWidth: size,
|
|
1342
1410
|
markerHeight: size,
|
|
1343
1411
|
refX: "2",
|
|
@@ -1348,7 +1416,7 @@ function bar(size, classPrefix, reverse = false) {
|
|
|
1348
1416
|
}
|
|
1349
1417
|
);
|
|
1350
1418
|
}
|
|
1351
|
-
function none(size,
|
|
1419
|
+
function none(size, reverse = false) {
|
|
1352
1420
|
return void 0;
|
|
1353
1421
|
}
|
|
1354
1422
|
function normalize(data) {
|
|
@@ -1367,7 +1435,7 @@ var markerDefs = {
|
|
|
1367
1435
|
};
|
|
1368
1436
|
|
|
1369
1437
|
// src/graph/services/lines.ts
|
|
1370
|
-
var
|
|
1438
|
+
var log8 = logger("lines");
|
|
1371
1439
|
var Lines = class _Lines {
|
|
1372
1440
|
static layoutSeg(g, seg) {
|
|
1373
1441
|
const sourcePos = Layout.anchorPos(g, seg, "source");
|
|
@@ -1413,7 +1481,11 @@ var Lines = class _Lines {
|
|
|
1413
1481
|
validTrack = track;
|
|
1414
1482
|
break;
|
|
1415
1483
|
}
|
|
1416
|
-
|
|
1484
|
+
const minA = Math.min(seg.p1, seg.p2);
|
|
1485
|
+
const maxA = Math.max(seg.p1, seg.p2);
|
|
1486
|
+
const minB = Math.min(other.p1, other.p2);
|
|
1487
|
+
const maxB = Math.max(other.p1, other.p2);
|
|
1488
|
+
if (minA < maxB && minB < maxA) {
|
|
1417
1489
|
overlap = true;
|
|
1418
1490
|
break;
|
|
1419
1491
|
}
|
|
@@ -1428,6 +1500,21 @@ var Lines = class _Lines {
|
|
|
1428
1500
|
else
|
|
1429
1501
|
trackSet.push([seg]);
|
|
1430
1502
|
}
|
|
1503
|
+
const midpoint = (s) => (s.p1 + s.p2) / 2;
|
|
1504
|
+
const sortTracks = (tracks2, goingRight) => {
|
|
1505
|
+
tracks2.sort((a, b) => {
|
|
1506
|
+
const midA = Math.max(...a.map(midpoint));
|
|
1507
|
+
const midB = Math.max(...b.map(midpoint));
|
|
1508
|
+
return goingRight ? midB - midA : midA - midB;
|
|
1509
|
+
});
|
|
1510
|
+
};
|
|
1511
|
+
sortTracks(rightTracks, true);
|
|
1512
|
+
sortTracks(leftTracks, false);
|
|
1513
|
+
allTracks.sort((a, b) => {
|
|
1514
|
+
const avgA = a.reduce((sum, s) => sum + midpoint(s), 0) / a.length;
|
|
1515
|
+
const avgB = b.reduce((sum, s) => sum + midpoint(s), 0) / b.length;
|
|
1516
|
+
return avgB - avgA;
|
|
1517
|
+
});
|
|
1431
1518
|
const tracks = [];
|
|
1432
1519
|
const all = leftTracks.concat(rightTracks).concat(allTracks);
|
|
1433
1520
|
for (const track of all)
|
|
@@ -1598,6 +1685,7 @@ var Lines = class _Lines {
|
|
|
1598
1685
|
};
|
|
1599
1686
|
|
|
1600
1687
|
// src/graph/graph.ts
|
|
1688
|
+
var log9 = logger("graph");
|
|
1601
1689
|
var emptyChanges = {
|
|
1602
1690
|
addedNodes: [],
|
|
1603
1691
|
removedNodes: [],
|
|
@@ -1872,29 +1960,8 @@ var screenPos = (x, y) => ({ x, y });
|
|
|
1872
1960
|
var canvasPos = (x, y) => ({ x, y });
|
|
1873
1961
|
var graphPos = (x, y) => ({ x, y });
|
|
1874
1962
|
|
|
1875
|
-
// src/canvas/node.tsx
|
|
1876
|
-
import styles from "./node.css?raw";
|
|
1877
|
-
|
|
1878
|
-
// src/canvas/styler.ts
|
|
1879
|
-
var injected = {};
|
|
1880
|
-
function styler(name, styles4, prefix) {
|
|
1881
|
-
if (prefix === "g3p" && !injected[name]) {
|
|
1882
|
-
const style = document.createElement("style");
|
|
1883
|
-
style.textContent = styles4;
|
|
1884
|
-
document.head.appendChild(style);
|
|
1885
|
-
injected[name] = true;
|
|
1886
|
-
}
|
|
1887
|
-
return (str, condition) => {
|
|
1888
|
-
if (!(condition ?? true)) return "";
|
|
1889
|
-
const parts = str.split(/\s+/);
|
|
1890
|
-
const fixed = parts.map((p) => `${prefix}-${name}-${p}`);
|
|
1891
|
-
return fixed.join(" ");
|
|
1892
|
-
};
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
1963
|
// src/canvas/node.tsx
|
|
1896
1964
|
import { jsx as jsx2, jsxs } from "jsx-dom/jsx-runtime";
|
|
1897
|
-
var log8 = logger("canvas");
|
|
1898
1965
|
var Node2 = class {
|
|
1899
1966
|
selected;
|
|
1900
1967
|
hovered;
|
|
@@ -1902,7 +1969,6 @@ var Node2 = class {
|
|
|
1902
1969
|
content;
|
|
1903
1970
|
canvas;
|
|
1904
1971
|
data;
|
|
1905
|
-
classPrefix;
|
|
1906
1972
|
isDummy;
|
|
1907
1973
|
pos;
|
|
1908
1974
|
constructor(canvas, data, isDummy = false) {
|
|
@@ -1910,20 +1976,18 @@ var Node2 = class {
|
|
|
1910
1976
|
this.data = data;
|
|
1911
1977
|
this.selected = false;
|
|
1912
1978
|
this.hovered = false;
|
|
1913
|
-
this.classPrefix = canvas.classPrefix;
|
|
1914
1979
|
this.isDummy = isDummy;
|
|
1915
1980
|
if (this.isDummy) {
|
|
1916
1981
|
const size = canvas.dummyNodeSize;
|
|
1917
1982
|
} else {
|
|
1918
1983
|
const render = data.render ?? canvas.renderNode;
|
|
1919
|
-
this.content = this.renderContent(render(data.data));
|
|
1984
|
+
this.content = this.renderContent(render(data.data, data));
|
|
1920
1985
|
}
|
|
1921
1986
|
}
|
|
1922
1987
|
remove() {
|
|
1923
1988
|
this.container.remove();
|
|
1924
1989
|
}
|
|
1925
1990
|
append() {
|
|
1926
|
-
console.log("append", this);
|
|
1927
1991
|
this.canvas.group.appendChild(this.container);
|
|
1928
1992
|
}
|
|
1929
1993
|
needsContentSize() {
|
|
@@ -1932,19 +1996,6 @@ var Node2 = class {
|
|
|
1932
1996
|
needsContainerSize() {
|
|
1933
1997
|
return !this.isDummy;
|
|
1934
1998
|
}
|
|
1935
|
-
handleClick(e) {
|
|
1936
|
-
e.stopPropagation();
|
|
1937
|
-
}
|
|
1938
|
-
handleMouseEnter(e) {
|
|
1939
|
-
}
|
|
1940
|
-
handleMouseLeave(e) {
|
|
1941
|
-
}
|
|
1942
|
-
handleContextMenu(e) {
|
|
1943
|
-
}
|
|
1944
|
-
handleMouseDown(e) {
|
|
1945
|
-
}
|
|
1946
|
-
handleMouseUp(e) {
|
|
1947
|
-
}
|
|
1948
1999
|
setPos(pos) {
|
|
1949
2000
|
this.pos = pos;
|
|
1950
2001
|
const { x, y } = pos;
|
|
@@ -1961,7 +2012,6 @@ var Node2 = class {
|
|
|
1961
2012
|
return el;
|
|
1962
2013
|
}
|
|
1963
2014
|
renderContainer() {
|
|
1964
|
-
const c = styler("node", styles, this.classPrefix);
|
|
1965
2015
|
const hasPorts = this.hasPorts();
|
|
1966
2016
|
const inner = this.isDummy ? this.renderDummy() : this.renderForeign();
|
|
1967
2017
|
const nodeType = this.data?.type;
|
|
@@ -1969,14 +2019,8 @@ var Node2 = class {
|
|
|
1969
2019
|
this.container = /* @__PURE__ */ jsx2(
|
|
1970
2020
|
"g",
|
|
1971
2021
|
{
|
|
1972
|
-
className:
|
|
1973
|
-
|
|
1974
|
-
onMouseEnter: (e) => this.handleMouseEnter(e),
|
|
1975
|
-
onMouseLeave: (e) => this.handleMouseLeave(e),
|
|
1976
|
-
onContextMenu: (e) => this.handleContextMenu(e),
|
|
1977
|
-
onMouseDown: (e) => this.handleMouseDown(e),
|
|
1978
|
-
onMouseUp: (e) => this.handleMouseUp(e),
|
|
1979
|
-
style: { cursor: "pointer" },
|
|
2022
|
+
className: `g3p-node-container ${this.isDummy ? "g3p-node-dummy" : ""} ${typeClass}`.trim(),
|
|
2023
|
+
"data-node-id": this.data?.id,
|
|
1980
2024
|
children: inner
|
|
1981
2025
|
}
|
|
1982
2026
|
);
|
|
@@ -1986,7 +2030,6 @@ var Node2 = class {
|
|
|
1986
2030
|
return /* @__PURE__ */ jsx2("foreignObject", { width: w, height: h, children: this.content });
|
|
1987
2031
|
}
|
|
1988
2032
|
renderDummy() {
|
|
1989
|
-
const c = styler("node", styles, this.classPrefix);
|
|
1990
2033
|
let w = this.canvas.dummyNodeSize;
|
|
1991
2034
|
let h = this.canvas.dummyNodeSize;
|
|
1992
2035
|
w /= 2;
|
|
@@ -1999,7 +2042,7 @@ var Node2 = class {
|
|
|
1999
2042
|
cy: h,
|
|
2000
2043
|
rx: w,
|
|
2001
2044
|
ry: h,
|
|
2002
|
-
className:
|
|
2045
|
+
className: "g3p-node-background"
|
|
2003
2046
|
}
|
|
2004
2047
|
),
|
|
2005
2048
|
/* @__PURE__ */ jsx2(
|
|
@@ -2010,7 +2053,7 @@ var Node2 = class {
|
|
|
2010
2053
|
rx: w,
|
|
2011
2054
|
ry: h,
|
|
2012
2055
|
fill: "none",
|
|
2013
|
-
className:
|
|
2056
|
+
className: "g3p-node-border"
|
|
2014
2057
|
}
|
|
2015
2058
|
)
|
|
2016
2059
|
] });
|
|
@@ -2023,7 +2066,7 @@ var Node2 = class {
|
|
|
2023
2066
|
const ports = data.ports?.[dir];
|
|
2024
2067
|
if (!ports) continue;
|
|
2025
2068
|
for (const port of ports) {
|
|
2026
|
-
const el = this.content.querySelector(
|
|
2069
|
+
const el = this.content.querySelector(`.g3p-node-port[data-node-id="${data.id}"][data-port-id="${port.id}"]`);
|
|
2027
2070
|
if (!el) continue;
|
|
2028
2071
|
const portRect = el.getBoundingClientRect();
|
|
2029
2072
|
if (isVertical) {
|
|
@@ -2061,23 +2104,22 @@ var Node2 = class {
|
|
|
2061
2104
|
renderPortRow(dir, inout) {
|
|
2062
2105
|
const ports = this.data?.ports?.[dir];
|
|
2063
2106
|
if (!ports?.length) return null;
|
|
2064
|
-
const c = styler("node", styles, this.classPrefix);
|
|
2065
2107
|
const pos = this.getPortPosition(dir);
|
|
2066
2108
|
const isVertical = this.isVerticalOrientation();
|
|
2067
2109
|
const layoutClass = isVertical ? "row" : "col";
|
|
2068
2110
|
const rotateLabels = false;
|
|
2069
2111
|
const rotateClass = rotateLabels ? `port-rotated-${pos}` : "";
|
|
2070
|
-
return /* @__PURE__ */ jsx2("div", { className:
|
|
2112
|
+
return /* @__PURE__ */ jsx2("div", { className: `g3p-node-ports g3p-node-ports-${layoutClass}`, children: ports.map((port) => /* @__PURE__ */ jsx2(
|
|
2071
2113
|
"div",
|
|
2072
2114
|
{
|
|
2073
|
-
|
|
2074
|
-
|
|
2115
|
+
className: `g3p-node-port g3p-node-port-${inout}-${pos} ${rotateClass}`,
|
|
2116
|
+
"data-node-id": this.data.id,
|
|
2117
|
+
"data-port-id": port.id,
|
|
2075
2118
|
children: port.label ?? port.id
|
|
2076
2119
|
}
|
|
2077
2120
|
)) });
|
|
2078
2121
|
}
|
|
2079
2122
|
renderInsidePorts(el) {
|
|
2080
|
-
const c = styler("node", styles, this.classPrefix);
|
|
2081
2123
|
const isVertical = this.isVerticalOrientation();
|
|
2082
2124
|
const isReversed = this.isReversedOrientation();
|
|
2083
2125
|
let inPorts = this.renderPortRow("in", "in");
|
|
@@ -2085,14 +2127,13 @@ var Node2 = class {
|
|
|
2085
2127
|
if (!inPorts && !outPorts) return el;
|
|
2086
2128
|
if (isReversed) [inPorts, outPorts] = [outPorts, inPorts];
|
|
2087
2129
|
const wrapperClass = isVertical ? "v" : "h";
|
|
2088
|
-
return /* @__PURE__ */ jsxs("div", { className:
|
|
2130
|
+
return /* @__PURE__ */ jsxs("div", { className: `g3p-node-with-ports g3p-node-with-ports-${wrapperClass}`, children: [
|
|
2089
2131
|
inPorts,
|
|
2090
2132
|
el,
|
|
2091
2133
|
outPorts
|
|
2092
2134
|
] });
|
|
2093
2135
|
}
|
|
2094
2136
|
renderOutsidePorts(el) {
|
|
2095
|
-
const c = styler("node", styles, this.classPrefix);
|
|
2096
2137
|
const isVertical = this.isVerticalOrientation();
|
|
2097
2138
|
const isReversed = this.isReversedOrientation();
|
|
2098
2139
|
let inPorts = this.renderPortRow("in", "out");
|
|
@@ -2100,71 +2141,59 @@ var Node2 = class {
|
|
|
2100
2141
|
if (!inPorts && !outPorts) return el;
|
|
2101
2142
|
if (isReversed) [inPorts, outPorts] = [outPorts, inPorts];
|
|
2102
2143
|
const wrapperClass = isVertical ? "v" : "h";
|
|
2103
|
-
return /* @__PURE__ */ jsxs("div", { className:
|
|
2144
|
+
return /* @__PURE__ */ jsxs("div", { className: `g3p-node-with-ports g3p-node-with-ports-${wrapperClass}`, children: [
|
|
2104
2145
|
inPorts,
|
|
2105
2146
|
el,
|
|
2106
2147
|
outPorts
|
|
2107
2148
|
] });
|
|
2108
2149
|
}
|
|
2109
2150
|
renderBorder(el) {
|
|
2110
|
-
|
|
2111
|
-
return /* @__PURE__ */ jsx2("div", { className: c("border"), children: el });
|
|
2151
|
+
return /* @__PURE__ */ jsx2("div", { className: "g3p-node-border", children: el });
|
|
2112
2152
|
}
|
|
2113
2153
|
};
|
|
2114
2154
|
|
|
2115
2155
|
// src/canvas/seg.tsx
|
|
2116
|
-
import styles2 from "./seg.css?raw";
|
|
2117
2156
|
import { jsx as jsx3, jsxs as jsxs2 } from "jsx-dom/jsx-runtime";
|
|
2118
|
-
var log9 = logger("canvas");
|
|
2119
2157
|
var Seg2 = class {
|
|
2120
2158
|
id;
|
|
2121
2159
|
selected;
|
|
2122
2160
|
hovered;
|
|
2123
2161
|
canvas;
|
|
2124
|
-
classPrefix;
|
|
2125
2162
|
type;
|
|
2126
2163
|
svg;
|
|
2127
2164
|
el;
|
|
2128
2165
|
source;
|
|
2129
2166
|
target;
|
|
2167
|
+
edgeIds;
|
|
2130
2168
|
constructor(canvas, data, g) {
|
|
2131
2169
|
this.id = data.id;
|
|
2132
2170
|
this.canvas = canvas;
|
|
2133
2171
|
this.selected = false;
|
|
2134
2172
|
this.hovered = false;
|
|
2135
2173
|
this.svg = data.svg;
|
|
2136
|
-
this.classPrefix = canvas.classPrefix;
|
|
2137
2174
|
this.source = { ...data.source, isDummy: data.sourceNode(g).isDummy };
|
|
2138
2175
|
this.target = { ...data.target, isDummy: data.targetNode(g).isDummy };
|
|
2139
2176
|
this.type = data.type;
|
|
2177
|
+
this.edgeIds = data.edgeIds.toArray();
|
|
2140
2178
|
this.el = this.render();
|
|
2141
2179
|
}
|
|
2142
|
-
handleClick(e) {
|
|
2143
|
-
e.stopPropagation();
|
|
2144
|
-
}
|
|
2145
|
-
handleMouseEnter(e) {
|
|
2146
|
-
}
|
|
2147
|
-
handleMouseLeave(e) {
|
|
2148
|
-
}
|
|
2149
|
-
handleContextMenu(e) {
|
|
2150
|
-
}
|
|
2151
2180
|
append() {
|
|
2152
2181
|
this.canvas.group.appendChild(this.el);
|
|
2153
2182
|
}
|
|
2154
2183
|
remove() {
|
|
2155
2184
|
this.el.remove();
|
|
2156
2185
|
}
|
|
2157
|
-
update(data) {
|
|
2186
|
+
update(data, g) {
|
|
2158
2187
|
this.svg = data.svg;
|
|
2159
2188
|
this.type = data.type;
|
|
2160
|
-
this.source = data.source;
|
|
2161
|
-
this.target = data.target;
|
|
2189
|
+
this.source = { ...data.source, isDummy: data.sourceNode(g).isDummy };
|
|
2190
|
+
this.target = { ...data.target, isDummy: data.targetNode(g).isDummy };
|
|
2191
|
+
this.edgeIds = data.edgeIds.toArray();
|
|
2162
2192
|
this.remove();
|
|
2163
2193
|
this.el = this.render();
|
|
2164
2194
|
this.append();
|
|
2165
2195
|
}
|
|
2166
2196
|
render() {
|
|
2167
|
-
const c = styler("seg", styles2, this.classPrefix);
|
|
2168
2197
|
let { source, target } = normalize(this);
|
|
2169
2198
|
if (this.source.isDummy) source = void 0;
|
|
2170
2199
|
if (this.target.isDummy) target = void 0;
|
|
@@ -2174,18 +2203,15 @@ var Seg2 = class {
|
|
|
2174
2203
|
{
|
|
2175
2204
|
ref: (el) => this.el = el,
|
|
2176
2205
|
id: `g3p-seg-${this.id}`,
|
|
2177
|
-
className:
|
|
2178
|
-
|
|
2179
|
-
onMouseEnter: this.handleMouseEnter.bind(this),
|
|
2180
|
-
onMouseLeave: this.handleMouseLeave.bind(this),
|
|
2181
|
-
onContextMenu: this.handleContextMenu.bind(this),
|
|
2206
|
+
className: `g3p-seg-container ${typeClass}`.trim(),
|
|
2207
|
+
"data-edge-id": this.id,
|
|
2182
2208
|
children: [
|
|
2183
2209
|
/* @__PURE__ */ jsx3(
|
|
2184
2210
|
"path",
|
|
2185
2211
|
{
|
|
2186
2212
|
d: this.svg,
|
|
2187
2213
|
fill: "none",
|
|
2188
|
-
className:
|
|
2214
|
+
className: "g3p-seg-line",
|
|
2189
2215
|
markerStart: source ? `url(#g3p-marker-${source}-reverse)` : void 0,
|
|
2190
2216
|
markerEnd: target ? `url(#g3p-marker-${target})` : void 0
|
|
2191
2217
|
}
|
|
@@ -2196,7 +2222,7 @@ var Seg2 = class {
|
|
|
2196
2222
|
d: this.svg,
|
|
2197
2223
|
stroke: "transparent",
|
|
2198
2224
|
fill: "none",
|
|
2199
|
-
className:
|
|
2225
|
+
className: "g3p-seg-hitbox",
|
|
2200
2226
|
style: { cursor: "pointer" }
|
|
2201
2227
|
}
|
|
2202
2228
|
)
|
|
@@ -2206,46 +2232,561 @@ var Seg2 = class {
|
|
|
2206
2232
|
}
|
|
2207
2233
|
};
|
|
2208
2234
|
|
|
2209
|
-
// src/canvas/
|
|
2210
|
-
|
|
2211
|
-
|
|
2235
|
+
// src/canvas/editMode.ts
|
|
2236
|
+
var EditMode = class {
|
|
2237
|
+
_state = { type: "idle" };
|
|
2238
|
+
_editable = false;
|
|
2239
|
+
get state() {
|
|
2240
|
+
return this._state;
|
|
2241
|
+
}
|
|
2242
|
+
get editable() {
|
|
2243
|
+
return this._editable;
|
|
2244
|
+
}
|
|
2245
|
+
set editable(value) {
|
|
2246
|
+
this._editable = value;
|
|
2247
|
+
if (!value) {
|
|
2248
|
+
this.reset();
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
get isIdle() {
|
|
2252
|
+
return this._state.type === "idle";
|
|
2253
|
+
}
|
|
2254
|
+
get isPanning() {
|
|
2255
|
+
return this._state.type === "panning";
|
|
2256
|
+
}
|
|
2257
|
+
get isCreatingEdge() {
|
|
2258
|
+
return this._state.type === "new-edge";
|
|
2259
|
+
}
|
|
2260
|
+
/** Start panning the canvas */
|
|
2261
|
+
startPan(startCanvas, startTransform) {
|
|
2262
|
+
this._state = { type: "panning", startCanvas, startTransform };
|
|
2263
|
+
}
|
|
2264
|
+
/** Start creating a new edge from a node or port */
|
|
2265
|
+
startNewEdge(id, startGraph, port) {
|
|
2266
|
+
if (!this._editable) return;
|
|
2267
|
+
this._state = {
|
|
2268
|
+
type: "new-edge",
|
|
2269
|
+
source: { id, port },
|
|
2270
|
+
startGraph,
|
|
2271
|
+
currentGraph: startGraph,
|
|
2272
|
+
target: null
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
/** Update the current position during new-edge mode */
|
|
2276
|
+
updateNewEdgePosition(currentGraph) {
|
|
2277
|
+
if (this._state.type === "new-edge") {
|
|
2278
|
+
this._state = { ...this._state, currentGraph };
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
/** Update the hover target during new-edge mode */
|
|
2282
|
+
setHoverTarget(target) {
|
|
2283
|
+
if (this._state.type === "new-edge") {
|
|
2284
|
+
this._state = { ...this._state, target };
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
/** Get the new-edge state if active */
|
|
2288
|
+
getNewEdgeState() {
|
|
2289
|
+
if (this._state.type === "new-edge") {
|
|
2290
|
+
return this._state;
|
|
2291
|
+
}
|
|
2292
|
+
return null;
|
|
2293
|
+
}
|
|
2294
|
+
/** Reset to idle state */
|
|
2295
|
+
reset() {
|
|
2296
|
+
this._state = { type: "idle" };
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
|
|
2300
|
+
// src/canvas/newEdge.tsx
|
|
2212
2301
|
import { jsx as jsx4, jsxs as jsxs3 } from "jsx-dom/jsx-runtime";
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2302
|
+
function renderNewEdge({ start, end }) {
|
|
2303
|
+
return /* @__PURE__ */ jsxs3("g", { className: "g3p-new-edge-container", children: [
|
|
2304
|
+
/* @__PURE__ */ jsx4(
|
|
2305
|
+
"circle",
|
|
2306
|
+
{
|
|
2307
|
+
cx: start.x,
|
|
2308
|
+
cy: start.y,
|
|
2309
|
+
r: 4,
|
|
2310
|
+
className: "g3p-new-edge-origin"
|
|
2311
|
+
}
|
|
2312
|
+
),
|
|
2313
|
+
/* @__PURE__ */ jsx4(
|
|
2314
|
+
"line",
|
|
2315
|
+
{
|
|
2316
|
+
x1: start.x,
|
|
2317
|
+
y1: start.y,
|
|
2318
|
+
x2: end.x,
|
|
2319
|
+
y2: end.y,
|
|
2320
|
+
className: "g3p-new-edge-line"
|
|
2321
|
+
}
|
|
2322
|
+
),
|
|
2323
|
+
/* @__PURE__ */ jsx4(
|
|
2324
|
+
"circle",
|
|
2325
|
+
{
|
|
2326
|
+
cx: end.x,
|
|
2327
|
+
cy: end.y,
|
|
2328
|
+
r: 3,
|
|
2329
|
+
className: "g3p-new-edge-end"
|
|
2330
|
+
}
|
|
2331
|
+
)
|
|
2332
|
+
] });
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// src/canvas/modal.tsx
|
|
2336
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "jsx-dom/jsx-runtime";
|
|
2337
|
+
var Modal = class {
|
|
2338
|
+
container;
|
|
2339
|
+
overlay;
|
|
2340
|
+
dialog;
|
|
2341
|
+
onClose;
|
|
2342
|
+
mouseDownOnOverlay = false;
|
|
2343
|
+
constructor(options) {
|
|
2344
|
+
this.onClose = options.onClose;
|
|
2345
|
+
this.overlay = /* @__PURE__ */ jsx5(
|
|
2346
|
+
"div",
|
|
2347
|
+
{
|
|
2348
|
+
className: "g3p-modal-overlay",
|
|
2349
|
+
onMouseDown: (e) => {
|
|
2350
|
+
this.mouseDownOnOverlay = e.target === this.overlay;
|
|
2351
|
+
},
|
|
2352
|
+
onMouseUp: (e) => {
|
|
2353
|
+
if (this.mouseDownOnOverlay && e.target === this.overlay) this.close();
|
|
2354
|
+
this.mouseDownOnOverlay = false;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
);
|
|
2358
|
+
this.dialog = /* @__PURE__ */ jsxs4("div", { className: "g3p-modal-dialog", children: [
|
|
2359
|
+
/* @__PURE__ */ jsxs4("div", { className: "g3p-modal-header", children: [
|
|
2360
|
+
/* @__PURE__ */ jsx5("span", { className: "g3p-modal-title", children: options.title }),
|
|
2361
|
+
/* @__PURE__ */ jsx5(
|
|
2362
|
+
"button",
|
|
2363
|
+
{
|
|
2364
|
+
className: "g3p-modal-close",
|
|
2365
|
+
onClick: () => this.close(),
|
|
2366
|
+
children: "\xD7"
|
|
2367
|
+
}
|
|
2368
|
+
)
|
|
2369
|
+
] }),
|
|
2370
|
+
/* @__PURE__ */ jsx5("div", { className: "g3p-modal-body" }),
|
|
2371
|
+
/* @__PURE__ */ jsx5("div", { className: "g3p-modal-footer" })
|
|
2372
|
+
] });
|
|
2373
|
+
if (options.position) {
|
|
2374
|
+
this.dialog.style.position = "absolute";
|
|
2375
|
+
this.dialog.style.left = `${options.position.x}px`;
|
|
2376
|
+
this.dialog.style.top = `${options.position.y}px`;
|
|
2377
|
+
this.dialog.style.transform = "translate(-50%, -50%)";
|
|
2378
|
+
}
|
|
2379
|
+
this.overlay.appendChild(this.dialog);
|
|
2380
|
+
this.container = this.overlay;
|
|
2381
|
+
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
2382
|
+
document.addEventListener("keydown", this.handleKeyDown);
|
|
2383
|
+
}
|
|
2384
|
+
handleKeyDown(e) {
|
|
2385
|
+
if (e.key === "Escape") {
|
|
2386
|
+
this.close();
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
get body() {
|
|
2390
|
+
return this.dialog.querySelector(".g3p-modal-body");
|
|
2391
|
+
}
|
|
2392
|
+
get footer() {
|
|
2393
|
+
return this.dialog.querySelector(".g3p-modal-footer");
|
|
2394
|
+
}
|
|
2395
|
+
show(parent) {
|
|
2396
|
+
parent.appendChild(this.container);
|
|
2397
|
+
const firstInput = this.dialog.querySelector("input, select, button");
|
|
2398
|
+
if (firstInput) firstInput.focus();
|
|
2399
|
+
}
|
|
2400
|
+
close() {
|
|
2401
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2402
|
+
this.container.remove();
|
|
2403
|
+
this.onClose();
|
|
2404
|
+
}
|
|
2228
2405
|
};
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2406
|
+
var NewNodeModal = class extends Modal {
|
|
2407
|
+
fieldInputs = /* @__PURE__ */ new Map();
|
|
2408
|
+
typeSelect;
|
|
2409
|
+
submitCallback;
|
|
2410
|
+
fields;
|
|
2411
|
+
constructor(options) {
|
|
2412
|
+
super({
|
|
2413
|
+
title: "New Node",
|
|
2414
|
+
onClose: () => options.onCancel?.()
|
|
2415
|
+
});
|
|
2416
|
+
this.submitCallback = options.onSubmit;
|
|
2417
|
+
this.fields = options.fields ?? /* @__PURE__ */ new Map([["title", "string"]]);
|
|
2418
|
+
this.renderBody(options.nodeTypes);
|
|
2419
|
+
this.renderFooter();
|
|
2420
|
+
}
|
|
2421
|
+
renderBody(nodeTypes) {
|
|
2422
|
+
this.body.innerHTML = "";
|
|
2423
|
+
for (const [name, type] of this.fields) {
|
|
2424
|
+
const label = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2425
|
+
const fieldGroup = this.renderField(name, label, type);
|
|
2426
|
+
this.body.appendChild(fieldGroup);
|
|
2240
2427
|
}
|
|
2241
|
-
if (
|
|
2242
|
-
|
|
2243
|
-
|
|
2428
|
+
if (nodeTypes && nodeTypes.length > 0) {
|
|
2429
|
+
const typeGroup = /* @__PURE__ */ jsxs4("div", { className: "g3p-modal-field", children: [
|
|
2430
|
+
/* @__PURE__ */ jsx5("label", { className: "g3p-modal-label", children: "Type" }),
|
|
2431
|
+
/* @__PURE__ */ jsxs4(
|
|
2432
|
+
"select",
|
|
2433
|
+
{
|
|
2434
|
+
className: "g3p-modal-select",
|
|
2435
|
+
ref: (el) => this.typeSelect = el,
|
|
2436
|
+
children: [
|
|
2437
|
+
/* @__PURE__ */ jsx5("option", { value: "", children: "Default" }),
|
|
2438
|
+
nodeTypes.map((type) => /* @__PURE__ */ jsx5("option", { value: type, children: type }))
|
|
2439
|
+
]
|
|
2440
|
+
}
|
|
2441
|
+
)
|
|
2442
|
+
] });
|
|
2443
|
+
this.body.appendChild(typeGroup);
|
|
2244
2444
|
}
|
|
2245
2445
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2446
|
+
renderField(name, label, type) {
|
|
2447
|
+
if (type === "boolean") {
|
|
2448
|
+
return /* @__PURE__ */ jsx5("div", { className: "g3p-modal-field g3p-modal-field-checkbox", children: /* @__PURE__ */ jsxs4("label", { className: "g3p-modal-label", children: [
|
|
2449
|
+
/* @__PURE__ */ jsx5(
|
|
2450
|
+
"input",
|
|
2451
|
+
{
|
|
2452
|
+
type: "checkbox",
|
|
2453
|
+
className: "g3p-modal-checkbox",
|
|
2454
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2455
|
+
}
|
|
2456
|
+
),
|
|
2457
|
+
label
|
|
2458
|
+
] }) });
|
|
2459
|
+
}
|
|
2460
|
+
return /* @__PURE__ */ jsxs4("div", { className: "g3p-modal-field", children: [
|
|
2461
|
+
/* @__PURE__ */ jsx5("label", { className: "g3p-modal-label", children: label }),
|
|
2462
|
+
/* @__PURE__ */ jsx5(
|
|
2463
|
+
"input",
|
|
2464
|
+
{
|
|
2465
|
+
type: type === "number" ? "number" : "text",
|
|
2466
|
+
className: "g3p-modal-input",
|
|
2467
|
+
placeholder: `Enter ${label.toLowerCase()}`,
|
|
2468
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2469
|
+
}
|
|
2470
|
+
)
|
|
2471
|
+
] });
|
|
2472
|
+
}
|
|
2473
|
+
renderFooter() {
|
|
2474
|
+
this.footer.innerHTML = "";
|
|
2475
|
+
this.footer.appendChild(
|
|
2476
|
+
/* @__PURE__ */ jsxs4("div", { className: "g3p-modal-buttons", children: [
|
|
2477
|
+
/* @__PURE__ */ jsx5(
|
|
2478
|
+
"button",
|
|
2479
|
+
{
|
|
2480
|
+
className: "g3p-modal-btn g3p-modal-btn-secondary",
|
|
2481
|
+
onClick: () => this.close(),
|
|
2482
|
+
children: "Cancel"
|
|
2483
|
+
}
|
|
2484
|
+
),
|
|
2485
|
+
/* @__PURE__ */ jsx5(
|
|
2486
|
+
"button",
|
|
2487
|
+
{
|
|
2488
|
+
className: "g3p-modal-btn g3p-modal-btn-primary",
|
|
2489
|
+
onClick: () => this.submit(),
|
|
2490
|
+
children: "Create"
|
|
2491
|
+
}
|
|
2492
|
+
)
|
|
2493
|
+
] })
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
submit() {
|
|
2497
|
+
const data = {};
|
|
2498
|
+
for (const [name, type] of this.fields) {
|
|
2499
|
+
const input = this.fieldInputs.get(name);
|
|
2500
|
+
if (!input) continue;
|
|
2501
|
+
if (type === "boolean") {
|
|
2502
|
+
data[name] = input.checked;
|
|
2503
|
+
} else if (type === "number") {
|
|
2504
|
+
const val = input.value;
|
|
2505
|
+
if (val) data[name] = Number(val);
|
|
2506
|
+
} else {
|
|
2507
|
+
const val = input.value.trim();
|
|
2508
|
+
if (val) data[name] = val;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
if (Object.keys(data).length === 0) {
|
|
2512
|
+
const firstInput = this.fieldInputs.values().next().value;
|
|
2513
|
+
if (firstInput) firstInput.focus();
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
if (this.typeSelect?.value) {
|
|
2517
|
+
data.type = this.typeSelect.value;
|
|
2518
|
+
}
|
|
2519
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2520
|
+
this.container.remove();
|
|
2521
|
+
this.submitCallback(data);
|
|
2522
|
+
}
|
|
2523
|
+
};
|
|
2524
|
+
var EditNodeModal = class extends Modal {
|
|
2525
|
+
fieldInputs = /* @__PURE__ */ new Map();
|
|
2526
|
+
typeSelect;
|
|
2527
|
+
node;
|
|
2528
|
+
fields;
|
|
2529
|
+
submitCallback;
|
|
2530
|
+
deleteCallback;
|
|
2531
|
+
constructor(options) {
|
|
2532
|
+
super({
|
|
2533
|
+
title: "Edit Node",
|
|
2534
|
+
position: options.position,
|
|
2535
|
+
onClose: () => options.onCancel?.()
|
|
2536
|
+
});
|
|
2537
|
+
this.node = options.node;
|
|
2538
|
+
this.submitCallback = options.onSubmit;
|
|
2539
|
+
this.deleteCallback = options.onDelete;
|
|
2540
|
+
this.fields = options.fields ?? /* @__PURE__ */ new Map([["title", "string"]]);
|
|
2541
|
+
if (!options.fields && !this.node.title)
|
|
2542
|
+
this.node = { ...this.node, title: this.node.id };
|
|
2543
|
+
this.renderBody(options.nodeTypes);
|
|
2544
|
+
this.renderFooter();
|
|
2545
|
+
}
|
|
2546
|
+
renderBody(nodeTypes) {
|
|
2547
|
+
console.log(`renderBody`, this.node);
|
|
2548
|
+
this.body.innerHTML = "";
|
|
2549
|
+
for (const [name, type] of this.fields) {
|
|
2550
|
+
const label = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2551
|
+
const currentValue = this.node[name];
|
|
2552
|
+
const fieldGroup = this.renderField(name, label, type, currentValue);
|
|
2553
|
+
this.body.appendChild(fieldGroup);
|
|
2554
|
+
}
|
|
2555
|
+
if (nodeTypes && nodeTypes.length > 0) {
|
|
2556
|
+
const currentType = this.node.type ?? "";
|
|
2557
|
+
const typeGroup = /* @__PURE__ */ jsxs4("div", { className: "g3p-modal-field", children: [
|
|
2558
|
+
/* @__PURE__ */ jsx5("label", { className: "g3p-modal-label", children: "Type" }),
|
|
2559
|
+
/* @__PURE__ */ jsxs4(
|
|
2560
|
+
"select",
|
|
2561
|
+
{
|
|
2562
|
+
className: "g3p-modal-select",
|
|
2563
|
+
ref: (el) => this.typeSelect = el,
|
|
2564
|
+
children: [
|
|
2565
|
+
/* @__PURE__ */ jsx5("option", { value: "", selected: !currentType, children: "Default" }),
|
|
2566
|
+
nodeTypes.map((type) => /* @__PURE__ */ jsx5("option", { value: type, selected: type === currentType, children: type }))
|
|
2567
|
+
]
|
|
2568
|
+
}
|
|
2569
|
+
)
|
|
2570
|
+
] });
|
|
2571
|
+
this.body.appendChild(typeGroup);
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
renderField(name, label, type, currentValue) {
|
|
2575
|
+
if (type === "boolean") {
|
|
2576
|
+
return /* @__PURE__ */ jsx5("div", { className: "g3p-modal-field g3p-modal-field-checkbox", children: /* @__PURE__ */ jsxs4("label", { className: "g3p-modal-label", children: [
|
|
2577
|
+
/* @__PURE__ */ jsx5(
|
|
2578
|
+
"input",
|
|
2579
|
+
{
|
|
2580
|
+
type: "checkbox",
|
|
2581
|
+
className: "g3p-modal-checkbox",
|
|
2582
|
+
checked: !!currentValue,
|
|
2583
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2584
|
+
}
|
|
2585
|
+
),
|
|
2586
|
+
label
|
|
2587
|
+
] }) });
|
|
2588
|
+
}
|
|
2589
|
+
return /* @__PURE__ */ jsxs4("div", { className: "g3p-modal-field", children: [
|
|
2590
|
+
/* @__PURE__ */ jsx5("label", { className: "g3p-modal-label", children: label }),
|
|
2591
|
+
/* @__PURE__ */ jsx5(
|
|
2592
|
+
"input",
|
|
2593
|
+
{
|
|
2594
|
+
type: type === "number" ? "number" : "text",
|
|
2595
|
+
className: "g3p-modal-input",
|
|
2596
|
+
value: currentValue ?? "",
|
|
2597
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2598
|
+
}
|
|
2599
|
+
)
|
|
2600
|
+
] });
|
|
2601
|
+
}
|
|
2602
|
+
renderFooter() {
|
|
2603
|
+
this.footer.innerHTML = "";
|
|
2604
|
+
this.footer.appendChild(
|
|
2605
|
+
/* @__PURE__ */ jsxs4("div", { className: "g3p-modal-buttons", children: [
|
|
2606
|
+
this.deleteCallback && /* @__PURE__ */ jsx5(
|
|
2607
|
+
"button",
|
|
2608
|
+
{
|
|
2609
|
+
className: "g3p-modal-btn g3p-modal-btn-danger",
|
|
2610
|
+
onClick: () => this.delete(),
|
|
2611
|
+
children: "Delete"
|
|
2612
|
+
}
|
|
2613
|
+
),
|
|
2614
|
+
/* @__PURE__ */ jsx5("div", { className: "g3p-modal-spacer" }),
|
|
2615
|
+
/* @__PURE__ */ jsx5(
|
|
2616
|
+
"button",
|
|
2617
|
+
{
|
|
2618
|
+
className: "g3p-modal-btn g3p-modal-btn-secondary",
|
|
2619
|
+
onClick: () => this.close(),
|
|
2620
|
+
children: "Cancel"
|
|
2621
|
+
}
|
|
2622
|
+
),
|
|
2623
|
+
/* @__PURE__ */ jsx5(
|
|
2624
|
+
"button",
|
|
2625
|
+
{
|
|
2626
|
+
className: "g3p-modal-btn g3p-modal-btn-primary",
|
|
2627
|
+
onClick: () => this.submit(),
|
|
2628
|
+
children: "Save"
|
|
2629
|
+
}
|
|
2630
|
+
)
|
|
2631
|
+
] })
|
|
2632
|
+
);
|
|
2633
|
+
}
|
|
2634
|
+
submit() {
|
|
2635
|
+
const data = { ...this.node };
|
|
2636
|
+
for (const [name, type] of this.fields) {
|
|
2637
|
+
const input = this.fieldInputs.get(name);
|
|
2638
|
+
if (!input) continue;
|
|
2639
|
+
if (type === "boolean") {
|
|
2640
|
+
data[name] = input.checked;
|
|
2641
|
+
} else if (type === "number") {
|
|
2642
|
+
const val = input.value;
|
|
2643
|
+
data[name] = val ? Number(val) : void 0;
|
|
2644
|
+
} else {
|
|
2645
|
+
const val = input.value.trim();
|
|
2646
|
+
data[name] = val || void 0;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
if (this.typeSelect) {
|
|
2650
|
+
data.type = this.typeSelect.value || void 0;
|
|
2651
|
+
}
|
|
2652
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2653
|
+
this.container.remove();
|
|
2654
|
+
this.submitCallback(data);
|
|
2655
|
+
}
|
|
2656
|
+
delete() {
|
|
2657
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2658
|
+
this.container.remove();
|
|
2659
|
+
this.deleteCallback?.();
|
|
2660
|
+
}
|
|
2661
|
+
};
|
|
2662
|
+
var EditEdgeModal = class _EditEdgeModal extends Modal {
|
|
2663
|
+
sourceMarkerSelect;
|
|
2664
|
+
targetMarkerSelect;
|
|
2665
|
+
typeSelect;
|
|
2666
|
+
edge;
|
|
2667
|
+
submitCallback;
|
|
2668
|
+
deleteCallback;
|
|
2669
|
+
static markerTypes = ["none", "arrow", "circle", "diamond", "bar"];
|
|
2670
|
+
constructor(options) {
|
|
2671
|
+
super({
|
|
2672
|
+
title: "Edit Edge",
|
|
2673
|
+
onClose: () => options.onCancel?.()
|
|
2674
|
+
});
|
|
2675
|
+
this.edge = options.edge;
|
|
2676
|
+
this.submitCallback = options.onSubmit;
|
|
2677
|
+
this.deleteCallback = options.onDelete;
|
|
2678
|
+
this.renderBody(options.edgeTypes);
|
|
2679
|
+
this.renderFooter();
|
|
2680
|
+
}
|
|
2681
|
+
renderBody(edgeTypes) {
|
|
2682
|
+
this.body.innerHTML = "";
|
|
2683
|
+
const currentSourceMarker = this.edge.source?.marker ?? "none";
|
|
2684
|
+
const currentTargetMarker = this.edge.target?.marker ?? "arrow";
|
|
2685
|
+
const sourceGroup = /* @__PURE__ */ jsxs4("div", { className: "g3p-modal-field", children: [
|
|
2686
|
+
/* @__PURE__ */ jsx5("label", { className: "g3p-modal-label", children: "Source Marker" }),
|
|
2687
|
+
/* @__PURE__ */ jsx5(
|
|
2688
|
+
"select",
|
|
2689
|
+
{
|
|
2690
|
+
className: "g3p-modal-select",
|
|
2691
|
+
ref: (el) => this.sourceMarkerSelect = el,
|
|
2692
|
+
children: _EditEdgeModal.markerTypes.map((type) => /* @__PURE__ */ jsx5("option", { value: type, selected: type === currentSourceMarker, children: type }))
|
|
2693
|
+
}
|
|
2694
|
+
)
|
|
2695
|
+
] });
|
|
2696
|
+
this.body.appendChild(sourceGroup);
|
|
2697
|
+
const targetGroup = /* @__PURE__ */ jsxs4("div", { className: "g3p-modal-field", children: [
|
|
2698
|
+
/* @__PURE__ */ jsx5("label", { className: "g3p-modal-label", children: "Target Marker" }),
|
|
2699
|
+
/* @__PURE__ */ jsx5(
|
|
2700
|
+
"select",
|
|
2701
|
+
{
|
|
2702
|
+
className: "g3p-modal-select",
|
|
2703
|
+
ref: (el) => this.targetMarkerSelect = el,
|
|
2704
|
+
children: _EditEdgeModal.markerTypes.map((type) => /* @__PURE__ */ jsx5("option", { value: type, selected: type === currentTargetMarker, children: type }))
|
|
2705
|
+
}
|
|
2706
|
+
)
|
|
2707
|
+
] });
|
|
2708
|
+
this.body.appendChild(targetGroup);
|
|
2709
|
+
if (edgeTypes && edgeTypes.length > 0) {
|
|
2710
|
+
const currentType = this.edge.type ?? "";
|
|
2711
|
+
const typeGroup = /* @__PURE__ */ jsxs4("div", { className: "g3p-modal-field", children: [
|
|
2712
|
+
/* @__PURE__ */ jsx5("label", { className: "g3p-modal-label", children: "Type" }),
|
|
2713
|
+
/* @__PURE__ */ jsxs4(
|
|
2714
|
+
"select",
|
|
2715
|
+
{
|
|
2716
|
+
className: "g3p-modal-select",
|
|
2717
|
+
ref: (el) => this.typeSelect = el,
|
|
2718
|
+
children: [
|
|
2719
|
+
/* @__PURE__ */ jsx5("option", { value: "", selected: !currentType, children: "Default" }),
|
|
2720
|
+
edgeTypes.map((type) => /* @__PURE__ */ jsx5("option", { value: type, selected: type === currentType, children: type }))
|
|
2721
|
+
]
|
|
2722
|
+
}
|
|
2723
|
+
)
|
|
2724
|
+
] });
|
|
2725
|
+
this.body.appendChild(typeGroup);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
renderFooter() {
|
|
2729
|
+
this.footer.innerHTML = "";
|
|
2730
|
+
this.footer.appendChild(
|
|
2731
|
+
/* @__PURE__ */ jsxs4("div", { className: "g3p-modal-buttons", children: [
|
|
2732
|
+
this.deleteCallback && /* @__PURE__ */ jsx5(
|
|
2733
|
+
"button",
|
|
2734
|
+
{
|
|
2735
|
+
className: "g3p-modal-btn g3p-modal-btn-danger",
|
|
2736
|
+
onClick: () => this.delete(),
|
|
2737
|
+
children: "Delete"
|
|
2738
|
+
}
|
|
2739
|
+
),
|
|
2740
|
+
/* @__PURE__ */ jsx5("div", { className: "g3p-modal-spacer" }),
|
|
2741
|
+
/* @__PURE__ */ jsx5(
|
|
2742
|
+
"button",
|
|
2743
|
+
{
|
|
2744
|
+
className: "g3p-modal-btn g3p-modal-btn-secondary",
|
|
2745
|
+
onClick: () => this.close(),
|
|
2746
|
+
children: "Cancel"
|
|
2747
|
+
}
|
|
2748
|
+
),
|
|
2749
|
+
/* @__PURE__ */ jsx5(
|
|
2750
|
+
"button",
|
|
2751
|
+
{
|
|
2752
|
+
className: "g3p-modal-btn g3p-modal-btn-primary",
|
|
2753
|
+
onClick: () => this.submit(),
|
|
2754
|
+
children: "Save"
|
|
2755
|
+
}
|
|
2756
|
+
)
|
|
2757
|
+
] })
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
submit() {
|
|
2761
|
+
const data = {
|
|
2762
|
+
...this.edge,
|
|
2763
|
+
source: {
|
|
2764
|
+
...this.edge.source,
|
|
2765
|
+
marker: this.sourceMarkerSelect.value === "none" ? void 0 : this.sourceMarkerSelect.value
|
|
2766
|
+
},
|
|
2767
|
+
target: {
|
|
2768
|
+
...this.edge.target,
|
|
2769
|
+
marker: this.targetMarkerSelect.value === "none" ? void 0 : this.targetMarkerSelect.value
|
|
2770
|
+
}
|
|
2771
|
+
};
|
|
2772
|
+
if (this.typeSelect) {
|
|
2773
|
+
data.type = this.typeSelect.value || void 0;
|
|
2774
|
+
}
|
|
2775
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2776
|
+
this.container.remove();
|
|
2777
|
+
this.submitCallback(data);
|
|
2778
|
+
}
|
|
2779
|
+
delete() {
|
|
2780
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2781
|
+
this.container.remove();
|
|
2782
|
+
this.deleteCallback?.();
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
|
|
2786
|
+
// src/canvas/canvas.tsx
|
|
2787
|
+
import styles from "./styles.css?raw";
|
|
2788
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "jsx-dom/jsx-runtime";
|
|
2789
|
+
var log10 = logger("canvas");
|
|
2249
2790
|
var Canvas = class {
|
|
2250
2791
|
container;
|
|
2251
2792
|
root;
|
|
@@ -2258,19 +2799,26 @@ var Canvas = class {
|
|
|
2258
2799
|
curSegs;
|
|
2259
2800
|
updating;
|
|
2260
2801
|
// Pan-zoom state
|
|
2261
|
-
isPanning = false;
|
|
2262
|
-
panStart = null;
|
|
2263
|
-
transformStart = null;
|
|
2264
2802
|
panScale = null;
|
|
2265
2803
|
zoomControls;
|
|
2266
|
-
|
|
2804
|
+
// Edit mode state machine
|
|
2805
|
+
editMode;
|
|
2806
|
+
api;
|
|
2807
|
+
// New-edge visual element
|
|
2808
|
+
newEdgeEl;
|
|
2809
|
+
// Pending drag state (for double-click debounce)
|
|
2810
|
+
pendingDrag = null;
|
|
2811
|
+
constructor(api, options) {
|
|
2267
2812
|
Object.assign(this, options);
|
|
2813
|
+
this.api = api;
|
|
2268
2814
|
this.allNodes = /* @__PURE__ */ new Map();
|
|
2269
2815
|
this.curNodes = /* @__PURE__ */ new Map();
|
|
2270
2816
|
this.curSegs = /* @__PURE__ */ new Map();
|
|
2271
2817
|
this.updating = false;
|
|
2272
2818
|
this.bounds = { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } };
|
|
2273
2819
|
this.transform = { x: 0, y: 0, scale: 1 };
|
|
2820
|
+
this.editMode = new EditMode();
|
|
2821
|
+
this.editMode.editable = this.editable;
|
|
2274
2822
|
this.createMeasurementContainer();
|
|
2275
2823
|
this.createCanvasContainer();
|
|
2276
2824
|
if (this.panZoom) this.setupPanZoom();
|
|
@@ -2315,7 +2863,6 @@ var Canvas = class {
|
|
|
2315
2863
|
if (gnode.isDummy) {
|
|
2316
2864
|
node = new Node2(this, gnode, true);
|
|
2317
2865
|
node.renderContainer();
|
|
2318
|
-
node.setPos(gnode.pos);
|
|
2319
2866
|
this.allNodes.set(key, node);
|
|
2320
2867
|
} else {
|
|
2321
2868
|
if (!this.allNodes.has(key))
|
|
@@ -2324,6 +2871,7 @@ var Canvas = class {
|
|
|
2324
2871
|
}
|
|
2325
2872
|
this.curNodes.set(gnode.id, node);
|
|
2326
2873
|
node.append();
|
|
2874
|
+
node.setPos(gnode.pos);
|
|
2327
2875
|
}
|
|
2328
2876
|
updateNode(gnode) {
|
|
2329
2877
|
if (gnode.isDummy) throw new Error("dummy node cannot be updated");
|
|
@@ -2345,10 +2893,10 @@ var Canvas = class {
|
|
|
2345
2893
|
this.curSegs.set(gseg.id, seg);
|
|
2346
2894
|
seg.append();
|
|
2347
2895
|
}
|
|
2348
|
-
updateSeg(gseg) {
|
|
2896
|
+
updateSeg(gseg, g) {
|
|
2349
2897
|
const seg = this.curSegs.get(gseg.id);
|
|
2350
2898
|
if (!seg) throw new Error("seg not found");
|
|
2351
|
-
seg.update(gseg);
|
|
2899
|
+
seg.update(gseg, g);
|
|
2352
2900
|
}
|
|
2353
2901
|
deleteSeg(gseg) {
|
|
2354
2902
|
const seg = this.curSegs.get(gseg.id);
|
|
@@ -2368,18 +2916,82 @@ var Canvas = class {
|
|
|
2368
2916
|
for (const node of newNodes.values()) {
|
|
2369
2917
|
node.measure(isVertical);
|
|
2370
2918
|
const { id, version } = node.data;
|
|
2371
|
-
const key =
|
|
2919
|
+
const key = `k:${id}:${version}`;
|
|
2372
2920
|
this.allNodes.set(key, node);
|
|
2373
2921
|
node.renderContainer();
|
|
2374
2922
|
}
|
|
2375
2923
|
this.measurement.innerHTML = "";
|
|
2376
2924
|
return newNodes;
|
|
2377
2925
|
}
|
|
2926
|
+
// ========== Mouse event handlers ==========
|
|
2378
2927
|
onClick(e) {
|
|
2379
|
-
|
|
2928
|
+
const hit = this.hitTest(e.clientX, e.clientY);
|
|
2929
|
+
if (hit.type === "node") {
|
|
2930
|
+
this.api.handleClickNode(hit.node.data.id);
|
|
2931
|
+
} else if (hit.type === "edge") {
|
|
2932
|
+
this.api.handleClickEdge(hit.segId);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
onDoubleClick(e) {
|
|
2936
|
+
if (this.pendingDrag) {
|
|
2937
|
+
window.clearTimeout(this.pendingDrag.timeout);
|
|
2938
|
+
this.pendingDrag = null;
|
|
2939
|
+
}
|
|
2940
|
+
if (!this.editMode.editable) return;
|
|
2941
|
+
const hit = this.hitTest(e.clientX, e.clientY);
|
|
2942
|
+
if (hit.type === "node") {
|
|
2943
|
+
if (hit.node.isDummy) return;
|
|
2944
|
+
this.api.handleEditNode(hit.node.data.id);
|
|
2945
|
+
} else if (hit.type === "edge") {
|
|
2946
|
+
this.api.handleEditEdge(hit.segId);
|
|
2947
|
+
} else {
|
|
2948
|
+
this.api.handleNewNode();
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
// ========== Built-in Modals ==========
|
|
2952
|
+
/** Show the new node modal */
|
|
2953
|
+
showNewNodeModal(callback) {
|
|
2954
|
+
const nodeTypes = Object.keys(this.nodeTypes);
|
|
2955
|
+
const fields = this.api.getNodeFields();
|
|
2956
|
+
const modal = new NewNodeModal({
|
|
2957
|
+
nodeTypes: nodeTypes.length > 0 ? nodeTypes : void 0,
|
|
2958
|
+
fields: fields.size > 0 ? fields : void 0,
|
|
2959
|
+
onSubmit: (data) => {
|
|
2960
|
+
callback(data);
|
|
2961
|
+
}
|
|
2962
|
+
});
|
|
2963
|
+
modal.show(document.body);
|
|
2964
|
+
}
|
|
2965
|
+
/** Show the edit node modal */
|
|
2966
|
+
showEditNodeModal(node, callback) {
|
|
2967
|
+
const nodeTypes = Object.keys(this.nodeTypes);
|
|
2968
|
+
const fields = this.api.getNodeFields();
|
|
2969
|
+
const modal = new EditNodeModal({
|
|
2970
|
+
node,
|
|
2971
|
+
nodeTypes: nodeTypes.length > 0 ? nodeTypes : void 0,
|
|
2972
|
+
fields: fields.size > 0 ? fields : void 0,
|
|
2973
|
+
onSubmit: (data) => {
|
|
2974
|
+
callback(data);
|
|
2975
|
+
},
|
|
2976
|
+
onDelete: () => {
|
|
2977
|
+
this.api.handleDeleteNode(node.id);
|
|
2978
|
+
}
|
|
2979
|
+
});
|
|
2980
|
+
modal.show(document.body);
|
|
2981
|
+
}
|
|
2982
|
+
/** Show the edit edge modal */
|
|
2983
|
+
showEditEdgeModal(edge, callback) {
|
|
2984
|
+
const modal = new EditEdgeModal({
|
|
2985
|
+
edge,
|
|
2986
|
+
edgeTypes: Object.keys(this.edgeTypes),
|
|
2987
|
+
onSubmit: callback,
|
|
2988
|
+
onDelete: () => {
|
|
2989
|
+
this.api.handleDeleteEdge(edge.id);
|
|
2990
|
+
}
|
|
2991
|
+
});
|
|
2992
|
+
modal.show(document.body);
|
|
2380
2993
|
}
|
|
2381
2994
|
onContextMenu(e) {
|
|
2382
|
-
console.log("context menu", e);
|
|
2383
2995
|
}
|
|
2384
2996
|
groupTransform() {
|
|
2385
2997
|
return `translate(${this.transform.x}, ${this.transform.y}) scale(${this.transform.scale})`;
|
|
@@ -2394,53 +3006,52 @@ var Canvas = class {
|
|
|
2394
3006
|
}
|
|
2395
3007
|
generateDynamicStyles() {
|
|
2396
3008
|
let css = "";
|
|
2397
|
-
|
|
2398
|
-
css += themeToCSS(this.theme, `.${prefix}-canvas-container`);
|
|
3009
|
+
css += themeToCSS(this.theme, `.g3p-canvas-container`);
|
|
2399
3010
|
for (const [type, vars] of Object.entries(this.nodeTypes)) {
|
|
2400
|
-
css += themeToCSS(vars,
|
|
3011
|
+
css += themeToCSS(vars, `.g3p-node-type-${type}`, "node");
|
|
2401
3012
|
}
|
|
2402
3013
|
for (const [type, vars] of Object.entries(this.edgeTypes)) {
|
|
2403
|
-
css += themeToCSS(vars,
|
|
3014
|
+
css += themeToCSS(vars, `.g3p-edge-type-${type}`);
|
|
2404
3015
|
}
|
|
2405
3016
|
return css;
|
|
2406
3017
|
}
|
|
2407
3018
|
createCanvasContainer() {
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
3019
|
+
if (!document.getElementById("g3p-styles")) {
|
|
3020
|
+
const baseStyleEl = document.createElement("style");
|
|
3021
|
+
baseStyleEl.id = "g3p-styles";
|
|
3022
|
+
baseStyleEl.textContent = styles;
|
|
3023
|
+
document.head.appendChild(baseStyleEl);
|
|
3024
|
+
}
|
|
2414
3025
|
const dynamicStyles = this.generateDynamicStyles();
|
|
2415
3026
|
if (dynamicStyles) {
|
|
2416
|
-
const
|
|
2417
|
-
|
|
2418
|
-
document.head.appendChild(
|
|
3027
|
+
const dynamicStyleEl = document.createElement("style");
|
|
3028
|
+
dynamicStyleEl.textContent = dynamicStyles;
|
|
3029
|
+
document.head.appendChild(dynamicStyleEl);
|
|
2419
3030
|
}
|
|
2420
|
-
const c = styler("canvas", styles3, this.classPrefix);
|
|
2421
3031
|
const colorModeClass = this.colorMode !== "system" ? `g3p-${this.colorMode}` : "";
|
|
2422
|
-
this.container = /* @__PURE__ */
|
|
3032
|
+
this.container = /* @__PURE__ */ jsx6(
|
|
2423
3033
|
"div",
|
|
2424
3034
|
{
|
|
2425
|
-
className:
|
|
3035
|
+
className: `g3p-canvas-container ${colorModeClass}`.trim(),
|
|
2426
3036
|
ref: (el) => this.container = el,
|
|
2427
3037
|
onContextMenu: this.onContextMenu.bind(this),
|
|
2428
|
-
children: /* @__PURE__ */
|
|
3038
|
+
children: /* @__PURE__ */ jsxs5(
|
|
2429
3039
|
"svg",
|
|
2430
3040
|
{
|
|
2431
3041
|
ref: (el) => this.root = el,
|
|
2432
|
-
className:
|
|
3042
|
+
className: "g3p-canvas-root",
|
|
2433
3043
|
width: this.width,
|
|
2434
3044
|
height: this.height,
|
|
2435
3045
|
viewBox: this.viewBox(),
|
|
2436
3046
|
preserveAspectRatio: "xMidYMid meet",
|
|
2437
3047
|
onClick: this.onClick.bind(this),
|
|
3048
|
+
onDblClick: this.onDoubleClick.bind(this),
|
|
2438
3049
|
children: [
|
|
2439
|
-
/* @__PURE__ */
|
|
2440
|
-
Object.values(markerDefs).map((marker) => marker(this.markerSize,
|
|
2441
|
-
Object.values(markerDefs).map((marker) => marker(this.markerSize,
|
|
3050
|
+
/* @__PURE__ */ jsxs5("defs", { children: [
|
|
3051
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, false)),
|
|
3052
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, true))
|
|
2442
3053
|
] }),
|
|
2443
|
-
/* @__PURE__ */
|
|
3054
|
+
/* @__PURE__ */ jsx6(
|
|
2444
3055
|
"g",
|
|
2445
3056
|
{
|
|
2446
3057
|
ref: (el) => this.group = el,
|
|
@@ -2459,14 +3070,34 @@ var Canvas = class {
|
|
|
2459
3070
|
this.container.addEventListener("mousedown", this.onMouseDown.bind(this));
|
|
2460
3071
|
document.addEventListener("mousemove", this.onMouseMove.bind(this));
|
|
2461
3072
|
document.addEventListener("mouseup", this.onMouseUp.bind(this));
|
|
3073
|
+
document.addEventListener("keydown", this.onKeyDown.bind(this));
|
|
2462
3074
|
this.createZoomControls();
|
|
2463
3075
|
}
|
|
3076
|
+
onKeyDown(e) {
|
|
3077
|
+
if (e.key === "Escape" && this.editMode.isCreatingEdge) {
|
|
3078
|
+
this.endNewEdge(true);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
2464
3081
|
/** Convert screen coordinates to canvas-relative coordinates */
|
|
2465
3082
|
screenToCanvas(screen) {
|
|
2466
3083
|
const rect = this.container.getBoundingClientRect();
|
|
2467
3084
|
return canvasPos(screen.x - rect.left, screen.y - rect.top);
|
|
2468
3085
|
}
|
|
2469
|
-
/**
|
|
3086
|
+
/** Convert canvas coordinates to graph coordinates */
|
|
3087
|
+
canvasToGraph(canvas) {
|
|
3088
|
+
const vb = this.currentViewBox();
|
|
3089
|
+
const { scale, offsetX, offsetY } = this.getEffectiveScale();
|
|
3090
|
+
return graphPos(
|
|
3091
|
+
vb.x - offsetX + canvas.x * scale,
|
|
3092
|
+
vb.y - offsetY + canvas.y * scale
|
|
3093
|
+
);
|
|
3094
|
+
}
|
|
3095
|
+
/** Convert screen coordinates to graph coordinates */
|
|
3096
|
+
screenToGraph(screen) {
|
|
3097
|
+
const canvas = this.screenToCanvas(screen);
|
|
3098
|
+
return this.canvasToGraph(canvas);
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
2470
3101
|
* Get the effective scale from canvas pixels to graph units,
|
|
2471
3102
|
* accounting for preserveAspectRatio="xMidYMid meet" which uses
|
|
2472
3103
|
* the smaller scale (to fit) and centers the content.
|
|
@@ -2483,15 +3114,6 @@ var Canvas = class {
|
|
|
2483
3114
|
const offsetY = (actualH - vb.h) / 2;
|
|
2484
3115
|
return { scale, offsetX, offsetY };
|
|
2485
3116
|
}
|
|
2486
|
-
/** Convert canvas coordinates to graph coordinates */
|
|
2487
|
-
canvasToGraph(canvas) {
|
|
2488
|
-
const vb = this.currentViewBox();
|
|
2489
|
-
const { scale, offsetX, offsetY } = this.getEffectiveScale();
|
|
2490
|
-
return graphPos(
|
|
2491
|
-
vb.x - offsetX + canvas.x * scale,
|
|
2492
|
-
vb.y - offsetY + canvas.y * scale
|
|
2493
|
-
);
|
|
2494
|
-
}
|
|
2495
3117
|
/** Get current viewBox as an object */
|
|
2496
3118
|
currentViewBox() {
|
|
2497
3119
|
const p = this.padding;
|
|
@@ -2526,28 +3148,67 @@ var Canvas = class {
|
|
|
2526
3148
|
onMouseDown(e) {
|
|
2527
3149
|
if (e.button !== 0) return;
|
|
2528
3150
|
if (e.target.closest(".g3p-zoom-controls")) return;
|
|
2529
|
-
this.
|
|
2530
|
-
this.
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
3151
|
+
const hit = this.hitTest(e.clientX, e.clientY);
|
|
3152
|
+
if (this.editMode.editable && (hit.type === "node" || hit.type === "port")) {
|
|
3153
|
+
const node = hit.node;
|
|
3154
|
+
if (node.isDummy) return;
|
|
3155
|
+
e.preventDefault();
|
|
3156
|
+
e.stopPropagation();
|
|
3157
|
+
const startGraph = this.screenToGraph(hit.center);
|
|
3158
|
+
const portId = hit.type === "port" ? hit.port : void 0;
|
|
3159
|
+
this.pendingDrag = {
|
|
3160
|
+
timeout: window.setTimeout(() => {
|
|
3161
|
+
if (this.pendingDrag) {
|
|
3162
|
+
this.startNewEdge(this.pendingDrag.nodeId, this.pendingDrag.startGraph, this.pendingDrag.portId);
|
|
3163
|
+
this.pendingDrag = null;
|
|
3164
|
+
}
|
|
3165
|
+
}, 200),
|
|
3166
|
+
nodeId: node.data.id,
|
|
3167
|
+
startGraph,
|
|
3168
|
+
portId
|
|
3169
|
+
};
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
if (hit.type === "canvas" || hit.type === "edge") {
|
|
3173
|
+
const startCanvas = this.screenToCanvas(screenPos(e.clientX, e.clientY));
|
|
3174
|
+
this.editMode.startPan(startCanvas, { ...this.transform });
|
|
3175
|
+
const { scale } = this.getEffectiveScale();
|
|
3176
|
+
this.panScale = { x: scale, y: scale };
|
|
3177
|
+
this.container.style.cursor = "grabbing";
|
|
3178
|
+
e.preventDefault();
|
|
3179
|
+
}
|
|
2536
3180
|
}
|
|
2537
3181
|
onMouseMove(e) {
|
|
2538
|
-
if (
|
|
3182
|
+
if (this.editMode.isCreatingEdge) {
|
|
3183
|
+
const screenCursor = screenPos(e.clientX, e.clientY);
|
|
3184
|
+
const canvasCursor = this.screenToCanvas(screenCursor);
|
|
3185
|
+
const graphCursor = this.canvasToGraph(canvasCursor);
|
|
3186
|
+
this.editMode.updateNewEdgePosition(graphCursor);
|
|
3187
|
+
this.updateNewEdgeVisual();
|
|
3188
|
+
this.detectHoverTarget(e.clientX, e.clientY);
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
if (!this.editMode.isPanning || !this.panScale) return;
|
|
3192
|
+
const panState = this.editMode.state;
|
|
3193
|
+
if (panState.type !== "panning") return;
|
|
2539
3194
|
const current = this.screenToCanvas(screenPos(e.clientX, e.clientY));
|
|
2540
|
-
const dx = current.x -
|
|
2541
|
-
const dy = current.y -
|
|
2542
|
-
this.transform.x =
|
|
2543
|
-
this.transform.y =
|
|
3195
|
+
const dx = current.x - panState.startCanvas.x;
|
|
3196
|
+
const dy = current.y - panState.startCanvas.y;
|
|
3197
|
+
this.transform.x = panState.startTransform.x + dx * this.panScale.x;
|
|
3198
|
+
this.transform.y = panState.startTransform.y + dy * this.panScale.y;
|
|
2544
3199
|
this.applyTransform();
|
|
2545
3200
|
}
|
|
2546
3201
|
onMouseUp(e) {
|
|
2547
|
-
if (
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
3202
|
+
if (this.pendingDrag) {
|
|
3203
|
+
window.clearTimeout(this.pendingDrag.timeout);
|
|
3204
|
+
this.pendingDrag = null;
|
|
3205
|
+
}
|
|
3206
|
+
if (this.editMode.isCreatingEdge) {
|
|
3207
|
+
this.endNewEdge(false);
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
3210
|
+
if (!this.editMode.isPanning) return;
|
|
3211
|
+
this.editMode.reset();
|
|
2551
3212
|
this.panScale = null;
|
|
2552
3213
|
this.container.style.cursor = "";
|
|
2553
3214
|
}
|
|
@@ -2557,12 +3218,11 @@ var Canvas = class {
|
|
|
2557
3218
|
this.updateZoomLevel();
|
|
2558
3219
|
}
|
|
2559
3220
|
createZoomControls() {
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
/* @__PURE__ */
|
|
2563
|
-
/* @__PURE__ */
|
|
2564
|
-
/* @__PURE__ */
|
|
2565
|
-
/* @__PURE__ */ jsx4("button", { className: `${c("btn")} ${c("reset")}`, onClick: () => this.zoomReset(), children: "\u27F2" })
|
|
3221
|
+
this.zoomControls = /* @__PURE__ */ jsxs5("div", { className: "g3p-zoom-controls", children: [
|
|
3222
|
+
/* @__PURE__ */ jsx6("button", { className: "g3p-zoom-btn", onClick: () => this.zoomIn(), children: "+" }),
|
|
3223
|
+
/* @__PURE__ */ jsx6("div", { className: "g3p-zoom-level", id: "g3p-zoom-level", children: "100%" }),
|
|
3224
|
+
/* @__PURE__ */ jsx6("button", { className: "g3p-zoom-btn", onClick: () => this.zoomOut(), children: "\u2212" }),
|
|
3225
|
+
/* @__PURE__ */ jsx6("button", { className: "g3p-zoom-btn g3p-zoom-reset", onClick: () => this.zoomReset(), children: "\u27F2" })
|
|
2566
3226
|
] });
|
|
2567
3227
|
this.container.appendChild(this.zoomControls);
|
|
2568
3228
|
}
|
|
@@ -2584,17 +3244,215 @@ var Canvas = class {
|
|
|
2584
3244
|
this.transform = { x: 0, y: 0, scale: 1 };
|
|
2585
3245
|
this.applyTransform();
|
|
2586
3246
|
}
|
|
3247
|
+
// ==================== New-Edge Mode ====================
|
|
3248
|
+
/** Start creating a new edge from a node */
|
|
3249
|
+
startNewEdge(sourceNodeId, startGraph, sourcePortId) {
|
|
3250
|
+
this.editMode.startNewEdge(sourceNodeId, startGraph, sourcePortId);
|
|
3251
|
+
this.updateNewEdgeVisual();
|
|
3252
|
+
this.container.style.cursor = "crosshair";
|
|
3253
|
+
}
|
|
3254
|
+
/** Update the new-edge visual during drag */
|
|
3255
|
+
updateNewEdgeVisual() {
|
|
3256
|
+
const state = this.editMode.getNewEdgeState();
|
|
3257
|
+
if (!state) {
|
|
3258
|
+
this.removeNewEdgeVisual();
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
3261
|
+
if (this.newEdgeEl) {
|
|
3262
|
+
this.newEdgeEl.remove();
|
|
3263
|
+
}
|
|
3264
|
+
this.newEdgeEl = renderNewEdge({
|
|
3265
|
+
start: state.startGraph,
|
|
3266
|
+
end: state.currentGraph
|
|
3267
|
+
});
|
|
3268
|
+
this.group.appendChild(this.newEdgeEl);
|
|
3269
|
+
}
|
|
3270
|
+
/** Remove the new-edge visual */
|
|
3271
|
+
removeNewEdgeVisual() {
|
|
3272
|
+
if (this.newEdgeEl) {
|
|
3273
|
+
this.newEdgeEl.remove();
|
|
3274
|
+
this.newEdgeEl = void 0;
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
/** Complete or cancel the new-edge creation */
|
|
3278
|
+
endNewEdge(cancelled = false) {
|
|
3279
|
+
const state = this.editMode.getNewEdgeState();
|
|
3280
|
+
if (!state) return;
|
|
3281
|
+
if (!cancelled) {
|
|
3282
|
+
const { target, source } = state;
|
|
3283
|
+
if (target?.type == "node") {
|
|
3284
|
+
this.api.handleAddEdge({ id: "", source, target });
|
|
3285
|
+
} else {
|
|
3286
|
+
this.api.handleNewNodeFrom(source);
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
this.removeNewEdgeVisual();
|
|
3290
|
+
this.clearDropTargetHighlight();
|
|
3291
|
+
this.editMode.reset();
|
|
3292
|
+
this.container.style.cursor = "";
|
|
3293
|
+
}
|
|
3294
|
+
/** Find node data by internal ID */
|
|
3295
|
+
findNodeDataById(nodeId) {
|
|
3296
|
+
for (const node of this.curNodes.values()) {
|
|
3297
|
+
if (node.data?.id === nodeId) {
|
|
3298
|
+
return node.data.data;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
return null;
|
|
3302
|
+
}
|
|
3303
|
+
/** Set hover target for new-edge mode */
|
|
3304
|
+
setNewEdgeHoverTarget(id, port) {
|
|
3305
|
+
this.clearDropTargetHighlight();
|
|
3306
|
+
this.editMode.setHoverTarget({ type: "node", id, port });
|
|
3307
|
+
if (port) {
|
|
3308
|
+
const portEl = this.container?.querySelector(`.g3p-node-port[data-node-id="${id}"][data-port-id="${port}"]`);
|
|
3309
|
+
if (portEl) portEl.classList.add("g3p-drop-target");
|
|
3310
|
+
} else {
|
|
3311
|
+
const node = this.curNodes.get(id);
|
|
3312
|
+
if (node?.container) node.container.classList.add("g3p-drop-target");
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
/** Clear hover target for new-edge mode */
|
|
3316
|
+
clearNewEdgeHoverTarget() {
|
|
3317
|
+
this.clearDropTargetHighlight();
|
|
3318
|
+
this.editMode.setHoverTarget(null);
|
|
3319
|
+
}
|
|
3320
|
+
/** Remove drop target highlight from all elements */
|
|
3321
|
+
clearDropTargetHighlight() {
|
|
3322
|
+
for (const node of this.curNodes.values()) {
|
|
3323
|
+
node.container?.classList.remove("g3p-drop-target");
|
|
3324
|
+
}
|
|
3325
|
+
this.container?.querySelectorAll(".g3p-drop-target").forEach((el) => {
|
|
3326
|
+
el.classList.remove("g3p-drop-target");
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
/** Detect hover target during new-edge drag using elementFromPoint */
|
|
3330
|
+
detectHoverTarget(clientX, clientY) {
|
|
3331
|
+
if (this.newEdgeEl) {
|
|
3332
|
+
this.newEdgeEl.style.display = "none";
|
|
3333
|
+
}
|
|
3334
|
+
const el = document.elementFromPoint(clientX, clientY);
|
|
3335
|
+
if (this.newEdgeEl) {
|
|
3336
|
+
this.newEdgeEl.style.display = "";
|
|
3337
|
+
}
|
|
3338
|
+
if (!el) {
|
|
3339
|
+
this.clearNewEdgeHoverTarget();
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
const portEl = el.closest(".g3p-node-port");
|
|
3343
|
+
if (portEl) {
|
|
3344
|
+
const nodeId = portEl.getAttribute("data-node-id");
|
|
3345
|
+
const portId = portEl.getAttribute("data-port-id");
|
|
3346
|
+
if (nodeId && portId) {
|
|
3347
|
+
const node = this.curNodes.get(nodeId);
|
|
3348
|
+
if (node && !node.isDummy) {
|
|
3349
|
+
this.setNewEdgeHoverTarget(nodeId, portId);
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
const nodeEl = el.closest(".g3p-node-container");
|
|
3355
|
+
if (nodeEl) {
|
|
3356
|
+
const nodeId = nodeEl.getAttribute("data-node-id");
|
|
3357
|
+
if (nodeId) {
|
|
3358
|
+
const node = this.curNodes.get(nodeId);
|
|
3359
|
+
if (node && !node.isDummy) {
|
|
3360
|
+
this.setNewEdgeHoverTarget(node.data.id);
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
this.clearNewEdgeHoverTarget();
|
|
3366
|
+
}
|
|
3367
|
+
// ==================== Hit Testing ====================
|
|
3368
|
+
/** Result of a hit test */
|
|
3369
|
+
hitTest(clientX, clientY) {
|
|
3370
|
+
const el = document.elementFromPoint(clientX, clientY);
|
|
3371
|
+
if (!el) return { type: "canvas" };
|
|
3372
|
+
const getCenter = (el2) => {
|
|
3373
|
+
const rect = el2.getBoundingClientRect();
|
|
3374
|
+
return screenPos(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
3375
|
+
};
|
|
3376
|
+
const portEl = el.closest(".g3p-node-port");
|
|
3377
|
+
if (portEl) {
|
|
3378
|
+
const nodeId = portEl.getAttribute("data-node-id");
|
|
3379
|
+
const portId = portEl.getAttribute("data-port-id");
|
|
3380
|
+
if (nodeId && portId) {
|
|
3381
|
+
const center = getCenter(portEl);
|
|
3382
|
+
const node = this.curNodes.get(nodeId);
|
|
3383
|
+
if (node) {
|
|
3384
|
+
return { type: "port", node, port: portId, center };
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
const nodeEl = el.closest(".g3p-node-container");
|
|
3389
|
+
if (nodeEl) {
|
|
3390
|
+
const nodeId = nodeEl.getAttribute("data-node-id");
|
|
3391
|
+
if (nodeId) {
|
|
3392
|
+
const borderEl = el.closest(".g3p-node-border");
|
|
3393
|
+
const center = getCenter(borderEl ?? nodeEl);
|
|
3394
|
+
const node = this.curNodes.get(nodeId);
|
|
3395
|
+
if (node) {
|
|
3396
|
+
return { type: "node", node, center };
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
const edgeEl = el.closest(".g3p-seg-container");
|
|
3401
|
+
if (edgeEl) {
|
|
3402
|
+
const segId = edgeEl.getAttribute("data-edge-id");
|
|
3403
|
+
if (segId) {
|
|
3404
|
+
return { type: "edge", segId };
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
return { type: "canvas" };
|
|
3408
|
+
}
|
|
2587
3409
|
};
|
|
3410
|
+
var themeVarMap = {
|
|
3411
|
+
// Canvas
|
|
3412
|
+
bg: "--g3p-bg",
|
|
3413
|
+
shadow: "--g3p-shadow",
|
|
3414
|
+
// Node
|
|
3415
|
+
border: "--g3p-border",
|
|
3416
|
+
borderHover: "--g3p-border-hover",
|
|
3417
|
+
borderSelected: "--g3p-border-selected",
|
|
3418
|
+
text: "--g3p-text",
|
|
3419
|
+
textMuted: "--g3p-text-muted",
|
|
3420
|
+
// Port
|
|
3421
|
+
bgHover: "--g3p-port-bg-hover",
|
|
3422
|
+
// Edge
|
|
3423
|
+
color: "--g3p-edge-color"
|
|
3424
|
+
};
|
|
3425
|
+
function themeToCSS(theme, selector, prefix = "") {
|
|
3426
|
+
const entries = Object.entries(theme).filter(([_, v]) => v !== void 0);
|
|
3427
|
+
if (!entries.length) return "";
|
|
3428
|
+
let css = `${selector} {
|
|
3429
|
+
`;
|
|
3430
|
+
for (const [key, value] of entries) {
|
|
3431
|
+
let cssVar = themeVarMap[key];
|
|
3432
|
+
if (key === "bg" && prefix === "node") {
|
|
3433
|
+
cssVar = "--g3p-bg-node";
|
|
3434
|
+
} else if (key === "bg" && prefix === "port") {
|
|
3435
|
+
cssVar = "--g3p-port-bg";
|
|
3436
|
+
}
|
|
3437
|
+
if (cssVar) {
|
|
3438
|
+
css += ` ${cssVar}: ${value};
|
|
3439
|
+
`;
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
css += "}\n";
|
|
3443
|
+
return css;
|
|
3444
|
+
}
|
|
2588
3445
|
|
|
2589
3446
|
// src/canvas/render-node.tsx
|
|
2590
|
-
import { jsx as
|
|
2591
|
-
function renderNode(node) {
|
|
3447
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "jsx-dom/jsx-runtime";
|
|
3448
|
+
function renderNode(node, props) {
|
|
2592
3449
|
if (typeof node == "string") node = { id: node };
|
|
2593
|
-
const title = node?.title ?? node?.label ?? node?.name ?? node?.text ?? node?.id ?? "?";
|
|
3450
|
+
const title = node?.title ?? props?.title ?? node?.label ?? node?.name ?? node?.text ?? props?.text ?? node?.id ?? "?";
|
|
2594
3451
|
const detail = node?.detail ?? node?.description ?? node?.subtitle;
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
3452
|
+
console.log(`renderNode: ${node.id} ${title} ${detail}`);
|
|
3453
|
+
return /* @__PURE__ */ jsxs6("div", { className: "g3p-node-default", children: [
|
|
3454
|
+
/* @__PURE__ */ jsx7("div", { className: "g3p-node-title", children: title }),
|
|
3455
|
+
detail && /* @__PURE__ */ jsx7("div", { className: "g3p-node-detail", children: detail })
|
|
2598
3456
|
] });
|
|
2599
3457
|
}
|
|
2600
3458
|
|
|
@@ -2626,7 +3484,6 @@ function defaults() {
|
|
|
2626
3484
|
},
|
|
2627
3485
|
canvas: {
|
|
2628
3486
|
renderNode,
|
|
2629
|
-
classPrefix: "g3p",
|
|
2630
3487
|
width: "100%",
|
|
2631
3488
|
height: "100%",
|
|
2632
3489
|
padding: 20,
|
|
@@ -2703,22 +3560,30 @@ var API = class {
|
|
|
2703
3560
|
nodeIds;
|
|
2704
3561
|
edgeIds;
|
|
2705
3562
|
nodeVersions;
|
|
3563
|
+
nodeOverrides;
|
|
3564
|
+
edgeOverrides;
|
|
3565
|
+
nodeFields;
|
|
2706
3566
|
nextNodeId;
|
|
2707
3567
|
nextEdgeId;
|
|
3568
|
+
events;
|
|
2708
3569
|
root;
|
|
2709
3570
|
constructor(args) {
|
|
2710
3571
|
this.root = args.root;
|
|
2711
3572
|
this.options = applyDefaults(args.options);
|
|
2712
3573
|
let graph2 = new Graph({ options: this.options.graph });
|
|
2713
3574
|
this.state = { graph: graph2, update: null };
|
|
3575
|
+
this.events = args.events || {};
|
|
2714
3576
|
this.seq = [this.state];
|
|
2715
3577
|
this.index = 0;
|
|
2716
3578
|
this.nodeIds = /* @__PURE__ */ new Map();
|
|
2717
3579
|
this.edgeIds = /* @__PURE__ */ new Map();
|
|
2718
3580
|
this.nodeVersions = /* @__PURE__ */ new Map();
|
|
3581
|
+
this.nodeOverrides = /* @__PURE__ */ new Map();
|
|
3582
|
+
this.edgeOverrides = /* @__PURE__ */ new Map();
|
|
3583
|
+
this.nodeFields = /* @__PURE__ */ new Map();
|
|
2719
3584
|
this.nextNodeId = 1;
|
|
2720
3585
|
this.nextEdgeId = 1;
|
|
2721
|
-
this.canvas = new Canvas({
|
|
3586
|
+
this.canvas = new Canvas(this, {
|
|
2722
3587
|
...this.options.canvas,
|
|
2723
3588
|
dummyNodeSize: this.options.graph.dummyNodeSize,
|
|
2724
3589
|
orientation: this.options.graph.orientation
|
|
@@ -2731,6 +3596,22 @@ var API = class {
|
|
|
2731
3596
|
this.history = [];
|
|
2732
3597
|
}
|
|
2733
3598
|
}
|
|
3599
|
+
/** Current history index (0-based) */
|
|
3600
|
+
getHistoryIndex() {
|
|
3601
|
+
return this.index;
|
|
3602
|
+
}
|
|
3603
|
+
/** Current history length */
|
|
3604
|
+
getHistoryLength() {
|
|
3605
|
+
return this.seq.length;
|
|
3606
|
+
}
|
|
3607
|
+
/** Toggle canvas editable mode without re-creating the graph */
|
|
3608
|
+
setEditable(editable) {
|
|
3609
|
+
this.canvas.editMode.editable = editable;
|
|
3610
|
+
}
|
|
3611
|
+
get graph() {
|
|
3612
|
+
return this.state.graph;
|
|
3613
|
+
}
|
|
3614
|
+
/** Initialize the API */
|
|
2734
3615
|
async init() {
|
|
2735
3616
|
const root = document.getElementById(this.root);
|
|
2736
3617
|
if (!root) throw new Error("root element not found");
|
|
@@ -2738,6 +3619,7 @@ var API = class {
|
|
|
2738
3619
|
for (const update of this.history)
|
|
2739
3620
|
await this.applyUpdate(update);
|
|
2740
3621
|
}
|
|
3622
|
+
/** Navigate to a different state */
|
|
2741
3623
|
nav(nav) {
|
|
2742
3624
|
let newIndex;
|
|
2743
3625
|
switch (nav) {
|
|
@@ -2759,6 +3641,8 @@ var API = class {
|
|
|
2759
3641
|
this.applyDiff(this.index, newIndex);
|
|
2760
3642
|
this.index = newIndex;
|
|
2761
3643
|
this.state = this.seq[this.index];
|
|
3644
|
+
if (this.events.historyChange)
|
|
3645
|
+
this.events.historyChange(this.index, this.seq.length);
|
|
2762
3646
|
}
|
|
2763
3647
|
applyDiff(oldIndex, newIndex) {
|
|
2764
3648
|
const oldGraph = this.seq[oldIndex].graph;
|
|
@@ -2768,36 +3652,72 @@ var API = class {
|
|
|
2768
3652
|
if (!newNode) this.canvas.deleteNode(oldNode);
|
|
2769
3653
|
}
|
|
2770
3654
|
for (const newNode of newGraph.nodes.values()) {
|
|
2771
|
-
|
|
3655
|
+
const oldNode = oldGraph.nodes.get(newNode.id);
|
|
3656
|
+
if (!oldNode) {
|
|
3657
|
+
this.canvas.addNode(newNode);
|
|
3658
|
+
} else if (oldNode.key !== newNode.key) {
|
|
3659
|
+
this.canvas.deleteNode(oldNode);
|
|
3660
|
+
this.canvas.addNode(newNode);
|
|
3661
|
+
} else if (oldNode.pos !== newNode.pos) {
|
|
3662
|
+
this.canvas.getNode(newNode.key).setPos(newNode.pos);
|
|
3663
|
+
}
|
|
2772
3664
|
}
|
|
2773
3665
|
for (const oldSeg of oldGraph.segs.values()) {
|
|
2774
3666
|
const newSeg = newGraph.segs.get(oldSeg.id);
|
|
2775
|
-
if (!newSeg)
|
|
3667
|
+
if (!newSeg) {
|
|
2776
3668
|
this.canvas.deleteSeg(oldSeg);
|
|
2777
|
-
else if (oldSeg
|
|
2778
|
-
this.canvas.updateSeg(newSeg);
|
|
3669
|
+
} else if (oldSeg !== newSeg) {
|
|
3670
|
+
this.canvas.updateSeg(newSeg, newGraph);
|
|
3671
|
+
}
|
|
2779
3672
|
}
|
|
2780
3673
|
for (const newSeg of newGraph.segs.values()) {
|
|
2781
|
-
if (!oldGraph.segs.has(newSeg.id))
|
|
3674
|
+
if (!oldGraph.segs.has(newSeg.id)) {
|
|
2782
3675
|
this.canvas.addSeg(newSeg, newGraph);
|
|
3676
|
+
}
|
|
2783
3677
|
}
|
|
2784
3678
|
this.canvas.update();
|
|
2785
3679
|
}
|
|
3680
|
+
/** Add a node */
|
|
2786
3681
|
async addNode(node) {
|
|
2787
3682
|
await this.update((update) => update.addNode(node));
|
|
2788
3683
|
}
|
|
3684
|
+
/** Delete a node */
|
|
2789
3685
|
async deleteNode(node) {
|
|
2790
3686
|
await this.update((update) => update.deleteNode(node));
|
|
2791
3687
|
}
|
|
3688
|
+
/** Update a node */
|
|
2792
3689
|
async updateNode(node) {
|
|
2793
3690
|
await this.update((update) => update.updateNode(node));
|
|
2794
3691
|
}
|
|
3692
|
+
/** Add an edge */
|
|
2795
3693
|
async addEdge(edge) {
|
|
2796
3694
|
await this.update((update) => update.addEdge(edge));
|
|
2797
3695
|
}
|
|
3696
|
+
/** Delete an edge */
|
|
2798
3697
|
async deleteEdge(edge) {
|
|
2799
3698
|
await this.update((update) => update.deleteEdge(edge));
|
|
2800
3699
|
}
|
|
3700
|
+
/** Update an edge */
|
|
3701
|
+
async updateEdge(edge) {
|
|
3702
|
+
await this.update((update) => update.updateEdge(edge));
|
|
3703
|
+
}
|
|
3704
|
+
/** Perform a batch of updates */
|
|
3705
|
+
async update(callback) {
|
|
3706
|
+
const updater = new Updater();
|
|
3707
|
+
callback(updater);
|
|
3708
|
+
await this.applyUpdate(updater.update);
|
|
3709
|
+
}
|
|
3710
|
+
/** Rebuild the graph from scratch (removes all then re-adds all nodes/edges) */
|
|
3711
|
+
async rebuild() {
|
|
3712
|
+
const nodes = [...this.nodeIds.keys()];
|
|
3713
|
+
const edges = [...this.edgeIds.keys()];
|
|
3714
|
+
await this.update((updater) => {
|
|
3715
|
+
for (const edge of edges) updater.deleteEdge(edge);
|
|
3716
|
+
for (const node of nodes) updater.deleteNode(node);
|
|
3717
|
+
for (const node of nodes) updater.addNode(node);
|
|
3718
|
+
for (const edge of edges) updater.addEdge(edge);
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
2801
3721
|
async applyUpdate(update) {
|
|
2802
3722
|
log11.info("applyUpdate", update);
|
|
2803
3723
|
const nodes = await this.measureNodes(update);
|
|
@@ -2814,12 +3734,16 @@ var API = class {
|
|
|
2814
3734
|
this._addEdge(edge, mut);
|
|
2815
3735
|
for (const edge of update.updateEdges ?? [])
|
|
2816
3736
|
this._updateEdge(edge, mut);
|
|
3737
|
+
this.nodeOverrides.clear();
|
|
3738
|
+
this.edgeOverrides.clear();
|
|
2817
3739
|
});
|
|
2818
3740
|
this.state = { graph: graph2, update };
|
|
2819
3741
|
this.setNodePositions();
|
|
2820
3742
|
this.seq.splice(this.index + 1);
|
|
2821
3743
|
this.seq.push(this.state);
|
|
2822
3744
|
this.nav("last");
|
|
3745
|
+
if (this.events.historyChange)
|
|
3746
|
+
this.events.historyChange(this.index, this.seq.length);
|
|
2823
3747
|
}
|
|
2824
3748
|
setNodePositions() {
|
|
2825
3749
|
const { graph: graph2 } = this.state;
|
|
@@ -2829,11 +3753,6 @@ var API = class {
|
|
|
2829
3753
|
this.canvas.getNode(node.key).setPos(node.pos);
|
|
2830
3754
|
}
|
|
2831
3755
|
}
|
|
2832
|
-
async update(callback) {
|
|
2833
|
-
const updater = new Updater();
|
|
2834
|
-
callback(updater);
|
|
2835
|
-
await this.applyUpdate(updater.update);
|
|
2836
|
-
}
|
|
2837
3756
|
async measureNodes(update) {
|
|
2838
3757
|
const data = [];
|
|
2839
3758
|
for (const set of [update.updateNodes, update.addNodes])
|
|
@@ -2848,7 +3767,10 @@ var API = class {
|
|
|
2848
3767
|
else if (!data) throw new Error(`invalid node ${data}`);
|
|
2849
3768
|
else if (typeof data == "string") props = { id: data };
|
|
2850
3769
|
else if (typeof data == "object") props = data;
|
|
2851
|
-
else throw new Error(`invalid node ${data}`);
|
|
3770
|
+
else throw new Error(`invalid node ${JSON.stringify(data)}`);
|
|
3771
|
+
this.detectNodeFields(data);
|
|
3772
|
+
const overrides = this.nodeOverrides.get(data);
|
|
3773
|
+
if (overrides) props = { ...props, ...overrides };
|
|
2852
3774
|
let { id, title, text, type, render } = props;
|
|
2853
3775
|
id ??= this.getNodeId(data);
|
|
2854
3776
|
const ports = this.parsePorts(props.ports);
|
|
@@ -2858,6 +3780,21 @@ var API = class {
|
|
|
2858
3780
|
this.nodeVersions.set(data, version);
|
|
2859
3781
|
return { id, data, ports, title, text, type, render, version };
|
|
2860
3782
|
}
|
|
3783
|
+
detectNodeFields(data) {
|
|
3784
|
+
if (typeof data != "object" || !data) return;
|
|
3785
|
+
const skip = /* @__PURE__ */ new Set(["id", "ports", "render", "version"]);
|
|
3786
|
+
for (const [key, value] of Object.entries(data)) {
|
|
3787
|
+
if (skip.has(key)) continue;
|
|
3788
|
+
if (value === null || value === void 0) continue;
|
|
3789
|
+
const type = typeof value;
|
|
3790
|
+
if (type === "string" || type === "number" || type === "boolean") {
|
|
3791
|
+
this.nodeFields.set(key, type);
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
getNodeFields() {
|
|
3796
|
+
return this.nodeFields;
|
|
3797
|
+
}
|
|
2861
3798
|
parseEdge(data) {
|
|
2862
3799
|
const get = this.options.props.edge;
|
|
2863
3800
|
let props;
|
|
@@ -2866,8 +3803,10 @@ var API = class {
|
|
|
2866
3803
|
else if (typeof data == "string") props = this.parseStringEdge(data);
|
|
2867
3804
|
else if (typeof data == "object") props = data;
|
|
2868
3805
|
else throw new Error(`invalid edge ${data}`);
|
|
3806
|
+
const overrides = this.edgeOverrides.get(data);
|
|
3807
|
+
if (overrides) props = { ...props, ...overrides };
|
|
2869
3808
|
let { id, source, target, type } = props;
|
|
2870
|
-
id
|
|
3809
|
+
if (!id) id = this.getEdgeId(data);
|
|
2871
3810
|
source = this.parseEdgeEnd(source);
|
|
2872
3811
|
target = this.parseEdgeEnd(target);
|
|
2873
3812
|
const edge = { id, source, target, type, data };
|
|
@@ -2880,14 +3819,14 @@ var API = class {
|
|
|
2880
3819
|
const keys = Object.keys(end);
|
|
2881
3820
|
const pidx = keys.indexOf("port");
|
|
2882
3821
|
if (pidx != -1) {
|
|
2883
|
-
if (typeof end.port != "string") return end;
|
|
3822
|
+
if (end.port !== void 0 && typeof end.port != "string") return end;
|
|
2884
3823
|
keys.splice(pidx, 1);
|
|
2885
3824
|
}
|
|
2886
3825
|
if (keys.length != 1) return end;
|
|
2887
3826
|
if (keys[0] == "id") return end;
|
|
2888
3827
|
if (keys[0] != "node") return end;
|
|
2889
3828
|
const id = this.nodeIds.get(end.node);
|
|
2890
|
-
if (!id) throw new Error(`edge end
|
|
3829
|
+
if (!id) throw new Error(`edge end references unknown node ${end.node}`);
|
|
2891
3830
|
return { id, port: end.port };
|
|
2892
3831
|
}
|
|
2893
3832
|
throw new Error(`invalid edge end ${end}`);
|
|
@@ -2897,13 +3836,19 @@ var API = class {
|
|
|
2897
3836
|
return { source, target };
|
|
2898
3837
|
}
|
|
2899
3838
|
parsePorts(ports) {
|
|
2900
|
-
const fixed = {
|
|
3839
|
+
const fixed = {};
|
|
2901
3840
|
for (const key of ["in", "out"]) {
|
|
2902
3841
|
if (ports?.[key] && ports[key].length > 0)
|
|
2903
3842
|
fixed[key] = ports[key].map((port) => typeof port == "string" ? { id: port } : port);
|
|
2904
3843
|
}
|
|
2905
3844
|
return fixed;
|
|
2906
3845
|
}
|
|
3846
|
+
getNode(id) {
|
|
3847
|
+
return this.graph.getNode(id);
|
|
3848
|
+
}
|
|
3849
|
+
getEdge(id) {
|
|
3850
|
+
return this.graph.getEdge(id);
|
|
3851
|
+
}
|
|
2907
3852
|
getNodeId(node) {
|
|
2908
3853
|
let id = this.nodeIds.get(node);
|
|
2909
3854
|
if (!id) {
|
|
@@ -2923,7 +3868,6 @@ var API = class {
|
|
|
2923
3868
|
_addNode(node, mut) {
|
|
2924
3869
|
const { data, id: newId } = node.data;
|
|
2925
3870
|
const oldId = this.nodeIds.get(data);
|
|
2926
|
-
console.log("addNode", node, oldId, newId);
|
|
2927
3871
|
if (oldId && oldId != newId)
|
|
2928
3872
|
throw new Error(`node id of ${data} changed from ${oldId} to ${newId}`);
|
|
2929
3873
|
this.nodeIds.set(data, newId);
|
|
@@ -2931,36 +3875,169 @@ var API = class {
|
|
|
2931
3875
|
}
|
|
2932
3876
|
_removeNode(node, mut) {
|
|
2933
3877
|
const id = this.nodeIds.get(node);
|
|
2934
|
-
if (!id) throw new Error(`removing node ${node} which does not exist`);
|
|
3878
|
+
if (!id) throw new Error(`removing node ${JSON.stringify(node)} which does not exist`);
|
|
2935
3879
|
mut.removeNode({ id });
|
|
2936
3880
|
}
|
|
2937
3881
|
_updateNode(node, mut) {
|
|
2938
3882
|
const { data, id: newId } = node.data;
|
|
2939
3883
|
const oldId = this.nodeIds.get(data);
|
|
2940
|
-
if (!oldId) throw new Error(`updating unknown node ${node}`);
|
|
2941
|
-
if (oldId != newId) throw new Error(`node id changed from ${oldId} to ${newId}`);
|
|
3884
|
+
if (!oldId) throw new Error(`updating unknown node ${JSON.stringify(node)} `);
|
|
3885
|
+
if (oldId != newId) throw new Error(`node id changed from ${oldId} to ${newId} `);
|
|
2942
3886
|
mut.updateNode(node.data);
|
|
2943
3887
|
}
|
|
2944
3888
|
_addEdge(edge, mut) {
|
|
2945
3889
|
const data = this.parseEdge(edge);
|
|
2946
3890
|
const id = this.edgeIds.get(edge);
|
|
2947
3891
|
if (id && id != data.id)
|
|
2948
|
-
throw new Error(`edge id changed from ${id} to ${data.id}`);
|
|
3892
|
+
throw new Error(`edge id changed from ${id} to ${data.id} `);
|
|
2949
3893
|
this.edgeIds.set(edge, data.id);
|
|
2950
3894
|
mut.addEdge(data);
|
|
2951
3895
|
}
|
|
2952
3896
|
_removeEdge(edge, mut) {
|
|
2953
3897
|
const id = this.edgeIds.get(edge);
|
|
2954
|
-
if (!id) throw new Error(`removing edge ${edge} which does not exist`);
|
|
3898
|
+
if (!id) throw new Error(`removing edge ${JSON.stringify(edge)} which does not exist`);
|
|
2955
3899
|
mut.removeEdge(this.parseEdge(edge));
|
|
2956
3900
|
}
|
|
2957
3901
|
_updateEdge(edge, mut) {
|
|
2958
3902
|
const id = this.edgeIds.get(edge);
|
|
2959
|
-
if (!id) throw new Error(`updating unknown edge ${edge}`);
|
|
3903
|
+
if (!id) throw new Error(`updating unknown edge ${JSON.stringify(edge)} `);
|
|
2960
3904
|
const data = this.parseEdge(edge);
|
|
2961
|
-
if (data.id !== id) throw new Error(`edge id changed from ${id} to ${data.id}`);
|
|
3905
|
+
if (data.id !== id) throw new Error(`edge id changed from ${id} to ${data.id} `);
|
|
2962
3906
|
mut.updateEdge(data);
|
|
2963
3907
|
}
|
|
3908
|
+
// Event Handlers
|
|
3909
|
+
handleClickNode(id) {
|
|
3910
|
+
const handler = this.events.nodeClick;
|
|
3911
|
+
const node = this.graph.getNode(id);
|
|
3912
|
+
if (handler) handler(node.data);
|
|
3913
|
+
}
|
|
3914
|
+
handleClickEdge(id) {
|
|
3915
|
+
const handler = this.events.edgeClick;
|
|
3916
|
+
if (!handler) return;
|
|
3917
|
+
const seg = this.graph.getSeg(id);
|
|
3918
|
+
if (seg.edgeIds.size != 1) return;
|
|
3919
|
+
const edge = this.graph.getEdge(seg.edgeIds.values().next().value);
|
|
3920
|
+
handler(edge.data);
|
|
3921
|
+
}
|
|
3922
|
+
async handleNewNode() {
|
|
3923
|
+
const gotNode = async (node) => {
|
|
3924
|
+
await this.addNode(node);
|
|
3925
|
+
};
|
|
3926
|
+
if (this.events.newNode)
|
|
3927
|
+
this.events.newNode(gotNode);
|
|
3928
|
+
else
|
|
3929
|
+
this.canvas.showNewNodeModal(async (data) => {
|
|
3930
|
+
if (this.events.addNode)
|
|
3931
|
+
this.events.addNode(data, gotNode);
|
|
3932
|
+
else
|
|
3933
|
+
await gotNode(data);
|
|
3934
|
+
});
|
|
3935
|
+
}
|
|
3936
|
+
async handleNewNodeFrom(source) {
|
|
3937
|
+
const gotNode = async (node) => {
|
|
3938
|
+
const gotEdge = async (edge) => {
|
|
3939
|
+
await this.update((u) => {
|
|
3940
|
+
u.addNode(node).addEdge(edge);
|
|
3941
|
+
});
|
|
3942
|
+
};
|
|
3943
|
+
const data = this.graph.getNode(source.id).data;
|
|
3944
|
+
const newEdge = {
|
|
3945
|
+
source: { node: data, port: source.port },
|
|
3946
|
+
target: { node }
|
|
3947
|
+
};
|
|
3948
|
+
if (this.events.addEdge)
|
|
3949
|
+
this.events.addEdge(newEdge, gotEdge);
|
|
3950
|
+
else
|
|
3951
|
+
await gotEdge(newEdge);
|
|
3952
|
+
};
|
|
3953
|
+
if (this.events.newNode)
|
|
3954
|
+
this.events.newNode(gotNode);
|
|
3955
|
+
else
|
|
3956
|
+
this.canvas.showNewNodeModal(async (data) => {
|
|
3957
|
+
if (this.events.addNode)
|
|
3958
|
+
this.events.addNode(data, gotNode);
|
|
3959
|
+
else
|
|
3960
|
+
await gotNode(data);
|
|
3961
|
+
});
|
|
3962
|
+
}
|
|
3963
|
+
async handleEditNode(id) {
|
|
3964
|
+
const node = this.graph.getNode(id);
|
|
3965
|
+
const gotNode = async (node2) => {
|
|
3966
|
+
if (node2) await this.updateNode(node2);
|
|
3967
|
+
};
|
|
3968
|
+
if (this.events.editNode)
|
|
3969
|
+
this.events.editNode(node.data, gotNode);
|
|
3970
|
+
else {
|
|
3971
|
+
this.canvas.showEditNodeModal(node, async (data) => {
|
|
3972
|
+
if (this.events.updateNode)
|
|
3973
|
+
this.events.updateNode(node.data, data, gotNode);
|
|
3974
|
+
else {
|
|
3975
|
+
this.nodeOverrides.set(node.data, data);
|
|
3976
|
+
await gotNode(node.data);
|
|
3977
|
+
}
|
|
3978
|
+
});
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
async handleEditEdge(id) {
|
|
3982
|
+
const seg = this.graph.getSeg(id);
|
|
3983
|
+
if (seg.edgeIds.size != 1) return;
|
|
3984
|
+
const edge = this.graph.getEdge(seg.edgeIds.values().next().value);
|
|
3985
|
+
const gotEdge = async (edge2) => {
|
|
3986
|
+
if (edge2) await this.updateEdge(edge2);
|
|
3987
|
+
};
|
|
3988
|
+
if (this.events.editEdge)
|
|
3989
|
+
this.events.editEdge(edge.data, gotEdge);
|
|
3990
|
+
else
|
|
3991
|
+
this.canvas.showEditEdgeModal(edge, async (data) => {
|
|
3992
|
+
const sourceNode = edge.sourceNode(this.graph);
|
|
3993
|
+
const targetNode = edge.targetNode(this.graph);
|
|
3994
|
+
const update = {
|
|
3995
|
+
source: { node: sourceNode.data, port: data.source.port, marker: data.source.marker },
|
|
3996
|
+
target: { node: targetNode.data, port: data.target.port, marker: data.target.marker }
|
|
3997
|
+
};
|
|
3998
|
+
if (this.events.updateEdge)
|
|
3999
|
+
this.events.updateEdge(edge.data, update, gotEdge);
|
|
4000
|
+
else {
|
|
4001
|
+
this.edgeOverrides.set(edge.data, {
|
|
4002
|
+
source: { id: sourceNode.id, port: data.source.port, marker: data.source.marker },
|
|
4003
|
+
target: { id: targetNode.id, port: data.target.port, marker: data.target.marker },
|
|
4004
|
+
type: data.type
|
|
4005
|
+
});
|
|
4006
|
+
await gotEdge(edge.data);
|
|
4007
|
+
}
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
async handleAddEdge(data) {
|
|
4011
|
+
const gotEdge = async (edge) => {
|
|
4012
|
+
if (edge) await this.addEdge(edge);
|
|
4013
|
+
};
|
|
4014
|
+
const newEdge = {
|
|
4015
|
+
source: { node: this.graph.getNode(data.source.id).data, port: data.source.port, marker: data.source.marker },
|
|
4016
|
+
target: { node: this.graph.getNode(data.target.id).data, port: data.target.port, marker: data.target.marker }
|
|
4017
|
+
};
|
|
4018
|
+
if (this.events.addEdge)
|
|
4019
|
+
this.events.addEdge(newEdge, gotEdge);
|
|
4020
|
+
else
|
|
4021
|
+
await gotEdge(data);
|
|
4022
|
+
}
|
|
4023
|
+
async handleDeleteNode(id) {
|
|
4024
|
+
const node = this.getNode(id);
|
|
4025
|
+
if (this.events.removeNode)
|
|
4026
|
+
this.events.removeNode(node.data, async (remove) => {
|
|
4027
|
+
if (remove) await this.deleteNode(node.data);
|
|
4028
|
+
});
|
|
4029
|
+
else
|
|
4030
|
+
await this.deleteNode(node.data);
|
|
4031
|
+
}
|
|
4032
|
+
async handleDeleteEdge(id) {
|
|
4033
|
+
const edge = this.getEdge(id);
|
|
4034
|
+
if (this.events.removeEdge)
|
|
4035
|
+
this.events.removeEdge(edge.data, async (remove) => {
|
|
4036
|
+
if (remove) await this.deleteEdge(edge.data);
|
|
4037
|
+
});
|
|
4038
|
+
else
|
|
4039
|
+
await this.deleteEdge(edge.data);
|
|
4040
|
+
}
|
|
2964
4041
|
};
|
|
2965
4042
|
|
|
2966
4043
|
// src/index.ts
|