@bian-womp/spark-workbench 0.3.2 → 0.3.4
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/lib/cjs/index.cjs +384 -231
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts +53 -0
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/contracts.d.ts +16 -0
- package/lib/cjs/src/core/contracts.d.ts.map +1 -1
- package/lib/cjs/src/misc/SelectionBoundOverlay.d.ts +10 -0
- package/lib/cjs/src/misc/SelectionBoundOverlay.d.ts.map +1 -0
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts +4 -0
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +0 -3
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/cjs/src/misc/context-menu/NodeContextMenu.d.ts +1 -1
- package/lib/cjs/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -1
- package/lib/cjs/src/misc/load.d.ts.map +1 -1
- package/lib/cjs/src/misc/mapping.d.ts +7 -0
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +6 -4
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +5 -3
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +7 -0
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +4 -0
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +385 -232
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts +53 -0
- package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/contracts.d.ts +16 -0
- package/lib/esm/src/core/contracts.d.ts.map +1 -1
- package/lib/esm/src/misc/SelectionBoundOverlay.d.ts +10 -0
- package/lib/esm/src/misc/SelectionBoundOverlay.d.ts.map +1 -0
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts +4 -0
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +0 -3
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/esm/src/misc/context-menu/NodeContextMenu.d.ts +1 -1
- package/lib/esm/src/misc/context-menu/NodeContextMenu.d.ts.map +1 -1
- package/lib/esm/src/misc/load.d.ts.map +1 -1
- package/lib/esm/src/misc/mapping.d.ts +7 -0
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +6 -4
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +5 -3
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +7 -0
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +4 -0
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -146,6 +146,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
146
146
|
edges: [],
|
|
147
147
|
};
|
|
148
148
|
this.nodeNames = {};
|
|
149
|
+
this.customData = {};
|
|
149
150
|
this.runtimeState = null;
|
|
150
151
|
this.viewport = null;
|
|
151
152
|
this.historyState = undefined;
|
|
@@ -174,6 +175,17 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
174
175
|
const filteredSizes = Object.fromEntries(Object.entries(this.sizes).filter(([id]) => defNodeIds.has(id)));
|
|
175
176
|
const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
|
|
176
177
|
const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
|
|
178
|
+
// Clean up extData for removed nodes/edges
|
|
179
|
+
if (this.customData.nodes) {
|
|
180
|
+
const filteredExtNodes = Object.fromEntries(Object.entries(this.customData.nodes).filter(([id]) => defNodeIds.has(id)));
|
|
181
|
+
this.customData.nodes =
|
|
182
|
+
Object.keys(filteredExtNodes).length > 0 ? filteredExtNodes : undefined;
|
|
183
|
+
}
|
|
184
|
+
if (this.customData.edges) {
|
|
185
|
+
const filteredExtEdges = Object.fromEntries(Object.entries(this.customData.edges).filter(([id]) => defEdgeIds.has(id)));
|
|
186
|
+
this.customData.edges =
|
|
187
|
+
Object.keys(filteredExtEdges).length > 0 ? filteredExtEdges : undefined;
|
|
188
|
+
}
|
|
177
189
|
this.positions = filteredPositions;
|
|
178
190
|
this.sizes = filteredSizes;
|
|
179
191
|
this.selection = { nodes: filteredNodes, edges: filteredEdges };
|
|
@@ -851,6 +863,107 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
851
863
|
...options,
|
|
852
864
|
});
|
|
853
865
|
}
|
|
866
|
+
/**
|
|
867
|
+
* Get custom data for a specific node.
|
|
868
|
+
*/
|
|
869
|
+
getCustomNodeData(nodeId) {
|
|
870
|
+
return this.customData.nodes?.[nodeId];
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Set custom data for a specific node.
|
|
874
|
+
*/
|
|
875
|
+
setCustomNodeData(nodeId, data, options) {
|
|
876
|
+
if (!this.customData.nodes) {
|
|
877
|
+
this.customData.nodes = {};
|
|
878
|
+
}
|
|
879
|
+
if (data === undefined) {
|
|
880
|
+
delete this.customData.nodes[nodeId];
|
|
881
|
+
if (Object.keys(this.customData.nodes).length === 0) {
|
|
882
|
+
delete this.customData.nodes;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
this.customData.nodes[nodeId] = data;
|
|
887
|
+
}
|
|
888
|
+
this.emit("graphUiChanged", {
|
|
889
|
+
change: { type: "customNodeData", nodeId, data },
|
|
890
|
+
...options,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Get custom data for a specific edge.
|
|
895
|
+
*/
|
|
896
|
+
getCustomEdgeData(edgeId) {
|
|
897
|
+
return this.customData.edges?.[edgeId];
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Set custom data for a specific edge.
|
|
901
|
+
*/
|
|
902
|
+
setCustomEdgeData(edgeId, data, options) {
|
|
903
|
+
if (!this.customData.edges) {
|
|
904
|
+
this.customData.edges = {};
|
|
905
|
+
}
|
|
906
|
+
if (data === undefined) {
|
|
907
|
+
delete this.customData.edges[edgeId];
|
|
908
|
+
if (Object.keys(this.customData.edges).length === 0) {
|
|
909
|
+
delete this.customData.edges;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
this.customData.edges[edgeId] = data;
|
|
914
|
+
}
|
|
915
|
+
this.emit("graphUiChanged", {
|
|
916
|
+
change: { type: "customEdgeData", edgeId, data },
|
|
917
|
+
...options,
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Get custom metadata.
|
|
922
|
+
*/
|
|
923
|
+
getCustomMetaData() {
|
|
924
|
+
return this.customData.meta;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Set custom metadata.
|
|
928
|
+
*/
|
|
929
|
+
setCustomMetaData(meta, options) {
|
|
930
|
+
if (meta === undefined) {
|
|
931
|
+
delete this.customData.meta;
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
this.customData.meta = meta;
|
|
935
|
+
}
|
|
936
|
+
this.emit("graphUiChanged", {
|
|
937
|
+
change: { type: "customMetaData", meta },
|
|
938
|
+
...options,
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Get all custom data.
|
|
943
|
+
*/
|
|
944
|
+
getCustomData() {
|
|
945
|
+
return { ...this.customData };
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Set all custom data.
|
|
949
|
+
*/
|
|
950
|
+
setCustomData(custom, options) {
|
|
951
|
+
if (custom === undefined) {
|
|
952
|
+
this.customData = {};
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
this.customData = lod.pick(custom, ["nodes", "edges", "meta"]);
|
|
956
|
+
}
|
|
957
|
+
this.emit("graphUiChanged", {
|
|
958
|
+
change: {
|
|
959
|
+
type: "customData",
|
|
960
|
+
nodes: custom?.nodes,
|
|
961
|
+
edges: custom?.edges,
|
|
962
|
+
meta: custom?.meta,
|
|
963
|
+
},
|
|
964
|
+
...options,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
854
967
|
}
|
|
855
968
|
|
|
856
969
|
class CLIWorkbench {
|
|
@@ -911,36 +1024,14 @@ class AbstractGraphRunner {
|
|
|
911
1024
|
this.stagedInputs = {};
|
|
912
1025
|
this.runnerId = "";
|
|
913
1026
|
}
|
|
914
|
-
launch(def, opts) {
|
|
915
|
-
// Auto-stop if engine is already running
|
|
916
|
-
if (this.engine) {
|
|
917
|
-
this.stop();
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
1027
|
async whenIdle() {
|
|
921
1028
|
await this.engine?.whenIdle();
|
|
922
1029
|
}
|
|
923
|
-
stop() {
|
|
924
|
-
if (!this.engine)
|
|
925
|
-
return;
|
|
926
|
-
// Dispose engine (cleans up timers, listeners, etc.)
|
|
927
|
-
this.engine.dispose();
|
|
928
|
-
this.engine = undefined;
|
|
929
|
-
// Emit status but keep runtime alive
|
|
930
|
-
if (this.runMode) {
|
|
931
|
-
this.runMode = undefined;
|
|
932
|
-
this.emit("status", { running: false, runMode: undefined });
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
1030
|
setRunMode(runMode) {
|
|
936
|
-
if (
|
|
937
|
-
|
|
1031
|
+
if (this.engine) {
|
|
1032
|
+
this.engine.setRunMode(runMode);
|
|
1033
|
+
this.emit("status", { running: true, runMode });
|
|
938
1034
|
}
|
|
939
|
-
// Update engine run mode (this will update pause/resume state)
|
|
940
|
-
this.engine.setRunMode(runMode);
|
|
941
|
-
// Update local state and emit status event
|
|
942
|
-
this.runMode = runMode;
|
|
943
|
-
this.emit("status", { running: true, runMode: this.runMode });
|
|
944
1035
|
}
|
|
945
1036
|
getInputDefaults(def) {
|
|
946
1037
|
const out = {};
|
|
@@ -970,17 +1061,10 @@ class AbstractGraphRunner {
|
|
|
970
1061
|
this.engine = undefined;
|
|
971
1062
|
this.runtime?.dispose();
|
|
972
1063
|
this.runtime = undefined;
|
|
973
|
-
if (this.runMode) {
|
|
974
|
-
this.runMode = undefined;
|
|
975
|
-
this.emit("status", { running: false, runMode: undefined });
|
|
976
|
-
}
|
|
977
1064
|
}
|
|
978
1065
|
isRunning() {
|
|
979
1066
|
return !!this.engine;
|
|
980
1067
|
}
|
|
981
|
-
getRunMode() {
|
|
982
|
-
return this.runMode;
|
|
983
|
-
}
|
|
984
1068
|
// Optional undo/redo support
|
|
985
1069
|
async undo() {
|
|
986
1070
|
return false;
|
|
@@ -999,6 +1083,7 @@ let localRunnerCounter = 0;
|
|
|
999
1083
|
class LocalGraphRunner extends AbstractGraphRunner {
|
|
1000
1084
|
constructor(registry) {
|
|
1001
1085
|
super(registry, { kind: "local" });
|
|
1086
|
+
this.extData = {};
|
|
1002
1087
|
this.setEnvironment = (env, opts) => {
|
|
1003
1088
|
if (!this.runtime)
|
|
1004
1089
|
return;
|
|
@@ -1047,7 +1132,10 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1047
1132
|
}
|
|
1048
1133
|
}
|
|
1049
1134
|
launch(def, opts) {
|
|
1050
|
-
|
|
1135
|
+
if (this.engine) {
|
|
1136
|
+
this.engine.dispose();
|
|
1137
|
+
this.engine = undefined;
|
|
1138
|
+
}
|
|
1051
1139
|
this.build(def);
|
|
1052
1140
|
if (!this.runtime)
|
|
1053
1141
|
throw new Error("Runtime not built");
|
|
@@ -1074,8 +1162,8 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1074
1162
|
this.engine.on("invalidate", (e) => this.emit("invalidate", e));
|
|
1075
1163
|
this.engine.on("stats", (e) => this.emit("stats", e));
|
|
1076
1164
|
this.engine.launch(opts?.invalidate);
|
|
1077
|
-
|
|
1078
|
-
this.emit("status", { running: true, runMode
|
|
1165
|
+
const runMode = opts?.runMode ?? "manual";
|
|
1166
|
+
this.emit("status", { running: true, runMode });
|
|
1079
1167
|
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
1080
1168
|
this.engine.setInputs(nodeId, map);
|
|
1081
1169
|
}
|
|
@@ -1169,6 +1257,24 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1169
1257
|
this.engine.copyOutputs(fromNodeId, toNodeId, options);
|
|
1170
1258
|
}
|
|
1171
1259
|
}
|
|
1260
|
+
async setExtData(data) {
|
|
1261
|
+
if (!data || typeof data !== "object") {
|
|
1262
|
+
this.extData = {};
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
this.extData = { ...this.extData, ...data };
|
|
1266
|
+
}
|
|
1267
|
+
async updateExtData(updates) {
|
|
1268
|
+
if (!this.extData ||
|
|
1269
|
+
typeof this.extData !== "object" ||
|
|
1270
|
+
Array.isArray(this.extData)) {
|
|
1271
|
+
this.extData = {};
|
|
1272
|
+
}
|
|
1273
|
+
for (const { path, value } of updates) {
|
|
1274
|
+
const pathSegments = sparkGraph.parseJsonPath(path);
|
|
1275
|
+
sparkGraph.setValueAtPathWithCreation(this.extData, pathSegments, value);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1172
1278
|
async snapshotFull() {
|
|
1173
1279
|
const def = undefined; // UI will supply def/positions on download for local
|
|
1174
1280
|
const inputs = this.getInputs(this.runtime
|
|
@@ -1190,7 +1296,8 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1190
1296
|
}
|
|
1191
1297
|
: { nodes: [], edges: [] });
|
|
1192
1298
|
const environment = this.getEnvironment() || {};
|
|
1193
|
-
|
|
1299
|
+
const extData = this.extData;
|
|
1300
|
+
return { def, environment, inputs, outputs, extData };
|
|
1194
1301
|
}
|
|
1195
1302
|
async applySnapshotFull(payload, options) {
|
|
1196
1303
|
if (payload.def && !options?.skipBuild) {
|
|
@@ -1198,6 +1305,9 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1198
1305
|
}
|
|
1199
1306
|
this.setEnvironment?.(payload.environment || {}, { merge: false });
|
|
1200
1307
|
this.hydrate(payload, { dry: options?.dry });
|
|
1308
|
+
if (payload.extData) {
|
|
1309
|
+
await this.setExtData(payload.extData);
|
|
1310
|
+
}
|
|
1201
1311
|
}
|
|
1202
1312
|
hydrate(snapshot, opts) {
|
|
1203
1313
|
// Hydrate via runtime for exact restore (this emits events on runtime emitter)
|
|
@@ -1567,7 +1677,10 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1567
1677
|
}
|
|
1568
1678
|
}
|
|
1569
1679
|
launch(def, opts) {
|
|
1570
|
-
|
|
1680
|
+
if (this.engine) {
|
|
1681
|
+
this.engine.dispose();
|
|
1682
|
+
this.engine = undefined;
|
|
1683
|
+
}
|
|
1571
1684
|
// Remote: build remotely then launch
|
|
1572
1685
|
this.ensureClient().then(async (client) => {
|
|
1573
1686
|
await client.api.build(def);
|
|
@@ -1623,8 +1736,8 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1623
1736
|
this.listenersBound = true;
|
|
1624
1737
|
}
|
|
1625
1738
|
this.engine = eng;
|
|
1626
|
-
|
|
1627
|
-
this.emit("status", { running: true, runMode
|
|
1739
|
+
const runMode = opts?.runMode ?? "manual";
|
|
1740
|
+
this.emit("status", { running: true, runMode });
|
|
1628
1741
|
// Re-apply staged inputs using client.setInputs for consistency
|
|
1629
1742
|
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
1630
1743
|
await eng.setInputs(nodeId, map, undefined).catch(() => {
|
|
@@ -1640,7 +1753,10 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1640
1753
|
* the runtime state that was just restored.
|
|
1641
1754
|
*/
|
|
1642
1755
|
launchExisting(def, opts) {
|
|
1643
|
-
|
|
1756
|
+
if (this.engine) {
|
|
1757
|
+
this.engine.dispose();
|
|
1758
|
+
this.engine = undefined;
|
|
1759
|
+
}
|
|
1644
1760
|
// Remote: attach to existing runtime and launch (do NOT rebuild)
|
|
1645
1761
|
this.ensureClient().then(async (client) => {
|
|
1646
1762
|
// NOTE: We do NOT call client.build(def) here because the backend runtime
|
|
@@ -1651,14 +1767,10 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1651
1767
|
});
|
|
1652
1768
|
}
|
|
1653
1769
|
setRunMode(runMode) {
|
|
1654
|
-
if (
|
|
1655
|
-
|
|
1770
|
+
if (this.engine) {
|
|
1771
|
+
this.engine.setRunMode(runMode);
|
|
1772
|
+
this.emit("status", { running: true, runMode });
|
|
1656
1773
|
}
|
|
1657
|
-
// Update engine run mode (sends SetRunMode command to backend)
|
|
1658
|
-
this.engine.setRunMode(runMode);
|
|
1659
|
-
// Update local state and emit status event
|
|
1660
|
-
this.runMode = runMode;
|
|
1661
|
-
this.emit("status", { running: true, runMode: this.runMode });
|
|
1662
1774
|
}
|
|
1663
1775
|
async computeNode(nodeId, options) {
|
|
1664
1776
|
const client = await this.ensureClient();
|
|
@@ -1721,6 +1833,10 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1721
1833
|
const client = await this.ensureClient();
|
|
1722
1834
|
await client.api.setExtData(data);
|
|
1723
1835
|
}
|
|
1836
|
+
async updateExtData(updates) {
|
|
1837
|
+
const client = await this.ensureClient();
|
|
1838
|
+
await client.api.updateExtData(updates);
|
|
1839
|
+
}
|
|
1724
1840
|
async commit(reason) {
|
|
1725
1841
|
const client = await this.ensureClient();
|
|
1726
1842
|
try {
|
|
@@ -2592,35 +2708,40 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2592
2708
|
}));
|
|
2593
2709
|
const handleLayout = geom.handleLayout;
|
|
2594
2710
|
const handles = geom.handles;
|
|
2711
|
+
const baseData = {
|
|
2712
|
+
typeId: n.typeId,
|
|
2713
|
+
params: n.params,
|
|
2714
|
+
inputHandles,
|
|
2715
|
+
outputHandles,
|
|
2716
|
+
inputConnected: Object.fromEntries(inputHandles.map((h) => [
|
|
2717
|
+
h.id,
|
|
2718
|
+
!!connectedInputs[n.nodeId]?.has(h.id),
|
|
2719
|
+
])),
|
|
2720
|
+
handleLayout,
|
|
2721
|
+
showValues: opts.showValues,
|
|
2722
|
+
renderWidth,
|
|
2723
|
+
renderHeight,
|
|
2724
|
+
initialWidth: initialGeom.width,
|
|
2725
|
+
initialHeight: initialGeom.height,
|
|
2726
|
+
inputValues: opts.inputs?.[n.nodeId],
|
|
2727
|
+
inputDefaults: opts.inputDefaults?.[n.nodeId],
|
|
2728
|
+
outputValues: opts.outputs?.[n.nodeId],
|
|
2729
|
+
status: opts.nodeStatus?.[n.nodeId],
|
|
2730
|
+
validation: {
|
|
2731
|
+
inputs: opts.nodeValidation?.inputs?.[n.nodeId] ?? [],
|
|
2732
|
+
outputs: opts.nodeValidation?.outputs?.[n.nodeId] ?? [],
|
|
2733
|
+
issues: opts.nodeValidation?.issues?.[n.nodeId] ?? [],
|
|
2734
|
+
},
|
|
2735
|
+
toString: opts.toString,
|
|
2736
|
+
toElement: opts.toElement,
|
|
2737
|
+
};
|
|
2738
|
+
const customNodeData = opts.customData?.nodes;
|
|
2739
|
+
const mergedData = customNodeData?.[n.nodeId]
|
|
2740
|
+
? { ...baseData, custom: customNodeData[n.nodeId] }
|
|
2741
|
+
: baseData;
|
|
2595
2742
|
return {
|
|
2596
2743
|
id: n.nodeId,
|
|
2597
|
-
data:
|
|
2598
|
-
typeId: n.typeId,
|
|
2599
|
-
params: n.params,
|
|
2600
|
-
inputHandles,
|
|
2601
|
-
outputHandles,
|
|
2602
|
-
inputConnected: Object.fromEntries(inputHandles.map((h) => [
|
|
2603
|
-
h.id,
|
|
2604
|
-
!!connectedInputs[n.nodeId]?.has(h.id),
|
|
2605
|
-
])),
|
|
2606
|
-
handleLayout,
|
|
2607
|
-
showValues: opts.showValues,
|
|
2608
|
-
renderWidth,
|
|
2609
|
-
renderHeight,
|
|
2610
|
-
initialWidth: initialGeom.width,
|
|
2611
|
-
initialHeight: initialGeom.height,
|
|
2612
|
-
inputValues: opts.inputs?.[n.nodeId],
|
|
2613
|
-
inputDefaults: opts.inputDefaults?.[n.nodeId],
|
|
2614
|
-
outputValues: opts.outputs?.[n.nodeId],
|
|
2615
|
-
status: opts.nodeStatus?.[n.nodeId],
|
|
2616
|
-
validation: {
|
|
2617
|
-
inputs: opts.nodeValidation?.inputs?.[n.nodeId] ?? [],
|
|
2618
|
-
outputs: opts.nodeValidation?.outputs?.[n.nodeId] ?? [],
|
|
2619
|
-
issues: opts.nodeValidation?.issues?.[n.nodeId] ?? [],
|
|
2620
|
-
},
|
|
2621
|
-
toString: opts.toString,
|
|
2622
|
-
toElement: opts.toElement,
|
|
2623
|
-
},
|
|
2744
|
+
data: mergedData,
|
|
2624
2745
|
position: positions[n.nodeId] ?? { x: 0, y: 0 },
|
|
2625
2746
|
type: opts.resolveNodeType?.(n.typeId) ?? "spark-default",
|
|
2626
2747
|
selected: opts.selectedNodeIds
|
|
@@ -2663,6 +2784,25 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2663
2784
|
const targetHandleTypeId = targetHandles?.inputs[e.target.handle]
|
|
2664
2785
|
? sparkGraph.getInputTypeId(targetHandles.inputs, e.target.handle) ?? "unknown"
|
|
2665
2786
|
: "unknown";
|
|
2787
|
+
const baseEdgeData = {
|
|
2788
|
+
sourceNodeId: e.source.nodeId,
|
|
2789
|
+
sourceNodeTypeId: sourceNode?.typeId || "unknown",
|
|
2790
|
+
sourceHandle: e.source.handle,
|
|
2791
|
+
sourceHandleTypeId,
|
|
2792
|
+
targetNodeId: e.target.nodeId,
|
|
2793
|
+
targetNodeTypeId: targetNode?.typeId || "unknown",
|
|
2794
|
+
targetHandle: e.target.handle,
|
|
2795
|
+
targetHandleTypeId,
|
|
2796
|
+
edgeTypeId,
|
|
2797
|
+
isRunning,
|
|
2798
|
+
hasError,
|
|
2799
|
+
isInvalid: isInvalidEdge,
|
|
2800
|
+
isMissing,
|
|
2801
|
+
};
|
|
2802
|
+
const customEdgeData = opts.customData?.edges;
|
|
2803
|
+
const mergedEdgeData = customEdgeData?.[e.id]
|
|
2804
|
+
? { ...baseEdgeData, custom: customEdgeData[e.id] }
|
|
2805
|
+
: baseEdgeData;
|
|
2666
2806
|
return {
|
|
2667
2807
|
id: e.id,
|
|
2668
2808
|
source: e.source.nodeId,
|
|
@@ -2676,21 +2816,7 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2676
2816
|
style,
|
|
2677
2817
|
label: edgeTypeId,
|
|
2678
2818
|
type: "default",
|
|
2679
|
-
data:
|
|
2680
|
-
sourceNodeId: e.source.nodeId,
|
|
2681
|
-
sourceNodeTypeId: sourceNode?.typeId || "unknown",
|
|
2682
|
-
sourceHandle: e.source.handle,
|
|
2683
|
-
sourceHandleTypeId,
|
|
2684
|
-
targetNodeId: e.target.nodeId,
|
|
2685
|
-
targetNodeTypeId: targetNode?.typeId || "unknown",
|
|
2686
|
-
targetHandle: e.target.handle,
|
|
2687
|
-
targetHandleTypeId,
|
|
2688
|
-
edgeTypeId,
|
|
2689
|
-
isRunning,
|
|
2690
|
-
hasError,
|
|
2691
|
-
isInvalid: isInvalidEdge,
|
|
2692
|
-
isMissing,
|
|
2693
|
-
},
|
|
2819
|
+
data: mergedEdgeData,
|
|
2694
2820
|
};
|
|
2695
2821
|
});
|
|
2696
2822
|
return { nodes, edges };
|
|
@@ -2846,6 +2972,9 @@ async function upload(parsed, wb, runner) {
|
|
|
2846
2972
|
if (extData.runtime && typeof extData.runtime === "object") {
|
|
2847
2973
|
wb.setRuntimeState(extData.runtime);
|
|
2848
2974
|
}
|
|
2975
|
+
if (extData.custom && typeof extData.custom === "object") {
|
|
2976
|
+
wb.setCustomData(extData.custom);
|
|
2977
|
+
}
|
|
2849
2978
|
if (runner.isRunning()) {
|
|
2850
2979
|
await runner.applySnapshotFull({
|
|
2851
2980
|
def: wb.def,
|
|
@@ -2862,6 +2991,9 @@ async function upload(parsed, wb, runner) {
|
|
|
2862
2991
|
runner.setInputs(nodeId, map, { dry: true });
|
|
2863
2992
|
}
|
|
2864
2993
|
}
|
|
2994
|
+
if (extData) {
|
|
2995
|
+
await runner.setExtData(extData);
|
|
2996
|
+
}
|
|
2865
2997
|
}
|
|
2866
2998
|
}
|
|
2867
2999
|
|
|
@@ -3658,15 +3790,7 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
3658
3790
|
const graphUiTick = useWorkbenchGraphUiTick(wb);
|
|
3659
3791
|
const versionTick = useWorkbenchVersionTick(runner);
|
|
3660
3792
|
const valuesTick = versionTick + graphTick + graphUiTick;
|
|
3661
|
-
// Keep local runMode state loosely in sync with runner status.
|
|
3662
|
-
// - Seed from runner.getRunMode() on mount if available.
|
|
3663
|
-
// - On status events, update only when a non-undefined runMode is reported,
|
|
3664
|
-
// so the UI preserves the last selected mode after stop().
|
|
3665
3793
|
React.useEffect(() => {
|
|
3666
|
-
const initialMode = runner.getRunMode();
|
|
3667
|
-
if (initialMode) {
|
|
3668
|
-
setRunModeState(initialMode);
|
|
3669
|
-
}
|
|
3670
3794
|
const offRunnerStatus = runner.on("status", (status) => {
|
|
3671
3795
|
if (status.runMode) {
|
|
3672
3796
|
setRunModeState(status.runMode);
|
|
@@ -3826,12 +3950,13 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
3826
3950
|
workbench.setRuntimeState(metadata);
|
|
3827
3951
|
const fullUiState = workbench.getUIState();
|
|
3828
3952
|
const uiWithoutViewport = excludeViewportFromUIState(fullUiState);
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
});
|
|
3953
|
+
// Use updateExtData for efficient batched partial updates
|
|
3954
|
+
const updates = [];
|
|
3955
|
+
if (Object.keys(uiWithoutViewport || {}).length > 0) {
|
|
3956
|
+
updates.push({ path: "ui", value: uiWithoutViewport });
|
|
3957
|
+
}
|
|
3958
|
+
updates.push({ path: "runtime", value: metadata });
|
|
3959
|
+
await graphRunner.updateExtData(updates);
|
|
3835
3960
|
}
|
|
3836
3961
|
catch (err) {
|
|
3837
3962
|
console.warn("[WorkbenchContext] Failed to save runtime metadata:", err);
|
|
@@ -4264,6 +4389,18 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4264
4389
|
else if (changeType === "selection") {
|
|
4265
4390
|
reason = "selection";
|
|
4266
4391
|
}
|
|
4392
|
+
else if (changeType === "customNodeData") {
|
|
4393
|
+
reason = "custom-node-data";
|
|
4394
|
+
}
|
|
4395
|
+
else if (changeType === "customEdgeData") {
|
|
4396
|
+
reason = "custom-edge-data";
|
|
4397
|
+
}
|
|
4398
|
+
else if (changeType === "customMetaData") {
|
|
4399
|
+
reason = "custom-meta";
|
|
4400
|
+
}
|
|
4401
|
+
else if (changeType === "customData") {
|
|
4402
|
+
reason = "custom";
|
|
4403
|
+
}
|
|
4267
4404
|
}
|
|
4268
4405
|
await saveUiRuntimeMetadata(wb, runner);
|
|
4269
4406
|
const history = await runner
|
|
@@ -4369,27 +4506,11 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4369
4506
|
};
|
|
4370
4507
|
}, [runner, wb]);
|
|
4371
4508
|
const isRunning = React.useCallback(() => runner.isRunning(), [runner]);
|
|
4372
|
-
const getRunMode = React.useCallback(() => runner.getRunMode(), [runner]);
|
|
4373
|
-
const stop = React.useCallback(() => runner.stop(), [runner]);
|
|
4374
|
-
// Run mode actions
|
|
4375
4509
|
const setRunMode = React.useCallback((mode) => {
|
|
4376
4510
|
if (mode === runMode)
|
|
4377
4511
|
return;
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
// Use setRunMode to change run mode without rebuilding
|
|
4381
|
-
try {
|
|
4382
|
-
runner.setRunMode(mode);
|
|
4383
|
-
setRunModeState(mode);
|
|
4384
|
-
}
|
|
4385
|
-
catch (err) {
|
|
4386
|
-
console.error("Failed to set run mode:", err);
|
|
4387
|
-
}
|
|
4388
|
-
}
|
|
4389
|
-
else {
|
|
4390
|
-
// Just update state if not running (will be applied on next launch)
|
|
4391
|
-
setRunModeState(mode);
|
|
4392
|
-
}
|
|
4512
|
+
runner.setRunMode(mode);
|
|
4513
|
+
setRunModeState(mode);
|
|
4393
4514
|
}, [runMode, runner]);
|
|
4394
4515
|
const runNodeAction = React.useCallback(async (nodeId) => {
|
|
4395
4516
|
await runner.computeNode(nodeId);
|
|
@@ -4491,8 +4612,6 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4491
4612
|
removeRegistryError,
|
|
4492
4613
|
removeInputValidationError,
|
|
4493
4614
|
isRunning,
|
|
4494
|
-
getRunMode,
|
|
4495
|
-
stop,
|
|
4496
4615
|
runMode,
|
|
4497
4616
|
setRunMode,
|
|
4498
4617
|
runNode: runNodeAction,
|
|
@@ -4533,8 +4652,6 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4533
4652
|
events,
|
|
4534
4653
|
clearEvents,
|
|
4535
4654
|
isRunning,
|
|
4536
|
-
getRunMode,
|
|
4537
|
-
stop,
|
|
4538
4655
|
runMode,
|
|
4539
4656
|
setRunMode,
|
|
4540
4657
|
runNodeAction,
|
|
@@ -5401,7 +5518,7 @@ function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, enab
|
|
|
5401
5518
|
!handlers.onRedo && jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node", " ", jsxRuntime.jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsxRuntime.jsx("div", { className: "px-2 pb-1", children: jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Filter nodes...", className: "w-full border border-gray-300 rounded px-2 py-1 text-sm outline-none focus:border-gray-400 select-text", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation() }) }), jsxRuntime.jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? (renderTree(root)) : (jsxRuntime.jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" })) })] }));
|
|
5402
5519
|
}
|
|
5403
5520
|
|
|
5404
|
-
function NodeContextMenu({ open, clientPos, nodeId, handlers, bakeableOutputs, runMode, enableKeyboardShortcuts = true, keyboardShortcuts = {
|
|
5521
|
+
function NodeContextMenu({ open, clientPos, nodeId, handlers, bakeableOutputs, runMode, wb, enableKeyboardShortcuts = true, keyboardShortcuts = {
|
|
5405
5522
|
copy: "⌘/Ctrl + C",
|
|
5406
5523
|
duplicate: "⌘/Ctrl + E",
|
|
5407
5524
|
duplicateWithEdges: "⌘/Ctrl + Shift + E",
|
|
@@ -5435,6 +5552,10 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, bakeableOutputs, r
|
|
|
5435
5552
|
}, [open]);
|
|
5436
5553
|
if (!open || !clientPos || !nodeId)
|
|
5437
5554
|
return null;
|
|
5555
|
+
// Determine if this is a start node (no inbound edges)
|
|
5556
|
+
const isStartNode = wb
|
|
5557
|
+
? !wb.def.edges.some((e) => e.target.nodeId === nodeId)
|
|
5558
|
+
: false;
|
|
5438
5559
|
// clamp
|
|
5439
5560
|
const MENU_MIN_WIDTH = 180;
|
|
5440
5561
|
const PADDING = 16;
|
|
@@ -5444,7 +5565,7 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, bakeableOutputs, r
|
|
|
5444
5565
|
return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700 select-none", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
|
|
5445
5566
|
e.preventDefault();
|
|
5446
5567
|
e.stopPropagation();
|
|
5447
|
-
}, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate", onClick: handlers.onDuplicate, shortcut: keyboardShortcuts.duplicate, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate with edges", onClick: handlers.onDuplicateWithEdges, shortcut: keyboardShortcuts.duplicateWithEdges, enableKeyboardShortcuts: enableKeyboardShortcuts }), runMode === "manual" && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [handlers.onRunNode && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunNode, children: "Run node" })), handlers.onRunFromHere && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunFromHere, children: "Run from here" }))] })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" }), bakeableOutputs.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxRuntime.jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h)))] }))] }));
|
|
5568
|
+
}, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx(ContextMenuButton, { label: "Delete", onClick: handlers.onDelete, shortcut: keyboardShortcuts.delete, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate", onClick: handlers.onDuplicate, shortcut: keyboardShortcuts.duplicate, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx(ContextMenuButton, { label: "Duplicate with edges", onClick: handlers.onDuplicateWithEdges, shortcut: keyboardShortcuts.duplicateWithEdges, enableKeyboardShortcuts: enableKeyboardShortcuts }), runMode === "manual" && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [handlers.onRunNode && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunNode, children: "Run node" })), handlers.onRunFromHere && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunFromHere, children: isStartNode ? "Run workflow" : "Run from here" }))] })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx(ContextMenuButton, { label: "Copy", onClick: handlers.onCopy, shortcut: keyboardShortcuts.copy, enableKeyboardShortcuts: enableKeyboardShortcuts }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" }), bakeableOutputs.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxRuntime.jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h)))] }))] }));
|
|
5448
5569
|
}
|
|
5449
5570
|
|
|
5450
5571
|
function SelectionContextMenu({ open, clientPos, handlers, enableKeyboardShortcuts = true, keyboardShortcuts = {
|
|
@@ -5530,7 +5651,56 @@ function useKeyboardShortcutToast() {
|
|
|
5530
5651
|
return { toast, showToast, hideToast };
|
|
5531
5652
|
}
|
|
5532
5653
|
|
|
5533
|
-
const
|
|
5654
|
+
const SelectionBoundOverlay = ({ selection, rfInstance }) => {
|
|
5655
|
+
const selectionBounds = React.useMemo(() => {
|
|
5656
|
+
if (typeof document === "undefined" ||
|
|
5657
|
+
!rfInstance ||
|
|
5658
|
+
selection.nodes.length < 2) {
|
|
5659
|
+
return null;
|
|
5660
|
+
}
|
|
5661
|
+
let bounds = null;
|
|
5662
|
+
for (const nodeId of selection.nodes) {
|
|
5663
|
+
const el = document.querySelector(`.react-flow__node[data-id="${nodeId}"]`);
|
|
5664
|
+
if (!el)
|
|
5665
|
+
continue;
|
|
5666
|
+
const rect = el.getBoundingClientRect();
|
|
5667
|
+
if (!bounds) {
|
|
5668
|
+
bounds = {
|
|
5669
|
+
left: rect.left,
|
|
5670
|
+
top: rect.top,
|
|
5671
|
+
right: rect.right,
|
|
5672
|
+
bottom: rect.bottom,
|
|
5673
|
+
};
|
|
5674
|
+
}
|
|
5675
|
+
else {
|
|
5676
|
+
bounds.left = Math.min(bounds.left, rect.left);
|
|
5677
|
+
bounds.top = Math.min(bounds.top, rect.top);
|
|
5678
|
+
bounds.right = Math.max(bounds.right, rect.right);
|
|
5679
|
+
bounds.bottom = Math.max(bounds.bottom, rect.bottom);
|
|
5680
|
+
}
|
|
5681
|
+
}
|
|
5682
|
+
return bounds;
|
|
5683
|
+
}, [selection.nodes, rfInstance]);
|
|
5684
|
+
if (!selectionBounds || selection.nodes.length < 2) {
|
|
5685
|
+
return null;
|
|
5686
|
+
}
|
|
5687
|
+
const { left, top, right, bottom } = selectionBounds;
|
|
5688
|
+
const width = right - left;
|
|
5689
|
+
const height = bottom - top;
|
|
5690
|
+
return (jsxRuntime.jsx("div", { style: {
|
|
5691
|
+
position: "fixed",
|
|
5692
|
+
left: `${left}px`,
|
|
5693
|
+
top: `${top}px`,
|
|
5694
|
+
width: `${width}px`,
|
|
5695
|
+
height: `${height}px`,
|
|
5696
|
+
border: "1px dashed #0ea5e9",
|
|
5697
|
+
pointerEvents: "none",
|
|
5698
|
+
zIndex: 4,
|
|
5699
|
+
boxSizing: "border-box",
|
|
5700
|
+
} }));
|
|
5701
|
+
};
|
|
5702
|
+
|
|
5703
|
+
const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize, reactFlowProps }, ref) => {
|
|
5534
5704
|
const { wb, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, registryVersion, runner, overrides, runNode, runFromHere, runMode, } = useWorkbenchContext();
|
|
5535
5705
|
const nodeValidation = validationByNode;
|
|
5536
5706
|
const edgeValidation = validationByEdge.errors;
|
|
@@ -5568,6 +5738,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5568
5738
|
status: n.data.status,
|
|
5569
5739
|
validation: n.data.validation,
|
|
5570
5740
|
inputConnected: n.data.inputConnected,
|
|
5741
|
+
custom: n.data.custom,
|
|
5571
5742
|
},
|
|
5572
5743
|
});
|
|
5573
5744
|
return lod.isEqual(pick(a), pick(b));
|
|
@@ -5583,6 +5754,14 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5583
5754
|
style: e.style,
|
|
5584
5755
|
label: e.label,
|
|
5585
5756
|
type: e.type,
|
|
5757
|
+
data: e.data && {
|
|
5758
|
+
edgeTypeId: e.data.edgeTypeId,
|
|
5759
|
+
isRunning: e.data.isRunning,
|
|
5760
|
+
hasError: e.data.hasError,
|
|
5761
|
+
isInvalid: e.data.isInvalid,
|
|
5762
|
+
isMissing: e.data.isMissing,
|
|
5763
|
+
custom: e.data.custom,
|
|
5764
|
+
},
|
|
5586
5765
|
});
|
|
5587
5766
|
return lod.isEqual(pick(a), pick(b));
|
|
5588
5767
|
};
|
|
@@ -5636,27 +5815,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5636
5815
|
}, [uiVersion, ui]);
|
|
5637
5816
|
const { nodes, edges } = React.useMemo(() => {
|
|
5638
5817
|
const sel = wb.getSelection();
|
|
5639
|
-
// Merge defaults with inputs for node display (defaults shown in lighter gray)
|
|
5640
|
-
const inputsWithDefaults = {};
|
|
5641
|
-
for (const n of wb.def.nodes) {
|
|
5642
|
-
const nodeInputs = inputsMap[n.nodeId] ?? {};
|
|
5643
|
-
const nodeDefaults = inputDefaultsMap[n.nodeId] ?? {};
|
|
5644
|
-
const inbound = new Set(wb.def.edges
|
|
5645
|
-
.filter((e) => e.target.nodeId === n.nodeId)
|
|
5646
|
-
.map((e) => e.target.handle));
|
|
5647
|
-
const merged = { ...nodeInputs };
|
|
5648
|
-
for (const [h, v] of Object.entries(nodeDefaults)) {
|
|
5649
|
-
if (!inbound.has(h) && merged[h] === undefined) {
|
|
5650
|
-
merged[h] = v;
|
|
5651
|
-
}
|
|
5652
|
-
}
|
|
5653
|
-
if (Object.keys(merged).length > 0) {
|
|
5654
|
-
inputsWithDefaults[n.nodeId] = merged;
|
|
5655
|
-
}
|
|
5656
|
-
}
|
|
5657
5818
|
const out = toReactFlow(wb.def, wb.getPositions(), wb.getSizes(), wb.registry, {
|
|
5658
5819
|
showValues,
|
|
5659
|
-
inputs:
|
|
5820
|
+
inputs: inputsMap,
|
|
5660
5821
|
inputDefaults: inputDefaultsMap,
|
|
5661
5822
|
outputs: outputsMap,
|
|
5662
5823
|
resolveNodeType,
|
|
@@ -5670,6 +5831,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5670
5831
|
selectedEdgeIds: new Set(sel.edges),
|
|
5671
5832
|
getDefaultNodeSize,
|
|
5672
5833
|
ui,
|
|
5834
|
+
customData: wb.getCustomData(),
|
|
5673
5835
|
});
|
|
5674
5836
|
// Retain references for unchanged items
|
|
5675
5837
|
const stableNodes = retainStabilityById(prevNodesRef.current, out.nodes, isSameNode);
|
|
@@ -5774,13 +5936,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5774
5936
|
resolveNodeType,
|
|
5775
5937
|
]);
|
|
5776
5938
|
const throttled = useThrottledValue({ nodes, edges }, 100);
|
|
5777
|
-
const [
|
|
5778
|
-
const [menuPos, setMenuPos] = React.useState(null);
|
|
5779
|
-
const [nodeMenuOpen, setNodeMenuOpen] = React.useState(false);
|
|
5780
|
-
const [nodeMenuPos, setNodeMenuPos] = React.useState(null);
|
|
5781
|
-
const [nodeAtMenu, setNodeAtMenu] = React.useState(null);
|
|
5782
|
-
const [selectionMenuPos, setSelectionMenuPos] = React.useState(null);
|
|
5783
|
-
const [selectionMenuOpen, setSelectionMenuOpen] = React.useState(false);
|
|
5939
|
+
const [menuState, setMenuState] = React.useState(null);
|
|
5784
5940
|
// Compute the rectangular screen-space bounds of the current selection
|
|
5785
5941
|
const getSelectionScreenBounds = () => {
|
|
5786
5942
|
if (typeof document === "undefined")
|
|
@@ -5821,31 +5977,24 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5821
5977
|
if (target) {
|
|
5822
5978
|
// Resolve node id from data-id attribute React Flow sets
|
|
5823
5979
|
const nodeId = target.getAttribute("data-id");
|
|
5824
|
-
|
|
5825
|
-
if (isSelected && isSingleNodeSelected) {
|
|
5826
|
-
// Right-clicked on the single selected node - show node menu
|
|
5827
|
-
setNodeAtMenu(nodeId);
|
|
5828
|
-
setNodeMenuPos({ x: e.clientX, y: e.clientY });
|
|
5829
|
-
setNodeMenuOpen(true);
|
|
5830
|
-
setMenuOpen(false);
|
|
5831
|
-
setSelectionMenuOpen(false);
|
|
5980
|
+
if (!nodeId)
|
|
5832
5981
|
return;
|
|
5833
|
-
|
|
5834
|
-
|
|
5982
|
+
const isSelected = selection.nodes.includes(nodeId);
|
|
5983
|
+
if (isSelected && !isSingleNodeSelected) {
|
|
5835
5984
|
// Right-clicked on a node that's part of multi-selection - show selection menu
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5985
|
+
setMenuState({
|
|
5986
|
+
type: "selection",
|
|
5987
|
+
menuPos: { x: e.clientX, y: e.clientY },
|
|
5988
|
+
});
|
|
5840
5989
|
return;
|
|
5841
5990
|
}
|
|
5842
5991
|
else {
|
|
5843
5992
|
// Right-clicked on a non-selected node - show node menu
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5993
|
+
setMenuState({
|
|
5994
|
+
type: "node",
|
|
5995
|
+
menuPos: { x: e.clientX, y: e.clientY },
|
|
5996
|
+
nodeId,
|
|
5997
|
+
});
|
|
5849
5998
|
return;
|
|
5850
5999
|
}
|
|
5851
6000
|
}
|
|
@@ -5857,32 +6006,22 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5857
6006
|
if (isSelected && isSingleNodeSelected) {
|
|
5858
6007
|
// Right-clicked on an edge, but only one node is selected - show node menu
|
|
5859
6008
|
const nodeId = selection.nodes[0];
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
6009
|
+
setMenuState({
|
|
6010
|
+
type: "node",
|
|
6011
|
+
menuPos: { x: e.clientX, y: e.clientY },
|
|
6012
|
+
nodeId,
|
|
6013
|
+
});
|
|
5865
6014
|
return;
|
|
5866
6015
|
}
|
|
5867
6016
|
else if (isSelected) {
|
|
5868
6017
|
// Right-clicked on a selected edge with multiple nodes - show selection menu
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
6018
|
+
setMenuState({
|
|
6019
|
+
type: "selection",
|
|
6020
|
+
menuPos: { x: e.clientX, y: e.clientY },
|
|
6021
|
+
});
|
|
5873
6022
|
return;
|
|
5874
6023
|
}
|
|
5875
6024
|
}
|
|
5876
|
-
// If only one node is selected (even with edges), show node menu for empty space clicks
|
|
5877
|
-
if (isSingleNodeSelected) {
|
|
5878
|
-
const nodeId = selection.nodes[0];
|
|
5879
|
-
setNodeAtMenu(nodeId);
|
|
5880
|
-
setNodeMenuPos({ x: e.clientX, y: e.clientY });
|
|
5881
|
-
setNodeMenuOpen(true);
|
|
5882
|
-
setMenuOpen(false);
|
|
5883
|
-
setSelectionMenuOpen(false);
|
|
5884
|
-
return;
|
|
5885
|
-
}
|
|
5886
6025
|
// Check if the cursor is inside the rectangular bounds of the current selection
|
|
5887
6026
|
// (for multi-selection when right-clicking on empty space within selection bounds)
|
|
5888
6027
|
const selectionBounds = getSelectionScreenBounds();
|
|
@@ -5892,28 +6031,38 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5892
6031
|
e.clientX <= right &&
|
|
5893
6032
|
e.clientY >= top &&
|
|
5894
6033
|
e.clientY <= bottom) {
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
6034
|
+
// If only one node is selected (even with edges), show node menu for empty space clicks
|
|
6035
|
+
if (isSingleNodeSelected) {
|
|
6036
|
+
const nodeId = selection.nodes[0];
|
|
6037
|
+
setMenuState({
|
|
6038
|
+
type: "node",
|
|
6039
|
+
menuPos: { x: e.clientX, y: e.clientY },
|
|
6040
|
+
nodeId,
|
|
6041
|
+
});
|
|
6042
|
+
return;
|
|
6043
|
+
}
|
|
6044
|
+
setMenuState({
|
|
6045
|
+
type: "selection",
|
|
6046
|
+
menuPos: { x: e.clientX, y: e.clientY },
|
|
6047
|
+
});
|
|
5899
6048
|
return;
|
|
5900
6049
|
}
|
|
5901
6050
|
}
|
|
5902
6051
|
// Right-clicked on empty space with no selection - show default menu
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
6052
|
+
setMenuState({
|
|
6053
|
+
type: "default",
|
|
6054
|
+
menuPos: { x: e.clientX, y: e.clientY },
|
|
6055
|
+
});
|
|
5907
6056
|
};
|
|
5908
6057
|
const addNodeAt = React.useCallback(async (typeId, opts) => wb.addNode({ typeId }, { inputs: opts.inputs, position: opts.position, commit: true }), [wb]);
|
|
5909
6058
|
const onCloseMenu = React.useCallback(() => {
|
|
5910
|
-
|
|
6059
|
+
setMenuState(null);
|
|
5911
6060
|
}, []);
|
|
5912
6061
|
const onCloseNodeMenu = React.useCallback(() => {
|
|
5913
|
-
|
|
6062
|
+
setMenuState(null);
|
|
5914
6063
|
}, []);
|
|
5915
6064
|
const onCloseSelectionMenu = React.useCallback(() => {
|
|
5916
|
-
|
|
6065
|
+
setMenuState(null);
|
|
5917
6066
|
}, []);
|
|
5918
6067
|
React.useEffect(() => {
|
|
5919
6068
|
const off = wb.on("historyChanged", (event) => {
|
|
@@ -5960,8 +6109,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5960
6109
|
return baseHandlers;
|
|
5961
6110
|
}, [wb, runner, overrides, onCloseSelectionMenu]);
|
|
5962
6111
|
const nodeContextMenuHandlers = React.useMemo(() => {
|
|
5963
|
-
if (
|
|
6112
|
+
if (menuState?.type !== "node")
|
|
5964
6113
|
return null;
|
|
6114
|
+
const nodeAtMenu = menuState.nodeId;
|
|
5965
6115
|
// Get storage from override or use workbench's internal storage
|
|
5966
6116
|
const storage = overrides?.getCopiedDataStorage
|
|
5967
6117
|
? overrides.getCopiedDataStorage()
|
|
@@ -5977,7 +6127,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5977
6127
|
}
|
|
5978
6128
|
return baseHandlers;
|
|
5979
6129
|
}, [
|
|
5980
|
-
|
|
6130
|
+
menuState,
|
|
5981
6131
|
wb,
|
|
5982
6132
|
runner,
|
|
5983
6133
|
wb.registry,
|
|
@@ -5990,10 +6140,10 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5990
6140
|
overrides?.getCopiedDataStorage,
|
|
5991
6141
|
]);
|
|
5992
6142
|
const bakeableOutputs = React.useMemo(() => {
|
|
5993
|
-
if (
|
|
6143
|
+
if (menuState?.type !== "node")
|
|
5994
6144
|
return [];
|
|
5995
|
-
return getBakeableOutputs(
|
|
5996
|
-
}, [
|
|
6145
|
+
return getBakeableOutputs(menuState.nodeId, wb, wb.registry, outputTypesMap);
|
|
6146
|
+
}, [menuState, wb, wb.registry, registryVersion, outputTypesMap]);
|
|
5997
6147
|
// Keyboard shortcuts configuration
|
|
5998
6148
|
const enableKeyboardShortcuts = overrides?.enableKeyboardShortcuts !== false; // Default to true
|
|
5999
6149
|
const keyboardShortcuts = overrides?.keyboardShortcuts || {
|
|
@@ -6063,7 +6213,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
6063
6213
|
const modKeyLabel = isMac ? "⌘" : "Ctrl";
|
|
6064
6214
|
showToast(`Copy (${modKeyLabel} + C)`);
|
|
6065
6215
|
// If single node selected, use node context menu handler; otherwise use selection handler
|
|
6066
|
-
if (selection.nodes.length === 1 &&
|
|
6216
|
+
if (selection.nodes.length === 1 &&
|
|
6217
|
+
nodeContextMenuHandlers?.onCopy) {
|
|
6067
6218
|
nodeContextMenuHandlers.onCopy();
|
|
6068
6219
|
}
|
|
6069
6220
|
else if (selectionContextMenuHandlers.onCopy) {
|
|
@@ -6183,21 +6334,25 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
6183
6334
|
});
|
|
6184
6335
|
return () => off();
|
|
6185
6336
|
}, [wb]);
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6337
|
+
const { onInit: userOnInit, ...restReactFlowProps } = reactFlowProps || {};
|
|
6338
|
+
return (jsxRuntime.jsxs("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: [jsxRuntime.jsxs(react.ReactFlowProvider, { children: [jsxRuntime.jsxs(react.ReactFlow, { ...restReactFlowProps, nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, connectionLineComponent: connectionLineRenderer, selectionOnDrag: true, onInit: (inst) => {
|
|
6339
|
+
rfInstanceRef.current = inst;
|
|
6340
|
+
const savedViewport = wb.getViewport();
|
|
6341
|
+
if (savedViewport) {
|
|
6342
|
+
inst.setViewport(lod.clone(savedViewport));
|
|
6343
|
+
}
|
|
6344
|
+
if (userOnInit) {
|
|
6345
|
+
userOnInit(inst);
|
|
6346
|
+
}
|
|
6347
|
+
}, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", children: [BackgroundRenderer ? (jsxRuntime.jsx(BackgroundRenderer, {})) : (jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? jsxRuntime.jsx(MinimapRenderer, {}) : jsxRuntime.jsx(react.MiniMap, {}), ControlsRenderer ? jsxRuntime.jsx(ControlsRenderer, {}) : jsxRuntime.jsx(react.Controls, {}), menuState?.type === "default" &&
|
|
6348
|
+
(DefaultContextMenuRenderer ? (jsxRuntime.jsx(DefaultContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, ...(enableKeyboardShortcuts !== false
|
|
6349
|
+
? { enableKeyboardShortcuts, keyboardShortcuts }
|
|
6350
|
+
: {}) })) : (jsxRuntime.jsx(DefaultContextMenu, { open: true, clientPos: menuState.menuPos, handlers: defaultContextMenuHandlers, registry: wb.registry, nodeIds: nodeIds, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts }))), menuState?.type === "node" &&
|
|
6351
|
+
nodeContextMenuHandlers &&
|
|
6352
|
+
(NodeContextMenuRenderer ? (jsxRuntime.jsx(NodeContextMenuRenderer, { open: true, clientPos: menuState.menuPos, nodeId: menuState.nodeId, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode, wb: wb, ...(enableKeyboardShortcuts !== false
|
|
6353
|
+
? { enableKeyboardShortcuts, keyboardShortcuts }
|
|
6354
|
+
: {}) })) : (jsxRuntime.jsx(NodeContextMenu, { open: true, clientPos: menuState.menuPos, nodeId: menuState.nodeId, handlers: nodeContextMenuHandlers, bakeableOutputs: bakeableOutputs, runMode: runMode }))), menuState?.type === "selection" &&
|
|
6355
|
+
(SelectionContextMenuRenderer ? (jsxRuntime.jsx(SelectionContextMenuRenderer, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })) : (jsxRuntime.jsx(SelectionContextMenu, { open: true, clientPos: menuState.menuPos, handlers: selectionContextMenuHandlers, enableKeyboardShortcuts: enableKeyboardShortcuts, keyboardShortcuts: keyboardShortcuts })))] }), jsxRuntime.jsx(SelectionBoundOverlay, { selection: wb.getSelection(), rfInstance: rfInstanceRef.current })] }), toast && (jsxRuntime.jsx(KeyboardShortcutToast, { message: toast.message, onClose: hideToast }, toast.id))] }));
|
|
6201
6356
|
});
|
|
6202
6357
|
|
|
6203
6358
|
function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
@@ -6225,9 +6380,6 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
|
|
|
6225
6380
|
if (isConnecting) {
|
|
6226
6381
|
return (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-gray-500 border-gray-400 flex items-center gap-1 disabled:opacity-50", disabled: true, title: "Connecting to backend...", children: [jsxRuntime.jsx(react$1.ClockClockwiseIcon, { size: 16, className: "animate-spin" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Connecting..." })] }));
|
|
6227
6382
|
}
|
|
6228
|
-
if (isGraphRunning) {
|
|
6229
|
-
return (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-red-700 border-red-600 flex items-center gap-1 disabled:opacity-50 disabled:text-gray-400 disabled:border-gray-300", onClick: () => runner.stop(), disabled: !canControl, title: canControl ? "Stop engine" : "Waiting for connection", children: [jsxRuntime.jsx(react$1.StopIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Stop" })] }));
|
|
6230
|
-
}
|
|
6231
6383
|
return (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-green-700 border-green-600 flex items-center gap-1 disabled:text-gray-400 disabled:border-gray-300 disabled:opacity-50", onClick: (evt) => {
|
|
6232
6384
|
if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
|
|
6233
6385
|
return;
|
|
@@ -6614,11 +6766,12 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
|
|
|
6614
6766
|
if (mode !== runMode) {
|
|
6615
6767
|
await setRunMode(mode);
|
|
6616
6768
|
}
|
|
6617
|
-
},
|
|
6618
|
-
? "Stop before switching run mode"
|
|
6619
|
-
: "Select run mode", children: [jsxRuntime.jsx("option", { value: "manual", children: "Manual" }), jsxRuntime.jsx("option", { value: "auto", children: "Auto" })] }), renderStartStopButton(), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsxRuntime.jsx(react$1.TreeStructureIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsxRuntime.jsx(react$1.CornersOutIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsxRuntime.jsx(react$1.DownloadIcon, { size: 24 }) }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsxRuntime.jsx(react$1.UploadIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
|
|
6769
|
+
}, title: "Select run mode", children: [jsxRuntime.jsx("option", { value: "manual", children: "Manual" }), jsxRuntime.jsx("option", { value: "auto", children: "Auto" })] }), renderStartStopButton(), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsxRuntime.jsx(react$1.TreeStructureIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsxRuntime.jsx(react$1.CornersOutIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsxRuntime.jsx(react$1.DownloadIcon, { size: 24 }) }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsxRuntime.jsx(react$1.UploadIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
|
|
6620
6770
|
await downloadCanvasThumbnail(canvasContainerRef.current);
|
|
6621
|
-
}, title: "Download Flow Thumbnail (SVG)", children: jsxRuntime.jsx(react$1.ImageIcon, { size: 24 }) }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx(react$1.BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx(react$1.ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", ref: canvasContainerRef, children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize
|
|
6771
|
+
}, title: "Download Flow Thumbnail (SVG)", children: jsxRuntime.jsx(react$1.ImageIcon, { size: 24 }) }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx(react$1.BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx(react$1.ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", ref: canvasContainerRef, children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize, reactFlowProps: {
|
|
6772
|
+
minZoom: 0.1,
|
|
6773
|
+
maxZoom: 5,
|
|
6774
|
+
} }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, contextPanel: overrides?.contextPanel })] })] }));
|
|
6622
6775
|
}
|
|
6623
6776
|
function WorkbenchStudio({ example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
|
|
6624
6777
|
const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
|