@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.cjs
CHANGED
|
@@ -3669,6 +3669,287 @@ var Updater = class _Updater {
|
|
|
3669
3669
|
}
|
|
3670
3670
|
};
|
|
3671
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(args) {
|
|
3721
|
+
this.url = args.url;
|
|
3722
|
+
this.onMessage = args.onMessage;
|
|
3723
|
+
this.onStatus = args.onStatus;
|
|
3724
|
+
this.reconnectMs = args.reconnectMs ?? 1500;
|
|
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(args) {
|
|
3826
|
+
this.url = args.url;
|
|
3827
|
+
this.onMessage = args.onMessage;
|
|
3828
|
+
this.onStatus = args.onStatus;
|
|
3829
|
+
this.intervalMs = args.intervalMs ?? 1e3;
|
|
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
|
+
|
|
3891
|
+
// src/api/sources/FileSystemSource.ts
|
|
3892
|
+
var FileSystemSource = class {
|
|
3893
|
+
handle = null;
|
|
3894
|
+
onMessage;
|
|
3895
|
+
onStatus;
|
|
3896
|
+
timer = null;
|
|
3897
|
+
lastSize = 0;
|
|
3898
|
+
filename;
|
|
3899
|
+
intervalMs;
|
|
3900
|
+
constructor(args) {
|
|
3901
|
+
this.filename = args.filename;
|
|
3902
|
+
this.onMessage = args.onMessage;
|
|
3903
|
+
this.onStatus = args.onStatus;
|
|
3904
|
+
this.intervalMs = args.intervalMs ?? 1e3;
|
|
3905
|
+
}
|
|
3906
|
+
async openDirectory() {
|
|
3907
|
+
try {
|
|
3908
|
+
const dir = await window.showDirectoryPicker?.();
|
|
3909
|
+
if (!dir) throw new Error("File System Access not supported or cancelled");
|
|
3910
|
+
const handle = await dir.getFileHandle(this.filename, { create: false });
|
|
3911
|
+
this.handle = handle;
|
|
3912
|
+
this.onStatus?.("opened", { file: this.filename });
|
|
3913
|
+
this.lastSize = 0;
|
|
3914
|
+
this.startPolling();
|
|
3915
|
+
} catch (e) {
|
|
3916
|
+
this.onStatus?.("error", e);
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
close() {
|
|
3920
|
+
if (this.timer) {
|
|
3921
|
+
window.clearInterval(this.timer);
|
|
3922
|
+
this.timer = null;
|
|
3923
|
+
}
|
|
3924
|
+
this.handle = null;
|
|
3925
|
+
this.onStatus?.("closed");
|
|
3926
|
+
}
|
|
3927
|
+
startPolling() {
|
|
3928
|
+
if (this.timer) window.clearInterval(this.timer);
|
|
3929
|
+
this.timer = window.setInterval(() => this.readNewLines(), this.intervalMs);
|
|
3930
|
+
}
|
|
3931
|
+
async readNewLines() {
|
|
3932
|
+
try {
|
|
3933
|
+
if (!this.handle) return;
|
|
3934
|
+
this.onStatus?.("reading");
|
|
3935
|
+
const file = await this.handle.getFile();
|
|
3936
|
+
if (file.size === this.lastSize) return;
|
|
3937
|
+
const slice = await file.slice(this.lastSize).text();
|
|
3938
|
+
this.lastSize = file.size;
|
|
3939
|
+
const lines = slice.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3940
|
+
for (const line of lines) {
|
|
3941
|
+
try {
|
|
3942
|
+
const obj = JSON.parse(line);
|
|
3943
|
+
this.onMessage(obj);
|
|
3944
|
+
} catch {
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
} catch (e) {
|
|
3948
|
+
this.onStatus?.("error", e);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
};
|
|
3952
|
+
|
|
3672
3953
|
// src/api/api.ts
|
|
3673
3954
|
var log11 = logger("api");
|
|
3674
3955
|
var API = class {
|
|
@@ -3687,11 +3968,16 @@ var API = class {
|
|
|
3687
3968
|
nextNodeId;
|
|
3688
3969
|
nextEdgeId;
|
|
3689
3970
|
events;
|
|
3971
|
+
ingest;
|
|
3972
|
+
ingestionSource;
|
|
3973
|
+
ingestionConfig;
|
|
3974
|
+
prevProps = {};
|
|
3690
3975
|
root;
|
|
3691
3976
|
constructor(args) {
|
|
3692
3977
|
this.root = args.root;
|
|
3693
3978
|
this.options = applyDefaults(args.options);
|
|
3694
3979
|
this.events = args.events || {};
|
|
3980
|
+
this.ingestionConfig = args.ingestion;
|
|
3695
3981
|
this.reset();
|
|
3696
3982
|
this.canvas = new Canvas(this, {
|
|
3697
3983
|
...this.options.canvas,
|
|
@@ -3705,6 +3991,15 @@ var API = class {
|
|
|
3705
3991
|
} else {
|
|
3706
3992
|
this.history = [];
|
|
3707
3993
|
}
|
|
3994
|
+
this.prevProps = {
|
|
3995
|
+
nodes: args.nodes,
|
|
3996
|
+
edges: args.edges,
|
|
3997
|
+
history: args.history,
|
|
3998
|
+
options: args.options
|
|
3999
|
+
};
|
|
4000
|
+
if (this.ingestionConfig) {
|
|
4001
|
+
this.ingest = new Ingest(this);
|
|
4002
|
+
}
|
|
3708
4003
|
}
|
|
3709
4004
|
reset() {
|
|
3710
4005
|
let graph2 = new Graph({ options: this.options.graph });
|
|
@@ -3727,10 +4022,40 @@ var API = class {
|
|
|
3727
4022
|
if (!root) throw new Error("root element not found");
|
|
3728
4023
|
root.appendChild(this.canvas.container);
|
|
3729
4024
|
await this.applyHistory();
|
|
4025
|
+
if (this.ingestionConfig && this.ingest) {
|
|
4026
|
+
this.connectIngestion();
|
|
4027
|
+
}
|
|
3730
4028
|
if (this.events.onInit) {
|
|
3731
4029
|
this.events.onInit();
|
|
3732
4030
|
}
|
|
3733
4031
|
}
|
|
4032
|
+
/** Connect to the configured ingestion source */
|
|
4033
|
+
connectIngestion() {
|
|
4034
|
+
if (!this.ingestionConfig || !this.ingest) return;
|
|
4035
|
+
const args = {
|
|
4036
|
+
...this.ingestionConfig,
|
|
4037
|
+
onMessage: (msg) => {
|
|
4038
|
+
this.ingest.apply(msg);
|
|
4039
|
+
}
|
|
4040
|
+
};
|
|
4041
|
+
const source = {
|
|
4042
|
+
"websocket": WebSocketSource,
|
|
4043
|
+
"file": FileSource,
|
|
4044
|
+
"filesystem": FileSystemSource
|
|
4045
|
+
}[this.ingestionConfig.type];
|
|
4046
|
+
this.ingestionSource = new source[this.ingestionConfig.type](args);
|
|
4047
|
+
this.ingestionSource?.connect();
|
|
4048
|
+
}
|
|
4049
|
+
/** Disconnect from the ingestion source */
|
|
4050
|
+
disconnectIngestion() {
|
|
4051
|
+
if (!this.ingestionSource) return;
|
|
4052
|
+
if (this.ingestionSource instanceof WebSocketSource) {
|
|
4053
|
+
this.ingestionSource.disconnect();
|
|
4054
|
+
} else if (this.ingestionSource instanceof FileSource) {
|
|
4055
|
+
this.ingestionSource.close();
|
|
4056
|
+
}
|
|
4057
|
+
this.ingestionSource = void 0;
|
|
4058
|
+
}
|
|
3734
4059
|
async applyHistory() {
|
|
3735
4060
|
for (const update of this.history)
|
|
3736
4061
|
await this.applyUpdate(update);
|
|
@@ -4117,368 +4442,193 @@ var API = class {
|
|
|
4117
4442
|
else {
|
|
4118
4443
|
this.canvas.showEditNodeModal(node, async (data) => {
|
|
4119
4444
|
if (this.events.updateNode)
|
|
4120
|
-
this.events.updateNode(node.data, data, gotNode);
|
|
4121
|
-
else {
|
|
4122
|
-
this.nodeOverrides.set(node.data, data);
|
|
4123
|
-
await gotNode(node.data);
|
|
4124
|
-
}
|
|
4125
|
-
});
|
|
4126
|
-
}
|
|
4127
|
-
}
|
|
4128
|
-
async handleEditEdge(id) {
|
|
4129
|
-
const seg = this.graph.getSeg(id);
|
|
4130
|
-
if (seg.edgeIds.size != 1) return;
|
|
4131
|
-
const edge = this.graph.getEdge(seg.edgeIds.values().next().value);
|
|
4132
|
-
const gotEdge = async (edge2) => {
|
|
4133
|
-
if (edge2) await this.updateEdge(edge2);
|
|
4134
|
-
};
|
|
4135
|
-
if (this.events.editEdge)
|
|
4136
|
-
this.events.editEdge(edge.data, gotEdge);
|
|
4137
|
-
else
|
|
4138
|
-
this.canvas.showEditEdgeModal(edge, async (data) => {
|
|
4139
|
-
const sourceNode = edge.sourceNode(this.graph);
|
|
4140
|
-
const targetNode = edge.targetNode(this.graph);
|
|
4141
|
-
const update = {
|
|
4142
|
-
source: { node: sourceNode.data, port: data.source.port, marker: data.source.marker },
|
|
4143
|
-
target: { node: targetNode.data, port: data.target.port, marker: data.target.marker }
|
|
4144
|
-
};
|
|
4145
|
-
if (this.events.updateEdge)
|
|
4146
|
-
this.events.updateEdge(edge.data, update, gotEdge);
|
|
4147
|
-
else {
|
|
4148
|
-
this.edgeOverrides.set(edge.data, {
|
|
4149
|
-
source: { id: sourceNode.id, port: data.source.port, marker: data.source.marker },
|
|
4150
|
-
target: { id: targetNode.id, port: data.target.port, marker: data.target.marker },
|
|
4151
|
-
type: data.type
|
|
4152
|
-
});
|
|
4153
|
-
await gotEdge(edge.data);
|
|
4154
|
-
}
|
|
4155
|
-
});
|
|
4156
|
-
}
|
|
4157
|
-
async handleAddEdge(data) {
|
|
4158
|
-
const gotEdge = async (edge) => {
|
|
4159
|
-
if (edge) await this.addEdge(edge);
|
|
4160
|
-
};
|
|
4161
|
-
const newEdge = {
|
|
4162
|
-
source: { node: this.graph.getNode(data.source.id).data, port: data.source.port, marker: data.source.marker },
|
|
4163
|
-
target: { node: this.graph.getNode(data.target.id).data, port: data.target.port, marker: data.target.marker }
|
|
4164
|
-
};
|
|
4165
|
-
if (this.events.addEdge)
|
|
4166
|
-
this.events.addEdge(newEdge, gotEdge);
|
|
4167
|
-
else
|
|
4168
|
-
await gotEdge(data);
|
|
4169
|
-
}
|
|
4170
|
-
async handleDeleteNode(id) {
|
|
4171
|
-
const node = this.getNode(id);
|
|
4172
|
-
if (this.events.removeNode)
|
|
4173
|
-
this.events.removeNode(node.data, async (remove) => {
|
|
4174
|
-
if (remove) await this.deleteNode(node.data);
|
|
4175
|
-
});
|
|
4176
|
-
else
|
|
4177
|
-
await this.deleteNode(node.data);
|
|
4178
|
-
}
|
|
4179
|
-
async handleDeleteEdge(id) {
|
|
4180
|
-
const edge = this.getEdge(id);
|
|
4181
|
-
if (this.events.removeEdge)
|
|
4182
|
-
this.events.removeEdge(edge.data, async (remove) => {
|
|
4183
|
-
if (remove) await this.deleteEdge(edge.data);
|
|
4184
|
-
});
|
|
4185
|
-
else
|
|
4186
|
-
await this.deleteEdge(edge.data);
|
|
4187
|
-
}
|
|
4188
|
-
/** Update theme and type styles dynamically */
|
|
4189
|
-
updateStyles(options) {
|
|
4190
|
-
this.canvas?.updateStyles(options);
|
|
4191
|
-
}
|
|
4192
|
-
/** Update color mode without recreating the canvas */
|
|
4193
|
-
setColorMode(colorMode) {
|
|
4194
|
-
this.canvas?.setColorMode(colorMode);
|
|
4195
|
-
}
|
|
4196
|
-
/** Cleanup resources when the graph is destroyed */
|
|
4197
|
-
destroy() {
|
|
4198
|
-
this.canvas?.destroy();
|
|
4199
|
-
}
|
|
4200
|
-
};
|
|
4201
|
-
|
|
4202
|
-
// src/api/ingest.ts
|
|
4203
|
-
var Ingest = class {
|
|
4204
|
-
constructor(api) {
|
|
4205
|
-
this.api = api;
|
|
4206
|
-
}
|
|
4207
|
-
/**
|
|
4208
|
-
* Apply an incoming ingest message to the API.
|
|
4209
|
-
* - snapshot: rebuild state from nodes/edges (clears prior history)
|
|
4210
|
-
* - update: apply incremental update
|
|
4211
|
-
* - history: initialize from a set of frames (clears prior history)
|
|
4212
|
-
*/
|
|
4213
|
-
async apply(msg) {
|
|
4214
|
-
switch (msg.type) {
|
|
4215
|
-
case "snapshot": {
|
|
4216
|
-
await this.api.replaceSnapshot(msg.nodes, msg.edges, msg.description);
|
|
4217
|
-
break;
|
|
4218
|
-
}
|
|
4219
|
-
case "update": {
|
|
4220
|
-
await this.api.update((u) => {
|
|
4221
|
-
if (msg.addNodes) u.addNodes(...msg.addNodes);
|
|
4222
|
-
if (msg.removeNodes) u.deleteNodes(...msg.removeNodes);
|
|
4223
|
-
if (msg.updateNodes) u.updateNodes(...msg.updateNodes);
|
|
4224
|
-
if (msg.addEdges) u.addEdges(...msg.addEdges);
|
|
4225
|
-
if (msg.removeEdges) u.deleteEdges(...msg.removeEdges);
|
|
4226
|
-
if (msg.updateEdges) u.updateEdges(...msg.updateEdges);
|
|
4227
|
-
if (msg.description) u.describe(msg.description);
|
|
4228
|
-
});
|
|
4229
|
-
break;
|
|
4230
|
-
}
|
|
4231
|
-
case "history": {
|
|
4232
|
-
await this.api.replaceHistory(msg.frames);
|
|
4233
|
-
break;
|
|
4234
|
-
}
|
|
4235
|
-
}
|
|
4236
|
-
}
|
|
4237
|
-
};
|
|
4238
|
-
|
|
4239
|
-
// src/api/sources/WebSocketSource.ts
|
|
4240
|
-
var WebSocketSource = class {
|
|
4241
|
-
url;
|
|
4242
|
-
ws = null;
|
|
4243
|
-
onMessage;
|
|
4244
|
-
onStatus;
|
|
4245
|
-
reconnectMs;
|
|
4246
|
-
closedByUser = false;
|
|
4247
|
-
connectStartTime = null;
|
|
4248
|
-
totalTimeoutMs = 1e4;
|
|
4249
|
-
totalTimeoutTimer = null;
|
|
4250
|
-
constructor(url, onMessage, onStatus, reconnectMs = 1500) {
|
|
4251
|
-
this.url = url;
|
|
4252
|
-
this.onMessage = onMessage;
|
|
4253
|
-
this.onStatus = onStatus;
|
|
4254
|
-
this.reconnectMs = reconnectMs;
|
|
4255
|
-
}
|
|
4256
|
-
connect() {
|
|
4257
|
-
this.closedByUser = false;
|
|
4258
|
-
this.connectStartTime = Date.now();
|
|
4259
|
-
this.startTotalTimeout();
|
|
4260
|
-
this.open();
|
|
4261
|
-
}
|
|
4262
|
-
disconnect() {
|
|
4263
|
-
this.closedByUser = true;
|
|
4264
|
-
this.clearTotalTimeout();
|
|
4265
|
-
if (this.ws) {
|
|
4266
|
-
try {
|
|
4267
|
-
this.ws.close();
|
|
4268
|
-
} catch {
|
|
4269
|
-
}
|
|
4270
|
-
this.ws = null;
|
|
4271
|
-
}
|
|
4272
|
-
this.onStatus?.("closed");
|
|
4273
|
-
}
|
|
4274
|
-
startTotalTimeout() {
|
|
4275
|
-
this.clearTotalTimeout();
|
|
4276
|
-
this.totalTimeoutTimer = window.setTimeout(() => {
|
|
4277
|
-
if (!this.closedByUser && this.ws?.readyState !== WebSocket.OPEN) {
|
|
4278
|
-
this.closedByUser = true;
|
|
4279
|
-
if (this.ws) {
|
|
4280
|
-
try {
|
|
4281
|
-
this.ws.close();
|
|
4282
|
-
} catch {
|
|
4283
|
-
}
|
|
4284
|
-
this.ws = null;
|
|
4445
|
+
this.events.updateNode(node.data, data, gotNode);
|
|
4446
|
+
else {
|
|
4447
|
+
this.nodeOverrides.set(node.data, data);
|
|
4448
|
+
await gotNode(node.data);
|
|
4285
4449
|
}
|
|
4286
|
-
|
|
4287
|
-
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
4288
|
-
}
|
|
4289
|
-
}, this.totalTimeoutMs);
|
|
4290
|
-
}
|
|
4291
|
-
clearTotalTimeout() {
|
|
4292
|
-
if (this.totalTimeoutTimer !== null) {
|
|
4293
|
-
clearTimeout(this.totalTimeoutTimer);
|
|
4294
|
-
this.totalTimeoutTimer = null;
|
|
4450
|
+
});
|
|
4295
4451
|
}
|
|
4296
|
-
this.connectStartTime = null;
|
|
4297
4452
|
}
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
}
|
|
4305
|
-
return;
|
|
4306
|
-
}
|
|
4307
|
-
this.onStatus?.(this.ws ? "reconnecting" : "connecting");
|
|
4308
|
-
const ws = new WebSocket(this.url);
|
|
4309
|
-
this.ws = ws;
|
|
4310
|
-
ws.onopen = () => {
|
|
4311
|
-
this.clearTotalTimeout();
|
|
4312
|
-
this.onStatus?.("connected");
|
|
4313
|
-
};
|
|
4314
|
-
ws.onerror = (e) => {
|
|
4315
|
-
this.onStatus?.("error", e);
|
|
4316
|
-
};
|
|
4317
|
-
ws.onclose = () => {
|
|
4318
|
-
if (this.closedByUser) {
|
|
4319
|
-
this.onStatus?.("closed");
|
|
4320
|
-
return;
|
|
4321
|
-
}
|
|
4322
|
-
if (this.connectStartTime && Date.now() - this.connectStartTime >= this.totalTimeoutMs) {
|
|
4323
|
-
this.closedByUser = true;
|
|
4324
|
-
this.clearTotalTimeout();
|
|
4325
|
-
this.onStatus?.("error", new Error("Connection timeout after 10 seconds"));
|
|
4326
|
-
return;
|
|
4327
|
-
}
|
|
4328
|
-
this.onStatus?.("reconnecting");
|
|
4329
|
-
setTimeout(() => this.open(), this.reconnectMs);
|
|
4453
|
+
async handleEditEdge(id) {
|
|
4454
|
+
const seg = this.graph.getSeg(id);
|
|
4455
|
+
if (seg.edgeIds.size != 1) return;
|
|
4456
|
+
const edge = this.graph.getEdge(seg.edgeIds.values().next().value);
|
|
4457
|
+
const gotEdge = async (edge2) => {
|
|
4458
|
+
if (edge2) await this.updateEdge(edge2);
|
|
4330
4459
|
};
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4460
|
+
if (this.events.editEdge)
|
|
4461
|
+
this.events.editEdge(edge.data, gotEdge);
|
|
4462
|
+
else
|
|
4463
|
+
this.canvas.showEditEdgeModal(edge, async (data) => {
|
|
4464
|
+
const sourceNode = edge.sourceNode(this.graph);
|
|
4465
|
+
const targetNode = edge.targetNode(this.graph);
|
|
4466
|
+
const update = {
|
|
4467
|
+
source: { node: sourceNode.data, port: data.source.port, marker: data.source.marker },
|
|
4468
|
+
target: { node: targetNode.data, port: data.target.port, marker: data.target.marker }
|
|
4469
|
+
};
|
|
4470
|
+
if (this.events.updateEdge)
|
|
4471
|
+
this.events.updateEdge(edge.data, update, gotEdge);
|
|
4472
|
+
else {
|
|
4473
|
+
this.edgeOverrides.set(edge.data, {
|
|
4474
|
+
source: { id: sourceNode.id, port: data.source.port, marker: data.source.marker },
|
|
4475
|
+
target: { id: targetNode.id, port: data.target.port, marker: data.target.marker },
|
|
4476
|
+
type: data.type
|
|
4477
|
+
});
|
|
4478
|
+
await gotEdge(edge.data);
|
|
4339
4479
|
}
|
|
4340
|
-
}
|
|
4480
|
+
});
|
|
4481
|
+
}
|
|
4482
|
+
async handleAddEdge(data) {
|
|
4483
|
+
const gotEdge = async (edge) => {
|
|
4484
|
+
if (edge) await this.addEdge(edge);
|
|
4485
|
+
};
|
|
4486
|
+
const newEdge = {
|
|
4487
|
+
source: { node: this.graph.getNode(data.source.id).data, port: data.source.port, marker: data.source.marker },
|
|
4488
|
+
target: { node: this.graph.getNode(data.target.id).data, port: data.target.port, marker: data.target.marker }
|
|
4341
4489
|
};
|
|
4490
|
+
if (this.events.addEdge)
|
|
4491
|
+
this.events.addEdge(newEdge, gotEdge);
|
|
4492
|
+
else
|
|
4493
|
+
await gotEdge(data);
|
|
4342
4494
|
}
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
lastSize = 0;
|
|
4352
|
-
filename;
|
|
4353
|
-
intervalMs;
|
|
4354
|
-
constructor(onMessage, onStatus, filename = "graph.ndjson", intervalMs = 1e3) {
|
|
4355
|
-
this.onMessage = onMessage;
|
|
4356
|
-
this.onStatus = onStatus;
|
|
4357
|
-
this.filename = filename;
|
|
4358
|
-
this.intervalMs = intervalMs;
|
|
4495
|
+
async handleDeleteNode(id) {
|
|
4496
|
+
const node = this.getNode(id);
|
|
4497
|
+
if (this.events.removeNode)
|
|
4498
|
+
this.events.removeNode(node.data, async (remove) => {
|
|
4499
|
+
if (remove) await this.deleteNode(node.data);
|
|
4500
|
+
});
|
|
4501
|
+
else
|
|
4502
|
+
await this.deleteNode(node.data);
|
|
4359
4503
|
}
|
|
4360
|
-
async
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
this.
|
|
4368
|
-
this.startPolling();
|
|
4369
|
-
} catch (e) {
|
|
4370
|
-
this.onStatus?.("error", e);
|
|
4371
|
-
}
|
|
4504
|
+
async handleDeleteEdge(id) {
|
|
4505
|
+
const edge = this.getEdge(id);
|
|
4506
|
+
if (this.events.removeEdge)
|
|
4507
|
+
this.events.removeEdge(edge.data, async (remove) => {
|
|
4508
|
+
if (remove) await this.deleteEdge(edge.data);
|
|
4509
|
+
});
|
|
4510
|
+
else
|
|
4511
|
+
await this.deleteEdge(edge.data);
|
|
4372
4512
|
}
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
this.timer = null;
|
|
4377
|
-
}
|
|
4378
|
-
this.handle = null;
|
|
4379
|
-
this.onStatus?.("closed");
|
|
4513
|
+
/** Update theme and type styles dynamically */
|
|
4514
|
+
updateStyles(options) {
|
|
4515
|
+
this.canvas?.updateStyles(options);
|
|
4380
4516
|
}
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
this.
|
|
4517
|
+
/** Update color mode without recreating the canvas */
|
|
4518
|
+
setColorMode(colorMode) {
|
|
4519
|
+
this.canvas?.setColorMode(colorMode);
|
|
4384
4520
|
}
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4521
|
+
/**
|
|
4522
|
+
* Apply prop changes by diffing against previously applied props.
|
|
4523
|
+
* This is a convenience method for framework wrappers that centralizes
|
|
4524
|
+
* the logic for detecting and applying changes to nodes, edges, history, and options.
|
|
4525
|
+
* The API stores the previous props internally, so you just pass the new props.
|
|
4526
|
+
*
|
|
4527
|
+
* @param props - The new props to apply
|
|
4528
|
+
*/
|
|
4529
|
+
applyProps(props) {
|
|
4530
|
+
const prev = this.prevProps;
|
|
4531
|
+
const nodesChanged = !shallowEqualArray(props.nodes, prev.nodes);
|
|
4532
|
+
const edgesChanged = !shallowEqualArray(props.edges, prev.edges);
|
|
4533
|
+
if (nodesChanged || edgesChanged) {
|
|
4534
|
+
if (props.nodes) {
|
|
4535
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
if (!nodesChanged && !edgesChanged && props.history !== prev.history) {
|
|
4539
|
+
if (props.history === void 0) {
|
|
4540
|
+
if (props.nodes) {
|
|
4541
|
+
this.replaceSnapshot(props.nodes, props.edges || [], void 0);
|
|
4542
|
+
}
|
|
4543
|
+
} else if (prev.history && isHistoryPrefix(prev.history, props.history)) {
|
|
4544
|
+
const prevLength = prev.history.length;
|
|
4545
|
+
const newFrames = props.history.slice(prevLength);
|
|
4546
|
+
for (const frame of newFrames) {
|
|
4547
|
+
this.update((u) => {
|
|
4548
|
+
if (frame.addNodes) u.addNodes(...frame.addNodes);
|
|
4549
|
+
if (frame.removeNodes) u.deleteNodes(...frame.removeNodes);
|
|
4550
|
+
if (frame.updateNodes) u.updateNodes(...frame.updateNodes);
|
|
4551
|
+
if (frame.addEdges) u.addEdges(...frame.addEdges);
|
|
4552
|
+
if (frame.removeEdges) u.deleteEdges(...frame.removeEdges);
|
|
4553
|
+
if (frame.updateEdges) u.updateEdges(...frame.updateEdges);
|
|
4554
|
+
if (frame.description) u.describe(frame.description);
|
|
4555
|
+
});
|
|
4399
4556
|
}
|
|
4557
|
+
} else {
|
|
4558
|
+
this.replaceHistory(props.history);
|
|
4400
4559
|
}
|
|
4401
|
-
} catch (e) {
|
|
4402
|
-
this.onStatus?.("error", e);
|
|
4403
4560
|
}
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
this.onMessage = onMessage;
|
|
4420
|
-
this.onStatus = onStatus;
|
|
4421
|
-
this.intervalMs = intervalMs;
|
|
4422
|
-
}
|
|
4423
|
-
async connect() {
|
|
4424
|
-
this.closed = false;
|
|
4425
|
-
this.lastETag = null;
|
|
4426
|
-
this.lastContent = "";
|
|
4427
|
-
this.onStatus?.("opened");
|
|
4428
|
-
this.startPolling();
|
|
4429
|
-
}
|
|
4430
|
-
close() {
|
|
4431
|
-
this.closed = true;
|
|
4432
|
-
if (this.timer) {
|
|
4433
|
-
window.clearInterval(this.timer);
|
|
4434
|
-
this.timer = null;
|
|
4561
|
+
const prevCanvas = prev.options?.canvas;
|
|
4562
|
+
const currCanvas = props.options?.canvas;
|
|
4563
|
+
const colorModeChanged = prevCanvas?.colorMode !== currCanvas?.colorMode;
|
|
4564
|
+
if (colorModeChanged && currCanvas?.colorMode) {
|
|
4565
|
+
this.setColorMode(currCanvas.colorMode);
|
|
4566
|
+
}
|
|
4567
|
+
const themeChanged = prevCanvas?.theme !== currCanvas?.theme;
|
|
4568
|
+
const nodeTypesChanged = prevCanvas?.nodeTypes !== currCanvas?.nodeTypes;
|
|
4569
|
+
const edgeTypesChanged = prevCanvas?.edgeTypes !== currCanvas?.edgeTypes;
|
|
4570
|
+
if (themeChanged || nodeTypesChanged || edgeTypesChanged) {
|
|
4571
|
+
this.updateStyles({
|
|
4572
|
+
theme: currCanvas?.theme,
|
|
4573
|
+
nodeTypes: currCanvas?.nodeTypes,
|
|
4574
|
+
edgeTypes: currCanvas?.edgeTypes
|
|
4575
|
+
});
|
|
4435
4576
|
}
|
|
4436
|
-
this.
|
|
4577
|
+
this.prevProps = {
|
|
4578
|
+
nodes: props.nodes,
|
|
4579
|
+
edges: props.edges,
|
|
4580
|
+
history: props.history,
|
|
4581
|
+
options: props.options
|
|
4582
|
+
};
|
|
4437
4583
|
}
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
this.
|
|
4441
|
-
this.
|
|
4584
|
+
/** Cleanup resources when the graph is destroyed */
|
|
4585
|
+
destroy() {
|
|
4586
|
+
this.disconnectIngestion();
|
|
4587
|
+
this.canvas?.destroy();
|
|
4442
4588
|
}
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
const etag = response.headers.get("ETag");
|
|
4459
|
-
if (etag) {
|
|
4460
|
-
this.lastETag = etag;
|
|
4461
|
-
}
|
|
4462
|
-
const content = await response.text();
|
|
4463
|
-
if (content === this.lastContent) {
|
|
4464
|
-
return;
|
|
4465
|
-
}
|
|
4466
|
-
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4467
|
-
const lastContentLines = this.lastContent.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4468
|
-
const newLines = lines.slice(lastContentLines.length);
|
|
4469
|
-
for (const line of newLines) {
|
|
4470
|
-
try {
|
|
4471
|
-
const obj = JSON.parse(line);
|
|
4472
|
-
this.onMessage(obj);
|
|
4473
|
-
} catch {
|
|
4589
|
+
};
|
|
4590
|
+
function shallowEqualArray(a, b) {
|
|
4591
|
+
if (a === b) return true;
|
|
4592
|
+
if (!a || !b) return false;
|
|
4593
|
+
if (a.length !== b.length) return false;
|
|
4594
|
+
for (let i = 0; i < a.length; i++) {
|
|
4595
|
+
if (a[i] !== b[i]) {
|
|
4596
|
+
if (typeof a[i] === "object" && a[i] !== null && typeof b[i] === "object" && b[i] !== null) {
|
|
4597
|
+
const aObj = a[i];
|
|
4598
|
+
const bObj = b[i];
|
|
4599
|
+
const aKeys = Object.keys(aObj);
|
|
4600
|
+
const bKeys = Object.keys(bObj);
|
|
4601
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
4602
|
+
for (const key of aKeys) {
|
|
4603
|
+
if (aObj[key] !== bObj[key]) return false;
|
|
4474
4604
|
}
|
|
4605
|
+
} else {
|
|
4606
|
+
return false;
|
|
4475
4607
|
}
|
|
4476
|
-
this.lastContent = content;
|
|
4477
|
-
} catch (e) {
|
|
4478
|
-
this.onStatus?.("error", e);
|
|
4479
4608
|
}
|
|
4480
4609
|
}
|
|
4481
|
-
|
|
4610
|
+
return true;
|
|
4611
|
+
}
|
|
4612
|
+
function isHistoryPrefix(oldHistory, newHistory) {
|
|
4613
|
+
if (newHistory.length < oldHistory.length) return false;
|
|
4614
|
+
for (let i = 0; i < oldHistory.length; i++) {
|
|
4615
|
+
if (!shallowEqualUpdate(oldHistory[i], newHistory[i])) {
|
|
4616
|
+
return false;
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
return true;
|
|
4620
|
+
}
|
|
4621
|
+
function shallowEqualUpdate(a, b) {
|
|
4622
|
+
if (a === b) return true;
|
|
4623
|
+
if (a.description !== b.description) return false;
|
|
4624
|
+
if (!shallowEqualArray(a.addNodes, b.addNodes)) return false;
|
|
4625
|
+
if (!shallowEqualArray(a.removeNodes, b.removeNodes)) return false;
|
|
4626
|
+
if (!shallowEqualArray(a.updateNodes, b.updateNodes)) return false;
|
|
4627
|
+
if (!shallowEqualArray(a.addEdges, b.addEdges)) return false;
|
|
4628
|
+
if (!shallowEqualArray(a.removeEdges, b.removeEdges)) return false;
|
|
4629
|
+
if (!shallowEqualArray(a.updateEdges, b.updateEdges)) return false;
|
|
4630
|
+
return true;
|
|
4631
|
+
}
|
|
4482
4632
|
|
|
4483
4633
|
// src/playground/playground.ts
|
|
4484
4634
|
var import_styles2 = __toESM(require("./styles.css?raw"), 1);
|
|
@@ -4758,10 +4908,18 @@ var Playground = class {
|
|
|
4758
4908
|
this.disconnectAllSources();
|
|
4759
4909
|
if (example.source.type === "websocket") {
|
|
4760
4910
|
this.wsUrl = example.source.url;
|
|
4761
|
-
this.wsSource = new WebSocketSource(
|
|
4911
|
+
this.wsSource = new WebSocketSource({
|
|
4912
|
+
url: example.source.url,
|
|
4913
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
4914
|
+
onStatus: this.updateWsStatus
|
|
4915
|
+
});
|
|
4762
4916
|
this.wsSource.connect();
|
|
4763
4917
|
} else if (example.source.type === "file") {
|
|
4764
|
-
this.fileSource = new FileSource(
|
|
4918
|
+
this.fileSource = new FileSource({
|
|
4919
|
+
url: example.source.path,
|
|
4920
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
4921
|
+
onStatus: this.updateFileStatus
|
|
4922
|
+
});
|
|
4765
4923
|
this.fileSource.connect();
|
|
4766
4924
|
}
|
|
4767
4925
|
}
|
|
@@ -5161,7 +5319,11 @@ var Playground = class {
|
|
|
5161
5319
|
if (this.wsSource) {
|
|
5162
5320
|
this.wsSource.disconnect();
|
|
5163
5321
|
}
|
|
5164
|
-
this.wsSource = new WebSocketSource(
|
|
5322
|
+
this.wsSource = new WebSocketSource({
|
|
5323
|
+
url,
|
|
5324
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
5325
|
+
onStatus: this.updateWsStatus
|
|
5326
|
+
});
|
|
5165
5327
|
this.wsSource.connect();
|
|
5166
5328
|
this.updateSourceModal();
|
|
5167
5329
|
}
|
|
@@ -5182,7 +5344,11 @@ var Playground = class {
|
|
|
5182
5344
|
}
|
|
5183
5345
|
async handleOpenFolder() {
|
|
5184
5346
|
if (!this.fsSource) {
|
|
5185
|
-
this.fsSource = new FileSystemSource(
|
|
5347
|
+
this.fsSource = new FileSystemSource({
|
|
5348
|
+
filename: "graph.ndjson",
|
|
5349
|
+
onMessage: this.handleIngestMessage.bind(this),
|
|
5350
|
+
onStatus: this.updateFsStatus
|
|
5351
|
+
});
|
|
5186
5352
|
}
|
|
5187
5353
|
this.updateSourceModal();
|
|
5188
5354
|
await this.fsSource.openDirectory();
|