@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.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
FileSystemSource: () => FileSystemSource,
|
|
35
35
|
Ingest: () => Ingest,
|
|
36
36
|
Playground: () => Playground,
|
|
37
|
+
Updater: () => Updater,
|
|
37
38
|
WebSocketSource: () => WebSocketSource,
|
|
38
39
|
default: () => index_default,
|
|
39
40
|
graph: () => graph
|
|
@@ -1380,15 +1381,16 @@ var Layout = class _Layout {
|
|
|
1380
1381
|
|
|
1381
1382
|
// src/canvas/marker.tsx
|
|
1382
1383
|
var import_jsx_runtime = require("jsx-dom/jsx-runtime");
|
|
1383
|
-
function arrow(size, reverse = false) {
|
|
1384
|
+
function arrow(size, reverse = false, prefix = "") {
|
|
1384
1385
|
const h = size / 1.5;
|
|
1385
1386
|
const w = size;
|
|
1386
1387
|
const ry = h / 2;
|
|
1387
1388
|
const suffix = reverse ? "-reverse" : "";
|
|
1389
|
+
const id = prefix ? `${prefix}-g3p-marker-arrow${suffix}` : `g3p-marker-arrow${suffix}`;
|
|
1388
1390
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1389
1391
|
"marker",
|
|
1390
1392
|
{
|
|
1391
|
-
id
|
|
1393
|
+
id,
|
|
1392
1394
|
className: "g3p-marker g3p-marker-arrow",
|
|
1393
1395
|
markerWidth: size,
|
|
1394
1396
|
markerHeight: size,
|
|
@@ -1400,14 +1402,15 @@ function arrow(size, reverse = false) {
|
|
|
1400
1402
|
}
|
|
1401
1403
|
);
|
|
1402
1404
|
}
|
|
1403
|
-
function circle(size, reverse = false) {
|
|
1405
|
+
function circle(size, reverse = false, prefix = "") {
|
|
1404
1406
|
const r = size / 3;
|
|
1405
1407
|
const cy = size / 2;
|
|
1406
1408
|
const suffix = reverse ? "-reverse" : "";
|
|
1409
|
+
const id = prefix ? `${prefix}-g3p-marker-circle${suffix}` : `g3p-marker-circle${suffix}`;
|
|
1407
1410
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1408
1411
|
"marker",
|
|
1409
1412
|
{
|
|
1410
|
-
id
|
|
1413
|
+
id,
|
|
1411
1414
|
className: "g3p-marker g3p-marker-circle",
|
|
1412
1415
|
markerWidth: size,
|
|
1413
1416
|
markerHeight: size,
|
|
@@ -1419,15 +1422,16 @@ function circle(size, reverse = false) {
|
|
|
1419
1422
|
}
|
|
1420
1423
|
);
|
|
1421
1424
|
}
|
|
1422
|
-
function diamond(size, reverse = false) {
|
|
1425
|
+
function diamond(size, reverse = false, prefix = "") {
|
|
1423
1426
|
const w = size * 0.7;
|
|
1424
1427
|
const h = size / 2;
|
|
1425
1428
|
const cy = size / 2;
|
|
1426
1429
|
const suffix = reverse ? "-reverse" : "";
|
|
1430
|
+
const id = prefix ? `${prefix}-g3p-marker-diamond${suffix}` : `g3p-marker-diamond${suffix}`;
|
|
1427
1431
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1428
1432
|
"marker",
|
|
1429
1433
|
{
|
|
1430
|
-
id
|
|
1434
|
+
id,
|
|
1431
1435
|
className: "g3p-marker g3p-marker-diamond",
|
|
1432
1436
|
markerWidth: size,
|
|
1433
1437
|
markerHeight: size,
|
|
@@ -1439,14 +1443,15 @@ function diamond(size, reverse = false) {
|
|
|
1439
1443
|
}
|
|
1440
1444
|
);
|
|
1441
1445
|
}
|
|
1442
|
-
function bar(size, reverse = false) {
|
|
1446
|
+
function bar(size, reverse = false, prefix = "") {
|
|
1443
1447
|
const h = size * 0.6;
|
|
1444
1448
|
const cy = size / 2;
|
|
1445
1449
|
const suffix = reverse ? "-reverse" : "";
|
|
1450
|
+
const id = prefix ? `${prefix}-g3p-marker-bar${suffix}` : `g3p-marker-bar${suffix}`;
|
|
1446
1451
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1447
1452
|
"marker",
|
|
1448
1453
|
{
|
|
1449
|
-
id
|
|
1454
|
+
id,
|
|
1450
1455
|
className: "g3p-marker g3p-marker-bar",
|
|
1451
1456
|
markerWidth: size,
|
|
1452
1457
|
markerHeight: size,
|
|
@@ -1458,7 +1463,7 @@ function bar(size, reverse = false) {
|
|
|
1458
1463
|
}
|
|
1459
1464
|
);
|
|
1460
1465
|
}
|
|
1461
|
-
function none(size, reverse = false) {
|
|
1466
|
+
function none(size, reverse = false, prefix = "") {
|
|
1462
1467
|
return void 0;
|
|
1463
1468
|
}
|
|
1464
1469
|
function normalize(data) {
|
|
@@ -2240,6 +2245,9 @@ var Seg2 = class {
|
|
|
2240
2245
|
if (this.source.isDummy) source = void 0;
|
|
2241
2246
|
if (this.target.isDummy) target = void 0;
|
|
2242
2247
|
const typeClass = this.type ? `g3p-edge-type-${this.type}` : "";
|
|
2248
|
+
const prefix = this.canvas.markerPrefix;
|
|
2249
|
+
const markerStartId = source ? prefix ? `${prefix}-g3p-marker-${source}-reverse` : `g3p-marker-${source}-reverse` : void 0;
|
|
2250
|
+
const markerEndId = target ? prefix ? `${prefix}-g3p-marker-${target}` : `g3p-marker-${target}` : void 0;
|
|
2243
2251
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2244
2252
|
"g",
|
|
2245
2253
|
{
|
|
@@ -2254,8 +2262,8 @@ var Seg2 = class {
|
|
|
2254
2262
|
d: this.svg,
|
|
2255
2263
|
fill: "none",
|
|
2256
2264
|
className: "g3p-seg-line",
|
|
2257
|
-
markerStart:
|
|
2258
|
-
markerEnd:
|
|
2265
|
+
markerStart: markerStartId ? `url(#${markerStartId})` : void 0,
|
|
2266
|
+
markerEnd: markerEndId ? `url(#${markerEndId})` : void 0
|
|
2259
2267
|
}
|
|
2260
2268
|
),
|
|
2261
2269
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -2840,6 +2848,10 @@ var Canvas = class {
|
|
|
2840
2848
|
curNodes;
|
|
2841
2849
|
curSegs;
|
|
2842
2850
|
updating;
|
|
2851
|
+
// Unique marker ID prefix for this canvas instance
|
|
2852
|
+
markerPrefix;
|
|
2853
|
+
// Dynamic style element for this instance (for cleanup)
|
|
2854
|
+
dynamicStyleEl;
|
|
2843
2855
|
// Pan-zoom state
|
|
2844
2856
|
panScale = null;
|
|
2845
2857
|
zoomControls;
|
|
@@ -2853,6 +2865,13 @@ var Canvas = class {
|
|
|
2853
2865
|
constructor(api, options) {
|
|
2854
2866
|
Object.assign(this, options);
|
|
2855
2867
|
this.api = api;
|
|
2868
|
+
this.reset();
|
|
2869
|
+
this.markerPrefix = api.root.replace(/[^a-zA-Z0-9-_]/g, "-");
|
|
2870
|
+
this.createMeasurementContainer();
|
|
2871
|
+
this.createCanvasContainer();
|
|
2872
|
+
if (this.panZoom) this.setupPanZoom();
|
|
2873
|
+
}
|
|
2874
|
+
reset() {
|
|
2856
2875
|
this.allNodes = /* @__PURE__ */ new Map();
|
|
2857
2876
|
this.curNodes = /* @__PURE__ */ new Map();
|
|
2858
2877
|
this.curSegs = /* @__PURE__ */ new Map();
|
|
@@ -2861,9 +2880,7 @@ var Canvas = class {
|
|
|
2861
2880
|
this.transform = { x: 0, y: 0, scale: 1 };
|
|
2862
2881
|
this.editMode = new EditMode();
|
|
2863
2882
|
this.editMode.editable = this.editable;
|
|
2864
|
-
this.
|
|
2865
|
-
this.createCanvasContainer();
|
|
2866
|
-
if (this.panZoom) this.setupPanZoom();
|
|
2883
|
+
if (this.group) this.group.innerHTML = "";
|
|
2867
2884
|
}
|
|
2868
2885
|
createMeasurementContainer() {
|
|
2869
2886
|
this.measurement = document.createElement("div");
|
|
@@ -3048,12 +3065,13 @@ var Canvas = class {
|
|
|
3048
3065
|
}
|
|
3049
3066
|
generateDynamicStyles() {
|
|
3050
3067
|
let css = "";
|
|
3051
|
-
|
|
3068
|
+
const scope = `[data-g3p-instance="${this.markerPrefix}"]`;
|
|
3069
|
+
css += themeToCSS(this.theme, scope);
|
|
3052
3070
|
for (const [type, vars] of Object.entries(this.nodeTypes)) {
|
|
3053
|
-
css += themeToCSS(vars,
|
|
3071
|
+
css += themeToCSS(vars, `${scope} .g3p-node-type-${type}`, "node");
|
|
3054
3072
|
}
|
|
3055
3073
|
for (const [type, vars] of Object.entries(this.edgeTypes)) {
|
|
3056
|
-
css += themeToCSS(vars,
|
|
3074
|
+
css += themeToCSS(vars, `${scope} .g3p-edge-type-${type}`);
|
|
3057
3075
|
}
|
|
3058
3076
|
return css;
|
|
3059
3077
|
}
|
|
@@ -3066,15 +3084,18 @@ var Canvas = class {
|
|
|
3066
3084
|
}
|
|
3067
3085
|
const dynamicStyles = this.generateDynamicStyles();
|
|
3068
3086
|
if (dynamicStyles) {
|
|
3069
|
-
|
|
3070
|
-
dynamicStyleEl
|
|
3071
|
-
|
|
3087
|
+
this.dynamicStyleEl?.remove();
|
|
3088
|
+
this.dynamicStyleEl = document.createElement("style");
|
|
3089
|
+
this.dynamicStyleEl.id = `g3p-styles-${this.markerPrefix}`;
|
|
3090
|
+
this.dynamicStyleEl.textContent = dynamicStyles;
|
|
3091
|
+
document.head.appendChild(this.dynamicStyleEl);
|
|
3072
3092
|
}
|
|
3073
3093
|
const colorModeClass = this.colorMode !== "system" ? `g3p-${this.colorMode}` : "";
|
|
3074
3094
|
this.container = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3075
3095
|
"div",
|
|
3076
3096
|
{
|
|
3077
3097
|
className: `g3p-canvas-container ${colorModeClass}`.trim(),
|
|
3098
|
+
"data-g3p-instance": this.markerPrefix,
|
|
3078
3099
|
ref: (el) => this.container = el,
|
|
3079
3100
|
onContextMenu: this.onContextMenu.bind(this),
|
|
3080
3101
|
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
@@ -3090,8 +3111,8 @@ var Canvas = class {
|
|
|
3090
3111
|
onDblClick: this.onDoubleClick.bind(this),
|
|
3091
3112
|
children: [
|
|
3092
3113
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("defs", { children: [
|
|
3093
|
-
Object.values(markerDefs).map((marker) => marker(this.markerSize, false)),
|
|
3094
|
-
Object.values(markerDefs).map((marker) => marker(this.markerSize, true))
|
|
3114
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, false, this.markerPrefix)),
|
|
3115
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, true, this.markerPrefix))
|
|
3095
3116
|
] }),
|
|
3096
3117
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3097
3118
|
"g",
|
|
@@ -3448,6 +3469,40 @@ var Canvas = class {
|
|
|
3448
3469
|
}
|
|
3449
3470
|
return { type: "canvas" };
|
|
3450
3471
|
}
|
|
3472
|
+
/** Update theme and type styles dynamically */
|
|
3473
|
+
updateStyles(options) {
|
|
3474
|
+
if (options.theme !== void 0) this.theme = options.theme;
|
|
3475
|
+
if (options.nodeTypes !== void 0) this.nodeTypes = options.nodeTypes;
|
|
3476
|
+
if (options.edgeTypes !== void 0) this.edgeTypes = options.edgeTypes;
|
|
3477
|
+
const dynamicStyles = this.generateDynamicStyles();
|
|
3478
|
+
if (dynamicStyles) {
|
|
3479
|
+
if (!this.dynamicStyleEl) {
|
|
3480
|
+
this.dynamicStyleEl = document.createElement("style");
|
|
3481
|
+
this.dynamicStyleEl.id = `g3p-styles-${this.markerPrefix}`;
|
|
3482
|
+
document.head.appendChild(this.dynamicStyleEl);
|
|
3483
|
+
}
|
|
3484
|
+
this.dynamicStyleEl.textContent = dynamicStyles;
|
|
3485
|
+
} else if (this.dynamicStyleEl) {
|
|
3486
|
+
this.dynamicStyleEl.remove();
|
|
3487
|
+
this.dynamicStyleEl = void 0;
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
/** Update color mode without recreating the canvas */
|
|
3491
|
+
setColorMode(colorMode) {
|
|
3492
|
+
if (!this.container) return;
|
|
3493
|
+
this.colorMode = colorMode;
|
|
3494
|
+
this.container.classList.remove("g3p-light", "g3p-dark");
|
|
3495
|
+
if (colorMode !== "system") {
|
|
3496
|
+
this.container.classList.add(`g3p-${colorMode}`);
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
/** Cleanup resources when the canvas is destroyed */
|
|
3500
|
+
destroy() {
|
|
3501
|
+
this.dynamicStyleEl?.remove();
|
|
3502
|
+
this.dynamicStyleEl = void 0;
|
|
3503
|
+
this.measurement?.remove();
|
|
3504
|
+
this.measurement = void 0;
|
|
3505
|
+
}
|
|
3451
3506
|
};
|
|
3452
3507
|
var themeVarMap = {
|
|
3453
3508
|
// Canvas
|
|
@@ -3614,6 +3669,225 @@ var Updater = class _Updater {
|
|
|
3614
3669
|
}
|
|
3615
3670
|
};
|
|
3616
3671
|
|
|
3672
|
+
// src/api/ingest.ts
|
|
3673
|
+
var Ingest = class {
|
|
3674
|
+
constructor(api) {
|
|
3675
|
+
this.api = api;
|
|
3676
|
+
}
|
|
3677
|
+
/**
|
|
3678
|
+
* Apply an incoming ingest message to the API.
|
|
3679
|
+
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
3680
|
+
* - update: apply incremental update
|
|
3681
|
+
* - history: initialize from a set of frames (clears prior history)
|
|
3682
|
+
*/
|
|
3683
|
+
async apply(msg) {
|
|
3684
|
+
switch (msg.type) {
|
|
3685
|
+
case "snapshot": {
|
|
3686
|
+
await this.api.replaceSnapshot(msg.nodes, msg.edges, msg.description);
|
|
3687
|
+
break;
|
|
3688
|
+
}
|
|
3689
|
+
case "update": {
|
|
3690
|
+
await this.api.update((u) => {
|
|
3691
|
+
if (msg.addNodes) u.addNodes(...msg.addNodes);
|
|
3692
|
+
if (msg.removeNodes) u.deleteNodes(...msg.removeNodes);
|
|
3693
|
+
if (msg.updateNodes) u.updateNodes(...msg.updateNodes);
|
|
3694
|
+
if (msg.addEdges) u.addEdges(...msg.addEdges);
|
|
3695
|
+
if (msg.removeEdges) u.deleteEdges(...msg.removeEdges);
|
|
3696
|
+
if (msg.updateEdges) u.updateEdges(...msg.updateEdges);
|
|
3697
|
+
if (msg.description) u.describe(msg.description);
|
|
3698
|
+
});
|
|
3699
|
+
break;
|
|
3700
|
+
}
|
|
3701
|
+
case "history": {
|
|
3702
|
+
await this.api.replaceHistory(msg.frames);
|
|
3703
|
+
break;
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
};
|
|
3708
|
+
|
|
3709
|
+
// src/api/sources/WebSocketSource.ts
|
|
3710
|
+
var WebSocketSource = class {
|
|
3711
|
+
url;
|
|
3712
|
+
ws = null;
|
|
3713
|
+
onMessage;
|
|
3714
|
+
onStatus;
|
|
3715
|
+
reconnectMs;
|
|
3716
|
+
closedByUser = false;
|
|
3717
|
+
connectStartTime = null;
|
|
3718
|
+
totalTimeoutMs = 1e4;
|
|
3719
|
+
totalTimeoutTimer = null;
|
|
3720
|
+
constructor(url, onMessage, onStatus, reconnectMs = 1500) {
|
|
3721
|
+
this.url = url;
|
|
3722
|
+
this.onMessage = onMessage;
|
|
3723
|
+
this.onStatus = onStatus;
|
|
3724
|
+
this.reconnectMs = reconnectMs;
|
|
3725
|
+
}
|
|
3726
|
+
connect() {
|
|
3727
|
+
this.closedByUser = false;
|
|
3728
|
+
this.connectStartTime = Date.now();
|
|
3729
|
+
this.startTotalTimeout();
|
|
3730
|
+
this.open();
|
|
3731
|
+
}
|
|
3732
|
+
disconnect() {
|
|
3733
|
+
this.closedByUser = true;
|
|
3734
|
+
this.clearTotalTimeout();
|
|
3735
|
+
if (this.ws) {
|
|
3736
|
+
try {
|
|
3737
|
+
this.ws.close();
|
|
3738
|
+
} catch {
|
|
3739
|
+
}
|
|
3740
|
+
this.ws = null;
|
|
3741
|
+
}
|
|
3742
|
+
this.onStatus?.("closed");
|
|
3743
|
+
}
|
|
3744
|
+
startTotalTimeout() {
|
|
3745
|
+
this.clearTotalTimeout();
|
|
3746
|
+
this.totalTimeoutTimer = window.setTimeout(() => {
|
|
3747
|
+
if (!this.closedByUser && this.ws?.readyState !== WebSocket.OPEN) {
|
|
3748
|
+
this.closedByUser = true;
|
|
3749
|
+
if (this.ws) {
|
|
3750
|
+
try {
|
|
3751
|
+
this.ws.close();
|
|
3752
|
+
} catch {
|
|
3753
|
+
}
|
|
3754
|
+
this.ws = null;
|
|
3755
|
+
}
|
|
3756
|
+
this.clearTotalTimeout();
|
|
3757
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3758
|
+
}
|
|
3759
|
+
}, this.totalTimeoutMs);
|
|
3760
|
+
}
|
|
3761
|
+
clearTotalTimeout() {
|
|
3762
|
+
if (this.totalTimeoutTimer !== null) {
|
|
3763
|
+
clearTimeout(this.totalTimeoutTimer);
|
|
3764
|
+
this.totalTimeoutTimer = null;
|
|
3765
|
+
}
|
|
3766
|
+
this.connectStartTime = null;
|
|
3767
|
+
}
|
|
3768
|
+
open() {
|
|
3769
|
+
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
3770
|
+
if (!this.closedByUser) {
|
|
3771
|
+
this.closedByUser = true;
|
|
3772
|
+
this.clearTotalTimeout();
|
|
3773
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3774
|
+
}
|
|
3775
|
+
return;
|
|
3776
|
+
}
|
|
3777
|
+
this.onStatus?.(this.ws ? "reconnecting" : "connecting");
|
|
3778
|
+
const ws = new WebSocket(this.url);
|
|
3779
|
+
this.ws = ws;
|
|
3780
|
+
ws.onopen = () => {
|
|
3781
|
+
this.clearTotalTimeout();
|
|
3782
|
+
this.onStatus?.("connected");
|
|
3783
|
+
};
|
|
3784
|
+
ws.onerror = (e) => {
|
|
3785
|
+
this.onStatus?.("error", e);
|
|
3786
|
+
};
|
|
3787
|
+
ws.onclose = () => {
|
|
3788
|
+
if (this.closedByUser) {
|
|
3789
|
+
this.onStatus?.("closed");
|
|
3790
|
+
return;
|
|
3791
|
+
}
|
|
3792
|
+
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
3793
|
+
this.closedByUser = true;
|
|
3794
|
+
this.clearTotalTimeout();
|
|
3795
|
+
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
3796
|
+
return;
|
|
3797
|
+
}
|
|
3798
|
+
this.onStatus?.("reconnecting");
|
|
3799
|
+
setTimeout(() => this.open(), this.reconnectMs);
|
|
3800
|
+
};
|
|
3801
|
+
ws.onmessage = (ev) => {
|
|
3802
|
+
const data = typeof ev.data === "string" ? ev.data : "";
|
|
3803
|
+
const lines = data.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3804
|
+
for (const line of lines) {
|
|
3805
|
+
try {
|
|
3806
|
+
const obj = JSON.parse(line);
|
|
3807
|
+
this.onMessage(obj);
|
|
3808
|
+
} catch {
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
};
|
|
3812
|
+
}
|
|
3813
|
+
};
|
|
3814
|
+
|
|
3815
|
+
// src/api/sources/FileSource.ts
|
|
3816
|
+
var FileSource = class {
|
|
3817
|
+
url;
|
|
3818
|
+
onMessage;
|
|
3819
|
+
onStatus;
|
|
3820
|
+
timer = null;
|
|
3821
|
+
lastETag = null;
|
|
3822
|
+
lastContent = "";
|
|
3823
|
+
intervalMs = 1e3;
|
|
3824
|
+
closed = false;
|
|
3825
|
+
constructor(url, onMessage, onStatus, intervalMs = 1e3) {
|
|
3826
|
+
this.url = url;
|
|
3827
|
+
this.onMessage = onMessage;
|
|
3828
|
+
this.onStatus = onStatus;
|
|
3829
|
+
this.intervalMs = intervalMs;
|
|
3830
|
+
}
|
|
3831
|
+
async connect() {
|
|
3832
|
+
this.closed = false;
|
|
3833
|
+
this.lastETag = null;
|
|
3834
|
+
this.lastContent = "";
|
|
3835
|
+
this.onStatus?.("opened");
|
|
3836
|
+
this.startPolling();
|
|
3837
|
+
}
|
|
3838
|
+
close() {
|
|
3839
|
+
this.closed = true;
|
|
3840
|
+
if (this.timer) {
|
|
3841
|
+
window.clearInterval(this.timer);
|
|
3842
|
+
this.timer = null;
|
|
3843
|
+
}
|
|
3844
|
+
this.onStatus?.("closed");
|
|
3845
|
+
}
|
|
3846
|
+
startPolling() {
|
|
3847
|
+
if (this.timer) window.clearInterval(this.timer);
|
|
3848
|
+
this.timer = window.setInterval(() => this.poll(), this.intervalMs);
|
|
3849
|
+
this.poll();
|
|
3850
|
+
}
|
|
3851
|
+
async poll() {
|
|
3852
|
+
if (this.closed) return;
|
|
3853
|
+
try {
|
|
3854
|
+
this.onStatus?.("reading");
|
|
3855
|
+
const headers = {};
|
|
3856
|
+
if (this.lastETag) {
|
|
3857
|
+
headers["If-None-Match"] = this.lastETag;
|
|
3858
|
+
}
|
|
3859
|
+
const response = await fetch(this.url, { headers });
|
|
3860
|
+
if (response.status === 304) {
|
|
3861
|
+
return;
|
|
3862
|
+
}
|
|
3863
|
+
if (!response.ok) {
|
|
3864
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
3865
|
+
}
|
|
3866
|
+
const etag = response.headers.get("ETag");
|
|
3867
|
+
if (etag) {
|
|
3868
|
+
this.lastETag = etag;
|
|
3869
|
+
}
|
|
3870
|
+
const content = await response.text();
|
|
3871
|
+
if (content === this.lastContent) {
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3875
|
+
const lastContentLines = this.lastContent.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3876
|
+
const newLines = lines.slice(lastContentLines.length);
|
|
3877
|
+
for (const line of newLines) {
|
|
3878
|
+
try {
|
|
3879
|
+
const obj = JSON.parse(line);
|
|
3880
|
+
this.onMessage(obj);
|
|
3881
|
+
} catch {
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
this.lastContent = content;
|
|
3885
|
+
} catch (e) {
|
|
3886
|
+
this.onStatus?.("error", e);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
};
|
|
3890
|
+
|
|
3617
3891
|
// src/api/api.ts
|
|
3618
3892
|
var log11 = logger("api");
|
|
3619
3893
|
var API = class {
|
|
@@ -3632,11 +3906,16 @@ var API = class {
|
|
|
3632
3906
|
nextNodeId;
|
|
3633
3907
|
nextEdgeId;
|
|
3634
3908
|
events;
|
|
3909
|
+
ingest;
|
|
3910
|
+
ingestionSource;
|
|
3911
|
+
ingestionConfig;
|
|
3912
|
+
prevProps = {};
|
|
3635
3913
|
root;
|
|
3636
3914
|
constructor(args) {
|
|
3637
3915
|
this.root = args.root;
|
|
3638
3916
|
this.options = applyDefaults(args.options);
|
|
3639
3917
|
this.events = args.events || {};
|
|
3918
|
+
this.ingestionConfig = args.ingestion;
|
|
3640
3919
|
this.reset();
|
|
3641
3920
|
this.canvas = new Canvas(this, {
|
|
3642
3921
|
...this.options.canvas,
|
|
@@ -3650,6 +3929,15 @@ var API = class {
|
|
|
3650
3929
|
} else {
|
|
3651
3930
|
this.history = [];
|
|
3652
3931
|
}
|
|
3932
|
+
this.prevProps = {
|
|
3933
|
+
nodes: args.nodes,
|
|
3934
|
+
edges: args.edges,
|
|
3935
|
+
history: args.history,
|
|
3936
|
+
options: args.options
|
|
3937
|
+
};
|
|
3938
|
+
if (this.ingestionConfig) {
|
|
3939
|
+
this.ingest = new Ingest(this);
|
|
3940
|
+
}
|
|
3653
3941
|
}
|
|
3654
3942
|
reset() {
|
|
3655
3943
|
let graph2 = new Graph({ options: this.options.graph });
|
|
@@ -3664,6 +3952,7 @@ var API = class {
|
|
|
3664
3952
|
this.nodeFields = /* @__PURE__ */ new Map();
|
|
3665
3953
|
this.nextNodeId = 1;
|
|
3666
3954
|
this.nextEdgeId = 1;
|
|
3955
|
+
this.canvas?.reset?.();
|
|
3667
3956
|
}
|
|
3668
3957
|
/** Initialize the API */
|
|
3669
3958
|
async init() {
|
|
@@ -3671,6 +3960,49 @@ var API = class {
|
|
|
3671
3960
|
if (!root) throw new Error("root element not found");
|
|
3672
3961
|
root.appendChild(this.canvas.container);
|
|
3673
3962
|
await this.applyHistory();
|
|
3963
|
+
if (this.ingestionConfig && this.ingest) {
|
|
3964
|
+
this.connectIngestion();
|
|
3965
|
+
}
|
|
3966
|
+
if (this.events.onInit) {
|
|
3967
|
+
this.events.onInit();
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
/** Connect to the configured ingestion source */
|
|
3971
|
+
connectIngestion() {
|
|
3972
|
+
if (!this.ingestionConfig || !this.ingest) return;
|
|
3973
|
+
const handleMessage = (msg) => {
|
|
3974
|
+
this.ingest.apply(msg);
|
|
3975
|
+
};
|
|
3976
|
+
switch (this.ingestionConfig.type) {
|
|
3977
|
+
case "websocket":
|
|
3978
|
+
this.ingestionSource = new WebSocketSource(
|
|
3979
|
+
this.ingestionConfig.url,
|
|
3980
|
+
handleMessage,
|
|
3981
|
+
void 0,
|
|
3982
|
+
this.ingestionConfig.reconnectMs
|
|
3983
|
+
);
|
|
3984
|
+
this.ingestionSource.connect();
|
|
3985
|
+
break;
|
|
3986
|
+
case "file":
|
|
3987
|
+
this.ingestionSource = new FileSource(
|
|
3988
|
+
this.ingestionConfig.url,
|
|
3989
|
+
handleMessage,
|
|
3990
|
+
void 0,
|
|
3991
|
+
this.ingestionConfig.intervalMs
|
|
3992
|
+
);
|
|
3993
|
+
this.ingestionSource.connect();
|
|
3994
|
+
break;
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
/** Disconnect from the ingestion source */
|
|
3998
|
+
disconnectIngestion() {
|
|
3999
|
+
if (!this.ingestionSource) return;
|
|
4000
|
+
if (this.ingestionSource instanceof WebSocketSource) {
|
|
4001
|
+
this.ingestionSource.disconnect();
|
|
4002
|
+
} else if (this.ingestionSource instanceof FileSource) {
|
|
4003
|
+
this.ingestionSource.close();
|
|
4004
|
+
}
|
|
4005
|
+
this.ingestionSource = void 0;
|
|
3674
4006
|
}
|
|
3675
4007
|
async applyHistory() {
|
|
3676
4008
|
for (const update of this.history)
|
|
@@ -4126,150 +4458,125 @@ var API = class {
|
|
|
4126
4458
|
else
|
|
4127
4459
|
await this.deleteEdge(edge.data);
|
|
4128
4460
|
}
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4461
|
+
/** Update theme and type styles dynamically */
|
|
4462
|
+
updateStyles(options) {
|
|
4463
|
+
this.canvas?.updateStyles(options);
|
|
4464
|
+
}
|
|
4465
|
+
/** Update color mode without recreating the canvas */
|
|
4466
|
+
setColorMode(colorMode) {
|
|
4467
|
+
this.canvas?.setColorMode(colorMode);
|
|
4135
4468
|
}
|
|
4136
4469
|
/**
|
|
4137
|
-
* Apply
|
|
4138
|
-
*
|
|
4139
|
-
*
|
|
4140
|
-
*
|
|
4470
|
+
* Apply prop changes by diffing against previously applied props.
|
|
4471
|
+
* This is a convenience method for framework wrappers that centralizes
|
|
4472
|
+
* the logic for detecting and applying changes to nodes, edges, history, and options.
|
|
4473
|
+
* The API stores the previous props internally, so you just pass the new props.
|
|
4474
|
+
*
|
|
4475
|
+
* @param props - The new props to apply
|
|
4141
4476
|
*/
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
await this.api.update((u) => {
|
|
4150
|
-
if (msg.addNodes) u.addNodes(...msg.addNodes);
|
|
4151
|
-
if (msg.removeNodes) u.deleteNodes(...msg.removeNodes);
|
|
4152
|
-
if (msg.updateNodes) u.updateNodes(...msg.updateNodes);
|
|
4153
|
-
if (msg.addEdges) u.addEdges(...msg.addEdges);
|
|
4154
|
-
if (msg.removeEdges) u.deleteEdges(...msg.removeEdges);
|
|
4155
|
-
if (msg.updateEdges) u.updateEdges(...msg.updateEdges);
|
|
4156
|
-
if (msg.description) u.describe(msg.description);
|
|
4157
|
-
});
|
|
4158
|
-
break;
|
|
4159
|
-
}
|
|
4160
|
-
case "history": {
|
|
4161
|
-
await this.api.replaceHistory(msg.frames);
|
|
4162
|
-
break;
|
|
4477
|
+
applyProps(props) {
|
|
4478
|
+
const prev = this.prevProps;
|
|
4479
|
+
const nodesChanged = !shallowEqualArray(props.nodes, prev.nodes);
|
|
4480
|
+
const edgesChanged = !shallowEqualArray(props.edges, prev.edges);
|
|
4481
|
+
if (nodesChanged || edgesChanged) {
|
|
4482
|
+
if (props.nodes) {
|
|
4483
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4163
4484
|
}
|
|
4164
4485
|
}
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
this.closedByUser = false;
|
|
4187
|
-
this.connectStartTime = Date.now();
|
|
4188
|
-
this.startTotalTimeout();
|
|
4189
|
-
this.open();
|
|
4190
|
-
}
|
|
4191
|
-
disconnect() {
|
|
4192
|
-
this.closedByUser = true;
|
|
4193
|
-
this.clearTotalTimeout();
|
|
4194
|
-
if (this.ws) {
|
|
4195
|
-
try {
|
|
4196
|
-
this.ws.close();
|
|
4197
|
-
} catch {
|
|
4486
|
+
if (!nodesChanged && !edgesChanged && props.history !== prev.history) {
|
|
4487
|
+
if (props.history === void 0) {
|
|
4488
|
+
if (props.nodes) {
|
|
4489
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4490
|
+
}
|
|
4491
|
+
} else if (prev.history && isHistoryPrefix(prev.history, props.history)) {
|
|
4492
|
+
const prevLength = prev.history.length;
|
|
4493
|
+
const newFrames = props.history.slice(prevLength);
|
|
4494
|
+
for (const frame of newFrames) {
|
|
4495
|
+
this.update((u) => {
|
|
4496
|
+
if (frame.addNodes) u.addNodes(...frame.addNodes);
|
|
4497
|
+
if (frame.removeNodes) u.deleteNodes(...frame.removeNodes);
|
|
4498
|
+
if (frame.updateNodes) u.updateNodes(...frame.updateNodes);
|
|
4499
|
+
if (frame.addEdges) u.addEdges(...frame.addEdges);
|
|
4500
|
+
if (frame.removeEdges) u.deleteEdges(...frame.removeEdges);
|
|
4501
|
+
if (frame.updateEdges) u.updateEdges(...frame.updateEdges);
|
|
4502
|
+
if (frame.description) u.describe(frame.description);
|
|
4503
|
+
});
|
|
4504
|
+
}
|
|
4505
|
+
} else {
|
|
4506
|
+
this.replaceHistory(props.history);
|
|
4198
4507
|
}
|
|
4199
|
-
this.ws = null;
|
|
4200
4508
|
}
|
|
4201
|
-
|
|
4509
|
+
const prevCanvas = prev.options?.canvas;
|
|
4510
|
+
const currCanvas = props.options?.canvas;
|
|
4511
|
+
const colorModeChanged = prevCanvas?.colorMode !== currCanvas?.colorMode;
|
|
4512
|
+
if (colorModeChanged && currCanvas?.colorMode) {
|
|
4513
|
+
this.setColorMode(currCanvas.colorMode);
|
|
4514
|
+
}
|
|
4515
|
+
const themeChanged = prevCanvas?.theme !== currCanvas?.theme;
|
|
4516
|
+
const nodeTypesChanged = prevCanvas?.nodeTypes !== currCanvas?.nodeTypes;
|
|
4517
|
+
const edgeTypesChanged = prevCanvas?.edgeTypes !== currCanvas?.edgeTypes;
|
|
4518
|
+
if (themeChanged || nodeTypesChanged || edgeTypesChanged) {
|
|
4519
|
+
this.updateStyles({
|
|
4520
|
+
theme: currCanvas?.theme,
|
|
4521
|
+
nodeTypes: currCanvas?.nodeTypes,
|
|
4522
|
+
edgeTypes: currCanvas?.edgeTypes
|
|
4523
|
+
});
|
|
4524
|
+
}
|
|
4525
|
+
this.prevProps = {
|
|
4526
|
+
nodes: props.nodes,
|
|
4527
|
+
edges: props.edges,
|
|
4528
|
+
history: props.history,
|
|
4529
|
+
options: props.options
|
|
4530
|
+
};
|
|
4202
4531
|
}
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
this.
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4532
|
+
/** Cleanup resources when the graph is destroyed */
|
|
4533
|
+
destroy() {
|
|
4534
|
+
this.disconnectIngestion();
|
|
4535
|
+
this.canvas?.destroy();
|
|
4536
|
+
}
|
|
4537
|
+
};
|
|
4538
|
+
function shallowEqualArray(a, b) {
|
|
4539
|
+
if (a === b) return true;
|
|
4540
|
+
if (!a || !b) return false;
|
|
4541
|
+
if (a.length !== b.length) return false;
|
|
4542
|
+
for (let i = 0; i < a.length; i++) {
|
|
4543
|
+
if (a[i] !== b[i]) {
|
|
4544
|
+
if (typeof a[i] === "object" && a[i] !== null && typeof b[i] === "object" && b[i] !== null) {
|
|
4545
|
+
const aObj = a[i];
|
|
4546
|
+
const bObj = b[i];
|
|
4547
|
+
const aKeys = Object.keys(aObj);
|
|
4548
|
+
const bKeys = Object.keys(bObj);
|
|
4549
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
4550
|
+
for (const key of aKeys) {
|
|
4551
|
+
if (aObj[key] !== bObj[key]) return false;
|
|
4214
4552
|
}
|
|
4215
|
-
|
|
4216
|
-
|
|
4553
|
+
} else {
|
|
4554
|
+
return false;
|
|
4217
4555
|
}
|
|
4218
|
-
}, this.totalTimeoutMs);
|
|
4219
|
-
}
|
|
4220
|
-
clearTotalTimeout() {
|
|
4221
|
-
if (this.totalTimeoutTimer !== null) {
|
|
4222
|
-
clearTimeout(this.totalTimeoutTimer);
|
|
4223
|
-
this.totalTimeoutTimer = null;
|
|
4224
4556
|
}
|
|
4225
|
-
this.connectStartTime = null;
|
|
4226
4557
|
}
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
return;
|
|
4558
|
+
return true;
|
|
4559
|
+
}
|
|
4560
|
+
function isHistoryPrefix(oldHistory, newHistory) {
|
|
4561
|
+
if (newHistory.length < oldHistory.length) return false;
|
|
4562
|
+
for (let i = 0; i < oldHistory.length; i++) {
|
|
4563
|
+
if (!shallowEqualUpdate(oldHistory[i], newHistory[i])) {
|
|
4564
|
+
return false;
|
|
4235
4565
|
}
|
|
4236
|
-
this.onStatus?.(this.ws ? "reconnecting" : "connecting");
|
|
4237
|
-
const ws = new WebSocket(this.url);
|
|
4238
|
-
this.ws = ws;
|
|
4239
|
-
ws.onopen = () => {
|
|
4240
|
-
this.clearTotalTimeout();
|
|
4241
|
-
this.onStatus?.("connected");
|
|
4242
|
-
};
|
|
4243
|
-
ws.onerror = (e) => {
|
|
4244
|
-
this.onStatus?.("error", e);
|
|
4245
|
-
};
|
|
4246
|
-
ws.onclose = () => {
|
|
4247
|
-
if (this.closedByUser) {
|
|
4248
|
-
this.onStatus?.("closed");
|
|
4249
|
-
return;
|
|
4250
|
-
}
|
|
4251
|
-
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
4252
|
-
this.closedByUser = true;
|
|
4253
|
-
this.clearTotalTimeout();
|
|
4254
|
-
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
4255
|
-
return;
|
|
4256
|
-
}
|
|
4257
|
-
this.onStatus?.("reconnecting");
|
|
4258
|
-
setTimeout(() => this.open(), this.reconnectMs);
|
|
4259
|
-
};
|
|
4260
|
-
ws.onmessage = (ev) => {
|
|
4261
|
-
const data = typeof ev.data === "string" ? ev.data : "";
|
|
4262
|
-
const lines = data.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4263
|
-
for (const line of lines) {
|
|
4264
|
-
try {
|
|
4265
|
-
const obj = JSON.parse(line);
|
|
4266
|
-
this.onMessage(obj);
|
|
4267
|
-
} catch {
|
|
4268
|
-
}
|
|
4269
|
-
}
|
|
4270
|
-
};
|
|
4271
4566
|
}
|
|
4272
|
-
|
|
4567
|
+
return true;
|
|
4568
|
+
}
|
|
4569
|
+
function shallowEqualUpdate(a, b) {
|
|
4570
|
+
if (a === b) return true;
|
|
4571
|
+
if (a.description !== b.description) return false;
|
|
4572
|
+
if (!shallowEqualArray(a.addNodes, b.addNodes)) return false;
|
|
4573
|
+
if (!shallowEqualArray(a.removeNodes, b.removeNodes)) return false;
|
|
4574
|
+
if (!shallowEqualArray(a.updateNodes, b.updateNodes)) return false;
|
|
4575
|
+
if (!shallowEqualArray(a.addEdges, b.addEdges)) return false;
|
|
4576
|
+
if (!shallowEqualArray(a.removeEdges, b.removeEdges)) return false;
|
|
4577
|
+
if (!shallowEqualArray(a.updateEdges, b.updateEdges)) return false;
|
|
4578
|
+
return true;
|
|
4579
|
+
}
|
|
4273
4580
|
|
|
4274
4581
|
// src/api/sources/FileSystemSource.ts
|
|
4275
4582
|
var FileSystemSource = class {
|
|
@@ -4333,88 +4640,13 @@ var FileSystemSource = class {
|
|
|
4333
4640
|
}
|
|
4334
4641
|
};
|
|
4335
4642
|
|
|
4336
|
-
// src/api/sources/FileSource.ts
|
|
4337
|
-
var FileSource = class {
|
|
4338
|
-
url;
|
|
4339
|
-
onMessage;
|
|
4340
|
-
onStatus;
|
|
4341
|
-
timer = null;
|
|
4342
|
-
lastETag = null;
|
|
4343
|
-
lastContent = "";
|
|
4344
|
-
intervalMs = 1e3;
|
|
4345
|
-
closed = false;
|
|
4346
|
-
constructor(url, onMessage, onStatus, intervalMs = 1e3) {
|
|
4347
|
-
this.url = url;
|
|
4348
|
-
this.onMessage = onMessage;
|
|
4349
|
-
this.onStatus = onStatus;
|
|
4350
|
-
this.intervalMs = intervalMs;
|
|
4351
|
-
}
|
|
4352
|
-
async connect() {
|
|
4353
|
-
this.closed = false;
|
|
4354
|
-
this.lastETag = null;
|
|
4355
|
-
this.lastContent = "";
|
|
4356
|
-
this.onStatus?.("opened");
|
|
4357
|
-
this.startPolling();
|
|
4358
|
-
}
|
|
4359
|
-
close() {
|
|
4360
|
-
this.closed = true;
|
|
4361
|
-
if (this.timer) {
|
|
4362
|
-
window.clearInterval(this.timer);
|
|
4363
|
-
this.timer = null;
|
|
4364
|
-
}
|
|
4365
|
-
this.onStatus?.("closed");
|
|
4366
|
-
}
|
|
4367
|
-
startPolling() {
|
|
4368
|
-
if (this.timer) window.clearInterval(this.timer);
|
|
4369
|
-
this.timer = window.setInterval(() => this.poll(), this.intervalMs);
|
|
4370
|
-
this.poll();
|
|
4371
|
-
}
|
|
4372
|
-
async poll() {
|
|
4373
|
-
if (this.closed) return;
|
|
4374
|
-
try {
|
|
4375
|
-
this.onStatus?.("reading");
|
|
4376
|
-
const headers = {};
|
|
4377
|
-
if (this.lastETag) {
|
|
4378
|
-
headers["If-None-Match"] = this.lastETag;
|
|
4379
|
-
}
|
|
4380
|
-
const response = await fetch(this.url, { headers });
|
|
4381
|
-
if (response.status === 304) {
|
|
4382
|
-
return;
|
|
4383
|
-
}
|
|
4384
|
-
if (!response.ok) {
|
|
4385
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
4386
|
-
}
|
|
4387
|
-
const etag = response.headers.get("ETag");
|
|
4388
|
-
if (etag) {
|
|
4389
|
-
this.lastETag = etag;
|
|
4390
|
-
}
|
|
4391
|
-
const content = await response.text();
|
|
4392
|
-
if (content === this.lastContent) {
|
|
4393
|
-
return;
|
|
4394
|
-
}
|
|
4395
|
-
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4396
|
-
const lastContentLines = this.lastContent.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4397
|
-
const newLines = lines.slice(lastContentLines.length);
|
|
4398
|
-
for (const line of newLines) {
|
|
4399
|
-
try {
|
|
4400
|
-
const obj = JSON.parse(line);
|
|
4401
|
-
this.onMessage(obj);
|
|
4402
|
-
} catch {
|
|
4403
|
-
}
|
|
4404
|
-
}
|
|
4405
|
-
this.lastContent = content;
|
|
4406
|
-
} catch (e) {
|
|
4407
|
-
this.onStatus?.("error", e);
|
|
4408
|
-
}
|
|
4409
|
-
}
|
|
4410
|
-
};
|
|
4411
|
-
|
|
4412
4643
|
// src/playground/playground.ts
|
|
4413
4644
|
var import_styles2 = __toESM(require("./styles.css?raw"), 1);
|
|
4414
4645
|
var Playground = class {
|
|
4415
4646
|
options;
|
|
4416
4647
|
rootElement;
|
|
4417
4648
|
currentExample;
|
|
4649
|
+
examples;
|
|
4418
4650
|
currentGraph = null;
|
|
4419
4651
|
ingest = null;
|
|
4420
4652
|
isEditable = false;
|
|
@@ -4432,7 +4664,8 @@ var Playground = class {
|
|
|
4432
4664
|
graphContainerId;
|
|
4433
4665
|
constructor(options) {
|
|
4434
4666
|
this.options = options;
|
|
4435
|
-
this.
|
|
4667
|
+
this.examples = { ...options.examples };
|
|
4668
|
+
this.exampleList = Object.keys(this.examples);
|
|
4436
4669
|
this.currentExample = options.defaultExample || this.exampleList[0];
|
|
4437
4670
|
this.graphContainerId = `playground-graph-${Math.random().toString(36).substr(2, 9)}`;
|
|
4438
4671
|
if (typeof options.root === "string") {
|
|
@@ -4463,7 +4696,7 @@ var Playground = class {
|
|
|
4463
4696
|
}
|
|
4464
4697
|
createDOM() {
|
|
4465
4698
|
const exampleList = this.exampleList.map((key, i) => {
|
|
4466
|
-
const example = this.
|
|
4699
|
+
const example = this.examples[key];
|
|
4467
4700
|
const isActive = i === 0 || key === this.currentExample;
|
|
4468
4701
|
return `
|
|
4469
4702
|
<button class="example-btn ${isActive ? "active" : ""}" data-example="${key}">
|
|
@@ -4549,7 +4782,14 @@ var Playground = class {
|
|
|
4549
4782
|
});
|
|
4550
4783
|
});
|
|
4551
4784
|
this.rootElement.querySelectorAll(".options select, .options input").forEach((el) => {
|
|
4552
|
-
el.addEventListener("change", () =>
|
|
4785
|
+
el.addEventListener("change", () => {
|
|
4786
|
+
if (el.id === "colorMode" && this.currentGraph) {
|
|
4787
|
+
const mode = el.value;
|
|
4788
|
+
this.currentGraph.setColorMode(mode);
|
|
4789
|
+
} else {
|
|
4790
|
+
this.renderGraph();
|
|
4791
|
+
}
|
|
4792
|
+
});
|
|
4553
4793
|
});
|
|
4554
4794
|
this.rootElement.querySelector("#nav-first")?.addEventListener("click", () => {
|
|
4555
4795
|
this.currentGraph?.nav("first");
|
|
@@ -4636,8 +4876,10 @@ var Playground = class {
|
|
|
4636
4876
|
async renderGraph() {
|
|
4637
4877
|
const container = this.rootElement.querySelector(`#${this.graphContainerId}`);
|
|
4638
4878
|
if (!container) return;
|
|
4879
|
+
this.currentGraph?.destroy();
|
|
4880
|
+
this.currentGraph = null;
|
|
4639
4881
|
container.innerHTML = "";
|
|
4640
|
-
const example = this.
|
|
4882
|
+
const example = this.examples[this.currentExample];
|
|
4641
4883
|
const options = this.getOptions(example.options);
|
|
4642
4884
|
try {
|
|
4643
4885
|
this.currentGraph = await graph({
|
|
@@ -4668,7 +4910,7 @@ var Playground = class {
|
|
|
4668
4910
|
}
|
|
4669
4911
|
}
|
|
4670
4912
|
connectExampleSource() {
|
|
4671
|
-
const example = this.
|
|
4913
|
+
const example = this.examples[this.currentExample];
|
|
4672
4914
|
if (!example.source) {
|
|
4673
4915
|
this.disconnectAllSources();
|
|
4674
4916
|
return;
|
|
@@ -5011,7 +5253,7 @@ var Playground = class {
|
|
|
5011
5253
|
`;
|
|
5012
5254
|
}
|
|
5013
5255
|
} else if (this.activeSourceType === "file") {
|
|
5014
|
-
const example = this.
|
|
5256
|
+
const example = this.examples[this.currentExample];
|
|
5015
5257
|
const filePath = example.source?.type === "file" ? example.source.path : "";
|
|
5016
5258
|
if (this.fileStatus === "connecting") {
|
|
5017
5259
|
statusDiv.innerHTML = `
|
|
@@ -5109,6 +5351,58 @@ var Playground = class {
|
|
|
5109
5351
|
this.fsSource?.close();
|
|
5110
5352
|
this.updateSourceModal();
|
|
5111
5353
|
}
|
|
5354
|
+
/**
|
|
5355
|
+
* Add or update an example
|
|
5356
|
+
*/
|
|
5357
|
+
addExample(key, example) {
|
|
5358
|
+
this.examples[key] = example;
|
|
5359
|
+
this.updateExampleList();
|
|
5360
|
+
if (this.currentExample === key) {
|
|
5361
|
+
this.renderGraph();
|
|
5362
|
+
this.connectExampleSource();
|
|
5363
|
+
}
|
|
5364
|
+
}
|
|
5365
|
+
/**
|
|
5366
|
+
* Remove an example
|
|
5367
|
+
*/
|
|
5368
|
+
removeExample(key) {
|
|
5369
|
+
delete this.examples[key];
|
|
5370
|
+
if (this.currentExample === key) {
|
|
5371
|
+
this.exampleList = Object.keys(this.examples);
|
|
5372
|
+
this.currentExample = this.exampleList[0] || "";
|
|
5373
|
+
if (this.currentExample) {
|
|
5374
|
+
this.renderGraph();
|
|
5375
|
+
this.connectExampleSource();
|
|
5376
|
+
}
|
|
5377
|
+
}
|
|
5378
|
+
this.updateExampleList();
|
|
5379
|
+
}
|
|
5380
|
+
/**
|
|
5381
|
+
* Update the example list in the DOM
|
|
5382
|
+
*/
|
|
5383
|
+
updateExampleList() {
|
|
5384
|
+
this.exampleList = Object.keys(this.examples);
|
|
5385
|
+
const exampleListEl = this.rootElement.querySelector(".example-list");
|
|
5386
|
+
if (!exampleListEl) return;
|
|
5387
|
+
exampleListEl.innerHTML = this.exampleList.map((key, i) => {
|
|
5388
|
+
const example = this.examples[key];
|
|
5389
|
+
const isActive = key === this.currentExample;
|
|
5390
|
+
return `
|
|
5391
|
+
<button class="example-btn ${isActive ? "active" : ""}" data-example="${key}">
|
|
5392
|
+
${example.name}
|
|
5393
|
+
</button>
|
|
5394
|
+
`;
|
|
5395
|
+
}).join("");
|
|
5396
|
+
exampleListEl.querySelectorAll(".example-btn").forEach((btn) => {
|
|
5397
|
+
btn.addEventListener("click", () => {
|
|
5398
|
+
this.rootElement.querySelectorAll(".example-btn").forEach((b) => b.classList.remove("active"));
|
|
5399
|
+
btn.classList.add("active");
|
|
5400
|
+
this.currentExample = btn.getAttribute("data-example") || this.exampleList[0];
|
|
5401
|
+
this.renderGraph();
|
|
5402
|
+
this.connectExampleSource();
|
|
5403
|
+
});
|
|
5404
|
+
});
|
|
5405
|
+
}
|
|
5112
5406
|
};
|
|
5113
5407
|
|
|
5114
5408
|
// src/index.ts
|
|
@@ -5124,6 +5418,7 @@ var index_default = graph;
|
|
|
5124
5418
|
FileSystemSource,
|
|
5125
5419
|
Ingest,
|
|
5126
5420
|
Playground,
|
|
5421
|
+
Updater,
|
|
5127
5422
|
WebSocketSource,
|
|
5128
5423
|
graph
|
|
5129
5424
|
});
|