@3plate/graph-core 0.1.7 → 0.1.10
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 +511 -345
- package/dist/index.d.cts +78 -7
- package/dist/index.d.ts +78 -7
- package/dist/index.js +511 -345
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3626,6 +3626,287 @@ var Updater = class _Updater {
|
|
|
3626
3626
|
}
|
|
3627
3627
|
};
|
|
3628
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(args) {
|
|
3678
|
+
this.url = args.url;
|
|
3679
|
+
this.onMessage = args.onMessage;
|
|
3680
|
+
this.onStatus = args.onStatus;
|
|
3681
|
+
this.reconnectMs = args.reconnectMs ?? 1500;
|
|
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(args) {
|
|
3783
|
+
this.url = args.url;
|
|
3784
|
+
this.onMessage = args.onMessage;
|
|
3785
|
+
this.onStatus = args.onStatus;
|
|
3786
|
+
this.intervalMs = args.intervalMs ?? 1e3;
|
|
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
|
+
|
|
3848
|
+
// src/api/sources/FileSystemSource.ts
|
|
3849
|
+
var FileSystemSource = class {
|
|
3850
|
+
handle = null;
|
|
3851
|
+
onMessage;
|
|
3852
|
+
onStatus;
|
|
3853
|
+
timer = null;
|
|
3854
|
+
lastSize = 0;
|
|
3855
|
+
filename;
|
|
3856
|
+
intervalMs;
|
|
3857
|
+
constructor(args) {
|
|
3858
|
+
this.filename = args.filename;
|
|
3859
|
+
this.onMessage = args.onMessage;
|
|
3860
|
+
this.onStatus = args.onStatus;
|
|
3861
|
+
this.intervalMs = args.intervalMs ?? 1e3;
|
|
3862
|
+
}
|
|
3863
|
+
async openDirectory() {
|
|
3864
|
+
try {
|
|
3865
|
+
const dir = await window.showDirectoryPicker?.();
|
|
3866
|
+
if (!dir) throw new Error("File System Access not supported or cancelled");
|
|
3867
|
+
const handle = await dir.getFileHandle(this.filename, { create: false });
|
|
3868
|
+
this.handle = handle;
|
|
3869
|
+
this.onStatus?.("opened", { file: this.filename });
|
|
3870
|
+
this.lastSize = 0;
|
|
3871
|
+
this.startPolling();
|
|
3872
|
+
} catch (e) {
|
|
3873
|
+
this.onStatus?.("error", e);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
close() {
|
|
3877
|
+
if (this.timer) {
|
|
3878
|
+
window.clearInterval(this.timer);
|
|
3879
|
+
this.timer = null;
|
|
3880
|
+
}
|
|
3881
|
+
this.handle = null;
|
|
3882
|
+
this.onStatus?.("closed");
|
|
3883
|
+
}
|
|
3884
|
+
startPolling() {
|
|
3885
|
+
if (this.timer) window.clearInterval(this.timer);
|
|
3886
|
+
this.timer = window.setInterval(() => this.readNewLines(), this.intervalMs);
|
|
3887
|
+
}
|
|
3888
|
+
async readNewLines() {
|
|
3889
|
+
try {
|
|
3890
|
+
if (!this.handle) return;
|
|
3891
|
+
this.onStatus?.("reading");
|
|
3892
|
+
const file = await this.handle.getFile();
|
|
3893
|
+
if (file.size === this.lastSize) return;
|
|
3894
|
+
const slice = await file.slice(this.lastSize).text();
|
|
3895
|
+
this.lastSize = file.size;
|
|
3896
|
+
const lines = slice.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3897
|
+
for (const line of lines) {
|
|
3898
|
+
try {
|
|
3899
|
+
const obj = JSON.parse(line);
|
|
3900
|
+
this.onMessage(obj);
|
|
3901
|
+
} catch {
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
} catch (e) {
|
|
3905
|
+
this.onStatus?.("error", e);
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
};
|
|
3909
|
+
|
|
3629
3910
|
// src/api/api.ts
|
|
3630
3911
|
var log11 = logger("api");
|
|
3631
3912
|
var API = class {
|
|
@@ -3644,11 +3925,16 @@ var API = class {
|
|
|
3644
3925
|
nextNodeId;
|
|
3645
3926
|
nextEdgeId;
|
|
3646
3927
|
events;
|
|
3928
|
+
ingest;
|
|
3929
|
+
ingestionSource;
|
|
3930
|
+
ingestionConfig;
|
|
3931
|
+
prevProps = {};
|
|
3647
3932
|
root;
|
|
3648
3933
|
constructor(args) {
|
|
3649
3934
|
this.root = args.root;
|
|
3650
3935
|
this.options = applyDefaults(args.options);
|
|
3651
3936
|
this.events = args.events || {};
|
|
3937
|
+
this.ingestionConfig = args.ingestion;
|
|
3652
3938
|
this.reset();
|
|
3653
3939
|
this.canvas = new Canvas(this, {
|
|
3654
3940
|
...this.options.canvas,
|
|
@@ -3662,6 +3948,15 @@ var API = class {
|
|
|
3662
3948
|
} else {
|
|
3663
3949
|
this.history = [];
|
|
3664
3950
|
}
|
|
3951
|
+
this.prevProps = {
|
|
3952
|
+
nodes: args.nodes,
|
|
3953
|
+
edges: args.edges,
|
|
3954
|
+
history: args.history,
|
|
3955
|
+
options: args.options
|
|
3956
|
+
};
|
|
3957
|
+
if (this.ingestionConfig) {
|
|
3958
|
+
this.ingest = new Ingest(this);
|
|
3959
|
+
}
|
|
3665
3960
|
}
|
|
3666
3961
|
reset() {
|
|
3667
3962
|
let graph2 = new Graph({ options: this.options.graph });
|
|
@@ -3684,10 +3979,40 @@ var API = class {
|
|
|
3684
3979
|
if (!root) throw new Error("root element not found");
|
|
3685
3980
|
root.appendChild(this.canvas.container);
|
|
3686
3981
|
await this.applyHistory();
|
|
3982
|
+
if (this.ingestionConfig && this.ingest) {
|
|
3983
|
+
this.connectIngestion();
|
|
3984
|
+
}
|
|
3687
3985
|
if (this.events.onInit) {
|
|
3688
3986
|
this.events.onInit();
|
|
3689
3987
|
}
|
|
3690
3988
|
}
|
|
3989
|
+
/** Connect to the configured ingestion source */
|
|
3990
|
+
connectIngestion() {
|
|
3991
|
+
if (!this.ingestionConfig || !this.ingest) return;
|
|
3992
|
+
const args = {
|
|
3993
|
+
...this.ingestionConfig,
|
|
3994
|
+
onMessage: (msg) => {
|
|
3995
|
+
this.ingest.apply(msg);
|
|
3996
|
+
}
|
|
3997
|
+
};
|
|
3998
|
+
const source = {
|
|
3999
|
+
"websocket": WebSocketSource,
|
|
4000
|
+
"file": FileSource,
|
|
4001
|
+
"filesystem": FileSystemSource
|
|
4002
|
+
}[this.ingestionConfig.type];
|
|
4003
|
+
this.ingestionSource = new source[this.ingestionConfig.type](args);
|
|
4004
|
+
this.ingestionSource?.connect();
|
|
4005
|
+
}
|
|
4006
|
+
/** Disconnect from the ingestion source */
|
|
4007
|
+
disconnectIngestion() {
|
|
4008
|
+
if (!this.ingestionSource) return;
|
|
4009
|
+
if (this.ingestionSource instanceof WebSocketSource) {
|
|
4010
|
+
this.ingestionSource.disconnect();
|
|
4011
|
+
} else if (this.ingestionSource instanceof FileSource) {
|
|
4012
|
+
this.ingestionSource.close();
|
|
4013
|
+
}
|
|
4014
|
+
this.ingestionSource = void 0;
|
|
4015
|
+
}
|
|
3691
4016
|
async applyHistory() {
|
|
3692
4017
|
for (const update of this.history)
|
|
3693
4018
|
await this.applyUpdate(update);
|
|
@@ -4074,368 +4399,193 @@ var API = class {
|
|
|
4074
4399
|
else {
|
|
4075
4400
|
this.canvas.showEditNodeModal(node, async (data) => {
|
|
4076
4401
|
if (this.events.updateNode)
|
|
4077
|
-
this.events.updateNode(node.data, data, gotNode);
|
|
4078
|
-
else {
|
|
4079
|
-
this.nodeOverrides.set(node.data, data);
|
|
4080
|
-
await gotNode(node.data);
|
|
4081
|
-
}
|
|
4082
|
-
});
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4085
|
-
async handleEditEdge(id) {
|
|
4086
|
-
const seg = this.graph.getSeg(id);
|
|
4087
|
-
if (seg.edgeIds.size != 1) return;
|
|
4088
|
-
const edge = this.graph.getEdge(seg.edgeIds.values().next().value);
|
|
4089
|
-
const gotEdge = async (edge2) => {
|
|
4090
|
-
if (edge2) await this.updateEdge(edge2);
|
|
4091
|
-
};
|
|
4092
|
-
if (this.events.editEdge)
|
|
4093
|
-
this.events.editEdge(edge.data, gotEdge);
|
|
4094
|
-
else
|
|
4095
|
-
this.canvas.showEditEdgeModal(edge, async (data) => {
|
|
4096
|
-
const sourceNode = edge.sourceNode(this.graph);
|
|
4097
|
-
const targetNode = edge.targetNode(this.graph);
|
|
4098
|
-
const update = {
|
|
4099
|
-
source: { node: sourceNode.data, port: data.source.port, marker: data.source.marker },
|
|
4100
|
-
target: { node: targetNode.data, port: data.target.port, marker: data.target.marker }
|
|
4101
|
-
};
|
|
4102
|
-
if (this.events.updateEdge)
|
|
4103
|
-
this.events.updateEdge(edge.data, update, gotEdge);
|
|
4104
|
-
else {
|
|
4105
|
-
this.edgeOverrides.set(edge.data, {
|
|
4106
|
-
source: { id: sourceNode.id, port: data.source.port, marker: data.source.marker },
|
|
4107
|
-
target: { id: targetNode.id, port: data.target.port, marker: data.target.marker },
|
|
4108
|
-
type: data.type
|
|
4109
|
-
});
|
|
4110
|
-
await gotEdge(edge.data);
|
|
4111
|
-
}
|
|
4112
|
-
});
|
|
4113
|
-
}
|
|
4114
|
-
async handleAddEdge(data) {
|
|
4115
|
-
const gotEdge = async (edge) => {
|
|
4116
|
-
if (edge) await this.addEdge(edge);
|
|
4117
|
-
};
|
|
4118
|
-
const newEdge = {
|
|
4119
|
-
source: { node: this.graph.getNode(data.source.id).data, port: data.source.port, marker: data.source.marker },
|
|
4120
|
-
target: { node: this.graph.getNode(data.target.id).data, port: data.target.port, marker: data.target.marker }
|
|
4121
|
-
};
|
|
4122
|
-
if (this.events.addEdge)
|
|
4123
|
-
this.events.addEdge(newEdge, gotEdge);
|
|
4124
|
-
else
|
|
4125
|
-
await gotEdge(data);
|
|
4126
|
-
}
|
|
4127
|
-
async handleDeleteNode(id) {
|
|
4128
|
-
const node = this.getNode(id);
|
|
4129
|
-
if (this.events.removeNode)
|
|
4130
|
-
this.events.removeNode(node.data, async (remove) => {
|
|
4131
|
-
if (remove) await this.deleteNode(node.data);
|
|
4132
|
-
});
|
|
4133
|
-
else
|
|
4134
|
-
await this.deleteNode(node.data);
|
|
4135
|
-
}
|
|
4136
|
-
async handleDeleteEdge(id) {
|
|
4137
|
-
const edge = this.getEdge(id);
|
|
4138
|
-
if (this.events.removeEdge)
|
|
4139
|
-
this.events.removeEdge(edge.data, async (remove) => {
|
|
4140
|
-
if (remove) await this.deleteEdge(edge.data);
|
|
4141
|
-
});
|
|
4142
|
-
else
|
|
4143
|
-
await this.deleteEdge(edge.data);
|
|
4144
|
-
}
|
|
4145
|
-
/** Update theme and type styles dynamically */
|
|
4146
|
-
updateStyles(options) {
|
|
4147
|
-
this.canvas?.updateStyles(options);
|
|
4148
|
-
}
|
|
4149
|
-
/** Update color mode without recreating the canvas */
|
|
4150
|
-
setColorMode(colorMode) {
|
|
4151
|
-
this.canvas?.setColorMode(colorMode);
|
|
4152
|
-
}
|
|
4153
|
-
/** Cleanup resources when the graph is destroyed */
|
|
4154
|
-
destroy() {
|
|
4155
|
-
this.canvas?.destroy();
|
|
4156
|
-
}
|
|
4157
|
-
};
|
|
4158
|
-
|
|
4159
|
-
// src/api/ingest.ts
|
|
4160
|
-
var Ingest = class {
|
|
4161
|
-
constructor(api) {
|
|
4162
|
-
this.api = api;
|
|
4163
|
-
}
|
|
4164
|
-
/**
|
|
4165
|
-
* Apply an incoming ingest message to the API.
|
|
4166
|
-
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
4167
|
-
* - update: apply incremental update
|
|
4168
|
-
* - history: initialize from a set of frames (clears prior history)
|
|
4169
|
-
*/
|
|
4170
|
-
async apply(msg) {
|
|
4171
|
-
switch (msg.type) {
|
|
4172
|
-
case "snapshot": {
|
|
4173
|
-
await this.api.replaceSnapshot(msg.nodes, msg.edges, msg.description);
|
|
4174
|
-
break;
|
|
4175
|
-
}
|
|
4176
|
-
case "update": {
|
|
4177
|
-
await this.api.update((u) => {
|
|
4178
|
-
if (msg.addNodes) u.addNodes(...msg.addNodes);
|
|
4179
|
-
if (msg.removeNodes) u.deleteNodes(...msg.removeNodes);
|
|
4180
|
-
if (msg.updateNodes) u.updateNodes(...msg.updateNodes);
|
|
4181
|
-
if (msg.addEdges) u.addEdges(...msg.addEdges);
|
|
4182
|
-
if (msg.removeEdges) u.deleteEdges(...msg.removeEdges);
|
|
4183
|
-
if (msg.updateEdges) u.updateEdges(...msg.updateEdges);
|
|
4184
|
-
if (msg.description) u.describe(msg.description);
|
|
4185
|
-
});
|
|
4186
|
-
break;
|
|
4187
|
-
}
|
|
4188
|
-
case "history": {
|
|
4189
|
-
await this.api.replaceHistory(msg.frames);
|
|
4190
|
-
break;
|
|
4191
|
-
}
|
|
4192
|
-
}
|
|
4193
|
-
}
|
|
4194
|
-
};
|
|
4195
|
-
|
|
4196
|
-
// src/api/sources/WebSocketSource.ts
|
|
4197
|
-
var WebSocketSource = class {
|
|
4198
|
-
url;
|
|
4199
|
-
ws = null;
|
|
4200
|
-
onMessage;
|
|
4201
|
-
onStatus;
|
|
4202
|
-
reconnectMs;
|
|
4203
|
-
closedByUser = false;
|
|
4204
|
-
connectStartTime = null;
|
|
4205
|
-
totalTimeoutMs = 1e4;
|
|
4206
|
-
totalTimeoutTimer = null;
|
|
4207
|
-
constructor(url, onMessage, onStatus, reconnectMs = 1500) {
|
|
4208
|
-
this.url = url;
|
|
4209
|
-
this.onMessage = onMessage;
|
|
4210
|
-
this.onStatus = onStatus;
|
|
4211
|
-
this.reconnectMs = reconnectMs;
|
|
4212
|
-
}
|
|
4213
|
-
connect() {
|
|
4214
|
-
this.closedByUser = false;
|
|
4215
|
-
this.connectStartTime = Date.now();
|
|
4216
|
-
this.startTotalTimeout();
|
|
4217
|
-
this.open();
|
|
4218
|
-
}
|
|
4219
|
-
disconnect() {
|
|
4220
|
-
this.closedByUser = true;
|
|
4221
|
-
this.clearTotalTimeout();
|
|
4222
|
-
if (this.ws) {
|
|
4223
|
-
try {
|
|
4224
|
-
this.ws.close();
|
|
4225
|
-
} catch {
|
|
4226
|
-
}
|
|
4227
|
-
this.ws = null;
|
|
4228
|
-
}
|
|
4229
|
-
this.onStatus?.("closed");
|
|
4230
|
-
}
|
|
4231
|
-
startTotalTimeout() {
|
|
4232
|
-
this.clearTotalTimeout();
|
|
4233
|
-
this.totalTimeoutTimer = window.setTimeout(() => {
|
|
4234
|
-
if (!this.closedByUser && this.ws?.readyState !== WebSocket.OPEN) {
|
|
4235
|
-
this.closedByUser = true;
|
|
4236
|
-
if (this.ws) {
|
|
4237
|
-
try {
|
|
4238
|
-
this.ws.close();
|
|
4239
|
-
} catch {
|
|
4240
|
-
}
|
|
4241
|
-
this.ws = null;
|
|
4402
|
+
this.events.updateNode(node.data, data, gotNode);
|
|
4403
|
+
else {
|
|
4404
|
+
this.nodeOverrides.set(node.data, data);
|
|
4405
|
+
await gotNode(node.data);
|
|
4242
4406
|
}
|
|
4243
|
-
|
|
4244
|
-
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
4245
|
-
}
|
|
4246
|
-
}, this.totalTimeoutMs);
|
|
4247
|
-
}
|
|
4248
|
-
clearTotalTimeout() {
|
|
4249
|
-
if (this.totalTimeoutTimer !== null) {
|
|
4250
|
-
clearTimeout(this.totalTimeoutTimer);
|
|
4251
|
-
this.totalTimeoutTimer = null;
|
|
4407
|
+
});
|
|
4252
4408
|
}
|
|
4253
|
-
this.connectStartTime = null;
|
|
4254
4409
|
}
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
}
|
|
4262
|
-
return;
|
|
4263
|
-
}
|
|
4264
|
-
this.onStatus?.(this.ws ? "reconnecting" : "connecting");
|
|
4265
|
-
const ws = new WebSocket(this.url);
|
|
4266
|
-
this.ws = ws;
|
|
4267
|
-
ws.onopen = () => {
|
|
4268
|
-
this.clearTotalTimeout();
|
|
4269
|
-
this.onStatus?.("connected");
|
|
4270
|
-
};
|
|
4271
|
-
ws.onerror = (e) => {
|
|
4272
|
-
this.onStatus?.("error", e);
|
|
4273
|
-
};
|
|
4274
|
-
ws.onclose = () => {
|
|
4275
|
-
if (this.closedByUser) {
|
|
4276
|
-
this.onStatus?.("closed");
|
|
4277
|
-
return;
|
|
4278
|
-
}
|
|
4279
|
-
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
4280
|
-
this.closedByUser = true;
|
|
4281
|
-
this.clearTotalTimeout();
|
|
4282
|
-
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
4283
|
-
return;
|
|
4284
|
-
}
|
|
4285
|
-
this.onStatus?.("reconnecting");
|
|
4286
|
-
setTimeout(() => this.open(), this.reconnectMs);
|
|
4410
|
+
async handleEditEdge(id) {
|
|
4411
|
+
const seg = this.graph.getSeg(id);
|
|
4412
|
+
if (seg.edgeIds.size != 1) return;
|
|
4413
|
+
const edge = this.graph.getEdge(seg.edgeIds.values().next().value);
|
|
4414
|
+
const gotEdge = async (edge2) => {
|
|
4415
|
+
if (edge2) await this.updateEdge(edge2);
|
|
4287
4416
|
};
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4417
|
+
if (this.events.editEdge)
|
|
4418
|
+
this.events.editEdge(edge.data, gotEdge);
|
|
4419
|
+
else
|
|
4420
|
+
this.canvas.showEditEdgeModal(edge, async (data) => {
|
|
4421
|
+
const sourceNode = edge.sourceNode(this.graph);
|
|
4422
|
+
const targetNode = edge.targetNode(this.graph);
|
|
4423
|
+
const update = {
|
|
4424
|
+
source: { node: sourceNode.data, port: data.source.port, marker: data.source.marker },
|
|
4425
|
+
target: { node: targetNode.data, port: data.target.port, marker: data.target.marker }
|
|
4426
|
+
};
|
|
4427
|
+
if (this.events.updateEdge)
|
|
4428
|
+
this.events.updateEdge(edge.data, update, gotEdge);
|
|
4429
|
+
else {
|
|
4430
|
+
this.edgeOverrides.set(edge.data, {
|
|
4431
|
+
source: { id: sourceNode.id, port: data.source.port, marker: data.source.marker },
|
|
4432
|
+
target: { id: targetNode.id, port: data.target.port, marker: data.target.marker },
|
|
4433
|
+
type: data.type
|
|
4434
|
+
});
|
|
4435
|
+
await gotEdge(edge.data);
|
|
4296
4436
|
}
|
|
4297
|
-
}
|
|
4437
|
+
});
|
|
4438
|
+
}
|
|
4439
|
+
async handleAddEdge(data) {
|
|
4440
|
+
const gotEdge = async (edge) => {
|
|
4441
|
+
if (edge) await this.addEdge(edge);
|
|
4442
|
+
};
|
|
4443
|
+
const newEdge = {
|
|
4444
|
+
source: { node: this.graph.getNode(data.source.id).data, port: data.source.port, marker: data.source.marker },
|
|
4445
|
+
target: { node: this.graph.getNode(data.target.id).data, port: data.target.port, marker: data.target.marker }
|
|
4298
4446
|
};
|
|
4447
|
+
if (this.events.addEdge)
|
|
4448
|
+
this.events.addEdge(newEdge, gotEdge);
|
|
4449
|
+
else
|
|
4450
|
+
await gotEdge(data);
|
|
4299
4451
|
}
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
lastSize = 0;
|
|
4309
|
-
filename;
|
|
4310
|
-
intervalMs;
|
|
4311
|
-
constructor(onMessage, onStatus, filename = "graph.ndjson", intervalMs = 1e3) {
|
|
4312
|
-
this.onMessage = onMessage;
|
|
4313
|
-
this.onStatus = onStatus;
|
|
4314
|
-
this.filename = filename;
|
|
4315
|
-
this.intervalMs = intervalMs;
|
|
4452
|
+
async handleDeleteNode(id) {
|
|
4453
|
+
const node = this.getNode(id);
|
|
4454
|
+
if (this.events.removeNode)
|
|
4455
|
+
this.events.removeNode(node.data, async (remove) => {
|
|
4456
|
+
if (remove) await this.deleteNode(node.data);
|
|
4457
|
+
});
|
|
4458
|
+
else
|
|
4459
|
+
await this.deleteNode(node.data);
|
|
4316
4460
|
}
|
|
4317
|
-
async
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
this.
|
|
4325
|
-
this.startPolling();
|
|
4326
|
-
} catch (e) {
|
|
4327
|
-
this.onStatus?.("error", e);
|
|
4328
|
-
}
|
|
4461
|
+
async handleDeleteEdge(id) {
|
|
4462
|
+
const edge = this.getEdge(id);
|
|
4463
|
+
if (this.events.removeEdge)
|
|
4464
|
+
this.events.removeEdge(edge.data, async (remove) => {
|
|
4465
|
+
if (remove) await this.deleteEdge(edge.data);
|
|
4466
|
+
});
|
|
4467
|
+
else
|
|
4468
|
+
await this.deleteEdge(edge.data);
|
|
4329
4469
|
}
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
this.timer = null;
|
|
4334
|
-
}
|
|
4335
|
-
this.handle = null;
|
|
4336
|
-
this.onStatus?.("closed");
|
|
4470
|
+
/** Update theme and type styles dynamically */
|
|
4471
|
+
updateStyles(options) {
|
|
4472
|
+
this.canvas?.updateStyles(options);
|
|
4337
4473
|
}
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
this.
|
|
4474
|
+
/** Update color mode without recreating the canvas */
|
|
4475
|
+
setColorMode(colorMode) {
|
|
4476
|
+
this.canvas?.setColorMode(colorMode);
|
|
4341
4477
|
}
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4478
|
+
/**
|
|
4479
|
+
* Apply prop changes by diffing against previously applied props.
|
|
4480
|
+
* This is a convenience method for framework wrappers that centralizes
|
|
4481
|
+
* the logic for detecting and applying changes to nodes, edges, history, and options.
|
|
4482
|
+
* The API stores the previous props internally, so you just pass the new props.
|
|
4483
|
+
*
|
|
4484
|
+
* @param props - The new props to apply
|
|
4485
|
+
*/
|
|
4486
|
+
applyProps(props) {
|
|
4487
|
+
const prev = this.prevProps;
|
|
4488
|
+
const nodesChanged = !shallowEqualArray(props.nodes, prev.nodes);
|
|
4489
|
+
const edgesChanged = !shallowEqualArray(props.edges, prev.edges);
|
|
4490
|
+
if (nodesChanged || edgesChanged) {
|
|
4491
|
+
if (props.nodes) {
|
|
4492
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
if (!nodesChanged && !edgesChanged && props.history !== prev.history) {
|
|
4496
|
+
if (props.history === void 0) {
|
|
4497
|
+
if (props.nodes) {
|
|
4498
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4499
|
+
}
|
|
4500
|
+
} else if (prev.history && isHistoryPrefix(prev.history, props.history)) {
|
|
4501
|
+
const prevLength = prev.history.length;
|
|
4502
|
+
const newFrames = props.history.slice(prevLength);
|
|
4503
|
+
for (const frame of newFrames) {
|
|
4504
|
+
this.update((u) => {
|
|
4505
|
+
if (frame.addNodes) u.addNodes(...frame.addNodes);
|
|
4506
|
+
if (frame.removeNodes) u.deleteNodes(...frame.removeNodes);
|
|
4507
|
+
if (frame.updateNodes) u.updateNodes(...frame.updateNodes);
|
|
4508
|
+
if (frame.addEdges) u.addEdges(...frame.addEdges);
|
|
4509
|
+
if (frame.removeEdges) u.deleteEdges(...frame.removeEdges);
|
|
4510
|
+
if (frame.updateEdges) u.updateEdges(...frame.updateEdges);
|
|
4511
|
+
if (frame.description) u.describe(frame.description);
|
|
4512
|
+
});
|
|
4356
4513
|
}
|
|
4514
|
+
} else {
|
|
4515
|
+
this.replaceHistory(props.history);
|
|
4357
4516
|
}
|
|
4358
|
-
} catch (e) {
|
|
4359
|
-
this.onStatus?.("error", e);
|
|
4360
4517
|
}
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
this.onMessage = onMessage;
|
|
4377
|
-
this.onStatus = onStatus;
|
|
4378
|
-
this.intervalMs = intervalMs;
|
|
4379
|
-
}
|
|
4380
|
-
async connect() {
|
|
4381
|
-
this.closed = false;
|
|
4382
|
-
this.lastETag = null;
|
|
4383
|
-
this.lastContent = "";
|
|
4384
|
-
this.onStatus?.("opened");
|
|
4385
|
-
this.startPolling();
|
|
4386
|
-
}
|
|
4387
|
-
close() {
|
|
4388
|
-
this.closed = true;
|
|
4389
|
-
if (this.timer) {
|
|
4390
|
-
window.clearInterval(this.timer);
|
|
4391
|
-
this.timer = null;
|
|
4518
|
+
const prevCanvas = prev.options?.canvas;
|
|
4519
|
+
const currCanvas = props.options?.canvas;
|
|
4520
|
+
const colorModeChanged = prevCanvas?.colorMode !== currCanvas?.colorMode;
|
|
4521
|
+
if (colorModeChanged && currCanvas?.colorMode) {
|
|
4522
|
+
this.setColorMode(currCanvas.colorMode);
|
|
4523
|
+
}
|
|
4524
|
+
const themeChanged = prevCanvas?.theme !== currCanvas?.theme;
|
|
4525
|
+
const nodeTypesChanged = prevCanvas?.nodeTypes !== currCanvas?.nodeTypes;
|
|
4526
|
+
const edgeTypesChanged = prevCanvas?.edgeTypes !== currCanvas?.edgeTypes;
|
|
4527
|
+
if (themeChanged || nodeTypesChanged || edgeTypesChanged) {
|
|
4528
|
+
this.updateStyles({
|
|
4529
|
+
theme: currCanvas?.theme,
|
|
4530
|
+
nodeTypes: currCanvas?.nodeTypes,
|
|
4531
|
+
edgeTypes: currCanvas?.edgeTypes
|
|
4532
|
+
});
|
|
4392
4533
|
}
|
|
4393
|
-
this.
|
|
4534
|
+
this.prevProps = {
|
|
4535
|
+
nodes: props.nodes,
|
|
4536
|
+
edges: props.edges,
|
|
4537
|
+
history: props.history,
|
|
4538
|
+
options: props.options
|
|
4539
|
+
};
|
|
4394
4540
|
}
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
this.
|
|
4398
|
-
this.
|
|
4541
|
+
/** Cleanup resources when the graph is destroyed */
|
|
4542
|
+
destroy() {
|
|
4543
|
+
this.disconnectIngestion();
|
|
4544
|
+
this.canvas?.destroy();
|
|
4399
4545
|
}
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
const etag = response.headers.get("ETag");
|
|
4416
|
-
if (etag) {
|
|
4417
|
-
this.lastETag = etag;
|
|
4418
|
-
}
|
|
4419
|
-
const content = await response.text();
|
|
4420
|
-
if (content === this.lastContent) {
|
|
4421
|
-
return;
|
|
4422
|
-
}
|
|
4423
|
-
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4424
|
-
const lastContentLines = this.lastContent.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4425
|
-
const newLines = lines.slice(lastContentLines.length);
|
|
4426
|
-
for (const line of newLines) {
|
|
4427
|
-
try {
|
|
4428
|
-
const obj = JSON.parse(line);
|
|
4429
|
-
this.onMessage(obj);
|
|
4430
|
-
} catch {
|
|
4546
|
+
};
|
|
4547
|
+
function shallowEqualArray(a, b) {
|
|
4548
|
+
if (a === b) return true;
|
|
4549
|
+
if (!a || !b) return false;
|
|
4550
|
+
if (a.length !== b.length) return false;
|
|
4551
|
+
for (let i = 0; i < a.length; i++) {
|
|
4552
|
+
if (a[i] !== b[i]) {
|
|
4553
|
+
if (typeof a[i] === "object" && a[i] !== null && typeof b[i] === "object" && b[i] !== null) {
|
|
4554
|
+
const aObj = a[i];
|
|
4555
|
+
const bObj = b[i];
|
|
4556
|
+
const aKeys = Object.keys(aObj);
|
|
4557
|
+
const bKeys = Object.keys(bObj);
|
|
4558
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
4559
|
+
for (const key of aKeys) {
|
|
4560
|
+
if (aObj[key] !== bObj[key]) return false;
|
|
4431
4561
|
}
|
|
4562
|
+
} else {
|
|
4563
|
+
return false;
|
|
4432
4564
|
}
|
|
4433
|
-
this.lastContent = content;
|
|
4434
|
-
} catch (e) {
|
|
4435
|
-
this.onStatus?.("error", e);
|
|
4436
4565
|
}
|
|
4437
4566
|
}
|
|
4438
|
-
|
|
4567
|
+
return true;
|
|
4568
|
+
}
|
|
4569
|
+
function isHistoryPrefix(oldHistory, newHistory) {
|
|
4570
|
+
if (newHistory.length < oldHistory.length) return false;
|
|
4571
|
+
for (let i = 0; i < oldHistory.length; i++) {
|
|
4572
|
+
if (!shallowEqualUpdate(oldHistory[i], newHistory[i])) {
|
|
4573
|
+
return false;
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
return true;
|
|
4577
|
+
}
|
|
4578
|
+
function shallowEqualUpdate(a, b) {
|
|
4579
|
+
if (a === b) return true;
|
|
4580
|
+
if (a.description !== b.description) return false;
|
|
4581
|
+
if (!shallowEqualArray(a.addNodes, b.addNodes)) return false;
|
|
4582
|
+
if (!shallowEqualArray(a.removeNodes, b.removeNodes)) return false;
|
|
4583
|
+
if (!shallowEqualArray(a.updateNodes, b.updateNodes)) return false;
|
|
4584
|
+
if (!shallowEqualArray(a.addEdges, b.addEdges)) return false;
|
|
4585
|
+
if (!shallowEqualArray(a.removeEdges, b.removeEdges)) return false;
|
|
4586
|
+
if (!shallowEqualArray(a.updateEdges, b.updateEdges)) return false;
|
|
4587
|
+
return true;
|
|
4588
|
+
}
|
|
4439
4589
|
|
|
4440
4590
|
// src/playground/playground.ts
|
|
4441
4591
|
import styles2 from "./styles.css?raw";
|
|
@@ -4715,10 +4865,18 @@ var Playground = class {
|
|
|
4715
4865
|
this.disconnectAllSources();
|
|
4716
4866
|
if (example.source.type === "websocket") {
|
|
4717
4867
|
this.wsUrl = example.source.url;
|
|
4718
|
-
this.wsSource = new WebSocketSource(
|
|
4868
|
+
this.wsSource = new WebSocketSource({
|
|
4869
|
+
url: example.source.url,
|
|
4870
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
4871
|
+
onStatus: this.updateWsStatus
|
|
4872
|
+
});
|
|
4719
4873
|
this.wsSource.connect();
|
|
4720
4874
|
} else if (example.source.type === "file") {
|
|
4721
|
-
this.fileSource = new FileSource(
|
|
4875
|
+
this.fileSource = new FileSource({
|
|
4876
|
+
url: example.source.path,
|
|
4877
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
4878
|
+
onStatus: this.updateFileStatus
|
|
4879
|
+
});
|
|
4722
4880
|
this.fileSource.connect();
|
|
4723
4881
|
}
|
|
4724
4882
|
}
|
|
@@ -5118,7 +5276,11 @@ var Playground = class {
|
|
|
5118
5276
|
if (this.wsSource) {
|
|
5119
5277
|
this.wsSource.disconnect();
|
|
5120
5278
|
}
|
|
5121
|
-
this.wsSource = new WebSocketSource(
|
|
5279
|
+
this.wsSource = new WebSocketSource({
|
|
5280
|
+
url,
|
|
5281
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
5282
|
+
onStatus: this.updateWsStatus
|
|
5283
|
+
});
|
|
5122
5284
|
this.wsSource.connect();
|
|
5123
5285
|
this.updateSourceModal();
|
|
5124
5286
|
}
|
|
@@ -5139,7 +5301,11 @@ var Playground = class {
|
|
|
5139
5301
|
}
|
|
5140
5302
|
async handleOpenFolder() {
|
|
5141
5303
|
if (!this.fsSource) {
|
|
5142
|
-
this.fsSource = new FileSystemSource(
|
|
5304
|
+
this.fsSource = new FileSystemSource({
|
|
5305
|
+
filename: "graph.ndjson",
|
|
5306
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
5307
|
+
onStatus: this.updateFsStatus
|
|
5308
|
+
});
|
|
5143
5309
|
}
|
|
5144
5310
|
this.updateSourceModal();
|
|
5145
5311
|
await this.fsSource.openDirectory();
|