@3plate/graph-core 0.1.6 → 0.1.9
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 +529 -234
- package/dist/index.d.cts +66 -4
- package/dist/index.d.ts +66 -4
- package/dist/index.js +528 -234
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1338,15 +1338,16 @@ var Layout = class _Layout {
|
|
|
1338
1338
|
|
|
1339
1339
|
// src/canvas/marker.tsx
|
|
1340
1340
|
import { jsx } from "jsx-dom/jsx-runtime";
|
|
1341
|
-
function arrow(size, reverse = false) {
|
|
1341
|
+
function arrow(size, reverse = false, prefix = "") {
|
|
1342
1342
|
const h = size / 1.5;
|
|
1343
1343
|
const w = size;
|
|
1344
1344
|
const ry = h / 2;
|
|
1345
1345
|
const suffix = reverse ? "-reverse" : "";
|
|
1346
|
+
const id = prefix ? `${prefix}-g3p-marker-arrow${suffix}` : `g3p-marker-arrow${suffix}`;
|
|
1346
1347
|
return /* @__PURE__ */ jsx(
|
|
1347
1348
|
"marker",
|
|
1348
1349
|
{
|
|
1349
|
-
id
|
|
1350
|
+
id,
|
|
1350
1351
|
className: "g3p-marker g3p-marker-arrow",
|
|
1351
1352
|
markerWidth: size,
|
|
1352
1353
|
markerHeight: size,
|
|
@@ -1358,14 +1359,15 @@ function arrow(size, reverse = false) {
|
|
|
1358
1359
|
}
|
|
1359
1360
|
);
|
|
1360
1361
|
}
|
|
1361
|
-
function circle(size, reverse = false) {
|
|
1362
|
+
function circle(size, reverse = false, prefix = "") {
|
|
1362
1363
|
const r = size / 3;
|
|
1363
1364
|
const cy = size / 2;
|
|
1364
1365
|
const suffix = reverse ? "-reverse" : "";
|
|
1366
|
+
const id = prefix ? `${prefix}-g3p-marker-circle${suffix}` : `g3p-marker-circle${suffix}`;
|
|
1365
1367
|
return /* @__PURE__ */ jsx(
|
|
1366
1368
|
"marker",
|
|
1367
1369
|
{
|
|
1368
|
-
id
|
|
1370
|
+
id,
|
|
1369
1371
|
className: "g3p-marker g3p-marker-circle",
|
|
1370
1372
|
markerWidth: size,
|
|
1371
1373
|
markerHeight: size,
|
|
@@ -1377,15 +1379,16 @@ function circle(size, reverse = false) {
|
|
|
1377
1379
|
}
|
|
1378
1380
|
);
|
|
1379
1381
|
}
|
|
1380
|
-
function diamond(size, reverse = false) {
|
|
1382
|
+
function diamond(size, reverse = false, prefix = "") {
|
|
1381
1383
|
const w = size * 0.7;
|
|
1382
1384
|
const h = size / 2;
|
|
1383
1385
|
const cy = size / 2;
|
|
1384
1386
|
const suffix = reverse ? "-reverse" : "";
|
|
1387
|
+
const id = prefix ? `${prefix}-g3p-marker-diamond${suffix}` : `g3p-marker-diamond${suffix}`;
|
|
1385
1388
|
return /* @__PURE__ */ jsx(
|
|
1386
1389
|
"marker",
|
|
1387
1390
|
{
|
|
1388
|
-
id
|
|
1391
|
+
id,
|
|
1389
1392
|
className: "g3p-marker g3p-marker-diamond",
|
|
1390
1393
|
markerWidth: size,
|
|
1391
1394
|
markerHeight: size,
|
|
@@ -1397,14 +1400,15 @@ function diamond(size, reverse = false) {
|
|
|
1397
1400
|
}
|
|
1398
1401
|
);
|
|
1399
1402
|
}
|
|
1400
|
-
function bar(size, reverse = false) {
|
|
1403
|
+
function bar(size, reverse = false, prefix = "") {
|
|
1401
1404
|
const h = size * 0.6;
|
|
1402
1405
|
const cy = size / 2;
|
|
1403
1406
|
const suffix = reverse ? "-reverse" : "";
|
|
1407
|
+
const id = prefix ? `${prefix}-g3p-marker-bar${suffix}` : `g3p-marker-bar${suffix}`;
|
|
1404
1408
|
return /* @__PURE__ */ jsx(
|
|
1405
1409
|
"marker",
|
|
1406
1410
|
{
|
|
1407
|
-
id
|
|
1411
|
+
id,
|
|
1408
1412
|
className: "g3p-marker g3p-marker-bar",
|
|
1409
1413
|
markerWidth: size,
|
|
1410
1414
|
markerHeight: size,
|
|
@@ -1416,7 +1420,7 @@ function bar(size, reverse = false) {
|
|
|
1416
1420
|
}
|
|
1417
1421
|
);
|
|
1418
1422
|
}
|
|
1419
|
-
function none(size, reverse = false) {
|
|
1423
|
+
function none(size, reverse = false, prefix = "") {
|
|
1420
1424
|
return void 0;
|
|
1421
1425
|
}
|
|
1422
1426
|
function normalize(data) {
|
|
@@ -2198,6 +2202,9 @@ var Seg2 = class {
|
|
|
2198
2202
|
if (this.source.isDummy) source = void 0;
|
|
2199
2203
|
if (this.target.isDummy) target = void 0;
|
|
2200
2204
|
const typeClass = this.type ? `g3p-edge-type-${this.type}` : "";
|
|
2205
|
+
const prefix = this.canvas.markerPrefix;
|
|
2206
|
+
const markerStartId = source ? prefix ? `${prefix}-g3p-marker-${source}-reverse` : `g3p-marker-${source}-reverse` : void 0;
|
|
2207
|
+
const markerEndId = target ? prefix ? `${prefix}-g3p-marker-${target}` : `g3p-marker-${target}` : void 0;
|
|
2201
2208
|
return /* @__PURE__ */ jsxs2(
|
|
2202
2209
|
"g",
|
|
2203
2210
|
{
|
|
@@ -2212,8 +2219,8 @@ var Seg2 = class {
|
|
|
2212
2219
|
d: this.svg,
|
|
2213
2220
|
fill: "none",
|
|
2214
2221
|
className: "g3p-seg-line",
|
|
2215
|
-
markerStart:
|
|
2216
|
-
markerEnd:
|
|
2222
|
+
markerStart: markerStartId ? `url(#${markerStartId})` : void 0,
|
|
2223
|
+
markerEnd: markerEndId ? `url(#${markerEndId})` : void 0
|
|
2217
2224
|
}
|
|
2218
2225
|
),
|
|
2219
2226
|
/* @__PURE__ */ jsx3(
|
|
@@ -2798,6 +2805,10 @@ var Canvas = class {
|
|
|
2798
2805
|
curNodes;
|
|
2799
2806
|
curSegs;
|
|
2800
2807
|
updating;
|
|
2808
|
+
// Unique marker ID prefix for this canvas instance
|
|
2809
|
+
markerPrefix;
|
|
2810
|
+
// Dynamic style element for this instance (for cleanup)
|
|
2811
|
+
dynamicStyleEl;
|
|
2801
2812
|
// Pan-zoom state
|
|
2802
2813
|
panScale = null;
|
|
2803
2814
|
zoomControls;
|
|
@@ -2811,6 +2822,13 @@ var Canvas = class {
|
|
|
2811
2822
|
constructor(api, options) {
|
|
2812
2823
|
Object.assign(this, options);
|
|
2813
2824
|
this.api = api;
|
|
2825
|
+
this.reset();
|
|
2826
|
+
this.markerPrefix = api.root.replace(/[^a-zA-Z0-9-_]/g, "-");
|
|
2827
|
+
this.createMeasurementContainer();
|
|
2828
|
+
this.createCanvasContainer();
|
|
2829
|
+
if (this.panZoom) this.setupPanZoom();
|
|
2830
|
+
}
|
|
2831
|
+
reset() {
|
|
2814
2832
|
this.allNodes = /* @__PURE__ */ new Map();
|
|
2815
2833
|
this.curNodes = /* @__PURE__ */ new Map();
|
|
2816
2834
|
this.curSegs = /* @__PURE__ */ new Map();
|
|
@@ -2819,9 +2837,7 @@ var Canvas = class {
|
|
|
2819
2837
|
this.transform = { x: 0, y: 0, scale: 1 };
|
|
2820
2838
|
this.editMode = new EditMode();
|
|
2821
2839
|
this.editMode.editable = this.editable;
|
|
2822
|
-
this.
|
|
2823
|
-
this.createCanvasContainer();
|
|
2824
|
-
if (this.panZoom) this.setupPanZoom();
|
|
2840
|
+
if (this.group) this.group.innerHTML = "";
|
|
2825
2841
|
}
|
|
2826
2842
|
createMeasurementContainer() {
|
|
2827
2843
|
this.measurement = document.createElement("div");
|
|
@@ -3006,12 +3022,13 @@ var Canvas = class {
|
|
|
3006
3022
|
}
|
|
3007
3023
|
generateDynamicStyles() {
|
|
3008
3024
|
let css = "";
|
|
3009
|
-
|
|
3025
|
+
const scope = `[data-g3p-instance="${this.markerPrefix}"]`;
|
|
3026
|
+
css += themeToCSS(this.theme, scope);
|
|
3010
3027
|
for (const [type, vars] of Object.entries(this.nodeTypes)) {
|
|
3011
|
-
css += themeToCSS(vars,
|
|
3028
|
+
css += themeToCSS(vars, `${scope} .g3p-node-type-${type}`, "node");
|
|
3012
3029
|
}
|
|
3013
3030
|
for (const [type, vars] of Object.entries(this.edgeTypes)) {
|
|
3014
|
-
css += themeToCSS(vars,
|
|
3031
|
+
css += themeToCSS(vars, `${scope} .g3p-edge-type-${type}`);
|
|
3015
3032
|
}
|
|
3016
3033
|
return css;
|
|
3017
3034
|
}
|
|
@@ -3024,15 +3041,18 @@ var Canvas = class {
|
|
|
3024
3041
|
}
|
|
3025
3042
|
const dynamicStyles = this.generateDynamicStyles();
|
|
3026
3043
|
if (dynamicStyles) {
|
|
3027
|
-
|
|
3028
|
-
dynamicStyleEl
|
|
3029
|
-
|
|
3044
|
+
this.dynamicStyleEl?.remove();
|
|
3045
|
+
this.dynamicStyleEl = document.createElement("style");
|
|
3046
|
+
this.dynamicStyleEl.id = `g3p-styles-${this.markerPrefix}`;
|
|
3047
|
+
this.dynamicStyleEl.textContent = dynamicStyles;
|
|
3048
|
+
document.head.appendChild(this.dynamicStyleEl);
|
|
3030
3049
|
}
|
|
3031
3050
|
const colorModeClass = this.colorMode !== "system" ? `g3p-${this.colorMode}` : "";
|
|
3032
3051
|
this.container = /* @__PURE__ */ jsx6(
|
|
3033
3052
|
"div",
|
|
3034
3053
|
{
|
|
3035
3054
|
className: `g3p-canvas-container ${colorModeClass}`.trim(),
|
|
3055
|
+
"data-g3p-instance": this.markerPrefix,
|
|
3036
3056
|
ref: (el) => this.container = el,
|
|
3037
3057
|
onContextMenu: this.onContextMenu.bind(this),
|
|
3038
3058
|
children: /* @__PURE__ */ jsxs5(
|
|
@@ -3048,8 +3068,8 @@ var Canvas = class {
|
|
|
3048
3068
|
onDblClick: this.onDoubleClick.bind(this),
|
|
3049
3069
|
children: [
|
|
3050
3070
|
/* @__PURE__ */ jsxs5("defs", { children: [
|
|
3051
|
-
Object.values(markerDefs).map((marker) => marker(this.markerSize, false)),
|
|
3052
|
-
Object.values(markerDefs).map((marker) => marker(this.markerSize, true))
|
|
3071
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, false, this.markerPrefix)),
|
|
3072
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, true, this.markerPrefix))
|
|
3053
3073
|
] }),
|
|
3054
3074
|
/* @__PURE__ */ jsx6(
|
|
3055
3075
|
"g",
|
|
@@ -3406,6 +3426,40 @@ var Canvas = class {
|
|
|
3406
3426
|
}
|
|
3407
3427
|
return { type: "canvas" };
|
|
3408
3428
|
}
|
|
3429
|
+
/** Update theme and type styles dynamically */
|
|
3430
|
+
updateStyles(options) {
|
|
3431
|
+
if (options.theme !== void 0) this.theme = options.theme;
|
|
3432
|
+
if (options.nodeTypes !== void 0) this.nodeTypes = options.nodeTypes;
|
|
3433
|
+
if (options.edgeTypes !== void 0) this.edgeTypes = options.edgeTypes;
|
|
3434
|
+
const dynamicStyles = this.generateDynamicStyles();
|
|
3435
|
+
if (dynamicStyles) {
|
|
3436
|
+
if (!this.dynamicStyleEl) {
|
|
3437
|
+
this.dynamicStyleEl = document.createElement("style");
|
|
3438
|
+
this.dynamicStyleEl.id = `g3p-styles-${this.markerPrefix}`;
|
|
3439
|
+
document.head.appendChild(this.dynamicStyleEl);
|
|
3440
|
+
}
|
|
3441
|
+
this.dynamicStyleEl.textContent = dynamicStyles;
|
|
3442
|
+
} else if (this.dynamicStyleEl) {
|
|
3443
|
+
this.dynamicStyleEl.remove();
|
|
3444
|
+
this.dynamicStyleEl = void 0;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
/** Update color mode without recreating the canvas */
|
|
3448
|
+
setColorMode(colorMode) {
|
|
3449
|
+
if (!this.container) return;
|
|
3450
|
+
this.colorMode = colorMode;
|
|
3451
|
+
this.container.classList.remove("g3p-light", "g3p-dark");
|
|
3452
|
+
if (colorMode !== "system") {
|
|
3453
|
+
this.container.classList.add(`g3p-${colorMode}`);
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
/** Cleanup resources when the canvas is destroyed */
|
|
3457
|
+
destroy() {
|
|
3458
|
+
this.dynamicStyleEl?.remove();
|
|
3459
|
+
this.dynamicStyleEl = void 0;
|
|
3460
|
+
this.measurement?.remove();
|
|
3461
|
+
this.measurement = void 0;
|
|
3462
|
+
}
|
|
3409
3463
|
};
|
|
3410
3464
|
var themeVarMap = {
|
|
3411
3465
|
// Canvas
|
|
@@ -3572,6 +3626,225 @@ var Updater = class _Updater {
|
|
|
3572
3626
|
}
|
|
3573
3627
|
};
|
|
3574
3628
|
|
|
3629
|
+
// src/api/ingest.ts
|
|
3630
|
+
var Ingest = class {
|
|
3631
|
+
constructor(api) {
|
|
3632
|
+
this.api = api;
|
|
3633
|
+
}
|
|
3634
|
+
/**
|
|
3635
|
+
* Apply an incoming ingest message to the API.
|
|
3636
|
+
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
3637
|
+
* - update: apply incremental update
|
|
3638
|
+
* - history: initialize from a set of frames (clears prior history)
|
|
3639
|
+
*/
|
|
3640
|
+
async apply(msg) {
|
|
3641
|
+
switch (msg.type) {
|
|
3642
|
+
case "snapshot": {
|
|
3643
|
+
await this.api.replaceSnapshot(msg.nodes, msg.edges, msg.description);
|
|
3644
|
+
break;
|
|
3645
|
+
}
|
|
3646
|
+
case "update": {
|
|
3647
|
+
await this.api.update((u) => {
|
|
3648
|
+
if (msg.addNodes) u.addNodes(...msg.addNodes);
|
|
3649
|
+
if (msg.removeNodes) u.deleteNodes(...msg.removeNodes);
|
|
3650
|
+
if (msg.updateNodes) u.updateNodes(...msg.updateNodes);
|
|
3651
|
+
if (msg.addEdges) u.addEdges(...msg.addEdges);
|
|
3652
|
+
if (msg.removeEdges) u.deleteEdges(...msg.removeEdges);
|
|
3653
|
+
if (msg.updateEdges) u.updateEdges(...msg.updateEdges);
|
|
3654
|
+
if (msg.description) u.describe(msg.description);
|
|
3655
|
+
});
|
|
3656
|
+
break;
|
|
3657
|
+
}
|
|
3658
|
+
case "history": {
|
|
3659
|
+
await this.api.replaceHistory(msg.frames);
|
|
3660
|
+
break;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
};
|
|
3665
|
+
|
|
3666
|
+
// src/api/sources/WebSocketSource.ts
|
|
3667
|
+
var WebSocketSource = class {
|
|
3668
|
+
url;
|
|
3669
|
+
ws = null;
|
|
3670
|
+
onMessage;
|
|
3671
|
+
onStatus;
|
|
3672
|
+
reconnectMs;
|
|
3673
|
+
closedByUser = false;
|
|
3674
|
+
connectStartTime = null;
|
|
3675
|
+
totalTimeoutMs = 1e4;
|
|
3676
|
+
totalTimeoutTimer = null;
|
|
3677
|
+
constructor(url, onMessage, onStatus, reconnectMs = 1500) {
|
|
3678
|
+
this.url = url;
|
|
3679
|
+
this.onMessage = onMessage;
|
|
3680
|
+
this.onStatus = onStatus;
|
|
3681
|
+
this.reconnectMs = reconnectMs;
|
|
3682
|
+
}
|
|
3683
|
+
connect() {
|
|
3684
|
+
this.closedByUser = false;
|
|
3685
|
+
this.connectStartTime = Date.now();
|
|
3686
|
+
this.startTotalTimeout();
|
|
3687
|
+
this.open();
|
|
3688
|
+
}
|
|
3689
|
+
disconnect() {
|
|
3690
|
+
this.closedByUser = true;
|
|
3691
|
+
this.clearTotalTimeout();
|
|
3692
|
+
if (this.ws) {
|
|
3693
|
+
try {
|
|
3694
|
+
this.ws.close();
|
|
3695
|
+
} catch {
|
|
3696
|
+
}
|
|
3697
|
+
this.ws = null;
|
|
3698
|
+
}
|
|
3699
|
+
this.onStatus?.("closed");
|
|
3700
|
+
}
|
|
3701
|
+
startTotalTimeout() {
|
|
3702
|
+
this.clearTotalTimeout();
|
|
3703
|
+
this.totalTimeoutTimer = window.setTimeout(() => {
|
|
3704
|
+
if (!this.closedByUser && this.ws?.readyState !== WebSocket.OPEN) {
|
|
3705
|
+
this.closedByUser = true;
|
|
3706
|
+
if (this.ws) {
|
|
3707
|
+
try {
|
|
3708
|
+
this.ws.close();
|
|
3709
|
+
} catch {
|
|
3710
|
+
}
|
|
3711
|
+
this.ws = null;
|
|
3712
|
+
}
|
|
3713
|
+
this.clearTotalTimeout();
|
|
3714
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3715
|
+
}
|
|
3716
|
+
}, this.totalTimeoutMs);
|
|
3717
|
+
}
|
|
3718
|
+
clearTotalTimeout() {
|
|
3719
|
+
if (this.totalTimeoutTimer !== null) {
|
|
3720
|
+
clearTimeout(this.totalTimeoutTimer);
|
|
3721
|
+
this.totalTimeoutTimer = null;
|
|
3722
|
+
}
|
|
3723
|
+
this.connectStartTime = null;
|
|
3724
|
+
}
|
|
3725
|
+
open() {
|
|
3726
|
+
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
3727
|
+
if (!this.closedByUser) {
|
|
3728
|
+
this.closedByUser = true;
|
|
3729
|
+
this.clearTotalTimeout();
|
|
3730
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3731
|
+
}
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
this.onStatus?.(this.ws ? "reconnecting" : "connecting");
|
|
3735
|
+
const ws = new WebSocket(this.url);
|
|
3736
|
+
this.ws = ws;
|
|
3737
|
+
ws.onopen = () => {
|
|
3738
|
+
this.clearTotalTimeout();
|
|
3739
|
+
this.onStatus?.("connected");
|
|
3740
|
+
};
|
|
3741
|
+
ws.onerror = (e) => {
|
|
3742
|
+
this.onStatus?.("error", e);
|
|
3743
|
+
};
|
|
3744
|
+
ws.onclose = () => {
|
|
3745
|
+
if (this.closedByUser) {
|
|
3746
|
+
this.onStatus?.("closed");
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
3750
|
+
this.closedByUser = true;
|
|
3751
|
+
this.clearTotalTimeout();
|
|
3752
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
this.onStatus?.("reconnecting");
|
|
3756
|
+
setTimeout(() => this.open(), this.reconnectMs);
|
|
3757
|
+
};
|
|
3758
|
+
ws.onmessage = (ev) => {
|
|
3759
|
+
const data = typeof ev.data === "string" ? ev.data : "";
|
|
3760
|
+
const lines = data.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3761
|
+
for (const line of lines) {
|
|
3762
|
+
try {
|
|
3763
|
+
const obj = JSON.parse(line);
|
|
3764
|
+
this.onMessage(obj);
|
|
3765
|
+
} catch {
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
};
|
|
3771
|
+
|
|
3772
|
+
// src/api/sources/FileSource.ts
|
|
3773
|
+
var FileSource = class {
|
|
3774
|
+
url;
|
|
3775
|
+
onMessage;
|
|
3776
|
+
onStatus;
|
|
3777
|
+
timer = null;
|
|
3778
|
+
lastETag = null;
|
|
3779
|
+
lastContent = "";
|
|
3780
|
+
intervalMs = 1e3;
|
|
3781
|
+
closed = false;
|
|
3782
|
+
constructor(url, onMessage, onStatus, intervalMs = 1e3) {
|
|
3783
|
+
this.url = url;
|
|
3784
|
+
this.onMessage = onMessage;
|
|
3785
|
+
this.onStatus = onStatus;
|
|
3786
|
+
this.intervalMs = intervalMs;
|
|
3787
|
+
}
|
|
3788
|
+
async connect() {
|
|
3789
|
+
this.closed = false;
|
|
3790
|
+
this.lastETag = null;
|
|
3791
|
+
this.lastContent = "";
|
|
3792
|
+
this.onStatus?.("opened");
|
|
3793
|
+
this.startPolling();
|
|
3794
|
+
}
|
|
3795
|
+
close() {
|
|
3796
|
+
this.closed = true;
|
|
3797
|
+
if (this.timer) {
|
|
3798
|
+
window.clearInterval(this.timer);
|
|
3799
|
+
this.timer = null;
|
|
3800
|
+
}
|
|
3801
|
+
this.onStatus?.("closed");
|
|
3802
|
+
}
|
|
3803
|
+
startPolling() {
|
|
3804
|
+
if (this.timer) window.clearInterval(this.timer);
|
|
3805
|
+
this.timer = window.setInterval(() => this.poll(), this.intervalMs);
|
|
3806
|
+
this.poll();
|
|
3807
|
+
}
|
|
3808
|
+
async poll() {
|
|
3809
|
+
if (this.closed) return;
|
|
3810
|
+
try {
|
|
3811
|
+
this.onStatus?.("reading");
|
|
3812
|
+
const headers = {};
|
|
3813
|
+
if (this.lastETag) {
|
|
3814
|
+
headers["If-None-Match"] = this.lastETag;
|
|
3815
|
+
}
|
|
3816
|
+
const response = await fetch(this.url, { headers });
|
|
3817
|
+
if (response.status === 304) {
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
if (!response.ok) {
|
|
3821
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
3822
|
+
}
|
|
3823
|
+
const etag = response.headers.get("ETag");
|
|
3824
|
+
if (etag) {
|
|
3825
|
+
this.lastETag = etag;
|
|
3826
|
+
}
|
|
3827
|
+
const content = await response.text();
|
|
3828
|
+
if (content === this.lastContent) {
|
|
3829
|
+
return;
|
|
3830
|
+
}
|
|
3831
|
+
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3832
|
+
const lastContentLines = this.lastContent.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3833
|
+
const newLines = lines.slice(lastContentLines.length);
|
|
3834
|
+
for (const line of newLines) {
|
|
3835
|
+
try {
|
|
3836
|
+
const obj = JSON.parse(line);
|
|
3837
|
+
this.onMessage(obj);
|
|
3838
|
+
} catch {
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
this.lastContent = content;
|
|
3842
|
+
} catch (e) {
|
|
3843
|
+
this.onStatus?.("error", e);
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
};
|
|
3847
|
+
|
|
3575
3848
|
// src/api/api.ts
|
|
3576
3849
|
var log11 = logger("api");
|
|
3577
3850
|
var API = class {
|
|
@@ -3590,11 +3863,16 @@ var API = class {
|
|
|
3590
3863
|
nextNodeId;
|
|
3591
3864
|
nextEdgeId;
|
|
3592
3865
|
events;
|
|
3866
|
+
ingest;
|
|
3867
|
+
ingestionSource;
|
|
3868
|
+
ingestionConfig;
|
|
3869
|
+
prevProps = {};
|
|
3593
3870
|
root;
|
|
3594
3871
|
constructor(args) {
|
|
3595
3872
|
this.root = args.root;
|
|
3596
3873
|
this.options = applyDefaults(args.options);
|
|
3597
3874
|
this.events = args.events || {};
|
|
3875
|
+
this.ingestionConfig = args.ingestion;
|
|
3598
3876
|
this.reset();
|
|
3599
3877
|
this.canvas = new Canvas(this, {
|
|
3600
3878
|
...this.options.canvas,
|
|
@@ -3608,6 +3886,15 @@ var API = class {
|
|
|
3608
3886
|
} else {
|
|
3609
3887
|
this.history = [];
|
|
3610
3888
|
}
|
|
3889
|
+
this.prevProps = {
|
|
3890
|
+
nodes: args.nodes,
|
|
3891
|
+
edges: args.edges,
|
|
3892
|
+
history: args.history,
|
|
3893
|
+
options: args.options
|
|
3894
|
+
};
|
|
3895
|
+
if (this.ingestionConfig) {
|
|
3896
|
+
this.ingest = new Ingest(this);
|
|
3897
|
+
}
|
|
3611
3898
|
}
|
|
3612
3899
|
reset() {
|
|
3613
3900
|
let graph2 = new Graph({ options: this.options.graph });
|
|
@@ -3622,6 +3909,7 @@ var API = class {
|
|
|
3622
3909
|
this.nodeFields = /* @__PURE__ */ new Map();
|
|
3623
3910
|
this.nextNodeId = 1;
|
|
3624
3911
|
this.nextEdgeId = 1;
|
|
3912
|
+
this.canvas?.reset?.();
|
|
3625
3913
|
}
|
|
3626
3914
|
/** Initialize the API */
|
|
3627
3915
|
async init() {
|
|
@@ -3629,6 +3917,49 @@ var API = class {
|
|
|
3629
3917
|
if (!root) throw new Error("root element not found");
|
|
3630
3918
|
root.appendChild(this.canvas.container);
|
|
3631
3919
|
await this.applyHistory();
|
|
3920
|
+
if (this.ingestionConfig && this.ingest) {
|
|
3921
|
+
this.connectIngestion();
|
|
3922
|
+
}
|
|
3923
|
+
if (this.events.onInit) {
|
|
3924
|
+
this.events.onInit();
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
/** Connect to the configured ingestion source */
|
|
3928
|
+
connectIngestion() {
|
|
3929
|
+
if (!this.ingestionConfig || !this.ingest) return;
|
|
3930
|
+
const handleMessage = (msg) => {
|
|
3931
|
+
this.ingest.apply(msg);
|
|
3932
|
+
};
|
|
3933
|
+
switch (this.ingestionConfig.type) {
|
|
3934
|
+
case "websocket":
|
|
3935
|
+
this.ingestionSource = new WebSocketSource(
|
|
3936
|
+
this.ingestionConfig.url,
|
|
3937
|
+
handleMessage,
|
|
3938
|
+
void 0,
|
|
3939
|
+
this.ingestionConfig.reconnectMs
|
|
3940
|
+
);
|
|
3941
|
+
this.ingestionSource.connect();
|
|
3942
|
+
break;
|
|
3943
|
+
case "file":
|
|
3944
|
+
this.ingestionSource = new FileSource(
|
|
3945
|
+
this.ingestionConfig.url,
|
|
3946
|
+
handleMessage,
|
|
3947
|
+
void 0,
|
|
3948
|
+
this.ingestionConfig.intervalMs
|
|
3949
|
+
);
|
|
3950
|
+
this.ingestionSource.connect();
|
|
3951
|
+
break;
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
/** Disconnect from the ingestion source */
|
|
3955
|
+
disconnectIngestion() {
|
|
3956
|
+
if (!this.ingestionSource) return;
|
|
3957
|
+
if (this.ingestionSource instanceof WebSocketSource) {
|
|
3958
|
+
this.ingestionSource.disconnect();
|
|
3959
|
+
} else if (this.ingestionSource instanceof FileSource) {
|
|
3960
|
+
this.ingestionSource.close();
|
|
3961
|
+
}
|
|
3962
|
+
this.ingestionSource = void 0;
|
|
3632
3963
|
}
|
|
3633
3964
|
async applyHistory() {
|
|
3634
3965
|
for (const update of this.history)
|
|
@@ -4084,150 +4415,125 @@ var API = class {
|
|
|
4084
4415
|
else
|
|
4085
4416
|
await this.deleteEdge(edge.data);
|
|
4086
4417
|
}
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4418
|
+
/** Update theme and type styles dynamically */
|
|
4419
|
+
updateStyles(options) {
|
|
4420
|
+
this.canvas?.updateStyles(options);
|
|
4421
|
+
}
|
|
4422
|
+
/** Update color mode without recreating the canvas */
|
|
4423
|
+
setColorMode(colorMode) {
|
|
4424
|
+
this.canvas?.setColorMode(colorMode);
|
|
4093
4425
|
}
|
|
4094
4426
|
/**
|
|
4095
|
-
* Apply
|
|
4096
|
-
*
|
|
4097
|
-
*
|
|
4098
|
-
*
|
|
4427
|
+
* Apply prop changes by diffing against previously applied props.
|
|
4428
|
+
* This is a convenience method for framework wrappers that centralizes
|
|
4429
|
+
* the logic for detecting and applying changes to nodes, edges, history, and options.
|
|
4430
|
+
* The API stores the previous props internally, so you just pass the new props.
|
|
4431
|
+
*
|
|
4432
|
+
* @param props - The new props to apply
|
|
4099
4433
|
*/
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
await this.api.update((u) => {
|
|
4108
|
-
if (msg.addNodes) u.addNodes(...msg.addNodes);
|
|
4109
|
-
if (msg.removeNodes) u.deleteNodes(...msg.removeNodes);
|
|
4110
|
-
if (msg.updateNodes) u.updateNodes(...msg.updateNodes);
|
|
4111
|
-
if (msg.addEdges) u.addEdges(...msg.addEdges);
|
|
4112
|
-
if (msg.removeEdges) u.deleteEdges(...msg.removeEdges);
|
|
4113
|
-
if (msg.updateEdges) u.updateEdges(...msg.updateEdges);
|
|
4114
|
-
if (msg.description) u.describe(msg.description);
|
|
4115
|
-
});
|
|
4116
|
-
break;
|
|
4117
|
-
}
|
|
4118
|
-
case "history": {
|
|
4119
|
-
await this.api.replaceHistory(msg.frames);
|
|
4120
|
-
break;
|
|
4434
|
+
applyProps(props) {
|
|
4435
|
+
const prev = this.prevProps;
|
|
4436
|
+
const nodesChanged = !shallowEqualArray(props.nodes, prev.nodes);
|
|
4437
|
+
const edgesChanged = !shallowEqualArray(props.edges, prev.edges);
|
|
4438
|
+
if (nodesChanged || edgesChanged) {
|
|
4439
|
+
if (props.nodes) {
|
|
4440
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4121
4441
|
}
|
|
4122
4442
|
}
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
this.closedByUser = false;
|
|
4145
|
-
this.connectStartTime = Date.now();
|
|
4146
|
-
this.startTotalTimeout();
|
|
4147
|
-
this.open();
|
|
4148
|
-
}
|
|
4149
|
-
disconnect() {
|
|
4150
|
-
this.closedByUser = true;
|
|
4151
|
-
this.clearTotalTimeout();
|
|
4152
|
-
if (this.ws) {
|
|
4153
|
-
try {
|
|
4154
|
-
this.ws.close();
|
|
4155
|
-
} catch {
|
|
4443
|
+
if (!nodesChanged && !edgesChanged && props.history !== prev.history) {
|
|
4444
|
+
if (props.history === void 0) {
|
|
4445
|
+
if (props.nodes) {
|
|
4446
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4447
|
+
}
|
|
4448
|
+
} else if (prev.history && isHistoryPrefix(prev.history, props.history)) {
|
|
4449
|
+
const prevLength = prev.history.length;
|
|
4450
|
+
const newFrames = props.history.slice(prevLength);
|
|
4451
|
+
for (const frame of newFrames) {
|
|
4452
|
+
this.update((u) => {
|
|
4453
|
+
if (frame.addNodes) u.addNodes(...frame.addNodes);
|
|
4454
|
+
if (frame.removeNodes) u.deleteNodes(...frame.removeNodes);
|
|
4455
|
+
if (frame.updateNodes) u.updateNodes(...frame.updateNodes);
|
|
4456
|
+
if (frame.addEdges) u.addEdges(...frame.addEdges);
|
|
4457
|
+
if (frame.removeEdges) u.deleteEdges(...frame.removeEdges);
|
|
4458
|
+
if (frame.updateEdges) u.updateEdges(...frame.updateEdges);
|
|
4459
|
+
if (frame.description) u.describe(frame.description);
|
|
4460
|
+
});
|
|
4461
|
+
}
|
|
4462
|
+
} else {
|
|
4463
|
+
this.replaceHistory(props.history);
|
|
4156
4464
|
}
|
|
4157
|
-
this.ws = null;
|
|
4158
4465
|
}
|
|
4159
|
-
|
|
4466
|
+
const prevCanvas = prev.options?.canvas;
|
|
4467
|
+
const currCanvas = props.options?.canvas;
|
|
4468
|
+
const colorModeChanged = prevCanvas?.colorMode !== currCanvas?.colorMode;
|
|
4469
|
+
if (colorModeChanged && currCanvas?.colorMode) {
|
|
4470
|
+
this.setColorMode(currCanvas.colorMode);
|
|
4471
|
+
}
|
|
4472
|
+
const themeChanged = prevCanvas?.theme !== currCanvas?.theme;
|
|
4473
|
+
const nodeTypesChanged = prevCanvas?.nodeTypes !== currCanvas?.nodeTypes;
|
|
4474
|
+
const edgeTypesChanged = prevCanvas?.edgeTypes !== currCanvas?.edgeTypes;
|
|
4475
|
+
if (themeChanged || nodeTypesChanged || edgeTypesChanged) {
|
|
4476
|
+
this.updateStyles({
|
|
4477
|
+
theme: currCanvas?.theme,
|
|
4478
|
+
nodeTypes: currCanvas?.nodeTypes,
|
|
4479
|
+
edgeTypes: currCanvas?.edgeTypes
|
|
4480
|
+
});
|
|
4481
|
+
}
|
|
4482
|
+
this.prevProps = {
|
|
4483
|
+
nodes: props.nodes,
|
|
4484
|
+
edges: props.edges,
|
|
4485
|
+
history: props.history,
|
|
4486
|
+
options: props.options
|
|
4487
|
+
};
|
|
4160
4488
|
}
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
this.
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4489
|
+
/** Cleanup resources when the graph is destroyed */
|
|
4490
|
+
destroy() {
|
|
4491
|
+
this.disconnectIngestion();
|
|
4492
|
+
this.canvas?.destroy();
|
|
4493
|
+
}
|
|
4494
|
+
};
|
|
4495
|
+
function shallowEqualArray(a, b) {
|
|
4496
|
+
if (a === b) return true;
|
|
4497
|
+
if (!a || !b) return false;
|
|
4498
|
+
if (a.length !== b.length) return false;
|
|
4499
|
+
for (let i = 0; i < a.length; i++) {
|
|
4500
|
+
if (a[i] !== b[i]) {
|
|
4501
|
+
if (typeof a[i] === "object" && a[i] !== null && typeof b[i] === "object" && b[i] !== null) {
|
|
4502
|
+
const aObj = a[i];
|
|
4503
|
+
const bObj = b[i];
|
|
4504
|
+
const aKeys = Object.keys(aObj);
|
|
4505
|
+
const bKeys = Object.keys(bObj);
|
|
4506
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
4507
|
+
for (const key of aKeys) {
|
|
4508
|
+
if (aObj[key] !== bObj[key]) return false;
|
|
4172
4509
|
}
|
|
4173
|
-
|
|
4174
|
-
|
|
4510
|
+
} else {
|
|
4511
|
+
return false;
|
|
4175
4512
|
}
|
|
4176
|
-
}, this.totalTimeoutMs);
|
|
4177
|
-
}
|
|
4178
|
-
clearTotalTimeout() {
|
|
4179
|
-
if (this.totalTimeoutTimer !== null) {
|
|
4180
|
-
clearTimeout(this.totalTimeoutTimer);
|
|
4181
|
-
this.totalTimeoutTimer = null;
|
|
4182
4513
|
}
|
|
4183
|
-
this.connectStartTime = null;
|
|
4184
4514
|
}
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
return;
|
|
4515
|
+
return true;
|
|
4516
|
+
}
|
|
4517
|
+
function isHistoryPrefix(oldHistory, newHistory) {
|
|
4518
|
+
if (newHistory.length < oldHistory.length) return false;
|
|
4519
|
+
for (let i = 0; i < oldHistory.length; i++) {
|
|
4520
|
+
if (!shallowEqualUpdate(oldHistory[i], newHistory[i])) {
|
|
4521
|
+
return false;
|
|
4193
4522
|
}
|
|
4194
|
-
this.onStatus?.(this.ws ? "reconnecting" : "connecting");
|
|
4195
|
-
const ws = new WebSocket(this.url);
|
|
4196
|
-
this.ws = ws;
|
|
4197
|
-
ws.onopen = () => {
|
|
4198
|
-
this.clearTotalTimeout();
|
|
4199
|
-
this.onStatus?.("connected");
|
|
4200
|
-
};
|
|
4201
|
-
ws.onerror = (e) => {
|
|
4202
|
-
this.onStatus?.("error", e);
|
|
4203
|
-
};
|
|
4204
|
-
ws.onclose = () => {
|
|
4205
|
-
if (this.closedByUser) {
|
|
4206
|
-
this.onStatus?.("closed");
|
|
4207
|
-
return;
|
|
4208
|
-
}
|
|
4209
|
-
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
4210
|
-
this.closedByUser = true;
|
|
4211
|
-
this.clearTotalTimeout();
|
|
4212
|
-
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
4213
|
-
return;
|
|
4214
|
-
}
|
|
4215
|
-
this.onStatus?.("reconnecting");
|
|
4216
|
-
setTimeout(() => this.open(), this.reconnectMs);
|
|
4217
|
-
};
|
|
4218
|
-
ws.onmessage = (ev) => {
|
|
4219
|
-
const data = typeof ev.data === "string" ? ev.data : "";
|
|
4220
|
-
const lines = data.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4221
|
-
for (const line of lines) {
|
|
4222
|
-
try {
|
|
4223
|
-
const obj = JSON.parse(line);
|
|
4224
|
-
this.onMessage(obj);
|
|
4225
|
-
} catch {
|
|
4226
|
-
}
|
|
4227
|
-
}
|
|
4228
|
-
};
|
|
4229
4523
|
}
|
|
4230
|
-
|
|
4524
|
+
return true;
|
|
4525
|
+
}
|
|
4526
|
+
function shallowEqualUpdate(a, b) {
|
|
4527
|
+
if (a === b) return true;
|
|
4528
|
+
if (a.description !== b.description) return false;
|
|
4529
|
+
if (!shallowEqualArray(a.addNodes, b.addNodes)) return false;
|
|
4530
|
+
if (!shallowEqualArray(a.removeNodes, b.removeNodes)) return false;
|
|
4531
|
+
if (!shallowEqualArray(a.updateNodes, b.updateNodes)) return false;
|
|
4532
|
+
if (!shallowEqualArray(a.addEdges, b.addEdges)) return false;
|
|
4533
|
+
if (!shallowEqualArray(a.removeEdges, b.removeEdges)) return false;
|
|
4534
|
+
if (!shallowEqualArray(a.updateEdges, b.updateEdges)) return false;
|
|
4535
|
+
return true;
|
|
4536
|
+
}
|
|
4231
4537
|
|
|
4232
4538
|
// src/api/sources/FileSystemSource.ts
|
|
4233
4539
|
var FileSystemSource = class {
|
|
@@ -4291,88 +4597,13 @@ var FileSystemSource = class {
|
|
|
4291
4597
|
}
|
|
4292
4598
|
};
|
|
4293
4599
|
|
|
4294
|
-
// src/api/sources/FileSource.ts
|
|
4295
|
-
var FileSource = class {
|
|
4296
|
-
url;
|
|
4297
|
-
onMessage;
|
|
4298
|
-
onStatus;
|
|
4299
|
-
timer = null;
|
|
4300
|
-
lastETag = null;
|
|
4301
|
-
lastContent = "";
|
|
4302
|
-
intervalMs = 1e3;
|
|
4303
|
-
closed = false;
|
|
4304
|
-
constructor(url, onMessage, onStatus, intervalMs = 1e3) {
|
|
4305
|
-
this.url = url;
|
|
4306
|
-
this.onMessage = onMessage;
|
|
4307
|
-
this.onStatus = onStatus;
|
|
4308
|
-
this.intervalMs = intervalMs;
|
|
4309
|
-
}
|
|
4310
|
-
async connect() {
|
|
4311
|
-
this.closed = false;
|
|
4312
|
-
this.lastETag = null;
|
|
4313
|
-
this.lastContent = "";
|
|
4314
|
-
this.onStatus?.("opened");
|
|
4315
|
-
this.startPolling();
|
|
4316
|
-
}
|
|
4317
|
-
close() {
|
|
4318
|
-
this.closed = true;
|
|
4319
|
-
if (this.timer) {
|
|
4320
|
-
window.clearInterval(this.timer);
|
|
4321
|
-
this.timer = null;
|
|
4322
|
-
}
|
|
4323
|
-
this.onStatus?.("closed");
|
|
4324
|
-
}
|
|
4325
|
-
startPolling() {
|
|
4326
|
-
if (this.timer) window.clearInterval(this.timer);
|
|
4327
|
-
this.timer = window.setInterval(() => this.poll(), this.intervalMs);
|
|
4328
|
-
this.poll();
|
|
4329
|
-
}
|
|
4330
|
-
async poll() {
|
|
4331
|
-
if (this.closed) return;
|
|
4332
|
-
try {
|
|
4333
|
-
this.onStatus?.("reading");
|
|
4334
|
-
const headers = {};
|
|
4335
|
-
if (this.lastETag) {
|
|
4336
|
-
headers["If-None-Match"] = this.lastETag;
|
|
4337
|
-
}
|
|
4338
|
-
const response = await fetch(this.url, { headers });
|
|
4339
|
-
if (response.status === 304) {
|
|
4340
|
-
return;
|
|
4341
|
-
}
|
|
4342
|
-
if (!response.ok) {
|
|
4343
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
4344
|
-
}
|
|
4345
|
-
const etag = response.headers.get("ETag");
|
|
4346
|
-
if (etag) {
|
|
4347
|
-
this.lastETag = etag;
|
|
4348
|
-
}
|
|
4349
|
-
const content = await response.text();
|
|
4350
|
-
if (content === this.lastContent) {
|
|
4351
|
-
return;
|
|
4352
|
-
}
|
|
4353
|
-
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4354
|
-
const lastContentLines = this.lastContent.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4355
|
-
const newLines = lines.slice(lastContentLines.length);
|
|
4356
|
-
for (const line of newLines) {
|
|
4357
|
-
try {
|
|
4358
|
-
const obj = JSON.parse(line);
|
|
4359
|
-
this.onMessage(obj);
|
|
4360
|
-
} catch {
|
|
4361
|
-
}
|
|
4362
|
-
}
|
|
4363
|
-
this.lastContent = content;
|
|
4364
|
-
} catch (e) {
|
|
4365
|
-
this.onStatus?.("error", e);
|
|
4366
|
-
}
|
|
4367
|
-
}
|
|
4368
|
-
};
|
|
4369
|
-
|
|
4370
4600
|
// src/playground/playground.ts
|
|
4371
4601
|
import styles2 from "./styles.css?raw";
|
|
4372
4602
|
var Playground = class {
|
|
4373
4603
|
options;
|
|
4374
4604
|
rootElement;
|
|
4375
4605
|
currentExample;
|
|
4606
|
+
examples;
|
|
4376
4607
|
currentGraph = null;
|
|
4377
4608
|
ingest = null;
|
|
4378
4609
|
isEditable = false;
|
|
@@ -4390,7 +4621,8 @@ var Playground = class {
|
|
|
4390
4621
|
graphContainerId;
|
|
4391
4622
|
constructor(options) {
|
|
4392
4623
|
this.options = options;
|
|
4393
|
-
this.
|
|
4624
|
+
this.examples = { ...options.examples };
|
|
4625
|
+
this.exampleList = Object.keys(this.examples);
|
|
4394
4626
|
this.currentExample = options.defaultExample || this.exampleList[0];
|
|
4395
4627
|
this.graphContainerId = `playground-graph-${Math.random().toString(36).substr(2, 9)}`;
|
|
4396
4628
|
if (typeof options.root === "string") {
|
|
@@ -4421,7 +4653,7 @@ var Playground = class {
|
|
|
4421
4653
|
}
|
|
4422
4654
|
createDOM() {
|
|
4423
4655
|
const exampleList = this.exampleList.map((key, i) => {
|
|
4424
|
-
const example = this.
|
|
4656
|
+
const example = this.examples[key];
|
|
4425
4657
|
const isActive = i === 0 || key === this.currentExample;
|
|
4426
4658
|
return `
|
|
4427
4659
|
<button class="example-btn ${isActive ? "active" : ""}" data-example="${key}">
|
|
@@ -4507,7 +4739,14 @@ var Playground = class {
|
|
|
4507
4739
|
});
|
|
4508
4740
|
});
|
|
4509
4741
|
this.rootElement.querySelectorAll(".options select, .options input").forEach((el) => {
|
|
4510
|
-
el.addEventListener("change", () =>
|
|
4742
|
+
el.addEventListener("change", () => {
|
|
4743
|
+
if (el.id === "colorMode" && this.currentGraph) {
|
|
4744
|
+
const mode = el.value;
|
|
4745
|
+
this.currentGraph.setColorMode(mode);
|
|
4746
|
+
} else {
|
|
4747
|
+
this.renderGraph();
|
|
4748
|
+
}
|
|
4749
|
+
});
|
|
4511
4750
|
});
|
|
4512
4751
|
this.rootElement.querySelector("#nav-first")?.addEventListener("click", () => {
|
|
4513
4752
|
this.currentGraph?.nav("first");
|
|
@@ -4594,8 +4833,10 @@ var Playground = class {
|
|
|
4594
4833
|
async renderGraph() {
|
|
4595
4834
|
const container = this.rootElement.querySelector(`#${this.graphContainerId}`);
|
|
4596
4835
|
if (!container) return;
|
|
4836
|
+
this.currentGraph?.destroy();
|
|
4837
|
+
this.currentGraph = null;
|
|
4597
4838
|
container.innerHTML = "";
|
|
4598
|
-
const example = this.
|
|
4839
|
+
const example = this.examples[this.currentExample];
|
|
4599
4840
|
const options = this.getOptions(example.options);
|
|
4600
4841
|
try {
|
|
4601
4842
|
this.currentGraph = await graph({
|
|
@@ -4626,7 +4867,7 @@ var Playground = class {
|
|
|
4626
4867
|
}
|
|
4627
4868
|
}
|
|
4628
4869
|
connectExampleSource() {
|
|
4629
|
-
const example = this.
|
|
4870
|
+
const example = this.examples[this.currentExample];
|
|
4630
4871
|
if (!example.source) {
|
|
4631
4872
|
this.disconnectAllSources();
|
|
4632
4873
|
return;
|
|
@@ -4969,7 +5210,7 @@ var Playground = class {
|
|
|
4969
5210
|
`;
|
|
4970
5211
|
}
|
|
4971
5212
|
} else if (this.activeSourceType === "file") {
|
|
4972
|
-
const example = this.
|
|
5213
|
+
const example = this.examples[this.currentExample];
|
|
4973
5214
|
const filePath = example.source?.type === "file" ? example.source.path : "";
|
|
4974
5215
|
if (this.fileStatus === "connecting") {
|
|
4975
5216
|
statusDiv.innerHTML = `
|
|
@@ -5067,6 +5308,58 @@ var Playground = class {
|
|
|
5067
5308
|
this.fsSource?.close();
|
|
5068
5309
|
this.updateSourceModal();
|
|
5069
5310
|
}
|
|
5311
|
+
/**
|
|
5312
|
+
* Add or update an example
|
|
5313
|
+
*/
|
|
5314
|
+
addExample(key, example) {
|
|
5315
|
+
this.examples[key] = example;
|
|
5316
|
+
this.updateExampleList();
|
|
5317
|
+
if (this.currentExample === key) {
|
|
5318
|
+
this.renderGraph();
|
|
5319
|
+
this.connectExampleSource();
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
/**
|
|
5323
|
+
* Remove an example
|
|
5324
|
+
*/
|
|
5325
|
+
removeExample(key) {
|
|
5326
|
+
delete this.examples[key];
|
|
5327
|
+
if (this.currentExample === key) {
|
|
5328
|
+
this.exampleList = Object.keys(this.examples);
|
|
5329
|
+
this.currentExample = this.exampleList[0] || "";
|
|
5330
|
+
if (this.currentExample) {
|
|
5331
|
+
this.renderGraph();
|
|
5332
|
+
this.connectExampleSource();
|
|
5333
|
+
}
|
|
5334
|
+
}
|
|
5335
|
+
this.updateExampleList();
|
|
5336
|
+
}
|
|
5337
|
+
/**
|
|
5338
|
+
* Update the example list in the DOM
|
|
5339
|
+
*/
|
|
5340
|
+
updateExampleList() {
|
|
5341
|
+
this.exampleList = Object.keys(this.examples);
|
|
5342
|
+
const exampleListEl = this.rootElement.querySelector(".example-list");
|
|
5343
|
+
if (!exampleListEl) return;
|
|
5344
|
+
exampleListEl.innerHTML = this.exampleList.map((key, i) => {
|
|
5345
|
+
const example = this.examples[key];
|
|
5346
|
+
const isActive = key === this.currentExample;
|
|
5347
|
+
return `
|
|
5348
|
+
<button class="example-btn ${isActive ? "active" : ""}" data-example="${key}">
|
|
5349
|
+
${example.name}
|
|
5350
|
+
</button>
|
|
5351
|
+
`;
|
|
5352
|
+
}).join("");
|
|
5353
|
+
exampleListEl.querySelectorAll(".example-btn").forEach((btn) => {
|
|
5354
|
+
btn.addEventListener("click", () => {
|
|
5355
|
+
this.rootElement.querySelectorAll(".example-btn").forEach((b) => b.classList.remove("active"));
|
|
5356
|
+
btn.classList.add("active");
|
|
5357
|
+
this.currentExample = btn.getAttribute("data-example") || this.exampleList[0];
|
|
5358
|
+
this.renderGraph();
|
|
5359
|
+
this.connectExampleSource();
|
|
5360
|
+
});
|
|
5361
|
+
});
|
|
5362
|
+
}
|
|
5070
5363
|
};
|
|
5071
5364
|
|
|
5072
5365
|
// src/index.ts
|
|
@@ -5081,6 +5374,7 @@ export {
|
|
|
5081
5374
|
FileSystemSource,
|
|
5082
5375
|
Ingest,
|
|
5083
5376
|
Playground,
|
|
5377
|
+
Updater,
|
|
5084
5378
|
WebSocketSource,
|
|
5085
5379
|
index_default as default,
|
|
5086
5380
|
graph
|