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