@3plate/graph-core 0.1.2 → 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 +2378 -812
- package/dist/index.d.cts +368 -70
- package/dist/index.d.ts +368 -70
- package/dist/index.js +2370 -814
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,20 +1,84 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import { jsx } from "jsx-dom/jsx-runtime";
|
|
3
|
-
function renderNode(node) {
|
|
4
|
-
return /* @__PURE__ */ jsx("div", { children: node?.id || "" });
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// src/graph/types/graph.ts
|
|
1
|
+
// src/graph/graph.ts
|
|
8
2
|
import { Map as IMap, List as IList, Set as ISet6 } from "immutable";
|
|
9
3
|
|
|
10
|
-
// src/graph/
|
|
4
|
+
// src/graph/node.ts
|
|
11
5
|
import { Record, Set as ISet } from "immutable";
|
|
12
|
-
|
|
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");
|
|
70
|
+
var defNodeData = {
|
|
13
71
|
id: "",
|
|
72
|
+
data: void 0,
|
|
73
|
+
version: 0,
|
|
74
|
+
title: void 0,
|
|
75
|
+
text: void 0,
|
|
76
|
+
type: void 0,
|
|
77
|
+
render: void 0,
|
|
78
|
+
ports: {},
|
|
14
79
|
aligned: {},
|
|
15
80
|
edges: { in: ISet(), out: ISet() },
|
|
16
81
|
segs: { in: ISet(), out: ISet() },
|
|
17
|
-
ports: { in: [], out: [] },
|
|
18
82
|
layerId: "",
|
|
19
83
|
isDummy: false,
|
|
20
84
|
isMerged: false,
|
|
@@ -25,44 +89,49 @@ var defaultNodeProps = {
|
|
|
25
89
|
dims: void 0,
|
|
26
90
|
mutable: false
|
|
27
91
|
};
|
|
28
|
-
var Node = class _Node extends Record(
|
|
92
|
+
var Node = class _Node extends Record(defNodeData) {
|
|
29
93
|
static dummyPrefix = "d:";
|
|
30
|
-
get edgeId() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
get edgeIds() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
static
|
|
94
|
+
// get edgeId(): EdgeId {
|
|
95
|
+
// if (!this.isDummy)
|
|
96
|
+
// throw new Error(`node ${this.id} is not a dummy`)
|
|
97
|
+
// if (this.isMerged)
|
|
98
|
+
// throw new Error(`node ${this.id} is merged`)
|
|
99
|
+
// return this.get('edgeIds')[0]
|
|
100
|
+
// }
|
|
101
|
+
// get edgeIds(): EdgeId[] {
|
|
102
|
+
// if (!this.isDummy)
|
|
103
|
+
// throw new Error(`node ${this.id} is not a dummy`)
|
|
104
|
+
// if (!this.isMerged)
|
|
105
|
+
// throw new Error(`node ${this.id} is not merged`)
|
|
106
|
+
// return this.get('edgeIds')
|
|
107
|
+
// }
|
|
108
|
+
get key() {
|
|
109
|
+
return this.isDummy ? this.id : _Node.key(this);
|
|
110
|
+
}
|
|
111
|
+
static key(node) {
|
|
112
|
+
return `k:${node.id}:${node.version}`;
|
|
113
|
+
}
|
|
114
|
+
static addNormal(g, data) {
|
|
48
115
|
const layer = g.layerAt(0);
|
|
49
116
|
const node = new _Node({
|
|
50
|
-
...
|
|
117
|
+
...data,
|
|
51
118
|
edges: { in: ISet(), out: ISet() },
|
|
52
119
|
segs: { in: ISet(), out: ISet() },
|
|
53
120
|
aligned: {},
|
|
54
121
|
edgeIds: [],
|
|
55
|
-
layerId: layer.id
|
|
122
|
+
layerId: layer.id,
|
|
123
|
+
lpos: void 0,
|
|
124
|
+
pos: void 0
|
|
56
125
|
});
|
|
57
126
|
layer.addNode(g, node.id);
|
|
58
127
|
g.nodes.set(node.id, node);
|
|
59
128
|
g.dirtyNodes.add(node.id);
|
|
60
129
|
return node;
|
|
61
130
|
}
|
|
62
|
-
static addDummy(g,
|
|
63
|
-
const layer = g.getLayer(
|
|
131
|
+
static addDummy(g, data) {
|
|
132
|
+
const layer = g.getLayer(data.layerId);
|
|
64
133
|
const node = new _Node({
|
|
65
|
-
...
|
|
134
|
+
...data,
|
|
66
135
|
id: `${_Node.dummyPrefix}${g.nextDummyId++}`,
|
|
67
136
|
edges: { in: ISet(), out: ISet() },
|
|
68
137
|
segs: { in: ISet(), out: ISet() },
|
|
@@ -78,6 +147,12 @@ var Node = class _Node extends Record(defaultNodeProps) {
|
|
|
78
147
|
g.dirtyNodes.add(node.id);
|
|
79
148
|
return node;
|
|
80
149
|
}
|
|
150
|
+
static del(g, node) {
|
|
151
|
+
return g.getNode(node.id).delSelf(g);
|
|
152
|
+
}
|
|
153
|
+
static update(g, data) {
|
|
154
|
+
return g.getNode(data.id).mut(g).merge(data);
|
|
155
|
+
}
|
|
81
156
|
mut(g) {
|
|
82
157
|
if (this.mutable) return this;
|
|
83
158
|
return g.mutateNode(this);
|
|
@@ -100,19 +175,32 @@ var Node = class _Node extends Record(defaultNodeProps) {
|
|
|
100
175
|
isUnlinked() {
|
|
101
176
|
return this.edges.in.size == 0 && this.edges.out.size == 0 && this.segs.in.size == 0 && this.segs.out.size == 0;
|
|
102
177
|
}
|
|
178
|
+
hasPorts() {
|
|
179
|
+
return !!this.ports?.in?.length || !!this.ports?.out?.length;
|
|
180
|
+
}
|
|
103
181
|
layerIndex(g) {
|
|
104
182
|
return this.getLayer(g).index;
|
|
105
183
|
}
|
|
106
184
|
getLayer(g) {
|
|
107
185
|
return g.getLayer(this.layerId);
|
|
108
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
|
+
}
|
|
109
199
|
setIndex(g, index) {
|
|
110
200
|
if (this.index == index) return this;
|
|
111
|
-
console.log(`set index of ${this.id} to ${index}`);
|
|
112
201
|
return this.mut(g).set("index", index);
|
|
113
202
|
}
|
|
114
203
|
setLayerPos(g, lpos) {
|
|
115
|
-
console.log("setLayerPos", this.id, lpos);
|
|
116
204
|
if (this.lpos == lpos) return this;
|
|
117
205
|
return this.mut(g).set("lpos", lpos);
|
|
118
206
|
}
|
|
@@ -126,7 +214,6 @@ var Node = class _Node extends Record(defaultNodeProps) {
|
|
|
126
214
|
return this;
|
|
127
215
|
}
|
|
128
216
|
moveToLayer(g, layer) {
|
|
129
|
-
console.log("moveToLayer", this, this.getLayer(g), layer);
|
|
130
217
|
this.getLayer(g).delNode(g, this.id);
|
|
131
218
|
layer.addNode(g, this.id);
|
|
132
219
|
return this.setLayer(g, layer.id);
|
|
@@ -155,9 +242,22 @@ var Node = class _Node extends Record(defaultNodeProps) {
|
|
|
155
242
|
return node;
|
|
156
243
|
}
|
|
157
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);
|
|
158
249
|
this.getLayer(g).delNode(g, this.id);
|
|
159
|
-
for (const
|
|
160
|
-
|
|
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
|
+
}
|
|
161
261
|
g.nodes.delete(this.id);
|
|
162
262
|
g.dirtyNodes.delete(this.id);
|
|
163
263
|
g.delNodes.add(this.id);
|
|
@@ -248,16 +348,20 @@ var Node = class _Node extends Record(defaultNodeProps) {
|
|
|
248
348
|
}
|
|
249
349
|
};
|
|
250
350
|
|
|
251
|
-
// src/graph/
|
|
351
|
+
// src/graph/edge.ts
|
|
252
352
|
import { Record as Record2 } from "immutable";
|
|
253
|
-
var
|
|
353
|
+
var log3 = logger("edge");
|
|
354
|
+
var defEdgeData = {
|
|
254
355
|
id: "",
|
|
356
|
+
data: null,
|
|
357
|
+
label: void 0,
|
|
255
358
|
source: { id: "" },
|
|
256
359
|
target: { id: "" },
|
|
360
|
+
type: void 0,
|
|
257
361
|
mutable: false,
|
|
258
362
|
segIds: []
|
|
259
363
|
};
|
|
260
|
-
var Edge = class _Edge extends Record2(
|
|
364
|
+
var Edge = class _Edge extends Record2(defEdgeData) {
|
|
261
365
|
static prefix = "e:";
|
|
262
366
|
mut(g) {
|
|
263
367
|
if (this.mutable) return this;
|
|
@@ -315,37 +419,44 @@ var Edge = class _Edge extends Record2(defaultEdgeProps) {
|
|
|
315
419
|
return _Edge.str(this);
|
|
316
420
|
}
|
|
317
421
|
static str(edge) {
|
|
318
|
-
let source = edge.source
|
|
319
|
-
if (edge
|
|
422
|
+
let source = edge.source?.id;
|
|
423
|
+
if (!source) throw new Error("edge source is undefined");
|
|
424
|
+
if (edge.source?.port)
|
|
320
425
|
source = `${source} (port ${edge.source.port})`;
|
|
321
|
-
let target = edge.target
|
|
322
|
-
if (edge
|
|
426
|
+
let target = edge.target?.id;
|
|
427
|
+
if (!target) throw new Error("edge target is undefined");
|
|
428
|
+
if (edge.target?.port)
|
|
323
429
|
target = `${target} (port ${edge.target.port})`;
|
|
324
430
|
let str = `edge from ${source} to ${target}`;
|
|
325
431
|
if (edge.type) str += ` of type ${edge.type}`;
|
|
326
432
|
return str;
|
|
327
433
|
}
|
|
328
|
-
static
|
|
434
|
+
static key(edge, prefix = _Edge.prefix, side = "both") {
|
|
329
435
|
let source = "", target = "";
|
|
330
436
|
if (side == "source" || side == "both") {
|
|
437
|
+
if (!edge.source?.id) throw new Error("edge source is undefined");
|
|
331
438
|
source = edge.source.id;
|
|
332
|
-
if (edge.source
|
|
439
|
+
if (edge.source?.port)
|
|
333
440
|
source = `${source}.${edge.source.port}`;
|
|
441
|
+
const marker = edge.source?.marker;
|
|
442
|
+
if (marker && marker != "none") source += `[${marker}]`;
|
|
334
443
|
source += "-";
|
|
335
444
|
}
|
|
336
445
|
if (side == "target" || side == "both") {
|
|
446
|
+
if (!edge.target?.id) throw new Error("edge target is undefined");
|
|
337
447
|
target = edge.target.id;
|
|
338
448
|
if (edge.target.port)
|
|
339
449
|
target = `${target}.${edge.target.port}`;
|
|
340
450
|
target = "-" + target;
|
|
451
|
+
const marker = edge.target?.marker ?? "arrow";
|
|
452
|
+
if (marker && marker != "none") target += `[${marker}]`;
|
|
341
453
|
}
|
|
342
454
|
const type = edge.type || "";
|
|
343
455
|
return `${prefix}${source}${type}${target}`;
|
|
344
456
|
}
|
|
345
|
-
static add(g,
|
|
457
|
+
static add(g, data) {
|
|
346
458
|
const edge = new _Edge({
|
|
347
|
-
...
|
|
348
|
-
id: _Edge.id(props),
|
|
459
|
+
...data,
|
|
349
460
|
segIds: []
|
|
350
461
|
});
|
|
351
462
|
edge.link(g);
|
|
@@ -353,18 +464,38 @@ var Edge = class _Edge extends Record2(defaultEdgeProps) {
|
|
|
353
464
|
g.dirtyEdges.add(edge.id);
|
|
354
465
|
return edge;
|
|
355
466
|
}
|
|
467
|
+
static del(g, data) {
|
|
468
|
+
return g.getEdge(data.id).delSelf(g);
|
|
469
|
+
}
|
|
470
|
+
static update(g, data) {
|
|
471
|
+
let edge = g.getEdge(data.id);
|
|
472
|
+
let relink = false;
|
|
473
|
+
if (data.source.id !== edge.source.id || data.target.id !== edge.target.id || data.source.port !== edge.source.port || data.target.port !== edge.target.port || data.type !== edge.type) {
|
|
474
|
+
for (const seg of edge.segs(g))
|
|
475
|
+
seg.delEdgeId(g, edge.id);
|
|
476
|
+
edge.unlink(g);
|
|
477
|
+
relink = true;
|
|
478
|
+
}
|
|
479
|
+
edge = edge.mut(g).merge(data);
|
|
480
|
+
if (relink)
|
|
481
|
+
edge.link(g);
|
|
482
|
+
return edge;
|
|
483
|
+
}
|
|
356
484
|
};
|
|
357
485
|
|
|
358
|
-
// src/graph/
|
|
486
|
+
// src/graph/seg.ts
|
|
359
487
|
import { Record as Record3, Set as ISet2 } from "immutable";
|
|
360
|
-
var
|
|
488
|
+
var defSegData = {
|
|
361
489
|
id: "",
|
|
362
490
|
source: { id: "" },
|
|
363
491
|
target: { id: "" },
|
|
492
|
+
type: void 0,
|
|
364
493
|
edgeIds: ISet2(),
|
|
494
|
+
trackPos: void 0,
|
|
495
|
+
svg: void 0,
|
|
365
496
|
mutable: false
|
|
366
497
|
};
|
|
367
|
-
var Seg = class _Seg extends Record3(
|
|
498
|
+
var Seg = class _Seg extends Record3(defSegData) {
|
|
368
499
|
static prefix = "s:";
|
|
369
500
|
mut(g) {
|
|
370
501
|
if (this.mutable) return this;
|
|
@@ -389,7 +520,7 @@ var Seg = class _Seg extends Record3(defaultSegProps) {
|
|
|
389
520
|
sameEnd(other, side) {
|
|
390
521
|
const mine = this[side];
|
|
391
522
|
const yours = other[side];
|
|
392
|
-
return mine.id === yours.id && mine.port === yours.port;
|
|
523
|
+
return mine.id === yours.id && mine.port === yours.port && mine.marker === yours.marker && this.type === other.type;
|
|
393
524
|
}
|
|
394
525
|
setPos(g, source, target) {
|
|
395
526
|
return this.mut(g).merge({
|
|
@@ -447,10 +578,10 @@ var Seg = class _Seg extends Record3(defaultSegProps) {
|
|
|
447
578
|
}
|
|
448
579
|
return this.mut(g).set("edgeIds", this.edgeIds.asMutable().remove(edgeId));
|
|
449
580
|
}
|
|
450
|
-
static add(g,
|
|
581
|
+
static add(g, data) {
|
|
451
582
|
const seg = new _Seg({
|
|
452
|
-
...
|
|
453
|
-
id: Edge.
|
|
583
|
+
...data,
|
|
584
|
+
id: Edge.key(data, _Seg.prefix)
|
|
454
585
|
});
|
|
455
586
|
seg.link(g);
|
|
456
587
|
g.segs.set(seg.id, seg);
|
|
@@ -459,33 +590,10 @@ var Seg = class _Seg extends Record3(defaultSegProps) {
|
|
|
459
590
|
}
|
|
460
591
|
};
|
|
461
592
|
|
|
462
|
-
// src/graph/
|
|
593
|
+
// src/graph/layer.ts
|
|
463
594
|
import { Record as Record4, Set as ISet3 } from "immutable";
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
var levels = {
|
|
467
|
-
error: 0,
|
|
468
|
-
warn: 1,
|
|
469
|
-
info: 2,
|
|
470
|
-
debug: 3
|
|
471
|
-
};
|
|
472
|
-
var currentLevel = "debug";
|
|
473
|
-
function shouldLog(level) {
|
|
474
|
-
return levels[level] <= levels[currentLevel];
|
|
475
|
-
}
|
|
476
|
-
function logger(module) {
|
|
477
|
-
return {
|
|
478
|
-
error: (msg, ...args) => shouldLog("error") && console.error(`[${module}] ${msg}`, ...args),
|
|
479
|
-
warn: (msg, ...args) => shouldLog("warn") && console.warn(`[${module}] ${msg}`, ...args),
|
|
480
|
-
info: (msg, ...args) => shouldLog("info") && console.info(`[${module}] ${msg}`, ...args),
|
|
481
|
-
debug: (msg, ...args) => shouldLog("debug") && console.debug(`[${module}] ${msg}`, ...args)
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
var log = logger("core");
|
|
485
|
-
|
|
486
|
-
// src/graph/types/layer.ts
|
|
487
|
-
var log2 = logger("layer");
|
|
488
|
-
var defaultLayerProps = {
|
|
595
|
+
var log4 = logger("layer");
|
|
596
|
+
var defLayerData = {
|
|
489
597
|
id: "",
|
|
490
598
|
index: 0,
|
|
491
599
|
nodeIds: ISet3(),
|
|
@@ -496,7 +604,7 @@ var defaultLayerProps = {
|
|
|
496
604
|
isSorted: false,
|
|
497
605
|
mutable: false
|
|
498
606
|
};
|
|
499
|
-
var Layer = class extends Record4(
|
|
607
|
+
var Layer = class extends Record4(defLayerData) {
|
|
500
608
|
static prefix = "l:";
|
|
501
609
|
mut(g) {
|
|
502
610
|
if (this.mutable) return this;
|
|
@@ -509,7 +617,7 @@ var Layer = class extends Record4(defaultLayerProps) {
|
|
|
509
617
|
mutable: false
|
|
510
618
|
}).asImmutable();
|
|
511
619
|
}
|
|
512
|
-
get
|
|
620
|
+
get nodeCount() {
|
|
513
621
|
return this.nodeIds.size;
|
|
514
622
|
}
|
|
515
623
|
*nodes(g) {
|
|
@@ -564,9 +672,8 @@ var Layer = class extends Record4(defaultLayerProps) {
|
|
|
564
672
|
reindex(g, nodeId) {
|
|
565
673
|
if (!this.isSorted) return void 0;
|
|
566
674
|
const sorted = this.sorted.filter((id) => id != nodeId);
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
g.getNode(sorted[i]).setIndex(g, i);
|
|
675
|
+
for (const [i, id] of this.sorted.entries())
|
|
676
|
+
g.getNode(id).setIndex(g, i);
|
|
570
677
|
return sorted;
|
|
571
678
|
}
|
|
572
679
|
delNode(g, nodeId) {
|
|
@@ -578,7 +685,6 @@ var Layer = class extends Record4(defaultLayerProps) {
|
|
|
578
685
|
}
|
|
579
686
|
setSorted(g, nodeIds) {
|
|
580
687
|
if (this.hasSortOrder(nodeIds)) return this;
|
|
581
|
-
console.log(`setting sorted for layer ${this.id}`);
|
|
582
688
|
nodeIds.forEach((nodeId, i) => g.getNode(nodeId).setIndex(g, i));
|
|
583
689
|
return this.mut(g).merge({ sorted: nodeIds, isSorted: true });
|
|
584
690
|
}
|
|
@@ -588,7 +694,7 @@ var Layer = class extends Record4(defaultLayerProps) {
|
|
|
588
694
|
}
|
|
589
695
|
};
|
|
590
696
|
|
|
591
|
-
// src/graph/
|
|
697
|
+
// src/graph/mutator.ts
|
|
592
698
|
var Mutator = class {
|
|
593
699
|
changes;
|
|
594
700
|
constructor() {
|
|
@@ -597,20 +703,27 @@ var Mutator = class {
|
|
|
597
703
|
removedNodes: [],
|
|
598
704
|
updatedNodes: [],
|
|
599
705
|
addedEdges: [],
|
|
600
|
-
removedEdges: []
|
|
706
|
+
removedEdges: [],
|
|
707
|
+
updatedEdges: []
|
|
601
708
|
};
|
|
602
709
|
}
|
|
710
|
+
describe(description) {
|
|
711
|
+
this.changes.description = description;
|
|
712
|
+
}
|
|
603
713
|
addNode(node) {
|
|
604
714
|
this.changes.addedNodes.push(node);
|
|
605
715
|
}
|
|
606
716
|
addNodes(...nodes) {
|
|
607
717
|
nodes.forEach((node) => this.addNode(node));
|
|
608
718
|
}
|
|
719
|
+
removeNode(node) {
|
|
720
|
+
this.changes.removedNodes.push(node);
|
|
721
|
+
}
|
|
722
|
+
removeNodes(...nodes) {
|
|
723
|
+
nodes.forEach((node) => this.removeNode(node));
|
|
724
|
+
}
|
|
609
725
|
updateNode(node) {
|
|
610
|
-
|
|
611
|
-
this.changes.updatedNodes.push({ id: node });
|
|
612
|
-
else
|
|
613
|
-
this.changes.updatedNodes.push(node);
|
|
726
|
+
this.changes.updatedNodes.push(node);
|
|
614
727
|
}
|
|
615
728
|
updateNodes(...nodes) {
|
|
616
729
|
nodes.forEach((node) => this.updateNode(node));
|
|
@@ -621,21 +734,18 @@ var Mutator = class {
|
|
|
621
734
|
addEdges(...edges) {
|
|
622
735
|
edges.forEach((edge) => this.addEdge(edge));
|
|
623
736
|
}
|
|
624
|
-
removeNode(node) {
|
|
625
|
-
if (typeof node === "string")
|
|
626
|
-
this.changes.removedNodes.push({ id: node });
|
|
627
|
-
else
|
|
628
|
-
this.changes.removedNodes.push(node);
|
|
629
|
-
}
|
|
630
|
-
removeNodes(...nodes) {
|
|
631
|
-
nodes.forEach((node) => this.removeNode(node));
|
|
632
|
-
}
|
|
633
737
|
removeEdge(edge) {
|
|
634
738
|
this.changes.removedEdges.push(edge);
|
|
635
739
|
}
|
|
636
740
|
removeEdges(...edges) {
|
|
637
741
|
edges.forEach((edge) => this.removeEdge(edge));
|
|
638
742
|
}
|
|
743
|
+
updateEdge(edge) {
|
|
744
|
+
this.changes.updatedEdges.push(edge);
|
|
745
|
+
}
|
|
746
|
+
updateEdges(...edges) {
|
|
747
|
+
edges.forEach((edge) => this.updateEdge(edge));
|
|
748
|
+
}
|
|
639
749
|
};
|
|
640
750
|
|
|
641
751
|
// src/graph/services/cycles.ts
|
|
@@ -735,12 +845,10 @@ var Cycles = class _Cycles {
|
|
|
735
845
|
|
|
736
846
|
// src/graph/services/dummy.ts
|
|
737
847
|
import { Set as ISet4 } from "immutable";
|
|
738
|
-
var
|
|
848
|
+
var log5 = logger("dummy");
|
|
739
849
|
var Dummy = class _Dummy {
|
|
740
850
|
static updateDummies(g) {
|
|
741
|
-
log3.debug(`updating dummies:`, [...g.dirtyEdges]);
|
|
742
851
|
for (const edgeId of g.dirtyEdges) {
|
|
743
|
-
log3.debug(`updating dummies of edge ${edgeId}`);
|
|
744
852
|
const edge = g.getEdge(edgeId);
|
|
745
853
|
const { type } = edge;
|
|
746
854
|
const sourceLayer = edge.sourceNode(g).layerIndex(g);
|
|
@@ -767,11 +875,9 @@ var Dummy = class _Dummy {
|
|
|
767
875
|
target = { id: dummy.id };
|
|
768
876
|
}
|
|
769
877
|
seg = Seg.add(g, { source, target, type, edgeIds: ISet4([edgeId]) });
|
|
770
|
-
log3.debug(`edge ${edgeId}: adding segment ${seg.id} from ${source.id} at layer ${layerIndex - 1} to ${target.id} at layer ${layerIndex}`);
|
|
771
878
|
segs.splice(segIndex, 0, seg.id);
|
|
772
879
|
changed = true;
|
|
773
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)) {
|
|
774
|
-
log3.debug(`edge ${edgeId}: removing segment ${seg.id} from layer ${layerIndex - 1} to layer ${layerIndex}`);
|
|
775
881
|
seg = seg.delEdgeId(g, edgeId);
|
|
776
882
|
segs.splice(segIndex, 1);
|
|
777
883
|
changed = true;
|
|
@@ -783,14 +889,12 @@ var Dummy = class _Dummy {
|
|
|
783
889
|
}
|
|
784
890
|
}
|
|
785
891
|
while (segIndex < segs.length) {
|
|
786
|
-
log3.debug(`edge ${edgeId}: removing trailing segment ${segs[segIndex]}`);
|
|
787
892
|
g.getSeg(segs[segIndex]).delEdgeId(g, edgeId);
|
|
788
893
|
segs.splice(segIndex, 1);
|
|
789
894
|
changed = true;
|
|
790
895
|
segIndex++;
|
|
791
896
|
}
|
|
792
897
|
if (changed) {
|
|
793
|
-
log3.debug(`edge ${edgeId}: updated segments to ${segs.join(", ")}`);
|
|
794
898
|
edge.setSegIds(g, segs);
|
|
795
899
|
}
|
|
796
900
|
}
|
|
@@ -805,22 +909,20 @@ var Dummy = class _Dummy {
|
|
|
805
909
|
const dir = side == "source" ? "in" : "out";
|
|
806
910
|
const altSide = side == "source" ? "target" : "source";
|
|
807
911
|
const altDir = altSide == "source" ? "in" : "out";
|
|
808
|
-
log3.debug(`merging dummies by ${side}`);
|
|
809
912
|
for (const layerId of layerIds) {
|
|
810
913
|
let layer = g.getLayer(layerId);
|
|
811
914
|
const groups = /* @__PURE__ */ new Map();
|
|
812
915
|
for (const nodeId of layer.nodeIds) {
|
|
813
|
-
if (!Node.isDummyId(nodeId)) continue;
|
|
814
916
|
const node = g.getNode(nodeId);
|
|
815
|
-
if (node.isMerged) continue;
|
|
917
|
+
if (!node.isDummy || node.isMerged) continue;
|
|
816
918
|
const edge = g.getEdge(node.edgeIds[0]);
|
|
817
|
-
const key = Edge.
|
|
919
|
+
const key = Edge.key(edge, "k:", side);
|
|
818
920
|
if (!groups.has(key)) groups.set(key, /* @__PURE__ */ new Set());
|
|
819
921
|
groups.get(key).add(node);
|
|
820
922
|
}
|
|
821
923
|
for (const [key, group] of groups) {
|
|
822
924
|
if (group.size == 1) continue;
|
|
823
|
-
const edgeIds = [...group].map((node) => node.
|
|
925
|
+
const edgeIds = [...group].map((node) => node.edgeIds[0]);
|
|
824
926
|
const dummy = Node.addDummy(g, { edgeIds, layerId, isMerged: true });
|
|
825
927
|
let seg;
|
|
826
928
|
for (const old of group) {
|
|
@@ -830,8 +932,9 @@ var Dummy = class _Dummy {
|
|
|
830
932
|
const example = g.getSeg(segId);
|
|
831
933
|
seg = Seg.add(g, {
|
|
832
934
|
...example,
|
|
833
|
-
edgeIds: ISet4(
|
|
834
|
-
[
|
|
935
|
+
edgeIds: ISet4(edgeIds),
|
|
936
|
+
[side]: { ...example[side] },
|
|
937
|
+
[altSide]: { ...example[altSide], id: dummy.id, port: void 0 }
|
|
835
938
|
});
|
|
836
939
|
}
|
|
837
940
|
edge = edge.replaceSegId(g, segId, seg.id);
|
|
@@ -843,12 +946,15 @@ var Dummy = class _Dummy {
|
|
|
843
946
|
const example = g.getSeg(segId);
|
|
844
947
|
const seg2 = Seg.add(g, {
|
|
845
948
|
...example,
|
|
846
|
-
edgeIds: ISet4([old.
|
|
847
|
-
[
|
|
949
|
+
edgeIds: ISet4([old.edgeIds[0]]),
|
|
950
|
+
[altSide]: { ...example[altSide] },
|
|
951
|
+
[side]: { ...example[side], id: dummy.id, port: void 0 }
|
|
848
952
|
});
|
|
849
953
|
edge = edge.replaceSegId(g, segId, seg2.id);
|
|
850
954
|
}
|
|
851
955
|
}
|
|
956
|
+
for (const old of group)
|
|
957
|
+
old.delSelf(g);
|
|
852
958
|
}
|
|
853
959
|
}
|
|
854
960
|
}
|
|
@@ -856,7 +962,7 @@ var Dummy = class _Dummy {
|
|
|
856
962
|
|
|
857
963
|
// src/graph/services/layers.ts
|
|
858
964
|
import { Seq } from "immutable";
|
|
859
|
-
var
|
|
965
|
+
var log6 = logger("layers");
|
|
860
966
|
var Layers = class {
|
|
861
967
|
static updateLayers(g) {
|
|
862
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);
|
|
@@ -919,13 +1025,11 @@ var Layers = class {
|
|
|
919
1025
|
|
|
920
1026
|
// src/graph/services/layout.ts
|
|
921
1027
|
import { Seq as Seq2 } from "immutable";
|
|
922
|
-
var
|
|
1028
|
+
var log7 = logger("layout");
|
|
923
1029
|
var Layout = class _Layout {
|
|
924
1030
|
static parentIndex(g, node) {
|
|
925
1031
|
const parents = Seq2([...node.adjs(g, "segs", "in")]);
|
|
926
|
-
console.log(`parents of ${node.id}:`, [...parents], [...parents.map((p) => p.index)]);
|
|
927
1032
|
const pidx = parents.map((p) => p.index).min();
|
|
928
|
-
log5.debug(`node ${node.id}: parent index ${pidx}`);
|
|
929
1033
|
if (pidx !== void 0) return pidx;
|
|
930
1034
|
return node.isDummy ? -Infinity : Infinity;
|
|
931
1035
|
}
|
|
@@ -938,12 +1042,11 @@ var Layout = class _Layout {
|
|
|
938
1042
|
if (a.isDummy && !b.isDummy) return -1;
|
|
939
1043
|
if (!a.isDummy && b.isDummy) return 1;
|
|
940
1044
|
if (!a.isDummy) return a.id.localeCompare(b.id);
|
|
941
|
-
const minA =
|
|
942
|
-
const minB =
|
|
1045
|
+
const minA = Seq2(a.edgeIds).min();
|
|
1046
|
+
const minB = Seq2(b.edgeIds).min();
|
|
943
1047
|
return minA.localeCompare(minB);
|
|
944
1048
|
}
|
|
945
1049
|
static positionNodes(g) {
|
|
946
|
-
console.log("positionNodes", g.dirtyNodes);
|
|
947
1050
|
for (const nodeId of g.dirtyNodes)
|
|
948
1051
|
g.dirtyLayers.add(g.getNode(nodeId).layerId);
|
|
949
1052
|
let adjustNext = false;
|
|
@@ -951,15 +1054,12 @@ var Layout = class _Layout {
|
|
|
951
1054
|
if (!adjustNext && !g.dirtyLayers.has(layerId)) continue;
|
|
952
1055
|
adjustNext = false;
|
|
953
1056
|
let layer = g.getLayer(layerId);
|
|
954
|
-
console.log(`positioning layer ${layerId} at ${layer.index}`);
|
|
955
1057
|
const pidxs = /* @__PURE__ */ new Map();
|
|
956
1058
|
for (const nodeId of layer.nodeIds)
|
|
957
1059
|
pidxs.set(nodeId, _Layout.parentIndex(g, g.getNode(nodeId)));
|
|
958
|
-
console.log("pidxs", pidxs);
|
|
959
1060
|
const sorted = [...layer.nodeIds].sort(
|
|
960
1061
|
(aId, bId) => _Layout.compareNodes(g, aId, bId, pidxs)
|
|
961
1062
|
);
|
|
962
|
-
console.log(`sorted:`, sorted);
|
|
963
1063
|
if (layer.hasSortOrder(sorted)) continue;
|
|
964
1064
|
g.dirtyLayers.add(layerId);
|
|
965
1065
|
layer = layer.setSorted(g, sorted);
|
|
@@ -967,10 +1067,14 @@ var Layout = class _Layout {
|
|
|
967
1067
|
let lpos = 0;
|
|
968
1068
|
for (let i = 0; i < sorted.length; i++) {
|
|
969
1069
|
let node = g.getNode(sorted[i]);
|
|
970
|
-
log5.debug(`node ${node.id}: final index ${i}`);
|
|
971
1070
|
node = node.setIndex(g, i).setLayerPos(g, lpos);
|
|
972
1071
|
const size = node.dims?.[g.w] ?? 0;
|
|
973
|
-
|
|
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;
|
|
974
1078
|
}
|
|
975
1079
|
}
|
|
976
1080
|
}
|
|
@@ -999,7 +1103,12 @@ var Layout = class _Layout {
|
|
|
999
1103
|
for (const layerId of layerIds) {
|
|
1000
1104
|
if (!adjustNext && !g.dirtyLayers.has(layerId)) continue;
|
|
1001
1105
|
adjustNext = false;
|
|
1106
|
+
let iterations = 0;
|
|
1002
1107
|
while (true) {
|
|
1108
|
+
if (++iterations > 10) {
|
|
1109
|
+
log7.error(`alignNodes: infinite loop detected in layer ${layerId}`);
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1003
1112
|
let changed = false;
|
|
1004
1113
|
const nodeIds = _Layout.sortLayer(g, layerId, reverseNodes);
|
|
1005
1114
|
for (const nodeId of nodeIds) {
|
|
@@ -1057,7 +1166,7 @@ var Layout = class _Layout {
|
|
|
1057
1166
|
static anchorPos(g, seg, side) {
|
|
1058
1167
|
const nodeId = seg[side].id;
|
|
1059
1168
|
const node = g.getNode(nodeId);
|
|
1060
|
-
let p = {
|
|
1169
|
+
let p = { [g.x]: node.lpos, [g.y]: node.pos?.[g.y] ?? 0 };
|
|
1061
1170
|
let w = node.dims?.[g.w] ?? 0;
|
|
1062
1171
|
let h = node.dims?.[g.h] ?? 0;
|
|
1063
1172
|
if (node.isDummy)
|
|
@@ -1065,32 +1174,56 @@ var Layout = class _Layout {
|
|
|
1065
1174
|
[g.x]: p[g.x] + w / 2,
|
|
1066
1175
|
[g.y]: p[g.y] + h / 2
|
|
1067
1176
|
};
|
|
1068
|
-
p[g.x] += _Layout.nodePortOffset(g, nodeId, seg
|
|
1069
|
-
if (side == "
|
|
1177
|
+
p[g.x] += _Layout.nodePortOffset(g, nodeId, seg, side);
|
|
1178
|
+
if (side == "target" == g.r)
|
|
1070
1179
|
p[g.y] += h;
|
|
1071
1180
|
return p;
|
|
1072
1181
|
}
|
|
1073
|
-
static nodePortOffset(g, nodeId,
|
|
1074
|
-
|
|
1075
|
-
|
|
1182
|
+
static nodePortOffset(g, nodeId, seg, side) {
|
|
1183
|
+
const node = g.getNode(nodeId);
|
|
1184
|
+
const dir = side == "source" ? "out" : "in";
|
|
1185
|
+
const portId = seg[side].port;
|
|
1186
|
+
let min = 0, size = node.dims?.[g.w] ?? 0;
|
|
1187
|
+
if (portId) {
|
|
1188
|
+
const ports = node.ports?.[dir];
|
|
1189
|
+
const port = ports?.find((p) => p.id === portId);
|
|
1190
|
+
if (port?.offset !== void 0) {
|
|
1191
|
+
min = port.offset;
|
|
1192
|
+
size = port.size ?? 0;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
const alt = side == "source" ? "target" : "source";
|
|
1196
|
+
let segs = [];
|
|
1197
|
+
const keyOf = (seg2) => `${seg2.type ?? ""}:${seg2[side].marker ?? ""}`;
|
|
1198
|
+
for (const segId of node.segs[dir])
|
|
1199
|
+
segs.push(g.getSeg(segId));
|
|
1200
|
+
if (portId) segs = segs.filter((s) => s[side].port == portId);
|
|
1201
|
+
const groups = Object.groupBy(segs, (s) => keyOf(s));
|
|
1202
|
+
const posMap = /* @__PURE__ */ new Map();
|
|
1203
|
+
for (const [key, segs2] of Object.entries(groups)) {
|
|
1204
|
+
let pos = Infinity;
|
|
1205
|
+
for (const seg2 of segs2) pos = Math.min(pos, seg2.node(g, alt).lpos);
|
|
1206
|
+
posMap.set(key, pos);
|
|
1207
|
+
}
|
|
1208
|
+
const keys = [...posMap.keys()].sort((a, b) => posMap.get(a) - posMap.get(b));
|
|
1209
|
+
const gap = size / (keys.length + 1);
|
|
1210
|
+
const index = keys.indexOf(keyOf(seg));
|
|
1211
|
+
return min + (index + 1) * gap;
|
|
1076
1212
|
}
|
|
1077
1213
|
static shiftNode(g, nodeId, alignId, dir, lpos, reverseMove, conservative) {
|
|
1078
1214
|
const node = g.getNode(nodeId);
|
|
1079
1215
|
if (!conservative)
|
|
1080
1216
|
_Layout.markAligned(g, nodeId, alignId, dir, lpos);
|
|
1081
|
-
const
|
|
1082
|
-
const nodeWidth = node.dims?.[g.w] ?? 0;
|
|
1083
|
-
const aMin = lpos - space, aMax = lpos + nodeWidth + space;
|
|
1217
|
+
const nodeRight = lpos + node.width(g);
|
|
1084
1218
|
repeat:
|
|
1085
1219
|
for (const otherId of node.getLayer(g).nodeIds) {
|
|
1086
1220
|
if (otherId == nodeId) continue;
|
|
1087
1221
|
const other = g.getNode(otherId);
|
|
1088
|
-
const
|
|
1089
|
-
const
|
|
1090
|
-
|
|
1091
|
-
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) {
|
|
1092
1225
|
if (conservative) return false;
|
|
1093
|
-
const safePos = reverseMove ?
|
|
1226
|
+
const safePos = reverseMove ? lpos - other.width(g) - margin : nodeRight + margin;
|
|
1094
1227
|
_Layout.shiftNode(g, otherId, void 0, dir, safePos, reverseMove, conservative);
|
|
1095
1228
|
continue repeat;
|
|
1096
1229
|
}
|
|
@@ -1106,7 +1239,7 @@ var Layout = class _Layout {
|
|
|
1106
1239
|
g.getNode(node.aligned[dir]).setAligned(g, alt, void 0);
|
|
1107
1240
|
if (otherId)
|
|
1108
1241
|
g.getNode(otherId).setAligned(g, alt, nodeId);
|
|
1109
|
-
node.setAligned(g, dir, otherId);
|
|
1242
|
+
node.setAligned(g, dir, otherId).setLayerPos(g, lpos);
|
|
1110
1243
|
}
|
|
1111
1244
|
static *aligned(g, nodeId, dir) {
|
|
1112
1245
|
const visit = function* (node2, dir2) {
|
|
@@ -1139,11 +1272,12 @@ var Layout = class _Layout {
|
|
|
1139
1272
|
for (const layerId of g.layerList) {
|
|
1140
1273
|
const layer = g.getLayer(layerId);
|
|
1141
1274
|
if (layer.sorted.length < 2) continue;
|
|
1142
|
-
for (const nodeId of layer.sorted) {
|
|
1275
|
+
for (const [i, nodeId] of layer.sorted.entries()) {
|
|
1143
1276
|
const node = g.getNode(nodeId);
|
|
1144
1277
|
if (node.index == 0) continue;
|
|
1145
1278
|
let minGap = Infinity;
|
|
1146
1279
|
const stack = [];
|
|
1280
|
+
let maxMargin = 0;
|
|
1147
1281
|
for (const right of _Layout.aligned(g, nodeId, "both")) {
|
|
1148
1282
|
stack.push(right);
|
|
1149
1283
|
const leftId = _Layout.leftOf(g, right);
|
|
@@ -1152,8 +1286,10 @@ var Layout = class _Layout {
|
|
|
1152
1286
|
const leftWidth = left.dims?.[g.w] ?? 0;
|
|
1153
1287
|
const gap = right.lpos - left.lpos - leftWidth;
|
|
1154
1288
|
if (gap < minGap) minGap = gap;
|
|
1289
|
+
const margin = right.marginWith(g, left);
|
|
1290
|
+
if (margin > maxMargin) maxMargin = margin;
|
|
1155
1291
|
}
|
|
1156
|
-
const delta = minGap -
|
|
1292
|
+
const delta = minGap - maxMargin;
|
|
1157
1293
|
if (delta <= 0) continue;
|
|
1158
1294
|
anyChanged = true;
|
|
1159
1295
|
for (const right of stack)
|
|
@@ -1166,39 +1302,140 @@ var Layout = class _Layout {
|
|
|
1166
1302
|
let pos = 0;
|
|
1167
1303
|
const dir = g.r ? -1 : 1;
|
|
1168
1304
|
const trackSep = Math.max(
|
|
1169
|
-
g.options.layerMargin,
|
|
1170
1305
|
g.options.edgeSpacing,
|
|
1171
1306
|
g.options.turnRadius
|
|
1172
1307
|
);
|
|
1308
|
+
const marginSep = Math.max(
|
|
1309
|
+
g.options.edgeSpacing,
|
|
1310
|
+
g.options.layerMargin,
|
|
1311
|
+
g.options.turnRadius + g.options.markerSize
|
|
1312
|
+
);
|
|
1173
1313
|
for (const layerId of g.layerList) {
|
|
1174
1314
|
let layer = g.getLayer(layerId);
|
|
1175
1315
|
let height;
|
|
1176
|
-
console.log(`getCoords: layer = ${layerId} at ${layer.index}`);
|
|
1177
1316
|
if (g.dirtyLayers.has(layerId)) {
|
|
1178
1317
|
height = Seq2(layer.nodes(g)).map((node) => node.dims?.[g.h] ?? 0).max() ?? 0;
|
|
1179
1318
|
layer = layer.setSize(g, height);
|
|
1180
1319
|
} else height = layer.size;
|
|
1181
|
-
console.log(`getCoords: layer = ${layerId}: pos = ${pos}, height = ${height}`);
|
|
1182
1320
|
for (const node of layer.nodes(g)) {
|
|
1183
1321
|
if (!g.dirtyNodes.has(node.id) && pos == layer.pos) continue;
|
|
1184
1322
|
const npos = { [g.x]: node.lpos, [g.y]: pos };
|
|
1185
1323
|
if (!g.n) npos[g.y] += dir * height;
|
|
1186
1324
|
if (g.r == g.n) npos[g.y] -= node.dims?.[g.h] ?? 0;
|
|
1187
|
-
console.log(`getCoords: node = ${node.id}: pos:`, npos);
|
|
1188
1325
|
node.setPos(g, npos);
|
|
1189
1326
|
}
|
|
1190
1327
|
layer = layer.setPos(g, pos);
|
|
1191
|
-
pos += dir * (height +
|
|
1328
|
+
pos += dir * (height + marginSep);
|
|
1192
1329
|
for (const track of layer.tracks) {
|
|
1193
1330
|
for (const segId of track)
|
|
1194
1331
|
g.getSeg(segId).setTrackPos(g, pos);
|
|
1195
|
-
pos += dir *
|
|
1332
|
+
pos += dir * trackSep;
|
|
1196
1333
|
}
|
|
1334
|
+
pos += dir * (marginSep - trackSep);
|
|
1197
1335
|
}
|
|
1198
1336
|
}
|
|
1199
1337
|
};
|
|
1200
1338
|
|
|
1339
|
+
// src/canvas/marker.tsx
|
|
1340
|
+
import { jsx } from "jsx-dom/jsx-runtime";
|
|
1341
|
+
function arrow(size, reverse = false) {
|
|
1342
|
+
const h = size / 1.5;
|
|
1343
|
+
const w = size;
|
|
1344
|
+
const ry = h / 2;
|
|
1345
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1346
|
+
return /* @__PURE__ */ jsx(
|
|
1347
|
+
"marker",
|
|
1348
|
+
{
|
|
1349
|
+
id: `g3p-marker-arrow${suffix}`,
|
|
1350
|
+
className: "g3p-marker g3p-marker-arrow",
|
|
1351
|
+
markerWidth: size,
|
|
1352
|
+
markerHeight: size,
|
|
1353
|
+
refX: "2",
|
|
1354
|
+
refY: ry,
|
|
1355
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1356
|
+
markerUnits: "userSpaceOnUse",
|
|
1357
|
+
children: /* @__PURE__ */ jsx("path", { d: `M0,0 L0,${h} L${w},${ry} z` })
|
|
1358
|
+
}
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
function circle(size, reverse = false) {
|
|
1362
|
+
const r = size / 3;
|
|
1363
|
+
const cy = size / 2;
|
|
1364
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1365
|
+
return /* @__PURE__ */ jsx(
|
|
1366
|
+
"marker",
|
|
1367
|
+
{
|
|
1368
|
+
id: `g3p-marker-circle${suffix}`,
|
|
1369
|
+
className: "g3p-marker g3p-marker-circle",
|
|
1370
|
+
markerWidth: size,
|
|
1371
|
+
markerHeight: size,
|
|
1372
|
+
refX: "2",
|
|
1373
|
+
refY: cy,
|
|
1374
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1375
|
+
markerUnits: "userSpaceOnUse",
|
|
1376
|
+
children: /* @__PURE__ */ jsx("circle", { cx: r + 2, cy, r })
|
|
1377
|
+
}
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
function diamond(size, reverse = false) {
|
|
1381
|
+
const w = size * 0.7;
|
|
1382
|
+
const h = size / 2;
|
|
1383
|
+
const cy = size / 2;
|
|
1384
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1385
|
+
return /* @__PURE__ */ jsx(
|
|
1386
|
+
"marker",
|
|
1387
|
+
{
|
|
1388
|
+
id: `g3p-marker-diamond${suffix}`,
|
|
1389
|
+
className: "g3p-marker g3p-marker-diamond",
|
|
1390
|
+
markerWidth: size,
|
|
1391
|
+
markerHeight: size,
|
|
1392
|
+
refX: "2",
|
|
1393
|
+
refY: cy,
|
|
1394
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1395
|
+
markerUnits: "userSpaceOnUse",
|
|
1396
|
+
children: /* @__PURE__ */ jsx("path", { d: `M2,${cy} L${2 + w / 2},${cy - h / 2} L${2 + w},${cy} L${2 + w / 2},${cy + h / 2} z` })
|
|
1397
|
+
}
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
function bar(size, reverse = false) {
|
|
1401
|
+
const h = size * 0.6;
|
|
1402
|
+
const cy = size / 2;
|
|
1403
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1404
|
+
return /* @__PURE__ */ jsx(
|
|
1405
|
+
"marker",
|
|
1406
|
+
{
|
|
1407
|
+
id: `g3p-marker-bar${suffix}`,
|
|
1408
|
+
className: "g3p-marker g3p-marker-bar",
|
|
1409
|
+
markerWidth: size,
|
|
1410
|
+
markerHeight: size,
|
|
1411
|
+
refX: "2",
|
|
1412
|
+
refY: cy,
|
|
1413
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1414
|
+
markerUnits: "userSpaceOnUse",
|
|
1415
|
+
children: /* @__PURE__ */ jsx("line", { x1: "2", y1: cy - h / 2, x2: "2", y2: cy + h / 2, "stroke-width": "2" })
|
|
1416
|
+
}
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
function none(size, reverse = false) {
|
|
1420
|
+
return void 0;
|
|
1421
|
+
}
|
|
1422
|
+
function normalize(data) {
|
|
1423
|
+
let source = data.source?.marker ?? data.style?.marker?.source;
|
|
1424
|
+
let target = data.target?.marker ?? data.style?.marker?.target ?? "arrow";
|
|
1425
|
+
if (source == "none") source = void 0;
|
|
1426
|
+
if (target == "none") target = void 0;
|
|
1427
|
+
return { source, target };
|
|
1428
|
+
}
|
|
1429
|
+
var markerDefs = {
|
|
1430
|
+
arrow,
|
|
1431
|
+
circle,
|
|
1432
|
+
diamond,
|
|
1433
|
+
bar,
|
|
1434
|
+
none
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1201
1437
|
// src/graph/services/lines.ts
|
|
1438
|
+
var log8 = logger("lines");
|
|
1202
1439
|
var Lines = class _Lines {
|
|
1203
1440
|
static layoutSeg(g, seg) {
|
|
1204
1441
|
const sourcePos = Layout.anchorPos(g, seg, "source");
|
|
@@ -1244,7 +1481,11 @@ var Lines = class _Lines {
|
|
|
1244
1481
|
validTrack = track;
|
|
1245
1482
|
break;
|
|
1246
1483
|
}
|
|
1247
|
-
|
|
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) {
|
|
1248
1489
|
overlap = true;
|
|
1249
1490
|
break;
|
|
1250
1491
|
}
|
|
@@ -1259,6 +1500,21 @@ var Lines = class _Lines {
|
|
|
1259
1500
|
else
|
|
1260
1501
|
trackSet.push([seg]);
|
|
1261
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
|
+
});
|
|
1262
1518
|
const tracks = [];
|
|
1263
1519
|
const all = leftTracks.concat(rightTracks).concat(allTracks);
|
|
1264
1520
|
for (const track of all)
|
|
@@ -1273,7 +1529,12 @@ var Lines = class _Lines {
|
|
|
1273
1529
|
const radius = g.options.turnRadius;
|
|
1274
1530
|
const p1 = Layout.anchorPos(g, seg, "source");
|
|
1275
1531
|
const p2 = Layout.anchorPos(g, seg, "target");
|
|
1276
|
-
const
|
|
1532
|
+
const source = seg.sourceNode(g);
|
|
1533
|
+
const target = seg.targetNode(g);
|
|
1534
|
+
const marker = normalize(seg);
|
|
1535
|
+
if (source.isDummy) marker.source = void 0;
|
|
1536
|
+
if (target.isDummy) marker.target = void 0;
|
|
1537
|
+
const path = seg.trackPos !== void 0 ? _Lines.createRailroadPath(g, p1, p2, seg.trackPos, radius, marker) : _Lines.createDirectPath(g, p1, p2, radius, marker);
|
|
1277
1538
|
const svg = _Lines.pathToSVG(path);
|
|
1278
1539
|
seg.setSVG(g, svg);
|
|
1279
1540
|
}
|
|
@@ -1299,7 +1560,7 @@ var Lines = class _Lines {
|
|
|
1299
1560
|
}
|
|
1300
1561
|
return line;
|
|
1301
1562
|
}
|
|
1302
|
-
static pathBuilder(g, start, end, trackPos, radius) {
|
|
1563
|
+
static pathBuilder(g, start, end, trackPos, radius, marker) {
|
|
1303
1564
|
const { x, y } = g;
|
|
1304
1565
|
const lr = end[x] > start[x];
|
|
1305
1566
|
const d = lr ? 1 : -1;
|
|
@@ -1311,6 +1572,8 @@ var Lines = class _Lines {
|
|
|
1311
1572
|
if (g.r) s = 1 - s;
|
|
1312
1573
|
if (!lr) s = 1 - s;
|
|
1313
1574
|
if (!g.v) s = 1 - s;
|
|
1575
|
+
if (marker.source) start[y] += o * (g.options.markerSize - 1);
|
|
1576
|
+
if (marker.target) end[y] -= o * (g.options.markerSize - 1);
|
|
1314
1577
|
const p = { ...start, s };
|
|
1315
1578
|
const path = [];
|
|
1316
1579
|
const advance = (p2, type) => {
|
|
@@ -1319,8 +1582,8 @@ var Lines = class _Lines {
|
|
|
1319
1582
|
return { x, y, lr, d, o, rd, ro, t, s, p, path, advance };
|
|
1320
1583
|
}
|
|
1321
1584
|
// Create a railroad-style path with two 90-degree turns
|
|
1322
|
-
static createRailroadPath(g, start, end, trackPos, radius) {
|
|
1323
|
-
const { x, y, rd, ro, t, s, p, path, advance } = this.pathBuilder(g, start, end, trackPos, radius);
|
|
1585
|
+
static createRailroadPath(g, start, end, trackPos, radius, marker) {
|
|
1586
|
+
const { x, y, rd, ro, t, s, p, path, advance } = this.pathBuilder(g, start, end, trackPos, radius, marker);
|
|
1324
1587
|
advance({ [y]: t - ro }, "line");
|
|
1325
1588
|
advance({ [x]: p[x] + rd, [y]: t }, "arc");
|
|
1326
1589
|
advance({ [x]: end[x] - rd }, "line");
|
|
@@ -1329,8 +1592,8 @@ var Lines = class _Lines {
|
|
|
1329
1592
|
return path;
|
|
1330
1593
|
}
|
|
1331
1594
|
// Create a mostly-vertical path with optional S-curve
|
|
1332
|
-
static createDirectPath(g, start, end, radius) {
|
|
1333
|
-
const { x, y, d, o, s, p, path, advance } = this.pathBuilder(g, start, end, 0, radius);
|
|
1595
|
+
static createDirectPath(g, start, end, radius, marker) {
|
|
1596
|
+
const { x, y, d, o, s, p, path, advance } = this.pathBuilder(g, start, end, 0, radius, marker);
|
|
1334
1597
|
const dx = Math.abs(end.x - start.x);
|
|
1335
1598
|
const dy = Math.abs(end.y - start.y);
|
|
1336
1599
|
const d_ = { x: dx, y: dy };
|
|
@@ -1421,21 +1684,15 @@ var Lines = class _Lines {
|
|
|
1421
1684
|
}
|
|
1422
1685
|
};
|
|
1423
1686
|
|
|
1424
|
-
// src/graph/
|
|
1425
|
-
var
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
orientation: "TB",
|
|
1434
|
-
layerMargin: 5,
|
|
1435
|
-
alignIterations: 5,
|
|
1436
|
-
alignThreshold: 10,
|
|
1437
|
-
separateTrackSets: true,
|
|
1438
|
-
layoutSteps: null
|
|
1687
|
+
// src/graph/graph.ts
|
|
1688
|
+
var log9 = logger("graph");
|
|
1689
|
+
var emptyChanges = {
|
|
1690
|
+
addedNodes: [],
|
|
1691
|
+
removedNodes: [],
|
|
1692
|
+
updatedNodes: [],
|
|
1693
|
+
addedEdges: [],
|
|
1694
|
+
removedEdges: [],
|
|
1695
|
+
updatedEdges: []
|
|
1439
1696
|
};
|
|
1440
1697
|
var Graph = class _Graph {
|
|
1441
1698
|
prior;
|
|
@@ -1464,30 +1721,10 @@ var Graph = class _Graph {
|
|
|
1464
1721
|
x;
|
|
1465
1722
|
y;
|
|
1466
1723
|
d;
|
|
1467
|
-
constructor({ prior, changes, options
|
|
1724
|
+
constructor({ prior, changes, options }) {
|
|
1725
|
+
this.options = prior?.options ?? options;
|
|
1726
|
+
this.changes = changes ?? emptyChanges;
|
|
1468
1727
|
this.initFromPrior(prior);
|
|
1469
|
-
this.dirtyNodes = /* @__PURE__ */ new Set();
|
|
1470
|
-
this.dirtyEdges = /* @__PURE__ */ new Set();
|
|
1471
|
-
this.dirtyLayers = /* @__PURE__ */ new Set();
|
|
1472
|
-
this.dirtySegs = /* @__PURE__ */ new Set();
|
|
1473
|
-
this.delNodes = /* @__PURE__ */ new Set();
|
|
1474
|
-
this.delEdges = /* @__PURE__ */ new Set();
|
|
1475
|
-
this.delSegs = /* @__PURE__ */ new Set();
|
|
1476
|
-
this.options = {
|
|
1477
|
-
...defaultOptions,
|
|
1478
|
-
...prior?.options,
|
|
1479
|
-
...options
|
|
1480
|
-
};
|
|
1481
|
-
this.changes = changes ?? {
|
|
1482
|
-
addedNodes: [],
|
|
1483
|
-
removedNodes: [],
|
|
1484
|
-
updatedNodes: [],
|
|
1485
|
-
addedEdges: [],
|
|
1486
|
-
removedEdges: []
|
|
1487
|
-
};
|
|
1488
|
-
this.changes.addedNodes.push(...nodes || []);
|
|
1489
|
-
this.changes.addedEdges.push(...edges || []);
|
|
1490
|
-
this.dirty = this.changes.addedNodes.length > 0 || this.changes.removedNodes.length > 0 || this.changes.addedEdges.length > 0 || this.changes.removedEdges.length > 0;
|
|
1491
1728
|
this.r = this.options.orientation === "BT" || this.options.orientation === "RL";
|
|
1492
1729
|
this.v = this.options.orientation === "TB" || this.options.orientation === "BT";
|
|
1493
1730
|
this.h = this.v ? "h" : "w";
|
|
@@ -1503,38 +1740,41 @@ var Graph = class _Graph {
|
|
|
1503
1740
|
this.n = true;
|
|
1504
1741
|
else
|
|
1505
1742
|
this.n = natAligns[this.options.orientation] == this.options.nodeAlign;
|
|
1506
|
-
if (this.dirty)
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1743
|
+
if (this.dirty) this.processUpdate();
|
|
1744
|
+
}
|
|
1745
|
+
processUpdate() {
|
|
1746
|
+
try {
|
|
1747
|
+
this.beginMutate();
|
|
1748
|
+
this.applyChanges();
|
|
1749
|
+
Cycles.checkCycles(this);
|
|
1750
|
+
Layers.updateLayers(this);
|
|
1751
|
+
Dummy.updateDummies(this);
|
|
1752
|
+
Dummy.mergeDummies(this);
|
|
1753
|
+
Layout.positionNodes(this);
|
|
1754
|
+
Layout.alignAll(this);
|
|
1755
|
+
Lines.trackEdges(this);
|
|
1756
|
+
Layout.getCoords(this);
|
|
1757
|
+
Lines.pathEdges(this);
|
|
1758
|
+
} catch (e) {
|
|
1759
|
+
this.initFromPrior(this.prior);
|
|
1760
|
+
throw e;
|
|
1761
|
+
} finally {
|
|
1762
|
+
this.endMutate();
|
|
1525
1763
|
}
|
|
1526
1764
|
}
|
|
1527
1765
|
applyChanges() {
|
|
1528
1766
|
for (const edge of this.changes.removedEdges)
|
|
1529
|
-
|
|
1767
|
+
Edge.del(this, edge);
|
|
1530
1768
|
for (const node of this.changes.removedNodes)
|
|
1531
|
-
|
|
1769
|
+
Node.del(this, node);
|
|
1532
1770
|
for (const node of this.changes.addedNodes)
|
|
1533
1771
|
Node.addNormal(this, node);
|
|
1534
1772
|
for (const edge of this.changes.addedEdges)
|
|
1535
1773
|
Edge.add(this, edge);
|
|
1536
1774
|
for (const node of this.changes.updatedNodes)
|
|
1537
|
-
|
|
1775
|
+
Node.update(this, node);
|
|
1776
|
+
for (const edge of this.changes.updatedEdges)
|
|
1777
|
+
Edge.update(this, edge);
|
|
1538
1778
|
}
|
|
1539
1779
|
layerAt(index) {
|
|
1540
1780
|
while (index >= this.layerList.size)
|
|
@@ -1611,24 +1851,24 @@ var Graph = class _Graph {
|
|
|
1611
1851
|
nodes.forEach((node) => mutator.addNode(node));
|
|
1612
1852
|
});
|
|
1613
1853
|
}
|
|
1614
|
-
|
|
1854
|
+
removeNodes(...nodes) {
|
|
1615
1855
|
return this.withMutations((mutator) => {
|
|
1616
|
-
|
|
1856
|
+
nodes.forEach((node) => mutator.removeNode(node));
|
|
1617
1857
|
});
|
|
1618
1858
|
}
|
|
1619
|
-
|
|
1859
|
+
removeNode(node) {
|
|
1620
1860
|
return this.withMutations((mutator) => {
|
|
1621
|
-
mutator.
|
|
1861
|
+
mutator.removeNode(node);
|
|
1622
1862
|
});
|
|
1623
1863
|
}
|
|
1624
|
-
|
|
1864
|
+
addEdges(...edges) {
|
|
1625
1865
|
return this.withMutations((mutator) => {
|
|
1626
|
-
|
|
1866
|
+
edges.forEach((edge) => mutator.addEdge(edge));
|
|
1627
1867
|
});
|
|
1628
1868
|
}
|
|
1629
|
-
|
|
1869
|
+
addEdge(edge) {
|
|
1630
1870
|
return this.withMutations((mutator) => {
|
|
1631
|
-
mutator.
|
|
1871
|
+
mutator.addEdge(edge);
|
|
1632
1872
|
});
|
|
1633
1873
|
}
|
|
1634
1874
|
removeEdges(...edges) {
|
|
@@ -1678,6 +1918,14 @@ var Graph = class _Graph {
|
|
|
1678
1918
|
this.nextLayerId = prior?.nextLayerId ?? 0;
|
|
1679
1919
|
this.nextDummyId = prior?.nextDummyId ?? 0;
|
|
1680
1920
|
this.prior = prior;
|
|
1921
|
+
this.dirtyNodes = /* @__PURE__ */ new Set();
|
|
1922
|
+
this.dirtyEdges = /* @__PURE__ */ new Set();
|
|
1923
|
+
this.dirtyLayers = /* @__PURE__ */ new Set();
|
|
1924
|
+
this.dirtySegs = /* @__PURE__ */ new Set();
|
|
1925
|
+
this.delNodes = /* @__PURE__ */ new Set();
|
|
1926
|
+
this.delEdges = /* @__PURE__ */ new Set();
|
|
1927
|
+
this.delSegs = /* @__PURE__ */ new Set();
|
|
1928
|
+
this.dirty = this.changes.addedNodes.length > 0 || this.changes.removedNodes.length > 0 || this.changes.updatedNodes.length > 0 || this.changes.addedEdges.length > 0 || this.changes.removedEdges.length > 0;
|
|
1681
1929
|
}
|
|
1682
1930
|
beginMutate() {
|
|
1683
1931
|
this.nodes = this.nodes.asMutable();
|
|
@@ -1707,118 +1955,86 @@ var Graph = class _Graph {
|
|
|
1707
1955
|
}
|
|
1708
1956
|
};
|
|
1709
1957
|
|
|
1710
|
-
// src/
|
|
1711
|
-
var
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
var injected = {};
|
|
1715
|
-
function styler(name, styles, prefix) {
|
|
1716
|
-
if (prefix === "g3p" && !injected[name]) {
|
|
1717
|
-
const style = document.createElement("style");
|
|
1718
|
-
style.textContent = styles;
|
|
1719
|
-
document.head.appendChild(style);
|
|
1720
|
-
injected[name] = true;
|
|
1721
|
-
}
|
|
1722
|
-
return (str, condition) => {
|
|
1723
|
-
if (!(condition ?? true)) return "";
|
|
1724
|
-
const parts = str.split(/\s+/);
|
|
1725
|
-
const fixed = parts.map((p) => `${prefix}-${name}-${p}`);
|
|
1726
|
-
return fixed.join(" ");
|
|
1727
|
-
};
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
// src/canvas/node.css
|
|
1731
|
-
var node_default = ".g3p-node-container {\n transition: opacity 0.2s ease;\n}\n\n.g3p-node-background {\n fill: var(--graph-node-bg, #ffffff);\n filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));\n}\n\n.g3p-node-border {\n stroke: var(--graph-node-border, #cbd5e1);\n transition: stroke 0.2s ease, stroke-width 0.2s ease;\n}\n\n.g3p-node-background.hovered {\n fill: var(--graph-node-hover, #f1f5f9);\n}\n\n.g3p-node-border.hovered {\n stroke: #94a3b8;\n}\n\n.g3p-node-border.selected {\n stroke: var(--graph-node-border-selected, #3b82f6);\n stroke-width: 3;\n}\n\n.g3p-node-content-wrapper {\n pointer-events: none;\n}\n\n.g3p-node-content {\n pointer-events: auto;\n box-sizing: border-box;\n}\n\n.g3p-node-content>div {\n width: 100%;\n height: 100%;\n}\n\n/* Dummy node styles */\n.g3p-node-dummy .g3p-node-background {\n fill: var(--graph-dummy-node-bg, #f8fafc);\n opacity: 0.8;\n}\n\n.g3p-node-dummy .g3p-node-border {\n stroke: var(--graph-dummy-node-border, #cbd5e1);\n stroke-dasharray: 3, 3;\n}";
|
|
1958
|
+
// src/common.ts
|
|
1959
|
+
var screenPos = (x, y) => ({ x, y });
|
|
1960
|
+
var canvasPos = (x, y) => ({ x, y });
|
|
1961
|
+
var graphPos = (x, y) => ({ x, y });
|
|
1732
1962
|
|
|
1733
1963
|
// src/canvas/node.tsx
|
|
1734
|
-
import {
|
|
1964
|
+
import { jsx as jsx2, jsxs } from "jsx-dom/jsx-runtime";
|
|
1735
1965
|
var Node2 = class {
|
|
1736
1966
|
selected;
|
|
1737
1967
|
hovered;
|
|
1738
1968
|
container;
|
|
1739
|
-
dims;
|
|
1740
1969
|
content;
|
|
1741
|
-
|
|
1970
|
+
canvas;
|
|
1971
|
+
data;
|
|
1742
1972
|
isDummy;
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
onContextMenu: () => null,
|
|
1753
|
-
onMouseDown: () => null,
|
|
1754
|
-
onMouseUp: () => null,
|
|
1755
|
-
classPrefix: "g3p",
|
|
1756
|
-
...options
|
|
1757
|
-
});
|
|
1758
|
-
if (!this.isDummy) {
|
|
1759
|
-
this.content = this.renderNode(this.data);
|
|
1760
|
-
this.measured = false;
|
|
1973
|
+
pos;
|
|
1974
|
+
constructor(canvas, data, isDummy = false) {
|
|
1975
|
+
this.canvas = canvas;
|
|
1976
|
+
this.data = data;
|
|
1977
|
+
this.selected = false;
|
|
1978
|
+
this.hovered = false;
|
|
1979
|
+
this.isDummy = isDummy;
|
|
1980
|
+
if (this.isDummy) {
|
|
1981
|
+
const size = canvas.dummyNodeSize;
|
|
1761
1982
|
} else {
|
|
1762
|
-
|
|
1983
|
+
const render = data.render ?? canvas.renderNode;
|
|
1984
|
+
this.content = this.renderContent(render(data.data, data));
|
|
1763
1985
|
}
|
|
1764
1986
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
this.dims = { w: rect.width, h: rect.height };
|
|
1768
|
-
this.measured = true;
|
|
1769
|
-
}
|
|
1770
|
-
handleClick(e) {
|
|
1771
|
-
e.stopPropagation();
|
|
1772
|
-
this.onClick?.(this.data, e);
|
|
1773
|
-
}
|
|
1774
|
-
handleMouseEnter(e) {
|
|
1775
|
-
this.onMouseEnter?.(this.data, e);
|
|
1987
|
+
remove() {
|
|
1988
|
+
this.container.remove();
|
|
1776
1989
|
}
|
|
1777
|
-
|
|
1778
|
-
this.
|
|
1990
|
+
append() {
|
|
1991
|
+
this.canvas.group.appendChild(this.container);
|
|
1779
1992
|
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
e.stopPropagation();
|
|
1783
|
-
this.onContextMenu(this.data, e);
|
|
1784
|
-
}
|
|
1993
|
+
needsContentSize() {
|
|
1994
|
+
return !this.isDummy && this.content instanceof HTMLElement;
|
|
1785
1995
|
}
|
|
1786
|
-
|
|
1787
|
-
this.
|
|
1788
|
-
}
|
|
1789
|
-
handleMouseUp(e) {
|
|
1790
|
-
this.onMouseUp?.(this.data, e);
|
|
1996
|
+
needsContainerSize() {
|
|
1997
|
+
return !this.isDummy;
|
|
1791
1998
|
}
|
|
1792
1999
|
setPos(pos) {
|
|
1793
|
-
console.log(`setPos:`, this, pos);
|
|
1794
2000
|
this.pos = pos;
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
2001
|
+
const { x, y } = pos;
|
|
2002
|
+
this.container.setAttribute("transform", `translate(${x}, ${y})`);
|
|
2003
|
+
}
|
|
2004
|
+
hasPorts() {
|
|
2005
|
+
return !!this.data?.ports?.in?.length || !!this.data?.ports?.out?.length;
|
|
2006
|
+
}
|
|
2007
|
+
renderContent(el) {
|
|
2008
|
+
const hasPorts = this.hasPorts();
|
|
2009
|
+
el = this.renderBorder(el);
|
|
2010
|
+
if (hasPorts)
|
|
2011
|
+
el = this.renderOutsidePorts(el);
|
|
2012
|
+
return el;
|
|
2013
|
+
}
|
|
2014
|
+
renderContainer() {
|
|
2015
|
+
const hasPorts = this.hasPorts();
|
|
2016
|
+
const inner = this.isDummy ? this.renderDummy() : this.renderForeign();
|
|
2017
|
+
const nodeType = this.data?.type;
|
|
2018
|
+
const typeClass = nodeType ? `g3p-node-type-${nodeType}` : "";
|
|
2019
|
+
this.container = /* @__PURE__ */ jsx2(
|
|
1801
2020
|
"g",
|
|
1802
2021
|
{
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
onMouseEnter: this.handleMouseEnter.bind(this),
|
|
1807
|
-
onMouseLeave: this.handleMouseLeave.bind(this),
|
|
1808
|
-
onContextMenu: this.handleContextMenu.bind(this),
|
|
1809
|
-
onMouseDown: this.handleMouseDown.bind(this),
|
|
1810
|
-
onMouseUp: this.handleMouseUp.bind(this),
|
|
1811
|
-
style: { cursor: "pointer" },
|
|
1812
|
-
children: this.isDummy ? this.renderDummy() : this.renderContent()
|
|
2022
|
+
className: `g3p-node-container ${this.isDummy ? "g3p-node-dummy" : ""} ${typeClass}`.trim(),
|
|
2023
|
+
"data-node-id": this.data?.id,
|
|
2024
|
+
children: inner
|
|
1813
2025
|
}
|
|
1814
2026
|
);
|
|
1815
2027
|
}
|
|
2028
|
+
renderForeign() {
|
|
2029
|
+
const { w, h } = this.data.dims;
|
|
2030
|
+
return /* @__PURE__ */ jsx2("foreignObject", { width: w, height: h, children: this.content });
|
|
2031
|
+
}
|
|
1816
2032
|
renderDummy() {
|
|
1817
|
-
|
|
1818
|
-
let
|
|
2033
|
+
let w = this.canvas.dummyNodeSize;
|
|
2034
|
+
let h = this.canvas.dummyNodeSize;
|
|
1819
2035
|
w /= 2;
|
|
1820
2036
|
h /= 2;
|
|
1821
|
-
return /* @__PURE__ */ jsxs(
|
|
2037
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
1822
2038
|
/* @__PURE__ */ jsx2(
|
|
1823
2039
|
"ellipse",
|
|
1824
2040
|
{
|
|
@@ -1826,7 +2042,7 @@ var Node2 = class {
|
|
|
1826
2042
|
cy: h,
|
|
1827
2043
|
rx: w,
|
|
1828
2044
|
ry: h,
|
|
1829
|
-
className:
|
|
2045
|
+
className: "g3p-node-background"
|
|
1830
2046
|
}
|
|
1831
2047
|
),
|
|
1832
2048
|
/* @__PURE__ */ jsx2(
|
|
@@ -1837,160 +2053,176 @@ var Node2 = class {
|
|
|
1837
2053
|
rx: w,
|
|
1838
2054
|
ry: h,
|
|
1839
2055
|
fill: "none",
|
|
1840
|
-
className:
|
|
1841
|
-
strokeWidth: "2"
|
|
2056
|
+
className: "g3p-node-border"
|
|
1842
2057
|
}
|
|
1843
2058
|
)
|
|
1844
2059
|
] });
|
|
1845
2060
|
}
|
|
1846
|
-
|
|
1847
|
-
const
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
className: c("border"),
|
|
1864
|
-
width: w,
|
|
1865
|
-
height: h,
|
|
1866
|
-
rx: 8,
|
|
1867
|
-
ry: 8,
|
|
1868
|
-
fill: "none",
|
|
1869
|
-
strokeWidth: "2"
|
|
1870
|
-
}
|
|
1871
|
-
),
|
|
1872
|
-
/* @__PURE__ */ jsx2(
|
|
1873
|
-
"foreignObject",
|
|
1874
|
-
{
|
|
1875
|
-
width: w,
|
|
1876
|
-
height: h,
|
|
1877
|
-
className: c("content-wrapper"),
|
|
1878
|
-
children: /* @__PURE__ */ jsx2(
|
|
1879
|
-
"div",
|
|
1880
|
-
{
|
|
1881
|
-
className: c("content"),
|
|
1882
|
-
style: {
|
|
1883
|
-
width: `${w}px`,
|
|
1884
|
-
height: `${h}px`,
|
|
1885
|
-
overflow: "hidden"
|
|
1886
|
-
},
|
|
1887
|
-
children: this.content
|
|
1888
|
-
}
|
|
1889
|
-
)
|
|
2061
|
+
measure(isVertical) {
|
|
2062
|
+
const rect = this.content.getBoundingClientRect();
|
|
2063
|
+
const data = this.data;
|
|
2064
|
+
data.dims = { w: rect.width, h: rect.height };
|
|
2065
|
+
for (const dir of ["in", "out"]) {
|
|
2066
|
+
const ports = data.ports?.[dir];
|
|
2067
|
+
if (!ports) continue;
|
|
2068
|
+
for (const port of ports) {
|
|
2069
|
+
const el = this.content.querySelector(`.g3p-node-port[data-node-id="${data.id}"][data-port-id="${port.id}"]`);
|
|
2070
|
+
if (!el) continue;
|
|
2071
|
+
const portRect = el.getBoundingClientRect();
|
|
2072
|
+
if (isVertical) {
|
|
2073
|
+
port.offset = portRect.left - rect.left;
|
|
2074
|
+
port.size = portRect.width;
|
|
2075
|
+
} else {
|
|
2076
|
+
port.offset = portRect.top - rect.top;
|
|
2077
|
+
port.size = portRect.height;
|
|
1890
2078
|
}
|
|
1891
|
-
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
getPortPosition(dir) {
|
|
2083
|
+
const o = this.canvas.orientation;
|
|
2084
|
+
if (dir === "in") {
|
|
2085
|
+
if (o === "TB") return "top";
|
|
2086
|
+
if (o === "BT") return "bottom";
|
|
2087
|
+
if (o === "LR") return "left";
|
|
2088
|
+
return "right";
|
|
2089
|
+
} else {
|
|
2090
|
+
if (o === "TB") return "bottom";
|
|
2091
|
+
if (o === "BT") return "top";
|
|
2092
|
+
if (o === "LR") return "right";
|
|
2093
|
+
return "left";
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
isVerticalOrientation() {
|
|
2097
|
+
const o = this.canvas.orientation;
|
|
2098
|
+
return o === "TB" || o === "BT";
|
|
2099
|
+
}
|
|
2100
|
+
isReversedOrientation() {
|
|
2101
|
+
const o = this.canvas.orientation;
|
|
2102
|
+
return o === "BT" || o === "RL";
|
|
2103
|
+
}
|
|
2104
|
+
renderPortRow(dir, inout) {
|
|
2105
|
+
const ports = this.data?.ports?.[dir];
|
|
2106
|
+
if (!ports?.length) return null;
|
|
2107
|
+
const pos = this.getPortPosition(dir);
|
|
2108
|
+
const isVertical = this.isVerticalOrientation();
|
|
2109
|
+
const layoutClass = isVertical ? "row" : "col";
|
|
2110
|
+
const rotateLabels = false;
|
|
2111
|
+
const rotateClass = rotateLabels ? `port-rotated-${pos}` : "";
|
|
2112
|
+
return /* @__PURE__ */ jsx2("div", { className: `g3p-node-ports g3p-node-ports-${layoutClass}`, children: ports.map((port) => /* @__PURE__ */ jsx2(
|
|
2113
|
+
"div",
|
|
2114
|
+
{
|
|
2115
|
+
className: `g3p-node-port g3p-node-port-${inout}-${pos} ${rotateClass}`,
|
|
2116
|
+
"data-node-id": this.data.id,
|
|
2117
|
+
"data-port-id": port.id,
|
|
2118
|
+
children: port.label ?? port.id
|
|
2119
|
+
}
|
|
2120
|
+
)) });
|
|
2121
|
+
}
|
|
2122
|
+
renderInsidePorts(el) {
|
|
2123
|
+
const isVertical = this.isVerticalOrientation();
|
|
2124
|
+
const isReversed = this.isReversedOrientation();
|
|
2125
|
+
let inPorts = this.renderPortRow("in", "in");
|
|
2126
|
+
let outPorts = this.renderPortRow("out", "in");
|
|
2127
|
+
if (!inPorts && !outPorts) return el;
|
|
2128
|
+
if (isReversed) [inPorts, outPorts] = [outPorts, inPorts];
|
|
2129
|
+
const wrapperClass = isVertical ? "v" : "h";
|
|
2130
|
+
return /* @__PURE__ */ jsxs("div", { className: `g3p-node-with-ports g3p-node-with-ports-${wrapperClass}`, children: [
|
|
2131
|
+
inPorts,
|
|
2132
|
+
el,
|
|
2133
|
+
outPorts
|
|
1892
2134
|
] });
|
|
1893
2135
|
}
|
|
2136
|
+
renderOutsidePorts(el) {
|
|
2137
|
+
const isVertical = this.isVerticalOrientation();
|
|
2138
|
+
const isReversed = this.isReversedOrientation();
|
|
2139
|
+
let inPorts = this.renderPortRow("in", "out");
|
|
2140
|
+
let outPorts = this.renderPortRow("out", "out");
|
|
2141
|
+
if (!inPorts && !outPorts) return el;
|
|
2142
|
+
if (isReversed) [inPorts, outPorts] = [outPorts, inPorts];
|
|
2143
|
+
const wrapperClass = isVertical ? "v" : "h";
|
|
2144
|
+
return /* @__PURE__ */ jsxs("div", { className: `g3p-node-with-ports g3p-node-with-ports-${wrapperClass}`, children: [
|
|
2145
|
+
inPorts,
|
|
2146
|
+
el,
|
|
2147
|
+
outPorts
|
|
2148
|
+
] });
|
|
2149
|
+
}
|
|
2150
|
+
renderBorder(el) {
|
|
2151
|
+
return /* @__PURE__ */ jsx2("div", { className: "g3p-node-border", children: el });
|
|
2152
|
+
}
|
|
1894
2153
|
};
|
|
1895
2154
|
|
|
1896
|
-
// src/canvas/seg.css
|
|
1897
|
-
var seg_default = ".g3p-seg-container {\n transition: opacity 0.2s ease;\n}\n\n.g3p-seg-line {\n transition: stroke 0.2s ease, stroke-width 0.2s ease;\n}\n\n.g3p-seg-line.hovered {\n stroke-width: 4;\n opacity: 1;\n}\n\n.g3p-seg-line.selected {\n stroke: var(--graph-node-border-selected, #3b82f6);\n stroke-width: 3;\n}\n\n.g3p-seg-hitbox {\n cursor: pointer;\n}";
|
|
1898
|
-
|
|
1899
2155
|
// src/canvas/seg.tsx
|
|
1900
2156
|
import { jsx as jsx3, jsxs as jsxs2 } from "jsx-dom/jsx-runtime";
|
|
1901
2157
|
var Seg2 = class {
|
|
2158
|
+
id;
|
|
1902
2159
|
selected;
|
|
1903
2160
|
hovered;
|
|
1904
|
-
|
|
2161
|
+
canvas;
|
|
2162
|
+
type;
|
|
2163
|
+
svg;
|
|
2164
|
+
el;
|
|
2165
|
+
source;
|
|
2166
|
+
target;
|
|
2167
|
+
edgeIds;
|
|
2168
|
+
constructor(canvas, data, g) {
|
|
2169
|
+
this.id = data.id;
|
|
2170
|
+
this.canvas = canvas;
|
|
1905
2171
|
this.selected = false;
|
|
1906
2172
|
this.hovered = false;
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
this.
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
this.
|
|
1928
|
-
|
|
1929
|
-
handleMouseLeave(e) {
|
|
1930
|
-
this.onMouseLeave?.(this.edgeData, e);
|
|
1931
|
-
}
|
|
1932
|
-
handleContextMenu(e) {
|
|
1933
|
-
if (this.onContextMenu) {
|
|
1934
|
-
e.stopPropagation();
|
|
1935
|
-
this.onContextMenu(this.edgeData, e);
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
renderTerminals() {
|
|
1939
|
-
return {
|
|
1940
|
-
source: this.renderTerminal(this.attrs.sourceTerminal, "source"),
|
|
1941
|
-
target: this.renderTerminal(this.attrs.targetTerminal, "target")
|
|
1942
|
-
};
|
|
1943
|
-
}
|
|
1944
|
-
setSVG(svg) {
|
|
1945
|
-
this.svg = svg;
|
|
1946
|
-
const n = this.el.childElementCount;
|
|
1947
|
-
this.el.childNodes[n - 2].setAttribute("d", svg);
|
|
1948
|
-
this.el.childNodes[n - 1].setAttribute("d", svg);
|
|
2173
|
+
this.svg = data.svg;
|
|
2174
|
+
this.source = { ...data.source, isDummy: data.sourceNode(g).isDummy };
|
|
2175
|
+
this.target = { ...data.target, isDummy: data.targetNode(g).isDummy };
|
|
2176
|
+
this.type = data.type;
|
|
2177
|
+
this.edgeIds = data.edgeIds.toArray();
|
|
2178
|
+
this.el = this.render();
|
|
2179
|
+
}
|
|
2180
|
+
append() {
|
|
2181
|
+
this.canvas.group.appendChild(this.el);
|
|
2182
|
+
}
|
|
2183
|
+
remove() {
|
|
2184
|
+
this.el.remove();
|
|
2185
|
+
}
|
|
2186
|
+
update(data, g) {
|
|
2187
|
+
this.svg = data.svg;
|
|
2188
|
+
this.type = data.type;
|
|
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();
|
|
2192
|
+
this.remove();
|
|
2193
|
+
this.el = this.render();
|
|
2194
|
+
this.append();
|
|
1949
2195
|
}
|
|
1950
2196
|
render() {
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
strokeDasharray: this.attrs.style
|
|
1956
|
-
};
|
|
1957
|
-
const hoverAttrs = {
|
|
1958
|
-
...styleAttrs,
|
|
1959
|
-
strokeWidth: styleAttrs.strokeWidth ? Math.max(styleAttrs.strokeWidth * 3, 10) : void 0
|
|
1960
|
-
};
|
|
1961
|
-
const { source, target } = this.renderTerminals();
|
|
2197
|
+
let { source, target } = normalize(this);
|
|
2198
|
+
if (this.source.isDummy) source = void 0;
|
|
2199
|
+
if (this.target.isDummy) target = void 0;
|
|
2200
|
+
const typeClass = this.type ? `g3p-edge-type-${this.type}` : "";
|
|
1962
2201
|
return /* @__PURE__ */ jsxs2(
|
|
1963
2202
|
"g",
|
|
1964
2203
|
{
|
|
1965
2204
|
ref: (el) => this.el = el,
|
|
1966
|
-
id: `g3p-seg-${this.
|
|
1967
|
-
className:
|
|
1968
|
-
|
|
1969
|
-
onMouseEnter: this.handleMouseEnter.bind(this),
|
|
1970
|
-
onMouseLeave: this.handleMouseLeave.bind(this),
|
|
1971
|
-
onContextMenu: this.handleContextMenu.bind(this),
|
|
2205
|
+
id: `g3p-seg-${this.id}`,
|
|
2206
|
+
className: `g3p-seg-container ${typeClass}`.trim(),
|
|
2207
|
+
"data-edge-id": this.id,
|
|
1972
2208
|
children: [
|
|
1973
|
-
source?.defs,
|
|
1974
|
-
target?.defs,
|
|
1975
2209
|
/* @__PURE__ */ jsx3(
|
|
1976
2210
|
"path",
|
|
1977
2211
|
{
|
|
1978
2212
|
d: this.svg,
|
|
1979
|
-
...styleAttrs,
|
|
1980
2213
|
fill: "none",
|
|
1981
|
-
className:
|
|
1982
|
-
markerStart: source ? `url(
|
|
1983
|
-
markerEnd: target ? `url(
|
|
2214
|
+
className: "g3p-seg-line",
|
|
2215
|
+
markerStart: source ? `url(#g3p-marker-${source}-reverse)` : void 0,
|
|
2216
|
+
markerEnd: target ? `url(#g3p-marker-${target})` : void 0
|
|
1984
2217
|
}
|
|
1985
2218
|
),
|
|
1986
2219
|
/* @__PURE__ */ jsx3(
|
|
1987
2220
|
"path",
|
|
1988
2221
|
{
|
|
1989
2222
|
d: this.svg,
|
|
1990
|
-
...hoverAttrs,
|
|
1991
2223
|
stroke: "transparent",
|
|
1992
2224
|
fill: "none",
|
|
1993
|
-
className:
|
|
2225
|
+
className: "g3p-seg-hitbox",
|
|
1994
2226
|
style: { cursor: "pointer" }
|
|
1995
2227
|
}
|
|
1996
2228
|
)
|
|
@@ -1998,243 +2230,1396 @@ var Seg2 = class {
|
|
|
1998
2230
|
}
|
|
1999
2231
|
);
|
|
2000
2232
|
}
|
|
2001
|
-
renderTerminal(type, side) {
|
|
2002
|
-
if (!type)
|
|
2003
|
-
return null;
|
|
2004
|
-
const id = `g3p-seg-${this.segId}-${side}-${type}`;
|
|
2005
|
-
const defs = /* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsx3(
|
|
2006
|
-
"marker",
|
|
2007
|
-
{
|
|
2008
|
-
id,
|
|
2009
|
-
markerWidth: "10",
|
|
2010
|
-
markerHeight: "10",
|
|
2011
|
-
refX: "9",
|
|
2012
|
-
refY: "3",
|
|
2013
|
-
orient: "auto",
|
|
2014
|
-
markerUnits: "userSpaceOnUse",
|
|
2015
|
-
children: /* @__PURE__ */ jsx3("path", { d: "M0,0 L0,6 L9,3 z" })
|
|
2016
|
-
}
|
|
2017
|
-
) });
|
|
2018
|
-
return { id, defs };
|
|
2019
|
-
}
|
|
2020
2233
|
};
|
|
2021
2234
|
|
|
2022
|
-
// src/canvas/
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
renderNode,
|
|
2037
|
-
nodeStyle: () => ({}),
|
|
2038
|
-
edgeStyle: () => ({}),
|
|
2039
|
-
portStyle: "outside",
|
|
2040
|
-
classPrefix: "g3p",
|
|
2041
|
-
width: "100%",
|
|
2042
|
-
height: "100%",
|
|
2043
|
-
transform: { x: 0, y: 0, scale: 1 },
|
|
2044
|
-
bounds: { min: { x: 0, y: 0 }, max: { x: 1, y: 1 } },
|
|
2045
|
-
...options
|
|
2046
|
-
});
|
|
2047
|
-
this.nodes = /* @__PURE__ */ new Map();
|
|
2048
|
-
this.segs = /* @__PURE__ */ new Map();
|
|
2049
|
-
this.updating = false;
|
|
2050
|
-
this.createMeasurementContainer();
|
|
2051
|
-
}
|
|
2052
|
-
createMeasurementContainer() {
|
|
2053
|
-
this.measurement = document.createElement("div");
|
|
2054
|
-
this.measurement.style.cssText = `
|
|
2055
|
-
position: absolute;
|
|
2056
|
-
left: -9999px;
|
|
2057
|
-
top: -9999px;
|
|
2058
|
-
visibility: hidden;
|
|
2059
|
-
pointer-events: none;
|
|
2060
|
-
`;
|
|
2061
|
-
document.body.appendChild(this.measurement);
|
|
2062
|
-
}
|
|
2063
|
-
update(callback) {
|
|
2064
|
-
this.updating = true;
|
|
2065
|
-
callback();
|
|
2066
|
-
this.updating = false;
|
|
2067
|
-
let bx0 = Infinity, by0 = Infinity;
|
|
2068
|
-
let bx1 = -Infinity, by1 = -Infinity;
|
|
2069
|
-
for (const node of this.nodes.values()) {
|
|
2070
|
-
const nx0 = node.pos.x, nx1 = node.pos.x + node.dims.w;
|
|
2071
|
-
const ny0 = node.pos.y, ny1 = node.pos.y + node.dims.h;
|
|
2072
|
-
bx0 = Math.min(bx0, nx0);
|
|
2073
|
-
by0 = Math.min(by0, ny0);
|
|
2074
|
-
bx1 = Math.max(bx1, nx1);
|
|
2075
|
-
by1 = Math.max(by1, ny1);
|
|
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();
|
|
2076
2249
|
}
|
|
2077
|
-
this.bounds = { min: { x: bx0, y: by0 }, max: { x: bx1, y: by1 } };
|
|
2078
|
-
console.log("bounds", this.bounds);
|
|
2079
|
-
this.root.setAttribute("viewBox", this.viewBox());
|
|
2080
2250
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
if (!node) throw new Error("node not found");
|
|
2084
|
-
if (!node.container) node.render();
|
|
2085
|
-
node.setPos(opts.pos);
|
|
2086
|
-
this.group.appendChild(node.container);
|
|
2087
|
-
}
|
|
2088
|
-
updateNode(opts) {
|
|
2089
|
-
const node = this.nodes.get(opts.data);
|
|
2090
|
-
if (!node) throw new Error("node not found");
|
|
2091
|
-
node.setPos(opts.pos);
|
|
2092
|
-
}
|
|
2093
|
-
deleteNode(opts) {
|
|
2094
|
-
const node = this.nodes.get(opts.data);
|
|
2095
|
-
if (!node) throw new Error("node not found");
|
|
2096
|
-
node.container.remove();
|
|
2097
|
-
}
|
|
2098
|
-
addSeg(opts) {
|
|
2099
|
-
const seg = new Seg2(opts);
|
|
2100
|
-
this.segs.set(seg.segId, seg);
|
|
2101
|
-
seg.render();
|
|
2102
|
-
this.group.appendChild(seg.el);
|
|
2103
|
-
}
|
|
2104
|
-
updateSeg(opts) {
|
|
2105
|
-
const seg = this.segs.get(opts.segId);
|
|
2106
|
-
if (!seg) throw new Error("seg not found");
|
|
2107
|
-
seg.setSVG(opts.svg);
|
|
2251
|
+
get isIdle() {
|
|
2252
|
+
return this._state.type === "idle";
|
|
2108
2253
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
if (!seg) throw new Error("seg not found");
|
|
2112
|
-
seg.el.remove();
|
|
2113
|
-
this.segs.delete(seg.segId);
|
|
2254
|
+
get isPanning() {
|
|
2255
|
+
return this._state.type === "panning";
|
|
2114
2256
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
for (const data of nodes) {
|
|
2118
|
-
if (this.nodes.has(data)) continue;
|
|
2119
|
-
const node = new Node2({
|
|
2120
|
-
data,
|
|
2121
|
-
renderNode: this.renderNode,
|
|
2122
|
-
classPrefix: this.classPrefix,
|
|
2123
|
-
isDummy: false
|
|
2124
|
-
});
|
|
2125
|
-
this.nodes.set(node.data, node);
|
|
2126
|
-
if (!node.measured) {
|
|
2127
|
-
this.measurement.appendChild(node.content);
|
|
2128
|
-
newNodes.push(node);
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
return new Promise((resolve) => {
|
|
2132
|
-
requestAnimationFrame(() => {
|
|
2133
|
-
for (const node of newNodes)
|
|
2134
|
-
node.getSize();
|
|
2135
|
-
this.measurement.textContent = "";
|
|
2136
|
-
resolve();
|
|
2137
|
-
});
|
|
2138
|
-
});
|
|
2257
|
+
get isCreatingEdge() {
|
|
2258
|
+
return this._state.type === "new-edge";
|
|
2139
2259
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2260
|
+
/** Start panning the canvas */
|
|
2261
|
+
startPan(startCanvas, startTransform) {
|
|
2262
|
+
this._state = { type: "panning", startCanvas, startTransform };
|
|
2142
2263
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
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
|
+
};
|
|
2145
2274
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
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
|
+
}
|
|
2148
2280
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
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
|
+
}
|
|
2151
2286
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
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;
|
|
2154
2293
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2294
|
+
/** Reset to idle state */
|
|
2295
|
+
reset() {
|
|
2296
|
+
this._state = { type: "idle" };
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
|
|
2300
|
+
// src/canvas/newEdge.tsx
|
|
2301
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "jsx-dom/jsx-runtime";
|
|
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
|
+
}
|
|
2405
|
+
};
|
|
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);
|
|
2427
|
+
}
|
|
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);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
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");
|
|
2790
|
+
var Canvas = class {
|
|
2791
|
+
container;
|
|
2792
|
+
root;
|
|
2793
|
+
group;
|
|
2794
|
+
transform;
|
|
2795
|
+
bounds;
|
|
2796
|
+
measurement;
|
|
2797
|
+
allNodes;
|
|
2798
|
+
curNodes;
|
|
2799
|
+
curSegs;
|
|
2800
|
+
updating;
|
|
2801
|
+
// Pan-zoom state
|
|
2802
|
+
panScale = null;
|
|
2803
|
+
zoomControls;
|
|
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) {
|
|
2812
|
+
Object.assign(this, options);
|
|
2813
|
+
this.api = api;
|
|
2814
|
+
this.allNodes = /* @__PURE__ */ new Map();
|
|
2815
|
+
this.curNodes = /* @__PURE__ */ new Map();
|
|
2816
|
+
this.curSegs = /* @__PURE__ */ new Map();
|
|
2817
|
+
this.updating = false;
|
|
2818
|
+
this.bounds = { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } };
|
|
2819
|
+
this.transform = { x: 0, y: 0, scale: 1 };
|
|
2820
|
+
this.editMode = new EditMode();
|
|
2821
|
+
this.editMode.editable = this.editable;
|
|
2822
|
+
this.createMeasurementContainer();
|
|
2823
|
+
this.createCanvasContainer();
|
|
2824
|
+
if (this.panZoom) this.setupPanZoom();
|
|
2825
|
+
}
|
|
2826
|
+
createMeasurementContainer() {
|
|
2827
|
+
this.measurement = document.createElement("div");
|
|
2828
|
+
this.measurement.style.cssText = `
|
|
2829
|
+
position: absolute;
|
|
2830
|
+
left: -9999px;
|
|
2831
|
+
top: -9999px;
|
|
2832
|
+
visibility: hidden;
|
|
2833
|
+
pointer-events: none;
|
|
2834
|
+
`;
|
|
2835
|
+
document.body.appendChild(this.measurement);
|
|
2836
|
+
}
|
|
2837
|
+
getNode(key) {
|
|
2838
|
+
const node = this.allNodes.get(key);
|
|
2839
|
+
if (!node) throw new Error(`node not found: ${key}`);
|
|
2840
|
+
return node;
|
|
2841
|
+
}
|
|
2842
|
+
update() {
|
|
2843
|
+
let bx0 = Infinity, by0 = Infinity;
|
|
2844
|
+
let bx1 = -Infinity, by1 = -Infinity;
|
|
2845
|
+
for (const node of this.curNodes.values()) {
|
|
2846
|
+
const { x, y } = node.pos;
|
|
2847
|
+
const { w, h } = node.data.dims;
|
|
2848
|
+
const nx0 = x, nx1 = x + w;
|
|
2849
|
+
const ny0 = y, ny1 = y + h;
|
|
2850
|
+
bx0 = Math.min(bx0, nx0);
|
|
2851
|
+
by0 = Math.min(by0, ny0);
|
|
2852
|
+
bx1 = Math.max(bx1, nx1);
|
|
2853
|
+
by1 = Math.max(by1, ny1);
|
|
2854
|
+
}
|
|
2855
|
+
this.bounds = { min: { x: bx0, y: by0 }, max: { x: bx1, y: by1 } };
|
|
2856
|
+
this.root.setAttribute("viewBox", this.viewBox());
|
|
2857
|
+
}
|
|
2858
|
+
addNode(gnode) {
|
|
2859
|
+
if (this.curNodes.has(gnode.id))
|
|
2860
|
+
throw new Error("node already exists");
|
|
2861
|
+
const { key } = gnode;
|
|
2862
|
+
let node;
|
|
2863
|
+
if (gnode.isDummy) {
|
|
2864
|
+
node = new Node2(this, gnode, true);
|
|
2865
|
+
node.renderContainer();
|
|
2866
|
+
this.allNodes.set(key, node);
|
|
2867
|
+
} else {
|
|
2868
|
+
if (!this.allNodes.has(key))
|
|
2869
|
+
throw new Error("node has not been measured");
|
|
2870
|
+
node = this.getNode(key);
|
|
2871
|
+
}
|
|
2872
|
+
this.curNodes.set(gnode.id, node);
|
|
2873
|
+
node.append();
|
|
2874
|
+
node.setPos(gnode.pos);
|
|
2875
|
+
}
|
|
2876
|
+
updateNode(gnode) {
|
|
2877
|
+
if (gnode.isDummy) throw new Error("dummy node cannot be updated");
|
|
2878
|
+
const node = this.getNode(gnode.key);
|
|
2879
|
+
const cur = this.curNodes.get(gnode.id);
|
|
2880
|
+
if (cur) cur.remove();
|
|
2881
|
+
this.curNodes.set(gnode.id, node);
|
|
2882
|
+
node.append();
|
|
2883
|
+
}
|
|
2884
|
+
deleteNode(gnode) {
|
|
2885
|
+
const node = this.getNode(gnode.key);
|
|
2886
|
+
this.curNodes.delete(gnode.id);
|
|
2887
|
+
node.remove();
|
|
2888
|
+
}
|
|
2889
|
+
addSeg(gseg, g) {
|
|
2890
|
+
if (this.curSegs.has(gseg.id))
|
|
2891
|
+
throw new Error("seg already exists");
|
|
2892
|
+
const seg = new Seg2(this, gseg, g);
|
|
2893
|
+
this.curSegs.set(gseg.id, seg);
|
|
2894
|
+
seg.append();
|
|
2895
|
+
}
|
|
2896
|
+
updateSeg(gseg, g) {
|
|
2897
|
+
const seg = this.curSegs.get(gseg.id);
|
|
2898
|
+
if (!seg) throw new Error("seg not found");
|
|
2899
|
+
seg.update(gseg, g);
|
|
2900
|
+
}
|
|
2901
|
+
deleteSeg(gseg) {
|
|
2902
|
+
const seg = this.curSegs.get(gseg.id);
|
|
2903
|
+
if (!seg) throw new Error("seg not found");
|
|
2904
|
+
this.curSegs.delete(gseg.id);
|
|
2905
|
+
seg.remove();
|
|
2906
|
+
}
|
|
2907
|
+
async measureNodes(nodes) {
|
|
2908
|
+
const newNodes = /* @__PURE__ */ new Map();
|
|
2909
|
+
for (const data of nodes) {
|
|
2910
|
+
const node = new Node2(this, data);
|
|
2911
|
+
newNodes.set(data.data, node);
|
|
2912
|
+
this.measurement.appendChild(node.content);
|
|
2913
|
+
}
|
|
2914
|
+
await new Promise(requestAnimationFrame);
|
|
2915
|
+
const isVertical = this.orientation === "TB" || this.orientation === "BT";
|
|
2916
|
+
for (const node of newNodes.values()) {
|
|
2917
|
+
node.measure(isVertical);
|
|
2918
|
+
const { id, version } = node.data;
|
|
2919
|
+
const key = `k:${id}:${version}`;
|
|
2920
|
+
this.allNodes.set(key, node);
|
|
2921
|
+
node.renderContainer();
|
|
2922
|
+
}
|
|
2923
|
+
this.measurement.innerHTML = "";
|
|
2924
|
+
return newNodes;
|
|
2925
|
+
}
|
|
2926
|
+
// ========== Mouse event handlers ==========
|
|
2927
|
+
onClick(e) {
|
|
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);
|
|
2993
|
+
}
|
|
2994
|
+
onContextMenu(e) {
|
|
2995
|
+
}
|
|
2996
|
+
groupTransform() {
|
|
2997
|
+
return `translate(${this.transform.x}, ${this.transform.y}) scale(${this.transform.scale})`;
|
|
2998
|
+
}
|
|
2999
|
+
viewBox() {
|
|
3000
|
+
const p = this.padding;
|
|
3001
|
+
const x = this.bounds.min.x - p;
|
|
3002
|
+
const y = this.bounds.min.y - p;
|
|
3003
|
+
const w = this.bounds.max.x - this.bounds.min.x + p * 2;
|
|
3004
|
+
const h = this.bounds.max.y - this.bounds.min.y + p * 2;
|
|
3005
|
+
return `${x} ${y} ${w} ${h}`;
|
|
3006
|
+
}
|
|
3007
|
+
generateDynamicStyles() {
|
|
3008
|
+
let css = "";
|
|
3009
|
+
css += themeToCSS(this.theme, `.g3p-canvas-container`);
|
|
3010
|
+
for (const [type, vars] of Object.entries(this.nodeTypes)) {
|
|
3011
|
+
css += themeToCSS(vars, `.g3p-node-type-${type}`, "node");
|
|
3012
|
+
}
|
|
3013
|
+
for (const [type, vars] of Object.entries(this.edgeTypes)) {
|
|
3014
|
+
css += themeToCSS(vars, `.g3p-edge-type-${type}`);
|
|
3015
|
+
}
|
|
3016
|
+
return css;
|
|
3017
|
+
}
|
|
3018
|
+
createCanvasContainer() {
|
|
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
|
+
}
|
|
3025
|
+
const dynamicStyles = this.generateDynamicStyles();
|
|
3026
|
+
if (dynamicStyles) {
|
|
3027
|
+
const dynamicStyleEl = document.createElement("style");
|
|
3028
|
+
dynamicStyleEl.textContent = dynamicStyles;
|
|
3029
|
+
document.head.appendChild(dynamicStyleEl);
|
|
3030
|
+
}
|
|
3031
|
+
const colorModeClass = this.colorMode !== "system" ? `g3p-${this.colorMode}` : "";
|
|
3032
|
+
this.container = /* @__PURE__ */ jsx6(
|
|
2158
3033
|
"div",
|
|
2159
3034
|
{
|
|
2160
|
-
className:
|
|
3035
|
+
className: `g3p-canvas-container ${colorModeClass}`.trim(),
|
|
2161
3036
|
ref: (el) => this.container = el,
|
|
2162
3037
|
onContextMenu: this.onContextMenu.bind(this),
|
|
2163
|
-
children: /* @__PURE__ */
|
|
3038
|
+
children: /* @__PURE__ */ jsxs5(
|
|
2164
3039
|
"svg",
|
|
2165
3040
|
{
|
|
2166
3041
|
ref: (el) => this.root = el,
|
|
2167
|
-
className:
|
|
3042
|
+
className: "g3p-canvas-root",
|
|
2168
3043
|
width: this.width,
|
|
2169
3044
|
height: this.height,
|
|
2170
3045
|
viewBox: this.viewBox(),
|
|
2171
3046
|
preserveAspectRatio: "xMidYMid meet",
|
|
2172
3047
|
onClick: this.onClick.bind(this),
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
{
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
}
|
|
2179
|
-
|
|
3048
|
+
onDblClick: this.onDoubleClick.bind(this),
|
|
3049
|
+
children: [
|
|
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))
|
|
3053
|
+
] }),
|
|
3054
|
+
/* @__PURE__ */ jsx6(
|
|
3055
|
+
"g",
|
|
3056
|
+
{
|
|
3057
|
+
ref: (el) => this.group = el,
|
|
3058
|
+
transform: this.groupTransform()
|
|
3059
|
+
}
|
|
3060
|
+
)
|
|
3061
|
+
]
|
|
2180
3062
|
}
|
|
2181
3063
|
)
|
|
2182
3064
|
}
|
|
2183
3065
|
);
|
|
2184
3066
|
}
|
|
3067
|
+
// ==================== Pan-Zoom ====================
|
|
3068
|
+
setupPanZoom() {
|
|
3069
|
+
this.container.addEventListener("wheel", this.onWheel.bind(this), { passive: false });
|
|
3070
|
+
this.container.addEventListener("mousedown", this.onMouseDown.bind(this));
|
|
3071
|
+
document.addEventListener("mousemove", this.onMouseMove.bind(this));
|
|
3072
|
+
document.addEventListener("mouseup", this.onMouseUp.bind(this));
|
|
3073
|
+
document.addEventListener("keydown", this.onKeyDown.bind(this));
|
|
3074
|
+
this.createZoomControls();
|
|
3075
|
+
}
|
|
3076
|
+
onKeyDown(e) {
|
|
3077
|
+
if (e.key === "Escape" && this.editMode.isCreatingEdge) {
|
|
3078
|
+
this.endNewEdge(true);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
/** Convert screen coordinates to canvas-relative coordinates */
|
|
3082
|
+
screenToCanvas(screen) {
|
|
3083
|
+
const rect = this.container.getBoundingClientRect();
|
|
3084
|
+
return canvasPos(screen.x - rect.left, screen.y - rect.top);
|
|
3085
|
+
}
|
|
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
|
+
/**
|
|
3101
|
+
* Get the effective scale from canvas pixels to graph units,
|
|
3102
|
+
* accounting for preserveAspectRatio="xMidYMid meet" which uses
|
|
3103
|
+
* the smaller scale (to fit) and centers the content.
|
|
3104
|
+
*/
|
|
3105
|
+
getEffectiveScale() {
|
|
3106
|
+
const vb = this.currentViewBox();
|
|
3107
|
+
const rect = this.container.getBoundingClientRect();
|
|
3108
|
+
const scaleX = vb.w / rect.width;
|
|
3109
|
+
const scaleY = vb.h / rect.height;
|
|
3110
|
+
const scale = Math.max(scaleX, scaleY);
|
|
3111
|
+
const actualW = rect.width * scale;
|
|
3112
|
+
const actualH = rect.height * scale;
|
|
3113
|
+
const offsetX = (actualW - vb.w) / 2;
|
|
3114
|
+
const offsetY = (actualH - vb.h) / 2;
|
|
3115
|
+
return { scale, offsetX, offsetY };
|
|
3116
|
+
}
|
|
3117
|
+
/** Get current viewBox as an object */
|
|
3118
|
+
currentViewBox() {
|
|
3119
|
+
const p = this.padding;
|
|
3120
|
+
const t = this.transform;
|
|
3121
|
+
const baseX = this.bounds.min.x - p;
|
|
3122
|
+
const baseY = this.bounds.min.y - p;
|
|
3123
|
+
const baseW = this.bounds.max.x - this.bounds.min.x + p * 2;
|
|
3124
|
+
const baseH = this.bounds.max.y - this.bounds.min.y + p * 2;
|
|
3125
|
+
const cx = baseX + baseW / 2;
|
|
3126
|
+
const cy = baseY + baseH / 2;
|
|
3127
|
+
const w = baseW / t.scale;
|
|
3128
|
+
const h = baseH / t.scale;
|
|
3129
|
+
const x = cx - w / 2 - t.x;
|
|
3130
|
+
const y = cy - h / 2 - t.y;
|
|
3131
|
+
return { x, y, w, h };
|
|
3132
|
+
}
|
|
3133
|
+
onWheel(e) {
|
|
3134
|
+
e.preventDefault();
|
|
3135
|
+
const zoomFactor = 1.1;
|
|
3136
|
+
const delta = e.deltaY > 0 ? 1 / zoomFactor : zoomFactor;
|
|
3137
|
+
const screenCursor = screenPos(e.clientX, e.clientY);
|
|
3138
|
+
const canvasCursor = this.screenToCanvas(screenCursor);
|
|
3139
|
+
const graphCursor = this.canvasToGraph(canvasCursor);
|
|
3140
|
+
const oldScale = this.transform.scale;
|
|
3141
|
+
const newScale = Math.max(0.1, Math.min(10, oldScale * delta));
|
|
3142
|
+
this.transform.scale = newScale;
|
|
3143
|
+
const newGraphCursor = this.canvasToGraph(canvasCursor);
|
|
3144
|
+
this.transform.x += newGraphCursor.x - graphCursor.x;
|
|
3145
|
+
this.transform.y += newGraphCursor.y - graphCursor.y;
|
|
3146
|
+
this.applyTransform();
|
|
3147
|
+
}
|
|
3148
|
+
onMouseDown(e) {
|
|
3149
|
+
if (e.button !== 0) return;
|
|
3150
|
+
if (e.target.closest(".g3p-zoom-controls")) return;
|
|
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
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
onMouseMove(e) {
|
|
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;
|
|
3194
|
+
const current = this.screenToCanvas(screenPos(e.clientX, e.clientY));
|
|
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;
|
|
3199
|
+
this.applyTransform();
|
|
3200
|
+
}
|
|
3201
|
+
onMouseUp(e) {
|
|
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();
|
|
3212
|
+
this.panScale = null;
|
|
3213
|
+
this.container.style.cursor = "";
|
|
3214
|
+
}
|
|
3215
|
+
applyTransform() {
|
|
3216
|
+
const vb = this.currentViewBox();
|
|
3217
|
+
this.root.setAttribute("viewBox", `${vb.x} ${vb.y} ${vb.w} ${vb.h}`);
|
|
3218
|
+
this.updateZoomLevel();
|
|
3219
|
+
}
|
|
3220
|
+
createZoomControls() {
|
|
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" })
|
|
3226
|
+
] });
|
|
3227
|
+
this.container.appendChild(this.zoomControls);
|
|
3228
|
+
}
|
|
3229
|
+
updateZoomLevel() {
|
|
3230
|
+
const level = this.container.querySelector("#g3p-zoom-level");
|
|
3231
|
+
if (level) {
|
|
3232
|
+
level.textContent = `${Math.round(this.transform.scale * 100)}%`;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
zoomIn() {
|
|
3236
|
+
this.transform.scale = Math.min(10, this.transform.scale * 1.2);
|
|
3237
|
+
this.applyTransform();
|
|
3238
|
+
}
|
|
3239
|
+
zoomOut() {
|
|
3240
|
+
this.transform.scale = Math.max(0.1, this.transform.scale / 1.2);
|
|
3241
|
+
this.applyTransform();
|
|
3242
|
+
}
|
|
3243
|
+
zoomReset() {
|
|
3244
|
+
this.transform = { x: 0, y: 0, scale: 1 };
|
|
3245
|
+
this.applyTransform();
|
|
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
|
+
}
|
|
2185
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
|
+
}
|
|
2186
3445
|
|
|
2187
|
-
// src/
|
|
2188
|
-
import {
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
});
|
|
3446
|
+
// src/canvas/render-node.tsx
|
|
3447
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "jsx-dom/jsx-runtime";
|
|
3448
|
+
function renderNode(node, props) {
|
|
3449
|
+
if (typeof node == "string") node = { id: node };
|
|
3450
|
+
const title = node?.title ?? props?.title ?? node?.label ?? node?.name ?? node?.text ?? props?.text ?? node?.id ?? "?";
|
|
3451
|
+
const detail = node?.detail ?? node?.description ?? node?.subtitle;
|
|
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 })
|
|
3456
|
+
] });
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
// src/api/defaults.ts
|
|
3460
|
+
function applyDefaults(options) {
|
|
3461
|
+
const { graph: graph2, canvas, props } = defaults();
|
|
3462
|
+
return {
|
|
3463
|
+
graph: { ...graph2, ...options?.graph },
|
|
3464
|
+
canvas: { ...canvas, ...options?.canvas },
|
|
3465
|
+
props: { ...props, ...options?.props }
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
function defaults() {
|
|
3469
|
+
return {
|
|
3470
|
+
graph: {
|
|
3471
|
+
mergeOrder: ["target", "source"],
|
|
3472
|
+
nodeMargin: 15,
|
|
3473
|
+
dummyNodeSize: 15,
|
|
3474
|
+
nodeAlign: "natural",
|
|
3475
|
+
edgeSpacing: 10,
|
|
3476
|
+
turnRadius: 10,
|
|
3477
|
+
orientation: "TB",
|
|
3478
|
+
layerMargin: 5,
|
|
3479
|
+
alignIterations: 5,
|
|
3480
|
+
alignThreshold: 10,
|
|
3481
|
+
separateTrackSets: true,
|
|
3482
|
+
markerSize: 10,
|
|
3483
|
+
layoutSteps: null
|
|
3484
|
+
},
|
|
3485
|
+
canvas: {
|
|
3486
|
+
renderNode,
|
|
3487
|
+
width: "100%",
|
|
3488
|
+
height: "100%",
|
|
3489
|
+
padding: 20,
|
|
3490
|
+
editable: false,
|
|
3491
|
+
panZoom: true,
|
|
3492
|
+
markerSize: 10,
|
|
3493
|
+
colorMode: "system",
|
|
3494
|
+
theme: {},
|
|
3495
|
+
nodeTypes: {},
|
|
3496
|
+
edgeTypes: {}
|
|
3497
|
+
},
|
|
3498
|
+
props: {}
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
// src/api/updater.ts
|
|
3503
|
+
var Updater = class _Updater {
|
|
3504
|
+
update;
|
|
3505
|
+
constructor() {
|
|
3506
|
+
this.update = {
|
|
3507
|
+
addNodes: [],
|
|
3508
|
+
removeNodes: [],
|
|
3509
|
+
updateNodes: [],
|
|
3510
|
+
addEdges: [],
|
|
3511
|
+
removeEdges: [],
|
|
3512
|
+
updateEdges: []
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
describe(desc) {
|
|
3516
|
+
this.update.description = desc;
|
|
3517
|
+
return this;
|
|
3518
|
+
}
|
|
3519
|
+
addNode(node) {
|
|
3520
|
+
this.update.addNodes.push(node);
|
|
3521
|
+
return this;
|
|
3522
|
+
}
|
|
3523
|
+
deleteNode(node) {
|
|
3524
|
+
this.update.removeNodes.push(node);
|
|
3525
|
+
return this;
|
|
3526
|
+
}
|
|
3527
|
+
updateNode(node) {
|
|
3528
|
+
this.update.updateNodes.push(node);
|
|
3529
|
+
return this;
|
|
3530
|
+
}
|
|
3531
|
+
addEdge(edge) {
|
|
3532
|
+
this.update.addEdges.push(edge);
|
|
3533
|
+
return this;
|
|
3534
|
+
}
|
|
3535
|
+
deleteEdge(edge) {
|
|
3536
|
+
this.update.removeEdges.push(edge);
|
|
3537
|
+
return this;
|
|
3538
|
+
}
|
|
3539
|
+
updateEdge(edge) {
|
|
3540
|
+
this.update.updateEdges.push(edge);
|
|
3541
|
+
return this;
|
|
3542
|
+
}
|
|
3543
|
+
static add(nodes, edges) {
|
|
3544
|
+
const updater = new _Updater();
|
|
3545
|
+
updater.update.addNodes = nodes;
|
|
3546
|
+
updater.update.addEdges = edges;
|
|
3547
|
+
return updater;
|
|
3548
|
+
}
|
|
3549
|
+
};
|
|
3550
|
+
|
|
3551
|
+
// src/api/api.ts
|
|
3552
|
+
var log11 = logger("api");
|
|
2203
3553
|
var API = class {
|
|
2204
3554
|
state;
|
|
2205
3555
|
seq;
|
|
2206
3556
|
index;
|
|
2207
3557
|
canvas;
|
|
2208
|
-
_options;
|
|
2209
3558
|
options;
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
this.
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
};
|
|
2228
|
-
let graph2 = new Graph({ options: this._options });
|
|
2229
|
-
this.state.graph = graph2;
|
|
3559
|
+
history;
|
|
3560
|
+
nodeIds;
|
|
3561
|
+
edgeIds;
|
|
3562
|
+
nodeVersions;
|
|
3563
|
+
nodeOverrides;
|
|
3564
|
+
edgeOverrides;
|
|
3565
|
+
nodeFields;
|
|
3566
|
+
nextNodeId;
|
|
3567
|
+
nextEdgeId;
|
|
3568
|
+
events;
|
|
3569
|
+
root;
|
|
3570
|
+
constructor(args) {
|
|
3571
|
+
this.root = args.root;
|
|
3572
|
+
this.options = applyDefaults(args.options);
|
|
3573
|
+
let graph2 = new Graph({ options: this.options.graph });
|
|
3574
|
+
this.state = { graph: graph2, update: null };
|
|
3575
|
+
this.events = args.events || {};
|
|
2230
3576
|
this.seq = [this.state];
|
|
2231
3577
|
this.index = 0;
|
|
2232
|
-
this.
|
|
2233
|
-
this.
|
|
3578
|
+
this.nodeIds = /* @__PURE__ */ new Map();
|
|
3579
|
+
this.edgeIds = /* @__PURE__ */ new Map();
|
|
3580
|
+
this.nodeVersions = /* @__PURE__ */ new Map();
|
|
3581
|
+
this.nodeOverrides = /* @__PURE__ */ new Map();
|
|
3582
|
+
this.edgeOverrides = /* @__PURE__ */ new Map();
|
|
3583
|
+
this.nodeFields = /* @__PURE__ */ new Map();
|
|
3584
|
+
this.nextNodeId = 1;
|
|
3585
|
+
this.nextEdgeId = 1;
|
|
3586
|
+
this.canvas = new Canvas(this, {
|
|
3587
|
+
...this.options.canvas,
|
|
3588
|
+
dummyNodeSize: this.options.graph.dummyNodeSize,
|
|
3589
|
+
orientation: this.options.graph.orientation
|
|
3590
|
+
});
|
|
3591
|
+
if (args.history) {
|
|
3592
|
+
this.history = args.history;
|
|
3593
|
+
} else if (args.nodes) {
|
|
3594
|
+
this.history = [Updater.add(args.nodes, args.edges || []).update];
|
|
3595
|
+
} else {
|
|
3596
|
+
this.history = [];
|
|
3597
|
+
}
|
|
2234
3598
|
}
|
|
2235
|
-
|
|
2236
|
-
|
|
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;
|
|
2237
3610
|
}
|
|
3611
|
+
get graph() {
|
|
3612
|
+
return this.state.graph;
|
|
3613
|
+
}
|
|
3614
|
+
/** Initialize the API */
|
|
3615
|
+
async init() {
|
|
3616
|
+
const root = document.getElementById(this.root);
|
|
3617
|
+
if (!root) throw new Error("root element not found");
|
|
3618
|
+
root.appendChild(this.canvas.container);
|
|
3619
|
+
for (const update of this.history)
|
|
3620
|
+
await this.applyUpdate(update);
|
|
3621
|
+
}
|
|
3622
|
+
/** Navigate to a different state */
|
|
2238
3623
|
nav(nav) {
|
|
2239
3624
|
let newIndex;
|
|
2240
3625
|
switch (nav) {
|
|
@@ -2256,238 +3641,409 @@ var API = class {
|
|
|
2256
3641
|
this.applyDiff(this.index, newIndex);
|
|
2257
3642
|
this.index = newIndex;
|
|
2258
3643
|
this.state = this.seq[this.index];
|
|
3644
|
+
if (this.events.historyChange)
|
|
3645
|
+
this.events.historyChange(this.index, this.seq.length);
|
|
2259
3646
|
}
|
|
2260
3647
|
applyDiff(oldIndex, newIndex) {
|
|
2261
|
-
const
|
|
2262
|
-
const
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
if (!oldState.nodes.has(newNode.id))
|
|
2277
|
-
this.canvas.addNode(newNode);
|
|
3648
|
+
const oldGraph = this.seq[oldIndex].graph;
|
|
3649
|
+
const newGraph = this.seq[newIndex].graph;
|
|
3650
|
+
for (const oldNode of oldGraph.nodes.values()) {
|
|
3651
|
+
const newNode = newGraph.nodes.get(oldNode.id);
|
|
3652
|
+
if (!newNode) this.canvas.deleteNode(oldNode);
|
|
3653
|
+
}
|
|
3654
|
+
for (const newNode of newGraph.nodes.values()) {
|
|
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);
|
|
2278
3663
|
}
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
3664
|
+
}
|
|
3665
|
+
for (const oldSeg of oldGraph.segs.values()) {
|
|
3666
|
+
const newSeg = newGraph.segs.get(oldSeg.id);
|
|
3667
|
+
if (!newSeg) {
|
|
3668
|
+
this.canvas.deleteSeg(oldSeg);
|
|
3669
|
+
} else if (oldSeg !== newSeg) {
|
|
3670
|
+
this.canvas.updateSeg(newSeg, newGraph);
|
|
2285
3671
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
3672
|
+
}
|
|
3673
|
+
for (const newSeg of newGraph.segs.values()) {
|
|
3674
|
+
if (!oldGraph.segs.has(newSeg.id)) {
|
|
3675
|
+
this.canvas.addSeg(newSeg, newGraph);
|
|
2289
3676
|
}
|
|
2290
|
-
}
|
|
3677
|
+
}
|
|
3678
|
+
this.canvas.update();
|
|
2291
3679
|
}
|
|
3680
|
+
/** Add a node */
|
|
2292
3681
|
async addNode(node) {
|
|
2293
3682
|
await this.update((update) => update.addNode(node));
|
|
2294
3683
|
}
|
|
3684
|
+
/** Delete a node */
|
|
2295
3685
|
async deleteNode(node) {
|
|
2296
3686
|
await this.update((update) => update.deleteNode(node));
|
|
2297
3687
|
}
|
|
3688
|
+
/** Update a node */
|
|
2298
3689
|
async updateNode(node) {
|
|
2299
3690
|
await this.update((update) => update.updateNode(node));
|
|
2300
3691
|
}
|
|
3692
|
+
/** Add an edge */
|
|
2301
3693
|
async addEdge(edge) {
|
|
2302
3694
|
await this.update((update) => update.addEdge(edge));
|
|
2303
3695
|
}
|
|
3696
|
+
/** Delete an edge */
|
|
2304
3697
|
async deleteEdge(edge) {
|
|
2305
3698
|
await this.update((update) => update.deleteEdge(edge));
|
|
2306
3699
|
}
|
|
3700
|
+
/** Update an edge */
|
|
3701
|
+
async updateEdge(edge) {
|
|
3702
|
+
await this.update((update) => update.updateEdge(edge));
|
|
3703
|
+
}
|
|
3704
|
+
/** Perform a batch of updates */
|
|
2307
3705
|
async update(callback) {
|
|
2308
|
-
const
|
|
2309
|
-
callback(
|
|
2310
|
-
await this.
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
for (const node of
|
|
2319
|
-
|
|
2320
|
-
for (const edge of
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
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
|
+
}
|
|
3721
|
+
async applyUpdate(update) {
|
|
3722
|
+
log11.info("applyUpdate", update);
|
|
3723
|
+
const nodes = await this.measureNodes(update);
|
|
3724
|
+
const graph2 = this.state.graph.withMutations((mut) => {
|
|
3725
|
+
for (const edge of update.removeEdges ?? [])
|
|
3726
|
+
this._removeEdge(edge, mut);
|
|
3727
|
+
for (const node of update.removeNodes ?? [])
|
|
2325
3728
|
this._removeNode(node, mut);
|
|
2326
|
-
for (const
|
|
3729
|
+
for (const node of update.addNodes ?? [])
|
|
3730
|
+
this._addNode(nodes.get(node), mut);
|
|
3731
|
+
for (const node of update.updateNodes ?? [])
|
|
3732
|
+
this._updateNode(nodes.get(node), mut);
|
|
3733
|
+
for (const edge of update.addEdges ?? [])
|
|
2327
3734
|
this._addEdge(edge, mut);
|
|
2328
|
-
for (const edge of update.
|
|
2329
|
-
this.
|
|
3735
|
+
for (const edge of update.updateEdges ?? [])
|
|
3736
|
+
this._updateEdge(edge, mut);
|
|
3737
|
+
this.nodeOverrides.clear();
|
|
3738
|
+
this.edgeOverrides.clear();
|
|
2330
3739
|
});
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
const node = newGraph.getNode(nodeId);
|
|
2334
|
-
console.log(`got pos of node ${nodeId}:`, node.pos);
|
|
2335
|
-
if (node.isDummy) {
|
|
2336
|
-
this.state.nodes.set(nodeId, { id: nodeId, pos: node.pos, isDummy: true });
|
|
2337
|
-
} else {
|
|
2338
|
-
const myNode = this.state.nodes.get(nodeId);
|
|
2339
|
-
this.state.nodes.set(nodeId, { ...myNode, pos: node.pos });
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
for (const nodeId of newGraph.delNodes)
|
|
2343
|
-
this.state.nodes.delete(nodeId);
|
|
2344
|
-
for (const segId of newGraph.delSegs)
|
|
2345
|
-
this.state.segs.delete(segId);
|
|
2346
|
-
for (const segId of newGraph.dirtySegs) {
|
|
2347
|
-
const seg = newGraph.getSeg(segId);
|
|
2348
|
-
const edge = this.state.edges.get(seg.edgeIds.values().next().value);
|
|
2349
|
-
const target = seg.targetNode(newGraph);
|
|
2350
|
-
this.state.segs.set(seg.id, {
|
|
2351
|
-
segId: seg.id,
|
|
2352
|
-
edgeId: edge.id,
|
|
2353
|
-
svg: seg.svg,
|
|
2354
|
-
attrs: edge.attrs,
|
|
2355
|
-
targetDummy: target.isDummy,
|
|
2356
|
-
edgeData: edge.data
|
|
2357
|
-
});
|
|
2358
|
-
}
|
|
2359
|
-
this.state = {
|
|
2360
|
-
nodes: this.state.nodes.asImmutable(),
|
|
2361
|
-
edges: this.state.edges.asImmutable(),
|
|
2362
|
-
ports: this.state.ports.asImmutable(),
|
|
2363
|
-
segs: this.state.segs.asImmutable(),
|
|
2364
|
-
graph: newGraph,
|
|
2365
|
-
update
|
|
2366
|
-
};
|
|
3740
|
+
this.state = { graph: graph2, update };
|
|
3741
|
+
this.setNodePositions();
|
|
2367
3742
|
this.seq.splice(this.index + 1);
|
|
2368
3743
|
this.seq.push(this.state);
|
|
2369
3744
|
this.nav("last");
|
|
3745
|
+
if (this.events.historyChange)
|
|
3746
|
+
this.events.historyChange(this.index, this.seq.length);
|
|
3747
|
+
}
|
|
3748
|
+
setNodePositions() {
|
|
3749
|
+
const { graph: graph2 } = this.state;
|
|
3750
|
+
for (const nodeId of graph2.dirtyNodes) {
|
|
3751
|
+
const node = graph2.getNode(nodeId);
|
|
3752
|
+
if (!node.isDummy)
|
|
3753
|
+
this.canvas.getNode(node.key).setPos(node.pos);
|
|
3754
|
+
}
|
|
2370
3755
|
}
|
|
2371
3756
|
async measureNodes(update) {
|
|
2372
|
-
const
|
|
2373
|
-
|
|
3757
|
+
const data = [];
|
|
3758
|
+
for (const set of [update.updateNodes, update.addNodes])
|
|
3759
|
+
for (const node of set ?? [])
|
|
3760
|
+
data.push(this.parseNode(node, true));
|
|
3761
|
+
return await this.canvas.measureNodes(data);
|
|
3762
|
+
}
|
|
3763
|
+
parseNode(data, bumpVersion = false) {
|
|
3764
|
+
const get = this.options.props.node;
|
|
3765
|
+
let props;
|
|
3766
|
+
if (get) props = get(data);
|
|
3767
|
+
else if (!data) throw new Error(`invalid node ${data}`);
|
|
3768
|
+
else if (typeof data == "string") props = { id: data };
|
|
3769
|
+
else if (typeof data == "object") props = 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 };
|
|
3774
|
+
let { id, title, text, type, render } = props;
|
|
3775
|
+
id ??= this.getNodeId(data);
|
|
3776
|
+
const ports = this.parsePorts(props.ports);
|
|
3777
|
+
let version = this.nodeVersions.get(data);
|
|
3778
|
+
if (!version) version = 1;
|
|
3779
|
+
else if (bumpVersion) version++;
|
|
3780
|
+
this.nodeVersions.set(data, version);
|
|
3781
|
+
return { id, data, ports, title, text, type, render, version };
|
|
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
|
+
}
|
|
2374
3794
|
}
|
|
2375
|
-
|
|
2376
|
-
return this.
|
|
3795
|
+
getNodeFields() {
|
|
3796
|
+
return this.nodeFields;
|
|
3797
|
+
}
|
|
3798
|
+
parseEdge(data) {
|
|
3799
|
+
const get = this.options.props.edge;
|
|
3800
|
+
let props;
|
|
3801
|
+
if (get) props = get(data);
|
|
3802
|
+
else if (!data) throw new Error(`invalid edge ${data}`);
|
|
3803
|
+
else if (typeof data == "string") props = this.parseStringEdge(data);
|
|
3804
|
+
else if (typeof data == "object") props = data;
|
|
3805
|
+
else throw new Error(`invalid edge ${data}`);
|
|
3806
|
+
const overrides = this.edgeOverrides.get(data);
|
|
3807
|
+
if (overrides) props = { ...props, ...overrides };
|
|
3808
|
+
let { id, source, target, type } = props;
|
|
3809
|
+
if (!id) id = this.getEdgeId(data);
|
|
3810
|
+
source = this.parseEdgeEnd(source);
|
|
3811
|
+
target = this.parseEdgeEnd(target);
|
|
3812
|
+
const edge = { id, source, target, type, data };
|
|
3813
|
+
return edge;
|
|
2377
3814
|
}
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
if (
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
3815
|
+
parseEdgeEnd(end) {
|
|
3816
|
+
if (!end) throw new Error(`edge has an undefined source or target`);
|
|
3817
|
+
if (typeof end == "string") return { id: end };
|
|
3818
|
+
if (typeof end == "object") {
|
|
3819
|
+
const keys = Object.keys(end);
|
|
3820
|
+
const pidx = keys.indexOf("port");
|
|
3821
|
+
if (pidx != -1) {
|
|
3822
|
+
if (end.port !== void 0 && typeof end.port != "string") return end;
|
|
3823
|
+
keys.splice(pidx, 1);
|
|
3824
|
+
}
|
|
3825
|
+
if (keys.length != 1) return end;
|
|
3826
|
+
if (keys[0] == "id") return end;
|
|
3827
|
+
if (keys[0] != "node") return end;
|
|
3828
|
+
const id = this.nodeIds.get(end.node);
|
|
3829
|
+
if (!id) throw new Error(`edge end references unknown node ${end.node}`);
|
|
3830
|
+
return { id, port: end.port };
|
|
3831
|
+
}
|
|
3832
|
+
throw new Error(`invalid edge end ${end}`);
|
|
2387
3833
|
}
|
|
2388
|
-
|
|
2389
|
-
const
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
const
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
3834
|
+
parseStringEdge(str) {
|
|
3835
|
+
const [source, target] = str.split(/\s*(?::|-+>?)\s*/);
|
|
3836
|
+
return { source, target };
|
|
3837
|
+
}
|
|
3838
|
+
parsePorts(ports) {
|
|
3839
|
+
const fixed = {};
|
|
3840
|
+
for (const key of ["in", "out"]) {
|
|
3841
|
+
if (ports?.[key] && ports[key].length > 0)
|
|
3842
|
+
fixed[key] = ports[key].map((port) => typeof port == "string" ? { id: port } : port);
|
|
3843
|
+
}
|
|
3844
|
+
return fixed;
|
|
3845
|
+
}
|
|
3846
|
+
getNode(id) {
|
|
3847
|
+
return this.graph.getNode(id);
|
|
3848
|
+
}
|
|
3849
|
+
getEdge(id) {
|
|
3850
|
+
return this.graph.getEdge(id);
|
|
3851
|
+
}
|
|
3852
|
+
getNodeId(node) {
|
|
3853
|
+
let id = this.nodeIds.get(node);
|
|
3854
|
+
if (!id) {
|
|
3855
|
+
id = `n${this.nextNodeId++}`;
|
|
3856
|
+
this.nodeIds.set(node, id);
|
|
3857
|
+
}
|
|
3858
|
+
return id;
|
|
3859
|
+
}
|
|
3860
|
+
getEdgeId(edge) {
|
|
3861
|
+
let id = this.edgeIds.get(edge);
|
|
3862
|
+
if (!id) {
|
|
3863
|
+
id = `e${this.nextEdgeId++}`;
|
|
3864
|
+
this.edgeIds.set(edge, id);
|
|
2401
3865
|
}
|
|
3866
|
+
return id;
|
|
2402
3867
|
}
|
|
2403
3868
|
_addNode(node, mut) {
|
|
2404
|
-
const
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
this.state.nodes.set(props.id, data);
|
|
2411
|
-
console.log("adding node:", { ...props, dims: this.getDims(node) });
|
|
2412
|
-
mut.addNode({ ...props, dims: this.getDims(node) });
|
|
3869
|
+
const { data, id: newId } = node.data;
|
|
3870
|
+
const oldId = this.nodeIds.get(data);
|
|
3871
|
+
if (oldId && oldId != newId)
|
|
3872
|
+
throw new Error(`node id of ${data} changed from ${oldId} to ${newId}`);
|
|
3873
|
+
this.nodeIds.set(data, newId);
|
|
3874
|
+
mut.addNode(node.data);
|
|
2413
3875
|
}
|
|
2414
3876
|
_removeNode(node, mut) {
|
|
2415
|
-
const
|
|
2416
|
-
if (!
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
3877
|
+
const id = this.nodeIds.get(node);
|
|
3878
|
+
if (!id) throw new Error(`removing node ${JSON.stringify(node)} which does not exist`);
|
|
3879
|
+
mut.removeNode({ id });
|
|
3880
|
+
}
|
|
3881
|
+
_updateNode(node, mut) {
|
|
3882
|
+
const { data, id: newId } = node.data;
|
|
3883
|
+
const oldId = this.nodeIds.get(data);
|
|
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} `);
|
|
3886
|
+
mut.updateNode(node.data);
|
|
2420
3887
|
}
|
|
2421
3888
|
_addEdge(edge, mut) {
|
|
2422
|
-
const
|
|
2423
|
-
const id =
|
|
2424
|
-
if (
|
|
2425
|
-
throw new Error(`edge ${
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
this.state.edges.set(id, data);
|
|
2429
|
-
mut.addEdge(props);
|
|
3889
|
+
const data = this.parseEdge(edge);
|
|
3890
|
+
const id = this.edgeIds.get(edge);
|
|
3891
|
+
if (id && id != data.id)
|
|
3892
|
+
throw new Error(`edge id changed from ${id} to ${data.id} `);
|
|
3893
|
+
this.edgeIds.set(edge, data.id);
|
|
3894
|
+
mut.addEdge(data);
|
|
2430
3895
|
}
|
|
2431
3896
|
_removeEdge(edge, mut) {
|
|
2432
|
-
const
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
throw new Error(`removing edge ${str} which does not exist`);
|
|
2436
|
-
this.state.edges.delete(id);
|
|
2437
|
-
mut.removeEdge(props);
|
|
2438
|
-
}
|
|
2439
|
-
_onOptionChange(prop) {
|
|
2440
|
-
}
|
|
2441
|
-
};
|
|
2442
|
-
var Update = class {
|
|
2443
|
-
addedNodes;
|
|
2444
|
-
removedNodes;
|
|
2445
|
-
updatedNodes;
|
|
2446
|
-
addedEdges;
|
|
2447
|
-
removedEdges;
|
|
2448
|
-
updatedEdges;
|
|
2449
|
-
desc;
|
|
2450
|
-
constructor() {
|
|
2451
|
-
this.addedNodes = [];
|
|
2452
|
-
this.removedNodes = [];
|
|
2453
|
-
this.updatedNodes = [];
|
|
2454
|
-
this.addedEdges = [];
|
|
2455
|
-
this.removedEdges = [];
|
|
2456
|
-
this.updatedEdges = [];
|
|
3897
|
+
const id = this.edgeIds.get(edge);
|
|
3898
|
+
if (!id) throw new Error(`removing edge ${JSON.stringify(edge)} which does not exist`);
|
|
3899
|
+
mut.removeEdge(this.parseEdge(edge));
|
|
2457
3900
|
}
|
|
2458
|
-
|
|
2459
|
-
|
|
3901
|
+
_updateEdge(edge, mut) {
|
|
3902
|
+
const id = this.edgeIds.get(edge);
|
|
3903
|
+
if (!id) throw new Error(`updating unknown edge ${JSON.stringify(edge)} `);
|
|
3904
|
+
const data = this.parseEdge(edge);
|
|
3905
|
+
if (data.id !== id) throw new Error(`edge id changed from ${id} to ${data.id} `);
|
|
3906
|
+
mut.updateEdge(data);
|
|
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
|
+
});
|
|
2460
3935
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
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
|
+
});
|
|
2463
3962
|
}
|
|
2464
|
-
|
|
2465
|
-
this.
|
|
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
|
+
}
|
|
2466
3980
|
}
|
|
2467
|
-
|
|
2468
|
-
this.
|
|
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
|
+
});
|
|
2469
4009
|
}
|
|
2470
|
-
|
|
2471
|
-
|
|
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);
|
|
2472
4022
|
}
|
|
2473
|
-
|
|
2474
|
-
this.
|
|
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);
|
|
2475
4031
|
}
|
|
2476
|
-
|
|
2477
|
-
this.
|
|
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);
|
|
2478
4040
|
}
|
|
2479
4041
|
};
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
for (const node of nodes)
|
|
2486
|
-
update.addNode(node);
|
|
2487
|
-
for (const edge of edges)
|
|
2488
|
-
update.addEdge(edge);
|
|
2489
|
-
});
|
|
2490
|
-
}
|
|
4042
|
+
|
|
4043
|
+
// src/index.ts
|
|
4044
|
+
async function graph(args = { root: "app" }) {
|
|
4045
|
+
const api = new API(args);
|
|
4046
|
+
await api.init();
|
|
2491
4047
|
return api;
|
|
2492
4048
|
}
|
|
2493
4049
|
var index_default = graph;
|