@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.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -25,23 +35,87 @@ __export(index_exports, {
|
|
|
25
35
|
});
|
|
26
36
|
module.exports = __toCommonJS(index_exports);
|
|
27
37
|
|
|
28
|
-
// src/
|
|
29
|
-
var import_jsx_runtime = require("jsx-dom/jsx-runtime");
|
|
30
|
-
function renderNode(node) {
|
|
31
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: node?.id || "" });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// src/graph/types/graph.ts
|
|
38
|
+
// src/graph/graph.ts
|
|
35
39
|
var import_immutable8 = require("immutable");
|
|
36
40
|
|
|
37
|
-
// src/graph/
|
|
41
|
+
// src/graph/node.ts
|
|
38
42
|
var import_immutable = require("immutable");
|
|
39
|
-
|
|
43
|
+
|
|
44
|
+
// src/log.ts
|
|
45
|
+
var import_pino = __toESM(require("pino"), 1);
|
|
46
|
+
var resolvedLevel = typeof globalThis !== "undefined" && globalThis.__LOG_LEVEL || typeof process !== "undefined" && process.env?.LOG_LEVEL || "debug";
|
|
47
|
+
var browserOpts = { asObject: true };
|
|
48
|
+
browserOpts.transmit = {
|
|
49
|
+
level: resolvedLevel,
|
|
50
|
+
send: (level, log12) => {
|
|
51
|
+
try {
|
|
52
|
+
const endpoint = typeof globalThis !== "undefined" && globalThis.__LOG_INGEST_URL || typeof process !== "undefined" && process.env?.LOG_INGEST_URL || void 0;
|
|
53
|
+
if (!endpoint || typeof window === "undefined") {
|
|
54
|
+
try {
|
|
55
|
+
console.debug("[graph-core] transmit skipped", { endpoint, hasWindow: typeof window !== "undefined", level });
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const line = JSON.stringify({ level, ...log12, ts: Date.now() }) + "\n";
|
|
61
|
+
try {
|
|
62
|
+
console.debug("[graph-core] transmit sending", { endpoint, level, bytes: line.length });
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
fetch(endpoint, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
mode: "no-cors",
|
|
68
|
+
credentials: "omit",
|
|
69
|
+
body: line,
|
|
70
|
+
keepalive: true
|
|
71
|
+
}).then(() => {
|
|
72
|
+
try {
|
|
73
|
+
console.debug("[graph-core] transmit fetch ok");
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}).catch((err) => {
|
|
77
|
+
try {
|
|
78
|
+
console.debug("[graph-core] transmit fetch error", err?.message || err);
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
} catch (e) {
|
|
83
|
+
try {
|
|
84
|
+
console.debug("[graph-core] transmit error", e?.message || e);
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var base = (0, import_pino.default)({
|
|
91
|
+
level: resolvedLevel,
|
|
92
|
+
browser: browserOpts
|
|
93
|
+
});
|
|
94
|
+
function logger(module2) {
|
|
95
|
+
const child = base.child({ module: module2 });
|
|
96
|
+
return {
|
|
97
|
+
error: (msg, ...args) => child.error({ args }, msg),
|
|
98
|
+
warn: (msg, ...args) => child.warn({ args }, msg),
|
|
99
|
+
info: (msg, ...args) => child.info({ args }, msg),
|
|
100
|
+
debug: (msg, ...args) => child.debug({ args }, msg)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
var log = logger("core");
|
|
104
|
+
|
|
105
|
+
// src/graph/node.ts
|
|
106
|
+
var log2 = logger("node");
|
|
107
|
+
var defNodeData = {
|
|
40
108
|
id: "",
|
|
109
|
+
data: void 0,
|
|
110
|
+
version: 0,
|
|
111
|
+
title: void 0,
|
|
112
|
+
text: void 0,
|
|
113
|
+
type: void 0,
|
|
114
|
+
render: void 0,
|
|
115
|
+
ports: {},
|
|
41
116
|
aligned: {},
|
|
42
117
|
edges: { in: (0, import_immutable.Set)(), out: (0, import_immutable.Set)() },
|
|
43
118
|
segs: { in: (0, import_immutable.Set)(), out: (0, import_immutable.Set)() },
|
|
44
|
-
ports: { in: [], out: [] },
|
|
45
119
|
layerId: "",
|
|
46
120
|
isDummy: false,
|
|
47
121
|
isMerged: false,
|
|
@@ -52,44 +126,49 @@ var defaultNodeProps = {
|
|
|
52
126
|
dims: void 0,
|
|
53
127
|
mutable: false
|
|
54
128
|
};
|
|
55
|
-
var Node = class _Node extends (0, import_immutable.Record)(
|
|
129
|
+
var Node = class _Node extends (0, import_immutable.Record)(defNodeData) {
|
|
56
130
|
static dummyPrefix = "d:";
|
|
57
|
-
get edgeId() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
get edgeIds() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
static
|
|
131
|
+
// get edgeId(): EdgeId {
|
|
132
|
+
// if (!this.isDummy)
|
|
133
|
+
// throw new Error(`node ${this.id} is not a dummy`)
|
|
134
|
+
// if (this.isMerged)
|
|
135
|
+
// throw new Error(`node ${this.id} is merged`)
|
|
136
|
+
// return this.get('edgeIds')[0]
|
|
137
|
+
// }
|
|
138
|
+
// get edgeIds(): EdgeId[] {
|
|
139
|
+
// if (!this.isDummy)
|
|
140
|
+
// throw new Error(`node ${this.id} is not a dummy`)
|
|
141
|
+
// if (!this.isMerged)
|
|
142
|
+
// throw new Error(`node ${this.id} is not merged`)
|
|
143
|
+
// return this.get('edgeIds')
|
|
144
|
+
// }
|
|
145
|
+
get key() {
|
|
146
|
+
return this.isDummy ? this.id : _Node.key(this);
|
|
147
|
+
}
|
|
148
|
+
static key(node) {
|
|
149
|
+
return `k:${node.id}:${node.version}`;
|
|
150
|
+
}
|
|
151
|
+
static addNormal(g, data) {
|
|
75
152
|
const layer = g.layerAt(0);
|
|
76
153
|
const node = new _Node({
|
|
77
|
-
...
|
|
154
|
+
...data,
|
|
78
155
|
edges: { in: (0, import_immutable.Set)(), out: (0, import_immutable.Set)() },
|
|
79
156
|
segs: { in: (0, import_immutable.Set)(), out: (0, import_immutable.Set)() },
|
|
80
157
|
aligned: {},
|
|
81
158
|
edgeIds: [],
|
|
82
|
-
layerId: layer.id
|
|
159
|
+
layerId: layer.id,
|
|
160
|
+
lpos: void 0,
|
|
161
|
+
pos: void 0
|
|
83
162
|
});
|
|
84
163
|
layer.addNode(g, node.id);
|
|
85
164
|
g.nodes.set(node.id, node);
|
|
86
165
|
g.dirtyNodes.add(node.id);
|
|
87
166
|
return node;
|
|
88
167
|
}
|
|
89
|
-
static addDummy(g,
|
|
90
|
-
const layer = g.getLayer(
|
|
168
|
+
static addDummy(g, data) {
|
|
169
|
+
const layer = g.getLayer(data.layerId);
|
|
91
170
|
const node = new _Node({
|
|
92
|
-
...
|
|
171
|
+
...data,
|
|
93
172
|
id: `${_Node.dummyPrefix}${g.nextDummyId++}`,
|
|
94
173
|
edges: { in: (0, import_immutable.Set)(), out: (0, import_immutable.Set)() },
|
|
95
174
|
segs: { in: (0, import_immutable.Set)(), out: (0, import_immutable.Set)() },
|
|
@@ -105,6 +184,12 @@ var Node = class _Node extends (0, import_immutable.Record)(defaultNodeProps) {
|
|
|
105
184
|
g.dirtyNodes.add(node.id);
|
|
106
185
|
return node;
|
|
107
186
|
}
|
|
187
|
+
static del(g, node) {
|
|
188
|
+
return g.getNode(node.id).delSelf(g);
|
|
189
|
+
}
|
|
190
|
+
static update(g, data) {
|
|
191
|
+
return g.getNode(data.id).mut(g).merge(data);
|
|
192
|
+
}
|
|
108
193
|
mut(g) {
|
|
109
194
|
if (this.mutable) return this;
|
|
110
195
|
return g.mutateNode(this);
|
|
@@ -127,19 +212,32 @@ var Node = class _Node extends (0, import_immutable.Record)(defaultNodeProps) {
|
|
|
127
212
|
isUnlinked() {
|
|
128
213
|
return this.edges.in.size == 0 && this.edges.out.size == 0 && this.segs.in.size == 0 && this.segs.out.size == 0;
|
|
129
214
|
}
|
|
215
|
+
hasPorts() {
|
|
216
|
+
return !!this.ports?.in?.length || !!this.ports?.out?.length;
|
|
217
|
+
}
|
|
130
218
|
layerIndex(g) {
|
|
131
219
|
return this.getLayer(g).index;
|
|
132
220
|
}
|
|
133
221
|
getLayer(g) {
|
|
134
222
|
return g.getLayer(this.layerId);
|
|
135
223
|
}
|
|
224
|
+
margin(g) {
|
|
225
|
+
return this.isDummy ? g.options.edgeSpacing - g.options.dummyNodeSize : g.options.nodeMargin;
|
|
226
|
+
}
|
|
227
|
+
marginWith(g, other) {
|
|
228
|
+
return Math.max(this.margin(g), other.margin(g));
|
|
229
|
+
}
|
|
230
|
+
width(g) {
|
|
231
|
+
return this.dims?.[g.w] ?? 0;
|
|
232
|
+
}
|
|
233
|
+
right(g) {
|
|
234
|
+
return this.lpos + this.width(g);
|
|
235
|
+
}
|
|
136
236
|
setIndex(g, index) {
|
|
137
237
|
if (this.index == index) return this;
|
|
138
|
-
console.log(`set index of ${this.id} to ${index}`);
|
|
139
238
|
return this.mut(g).set("index", index);
|
|
140
239
|
}
|
|
141
240
|
setLayerPos(g, lpos) {
|
|
142
|
-
console.log("setLayerPos", this.id, lpos);
|
|
143
241
|
if (this.lpos == lpos) return this;
|
|
144
242
|
return this.mut(g).set("lpos", lpos);
|
|
145
243
|
}
|
|
@@ -153,7 +251,6 @@ var Node = class _Node extends (0, import_immutable.Record)(defaultNodeProps) {
|
|
|
153
251
|
return this;
|
|
154
252
|
}
|
|
155
253
|
moveToLayer(g, layer) {
|
|
156
|
-
console.log("moveToLayer", this, this.getLayer(g), layer);
|
|
157
254
|
this.getLayer(g).delNode(g, this.id);
|
|
158
255
|
layer.addNode(g, this.id);
|
|
159
256
|
return this.setLayer(g, layer.id);
|
|
@@ -182,9 +279,22 @@ var Node = class _Node extends (0, import_immutable.Record)(defaultNodeProps) {
|
|
|
182
279
|
return node;
|
|
183
280
|
}
|
|
184
281
|
delSelf(g) {
|
|
282
|
+
if (this.aligned.in)
|
|
283
|
+
g.getNode(this.aligned.in).setAligned(g, "out", void 0);
|
|
284
|
+
if (this.aligned.out)
|
|
285
|
+
g.getNode(this.aligned.out).setAligned(g, "in", void 0);
|
|
185
286
|
this.getLayer(g).delNode(g, this.id);
|
|
186
|
-
for (const
|
|
187
|
-
|
|
287
|
+
for (const edge of this.rels(g, "edges", "both"))
|
|
288
|
+
edge.delSelf(g);
|
|
289
|
+
const remainingSegIds = [...this.segs.in, ...this.segs.out];
|
|
290
|
+
if (remainingSegIds.length > 0) {
|
|
291
|
+
for (const segId of remainingSegIds) {
|
|
292
|
+
if (g.segs?.has?.(segId)) {
|
|
293
|
+
g.getSeg(segId).delSelf(g);
|
|
294
|
+
} else {
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
188
298
|
g.nodes.delete(this.id);
|
|
189
299
|
g.dirtyNodes.delete(this.id);
|
|
190
300
|
g.delNodes.add(this.id);
|
|
@@ -275,16 +385,20 @@ var Node = class _Node extends (0, import_immutable.Record)(defaultNodeProps) {
|
|
|
275
385
|
}
|
|
276
386
|
};
|
|
277
387
|
|
|
278
|
-
// src/graph/
|
|
388
|
+
// src/graph/edge.ts
|
|
279
389
|
var import_immutable2 = require("immutable");
|
|
280
|
-
var
|
|
390
|
+
var log3 = logger("edge");
|
|
391
|
+
var defEdgeData = {
|
|
281
392
|
id: "",
|
|
393
|
+
data: null,
|
|
394
|
+
label: void 0,
|
|
282
395
|
source: { id: "" },
|
|
283
396
|
target: { id: "" },
|
|
397
|
+
type: void 0,
|
|
284
398
|
mutable: false,
|
|
285
399
|
segIds: []
|
|
286
400
|
};
|
|
287
|
-
var Edge = class _Edge extends (0, import_immutable2.Record)(
|
|
401
|
+
var Edge = class _Edge extends (0, import_immutable2.Record)(defEdgeData) {
|
|
288
402
|
static prefix = "e:";
|
|
289
403
|
mut(g) {
|
|
290
404
|
if (this.mutable) return this;
|
|
@@ -342,37 +456,44 @@ var Edge = class _Edge extends (0, import_immutable2.Record)(defaultEdgeProps) {
|
|
|
342
456
|
return _Edge.str(this);
|
|
343
457
|
}
|
|
344
458
|
static str(edge) {
|
|
345
|
-
let source = edge.source
|
|
346
|
-
if (edge
|
|
459
|
+
let source = edge.source?.id;
|
|
460
|
+
if (!source) throw new Error("edge source is undefined");
|
|
461
|
+
if (edge.source?.port)
|
|
347
462
|
source = `${source} (port ${edge.source.port})`;
|
|
348
|
-
let target = edge.target
|
|
349
|
-
if (edge
|
|
463
|
+
let target = edge.target?.id;
|
|
464
|
+
if (!target) throw new Error("edge target is undefined");
|
|
465
|
+
if (edge.target?.port)
|
|
350
466
|
target = `${target} (port ${edge.target.port})`;
|
|
351
467
|
let str = `edge from ${source} to ${target}`;
|
|
352
468
|
if (edge.type) str += ` of type ${edge.type}`;
|
|
353
469
|
return str;
|
|
354
470
|
}
|
|
355
|
-
static
|
|
471
|
+
static key(edge, prefix = _Edge.prefix, side = "both") {
|
|
356
472
|
let source = "", target = "";
|
|
357
473
|
if (side == "source" || side == "both") {
|
|
474
|
+
if (!edge.source?.id) throw new Error("edge source is undefined");
|
|
358
475
|
source = edge.source.id;
|
|
359
|
-
if (edge.source
|
|
476
|
+
if (edge.source?.port)
|
|
360
477
|
source = `${source}.${edge.source.port}`;
|
|
478
|
+
const marker = edge.source?.marker;
|
|
479
|
+
if (marker && marker != "none") source += `[${marker}]`;
|
|
361
480
|
source += "-";
|
|
362
481
|
}
|
|
363
482
|
if (side == "target" || side == "both") {
|
|
483
|
+
if (!edge.target?.id) throw new Error("edge target is undefined");
|
|
364
484
|
target = edge.target.id;
|
|
365
485
|
if (edge.target.port)
|
|
366
486
|
target = `${target}.${edge.target.port}`;
|
|
367
487
|
target = "-" + target;
|
|
488
|
+
const marker = edge.target?.marker ?? "arrow";
|
|
489
|
+
if (marker && marker != "none") target += `[${marker}]`;
|
|
368
490
|
}
|
|
369
491
|
const type = edge.type || "";
|
|
370
492
|
return `${prefix}${source}${type}${target}`;
|
|
371
493
|
}
|
|
372
|
-
static add(g,
|
|
494
|
+
static add(g, data) {
|
|
373
495
|
const edge = new _Edge({
|
|
374
|
-
...
|
|
375
|
-
id: _Edge.id(props),
|
|
496
|
+
...data,
|
|
376
497
|
segIds: []
|
|
377
498
|
});
|
|
378
499
|
edge.link(g);
|
|
@@ -380,18 +501,38 @@ var Edge = class _Edge extends (0, import_immutable2.Record)(defaultEdgeProps) {
|
|
|
380
501
|
g.dirtyEdges.add(edge.id);
|
|
381
502
|
return edge;
|
|
382
503
|
}
|
|
504
|
+
static del(g, data) {
|
|
505
|
+
return g.getEdge(data.id).delSelf(g);
|
|
506
|
+
}
|
|
507
|
+
static update(g, data) {
|
|
508
|
+
let edge = g.getEdge(data.id);
|
|
509
|
+
let relink = false;
|
|
510
|
+
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) {
|
|
511
|
+
for (const seg of edge.segs(g))
|
|
512
|
+
seg.delEdgeId(g, edge.id);
|
|
513
|
+
edge.unlink(g);
|
|
514
|
+
relink = true;
|
|
515
|
+
}
|
|
516
|
+
edge = edge.mut(g).merge(data);
|
|
517
|
+
if (relink)
|
|
518
|
+
edge.link(g);
|
|
519
|
+
return edge;
|
|
520
|
+
}
|
|
383
521
|
};
|
|
384
522
|
|
|
385
|
-
// src/graph/
|
|
523
|
+
// src/graph/seg.ts
|
|
386
524
|
var import_immutable3 = require("immutable");
|
|
387
|
-
var
|
|
525
|
+
var defSegData = {
|
|
388
526
|
id: "",
|
|
389
527
|
source: { id: "" },
|
|
390
528
|
target: { id: "" },
|
|
529
|
+
type: void 0,
|
|
391
530
|
edgeIds: (0, import_immutable3.Set)(),
|
|
531
|
+
trackPos: void 0,
|
|
532
|
+
svg: void 0,
|
|
392
533
|
mutable: false
|
|
393
534
|
};
|
|
394
|
-
var Seg = class _Seg extends (0, import_immutable3.Record)(
|
|
535
|
+
var Seg = class _Seg extends (0, import_immutable3.Record)(defSegData) {
|
|
395
536
|
static prefix = "s:";
|
|
396
537
|
mut(g) {
|
|
397
538
|
if (this.mutable) return this;
|
|
@@ -416,7 +557,7 @@ var Seg = class _Seg extends (0, import_immutable3.Record)(defaultSegProps) {
|
|
|
416
557
|
sameEnd(other, side) {
|
|
417
558
|
const mine = this[side];
|
|
418
559
|
const yours = other[side];
|
|
419
|
-
return mine.id === yours.id && mine.port === yours.port;
|
|
560
|
+
return mine.id === yours.id && mine.port === yours.port && mine.marker === yours.marker && this.type === other.type;
|
|
420
561
|
}
|
|
421
562
|
setPos(g, source, target) {
|
|
422
563
|
return this.mut(g).merge({
|
|
@@ -474,10 +615,10 @@ var Seg = class _Seg extends (0, import_immutable3.Record)(defaultSegProps) {
|
|
|
474
615
|
}
|
|
475
616
|
return this.mut(g).set("edgeIds", this.edgeIds.asMutable().remove(edgeId));
|
|
476
617
|
}
|
|
477
|
-
static add(g,
|
|
618
|
+
static add(g, data) {
|
|
478
619
|
const seg = new _Seg({
|
|
479
|
-
...
|
|
480
|
-
id: Edge.
|
|
620
|
+
...data,
|
|
621
|
+
id: Edge.key(data, _Seg.prefix)
|
|
481
622
|
});
|
|
482
623
|
seg.link(g);
|
|
483
624
|
g.segs.set(seg.id, seg);
|
|
@@ -486,33 +627,10 @@ var Seg = class _Seg extends (0, import_immutable3.Record)(defaultSegProps) {
|
|
|
486
627
|
}
|
|
487
628
|
};
|
|
488
629
|
|
|
489
|
-
// src/graph/
|
|
630
|
+
// src/graph/layer.ts
|
|
490
631
|
var import_immutable4 = require("immutable");
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
var levels = {
|
|
494
|
-
error: 0,
|
|
495
|
-
warn: 1,
|
|
496
|
-
info: 2,
|
|
497
|
-
debug: 3
|
|
498
|
-
};
|
|
499
|
-
var currentLevel = "debug";
|
|
500
|
-
function shouldLog(level) {
|
|
501
|
-
return levels[level] <= levels[currentLevel];
|
|
502
|
-
}
|
|
503
|
-
function logger(module2) {
|
|
504
|
-
return {
|
|
505
|
-
error: (msg, ...args) => shouldLog("error") && console.error(`[${module2}] ${msg}`, ...args),
|
|
506
|
-
warn: (msg, ...args) => shouldLog("warn") && console.warn(`[${module2}] ${msg}`, ...args),
|
|
507
|
-
info: (msg, ...args) => shouldLog("info") && console.info(`[${module2}] ${msg}`, ...args),
|
|
508
|
-
debug: (msg, ...args) => shouldLog("debug") && console.debug(`[${module2}] ${msg}`, ...args)
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
var log = logger("core");
|
|
512
|
-
|
|
513
|
-
// src/graph/types/layer.ts
|
|
514
|
-
var log2 = logger("layer");
|
|
515
|
-
var defaultLayerProps = {
|
|
632
|
+
var log4 = logger("layer");
|
|
633
|
+
var defLayerData = {
|
|
516
634
|
id: "",
|
|
517
635
|
index: 0,
|
|
518
636
|
nodeIds: (0, import_immutable4.Set)(),
|
|
@@ -523,7 +641,7 @@ var defaultLayerProps = {
|
|
|
523
641
|
isSorted: false,
|
|
524
642
|
mutable: false
|
|
525
643
|
};
|
|
526
|
-
var Layer = class extends (0, import_immutable4.Record)(
|
|
644
|
+
var Layer = class extends (0, import_immutable4.Record)(defLayerData) {
|
|
527
645
|
static prefix = "l:";
|
|
528
646
|
mut(g) {
|
|
529
647
|
if (this.mutable) return this;
|
|
@@ -536,7 +654,7 @@ var Layer = class extends (0, import_immutable4.Record)(defaultLayerProps) {
|
|
|
536
654
|
mutable: false
|
|
537
655
|
}).asImmutable();
|
|
538
656
|
}
|
|
539
|
-
get
|
|
657
|
+
get nodeCount() {
|
|
540
658
|
return this.nodeIds.size;
|
|
541
659
|
}
|
|
542
660
|
*nodes(g) {
|
|
@@ -591,9 +709,8 @@ var Layer = class extends (0, import_immutable4.Record)(defaultLayerProps) {
|
|
|
591
709
|
reindex(g, nodeId) {
|
|
592
710
|
if (!this.isSorted) return void 0;
|
|
593
711
|
const sorted = this.sorted.filter((id) => id != nodeId);
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
g.getNode(sorted[i]).setIndex(g, i);
|
|
712
|
+
for (const [i, id] of this.sorted.entries())
|
|
713
|
+
g.getNode(id).setIndex(g, i);
|
|
597
714
|
return sorted;
|
|
598
715
|
}
|
|
599
716
|
delNode(g, nodeId) {
|
|
@@ -605,7 +722,6 @@ var Layer = class extends (0, import_immutable4.Record)(defaultLayerProps) {
|
|
|
605
722
|
}
|
|
606
723
|
setSorted(g, nodeIds) {
|
|
607
724
|
if (this.hasSortOrder(nodeIds)) return this;
|
|
608
|
-
console.log(`setting sorted for layer ${this.id}`);
|
|
609
725
|
nodeIds.forEach((nodeId, i) => g.getNode(nodeId).setIndex(g, i));
|
|
610
726
|
return this.mut(g).merge({ sorted: nodeIds, isSorted: true });
|
|
611
727
|
}
|
|
@@ -615,7 +731,7 @@ var Layer = class extends (0, import_immutable4.Record)(defaultLayerProps) {
|
|
|
615
731
|
}
|
|
616
732
|
};
|
|
617
733
|
|
|
618
|
-
// src/graph/
|
|
734
|
+
// src/graph/mutator.ts
|
|
619
735
|
var Mutator = class {
|
|
620
736
|
changes;
|
|
621
737
|
constructor() {
|
|
@@ -624,20 +740,27 @@ var Mutator = class {
|
|
|
624
740
|
removedNodes: [],
|
|
625
741
|
updatedNodes: [],
|
|
626
742
|
addedEdges: [],
|
|
627
|
-
removedEdges: []
|
|
743
|
+
removedEdges: [],
|
|
744
|
+
updatedEdges: []
|
|
628
745
|
};
|
|
629
746
|
}
|
|
747
|
+
describe(description) {
|
|
748
|
+
this.changes.description = description;
|
|
749
|
+
}
|
|
630
750
|
addNode(node) {
|
|
631
751
|
this.changes.addedNodes.push(node);
|
|
632
752
|
}
|
|
633
753
|
addNodes(...nodes) {
|
|
634
754
|
nodes.forEach((node) => this.addNode(node));
|
|
635
755
|
}
|
|
756
|
+
removeNode(node) {
|
|
757
|
+
this.changes.removedNodes.push(node);
|
|
758
|
+
}
|
|
759
|
+
removeNodes(...nodes) {
|
|
760
|
+
nodes.forEach((node) => this.removeNode(node));
|
|
761
|
+
}
|
|
636
762
|
updateNode(node) {
|
|
637
|
-
|
|
638
|
-
this.changes.updatedNodes.push({ id: node });
|
|
639
|
-
else
|
|
640
|
-
this.changes.updatedNodes.push(node);
|
|
763
|
+
this.changes.updatedNodes.push(node);
|
|
641
764
|
}
|
|
642
765
|
updateNodes(...nodes) {
|
|
643
766
|
nodes.forEach((node) => this.updateNode(node));
|
|
@@ -648,21 +771,18 @@ var Mutator = class {
|
|
|
648
771
|
addEdges(...edges) {
|
|
649
772
|
edges.forEach((edge) => this.addEdge(edge));
|
|
650
773
|
}
|
|
651
|
-
removeNode(node) {
|
|
652
|
-
if (typeof node === "string")
|
|
653
|
-
this.changes.removedNodes.push({ id: node });
|
|
654
|
-
else
|
|
655
|
-
this.changes.removedNodes.push(node);
|
|
656
|
-
}
|
|
657
|
-
removeNodes(...nodes) {
|
|
658
|
-
nodes.forEach((node) => this.removeNode(node));
|
|
659
|
-
}
|
|
660
774
|
removeEdge(edge) {
|
|
661
775
|
this.changes.removedEdges.push(edge);
|
|
662
776
|
}
|
|
663
777
|
removeEdges(...edges) {
|
|
664
778
|
edges.forEach((edge) => this.removeEdge(edge));
|
|
665
779
|
}
|
|
780
|
+
updateEdge(edge) {
|
|
781
|
+
this.changes.updatedEdges.push(edge);
|
|
782
|
+
}
|
|
783
|
+
updateEdges(...edges) {
|
|
784
|
+
edges.forEach((edge) => this.updateEdge(edge));
|
|
785
|
+
}
|
|
666
786
|
};
|
|
667
787
|
|
|
668
788
|
// src/graph/services/cycles.ts
|
|
@@ -762,12 +882,10 @@ var Cycles = class _Cycles {
|
|
|
762
882
|
|
|
763
883
|
// src/graph/services/dummy.ts
|
|
764
884
|
var import_immutable5 = require("immutable");
|
|
765
|
-
var
|
|
885
|
+
var log5 = logger("dummy");
|
|
766
886
|
var Dummy = class _Dummy {
|
|
767
887
|
static updateDummies(g) {
|
|
768
|
-
log3.debug(`updating dummies:`, [...g.dirtyEdges]);
|
|
769
888
|
for (const edgeId of g.dirtyEdges) {
|
|
770
|
-
log3.debug(`updating dummies of edge ${edgeId}`);
|
|
771
889
|
const edge = g.getEdge(edgeId);
|
|
772
890
|
const { type } = edge;
|
|
773
891
|
const sourceLayer = edge.sourceNode(g).layerIndex(g);
|
|
@@ -794,11 +912,9 @@ var Dummy = class _Dummy {
|
|
|
794
912
|
target = { id: dummy.id };
|
|
795
913
|
}
|
|
796
914
|
seg = Seg.add(g, { source, target, type, edgeIds: (0, import_immutable5.Set)([edgeId]) });
|
|
797
|
-
log3.debug(`edge ${edgeId}: adding segment ${seg.id} from ${source.id} at layer ${layerIndex - 1} to ${target.id} at layer ${layerIndex}`);
|
|
798
915
|
segs.splice(segIndex, 0, seg.id);
|
|
799
916
|
changed = true;
|
|
800
917
|
} else if (segLayer < layerIndex || seg.source.id != source.id || seg.source.port != source.port || layerIndex == targetLayer && (seg.target.id != edge.target.id || seg.target.port != edge.target.port)) {
|
|
801
|
-
log3.debug(`edge ${edgeId}: removing segment ${seg.id} from layer ${layerIndex - 1} to layer ${layerIndex}`);
|
|
802
918
|
seg = seg.delEdgeId(g, edgeId);
|
|
803
919
|
segs.splice(segIndex, 1);
|
|
804
920
|
changed = true;
|
|
@@ -810,14 +926,12 @@ var Dummy = class _Dummy {
|
|
|
810
926
|
}
|
|
811
927
|
}
|
|
812
928
|
while (segIndex < segs.length) {
|
|
813
|
-
log3.debug(`edge ${edgeId}: removing trailing segment ${segs[segIndex]}`);
|
|
814
929
|
g.getSeg(segs[segIndex]).delEdgeId(g, edgeId);
|
|
815
930
|
segs.splice(segIndex, 1);
|
|
816
931
|
changed = true;
|
|
817
932
|
segIndex++;
|
|
818
933
|
}
|
|
819
934
|
if (changed) {
|
|
820
|
-
log3.debug(`edge ${edgeId}: updated segments to ${segs.join(", ")}`);
|
|
821
935
|
edge.setSegIds(g, segs);
|
|
822
936
|
}
|
|
823
937
|
}
|
|
@@ -832,22 +946,20 @@ var Dummy = class _Dummy {
|
|
|
832
946
|
const dir = side == "source" ? "in" : "out";
|
|
833
947
|
const altSide = side == "source" ? "target" : "source";
|
|
834
948
|
const altDir = altSide == "source" ? "in" : "out";
|
|
835
|
-
log3.debug(`merging dummies by ${side}`);
|
|
836
949
|
for (const layerId of layerIds) {
|
|
837
950
|
let layer = g.getLayer(layerId);
|
|
838
951
|
const groups = /* @__PURE__ */ new Map();
|
|
839
952
|
for (const nodeId of layer.nodeIds) {
|
|
840
|
-
if (!Node.isDummyId(nodeId)) continue;
|
|
841
953
|
const node = g.getNode(nodeId);
|
|
842
|
-
if (node.isMerged) continue;
|
|
954
|
+
if (!node.isDummy || node.isMerged) continue;
|
|
843
955
|
const edge = g.getEdge(node.edgeIds[0]);
|
|
844
|
-
const key = Edge.
|
|
956
|
+
const key = Edge.key(edge, "k:", side);
|
|
845
957
|
if (!groups.has(key)) groups.set(key, /* @__PURE__ */ new Set());
|
|
846
958
|
groups.get(key).add(node);
|
|
847
959
|
}
|
|
848
960
|
for (const [key, group] of groups) {
|
|
849
961
|
if (group.size == 1) continue;
|
|
850
|
-
const edgeIds = [...group].map((node) => node.
|
|
962
|
+
const edgeIds = [...group].map((node) => node.edgeIds[0]);
|
|
851
963
|
const dummy = Node.addDummy(g, { edgeIds, layerId, isMerged: true });
|
|
852
964
|
let seg;
|
|
853
965
|
for (const old of group) {
|
|
@@ -857,8 +969,9 @@ var Dummy = class _Dummy {
|
|
|
857
969
|
const example = g.getSeg(segId);
|
|
858
970
|
seg = Seg.add(g, {
|
|
859
971
|
...example,
|
|
860
|
-
edgeIds: (0, import_immutable5.Set)(
|
|
861
|
-
[
|
|
972
|
+
edgeIds: (0, import_immutable5.Set)(edgeIds),
|
|
973
|
+
[side]: { ...example[side] },
|
|
974
|
+
[altSide]: { ...example[altSide], id: dummy.id, port: void 0 }
|
|
862
975
|
});
|
|
863
976
|
}
|
|
864
977
|
edge = edge.replaceSegId(g, segId, seg.id);
|
|
@@ -870,12 +983,15 @@ var Dummy = class _Dummy {
|
|
|
870
983
|
const example = g.getSeg(segId);
|
|
871
984
|
const seg2 = Seg.add(g, {
|
|
872
985
|
...example,
|
|
873
|
-
edgeIds: (0, import_immutable5.Set)([old.
|
|
874
|
-
[
|
|
986
|
+
edgeIds: (0, import_immutable5.Set)([old.edgeIds[0]]),
|
|
987
|
+
[altSide]: { ...example[altSide] },
|
|
988
|
+
[side]: { ...example[side], id: dummy.id, port: void 0 }
|
|
875
989
|
});
|
|
876
990
|
edge = edge.replaceSegId(g, segId, seg2.id);
|
|
877
991
|
}
|
|
878
992
|
}
|
|
993
|
+
for (const old of group)
|
|
994
|
+
old.delSelf(g);
|
|
879
995
|
}
|
|
880
996
|
}
|
|
881
997
|
}
|
|
@@ -883,7 +999,7 @@ var Dummy = class _Dummy {
|
|
|
883
999
|
|
|
884
1000
|
// src/graph/services/layers.ts
|
|
885
1001
|
var import_immutable6 = require("immutable");
|
|
886
|
-
var
|
|
1002
|
+
var log6 = logger("layers");
|
|
887
1003
|
var Layers = class {
|
|
888
1004
|
static updateLayers(g) {
|
|
889
1005
|
const stack = [...g.dirtyNodes].map((id) => g.getNode(id)).filter((node) => !node.isDummy).sort((a, b) => b.layerIndex(g) - a.layerIndex(g)).map((node) => node.id);
|
|
@@ -946,13 +1062,11 @@ var Layers = class {
|
|
|
946
1062
|
|
|
947
1063
|
// src/graph/services/layout.ts
|
|
948
1064
|
var import_immutable7 = require("immutable");
|
|
949
|
-
var
|
|
1065
|
+
var log7 = logger("layout");
|
|
950
1066
|
var Layout = class _Layout {
|
|
951
1067
|
static parentIndex(g, node) {
|
|
952
1068
|
const parents = (0, import_immutable7.Seq)([...node.adjs(g, "segs", "in")]);
|
|
953
|
-
console.log(`parents of ${node.id}:`, [...parents], [...parents.map((p) => p.index)]);
|
|
954
1069
|
const pidx = parents.map((p) => p.index).min();
|
|
955
|
-
log5.debug(`node ${node.id}: parent index ${pidx}`);
|
|
956
1070
|
if (pidx !== void 0) return pidx;
|
|
957
1071
|
return node.isDummy ? -Infinity : Infinity;
|
|
958
1072
|
}
|
|
@@ -965,12 +1079,11 @@ var Layout = class _Layout {
|
|
|
965
1079
|
if (a.isDummy && !b.isDummy) return -1;
|
|
966
1080
|
if (!a.isDummy && b.isDummy) return 1;
|
|
967
1081
|
if (!a.isDummy) return a.id.localeCompare(b.id);
|
|
968
|
-
const minA =
|
|
969
|
-
const minB =
|
|
1082
|
+
const minA = (0, import_immutable7.Seq)(a.edgeIds).min();
|
|
1083
|
+
const minB = (0, import_immutable7.Seq)(b.edgeIds).min();
|
|
970
1084
|
return minA.localeCompare(minB);
|
|
971
1085
|
}
|
|
972
1086
|
static positionNodes(g) {
|
|
973
|
-
console.log("positionNodes", g.dirtyNodes);
|
|
974
1087
|
for (const nodeId of g.dirtyNodes)
|
|
975
1088
|
g.dirtyLayers.add(g.getNode(nodeId).layerId);
|
|
976
1089
|
let adjustNext = false;
|
|
@@ -978,15 +1091,12 @@ var Layout = class _Layout {
|
|
|
978
1091
|
if (!adjustNext && !g.dirtyLayers.has(layerId)) continue;
|
|
979
1092
|
adjustNext = false;
|
|
980
1093
|
let layer = g.getLayer(layerId);
|
|
981
|
-
console.log(`positioning layer ${layerId} at ${layer.index}`);
|
|
982
1094
|
const pidxs = /* @__PURE__ */ new Map();
|
|
983
1095
|
for (const nodeId of layer.nodeIds)
|
|
984
1096
|
pidxs.set(nodeId, _Layout.parentIndex(g, g.getNode(nodeId)));
|
|
985
|
-
console.log("pidxs", pidxs);
|
|
986
1097
|
const sorted = [...layer.nodeIds].sort(
|
|
987
1098
|
(aId, bId) => _Layout.compareNodes(g, aId, bId, pidxs)
|
|
988
1099
|
);
|
|
989
|
-
console.log(`sorted:`, sorted);
|
|
990
1100
|
if (layer.hasSortOrder(sorted)) continue;
|
|
991
1101
|
g.dirtyLayers.add(layerId);
|
|
992
1102
|
layer = layer.setSorted(g, sorted);
|
|
@@ -994,10 +1104,14 @@ var Layout = class _Layout {
|
|
|
994
1104
|
let lpos = 0;
|
|
995
1105
|
for (let i = 0; i < sorted.length; i++) {
|
|
996
1106
|
let node = g.getNode(sorted[i]);
|
|
997
|
-
log5.debug(`node ${node.id}: final index ${i}`);
|
|
998
1107
|
node = node.setIndex(g, i).setLayerPos(g, lpos);
|
|
999
1108
|
const size = node.dims?.[g.w] ?? 0;
|
|
1000
|
-
|
|
1109
|
+
let margin = node.margin(g);
|
|
1110
|
+
if (i + 1 < sorted.length) {
|
|
1111
|
+
const next = g.getNode(sorted[i + 1]);
|
|
1112
|
+
margin = node.marginWith(g, next);
|
|
1113
|
+
}
|
|
1114
|
+
lpos += size + margin;
|
|
1001
1115
|
}
|
|
1002
1116
|
}
|
|
1003
1117
|
}
|
|
@@ -1026,7 +1140,12 @@ var Layout = class _Layout {
|
|
|
1026
1140
|
for (const layerId of layerIds) {
|
|
1027
1141
|
if (!adjustNext && !g.dirtyLayers.has(layerId)) continue;
|
|
1028
1142
|
adjustNext = false;
|
|
1143
|
+
let iterations = 0;
|
|
1029
1144
|
while (true) {
|
|
1145
|
+
if (++iterations > 10) {
|
|
1146
|
+
log7.error(`alignNodes: infinite loop detected in layer ${layerId}`);
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1030
1149
|
let changed = false;
|
|
1031
1150
|
const nodeIds = _Layout.sortLayer(g, layerId, reverseNodes);
|
|
1032
1151
|
for (const nodeId of nodeIds) {
|
|
@@ -1084,7 +1203,7 @@ var Layout = class _Layout {
|
|
|
1084
1203
|
static anchorPos(g, seg, side) {
|
|
1085
1204
|
const nodeId = seg[side].id;
|
|
1086
1205
|
const node = g.getNode(nodeId);
|
|
1087
|
-
let p = {
|
|
1206
|
+
let p = { [g.x]: node.lpos, [g.y]: node.pos?.[g.y] ?? 0 };
|
|
1088
1207
|
let w = node.dims?.[g.w] ?? 0;
|
|
1089
1208
|
let h = node.dims?.[g.h] ?? 0;
|
|
1090
1209
|
if (node.isDummy)
|
|
@@ -1092,32 +1211,56 @@ var Layout = class _Layout {
|
|
|
1092
1211
|
[g.x]: p[g.x] + w / 2,
|
|
1093
1212
|
[g.y]: p[g.y] + h / 2
|
|
1094
1213
|
};
|
|
1095
|
-
p[g.x] += _Layout.nodePortOffset(g, nodeId, seg
|
|
1096
|
-
if (side == "
|
|
1214
|
+
p[g.x] += _Layout.nodePortOffset(g, nodeId, seg, side);
|
|
1215
|
+
if (side == "target" == g.r)
|
|
1097
1216
|
p[g.y] += h;
|
|
1098
1217
|
return p;
|
|
1099
1218
|
}
|
|
1100
|
-
static nodePortOffset(g, nodeId,
|
|
1101
|
-
|
|
1102
|
-
|
|
1219
|
+
static nodePortOffset(g, nodeId, seg, side) {
|
|
1220
|
+
const node = g.getNode(nodeId);
|
|
1221
|
+
const dir = side == "source" ? "out" : "in";
|
|
1222
|
+
const portId = seg[side].port;
|
|
1223
|
+
let min = 0, size = node.dims?.[g.w] ?? 0;
|
|
1224
|
+
if (portId) {
|
|
1225
|
+
const ports = node.ports?.[dir];
|
|
1226
|
+
const port = ports?.find((p) => p.id === portId);
|
|
1227
|
+
if (port?.offset !== void 0) {
|
|
1228
|
+
min = port.offset;
|
|
1229
|
+
size = port.size ?? 0;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
const alt = side == "source" ? "target" : "source";
|
|
1233
|
+
let segs = [];
|
|
1234
|
+
const keyOf = (seg2) => `${seg2.type ?? ""}:${seg2[side].marker ?? ""}`;
|
|
1235
|
+
for (const segId of node.segs[dir])
|
|
1236
|
+
segs.push(g.getSeg(segId));
|
|
1237
|
+
if (portId) segs = segs.filter((s) => s[side].port == portId);
|
|
1238
|
+
const groups = Object.groupBy(segs, (s) => keyOf(s));
|
|
1239
|
+
const posMap = /* @__PURE__ */ new Map();
|
|
1240
|
+
for (const [key, segs2] of Object.entries(groups)) {
|
|
1241
|
+
let pos = Infinity;
|
|
1242
|
+
for (const seg2 of segs2) pos = Math.min(pos, seg2.node(g, alt).lpos);
|
|
1243
|
+
posMap.set(key, pos);
|
|
1244
|
+
}
|
|
1245
|
+
const keys = [...posMap.keys()].sort((a, b) => posMap.get(a) - posMap.get(b));
|
|
1246
|
+
const gap = size / (keys.length + 1);
|
|
1247
|
+
const index = keys.indexOf(keyOf(seg));
|
|
1248
|
+
return min + (index + 1) * gap;
|
|
1103
1249
|
}
|
|
1104
1250
|
static shiftNode(g, nodeId, alignId, dir, lpos, reverseMove, conservative) {
|
|
1105
1251
|
const node = g.getNode(nodeId);
|
|
1106
1252
|
if (!conservative)
|
|
1107
1253
|
_Layout.markAligned(g, nodeId, alignId, dir, lpos);
|
|
1108
|
-
const
|
|
1109
|
-
const nodeWidth = node.dims?.[g.w] ?? 0;
|
|
1110
|
-
const aMin = lpos - space, aMax = lpos + nodeWidth + space;
|
|
1254
|
+
const nodeRight = lpos + node.width(g);
|
|
1111
1255
|
repeat:
|
|
1112
1256
|
for (const otherId of node.getLayer(g).nodeIds) {
|
|
1113
1257
|
if (otherId == nodeId) continue;
|
|
1114
1258
|
const other = g.getNode(otherId);
|
|
1115
|
-
const
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
if (aMin < bMax && bMin < aMax) {
|
|
1259
|
+
const margin = node.marginWith(g, other);
|
|
1260
|
+
const gap = lpos < other.lpos ? other.lpos - nodeRight : lpos - other.right(g);
|
|
1261
|
+
if (gap < margin) {
|
|
1119
1262
|
if (conservative) return false;
|
|
1120
|
-
const safePos = reverseMove ?
|
|
1263
|
+
const safePos = reverseMove ? lpos - other.width(g) - margin : nodeRight + margin;
|
|
1121
1264
|
_Layout.shiftNode(g, otherId, void 0, dir, safePos, reverseMove, conservative);
|
|
1122
1265
|
continue repeat;
|
|
1123
1266
|
}
|
|
@@ -1133,7 +1276,7 @@ var Layout = class _Layout {
|
|
|
1133
1276
|
g.getNode(node.aligned[dir]).setAligned(g, alt, void 0);
|
|
1134
1277
|
if (otherId)
|
|
1135
1278
|
g.getNode(otherId).setAligned(g, alt, nodeId);
|
|
1136
|
-
node.setAligned(g, dir, otherId);
|
|
1279
|
+
node.setAligned(g, dir, otherId).setLayerPos(g, lpos);
|
|
1137
1280
|
}
|
|
1138
1281
|
static *aligned(g, nodeId, dir) {
|
|
1139
1282
|
const visit = function* (node2, dir2) {
|
|
@@ -1166,11 +1309,12 @@ var Layout = class _Layout {
|
|
|
1166
1309
|
for (const layerId of g.layerList) {
|
|
1167
1310
|
const layer = g.getLayer(layerId);
|
|
1168
1311
|
if (layer.sorted.length < 2) continue;
|
|
1169
|
-
for (const nodeId of layer.sorted) {
|
|
1312
|
+
for (const [i, nodeId] of layer.sorted.entries()) {
|
|
1170
1313
|
const node = g.getNode(nodeId);
|
|
1171
1314
|
if (node.index == 0) continue;
|
|
1172
1315
|
let minGap = Infinity;
|
|
1173
1316
|
const stack = [];
|
|
1317
|
+
let maxMargin = 0;
|
|
1174
1318
|
for (const right of _Layout.aligned(g, nodeId, "both")) {
|
|
1175
1319
|
stack.push(right);
|
|
1176
1320
|
const leftId = _Layout.leftOf(g, right);
|
|
@@ -1179,8 +1323,10 @@ var Layout = class _Layout {
|
|
|
1179
1323
|
const leftWidth = left.dims?.[g.w] ?? 0;
|
|
1180
1324
|
const gap = right.lpos - left.lpos - leftWidth;
|
|
1181
1325
|
if (gap < minGap) minGap = gap;
|
|
1326
|
+
const margin = right.marginWith(g, left);
|
|
1327
|
+
if (margin > maxMargin) maxMargin = margin;
|
|
1182
1328
|
}
|
|
1183
|
-
const delta = minGap -
|
|
1329
|
+
const delta = minGap - maxMargin;
|
|
1184
1330
|
if (delta <= 0) continue;
|
|
1185
1331
|
anyChanged = true;
|
|
1186
1332
|
for (const right of stack)
|
|
@@ -1193,39 +1339,140 @@ var Layout = class _Layout {
|
|
|
1193
1339
|
let pos = 0;
|
|
1194
1340
|
const dir = g.r ? -1 : 1;
|
|
1195
1341
|
const trackSep = Math.max(
|
|
1196
|
-
g.options.layerMargin,
|
|
1197
1342
|
g.options.edgeSpacing,
|
|
1198
1343
|
g.options.turnRadius
|
|
1199
1344
|
);
|
|
1345
|
+
const marginSep = Math.max(
|
|
1346
|
+
g.options.edgeSpacing,
|
|
1347
|
+
g.options.layerMargin,
|
|
1348
|
+
g.options.turnRadius + g.options.markerSize
|
|
1349
|
+
);
|
|
1200
1350
|
for (const layerId of g.layerList) {
|
|
1201
1351
|
let layer = g.getLayer(layerId);
|
|
1202
1352
|
let height;
|
|
1203
|
-
console.log(`getCoords: layer = ${layerId} at ${layer.index}`);
|
|
1204
1353
|
if (g.dirtyLayers.has(layerId)) {
|
|
1205
1354
|
height = (0, import_immutable7.Seq)(layer.nodes(g)).map((node) => node.dims?.[g.h] ?? 0).max() ?? 0;
|
|
1206
1355
|
layer = layer.setSize(g, height);
|
|
1207
1356
|
} else height = layer.size;
|
|
1208
|
-
console.log(`getCoords: layer = ${layerId}: pos = ${pos}, height = ${height}`);
|
|
1209
1357
|
for (const node of layer.nodes(g)) {
|
|
1210
1358
|
if (!g.dirtyNodes.has(node.id) && pos == layer.pos) continue;
|
|
1211
1359
|
const npos = { [g.x]: node.lpos, [g.y]: pos };
|
|
1212
1360
|
if (!g.n) npos[g.y] += dir * height;
|
|
1213
1361
|
if (g.r == g.n) npos[g.y] -= node.dims?.[g.h] ?? 0;
|
|
1214
|
-
console.log(`getCoords: node = ${node.id}: pos:`, npos);
|
|
1215
1362
|
node.setPos(g, npos);
|
|
1216
1363
|
}
|
|
1217
1364
|
layer = layer.setPos(g, pos);
|
|
1218
|
-
pos += dir * (height +
|
|
1365
|
+
pos += dir * (height + marginSep);
|
|
1219
1366
|
for (const track of layer.tracks) {
|
|
1220
1367
|
for (const segId of track)
|
|
1221
1368
|
g.getSeg(segId).setTrackPos(g, pos);
|
|
1222
|
-
pos += dir *
|
|
1369
|
+
pos += dir * trackSep;
|
|
1223
1370
|
}
|
|
1371
|
+
pos += dir * (marginSep - trackSep);
|
|
1224
1372
|
}
|
|
1225
1373
|
}
|
|
1226
1374
|
};
|
|
1227
1375
|
|
|
1376
|
+
// src/canvas/marker.tsx
|
|
1377
|
+
var import_jsx_runtime = require("jsx-dom/jsx-runtime");
|
|
1378
|
+
function arrow(size, reverse = false) {
|
|
1379
|
+
const h = size / 1.5;
|
|
1380
|
+
const w = size;
|
|
1381
|
+
const ry = h / 2;
|
|
1382
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1383
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1384
|
+
"marker",
|
|
1385
|
+
{
|
|
1386
|
+
id: `g3p-marker-arrow${suffix}`,
|
|
1387
|
+
className: "g3p-marker g3p-marker-arrow",
|
|
1388
|
+
markerWidth: size,
|
|
1389
|
+
markerHeight: size,
|
|
1390
|
+
refX: "2",
|
|
1391
|
+
refY: ry,
|
|
1392
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1393
|
+
markerUnits: "userSpaceOnUse",
|
|
1394
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: `M0,0 L0,${h} L${w},${ry} z` })
|
|
1395
|
+
}
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
function circle(size, reverse = false) {
|
|
1399
|
+
const r = size / 3;
|
|
1400
|
+
const cy = size / 2;
|
|
1401
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1403
|
+
"marker",
|
|
1404
|
+
{
|
|
1405
|
+
id: `g3p-marker-circle${suffix}`,
|
|
1406
|
+
className: "g3p-marker g3p-marker-circle",
|
|
1407
|
+
markerWidth: size,
|
|
1408
|
+
markerHeight: size,
|
|
1409
|
+
refX: "2",
|
|
1410
|
+
refY: cy,
|
|
1411
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1412
|
+
markerUnits: "userSpaceOnUse",
|
|
1413
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: r + 2, cy, r })
|
|
1414
|
+
}
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
function diamond(size, reverse = false) {
|
|
1418
|
+
const w = size * 0.7;
|
|
1419
|
+
const h = size / 2;
|
|
1420
|
+
const cy = size / 2;
|
|
1421
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1422
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1423
|
+
"marker",
|
|
1424
|
+
{
|
|
1425
|
+
id: `g3p-marker-diamond${suffix}`,
|
|
1426
|
+
className: "g3p-marker g3p-marker-diamond",
|
|
1427
|
+
markerWidth: size,
|
|
1428
|
+
markerHeight: size,
|
|
1429
|
+
refX: "2",
|
|
1430
|
+
refY: cy,
|
|
1431
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1432
|
+
markerUnits: "userSpaceOnUse",
|
|
1433
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: `M2,${cy} L${2 + w / 2},${cy - h / 2} L${2 + w},${cy} L${2 + w / 2},${cy + h / 2} z` })
|
|
1434
|
+
}
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
function bar(size, reverse = false) {
|
|
1438
|
+
const h = size * 0.6;
|
|
1439
|
+
const cy = size / 2;
|
|
1440
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1441
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1442
|
+
"marker",
|
|
1443
|
+
{
|
|
1444
|
+
id: `g3p-marker-bar${suffix}`,
|
|
1445
|
+
className: "g3p-marker g3p-marker-bar",
|
|
1446
|
+
markerWidth: size,
|
|
1447
|
+
markerHeight: size,
|
|
1448
|
+
refX: "2",
|
|
1449
|
+
refY: cy,
|
|
1450
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1451
|
+
markerUnits: "userSpaceOnUse",
|
|
1452
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "2", y1: cy - h / 2, x2: "2", y2: cy + h / 2, "stroke-width": "2" })
|
|
1453
|
+
}
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
function none(size, reverse = false) {
|
|
1457
|
+
return void 0;
|
|
1458
|
+
}
|
|
1459
|
+
function normalize(data) {
|
|
1460
|
+
let source = data.source?.marker ?? data.style?.marker?.source;
|
|
1461
|
+
let target = data.target?.marker ?? data.style?.marker?.target ?? "arrow";
|
|
1462
|
+
if (source == "none") source = void 0;
|
|
1463
|
+
if (target == "none") target = void 0;
|
|
1464
|
+
return { source, target };
|
|
1465
|
+
}
|
|
1466
|
+
var markerDefs = {
|
|
1467
|
+
arrow,
|
|
1468
|
+
circle,
|
|
1469
|
+
diamond,
|
|
1470
|
+
bar,
|
|
1471
|
+
none
|
|
1472
|
+
};
|
|
1473
|
+
|
|
1228
1474
|
// src/graph/services/lines.ts
|
|
1475
|
+
var log8 = logger("lines");
|
|
1229
1476
|
var Lines = class _Lines {
|
|
1230
1477
|
static layoutSeg(g, seg) {
|
|
1231
1478
|
const sourcePos = Layout.anchorPos(g, seg, "source");
|
|
@@ -1271,7 +1518,11 @@ var Lines = class _Lines {
|
|
|
1271
1518
|
validTrack = track;
|
|
1272
1519
|
break;
|
|
1273
1520
|
}
|
|
1274
|
-
|
|
1521
|
+
const minA = Math.min(seg.p1, seg.p2);
|
|
1522
|
+
const maxA = Math.max(seg.p1, seg.p2);
|
|
1523
|
+
const minB = Math.min(other.p1, other.p2);
|
|
1524
|
+
const maxB = Math.max(other.p1, other.p2);
|
|
1525
|
+
if (minA < maxB && minB < maxA) {
|
|
1275
1526
|
overlap = true;
|
|
1276
1527
|
break;
|
|
1277
1528
|
}
|
|
@@ -1286,6 +1537,21 @@ var Lines = class _Lines {
|
|
|
1286
1537
|
else
|
|
1287
1538
|
trackSet.push([seg]);
|
|
1288
1539
|
}
|
|
1540
|
+
const midpoint = (s) => (s.p1 + s.p2) / 2;
|
|
1541
|
+
const sortTracks = (tracks2, goingRight) => {
|
|
1542
|
+
tracks2.sort((a, b) => {
|
|
1543
|
+
const midA = Math.max(...a.map(midpoint));
|
|
1544
|
+
const midB = Math.max(...b.map(midpoint));
|
|
1545
|
+
return goingRight ? midB - midA : midA - midB;
|
|
1546
|
+
});
|
|
1547
|
+
};
|
|
1548
|
+
sortTracks(rightTracks, true);
|
|
1549
|
+
sortTracks(leftTracks, false);
|
|
1550
|
+
allTracks.sort((a, b) => {
|
|
1551
|
+
const avgA = a.reduce((sum, s) => sum + midpoint(s), 0) / a.length;
|
|
1552
|
+
const avgB = b.reduce((sum, s) => sum + midpoint(s), 0) / b.length;
|
|
1553
|
+
return avgB - avgA;
|
|
1554
|
+
});
|
|
1289
1555
|
const tracks = [];
|
|
1290
1556
|
const all = leftTracks.concat(rightTracks).concat(allTracks);
|
|
1291
1557
|
for (const track of all)
|
|
@@ -1300,7 +1566,12 @@ var Lines = class _Lines {
|
|
|
1300
1566
|
const radius = g.options.turnRadius;
|
|
1301
1567
|
const p1 = Layout.anchorPos(g, seg, "source");
|
|
1302
1568
|
const p2 = Layout.anchorPos(g, seg, "target");
|
|
1303
|
-
const
|
|
1569
|
+
const source = seg.sourceNode(g);
|
|
1570
|
+
const target = seg.targetNode(g);
|
|
1571
|
+
const marker = normalize(seg);
|
|
1572
|
+
if (source.isDummy) marker.source = void 0;
|
|
1573
|
+
if (target.isDummy) marker.target = void 0;
|
|
1574
|
+
const path = seg.trackPos !== void 0 ? _Lines.createRailroadPath(g, p1, p2, seg.trackPos, radius, marker) : _Lines.createDirectPath(g, p1, p2, radius, marker);
|
|
1304
1575
|
const svg = _Lines.pathToSVG(path);
|
|
1305
1576
|
seg.setSVG(g, svg);
|
|
1306
1577
|
}
|
|
@@ -1326,7 +1597,7 @@ var Lines = class _Lines {
|
|
|
1326
1597
|
}
|
|
1327
1598
|
return line;
|
|
1328
1599
|
}
|
|
1329
|
-
static pathBuilder(g, start, end, trackPos, radius) {
|
|
1600
|
+
static pathBuilder(g, start, end, trackPos, radius, marker) {
|
|
1330
1601
|
const { x, y } = g;
|
|
1331
1602
|
const lr = end[x] > start[x];
|
|
1332
1603
|
const d = lr ? 1 : -1;
|
|
@@ -1338,6 +1609,8 @@ var Lines = class _Lines {
|
|
|
1338
1609
|
if (g.r) s = 1 - s;
|
|
1339
1610
|
if (!lr) s = 1 - s;
|
|
1340
1611
|
if (!g.v) s = 1 - s;
|
|
1612
|
+
if (marker.source) start[y] += o * (g.options.markerSize - 1);
|
|
1613
|
+
if (marker.target) end[y] -= o * (g.options.markerSize - 1);
|
|
1341
1614
|
const p = { ...start, s };
|
|
1342
1615
|
const path = [];
|
|
1343
1616
|
const advance = (p2, type) => {
|
|
@@ -1346,8 +1619,8 @@ var Lines = class _Lines {
|
|
|
1346
1619
|
return { x, y, lr, d, o, rd, ro, t, s, p, path, advance };
|
|
1347
1620
|
}
|
|
1348
1621
|
// Create a railroad-style path with two 90-degree turns
|
|
1349
|
-
static createRailroadPath(g, start, end, trackPos, radius) {
|
|
1350
|
-
const { x, y, rd, ro, t, s, p, path, advance } = this.pathBuilder(g, start, end, trackPos, radius);
|
|
1622
|
+
static createRailroadPath(g, start, end, trackPos, radius, marker) {
|
|
1623
|
+
const { x, y, rd, ro, t, s, p, path, advance } = this.pathBuilder(g, start, end, trackPos, radius, marker);
|
|
1351
1624
|
advance({ [y]: t - ro }, "line");
|
|
1352
1625
|
advance({ [x]: p[x] + rd, [y]: t }, "arc");
|
|
1353
1626
|
advance({ [x]: end[x] - rd }, "line");
|
|
@@ -1356,8 +1629,8 @@ var Lines = class _Lines {
|
|
|
1356
1629
|
return path;
|
|
1357
1630
|
}
|
|
1358
1631
|
// Create a mostly-vertical path with optional S-curve
|
|
1359
|
-
static createDirectPath(g, start, end, radius) {
|
|
1360
|
-
const { x, y, d, o, s, p, path, advance } = this.pathBuilder(g, start, end, 0, radius);
|
|
1632
|
+
static createDirectPath(g, start, end, radius, marker) {
|
|
1633
|
+
const { x, y, d, o, s, p, path, advance } = this.pathBuilder(g, start, end, 0, radius, marker);
|
|
1361
1634
|
const dx = Math.abs(end.x - start.x);
|
|
1362
1635
|
const dy = Math.abs(end.y - start.y);
|
|
1363
1636
|
const d_ = { x: dx, y: dy };
|
|
@@ -1448,21 +1721,15 @@ var Lines = class _Lines {
|
|
|
1448
1721
|
}
|
|
1449
1722
|
};
|
|
1450
1723
|
|
|
1451
|
-
// src/graph/
|
|
1452
|
-
var
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
orientation: "TB",
|
|
1461
|
-
layerMargin: 5,
|
|
1462
|
-
alignIterations: 5,
|
|
1463
|
-
alignThreshold: 10,
|
|
1464
|
-
separateTrackSets: true,
|
|
1465
|
-
layoutSteps: null
|
|
1724
|
+
// src/graph/graph.ts
|
|
1725
|
+
var log9 = logger("graph");
|
|
1726
|
+
var emptyChanges = {
|
|
1727
|
+
addedNodes: [],
|
|
1728
|
+
removedNodes: [],
|
|
1729
|
+
updatedNodes: [],
|
|
1730
|
+
addedEdges: [],
|
|
1731
|
+
removedEdges: [],
|
|
1732
|
+
updatedEdges: []
|
|
1466
1733
|
};
|
|
1467
1734
|
var Graph = class _Graph {
|
|
1468
1735
|
prior;
|
|
@@ -1491,30 +1758,10 @@ var Graph = class _Graph {
|
|
|
1491
1758
|
x;
|
|
1492
1759
|
y;
|
|
1493
1760
|
d;
|
|
1494
|
-
constructor({ prior, changes, options
|
|
1761
|
+
constructor({ prior, changes, options }) {
|
|
1762
|
+
this.options = prior?.options ?? options;
|
|
1763
|
+
this.changes = changes ?? emptyChanges;
|
|
1495
1764
|
this.initFromPrior(prior);
|
|
1496
|
-
this.dirtyNodes = /* @__PURE__ */ new Set();
|
|
1497
|
-
this.dirtyEdges = /* @__PURE__ */ new Set();
|
|
1498
|
-
this.dirtyLayers = /* @__PURE__ */ new Set();
|
|
1499
|
-
this.dirtySegs = /* @__PURE__ */ new Set();
|
|
1500
|
-
this.delNodes = /* @__PURE__ */ new Set();
|
|
1501
|
-
this.delEdges = /* @__PURE__ */ new Set();
|
|
1502
|
-
this.delSegs = /* @__PURE__ */ new Set();
|
|
1503
|
-
this.options = {
|
|
1504
|
-
...defaultOptions,
|
|
1505
|
-
...prior?.options,
|
|
1506
|
-
...options
|
|
1507
|
-
};
|
|
1508
|
-
this.changes = changes ?? {
|
|
1509
|
-
addedNodes: [],
|
|
1510
|
-
removedNodes: [],
|
|
1511
|
-
updatedNodes: [],
|
|
1512
|
-
addedEdges: [],
|
|
1513
|
-
removedEdges: []
|
|
1514
|
-
};
|
|
1515
|
-
this.changes.addedNodes.push(...nodes || []);
|
|
1516
|
-
this.changes.addedEdges.push(...edges || []);
|
|
1517
|
-
this.dirty = this.changes.addedNodes.length > 0 || this.changes.removedNodes.length > 0 || this.changes.addedEdges.length > 0 || this.changes.removedEdges.length > 0;
|
|
1518
1765
|
this.r = this.options.orientation === "BT" || this.options.orientation === "RL";
|
|
1519
1766
|
this.v = this.options.orientation === "TB" || this.options.orientation === "BT";
|
|
1520
1767
|
this.h = this.v ? "h" : "w";
|
|
@@ -1530,38 +1777,41 @@ var Graph = class _Graph {
|
|
|
1530
1777
|
this.n = true;
|
|
1531
1778
|
else
|
|
1532
1779
|
this.n = natAligns[this.options.orientation] == this.options.nodeAlign;
|
|
1533
|
-
if (this.dirty)
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1780
|
+
if (this.dirty) this.processUpdate();
|
|
1781
|
+
}
|
|
1782
|
+
processUpdate() {
|
|
1783
|
+
try {
|
|
1784
|
+
this.beginMutate();
|
|
1785
|
+
this.applyChanges();
|
|
1786
|
+
Cycles.checkCycles(this);
|
|
1787
|
+
Layers.updateLayers(this);
|
|
1788
|
+
Dummy.updateDummies(this);
|
|
1789
|
+
Dummy.mergeDummies(this);
|
|
1790
|
+
Layout.positionNodes(this);
|
|
1791
|
+
Layout.alignAll(this);
|
|
1792
|
+
Lines.trackEdges(this);
|
|
1793
|
+
Layout.getCoords(this);
|
|
1794
|
+
Lines.pathEdges(this);
|
|
1795
|
+
} catch (e) {
|
|
1796
|
+
this.initFromPrior(this.prior);
|
|
1797
|
+
throw e;
|
|
1798
|
+
} finally {
|
|
1799
|
+
this.endMutate();
|
|
1552
1800
|
}
|
|
1553
1801
|
}
|
|
1554
1802
|
applyChanges() {
|
|
1555
1803
|
for (const edge of this.changes.removedEdges)
|
|
1556
|
-
|
|
1804
|
+
Edge.del(this, edge);
|
|
1557
1805
|
for (const node of this.changes.removedNodes)
|
|
1558
|
-
|
|
1806
|
+
Node.del(this, node);
|
|
1559
1807
|
for (const node of this.changes.addedNodes)
|
|
1560
1808
|
Node.addNormal(this, node);
|
|
1561
1809
|
for (const edge of this.changes.addedEdges)
|
|
1562
1810
|
Edge.add(this, edge);
|
|
1563
1811
|
for (const node of this.changes.updatedNodes)
|
|
1564
|
-
|
|
1812
|
+
Node.update(this, node);
|
|
1813
|
+
for (const edge of this.changes.updatedEdges)
|
|
1814
|
+
Edge.update(this, edge);
|
|
1565
1815
|
}
|
|
1566
1816
|
layerAt(index) {
|
|
1567
1817
|
while (index >= this.layerList.size)
|
|
@@ -1638,24 +1888,24 @@ var Graph = class _Graph {
|
|
|
1638
1888
|
nodes.forEach((node) => mutator.addNode(node));
|
|
1639
1889
|
});
|
|
1640
1890
|
}
|
|
1641
|
-
|
|
1891
|
+
removeNodes(...nodes) {
|
|
1642
1892
|
return this.withMutations((mutator) => {
|
|
1643
|
-
|
|
1893
|
+
nodes.forEach((node) => mutator.removeNode(node));
|
|
1644
1894
|
});
|
|
1645
1895
|
}
|
|
1646
|
-
|
|
1896
|
+
removeNode(node) {
|
|
1647
1897
|
return this.withMutations((mutator) => {
|
|
1648
|
-
mutator.
|
|
1898
|
+
mutator.removeNode(node);
|
|
1649
1899
|
});
|
|
1650
1900
|
}
|
|
1651
|
-
|
|
1901
|
+
addEdges(...edges) {
|
|
1652
1902
|
return this.withMutations((mutator) => {
|
|
1653
|
-
|
|
1903
|
+
edges.forEach((edge) => mutator.addEdge(edge));
|
|
1654
1904
|
});
|
|
1655
1905
|
}
|
|
1656
|
-
|
|
1906
|
+
addEdge(edge) {
|
|
1657
1907
|
return this.withMutations((mutator) => {
|
|
1658
|
-
mutator.
|
|
1908
|
+
mutator.addEdge(edge);
|
|
1659
1909
|
});
|
|
1660
1910
|
}
|
|
1661
1911
|
removeEdges(...edges) {
|
|
@@ -1705,6 +1955,14 @@ var Graph = class _Graph {
|
|
|
1705
1955
|
this.nextLayerId = prior?.nextLayerId ?? 0;
|
|
1706
1956
|
this.nextDummyId = prior?.nextDummyId ?? 0;
|
|
1707
1957
|
this.prior = prior;
|
|
1958
|
+
this.dirtyNodes = /* @__PURE__ */ new Set();
|
|
1959
|
+
this.dirtyEdges = /* @__PURE__ */ new Set();
|
|
1960
|
+
this.dirtyLayers = /* @__PURE__ */ new Set();
|
|
1961
|
+
this.dirtySegs = /* @__PURE__ */ new Set();
|
|
1962
|
+
this.delNodes = /* @__PURE__ */ new Set();
|
|
1963
|
+
this.delEdges = /* @__PURE__ */ new Set();
|
|
1964
|
+
this.delSegs = /* @__PURE__ */ new Set();
|
|
1965
|
+
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;
|
|
1708
1966
|
}
|
|
1709
1967
|
beginMutate() {
|
|
1710
1968
|
this.nodes = this.nodes.asMutable();
|
|
@@ -1734,28 +1992,10 @@ var Graph = class _Graph {
|
|
|
1734
1992
|
}
|
|
1735
1993
|
};
|
|
1736
1994
|
|
|
1737
|
-
// src/
|
|
1738
|
-
var
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
var injected = {};
|
|
1742
|
-
function styler(name, styles, prefix) {
|
|
1743
|
-
if (prefix === "g3p" && !injected[name]) {
|
|
1744
|
-
const style = document.createElement("style");
|
|
1745
|
-
style.textContent = styles;
|
|
1746
|
-
document.head.appendChild(style);
|
|
1747
|
-
injected[name] = true;
|
|
1748
|
-
}
|
|
1749
|
-
return (str, condition) => {
|
|
1750
|
-
if (!(condition ?? true)) return "";
|
|
1751
|
-
const parts = str.split(/\s+/);
|
|
1752
|
-
const fixed = parts.map((p) => `${prefix}-${name}-${p}`);
|
|
1753
|
-
return fixed.join(" ");
|
|
1754
|
-
};
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
// src/canvas/node.css
|
|
1758
|
-
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}";
|
|
1995
|
+
// src/common.ts
|
|
1996
|
+
var screenPos = (x, y) => ({ x, y });
|
|
1997
|
+
var canvasPos = (x, y) => ({ x, y });
|
|
1998
|
+
var graphPos = (x, y) => ({ x, y });
|
|
1759
1999
|
|
|
1760
2000
|
// src/canvas/node.tsx
|
|
1761
2001
|
var import_jsx_runtime2 = require("jsx-dom/jsx-runtime");
|
|
@@ -1763,89 +2003,75 @@ var Node2 = class {
|
|
|
1763
2003
|
selected;
|
|
1764
2004
|
hovered;
|
|
1765
2005
|
container;
|
|
1766
|
-
dims;
|
|
1767
2006
|
content;
|
|
1768
|
-
|
|
2007
|
+
canvas;
|
|
2008
|
+
data;
|
|
1769
2009
|
isDummy;
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
onContextMenu: () => null,
|
|
1780
|
-
onMouseDown: () => null,
|
|
1781
|
-
onMouseUp: () => null,
|
|
1782
|
-
classPrefix: "g3p",
|
|
1783
|
-
...options
|
|
1784
|
-
});
|
|
1785
|
-
if (!this.isDummy) {
|
|
1786
|
-
this.content = this.renderNode(this.data);
|
|
1787
|
-
this.measured = false;
|
|
2010
|
+
pos;
|
|
2011
|
+
constructor(canvas, data, isDummy = false) {
|
|
2012
|
+
this.canvas = canvas;
|
|
2013
|
+
this.data = data;
|
|
2014
|
+
this.selected = false;
|
|
2015
|
+
this.hovered = false;
|
|
2016
|
+
this.isDummy = isDummy;
|
|
2017
|
+
if (this.isDummy) {
|
|
2018
|
+
const size = canvas.dummyNodeSize;
|
|
1788
2019
|
} else {
|
|
1789
|
-
|
|
2020
|
+
const render = data.render ?? canvas.renderNode;
|
|
2021
|
+
this.content = this.renderContent(render(data.data, data));
|
|
1790
2022
|
}
|
|
1791
2023
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
this.dims = { w: rect.width, h: rect.height };
|
|
1795
|
-
this.measured = true;
|
|
1796
|
-
}
|
|
1797
|
-
handleClick(e) {
|
|
1798
|
-
e.stopPropagation();
|
|
1799
|
-
this.onClick?.(this.data, e);
|
|
1800
|
-
}
|
|
1801
|
-
handleMouseEnter(e) {
|
|
1802
|
-
this.onMouseEnter?.(this.data, e);
|
|
2024
|
+
remove() {
|
|
2025
|
+
this.container.remove();
|
|
1803
2026
|
}
|
|
1804
|
-
|
|
1805
|
-
this.
|
|
2027
|
+
append() {
|
|
2028
|
+
this.canvas.group.appendChild(this.container);
|
|
1806
2029
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
e.stopPropagation();
|
|
1810
|
-
this.onContextMenu(this.data, e);
|
|
1811
|
-
}
|
|
2030
|
+
needsContentSize() {
|
|
2031
|
+
return !this.isDummy && this.content instanceof HTMLElement;
|
|
1812
2032
|
}
|
|
1813
|
-
|
|
1814
|
-
this.
|
|
1815
|
-
}
|
|
1816
|
-
handleMouseUp(e) {
|
|
1817
|
-
this.onMouseUp?.(this.data, e);
|
|
2033
|
+
needsContainerSize() {
|
|
2034
|
+
return !this.isDummy;
|
|
1818
2035
|
}
|
|
1819
2036
|
setPos(pos) {
|
|
1820
|
-
console.log(`setPos:`, this, pos);
|
|
1821
2037
|
this.pos = pos;
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
2038
|
+
const { x, y } = pos;
|
|
2039
|
+
this.container.setAttribute("transform", `translate(${x}, ${y})`);
|
|
2040
|
+
}
|
|
2041
|
+
hasPorts() {
|
|
2042
|
+
return !!this.data?.ports?.in?.length || !!this.data?.ports?.out?.length;
|
|
2043
|
+
}
|
|
2044
|
+
renderContent(el) {
|
|
2045
|
+
const hasPorts = this.hasPorts();
|
|
2046
|
+
el = this.renderBorder(el);
|
|
2047
|
+
if (hasPorts)
|
|
2048
|
+
el = this.renderOutsidePorts(el);
|
|
2049
|
+
return el;
|
|
2050
|
+
}
|
|
2051
|
+
renderContainer() {
|
|
2052
|
+
const hasPorts = this.hasPorts();
|
|
2053
|
+
const inner = this.isDummy ? this.renderDummy() : this.renderForeign();
|
|
2054
|
+
const nodeType = this.data?.type;
|
|
2055
|
+
const typeClass = nodeType ? `g3p-node-type-${nodeType}` : "";
|
|
2056
|
+
this.container = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1828
2057
|
"g",
|
|
1829
2058
|
{
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
onMouseEnter: this.handleMouseEnter.bind(this),
|
|
1834
|
-
onMouseLeave: this.handleMouseLeave.bind(this),
|
|
1835
|
-
onContextMenu: this.handleContextMenu.bind(this),
|
|
1836
|
-
onMouseDown: this.handleMouseDown.bind(this),
|
|
1837
|
-
onMouseUp: this.handleMouseUp.bind(this),
|
|
1838
|
-
style: { cursor: "pointer" },
|
|
1839
|
-
children: this.isDummy ? this.renderDummy() : this.renderContent()
|
|
2059
|
+
className: `g3p-node-container ${this.isDummy ? "g3p-node-dummy" : ""} ${typeClass}`.trim(),
|
|
2060
|
+
"data-node-id": this.data?.id,
|
|
2061
|
+
children: inner
|
|
1840
2062
|
}
|
|
1841
2063
|
);
|
|
1842
2064
|
}
|
|
2065
|
+
renderForeign() {
|
|
2066
|
+
const { w, h } = this.data.dims;
|
|
2067
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("foreignObject", { width: w, height: h, children: this.content });
|
|
2068
|
+
}
|
|
1843
2069
|
renderDummy() {
|
|
1844
|
-
|
|
1845
|
-
let
|
|
2070
|
+
let w = this.canvas.dummyNodeSize;
|
|
2071
|
+
let h = this.canvas.dummyNodeSize;
|
|
1846
2072
|
w /= 2;
|
|
1847
2073
|
h /= 2;
|
|
1848
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2074
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
|
|
1849
2075
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1850
2076
|
"ellipse",
|
|
1851
2077
|
{
|
|
@@ -1853,7 +2079,7 @@ var Node2 = class {
|
|
|
1853
2079
|
cy: h,
|
|
1854
2080
|
rx: w,
|
|
1855
2081
|
ry: h,
|
|
1856
|
-
className:
|
|
2082
|
+
className: "g3p-node-background"
|
|
1857
2083
|
}
|
|
1858
2084
|
),
|
|
1859
2085
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -1864,160 +2090,176 @@ var Node2 = class {
|
|
|
1864
2090
|
rx: w,
|
|
1865
2091
|
ry: h,
|
|
1866
2092
|
fill: "none",
|
|
1867
|
-
className:
|
|
1868
|
-
strokeWidth: "2"
|
|
2093
|
+
className: "g3p-node-border"
|
|
1869
2094
|
}
|
|
1870
2095
|
)
|
|
1871
2096
|
] });
|
|
1872
2097
|
}
|
|
1873
|
-
|
|
1874
|
-
const
|
|
1875
|
-
const
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
className: c("border"),
|
|
1891
|
-
width: w,
|
|
1892
|
-
height: h,
|
|
1893
|
-
rx: 8,
|
|
1894
|
-
ry: 8,
|
|
1895
|
-
fill: "none",
|
|
1896
|
-
strokeWidth: "2"
|
|
1897
|
-
}
|
|
1898
|
-
),
|
|
1899
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1900
|
-
"foreignObject",
|
|
1901
|
-
{
|
|
1902
|
-
width: w,
|
|
1903
|
-
height: h,
|
|
1904
|
-
className: c("content-wrapper"),
|
|
1905
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1906
|
-
"div",
|
|
1907
|
-
{
|
|
1908
|
-
className: c("content"),
|
|
1909
|
-
style: {
|
|
1910
|
-
width: `${w}px`,
|
|
1911
|
-
height: `${h}px`,
|
|
1912
|
-
overflow: "hidden"
|
|
1913
|
-
},
|
|
1914
|
-
children: this.content
|
|
1915
|
-
}
|
|
1916
|
-
)
|
|
2098
|
+
measure(isVertical) {
|
|
2099
|
+
const rect = this.content.getBoundingClientRect();
|
|
2100
|
+
const data = this.data;
|
|
2101
|
+
data.dims = { w: rect.width, h: rect.height };
|
|
2102
|
+
for (const dir of ["in", "out"]) {
|
|
2103
|
+
const ports = data.ports?.[dir];
|
|
2104
|
+
if (!ports) continue;
|
|
2105
|
+
for (const port of ports) {
|
|
2106
|
+
const el = this.content.querySelector(`.g3p-node-port[data-node-id="${data.id}"][data-port-id="${port.id}"]`);
|
|
2107
|
+
if (!el) continue;
|
|
2108
|
+
const portRect = el.getBoundingClientRect();
|
|
2109
|
+
if (isVertical) {
|
|
2110
|
+
port.offset = portRect.left - rect.left;
|
|
2111
|
+
port.size = portRect.width;
|
|
2112
|
+
} else {
|
|
2113
|
+
port.offset = portRect.top - rect.top;
|
|
2114
|
+
port.size = portRect.height;
|
|
1917
2115
|
}
|
|
1918
|
-
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
getPortPosition(dir) {
|
|
2120
|
+
const o = this.canvas.orientation;
|
|
2121
|
+
if (dir === "in") {
|
|
2122
|
+
if (o === "TB") return "top";
|
|
2123
|
+
if (o === "BT") return "bottom";
|
|
2124
|
+
if (o === "LR") return "left";
|
|
2125
|
+
return "right";
|
|
2126
|
+
} else {
|
|
2127
|
+
if (o === "TB") return "bottom";
|
|
2128
|
+
if (o === "BT") return "top";
|
|
2129
|
+
if (o === "LR") return "right";
|
|
2130
|
+
return "left";
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
isVerticalOrientation() {
|
|
2134
|
+
const o = this.canvas.orientation;
|
|
2135
|
+
return o === "TB" || o === "BT";
|
|
2136
|
+
}
|
|
2137
|
+
isReversedOrientation() {
|
|
2138
|
+
const o = this.canvas.orientation;
|
|
2139
|
+
return o === "BT" || o === "RL";
|
|
2140
|
+
}
|
|
2141
|
+
renderPortRow(dir, inout) {
|
|
2142
|
+
const ports = this.data?.ports?.[dir];
|
|
2143
|
+
if (!ports?.length) return null;
|
|
2144
|
+
const pos = this.getPortPosition(dir);
|
|
2145
|
+
const isVertical = this.isVerticalOrientation();
|
|
2146
|
+
const layoutClass = isVertical ? "row" : "col";
|
|
2147
|
+
const rotateLabels = false;
|
|
2148
|
+
const rotateClass = rotateLabels ? `port-rotated-${pos}` : "";
|
|
2149
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: `g3p-node-ports g3p-node-ports-${layoutClass}`, children: ports.map((port) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2150
|
+
"div",
|
|
2151
|
+
{
|
|
2152
|
+
className: `g3p-node-port g3p-node-port-${inout}-${pos} ${rotateClass}`,
|
|
2153
|
+
"data-node-id": this.data.id,
|
|
2154
|
+
"data-port-id": port.id,
|
|
2155
|
+
children: port.label ?? port.id
|
|
2156
|
+
}
|
|
2157
|
+
)) });
|
|
2158
|
+
}
|
|
2159
|
+
renderInsidePorts(el) {
|
|
2160
|
+
const isVertical = this.isVerticalOrientation();
|
|
2161
|
+
const isReversed = this.isReversedOrientation();
|
|
2162
|
+
let inPorts = this.renderPortRow("in", "in");
|
|
2163
|
+
let outPorts = this.renderPortRow("out", "in");
|
|
2164
|
+
if (!inPorts && !outPorts) return el;
|
|
2165
|
+
if (isReversed) [inPorts, outPorts] = [outPorts, inPorts];
|
|
2166
|
+
const wrapperClass = isVertical ? "v" : "h";
|
|
2167
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `g3p-node-with-ports g3p-node-with-ports-${wrapperClass}`, children: [
|
|
2168
|
+
inPorts,
|
|
2169
|
+
el,
|
|
2170
|
+
outPorts
|
|
1919
2171
|
] });
|
|
1920
2172
|
}
|
|
2173
|
+
renderOutsidePorts(el) {
|
|
2174
|
+
const isVertical = this.isVerticalOrientation();
|
|
2175
|
+
const isReversed = this.isReversedOrientation();
|
|
2176
|
+
let inPorts = this.renderPortRow("in", "out");
|
|
2177
|
+
let outPorts = this.renderPortRow("out", "out");
|
|
2178
|
+
if (!inPorts && !outPorts) return el;
|
|
2179
|
+
if (isReversed) [inPorts, outPorts] = [outPorts, inPorts];
|
|
2180
|
+
const wrapperClass = isVertical ? "v" : "h";
|
|
2181
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `g3p-node-with-ports g3p-node-with-ports-${wrapperClass}`, children: [
|
|
2182
|
+
inPorts,
|
|
2183
|
+
el,
|
|
2184
|
+
outPorts
|
|
2185
|
+
] });
|
|
2186
|
+
}
|
|
2187
|
+
renderBorder(el) {
|
|
2188
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "g3p-node-border", children: el });
|
|
2189
|
+
}
|
|
1921
2190
|
};
|
|
1922
2191
|
|
|
1923
|
-
// src/canvas/seg.css
|
|
1924
|
-
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}";
|
|
1925
|
-
|
|
1926
2192
|
// src/canvas/seg.tsx
|
|
1927
2193
|
var import_jsx_runtime3 = require("jsx-dom/jsx-runtime");
|
|
1928
2194
|
var Seg2 = class {
|
|
2195
|
+
id;
|
|
1929
2196
|
selected;
|
|
1930
2197
|
hovered;
|
|
1931
|
-
|
|
2198
|
+
canvas;
|
|
2199
|
+
type;
|
|
2200
|
+
svg;
|
|
2201
|
+
el;
|
|
2202
|
+
source;
|
|
2203
|
+
target;
|
|
2204
|
+
edgeIds;
|
|
2205
|
+
constructor(canvas, data, g) {
|
|
2206
|
+
this.id = data.id;
|
|
2207
|
+
this.canvas = canvas;
|
|
1932
2208
|
this.selected = false;
|
|
1933
2209
|
this.hovered = false;
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
this.
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
this.
|
|
1955
|
-
|
|
1956
|
-
handleMouseLeave(e) {
|
|
1957
|
-
this.onMouseLeave?.(this.edgeData, e);
|
|
1958
|
-
}
|
|
1959
|
-
handleContextMenu(e) {
|
|
1960
|
-
if (this.onContextMenu) {
|
|
1961
|
-
e.stopPropagation();
|
|
1962
|
-
this.onContextMenu(this.edgeData, e);
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
renderTerminals() {
|
|
1966
|
-
return {
|
|
1967
|
-
source: this.renderTerminal(this.attrs.sourceTerminal, "source"),
|
|
1968
|
-
target: this.renderTerminal(this.attrs.targetTerminal, "target")
|
|
1969
|
-
};
|
|
1970
|
-
}
|
|
1971
|
-
setSVG(svg) {
|
|
1972
|
-
this.svg = svg;
|
|
1973
|
-
const n = this.el.childElementCount;
|
|
1974
|
-
this.el.childNodes[n - 2].setAttribute("d", svg);
|
|
1975
|
-
this.el.childNodes[n - 1].setAttribute("d", svg);
|
|
2210
|
+
this.svg = data.svg;
|
|
2211
|
+
this.source = { ...data.source, isDummy: data.sourceNode(g).isDummy };
|
|
2212
|
+
this.target = { ...data.target, isDummy: data.targetNode(g).isDummy };
|
|
2213
|
+
this.type = data.type;
|
|
2214
|
+
this.edgeIds = data.edgeIds.toArray();
|
|
2215
|
+
this.el = this.render();
|
|
2216
|
+
}
|
|
2217
|
+
append() {
|
|
2218
|
+
this.canvas.group.appendChild(this.el);
|
|
2219
|
+
}
|
|
2220
|
+
remove() {
|
|
2221
|
+
this.el.remove();
|
|
2222
|
+
}
|
|
2223
|
+
update(data, g) {
|
|
2224
|
+
this.svg = data.svg;
|
|
2225
|
+
this.type = data.type;
|
|
2226
|
+
this.source = { ...data.source, isDummy: data.sourceNode(g).isDummy };
|
|
2227
|
+
this.target = { ...data.target, isDummy: data.targetNode(g).isDummy };
|
|
2228
|
+
this.edgeIds = data.edgeIds.toArray();
|
|
2229
|
+
this.remove();
|
|
2230
|
+
this.el = this.render();
|
|
2231
|
+
this.append();
|
|
1976
2232
|
}
|
|
1977
2233
|
render() {
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
strokeDasharray: this.attrs.style
|
|
1983
|
-
};
|
|
1984
|
-
const hoverAttrs = {
|
|
1985
|
-
...styleAttrs,
|
|
1986
|
-
strokeWidth: styleAttrs.strokeWidth ? Math.max(styleAttrs.strokeWidth * 3, 10) : void 0
|
|
1987
|
-
};
|
|
1988
|
-
const { source, target } = this.renderTerminals();
|
|
2234
|
+
let { source, target } = normalize(this);
|
|
2235
|
+
if (this.source.isDummy) source = void 0;
|
|
2236
|
+
if (this.target.isDummy) target = void 0;
|
|
2237
|
+
const typeClass = this.type ? `g3p-edge-type-${this.type}` : "";
|
|
1989
2238
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1990
2239
|
"g",
|
|
1991
2240
|
{
|
|
1992
2241
|
ref: (el) => this.el = el,
|
|
1993
|
-
id: `g3p-seg-${this.
|
|
1994
|
-
className:
|
|
1995
|
-
|
|
1996
|
-
onMouseEnter: this.handleMouseEnter.bind(this),
|
|
1997
|
-
onMouseLeave: this.handleMouseLeave.bind(this),
|
|
1998
|
-
onContextMenu: this.handleContextMenu.bind(this),
|
|
2242
|
+
id: `g3p-seg-${this.id}`,
|
|
2243
|
+
className: `g3p-seg-container ${typeClass}`.trim(),
|
|
2244
|
+
"data-edge-id": this.id,
|
|
1999
2245
|
children: [
|
|
2000
|
-
source?.defs,
|
|
2001
|
-
target?.defs,
|
|
2002
2246
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2003
2247
|
"path",
|
|
2004
2248
|
{
|
|
2005
2249
|
d: this.svg,
|
|
2006
|
-
...styleAttrs,
|
|
2007
2250
|
fill: "none",
|
|
2008
|
-
className:
|
|
2009
|
-
markerStart: source ? `url(
|
|
2010
|
-
markerEnd: target ? `url(
|
|
2251
|
+
className: "g3p-seg-line",
|
|
2252
|
+
markerStart: source ? `url(#g3p-marker-${source}-reverse)` : void 0,
|
|
2253
|
+
markerEnd: target ? `url(#g3p-marker-${target})` : void 0
|
|
2011
2254
|
}
|
|
2012
2255
|
),
|
|
2013
2256
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2014
2257
|
"path",
|
|
2015
2258
|
{
|
|
2016
2259
|
d: this.svg,
|
|
2017
|
-
...hoverAttrs,
|
|
2018
2260
|
stroke: "transparent",
|
|
2019
2261
|
fill: "none",
|
|
2020
|
-
className:
|
|
2262
|
+
className: "g3p-seg-hitbox",
|
|
2021
2263
|
style: { cursor: "pointer" }
|
|
2022
2264
|
}
|
|
2023
2265
|
)
|
|
@@ -2025,243 +2267,1396 @@ var Seg2 = class {
|
|
|
2025
2267
|
}
|
|
2026
2268
|
);
|
|
2027
2269
|
}
|
|
2028
|
-
renderTerminal(type, side) {
|
|
2029
|
-
if (!type)
|
|
2030
|
-
return null;
|
|
2031
|
-
const id = `g3p-seg-${this.segId}-${side}-${type}`;
|
|
2032
|
-
const defs = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2033
|
-
"marker",
|
|
2034
|
-
{
|
|
2035
|
-
id,
|
|
2036
|
-
markerWidth: "10",
|
|
2037
|
-
markerHeight: "10",
|
|
2038
|
-
refX: "9",
|
|
2039
|
-
refY: "3",
|
|
2040
|
-
orient: "auto",
|
|
2041
|
-
markerUnits: "userSpaceOnUse",
|
|
2042
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M0,0 L0,6 L9,3 z" })
|
|
2043
|
-
}
|
|
2044
|
-
) });
|
|
2045
|
-
return { id, defs };
|
|
2046
|
-
}
|
|
2047
2270
|
};
|
|
2048
2271
|
|
|
2049
|
-
// src/canvas/
|
|
2050
|
-
var
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
nodeStyle: () => ({}),
|
|
2065
|
-
edgeStyle: () => ({}),
|
|
2066
|
-
portStyle: "outside",
|
|
2067
|
-
classPrefix: "g3p",
|
|
2068
|
-
width: "100%",
|
|
2069
|
-
height: "100%",
|
|
2070
|
-
transform: { x: 0, y: 0, scale: 1 },
|
|
2071
|
-
bounds: { min: { x: 0, y: 0 }, max: { x: 1, y: 1 } },
|
|
2072
|
-
...options
|
|
2073
|
-
});
|
|
2074
|
-
this.nodes = /* @__PURE__ */ new Map();
|
|
2075
|
-
this.segs = /* @__PURE__ */ new Map();
|
|
2076
|
-
this.updating = false;
|
|
2077
|
-
this.createMeasurementContainer();
|
|
2272
|
+
// src/canvas/editMode.ts
|
|
2273
|
+
var EditMode = class {
|
|
2274
|
+
_state = { type: "idle" };
|
|
2275
|
+
_editable = false;
|
|
2276
|
+
get state() {
|
|
2277
|
+
return this._state;
|
|
2278
|
+
}
|
|
2279
|
+
get editable() {
|
|
2280
|
+
return this._editable;
|
|
2281
|
+
}
|
|
2282
|
+
set editable(value) {
|
|
2283
|
+
this._editable = value;
|
|
2284
|
+
if (!value) {
|
|
2285
|
+
this.reset();
|
|
2286
|
+
}
|
|
2078
2287
|
}
|
|
2079
|
-
|
|
2080
|
-
this.
|
|
2081
|
-
this.measurement.style.cssText = `
|
|
2082
|
-
position: absolute;
|
|
2083
|
-
left: -9999px;
|
|
2084
|
-
top: -9999px;
|
|
2085
|
-
visibility: hidden;
|
|
2086
|
-
pointer-events: none;
|
|
2087
|
-
`;
|
|
2088
|
-
document.body.appendChild(this.measurement);
|
|
2288
|
+
get isIdle() {
|
|
2289
|
+
return this._state.type === "idle";
|
|
2089
2290
|
}
|
|
2090
|
-
|
|
2091
|
-
this.
|
|
2092
|
-
callback();
|
|
2093
|
-
this.updating = false;
|
|
2094
|
-
let bx0 = Infinity, by0 = Infinity;
|
|
2095
|
-
let bx1 = -Infinity, by1 = -Infinity;
|
|
2096
|
-
for (const node of this.nodes.values()) {
|
|
2097
|
-
const nx0 = node.pos.x, nx1 = node.pos.x + node.dims.w;
|
|
2098
|
-
const ny0 = node.pos.y, ny1 = node.pos.y + node.dims.h;
|
|
2099
|
-
bx0 = Math.min(bx0, nx0);
|
|
2100
|
-
by0 = Math.min(by0, ny0);
|
|
2101
|
-
bx1 = Math.max(bx1, nx1);
|
|
2102
|
-
by1 = Math.max(by1, ny1);
|
|
2103
|
-
}
|
|
2104
|
-
this.bounds = { min: { x: bx0, y: by0 }, max: { x: bx1, y: by1 } };
|
|
2105
|
-
console.log("bounds", this.bounds);
|
|
2106
|
-
this.root.setAttribute("viewBox", this.viewBox());
|
|
2291
|
+
get isPanning() {
|
|
2292
|
+
return this._state.type === "panning";
|
|
2107
2293
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
if (!node) throw new Error("node not found");
|
|
2111
|
-
if (!node.container) node.render();
|
|
2112
|
-
node.setPos(opts.pos);
|
|
2113
|
-
this.group.appendChild(node.container);
|
|
2114
|
-
}
|
|
2115
|
-
updateNode(opts) {
|
|
2116
|
-
const node = this.nodes.get(opts.data);
|
|
2117
|
-
if (!node) throw new Error("node not found");
|
|
2118
|
-
node.setPos(opts.pos);
|
|
2119
|
-
}
|
|
2120
|
-
deleteNode(opts) {
|
|
2121
|
-
const node = this.nodes.get(opts.data);
|
|
2122
|
-
if (!node) throw new Error("node not found");
|
|
2123
|
-
node.container.remove();
|
|
2124
|
-
}
|
|
2125
|
-
addSeg(opts) {
|
|
2126
|
-
const seg = new Seg2(opts);
|
|
2127
|
-
this.segs.set(seg.segId, seg);
|
|
2128
|
-
seg.render();
|
|
2129
|
-
this.group.appendChild(seg.el);
|
|
2130
|
-
}
|
|
2131
|
-
updateSeg(opts) {
|
|
2132
|
-
const seg = this.segs.get(opts.segId);
|
|
2133
|
-
if (!seg) throw new Error("seg not found");
|
|
2134
|
-
seg.setSVG(opts.svg);
|
|
2294
|
+
get isCreatingEdge() {
|
|
2295
|
+
return this._state.type === "new-edge";
|
|
2135
2296
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
seg.el.remove();
|
|
2140
|
-
this.segs.delete(seg.segId);
|
|
2297
|
+
/** Start panning the canvas */
|
|
2298
|
+
startPan(startCanvas, startTransform) {
|
|
2299
|
+
this._state = { type: "panning", startCanvas, startTransform };
|
|
2141
2300
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
this.nodes.set(node.data, node);
|
|
2153
|
-
if (!node.measured) {
|
|
2154
|
-
this.measurement.appendChild(node.content);
|
|
2155
|
-
newNodes.push(node);
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
return new Promise((resolve) => {
|
|
2159
|
-
requestAnimationFrame(() => {
|
|
2160
|
-
for (const node of newNodes)
|
|
2161
|
-
node.getSize();
|
|
2162
|
-
this.measurement.textContent = "";
|
|
2163
|
-
resolve();
|
|
2164
|
-
});
|
|
2165
|
-
});
|
|
2301
|
+
/** Start creating a new edge from a node or port */
|
|
2302
|
+
startNewEdge(id, startGraph, port) {
|
|
2303
|
+
if (!this._editable) return;
|
|
2304
|
+
this._state = {
|
|
2305
|
+
type: "new-edge",
|
|
2306
|
+
source: { id, port },
|
|
2307
|
+
startGraph,
|
|
2308
|
+
currentGraph: startGraph,
|
|
2309
|
+
target: null
|
|
2310
|
+
};
|
|
2166
2311
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2312
|
+
/** Update the current position during new-edge mode */
|
|
2313
|
+
updateNewEdgePosition(currentGraph) {
|
|
2314
|
+
if (this._state.type === "new-edge") {
|
|
2315
|
+
this._state = { ...this._state, currentGraph };
|
|
2316
|
+
}
|
|
2169
2317
|
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2318
|
+
/** Update the hover target during new-edge mode */
|
|
2319
|
+
setHoverTarget(target) {
|
|
2320
|
+
if (this._state.type === "new-edge") {
|
|
2321
|
+
this._state = { ...this._state, target };
|
|
2322
|
+
}
|
|
2172
2323
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2324
|
+
/** Get the new-edge state if active */
|
|
2325
|
+
getNewEdgeState() {
|
|
2326
|
+
if (this._state.type === "new-edge") {
|
|
2327
|
+
return this._state;
|
|
2328
|
+
}
|
|
2329
|
+
return null;
|
|
2175
2330
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2331
|
+
/** Reset to idle state */
|
|
2332
|
+
reset() {
|
|
2333
|
+
this._state = { type: "idle" };
|
|
2178
2334
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2335
|
+
};
|
|
2336
|
+
|
|
2337
|
+
// src/canvas/newEdge.tsx
|
|
2338
|
+
var import_jsx_runtime4 = require("jsx-dom/jsx-runtime");
|
|
2339
|
+
function renderNewEdge({ start, end }) {
|
|
2340
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { className: "g3p-new-edge-container", children: [
|
|
2341
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2342
|
+
"circle",
|
|
2343
|
+
{
|
|
2344
|
+
cx: start.x,
|
|
2345
|
+
cy: start.y,
|
|
2346
|
+
r: 4,
|
|
2347
|
+
className: "g3p-new-edge-origin"
|
|
2348
|
+
}
|
|
2349
|
+
),
|
|
2350
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2351
|
+
"line",
|
|
2352
|
+
{
|
|
2353
|
+
x1: start.x,
|
|
2354
|
+
y1: start.y,
|
|
2355
|
+
x2: end.x,
|
|
2356
|
+
y2: end.y,
|
|
2357
|
+
className: "g3p-new-edge-line"
|
|
2358
|
+
}
|
|
2359
|
+
),
|
|
2360
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2361
|
+
"circle",
|
|
2362
|
+
{
|
|
2363
|
+
cx: end.x,
|
|
2364
|
+
cy: end.y,
|
|
2365
|
+
r: 3,
|
|
2366
|
+
className: "g3p-new-edge-end"
|
|
2367
|
+
}
|
|
2368
|
+
)
|
|
2369
|
+
] });
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
// src/canvas/modal.tsx
|
|
2373
|
+
var import_jsx_runtime5 = require("jsx-dom/jsx-runtime");
|
|
2374
|
+
var Modal = class {
|
|
2375
|
+
container;
|
|
2376
|
+
overlay;
|
|
2377
|
+
dialog;
|
|
2378
|
+
onClose;
|
|
2379
|
+
mouseDownOnOverlay = false;
|
|
2380
|
+
constructor(options) {
|
|
2381
|
+
this.onClose = options.onClose;
|
|
2382
|
+
this.overlay = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2383
|
+
"div",
|
|
2384
|
+
{
|
|
2385
|
+
className: "g3p-modal-overlay",
|
|
2386
|
+
onMouseDown: (e) => {
|
|
2387
|
+
this.mouseDownOnOverlay = e.target === this.overlay;
|
|
2388
|
+
},
|
|
2389
|
+
onMouseUp: (e) => {
|
|
2390
|
+
if (this.mouseDownOnOverlay && e.target === this.overlay) this.close();
|
|
2391
|
+
this.mouseDownOnOverlay = false;
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
);
|
|
2395
|
+
this.dialog = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-dialog", children: [
|
|
2396
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-header", children: [
|
|
2397
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "g3p-modal-title", children: options.title }),
|
|
2398
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2399
|
+
"button",
|
|
2400
|
+
{
|
|
2401
|
+
className: "g3p-modal-close",
|
|
2402
|
+
onClick: () => this.close(),
|
|
2403
|
+
children: "\xD7"
|
|
2404
|
+
}
|
|
2405
|
+
)
|
|
2406
|
+
] }),
|
|
2407
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-body" }),
|
|
2408
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-footer" })
|
|
2409
|
+
] });
|
|
2410
|
+
if (options.position) {
|
|
2411
|
+
this.dialog.style.position = "absolute";
|
|
2412
|
+
this.dialog.style.left = `${options.position.x}px`;
|
|
2413
|
+
this.dialog.style.top = `${options.position.y}px`;
|
|
2414
|
+
this.dialog.style.transform = "translate(-50%, -50%)";
|
|
2415
|
+
}
|
|
2416
|
+
this.overlay.appendChild(this.dialog);
|
|
2417
|
+
this.container = this.overlay;
|
|
2418
|
+
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
2419
|
+
document.addEventListener("keydown", this.handleKeyDown);
|
|
2420
|
+
}
|
|
2421
|
+
handleKeyDown(e) {
|
|
2422
|
+
if (e.key === "Escape") {
|
|
2423
|
+
this.close();
|
|
2424
|
+
}
|
|
2181
2425
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2426
|
+
get body() {
|
|
2427
|
+
return this.dialog.querySelector(".g3p-modal-body");
|
|
2428
|
+
}
|
|
2429
|
+
get footer() {
|
|
2430
|
+
return this.dialog.querySelector(".g3p-modal-footer");
|
|
2431
|
+
}
|
|
2432
|
+
show(parent) {
|
|
2433
|
+
parent.appendChild(this.container);
|
|
2434
|
+
const firstInput = this.dialog.querySelector("input, select, button");
|
|
2435
|
+
if (firstInput) firstInput.focus();
|
|
2436
|
+
}
|
|
2437
|
+
close() {
|
|
2438
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2439
|
+
this.container.remove();
|
|
2440
|
+
this.onClose();
|
|
2441
|
+
}
|
|
2442
|
+
};
|
|
2443
|
+
var NewNodeModal = class extends Modal {
|
|
2444
|
+
fieldInputs = /* @__PURE__ */ new Map();
|
|
2445
|
+
typeSelect;
|
|
2446
|
+
submitCallback;
|
|
2447
|
+
fields;
|
|
2448
|
+
constructor(options) {
|
|
2449
|
+
super({
|
|
2450
|
+
title: "New Node",
|
|
2451
|
+
onClose: () => options.onCancel?.()
|
|
2452
|
+
});
|
|
2453
|
+
this.submitCallback = options.onSubmit;
|
|
2454
|
+
this.fields = options.fields ?? /* @__PURE__ */ new Map([["title", "string"]]);
|
|
2455
|
+
this.renderBody(options.nodeTypes);
|
|
2456
|
+
this.renderFooter();
|
|
2457
|
+
}
|
|
2458
|
+
renderBody(nodeTypes) {
|
|
2459
|
+
this.body.innerHTML = "";
|
|
2460
|
+
for (const [name, type] of this.fields) {
|
|
2461
|
+
const label = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2462
|
+
const fieldGroup = this.renderField(name, label, type);
|
|
2463
|
+
this.body.appendChild(fieldGroup);
|
|
2464
|
+
}
|
|
2465
|
+
if (nodeTypes && nodeTypes.length > 0) {
|
|
2466
|
+
const typeGroup = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2467
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: "Type" }),
|
|
2468
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2469
|
+
"select",
|
|
2470
|
+
{
|
|
2471
|
+
className: "g3p-modal-select",
|
|
2472
|
+
ref: (el) => this.typeSelect = el,
|
|
2473
|
+
children: [
|
|
2474
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", children: "Default" }),
|
|
2475
|
+
nodeTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, children: type }))
|
|
2476
|
+
]
|
|
2477
|
+
}
|
|
2478
|
+
)
|
|
2479
|
+
] });
|
|
2480
|
+
this.body.appendChild(typeGroup);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
renderField(name, label, type) {
|
|
2484
|
+
if (type === "boolean") {
|
|
2485
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-field g3p-modal-field-checkbox", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "g3p-modal-label", children: [
|
|
2486
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2487
|
+
"input",
|
|
2488
|
+
{
|
|
2489
|
+
type: "checkbox",
|
|
2490
|
+
className: "g3p-modal-checkbox",
|
|
2491
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2492
|
+
}
|
|
2493
|
+
),
|
|
2494
|
+
label
|
|
2495
|
+
] }) });
|
|
2496
|
+
}
|
|
2497
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2498
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: label }),
|
|
2499
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2500
|
+
"input",
|
|
2501
|
+
{
|
|
2502
|
+
type: type === "number" ? "number" : "text",
|
|
2503
|
+
className: "g3p-modal-input",
|
|
2504
|
+
placeholder: `Enter ${label.toLowerCase()}`,
|
|
2505
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2506
|
+
}
|
|
2507
|
+
)
|
|
2508
|
+
] });
|
|
2509
|
+
}
|
|
2510
|
+
renderFooter() {
|
|
2511
|
+
this.footer.innerHTML = "";
|
|
2512
|
+
this.footer.appendChild(
|
|
2513
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-buttons", children: [
|
|
2514
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2515
|
+
"button",
|
|
2516
|
+
{
|
|
2517
|
+
className: "g3p-modal-btn g3p-modal-btn-secondary",
|
|
2518
|
+
onClick: () => this.close(),
|
|
2519
|
+
children: "Cancel"
|
|
2520
|
+
}
|
|
2521
|
+
),
|
|
2522
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2523
|
+
"button",
|
|
2524
|
+
{
|
|
2525
|
+
className: "g3p-modal-btn g3p-modal-btn-primary",
|
|
2526
|
+
onClick: () => this.submit(),
|
|
2527
|
+
children: "Create"
|
|
2528
|
+
}
|
|
2529
|
+
)
|
|
2530
|
+
] })
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2533
|
+
submit() {
|
|
2534
|
+
const data = {};
|
|
2535
|
+
for (const [name, type] of this.fields) {
|
|
2536
|
+
const input = this.fieldInputs.get(name);
|
|
2537
|
+
if (!input) continue;
|
|
2538
|
+
if (type === "boolean") {
|
|
2539
|
+
data[name] = input.checked;
|
|
2540
|
+
} else if (type === "number") {
|
|
2541
|
+
const val = input.value;
|
|
2542
|
+
if (val) data[name] = Number(val);
|
|
2543
|
+
} else {
|
|
2544
|
+
const val = input.value.trim();
|
|
2545
|
+
if (val) data[name] = val;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
if (Object.keys(data).length === 0) {
|
|
2549
|
+
const firstInput = this.fieldInputs.values().next().value;
|
|
2550
|
+
if (firstInput) firstInput.focus();
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (this.typeSelect?.value) {
|
|
2554
|
+
data.type = this.typeSelect.value;
|
|
2555
|
+
}
|
|
2556
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2557
|
+
this.container.remove();
|
|
2558
|
+
this.submitCallback(data);
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2561
|
+
var EditNodeModal = class extends Modal {
|
|
2562
|
+
fieldInputs = /* @__PURE__ */ new Map();
|
|
2563
|
+
typeSelect;
|
|
2564
|
+
node;
|
|
2565
|
+
fields;
|
|
2566
|
+
submitCallback;
|
|
2567
|
+
deleteCallback;
|
|
2568
|
+
constructor(options) {
|
|
2569
|
+
super({
|
|
2570
|
+
title: "Edit Node",
|
|
2571
|
+
position: options.position,
|
|
2572
|
+
onClose: () => options.onCancel?.()
|
|
2573
|
+
});
|
|
2574
|
+
this.node = options.node;
|
|
2575
|
+
this.submitCallback = options.onSubmit;
|
|
2576
|
+
this.deleteCallback = options.onDelete;
|
|
2577
|
+
this.fields = options.fields ?? /* @__PURE__ */ new Map([["title", "string"]]);
|
|
2578
|
+
if (!options.fields && !this.node.title)
|
|
2579
|
+
this.node = { ...this.node, title: this.node.id };
|
|
2580
|
+
this.renderBody(options.nodeTypes);
|
|
2581
|
+
this.renderFooter();
|
|
2582
|
+
}
|
|
2583
|
+
renderBody(nodeTypes) {
|
|
2584
|
+
console.log(`renderBody`, this.node);
|
|
2585
|
+
this.body.innerHTML = "";
|
|
2586
|
+
for (const [name, type] of this.fields) {
|
|
2587
|
+
const label = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2588
|
+
const currentValue = this.node[name];
|
|
2589
|
+
const fieldGroup = this.renderField(name, label, type, currentValue);
|
|
2590
|
+
this.body.appendChild(fieldGroup);
|
|
2591
|
+
}
|
|
2592
|
+
if (nodeTypes && nodeTypes.length > 0) {
|
|
2593
|
+
const currentType = this.node.type ?? "";
|
|
2594
|
+
const typeGroup = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2595
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: "Type" }),
|
|
2596
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2597
|
+
"select",
|
|
2598
|
+
{
|
|
2599
|
+
className: "g3p-modal-select",
|
|
2600
|
+
ref: (el) => this.typeSelect = el,
|
|
2601
|
+
children: [
|
|
2602
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", selected: !currentType, children: "Default" }),
|
|
2603
|
+
nodeTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, selected: type === currentType, children: type }))
|
|
2604
|
+
]
|
|
2605
|
+
}
|
|
2606
|
+
)
|
|
2607
|
+
] });
|
|
2608
|
+
this.body.appendChild(typeGroup);
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
renderField(name, label, type, currentValue) {
|
|
2612
|
+
if (type === "boolean") {
|
|
2613
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-field g3p-modal-field-checkbox", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "g3p-modal-label", children: [
|
|
2614
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2615
|
+
"input",
|
|
2616
|
+
{
|
|
2617
|
+
type: "checkbox",
|
|
2618
|
+
className: "g3p-modal-checkbox",
|
|
2619
|
+
checked: !!currentValue,
|
|
2620
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2621
|
+
}
|
|
2622
|
+
),
|
|
2623
|
+
label
|
|
2624
|
+
] }) });
|
|
2625
|
+
}
|
|
2626
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2627
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: label }),
|
|
2628
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2629
|
+
"input",
|
|
2630
|
+
{
|
|
2631
|
+
type: type === "number" ? "number" : "text",
|
|
2632
|
+
className: "g3p-modal-input",
|
|
2633
|
+
value: currentValue ?? "",
|
|
2634
|
+
ref: (el) => this.fieldInputs.set(name, el)
|
|
2635
|
+
}
|
|
2636
|
+
)
|
|
2637
|
+
] });
|
|
2638
|
+
}
|
|
2639
|
+
renderFooter() {
|
|
2640
|
+
this.footer.innerHTML = "";
|
|
2641
|
+
this.footer.appendChild(
|
|
2642
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-buttons", children: [
|
|
2643
|
+
this.deleteCallback && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2644
|
+
"button",
|
|
2645
|
+
{
|
|
2646
|
+
className: "g3p-modal-btn g3p-modal-btn-danger",
|
|
2647
|
+
onClick: () => this.delete(),
|
|
2648
|
+
children: "Delete"
|
|
2649
|
+
}
|
|
2650
|
+
),
|
|
2651
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-spacer" }),
|
|
2652
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2653
|
+
"button",
|
|
2654
|
+
{
|
|
2655
|
+
className: "g3p-modal-btn g3p-modal-btn-secondary",
|
|
2656
|
+
onClick: () => this.close(),
|
|
2657
|
+
children: "Cancel"
|
|
2658
|
+
}
|
|
2659
|
+
),
|
|
2660
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2661
|
+
"button",
|
|
2662
|
+
{
|
|
2663
|
+
className: "g3p-modal-btn g3p-modal-btn-primary",
|
|
2664
|
+
onClick: () => this.submit(),
|
|
2665
|
+
children: "Save"
|
|
2666
|
+
}
|
|
2667
|
+
)
|
|
2668
|
+
] })
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2671
|
+
submit() {
|
|
2672
|
+
const data = { ...this.node };
|
|
2673
|
+
for (const [name, type] of this.fields) {
|
|
2674
|
+
const input = this.fieldInputs.get(name);
|
|
2675
|
+
if (!input) continue;
|
|
2676
|
+
if (type === "boolean") {
|
|
2677
|
+
data[name] = input.checked;
|
|
2678
|
+
} else if (type === "number") {
|
|
2679
|
+
const val = input.value;
|
|
2680
|
+
data[name] = val ? Number(val) : void 0;
|
|
2681
|
+
} else {
|
|
2682
|
+
const val = input.value.trim();
|
|
2683
|
+
data[name] = val || void 0;
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
if (this.typeSelect) {
|
|
2687
|
+
data.type = this.typeSelect.value || void 0;
|
|
2688
|
+
}
|
|
2689
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2690
|
+
this.container.remove();
|
|
2691
|
+
this.submitCallback(data);
|
|
2692
|
+
}
|
|
2693
|
+
delete() {
|
|
2694
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2695
|
+
this.container.remove();
|
|
2696
|
+
this.deleteCallback?.();
|
|
2697
|
+
}
|
|
2698
|
+
};
|
|
2699
|
+
var EditEdgeModal = class _EditEdgeModal extends Modal {
|
|
2700
|
+
sourceMarkerSelect;
|
|
2701
|
+
targetMarkerSelect;
|
|
2702
|
+
typeSelect;
|
|
2703
|
+
edge;
|
|
2704
|
+
submitCallback;
|
|
2705
|
+
deleteCallback;
|
|
2706
|
+
static markerTypes = ["none", "arrow", "circle", "diamond", "bar"];
|
|
2707
|
+
constructor(options) {
|
|
2708
|
+
super({
|
|
2709
|
+
title: "Edit Edge",
|
|
2710
|
+
onClose: () => options.onCancel?.()
|
|
2711
|
+
});
|
|
2712
|
+
this.edge = options.edge;
|
|
2713
|
+
this.submitCallback = options.onSubmit;
|
|
2714
|
+
this.deleteCallback = options.onDelete;
|
|
2715
|
+
this.renderBody(options.edgeTypes);
|
|
2716
|
+
this.renderFooter();
|
|
2717
|
+
}
|
|
2718
|
+
renderBody(edgeTypes) {
|
|
2719
|
+
this.body.innerHTML = "";
|
|
2720
|
+
const currentSourceMarker = this.edge.source?.marker ?? "none";
|
|
2721
|
+
const currentTargetMarker = this.edge.target?.marker ?? "arrow";
|
|
2722
|
+
const sourceGroup = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2723
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: "Source Marker" }),
|
|
2724
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2725
|
+
"select",
|
|
2726
|
+
{
|
|
2727
|
+
className: "g3p-modal-select",
|
|
2728
|
+
ref: (el) => this.sourceMarkerSelect = el,
|
|
2729
|
+
children: _EditEdgeModal.markerTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, selected: type === currentSourceMarker, children: type }))
|
|
2730
|
+
}
|
|
2731
|
+
)
|
|
2732
|
+
] });
|
|
2733
|
+
this.body.appendChild(sourceGroup);
|
|
2734
|
+
const targetGroup = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2735
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: "Target Marker" }),
|
|
2736
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2737
|
+
"select",
|
|
2738
|
+
{
|
|
2739
|
+
className: "g3p-modal-select",
|
|
2740
|
+
ref: (el) => this.targetMarkerSelect = el,
|
|
2741
|
+
children: _EditEdgeModal.markerTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, selected: type === currentTargetMarker, children: type }))
|
|
2742
|
+
}
|
|
2743
|
+
)
|
|
2744
|
+
] });
|
|
2745
|
+
this.body.appendChild(targetGroup);
|
|
2746
|
+
if (edgeTypes && edgeTypes.length > 0) {
|
|
2747
|
+
const currentType = this.edge.type ?? "";
|
|
2748
|
+
const typeGroup = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-field", children: [
|
|
2749
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "g3p-modal-label", children: "Type" }),
|
|
2750
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2751
|
+
"select",
|
|
2752
|
+
{
|
|
2753
|
+
className: "g3p-modal-select",
|
|
2754
|
+
ref: (el) => this.typeSelect = el,
|
|
2755
|
+
children: [
|
|
2756
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", selected: !currentType, children: "Default" }),
|
|
2757
|
+
edgeTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, selected: type === currentType, children: type }))
|
|
2758
|
+
]
|
|
2759
|
+
}
|
|
2760
|
+
)
|
|
2761
|
+
] });
|
|
2762
|
+
this.body.appendChild(typeGroup);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
renderFooter() {
|
|
2766
|
+
this.footer.innerHTML = "";
|
|
2767
|
+
this.footer.appendChild(
|
|
2768
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "g3p-modal-buttons", children: [
|
|
2769
|
+
this.deleteCallback && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2770
|
+
"button",
|
|
2771
|
+
{
|
|
2772
|
+
className: "g3p-modal-btn g3p-modal-btn-danger",
|
|
2773
|
+
onClick: () => this.delete(),
|
|
2774
|
+
children: "Delete"
|
|
2775
|
+
}
|
|
2776
|
+
),
|
|
2777
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "g3p-modal-spacer" }),
|
|
2778
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2779
|
+
"button",
|
|
2780
|
+
{
|
|
2781
|
+
className: "g3p-modal-btn g3p-modal-btn-secondary",
|
|
2782
|
+
onClick: () => this.close(),
|
|
2783
|
+
children: "Cancel"
|
|
2784
|
+
}
|
|
2785
|
+
),
|
|
2786
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2787
|
+
"button",
|
|
2788
|
+
{
|
|
2789
|
+
className: "g3p-modal-btn g3p-modal-btn-primary",
|
|
2790
|
+
onClick: () => this.submit(),
|
|
2791
|
+
children: "Save"
|
|
2792
|
+
}
|
|
2793
|
+
)
|
|
2794
|
+
] })
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
submit() {
|
|
2798
|
+
const data = {
|
|
2799
|
+
...this.edge,
|
|
2800
|
+
source: {
|
|
2801
|
+
...this.edge.source,
|
|
2802
|
+
marker: this.sourceMarkerSelect.value === "none" ? void 0 : this.sourceMarkerSelect.value
|
|
2803
|
+
},
|
|
2804
|
+
target: {
|
|
2805
|
+
...this.edge.target,
|
|
2806
|
+
marker: this.targetMarkerSelect.value === "none" ? void 0 : this.targetMarkerSelect.value
|
|
2807
|
+
}
|
|
2808
|
+
};
|
|
2809
|
+
if (this.typeSelect) {
|
|
2810
|
+
data.type = this.typeSelect.value || void 0;
|
|
2811
|
+
}
|
|
2812
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2813
|
+
this.container.remove();
|
|
2814
|
+
this.submitCallback(data);
|
|
2815
|
+
}
|
|
2816
|
+
delete() {
|
|
2817
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
2818
|
+
this.container.remove();
|
|
2819
|
+
this.deleteCallback?.();
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
|
|
2823
|
+
// src/canvas/canvas.tsx
|
|
2824
|
+
var import_styles = __toESM(require("./styles.css?raw"), 1);
|
|
2825
|
+
var import_jsx_runtime6 = require("jsx-dom/jsx-runtime");
|
|
2826
|
+
var log10 = logger("canvas");
|
|
2827
|
+
var Canvas = class {
|
|
2828
|
+
container;
|
|
2829
|
+
root;
|
|
2830
|
+
group;
|
|
2831
|
+
transform;
|
|
2832
|
+
bounds;
|
|
2833
|
+
measurement;
|
|
2834
|
+
allNodes;
|
|
2835
|
+
curNodes;
|
|
2836
|
+
curSegs;
|
|
2837
|
+
updating;
|
|
2838
|
+
// Pan-zoom state
|
|
2839
|
+
panScale = null;
|
|
2840
|
+
zoomControls;
|
|
2841
|
+
// Edit mode state machine
|
|
2842
|
+
editMode;
|
|
2843
|
+
api;
|
|
2844
|
+
// New-edge visual element
|
|
2845
|
+
newEdgeEl;
|
|
2846
|
+
// Pending drag state (for double-click debounce)
|
|
2847
|
+
pendingDrag = null;
|
|
2848
|
+
constructor(api, options) {
|
|
2849
|
+
Object.assign(this, options);
|
|
2850
|
+
this.api = api;
|
|
2851
|
+
this.allNodes = /* @__PURE__ */ new Map();
|
|
2852
|
+
this.curNodes = /* @__PURE__ */ new Map();
|
|
2853
|
+
this.curSegs = /* @__PURE__ */ new Map();
|
|
2854
|
+
this.updating = false;
|
|
2855
|
+
this.bounds = { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } };
|
|
2856
|
+
this.transform = { x: 0, y: 0, scale: 1 };
|
|
2857
|
+
this.editMode = new EditMode();
|
|
2858
|
+
this.editMode.editable = this.editable;
|
|
2859
|
+
this.createMeasurementContainer();
|
|
2860
|
+
this.createCanvasContainer();
|
|
2861
|
+
if (this.panZoom) this.setupPanZoom();
|
|
2862
|
+
}
|
|
2863
|
+
createMeasurementContainer() {
|
|
2864
|
+
this.measurement = document.createElement("div");
|
|
2865
|
+
this.measurement.style.cssText = `
|
|
2866
|
+
position: absolute;
|
|
2867
|
+
left: -9999px;
|
|
2868
|
+
top: -9999px;
|
|
2869
|
+
visibility: hidden;
|
|
2870
|
+
pointer-events: none;
|
|
2871
|
+
`;
|
|
2872
|
+
document.body.appendChild(this.measurement);
|
|
2873
|
+
}
|
|
2874
|
+
getNode(key) {
|
|
2875
|
+
const node = this.allNodes.get(key);
|
|
2876
|
+
if (!node) throw new Error(`node not found: ${key}`);
|
|
2877
|
+
return node;
|
|
2878
|
+
}
|
|
2879
|
+
update() {
|
|
2880
|
+
let bx0 = Infinity, by0 = Infinity;
|
|
2881
|
+
let bx1 = -Infinity, by1 = -Infinity;
|
|
2882
|
+
for (const node of this.curNodes.values()) {
|
|
2883
|
+
const { x, y } = node.pos;
|
|
2884
|
+
const { w, h } = node.data.dims;
|
|
2885
|
+
const nx0 = x, nx1 = x + w;
|
|
2886
|
+
const ny0 = y, ny1 = y + h;
|
|
2887
|
+
bx0 = Math.min(bx0, nx0);
|
|
2888
|
+
by0 = Math.min(by0, ny0);
|
|
2889
|
+
bx1 = Math.max(bx1, nx1);
|
|
2890
|
+
by1 = Math.max(by1, ny1);
|
|
2891
|
+
}
|
|
2892
|
+
this.bounds = { min: { x: bx0, y: by0 }, max: { x: bx1, y: by1 } };
|
|
2893
|
+
this.root.setAttribute("viewBox", this.viewBox());
|
|
2894
|
+
}
|
|
2895
|
+
addNode(gnode) {
|
|
2896
|
+
if (this.curNodes.has(gnode.id))
|
|
2897
|
+
throw new Error("node already exists");
|
|
2898
|
+
const { key } = gnode;
|
|
2899
|
+
let node;
|
|
2900
|
+
if (gnode.isDummy) {
|
|
2901
|
+
node = new Node2(this, gnode, true);
|
|
2902
|
+
node.renderContainer();
|
|
2903
|
+
this.allNodes.set(key, node);
|
|
2904
|
+
} else {
|
|
2905
|
+
if (!this.allNodes.has(key))
|
|
2906
|
+
throw new Error("node has not been measured");
|
|
2907
|
+
node = this.getNode(key);
|
|
2908
|
+
}
|
|
2909
|
+
this.curNodes.set(gnode.id, node);
|
|
2910
|
+
node.append();
|
|
2911
|
+
node.setPos(gnode.pos);
|
|
2912
|
+
}
|
|
2913
|
+
updateNode(gnode) {
|
|
2914
|
+
if (gnode.isDummy) throw new Error("dummy node cannot be updated");
|
|
2915
|
+
const node = this.getNode(gnode.key);
|
|
2916
|
+
const cur = this.curNodes.get(gnode.id);
|
|
2917
|
+
if (cur) cur.remove();
|
|
2918
|
+
this.curNodes.set(gnode.id, node);
|
|
2919
|
+
node.append();
|
|
2920
|
+
}
|
|
2921
|
+
deleteNode(gnode) {
|
|
2922
|
+
const node = this.getNode(gnode.key);
|
|
2923
|
+
this.curNodes.delete(gnode.id);
|
|
2924
|
+
node.remove();
|
|
2925
|
+
}
|
|
2926
|
+
addSeg(gseg, g) {
|
|
2927
|
+
if (this.curSegs.has(gseg.id))
|
|
2928
|
+
throw new Error("seg already exists");
|
|
2929
|
+
const seg = new Seg2(this, gseg, g);
|
|
2930
|
+
this.curSegs.set(gseg.id, seg);
|
|
2931
|
+
seg.append();
|
|
2932
|
+
}
|
|
2933
|
+
updateSeg(gseg, g) {
|
|
2934
|
+
const seg = this.curSegs.get(gseg.id);
|
|
2935
|
+
if (!seg) throw new Error("seg not found");
|
|
2936
|
+
seg.update(gseg, g);
|
|
2937
|
+
}
|
|
2938
|
+
deleteSeg(gseg) {
|
|
2939
|
+
const seg = this.curSegs.get(gseg.id);
|
|
2940
|
+
if (!seg) throw new Error("seg not found");
|
|
2941
|
+
this.curSegs.delete(gseg.id);
|
|
2942
|
+
seg.remove();
|
|
2943
|
+
}
|
|
2944
|
+
async measureNodes(nodes) {
|
|
2945
|
+
const newNodes = /* @__PURE__ */ new Map();
|
|
2946
|
+
for (const data of nodes) {
|
|
2947
|
+
const node = new Node2(this, data);
|
|
2948
|
+
newNodes.set(data.data, node);
|
|
2949
|
+
this.measurement.appendChild(node.content);
|
|
2950
|
+
}
|
|
2951
|
+
await new Promise(requestAnimationFrame);
|
|
2952
|
+
const isVertical = this.orientation === "TB" || this.orientation === "BT";
|
|
2953
|
+
for (const node of newNodes.values()) {
|
|
2954
|
+
node.measure(isVertical);
|
|
2955
|
+
const { id, version } = node.data;
|
|
2956
|
+
const key = `k:${id}:${version}`;
|
|
2957
|
+
this.allNodes.set(key, node);
|
|
2958
|
+
node.renderContainer();
|
|
2959
|
+
}
|
|
2960
|
+
this.measurement.innerHTML = "";
|
|
2961
|
+
return newNodes;
|
|
2962
|
+
}
|
|
2963
|
+
// ========== Mouse event handlers ==========
|
|
2964
|
+
onClick(e) {
|
|
2965
|
+
const hit = this.hitTest(e.clientX, e.clientY);
|
|
2966
|
+
if (hit.type === "node") {
|
|
2967
|
+
this.api.handleClickNode(hit.node.data.id);
|
|
2968
|
+
} else if (hit.type === "edge") {
|
|
2969
|
+
this.api.handleClickEdge(hit.segId);
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
onDoubleClick(e) {
|
|
2973
|
+
if (this.pendingDrag) {
|
|
2974
|
+
window.clearTimeout(this.pendingDrag.timeout);
|
|
2975
|
+
this.pendingDrag = null;
|
|
2976
|
+
}
|
|
2977
|
+
if (!this.editMode.editable) return;
|
|
2978
|
+
const hit = this.hitTest(e.clientX, e.clientY);
|
|
2979
|
+
if (hit.type === "node") {
|
|
2980
|
+
if (hit.node.isDummy) return;
|
|
2981
|
+
this.api.handleEditNode(hit.node.data.id);
|
|
2982
|
+
} else if (hit.type === "edge") {
|
|
2983
|
+
this.api.handleEditEdge(hit.segId);
|
|
2984
|
+
} else {
|
|
2985
|
+
this.api.handleNewNode();
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
// ========== Built-in Modals ==========
|
|
2989
|
+
/** Show the new node modal */
|
|
2990
|
+
showNewNodeModal(callback) {
|
|
2991
|
+
const nodeTypes = Object.keys(this.nodeTypes);
|
|
2992
|
+
const fields = this.api.getNodeFields();
|
|
2993
|
+
const modal = new NewNodeModal({
|
|
2994
|
+
nodeTypes: nodeTypes.length > 0 ? nodeTypes : void 0,
|
|
2995
|
+
fields: fields.size > 0 ? fields : void 0,
|
|
2996
|
+
onSubmit: (data) => {
|
|
2997
|
+
callback(data);
|
|
2998
|
+
}
|
|
2999
|
+
});
|
|
3000
|
+
modal.show(document.body);
|
|
3001
|
+
}
|
|
3002
|
+
/** Show the edit node modal */
|
|
3003
|
+
showEditNodeModal(node, callback) {
|
|
3004
|
+
const nodeTypes = Object.keys(this.nodeTypes);
|
|
3005
|
+
const fields = this.api.getNodeFields();
|
|
3006
|
+
const modal = new EditNodeModal({
|
|
3007
|
+
node,
|
|
3008
|
+
nodeTypes: nodeTypes.length > 0 ? nodeTypes : void 0,
|
|
3009
|
+
fields: fields.size > 0 ? fields : void 0,
|
|
3010
|
+
onSubmit: (data) => {
|
|
3011
|
+
callback(data);
|
|
3012
|
+
},
|
|
3013
|
+
onDelete: () => {
|
|
3014
|
+
this.api.handleDeleteNode(node.id);
|
|
3015
|
+
}
|
|
3016
|
+
});
|
|
3017
|
+
modal.show(document.body);
|
|
3018
|
+
}
|
|
3019
|
+
/** Show the edit edge modal */
|
|
3020
|
+
showEditEdgeModal(edge, callback) {
|
|
3021
|
+
const modal = new EditEdgeModal({
|
|
3022
|
+
edge,
|
|
3023
|
+
edgeTypes: Object.keys(this.edgeTypes),
|
|
3024
|
+
onSubmit: callback,
|
|
3025
|
+
onDelete: () => {
|
|
3026
|
+
this.api.handleDeleteEdge(edge.id);
|
|
3027
|
+
}
|
|
3028
|
+
});
|
|
3029
|
+
modal.show(document.body);
|
|
3030
|
+
}
|
|
3031
|
+
onContextMenu(e) {
|
|
3032
|
+
}
|
|
3033
|
+
groupTransform() {
|
|
3034
|
+
return `translate(${this.transform.x}, ${this.transform.y}) scale(${this.transform.scale})`;
|
|
3035
|
+
}
|
|
3036
|
+
viewBox() {
|
|
3037
|
+
const p = this.padding;
|
|
3038
|
+
const x = this.bounds.min.x - p;
|
|
3039
|
+
const y = this.bounds.min.y - p;
|
|
3040
|
+
const w = this.bounds.max.x - this.bounds.min.x + p * 2;
|
|
3041
|
+
const h = this.bounds.max.y - this.bounds.min.y + p * 2;
|
|
3042
|
+
return `${x} ${y} ${w} ${h}`;
|
|
3043
|
+
}
|
|
3044
|
+
generateDynamicStyles() {
|
|
3045
|
+
let css = "";
|
|
3046
|
+
css += themeToCSS(this.theme, `.g3p-canvas-container`);
|
|
3047
|
+
for (const [type, vars] of Object.entries(this.nodeTypes)) {
|
|
3048
|
+
css += themeToCSS(vars, `.g3p-node-type-${type}`, "node");
|
|
3049
|
+
}
|
|
3050
|
+
for (const [type, vars] of Object.entries(this.edgeTypes)) {
|
|
3051
|
+
css += themeToCSS(vars, `.g3p-edge-type-${type}`);
|
|
3052
|
+
}
|
|
3053
|
+
return css;
|
|
3054
|
+
}
|
|
3055
|
+
createCanvasContainer() {
|
|
3056
|
+
if (!document.getElementById("g3p-styles")) {
|
|
3057
|
+
const baseStyleEl = document.createElement("style");
|
|
3058
|
+
baseStyleEl.id = "g3p-styles";
|
|
3059
|
+
baseStyleEl.textContent = import_styles.default;
|
|
3060
|
+
document.head.appendChild(baseStyleEl);
|
|
3061
|
+
}
|
|
3062
|
+
const dynamicStyles = this.generateDynamicStyles();
|
|
3063
|
+
if (dynamicStyles) {
|
|
3064
|
+
const dynamicStyleEl = document.createElement("style");
|
|
3065
|
+
dynamicStyleEl.textContent = dynamicStyles;
|
|
3066
|
+
document.head.appendChild(dynamicStyleEl);
|
|
3067
|
+
}
|
|
3068
|
+
const colorModeClass = this.colorMode !== "system" ? `g3p-${this.colorMode}` : "";
|
|
3069
|
+
this.container = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2185
3070
|
"div",
|
|
2186
3071
|
{
|
|
2187
|
-
className:
|
|
3072
|
+
className: `g3p-canvas-container ${colorModeClass}`.trim(),
|
|
2188
3073
|
ref: (el) => this.container = el,
|
|
2189
3074
|
onContextMenu: this.onContextMenu.bind(this),
|
|
2190
|
-
children: /* @__PURE__ */ (0,
|
|
3075
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2191
3076
|
"svg",
|
|
2192
3077
|
{
|
|
2193
3078
|
ref: (el) => this.root = el,
|
|
2194
|
-
className:
|
|
3079
|
+
className: "g3p-canvas-root",
|
|
2195
3080
|
width: this.width,
|
|
2196
3081
|
height: this.height,
|
|
2197
3082
|
viewBox: this.viewBox(),
|
|
2198
3083
|
preserveAspectRatio: "xMidYMid meet",
|
|
2199
3084
|
onClick: this.onClick.bind(this),
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
{
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
}
|
|
2206
|
-
|
|
3085
|
+
onDblClick: this.onDoubleClick.bind(this),
|
|
3086
|
+
children: [
|
|
3087
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("defs", { children: [
|
|
3088
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, false)),
|
|
3089
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, true))
|
|
3090
|
+
] }),
|
|
3091
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3092
|
+
"g",
|
|
3093
|
+
{
|
|
3094
|
+
ref: (el) => this.group = el,
|
|
3095
|
+
transform: this.groupTransform()
|
|
3096
|
+
}
|
|
3097
|
+
)
|
|
3098
|
+
]
|
|
2207
3099
|
}
|
|
2208
3100
|
)
|
|
2209
3101
|
}
|
|
2210
3102
|
);
|
|
2211
3103
|
}
|
|
3104
|
+
// ==================== Pan-Zoom ====================
|
|
3105
|
+
setupPanZoom() {
|
|
3106
|
+
this.container.addEventListener("wheel", this.onWheel.bind(this), { passive: false });
|
|
3107
|
+
this.container.addEventListener("mousedown", this.onMouseDown.bind(this));
|
|
3108
|
+
document.addEventListener("mousemove", this.onMouseMove.bind(this));
|
|
3109
|
+
document.addEventListener("mouseup", this.onMouseUp.bind(this));
|
|
3110
|
+
document.addEventListener("keydown", this.onKeyDown.bind(this));
|
|
3111
|
+
this.createZoomControls();
|
|
3112
|
+
}
|
|
3113
|
+
onKeyDown(e) {
|
|
3114
|
+
if (e.key === "Escape" && this.editMode.isCreatingEdge) {
|
|
3115
|
+
this.endNewEdge(true);
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
/** Convert screen coordinates to canvas-relative coordinates */
|
|
3119
|
+
screenToCanvas(screen) {
|
|
3120
|
+
const rect = this.container.getBoundingClientRect();
|
|
3121
|
+
return canvasPos(screen.x - rect.left, screen.y - rect.top);
|
|
3122
|
+
}
|
|
3123
|
+
/** Convert canvas coordinates to graph coordinates */
|
|
3124
|
+
canvasToGraph(canvas) {
|
|
3125
|
+
const vb = this.currentViewBox();
|
|
3126
|
+
const { scale, offsetX, offsetY } = this.getEffectiveScale();
|
|
3127
|
+
return graphPos(
|
|
3128
|
+
vb.x - offsetX + canvas.x * scale,
|
|
3129
|
+
vb.y - offsetY + canvas.y * scale
|
|
3130
|
+
);
|
|
3131
|
+
}
|
|
3132
|
+
/** Convert screen coordinates to graph coordinates */
|
|
3133
|
+
screenToGraph(screen) {
|
|
3134
|
+
const canvas = this.screenToCanvas(screen);
|
|
3135
|
+
return this.canvasToGraph(canvas);
|
|
3136
|
+
}
|
|
3137
|
+
/**
|
|
3138
|
+
* Get the effective scale from canvas pixels to graph units,
|
|
3139
|
+
* accounting for preserveAspectRatio="xMidYMid meet" which uses
|
|
3140
|
+
* the smaller scale (to fit) and centers the content.
|
|
3141
|
+
*/
|
|
3142
|
+
getEffectiveScale() {
|
|
3143
|
+
const vb = this.currentViewBox();
|
|
3144
|
+
const rect = this.container.getBoundingClientRect();
|
|
3145
|
+
const scaleX = vb.w / rect.width;
|
|
3146
|
+
const scaleY = vb.h / rect.height;
|
|
3147
|
+
const scale = Math.max(scaleX, scaleY);
|
|
3148
|
+
const actualW = rect.width * scale;
|
|
3149
|
+
const actualH = rect.height * scale;
|
|
3150
|
+
const offsetX = (actualW - vb.w) / 2;
|
|
3151
|
+
const offsetY = (actualH - vb.h) / 2;
|
|
3152
|
+
return { scale, offsetX, offsetY };
|
|
3153
|
+
}
|
|
3154
|
+
/** Get current viewBox as an object */
|
|
3155
|
+
currentViewBox() {
|
|
3156
|
+
const p = this.padding;
|
|
3157
|
+
const t = this.transform;
|
|
3158
|
+
const baseX = this.bounds.min.x - p;
|
|
3159
|
+
const baseY = this.bounds.min.y - p;
|
|
3160
|
+
const baseW = this.bounds.max.x - this.bounds.min.x + p * 2;
|
|
3161
|
+
const baseH = this.bounds.max.y - this.bounds.min.y + p * 2;
|
|
3162
|
+
const cx = baseX + baseW / 2;
|
|
3163
|
+
const cy = baseY + baseH / 2;
|
|
3164
|
+
const w = baseW / t.scale;
|
|
3165
|
+
const h = baseH / t.scale;
|
|
3166
|
+
const x = cx - w / 2 - t.x;
|
|
3167
|
+
const y = cy - h / 2 - t.y;
|
|
3168
|
+
return { x, y, w, h };
|
|
3169
|
+
}
|
|
3170
|
+
onWheel(e) {
|
|
3171
|
+
e.preventDefault();
|
|
3172
|
+
const zoomFactor = 1.1;
|
|
3173
|
+
const delta = e.deltaY > 0 ? 1 / zoomFactor : zoomFactor;
|
|
3174
|
+
const screenCursor = screenPos(e.clientX, e.clientY);
|
|
3175
|
+
const canvasCursor = this.screenToCanvas(screenCursor);
|
|
3176
|
+
const graphCursor = this.canvasToGraph(canvasCursor);
|
|
3177
|
+
const oldScale = this.transform.scale;
|
|
3178
|
+
const newScale = Math.max(0.1, Math.min(10, oldScale * delta));
|
|
3179
|
+
this.transform.scale = newScale;
|
|
3180
|
+
const newGraphCursor = this.canvasToGraph(canvasCursor);
|
|
3181
|
+
this.transform.x += newGraphCursor.x - graphCursor.x;
|
|
3182
|
+
this.transform.y += newGraphCursor.y - graphCursor.y;
|
|
3183
|
+
this.applyTransform();
|
|
3184
|
+
}
|
|
3185
|
+
onMouseDown(e) {
|
|
3186
|
+
if (e.button !== 0) return;
|
|
3187
|
+
if (e.target.closest(".g3p-zoom-controls")) return;
|
|
3188
|
+
const hit = this.hitTest(e.clientX, e.clientY);
|
|
3189
|
+
if (this.editMode.editable && (hit.type === "node" || hit.type === "port")) {
|
|
3190
|
+
const node = hit.node;
|
|
3191
|
+
if (node.isDummy) return;
|
|
3192
|
+
e.preventDefault();
|
|
3193
|
+
e.stopPropagation();
|
|
3194
|
+
const startGraph = this.screenToGraph(hit.center);
|
|
3195
|
+
const portId = hit.type === "port" ? hit.port : void 0;
|
|
3196
|
+
this.pendingDrag = {
|
|
3197
|
+
timeout: window.setTimeout(() => {
|
|
3198
|
+
if (this.pendingDrag) {
|
|
3199
|
+
this.startNewEdge(this.pendingDrag.nodeId, this.pendingDrag.startGraph, this.pendingDrag.portId);
|
|
3200
|
+
this.pendingDrag = null;
|
|
3201
|
+
}
|
|
3202
|
+
}, 200),
|
|
3203
|
+
nodeId: node.data.id,
|
|
3204
|
+
startGraph,
|
|
3205
|
+
portId
|
|
3206
|
+
};
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
if (hit.type === "canvas" || hit.type === "edge") {
|
|
3210
|
+
const startCanvas = this.screenToCanvas(screenPos(e.clientX, e.clientY));
|
|
3211
|
+
this.editMode.startPan(startCanvas, { ...this.transform });
|
|
3212
|
+
const { scale } = this.getEffectiveScale();
|
|
3213
|
+
this.panScale = { x: scale, y: scale };
|
|
3214
|
+
this.container.style.cursor = "grabbing";
|
|
3215
|
+
e.preventDefault();
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
onMouseMove(e) {
|
|
3219
|
+
if (this.editMode.isCreatingEdge) {
|
|
3220
|
+
const screenCursor = screenPos(e.clientX, e.clientY);
|
|
3221
|
+
const canvasCursor = this.screenToCanvas(screenCursor);
|
|
3222
|
+
const graphCursor = this.canvasToGraph(canvasCursor);
|
|
3223
|
+
this.editMode.updateNewEdgePosition(graphCursor);
|
|
3224
|
+
this.updateNewEdgeVisual();
|
|
3225
|
+
this.detectHoverTarget(e.clientX, e.clientY);
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
3228
|
+
if (!this.editMode.isPanning || !this.panScale) return;
|
|
3229
|
+
const panState = this.editMode.state;
|
|
3230
|
+
if (panState.type !== "panning") return;
|
|
3231
|
+
const current = this.screenToCanvas(screenPos(e.clientX, e.clientY));
|
|
3232
|
+
const dx = current.x - panState.startCanvas.x;
|
|
3233
|
+
const dy = current.y - panState.startCanvas.y;
|
|
3234
|
+
this.transform.x = panState.startTransform.x + dx * this.panScale.x;
|
|
3235
|
+
this.transform.y = panState.startTransform.y + dy * this.panScale.y;
|
|
3236
|
+
this.applyTransform();
|
|
3237
|
+
}
|
|
3238
|
+
onMouseUp(e) {
|
|
3239
|
+
if (this.pendingDrag) {
|
|
3240
|
+
window.clearTimeout(this.pendingDrag.timeout);
|
|
3241
|
+
this.pendingDrag = null;
|
|
3242
|
+
}
|
|
3243
|
+
if (this.editMode.isCreatingEdge) {
|
|
3244
|
+
this.endNewEdge(false);
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
if (!this.editMode.isPanning) return;
|
|
3248
|
+
this.editMode.reset();
|
|
3249
|
+
this.panScale = null;
|
|
3250
|
+
this.container.style.cursor = "";
|
|
3251
|
+
}
|
|
3252
|
+
applyTransform() {
|
|
3253
|
+
const vb = this.currentViewBox();
|
|
3254
|
+
this.root.setAttribute("viewBox", `${vb.x} ${vb.y} ${vb.w} ${vb.h}`);
|
|
3255
|
+
this.updateZoomLevel();
|
|
3256
|
+
}
|
|
3257
|
+
createZoomControls() {
|
|
3258
|
+
this.zoomControls = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "g3p-zoom-controls", children: [
|
|
3259
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "g3p-zoom-btn", onClick: () => this.zoomIn(), children: "+" }),
|
|
3260
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "g3p-zoom-level", id: "g3p-zoom-level", children: "100%" }),
|
|
3261
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "g3p-zoom-btn", onClick: () => this.zoomOut(), children: "\u2212" }),
|
|
3262
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "g3p-zoom-btn g3p-zoom-reset", onClick: () => this.zoomReset(), children: "\u27F2" })
|
|
3263
|
+
] });
|
|
3264
|
+
this.container.appendChild(this.zoomControls);
|
|
3265
|
+
}
|
|
3266
|
+
updateZoomLevel() {
|
|
3267
|
+
const level = this.container.querySelector("#g3p-zoom-level");
|
|
3268
|
+
if (level) {
|
|
3269
|
+
level.textContent = `${Math.round(this.transform.scale * 100)}%`;
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
zoomIn() {
|
|
3273
|
+
this.transform.scale = Math.min(10, this.transform.scale * 1.2);
|
|
3274
|
+
this.applyTransform();
|
|
3275
|
+
}
|
|
3276
|
+
zoomOut() {
|
|
3277
|
+
this.transform.scale = Math.max(0.1, this.transform.scale / 1.2);
|
|
3278
|
+
this.applyTransform();
|
|
3279
|
+
}
|
|
3280
|
+
zoomReset() {
|
|
3281
|
+
this.transform = { x: 0, y: 0, scale: 1 };
|
|
3282
|
+
this.applyTransform();
|
|
3283
|
+
}
|
|
3284
|
+
// ==================== New-Edge Mode ====================
|
|
3285
|
+
/** Start creating a new edge from a node */
|
|
3286
|
+
startNewEdge(sourceNodeId, startGraph, sourcePortId) {
|
|
3287
|
+
this.editMode.startNewEdge(sourceNodeId, startGraph, sourcePortId);
|
|
3288
|
+
this.updateNewEdgeVisual();
|
|
3289
|
+
this.container.style.cursor = "crosshair";
|
|
3290
|
+
}
|
|
3291
|
+
/** Update the new-edge visual during drag */
|
|
3292
|
+
updateNewEdgeVisual() {
|
|
3293
|
+
const state = this.editMode.getNewEdgeState();
|
|
3294
|
+
if (!state) {
|
|
3295
|
+
this.removeNewEdgeVisual();
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
if (this.newEdgeEl) {
|
|
3299
|
+
this.newEdgeEl.remove();
|
|
3300
|
+
}
|
|
3301
|
+
this.newEdgeEl = renderNewEdge({
|
|
3302
|
+
start: state.startGraph,
|
|
3303
|
+
end: state.currentGraph
|
|
3304
|
+
});
|
|
3305
|
+
this.group.appendChild(this.newEdgeEl);
|
|
3306
|
+
}
|
|
3307
|
+
/** Remove the new-edge visual */
|
|
3308
|
+
removeNewEdgeVisual() {
|
|
3309
|
+
if (this.newEdgeEl) {
|
|
3310
|
+
this.newEdgeEl.remove();
|
|
3311
|
+
this.newEdgeEl = void 0;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
/** Complete or cancel the new-edge creation */
|
|
3315
|
+
endNewEdge(cancelled = false) {
|
|
3316
|
+
const state = this.editMode.getNewEdgeState();
|
|
3317
|
+
if (!state) return;
|
|
3318
|
+
if (!cancelled) {
|
|
3319
|
+
const { target, source } = state;
|
|
3320
|
+
if (target?.type == "node") {
|
|
3321
|
+
this.api.handleAddEdge({ id: "", source, target });
|
|
3322
|
+
} else {
|
|
3323
|
+
this.api.handleNewNodeFrom(source);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
this.removeNewEdgeVisual();
|
|
3327
|
+
this.clearDropTargetHighlight();
|
|
3328
|
+
this.editMode.reset();
|
|
3329
|
+
this.container.style.cursor = "";
|
|
3330
|
+
}
|
|
3331
|
+
/** Find node data by internal ID */
|
|
3332
|
+
findNodeDataById(nodeId) {
|
|
3333
|
+
for (const node of this.curNodes.values()) {
|
|
3334
|
+
if (node.data?.id === nodeId) {
|
|
3335
|
+
return node.data.data;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
return null;
|
|
3339
|
+
}
|
|
3340
|
+
/** Set hover target for new-edge mode */
|
|
3341
|
+
setNewEdgeHoverTarget(id, port) {
|
|
3342
|
+
this.clearDropTargetHighlight();
|
|
3343
|
+
this.editMode.setHoverTarget({ type: "node", id, port });
|
|
3344
|
+
if (port) {
|
|
3345
|
+
const portEl = this.container?.querySelector(`.g3p-node-port[data-node-id="${id}"][data-port-id="${port}"]`);
|
|
3346
|
+
if (portEl) portEl.classList.add("g3p-drop-target");
|
|
3347
|
+
} else {
|
|
3348
|
+
const node = this.curNodes.get(id);
|
|
3349
|
+
if (node?.container) node.container.classList.add("g3p-drop-target");
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
/** Clear hover target for new-edge mode */
|
|
3353
|
+
clearNewEdgeHoverTarget() {
|
|
3354
|
+
this.clearDropTargetHighlight();
|
|
3355
|
+
this.editMode.setHoverTarget(null);
|
|
3356
|
+
}
|
|
3357
|
+
/** Remove drop target highlight from all elements */
|
|
3358
|
+
clearDropTargetHighlight() {
|
|
3359
|
+
for (const node of this.curNodes.values()) {
|
|
3360
|
+
node.container?.classList.remove("g3p-drop-target");
|
|
3361
|
+
}
|
|
3362
|
+
this.container?.querySelectorAll(".g3p-drop-target").forEach((el) => {
|
|
3363
|
+
el.classList.remove("g3p-drop-target");
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
/** Detect hover target during new-edge drag using elementFromPoint */
|
|
3367
|
+
detectHoverTarget(clientX, clientY) {
|
|
3368
|
+
if (this.newEdgeEl) {
|
|
3369
|
+
this.newEdgeEl.style.display = "none";
|
|
3370
|
+
}
|
|
3371
|
+
const el = document.elementFromPoint(clientX, clientY);
|
|
3372
|
+
if (this.newEdgeEl) {
|
|
3373
|
+
this.newEdgeEl.style.display = "";
|
|
3374
|
+
}
|
|
3375
|
+
if (!el) {
|
|
3376
|
+
this.clearNewEdgeHoverTarget();
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
const portEl = el.closest(".g3p-node-port");
|
|
3380
|
+
if (portEl) {
|
|
3381
|
+
const nodeId = portEl.getAttribute("data-node-id");
|
|
3382
|
+
const portId = portEl.getAttribute("data-port-id");
|
|
3383
|
+
if (nodeId && portId) {
|
|
3384
|
+
const node = this.curNodes.get(nodeId);
|
|
3385
|
+
if (node && !node.isDummy) {
|
|
3386
|
+
this.setNewEdgeHoverTarget(nodeId, portId);
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
const nodeEl = el.closest(".g3p-node-container");
|
|
3392
|
+
if (nodeEl) {
|
|
3393
|
+
const nodeId = nodeEl.getAttribute("data-node-id");
|
|
3394
|
+
if (nodeId) {
|
|
3395
|
+
const node = this.curNodes.get(nodeId);
|
|
3396
|
+
if (node && !node.isDummy) {
|
|
3397
|
+
this.setNewEdgeHoverTarget(node.data.id);
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
this.clearNewEdgeHoverTarget();
|
|
3403
|
+
}
|
|
3404
|
+
// ==================== Hit Testing ====================
|
|
3405
|
+
/** Result of a hit test */
|
|
3406
|
+
hitTest(clientX, clientY) {
|
|
3407
|
+
const el = document.elementFromPoint(clientX, clientY);
|
|
3408
|
+
if (!el) return { type: "canvas" };
|
|
3409
|
+
const getCenter = (el2) => {
|
|
3410
|
+
const rect = el2.getBoundingClientRect();
|
|
3411
|
+
return screenPos(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
3412
|
+
};
|
|
3413
|
+
const portEl = el.closest(".g3p-node-port");
|
|
3414
|
+
if (portEl) {
|
|
3415
|
+
const nodeId = portEl.getAttribute("data-node-id");
|
|
3416
|
+
const portId = portEl.getAttribute("data-port-id");
|
|
3417
|
+
if (nodeId && portId) {
|
|
3418
|
+
const center = getCenter(portEl);
|
|
3419
|
+
const node = this.curNodes.get(nodeId);
|
|
3420
|
+
if (node) {
|
|
3421
|
+
return { type: "port", node, port: portId, center };
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
const nodeEl = el.closest(".g3p-node-container");
|
|
3426
|
+
if (nodeEl) {
|
|
3427
|
+
const nodeId = nodeEl.getAttribute("data-node-id");
|
|
3428
|
+
if (nodeId) {
|
|
3429
|
+
const borderEl = el.closest(".g3p-node-border");
|
|
3430
|
+
const center = getCenter(borderEl ?? nodeEl);
|
|
3431
|
+
const node = this.curNodes.get(nodeId);
|
|
3432
|
+
if (node) {
|
|
3433
|
+
return { type: "node", node, center };
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
const edgeEl = el.closest(".g3p-seg-container");
|
|
3438
|
+
if (edgeEl) {
|
|
3439
|
+
const segId = edgeEl.getAttribute("data-edge-id");
|
|
3440
|
+
if (segId) {
|
|
3441
|
+
return { type: "edge", segId };
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
return { type: "canvas" };
|
|
3445
|
+
}
|
|
2212
3446
|
};
|
|
3447
|
+
var themeVarMap = {
|
|
3448
|
+
// Canvas
|
|
3449
|
+
bg: "--g3p-bg",
|
|
3450
|
+
shadow: "--g3p-shadow",
|
|
3451
|
+
// Node
|
|
3452
|
+
border: "--g3p-border",
|
|
3453
|
+
borderHover: "--g3p-border-hover",
|
|
3454
|
+
borderSelected: "--g3p-border-selected",
|
|
3455
|
+
text: "--g3p-text",
|
|
3456
|
+
textMuted: "--g3p-text-muted",
|
|
3457
|
+
// Port
|
|
3458
|
+
bgHover: "--g3p-port-bg-hover",
|
|
3459
|
+
// Edge
|
|
3460
|
+
color: "--g3p-edge-color"
|
|
3461
|
+
};
|
|
3462
|
+
function themeToCSS(theme, selector, prefix = "") {
|
|
3463
|
+
const entries = Object.entries(theme).filter(([_, v]) => v !== void 0);
|
|
3464
|
+
if (!entries.length) return "";
|
|
3465
|
+
let css = `${selector} {
|
|
3466
|
+
`;
|
|
3467
|
+
for (const [key, value] of entries) {
|
|
3468
|
+
let cssVar = themeVarMap[key];
|
|
3469
|
+
if (key === "bg" && prefix === "node") {
|
|
3470
|
+
cssVar = "--g3p-bg-node";
|
|
3471
|
+
} else if (key === "bg" && prefix === "port") {
|
|
3472
|
+
cssVar = "--g3p-port-bg";
|
|
3473
|
+
}
|
|
3474
|
+
if (cssVar) {
|
|
3475
|
+
css += ` ${cssVar}: ${value};
|
|
3476
|
+
`;
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
css += "}\n";
|
|
3480
|
+
return css;
|
|
3481
|
+
}
|
|
2213
3482
|
|
|
2214
|
-
// src/
|
|
2215
|
-
var
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
});
|
|
3483
|
+
// src/canvas/render-node.tsx
|
|
3484
|
+
var import_jsx_runtime7 = require("jsx-dom/jsx-runtime");
|
|
3485
|
+
function renderNode(node, props) {
|
|
3486
|
+
if (typeof node == "string") node = { id: node };
|
|
3487
|
+
const title = node?.title ?? props?.title ?? node?.label ?? node?.name ?? node?.text ?? props?.text ?? node?.id ?? "?";
|
|
3488
|
+
const detail = node?.detail ?? node?.description ?? node?.subtitle;
|
|
3489
|
+
console.log(`renderNode: ${node.id} ${title} ${detail}`);
|
|
3490
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "g3p-node-default", children: [
|
|
3491
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "g3p-node-title", children: title }),
|
|
3492
|
+
detail && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "g3p-node-detail", children: detail })
|
|
3493
|
+
] });
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
// src/api/defaults.ts
|
|
3497
|
+
function applyDefaults(options) {
|
|
3498
|
+
const { graph: graph2, canvas, props } = defaults();
|
|
3499
|
+
return {
|
|
3500
|
+
graph: { ...graph2, ...options?.graph },
|
|
3501
|
+
canvas: { ...canvas, ...options?.canvas },
|
|
3502
|
+
props: { ...props, ...options?.props }
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
function defaults() {
|
|
3506
|
+
return {
|
|
3507
|
+
graph: {
|
|
3508
|
+
mergeOrder: ["target", "source"],
|
|
3509
|
+
nodeMargin: 15,
|
|
3510
|
+
dummyNodeSize: 15,
|
|
3511
|
+
nodeAlign: "natural",
|
|
3512
|
+
edgeSpacing: 10,
|
|
3513
|
+
turnRadius: 10,
|
|
3514
|
+
orientation: "TB",
|
|
3515
|
+
layerMargin: 5,
|
|
3516
|
+
alignIterations: 5,
|
|
3517
|
+
alignThreshold: 10,
|
|
3518
|
+
separateTrackSets: true,
|
|
3519
|
+
markerSize: 10,
|
|
3520
|
+
layoutSteps: null
|
|
3521
|
+
},
|
|
3522
|
+
canvas: {
|
|
3523
|
+
renderNode,
|
|
3524
|
+
width: "100%",
|
|
3525
|
+
height: "100%",
|
|
3526
|
+
padding: 20,
|
|
3527
|
+
editable: false,
|
|
3528
|
+
panZoom: true,
|
|
3529
|
+
markerSize: 10,
|
|
3530
|
+
colorMode: "system",
|
|
3531
|
+
theme: {},
|
|
3532
|
+
nodeTypes: {},
|
|
3533
|
+
edgeTypes: {}
|
|
3534
|
+
},
|
|
3535
|
+
props: {}
|
|
3536
|
+
};
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
// src/api/updater.ts
|
|
3540
|
+
var Updater = class _Updater {
|
|
3541
|
+
update;
|
|
3542
|
+
constructor() {
|
|
3543
|
+
this.update = {
|
|
3544
|
+
addNodes: [],
|
|
3545
|
+
removeNodes: [],
|
|
3546
|
+
updateNodes: [],
|
|
3547
|
+
addEdges: [],
|
|
3548
|
+
removeEdges: [],
|
|
3549
|
+
updateEdges: []
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3552
|
+
describe(desc) {
|
|
3553
|
+
this.update.description = desc;
|
|
3554
|
+
return this;
|
|
3555
|
+
}
|
|
3556
|
+
addNode(node) {
|
|
3557
|
+
this.update.addNodes.push(node);
|
|
3558
|
+
return this;
|
|
3559
|
+
}
|
|
3560
|
+
deleteNode(node) {
|
|
3561
|
+
this.update.removeNodes.push(node);
|
|
3562
|
+
return this;
|
|
3563
|
+
}
|
|
3564
|
+
updateNode(node) {
|
|
3565
|
+
this.update.updateNodes.push(node);
|
|
3566
|
+
return this;
|
|
3567
|
+
}
|
|
3568
|
+
addEdge(edge) {
|
|
3569
|
+
this.update.addEdges.push(edge);
|
|
3570
|
+
return this;
|
|
3571
|
+
}
|
|
3572
|
+
deleteEdge(edge) {
|
|
3573
|
+
this.update.removeEdges.push(edge);
|
|
3574
|
+
return this;
|
|
3575
|
+
}
|
|
3576
|
+
updateEdge(edge) {
|
|
3577
|
+
this.update.updateEdges.push(edge);
|
|
3578
|
+
return this;
|
|
3579
|
+
}
|
|
3580
|
+
static add(nodes, edges) {
|
|
3581
|
+
const updater = new _Updater();
|
|
3582
|
+
updater.update.addNodes = nodes;
|
|
3583
|
+
updater.update.addEdges = edges;
|
|
3584
|
+
return updater;
|
|
3585
|
+
}
|
|
3586
|
+
};
|
|
3587
|
+
|
|
3588
|
+
// src/api/api.ts
|
|
3589
|
+
var log11 = logger("api");
|
|
2230
3590
|
var API = class {
|
|
2231
3591
|
state;
|
|
2232
3592
|
seq;
|
|
2233
3593
|
index;
|
|
2234
3594
|
canvas;
|
|
2235
|
-
_options;
|
|
2236
3595
|
options;
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
this.
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
};
|
|
2255
|
-
let graph2 = new Graph({ options: this._options });
|
|
2256
|
-
this.state.graph = graph2;
|
|
3596
|
+
history;
|
|
3597
|
+
nodeIds;
|
|
3598
|
+
edgeIds;
|
|
3599
|
+
nodeVersions;
|
|
3600
|
+
nodeOverrides;
|
|
3601
|
+
edgeOverrides;
|
|
3602
|
+
nodeFields;
|
|
3603
|
+
nextNodeId;
|
|
3604
|
+
nextEdgeId;
|
|
3605
|
+
events;
|
|
3606
|
+
root;
|
|
3607
|
+
constructor(args) {
|
|
3608
|
+
this.root = args.root;
|
|
3609
|
+
this.options = applyDefaults(args.options);
|
|
3610
|
+
let graph2 = new Graph({ options: this.options.graph });
|
|
3611
|
+
this.state = { graph: graph2, update: null };
|
|
3612
|
+
this.events = args.events || {};
|
|
2257
3613
|
this.seq = [this.state];
|
|
2258
3614
|
this.index = 0;
|
|
2259
|
-
this.
|
|
2260
|
-
this.
|
|
3615
|
+
this.nodeIds = /* @__PURE__ */ new Map();
|
|
3616
|
+
this.edgeIds = /* @__PURE__ */ new Map();
|
|
3617
|
+
this.nodeVersions = /* @__PURE__ */ new Map();
|
|
3618
|
+
this.nodeOverrides = /* @__PURE__ */ new Map();
|
|
3619
|
+
this.edgeOverrides = /* @__PURE__ */ new Map();
|
|
3620
|
+
this.nodeFields = /* @__PURE__ */ new Map();
|
|
3621
|
+
this.nextNodeId = 1;
|
|
3622
|
+
this.nextEdgeId = 1;
|
|
3623
|
+
this.canvas = new Canvas(this, {
|
|
3624
|
+
...this.options.canvas,
|
|
3625
|
+
dummyNodeSize: this.options.graph.dummyNodeSize,
|
|
3626
|
+
orientation: this.options.graph.orientation
|
|
3627
|
+
});
|
|
3628
|
+
if (args.history) {
|
|
3629
|
+
this.history = args.history;
|
|
3630
|
+
} else if (args.nodes) {
|
|
3631
|
+
this.history = [Updater.add(args.nodes, args.edges || []).update];
|
|
3632
|
+
} else {
|
|
3633
|
+
this.history = [];
|
|
3634
|
+
}
|
|
2261
3635
|
}
|
|
2262
|
-
|
|
2263
|
-
|
|
3636
|
+
/** Current history index (0-based) */
|
|
3637
|
+
getHistoryIndex() {
|
|
3638
|
+
return this.index;
|
|
3639
|
+
}
|
|
3640
|
+
/** Current history length */
|
|
3641
|
+
getHistoryLength() {
|
|
3642
|
+
return this.seq.length;
|
|
3643
|
+
}
|
|
3644
|
+
/** Toggle canvas editable mode without re-creating the graph */
|
|
3645
|
+
setEditable(editable) {
|
|
3646
|
+
this.canvas.editMode.editable = editable;
|
|
2264
3647
|
}
|
|
3648
|
+
get graph() {
|
|
3649
|
+
return this.state.graph;
|
|
3650
|
+
}
|
|
3651
|
+
/** Initialize the API */
|
|
3652
|
+
async init() {
|
|
3653
|
+
const root = document.getElementById(this.root);
|
|
3654
|
+
if (!root) throw new Error("root element not found");
|
|
3655
|
+
root.appendChild(this.canvas.container);
|
|
3656
|
+
for (const update of this.history)
|
|
3657
|
+
await this.applyUpdate(update);
|
|
3658
|
+
}
|
|
3659
|
+
/** Navigate to a different state */
|
|
2265
3660
|
nav(nav) {
|
|
2266
3661
|
let newIndex;
|
|
2267
3662
|
switch (nav) {
|
|
@@ -2283,238 +3678,409 @@ var API = class {
|
|
|
2283
3678
|
this.applyDiff(this.index, newIndex);
|
|
2284
3679
|
this.index = newIndex;
|
|
2285
3680
|
this.state = this.seq[this.index];
|
|
3681
|
+
if (this.events.historyChange)
|
|
3682
|
+
this.events.historyChange(this.index, this.seq.length);
|
|
2286
3683
|
}
|
|
2287
3684
|
applyDiff(oldIndex, newIndex) {
|
|
2288
|
-
const
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
|
-
if (!oldState.nodes.has(newNode.id))
|
|
2304
|
-
this.canvas.addNode(newNode);
|
|
3685
|
+
const oldGraph = this.seq[oldIndex].graph;
|
|
3686
|
+
const newGraph = this.seq[newIndex].graph;
|
|
3687
|
+
for (const oldNode of oldGraph.nodes.values()) {
|
|
3688
|
+
const newNode = newGraph.nodes.get(oldNode.id);
|
|
3689
|
+
if (!newNode) this.canvas.deleteNode(oldNode);
|
|
3690
|
+
}
|
|
3691
|
+
for (const newNode of newGraph.nodes.values()) {
|
|
3692
|
+
const oldNode = oldGraph.nodes.get(newNode.id);
|
|
3693
|
+
if (!oldNode) {
|
|
3694
|
+
this.canvas.addNode(newNode);
|
|
3695
|
+
} else if (oldNode.key !== newNode.key) {
|
|
3696
|
+
this.canvas.deleteNode(oldNode);
|
|
3697
|
+
this.canvas.addNode(newNode);
|
|
3698
|
+
} else if (oldNode.pos !== newNode.pos) {
|
|
3699
|
+
this.canvas.getNode(newNode.key).setPos(newNode.pos);
|
|
2305
3700
|
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
3701
|
+
}
|
|
3702
|
+
for (const oldSeg of oldGraph.segs.values()) {
|
|
3703
|
+
const newSeg = newGraph.segs.get(oldSeg.id);
|
|
3704
|
+
if (!newSeg) {
|
|
3705
|
+
this.canvas.deleteSeg(oldSeg);
|
|
3706
|
+
} else if (oldSeg !== newSeg) {
|
|
3707
|
+
this.canvas.updateSeg(newSeg, newGraph);
|
|
2312
3708
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
3709
|
+
}
|
|
3710
|
+
for (const newSeg of newGraph.segs.values()) {
|
|
3711
|
+
if (!oldGraph.segs.has(newSeg.id)) {
|
|
3712
|
+
this.canvas.addSeg(newSeg, newGraph);
|
|
2316
3713
|
}
|
|
2317
|
-
}
|
|
3714
|
+
}
|
|
3715
|
+
this.canvas.update();
|
|
2318
3716
|
}
|
|
3717
|
+
/** Add a node */
|
|
2319
3718
|
async addNode(node) {
|
|
2320
3719
|
await this.update((update) => update.addNode(node));
|
|
2321
3720
|
}
|
|
3721
|
+
/** Delete a node */
|
|
2322
3722
|
async deleteNode(node) {
|
|
2323
3723
|
await this.update((update) => update.deleteNode(node));
|
|
2324
3724
|
}
|
|
3725
|
+
/** Update a node */
|
|
2325
3726
|
async updateNode(node) {
|
|
2326
3727
|
await this.update((update) => update.updateNode(node));
|
|
2327
3728
|
}
|
|
3729
|
+
/** Add an edge */
|
|
2328
3730
|
async addEdge(edge) {
|
|
2329
3731
|
await this.update((update) => update.addEdge(edge));
|
|
2330
3732
|
}
|
|
3733
|
+
/** Delete an edge */
|
|
2331
3734
|
async deleteEdge(edge) {
|
|
2332
3735
|
await this.update((update) => update.deleteEdge(edge));
|
|
2333
3736
|
}
|
|
3737
|
+
/** Update an edge */
|
|
3738
|
+
async updateEdge(edge) {
|
|
3739
|
+
await this.update((update) => update.updateEdge(edge));
|
|
3740
|
+
}
|
|
3741
|
+
/** Perform a batch of updates */
|
|
2334
3742
|
async update(callback) {
|
|
2335
|
-
const
|
|
2336
|
-
callback(
|
|
2337
|
-
await this.
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
for (const node of
|
|
2346
|
-
|
|
2347
|
-
for (const edge of
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
3743
|
+
const updater = new Updater();
|
|
3744
|
+
callback(updater);
|
|
3745
|
+
await this.applyUpdate(updater.update);
|
|
3746
|
+
}
|
|
3747
|
+
/** Rebuild the graph from scratch (removes all then re-adds all nodes/edges) */
|
|
3748
|
+
async rebuild() {
|
|
3749
|
+
const nodes = [...this.nodeIds.keys()];
|
|
3750
|
+
const edges = [...this.edgeIds.keys()];
|
|
3751
|
+
await this.update((updater) => {
|
|
3752
|
+
for (const edge of edges) updater.deleteEdge(edge);
|
|
3753
|
+
for (const node of nodes) updater.deleteNode(node);
|
|
3754
|
+
for (const node of nodes) updater.addNode(node);
|
|
3755
|
+
for (const edge of edges) updater.addEdge(edge);
|
|
3756
|
+
});
|
|
3757
|
+
}
|
|
3758
|
+
async applyUpdate(update) {
|
|
3759
|
+
log11.info("applyUpdate", update);
|
|
3760
|
+
const nodes = await this.measureNodes(update);
|
|
3761
|
+
const graph2 = this.state.graph.withMutations((mut) => {
|
|
3762
|
+
for (const edge of update.removeEdges ?? [])
|
|
3763
|
+
this._removeEdge(edge, mut);
|
|
3764
|
+
for (const node of update.removeNodes ?? [])
|
|
2352
3765
|
this._removeNode(node, mut);
|
|
2353
|
-
for (const
|
|
3766
|
+
for (const node of update.addNodes ?? [])
|
|
3767
|
+
this._addNode(nodes.get(node), mut);
|
|
3768
|
+
for (const node of update.updateNodes ?? [])
|
|
3769
|
+
this._updateNode(nodes.get(node), mut);
|
|
3770
|
+
for (const edge of update.addEdges ?? [])
|
|
2354
3771
|
this._addEdge(edge, mut);
|
|
2355
|
-
for (const edge of update.
|
|
2356
|
-
this.
|
|
3772
|
+
for (const edge of update.updateEdges ?? [])
|
|
3773
|
+
this._updateEdge(edge, mut);
|
|
3774
|
+
this.nodeOverrides.clear();
|
|
3775
|
+
this.edgeOverrides.clear();
|
|
2357
3776
|
});
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
const node = newGraph.getNode(nodeId);
|
|
2361
|
-
console.log(`got pos of node ${nodeId}:`, node.pos);
|
|
2362
|
-
if (node.isDummy) {
|
|
2363
|
-
this.state.nodes.set(nodeId, { id: nodeId, pos: node.pos, isDummy: true });
|
|
2364
|
-
} else {
|
|
2365
|
-
const myNode = this.state.nodes.get(nodeId);
|
|
2366
|
-
this.state.nodes.set(nodeId, { ...myNode, pos: node.pos });
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
for (const nodeId of newGraph.delNodes)
|
|
2370
|
-
this.state.nodes.delete(nodeId);
|
|
2371
|
-
for (const segId of newGraph.delSegs)
|
|
2372
|
-
this.state.segs.delete(segId);
|
|
2373
|
-
for (const segId of newGraph.dirtySegs) {
|
|
2374
|
-
const seg = newGraph.getSeg(segId);
|
|
2375
|
-
const edge = this.state.edges.get(seg.edgeIds.values().next().value);
|
|
2376
|
-
const target = seg.targetNode(newGraph);
|
|
2377
|
-
this.state.segs.set(seg.id, {
|
|
2378
|
-
segId: seg.id,
|
|
2379
|
-
edgeId: edge.id,
|
|
2380
|
-
svg: seg.svg,
|
|
2381
|
-
attrs: edge.attrs,
|
|
2382
|
-
targetDummy: target.isDummy,
|
|
2383
|
-
edgeData: edge.data
|
|
2384
|
-
});
|
|
2385
|
-
}
|
|
2386
|
-
this.state = {
|
|
2387
|
-
nodes: this.state.nodes.asImmutable(),
|
|
2388
|
-
edges: this.state.edges.asImmutable(),
|
|
2389
|
-
ports: this.state.ports.asImmutable(),
|
|
2390
|
-
segs: this.state.segs.asImmutable(),
|
|
2391
|
-
graph: newGraph,
|
|
2392
|
-
update
|
|
2393
|
-
};
|
|
3777
|
+
this.state = { graph: graph2, update };
|
|
3778
|
+
this.setNodePositions();
|
|
2394
3779
|
this.seq.splice(this.index + 1);
|
|
2395
3780
|
this.seq.push(this.state);
|
|
2396
3781
|
this.nav("last");
|
|
3782
|
+
if (this.events.historyChange)
|
|
3783
|
+
this.events.historyChange(this.index, this.seq.length);
|
|
3784
|
+
}
|
|
3785
|
+
setNodePositions() {
|
|
3786
|
+
const { graph: graph2 } = this.state;
|
|
3787
|
+
for (const nodeId of graph2.dirtyNodes) {
|
|
3788
|
+
const node = graph2.getNode(nodeId);
|
|
3789
|
+
if (!node.isDummy)
|
|
3790
|
+
this.canvas.getNode(node.key).setPos(node.pos);
|
|
3791
|
+
}
|
|
2397
3792
|
}
|
|
2398
3793
|
async measureNodes(update) {
|
|
2399
|
-
const
|
|
2400
|
-
|
|
3794
|
+
const data = [];
|
|
3795
|
+
for (const set of [update.updateNodes, update.addNodes])
|
|
3796
|
+
for (const node of set ?? [])
|
|
3797
|
+
data.push(this.parseNode(node, true));
|
|
3798
|
+
return await this.canvas.measureNodes(data);
|
|
3799
|
+
}
|
|
3800
|
+
parseNode(data, bumpVersion = false) {
|
|
3801
|
+
const get = this.options.props.node;
|
|
3802
|
+
let props;
|
|
3803
|
+
if (get) props = get(data);
|
|
3804
|
+
else if (!data) throw new Error(`invalid node ${data}`);
|
|
3805
|
+
else if (typeof data == "string") props = { id: data };
|
|
3806
|
+
else if (typeof data == "object") props = data;
|
|
3807
|
+
else throw new Error(`invalid node ${JSON.stringify(data)}`);
|
|
3808
|
+
this.detectNodeFields(data);
|
|
3809
|
+
const overrides = this.nodeOverrides.get(data);
|
|
3810
|
+
if (overrides) props = { ...props, ...overrides };
|
|
3811
|
+
let { id, title, text, type, render } = props;
|
|
3812
|
+
id ??= this.getNodeId(data);
|
|
3813
|
+
const ports = this.parsePorts(props.ports);
|
|
3814
|
+
let version = this.nodeVersions.get(data);
|
|
3815
|
+
if (!version) version = 1;
|
|
3816
|
+
else if (bumpVersion) version++;
|
|
3817
|
+
this.nodeVersions.set(data, version);
|
|
3818
|
+
return { id, data, ports, title, text, type, render, version };
|
|
3819
|
+
}
|
|
3820
|
+
detectNodeFields(data) {
|
|
3821
|
+
if (typeof data != "object" || !data) return;
|
|
3822
|
+
const skip = /* @__PURE__ */ new Set(["id", "ports", "render", "version"]);
|
|
3823
|
+
for (const [key, value] of Object.entries(data)) {
|
|
3824
|
+
if (skip.has(key)) continue;
|
|
3825
|
+
if (value === null || value === void 0) continue;
|
|
3826
|
+
const type = typeof value;
|
|
3827
|
+
if (type === "string" || type === "number" || type === "boolean") {
|
|
3828
|
+
this.nodeFields.set(key, type);
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
2401
3831
|
}
|
|
2402
|
-
|
|
2403
|
-
return this.
|
|
3832
|
+
getNodeFields() {
|
|
3833
|
+
return this.nodeFields;
|
|
3834
|
+
}
|
|
3835
|
+
parseEdge(data) {
|
|
3836
|
+
const get = this.options.props.edge;
|
|
3837
|
+
let props;
|
|
3838
|
+
if (get) props = get(data);
|
|
3839
|
+
else if (!data) throw new Error(`invalid edge ${data}`);
|
|
3840
|
+
else if (typeof data == "string") props = this.parseStringEdge(data);
|
|
3841
|
+
else if (typeof data == "object") props = data;
|
|
3842
|
+
else throw new Error(`invalid edge ${data}`);
|
|
3843
|
+
const overrides = this.edgeOverrides.get(data);
|
|
3844
|
+
if (overrides) props = { ...props, ...overrides };
|
|
3845
|
+
let { id, source, target, type } = props;
|
|
3846
|
+
if (!id) id = this.getEdgeId(data);
|
|
3847
|
+
source = this.parseEdgeEnd(source);
|
|
3848
|
+
target = this.parseEdgeEnd(target);
|
|
3849
|
+
const edge = { id, source, target, type, data };
|
|
3850
|
+
return edge;
|
|
2404
3851
|
}
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
if (
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
3852
|
+
parseEdgeEnd(end) {
|
|
3853
|
+
if (!end) throw new Error(`edge has an undefined source or target`);
|
|
3854
|
+
if (typeof end == "string") return { id: end };
|
|
3855
|
+
if (typeof end == "object") {
|
|
3856
|
+
const keys = Object.keys(end);
|
|
3857
|
+
const pidx = keys.indexOf("port");
|
|
3858
|
+
if (pidx != -1) {
|
|
3859
|
+
if (end.port !== void 0 && typeof end.port != "string") return end;
|
|
3860
|
+
keys.splice(pidx, 1);
|
|
3861
|
+
}
|
|
3862
|
+
if (keys.length != 1) return end;
|
|
3863
|
+
if (keys[0] == "id") return end;
|
|
3864
|
+
if (keys[0] != "node") return end;
|
|
3865
|
+
const id = this.nodeIds.get(end.node);
|
|
3866
|
+
if (!id) throw new Error(`edge end references unknown node ${end.node}`);
|
|
3867
|
+
return { id, port: end.port };
|
|
3868
|
+
}
|
|
3869
|
+
throw new Error(`invalid edge end ${end}`);
|
|
2414
3870
|
}
|
|
2415
|
-
|
|
2416
|
-
const
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
3871
|
+
parseStringEdge(str) {
|
|
3872
|
+
const [source, target] = str.split(/\s*(?::|-+>?)\s*/);
|
|
3873
|
+
return { source, target };
|
|
3874
|
+
}
|
|
3875
|
+
parsePorts(ports) {
|
|
3876
|
+
const fixed = {};
|
|
3877
|
+
for (const key of ["in", "out"]) {
|
|
3878
|
+
if (ports?.[key] && ports[key].length > 0)
|
|
3879
|
+
fixed[key] = ports[key].map((port) => typeof port == "string" ? { id: port } : port);
|
|
3880
|
+
}
|
|
3881
|
+
return fixed;
|
|
3882
|
+
}
|
|
3883
|
+
getNode(id) {
|
|
3884
|
+
return this.graph.getNode(id);
|
|
3885
|
+
}
|
|
3886
|
+
getEdge(id) {
|
|
3887
|
+
return this.graph.getEdge(id);
|
|
3888
|
+
}
|
|
3889
|
+
getNodeId(node) {
|
|
3890
|
+
let id = this.nodeIds.get(node);
|
|
3891
|
+
if (!id) {
|
|
3892
|
+
id = `n${this.nextNodeId++}`;
|
|
3893
|
+
this.nodeIds.set(node, id);
|
|
3894
|
+
}
|
|
3895
|
+
return id;
|
|
3896
|
+
}
|
|
3897
|
+
getEdgeId(edge) {
|
|
3898
|
+
let id = this.edgeIds.get(edge);
|
|
3899
|
+
if (!id) {
|
|
3900
|
+
id = `e${this.nextEdgeId++}`;
|
|
3901
|
+
this.edgeIds.set(edge, id);
|
|
2428
3902
|
}
|
|
3903
|
+
return id;
|
|
2429
3904
|
}
|
|
2430
3905
|
_addNode(node, mut) {
|
|
2431
|
-
const
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
this.state.nodes.set(props.id, data);
|
|
2438
|
-
console.log("adding node:", { ...props, dims: this.getDims(node) });
|
|
2439
|
-
mut.addNode({ ...props, dims: this.getDims(node) });
|
|
3906
|
+
const { data, id: newId } = node.data;
|
|
3907
|
+
const oldId = this.nodeIds.get(data);
|
|
3908
|
+
if (oldId && oldId != newId)
|
|
3909
|
+
throw new Error(`node id of ${data} changed from ${oldId} to ${newId}`);
|
|
3910
|
+
this.nodeIds.set(data, newId);
|
|
3911
|
+
mut.addNode(node.data);
|
|
2440
3912
|
}
|
|
2441
3913
|
_removeNode(node, mut) {
|
|
2442
|
-
const
|
|
2443
|
-
if (!
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
3914
|
+
const id = this.nodeIds.get(node);
|
|
3915
|
+
if (!id) throw new Error(`removing node ${JSON.stringify(node)} which does not exist`);
|
|
3916
|
+
mut.removeNode({ id });
|
|
3917
|
+
}
|
|
3918
|
+
_updateNode(node, mut) {
|
|
3919
|
+
const { data, id: newId } = node.data;
|
|
3920
|
+
const oldId = this.nodeIds.get(data);
|
|
3921
|
+
if (!oldId) throw new Error(`updating unknown node ${JSON.stringify(node)} `);
|
|
3922
|
+
if (oldId != newId) throw new Error(`node id changed from ${oldId} to ${newId} `);
|
|
3923
|
+
mut.updateNode(node.data);
|
|
2447
3924
|
}
|
|
2448
3925
|
_addEdge(edge, mut) {
|
|
2449
|
-
const
|
|
2450
|
-
const id =
|
|
2451
|
-
if (
|
|
2452
|
-
throw new Error(`edge ${
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
this.state.edges.set(id, data);
|
|
2456
|
-
mut.addEdge(props);
|
|
3926
|
+
const data = this.parseEdge(edge);
|
|
3927
|
+
const id = this.edgeIds.get(edge);
|
|
3928
|
+
if (id && id != data.id)
|
|
3929
|
+
throw new Error(`edge id changed from ${id} to ${data.id} `);
|
|
3930
|
+
this.edgeIds.set(edge, data.id);
|
|
3931
|
+
mut.addEdge(data);
|
|
2457
3932
|
}
|
|
2458
3933
|
_removeEdge(edge, mut) {
|
|
2459
|
-
const
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
throw new Error(`removing edge ${str} which does not exist`);
|
|
2463
|
-
this.state.edges.delete(id);
|
|
2464
|
-
mut.removeEdge(props);
|
|
2465
|
-
}
|
|
2466
|
-
_onOptionChange(prop) {
|
|
2467
|
-
}
|
|
2468
|
-
};
|
|
2469
|
-
var Update = class {
|
|
2470
|
-
addedNodes;
|
|
2471
|
-
removedNodes;
|
|
2472
|
-
updatedNodes;
|
|
2473
|
-
addedEdges;
|
|
2474
|
-
removedEdges;
|
|
2475
|
-
updatedEdges;
|
|
2476
|
-
desc;
|
|
2477
|
-
constructor() {
|
|
2478
|
-
this.addedNodes = [];
|
|
2479
|
-
this.removedNodes = [];
|
|
2480
|
-
this.updatedNodes = [];
|
|
2481
|
-
this.addedEdges = [];
|
|
2482
|
-
this.removedEdges = [];
|
|
2483
|
-
this.updatedEdges = [];
|
|
3934
|
+
const id = this.edgeIds.get(edge);
|
|
3935
|
+
if (!id) throw new Error(`removing edge ${JSON.stringify(edge)} which does not exist`);
|
|
3936
|
+
mut.removeEdge(this.parseEdge(edge));
|
|
2484
3937
|
}
|
|
2485
|
-
|
|
2486
|
-
|
|
3938
|
+
_updateEdge(edge, mut) {
|
|
3939
|
+
const id = this.edgeIds.get(edge);
|
|
3940
|
+
if (!id) throw new Error(`updating unknown edge ${JSON.stringify(edge)} `);
|
|
3941
|
+
const data = this.parseEdge(edge);
|
|
3942
|
+
if (data.id !== id) throw new Error(`edge id changed from ${id} to ${data.id} `);
|
|
3943
|
+
mut.updateEdge(data);
|
|
3944
|
+
}
|
|
3945
|
+
// Event Handlers
|
|
3946
|
+
handleClickNode(id) {
|
|
3947
|
+
const handler = this.events.nodeClick;
|
|
3948
|
+
const node = this.graph.getNode(id);
|
|
3949
|
+
if (handler) handler(node.data);
|
|
3950
|
+
}
|
|
3951
|
+
handleClickEdge(id) {
|
|
3952
|
+
const handler = this.events.edgeClick;
|
|
3953
|
+
if (!handler) return;
|
|
3954
|
+
const seg = this.graph.getSeg(id);
|
|
3955
|
+
if (seg.edgeIds.size != 1) return;
|
|
3956
|
+
const edge = this.graph.getEdge(seg.edgeIds.values().next().value);
|
|
3957
|
+
handler(edge.data);
|
|
3958
|
+
}
|
|
3959
|
+
async handleNewNode() {
|
|
3960
|
+
const gotNode = async (node) => {
|
|
3961
|
+
await this.addNode(node);
|
|
3962
|
+
};
|
|
3963
|
+
if (this.events.newNode)
|
|
3964
|
+
this.events.newNode(gotNode);
|
|
3965
|
+
else
|
|
3966
|
+
this.canvas.showNewNodeModal(async (data) => {
|
|
3967
|
+
if (this.events.addNode)
|
|
3968
|
+
this.events.addNode(data, gotNode);
|
|
3969
|
+
else
|
|
3970
|
+
await gotNode(data);
|
|
3971
|
+
});
|
|
2487
3972
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
3973
|
+
async handleNewNodeFrom(source) {
|
|
3974
|
+
const gotNode = async (node) => {
|
|
3975
|
+
const gotEdge = async (edge) => {
|
|
3976
|
+
await this.update((u) => {
|
|
3977
|
+
u.addNode(node).addEdge(edge);
|
|
3978
|
+
});
|
|
3979
|
+
};
|
|
3980
|
+
const data = this.graph.getNode(source.id).data;
|
|
3981
|
+
const newEdge = {
|
|
3982
|
+
source: { node: data, port: source.port },
|
|
3983
|
+
target: { node }
|
|
3984
|
+
};
|
|
3985
|
+
if (this.events.addEdge)
|
|
3986
|
+
this.events.addEdge(newEdge, gotEdge);
|
|
3987
|
+
else
|
|
3988
|
+
await gotEdge(newEdge);
|
|
3989
|
+
};
|
|
3990
|
+
if (this.events.newNode)
|
|
3991
|
+
this.events.newNode(gotNode);
|
|
3992
|
+
else
|
|
3993
|
+
this.canvas.showNewNodeModal(async (data) => {
|
|
3994
|
+
if (this.events.addNode)
|
|
3995
|
+
this.events.addNode(data, gotNode);
|
|
3996
|
+
else
|
|
3997
|
+
await gotNode(data);
|
|
3998
|
+
});
|
|
2490
3999
|
}
|
|
2491
|
-
|
|
2492
|
-
this.
|
|
4000
|
+
async handleEditNode(id) {
|
|
4001
|
+
const node = this.graph.getNode(id);
|
|
4002
|
+
const gotNode = async (node2) => {
|
|
4003
|
+
if (node2) await this.updateNode(node2);
|
|
4004
|
+
};
|
|
4005
|
+
if (this.events.editNode)
|
|
4006
|
+
this.events.editNode(node.data, gotNode);
|
|
4007
|
+
else {
|
|
4008
|
+
this.canvas.showEditNodeModal(node, async (data) => {
|
|
4009
|
+
if (this.events.updateNode)
|
|
4010
|
+
this.events.updateNode(node.data, data, gotNode);
|
|
4011
|
+
else {
|
|
4012
|
+
this.nodeOverrides.set(node.data, data);
|
|
4013
|
+
await gotNode(node.data);
|
|
4014
|
+
}
|
|
4015
|
+
});
|
|
4016
|
+
}
|
|
2493
4017
|
}
|
|
2494
|
-
|
|
2495
|
-
this.
|
|
4018
|
+
async handleEditEdge(id) {
|
|
4019
|
+
const seg = this.graph.getSeg(id);
|
|
4020
|
+
if (seg.edgeIds.size != 1) return;
|
|
4021
|
+
const edge = this.graph.getEdge(seg.edgeIds.values().next().value);
|
|
4022
|
+
const gotEdge = async (edge2) => {
|
|
4023
|
+
if (edge2) await this.updateEdge(edge2);
|
|
4024
|
+
};
|
|
4025
|
+
if (this.events.editEdge)
|
|
4026
|
+
this.events.editEdge(edge.data, gotEdge);
|
|
4027
|
+
else
|
|
4028
|
+
this.canvas.showEditEdgeModal(edge, async (data) => {
|
|
4029
|
+
const sourceNode = edge.sourceNode(this.graph);
|
|
4030
|
+
const targetNode = edge.targetNode(this.graph);
|
|
4031
|
+
const update = {
|
|
4032
|
+
source: { node: sourceNode.data, port: data.source.port, marker: data.source.marker },
|
|
4033
|
+
target: { node: targetNode.data, port: data.target.port, marker: data.target.marker }
|
|
4034
|
+
};
|
|
4035
|
+
if (this.events.updateEdge)
|
|
4036
|
+
this.events.updateEdge(edge.data, update, gotEdge);
|
|
4037
|
+
else {
|
|
4038
|
+
this.edgeOverrides.set(edge.data, {
|
|
4039
|
+
source: { id: sourceNode.id, port: data.source.port, marker: data.source.marker },
|
|
4040
|
+
target: { id: targetNode.id, port: data.target.port, marker: data.target.marker },
|
|
4041
|
+
type: data.type
|
|
4042
|
+
});
|
|
4043
|
+
await gotEdge(edge.data);
|
|
4044
|
+
}
|
|
4045
|
+
});
|
|
2496
4046
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
4047
|
+
async handleAddEdge(data) {
|
|
4048
|
+
const gotEdge = async (edge) => {
|
|
4049
|
+
if (edge) await this.addEdge(edge);
|
|
4050
|
+
};
|
|
4051
|
+
const newEdge = {
|
|
4052
|
+
source: { node: this.graph.getNode(data.source.id).data, port: data.source.port, marker: data.source.marker },
|
|
4053
|
+
target: { node: this.graph.getNode(data.target.id).data, port: data.target.port, marker: data.target.marker }
|
|
4054
|
+
};
|
|
4055
|
+
if (this.events.addEdge)
|
|
4056
|
+
this.events.addEdge(newEdge, gotEdge);
|
|
4057
|
+
else
|
|
4058
|
+
await gotEdge(data);
|
|
2499
4059
|
}
|
|
2500
|
-
|
|
2501
|
-
this.
|
|
4060
|
+
async handleDeleteNode(id) {
|
|
4061
|
+
const node = this.getNode(id);
|
|
4062
|
+
if (this.events.removeNode)
|
|
4063
|
+
this.events.removeNode(node.data, async (remove) => {
|
|
4064
|
+
if (remove) await this.deleteNode(node.data);
|
|
4065
|
+
});
|
|
4066
|
+
else
|
|
4067
|
+
await this.deleteNode(node.data);
|
|
2502
4068
|
}
|
|
2503
|
-
|
|
2504
|
-
this.
|
|
4069
|
+
async handleDeleteEdge(id) {
|
|
4070
|
+
const edge = this.getEdge(id);
|
|
4071
|
+
if (this.events.removeEdge)
|
|
4072
|
+
this.events.removeEdge(edge.data, async (remove) => {
|
|
4073
|
+
if (remove) await this.deleteEdge(edge.data);
|
|
4074
|
+
});
|
|
4075
|
+
else
|
|
4076
|
+
await this.deleteEdge(edge.data);
|
|
2505
4077
|
}
|
|
2506
4078
|
};
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
for (const node of nodes)
|
|
2513
|
-
update.addNode(node);
|
|
2514
|
-
for (const edge of edges)
|
|
2515
|
-
update.addEdge(edge);
|
|
2516
|
-
});
|
|
2517
|
-
}
|
|
4079
|
+
|
|
4080
|
+
// src/index.ts
|
|
4081
|
+
async function graph(args = { root: "app" }) {
|
|
4082
|
+
const api = new API(args);
|
|
4083
|
+
await api.init();
|
|
2518
4084
|
return api;
|
|
2519
4085
|
}
|
|
2520
4086
|
var index_default = graph;
|