@bian-womp/spark-workbench 0.2.24 → 0.2.26
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 +208 -107
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts +11 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +5 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +9 -0
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +209 -108
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts +11 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +5 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +9 -0
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -572,6 +572,10 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
572
572
|
super(registry, backend);
|
|
573
573
|
this.valueCache = new Map();
|
|
574
574
|
this.listenersBound = false;
|
|
575
|
+
this.registryFetched = false;
|
|
576
|
+
this.registryFetching = false;
|
|
577
|
+
this.MAX_REGISTRY_FETCH_ATTEMPTS = 3;
|
|
578
|
+
this.INITIAL_RETRY_DELAY_MS = 1000; // 1 second
|
|
575
579
|
// Auto-handle registry-changed invalidations from remote
|
|
576
580
|
// We listen on invalidate and if reason matches, we rehydrate registry and emit a registry event
|
|
577
581
|
this.ensureRemoteRunner().then(async (runner) => {
|
|
@@ -806,39 +810,178 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
806
810
|
super.dispose();
|
|
807
811
|
this.runner = undefined;
|
|
808
812
|
this.transport = undefined;
|
|
813
|
+
this.registryFetched = false; // Reset so registry is fetched again on reconnect
|
|
814
|
+
this.registryFetching = false; // Reset fetching state
|
|
809
815
|
this.emit("transport", {
|
|
810
816
|
state: "disconnected",
|
|
811
817
|
kind: this.backend.kind,
|
|
812
818
|
});
|
|
813
819
|
}
|
|
820
|
+
/**
|
|
821
|
+
* Fetch full registry description from remote and register it locally.
|
|
822
|
+
* Called automatically on first connection with retry mechanism.
|
|
823
|
+
*/
|
|
824
|
+
async fetchRegistry(runner, attempt = 1) {
|
|
825
|
+
if (this.registryFetching) {
|
|
826
|
+
// Already fetching, don't start another fetch
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
this.registryFetching = true;
|
|
830
|
+
try {
|
|
831
|
+
const desc = await runner.describeRegistry();
|
|
832
|
+
// Register types
|
|
833
|
+
for (const t of desc.types) {
|
|
834
|
+
if (t.options) {
|
|
835
|
+
this.registry.registerEnum({
|
|
836
|
+
id: t.id,
|
|
837
|
+
options: t.options,
|
|
838
|
+
bakeTarget: t.bakeTarget,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
if (!this.registry.types.has(t.id)) {
|
|
843
|
+
this.registry.registerType({
|
|
844
|
+
id: t.id,
|
|
845
|
+
displayName: t.displayName,
|
|
846
|
+
validate: (_v) => true,
|
|
847
|
+
bakeTarget: t.bakeTarget,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
// Register categories
|
|
853
|
+
for (const c of desc.categories || []) {
|
|
854
|
+
if (!this.registry.categories.has(c.id)) {
|
|
855
|
+
// Create placeholder category descriptor
|
|
856
|
+
const category = {
|
|
857
|
+
id: c.id,
|
|
858
|
+
displayName: c.displayName,
|
|
859
|
+
createRuntime: () => ({
|
|
860
|
+
async onInputsChanged() { },
|
|
861
|
+
}),
|
|
862
|
+
policy: { asyncConcurrency: "switch" },
|
|
863
|
+
};
|
|
864
|
+
this.registry.categories.register(category);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
// Register coercions
|
|
868
|
+
for (const c of desc.coercions) {
|
|
869
|
+
if (c.async) {
|
|
870
|
+
this.registry.registerAsyncCoercion(c.from, c.to, async (v) => v, {
|
|
871
|
+
nonTransitive: c.nonTransitive,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
this.registry.registerCoercion(c.from, c.to, (v) => v, {
|
|
876
|
+
nonTransitive: c.nonTransitive,
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
// Register nodes
|
|
881
|
+
for (const n of desc.nodes) {
|
|
882
|
+
if (!this.registry.nodes.has(n.id)) {
|
|
883
|
+
this.registry.registerNode({
|
|
884
|
+
id: n.id,
|
|
885
|
+
categoryId: n.categoryId,
|
|
886
|
+
displayName: n.displayName,
|
|
887
|
+
inputs: n.inputs || {},
|
|
888
|
+
outputs: n.outputs || {},
|
|
889
|
+
impl: () => { },
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
this.registryFetched = true;
|
|
894
|
+
this.registryFetching = false;
|
|
895
|
+
this.emit("registry", this.registry);
|
|
896
|
+
}
|
|
897
|
+
catch (err) {
|
|
898
|
+
this.registryFetching = false;
|
|
899
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
900
|
+
// Retry with exponential backoff if attempts remaining
|
|
901
|
+
if (attempt < this.MAX_REGISTRY_FETCH_ATTEMPTS) {
|
|
902
|
+
const delayMs = this.INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
|
|
903
|
+
console.warn(`Failed to fetch registry (attempt ${attempt}/${this.MAX_REGISTRY_FETCH_ATTEMPTS}), retrying in ${delayMs}ms...`, error);
|
|
904
|
+
// Emit error event for UI feedback
|
|
905
|
+
this.emit("error", {
|
|
906
|
+
kind: "registry",
|
|
907
|
+
message: `Registry fetch failed (attempt ${attempt}/${this.MAX_REGISTRY_FETCH_ATTEMPTS}), retrying...`,
|
|
908
|
+
err: error,
|
|
909
|
+
attempt,
|
|
910
|
+
maxAttempts: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
911
|
+
});
|
|
912
|
+
// Retry after delay
|
|
913
|
+
setTimeout(() => {
|
|
914
|
+
this.fetchRegistry(runner, attempt + 1).catch(() => {
|
|
915
|
+
// Final failure handled below
|
|
916
|
+
});
|
|
917
|
+
}, delayMs);
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
// Max attempts reached, emit final error
|
|
921
|
+
console.error(`Failed to fetch registry after ${this.MAX_REGISTRY_FETCH_ATTEMPTS} attempts:`, error);
|
|
922
|
+
this.emit("error", {
|
|
923
|
+
kind: "registry",
|
|
924
|
+
message: `Failed to fetch registry after ${this.MAX_REGISTRY_FETCH_ATTEMPTS} attempts. Please check your connection and try refreshing.`,
|
|
925
|
+
err: error,
|
|
926
|
+
attempt: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
927
|
+
maxAttempts: this.MAX_REGISTRY_FETCH_ATTEMPTS,
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
814
932
|
// Ensure remote transport/runner
|
|
815
933
|
async ensureRemoteRunner() {
|
|
816
934
|
if (this.runner)
|
|
817
935
|
return this.runner;
|
|
818
936
|
let transport;
|
|
819
937
|
const kind = this.backend.kind;
|
|
938
|
+
const backend = this.backend;
|
|
939
|
+
const connectOptions = backend.connectOptions;
|
|
820
940
|
this.emit("transport", { state: "connecting", kind });
|
|
821
|
-
if (
|
|
941
|
+
if (backend.kind === "remote-http") {
|
|
822
942
|
if (!sparkRemote.HttpPollingTransport)
|
|
823
943
|
throw new Error("HttpPollingTransport not available");
|
|
824
|
-
transport = new sparkRemote.HttpPollingTransport(
|
|
825
|
-
await transport.connect();
|
|
944
|
+
transport = new sparkRemote.HttpPollingTransport(backend.baseUrl);
|
|
945
|
+
await transport.connect(connectOptions);
|
|
826
946
|
}
|
|
827
|
-
else if (
|
|
947
|
+
else if (backend.kind === "remote-ws") {
|
|
828
948
|
if (!sparkRemote.WebSocketTransport)
|
|
829
949
|
throw new Error("WebSocketTransport not available");
|
|
830
|
-
transport = new sparkRemote.WebSocketTransport(
|
|
831
|
-
await transport.connect();
|
|
950
|
+
transport = new sparkRemote.WebSocketTransport(backend.url);
|
|
951
|
+
await transport.connect(connectOptions);
|
|
832
952
|
}
|
|
833
953
|
else {
|
|
834
954
|
throw new Error("Remote backend not configured");
|
|
835
955
|
}
|
|
956
|
+
// Subscribe to custom events if handler provided
|
|
957
|
+
if (backend.onCustomEvent) {
|
|
958
|
+
transport.subscribe((event) => {
|
|
959
|
+
// Filter out standard runtime events, pass others to custom handler
|
|
960
|
+
const msg = event.message;
|
|
961
|
+
if (msg && typeof msg === "object" && "type" in msg) {
|
|
962
|
+
const type = msg.type;
|
|
963
|
+
// Standard runtime events: stats, value, error, invalidate
|
|
964
|
+
// Custom events are anything else (e.g., flow-opened, flow-latest)
|
|
965
|
+
if (!["stats", "value", "error", "invalidate"].includes(type)) {
|
|
966
|
+
backend.onCustomEvent?.(event);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
}
|
|
836
971
|
const runner = new sparkRemote.RemoteRunner(transport);
|
|
837
972
|
this.runner = runner;
|
|
838
973
|
this.transport = transport;
|
|
839
974
|
this.valueCache.clear();
|
|
840
975
|
this.listenersBound = false;
|
|
841
976
|
this.emit("transport", { state: "connected", kind });
|
|
977
|
+
// Auto-fetch registry on first connection (only once)
|
|
978
|
+
if (!this.registryFetched && !this.registryFetching) {
|
|
979
|
+
// Log loading state (UI can listen to transport status for loading indication)
|
|
980
|
+
console.info("Loading registry from remote...");
|
|
981
|
+
this.fetchRegistry(runner).catch(() => {
|
|
982
|
+
// Error handling is done inside fetchRegistry
|
|
983
|
+
});
|
|
984
|
+
}
|
|
842
985
|
return runner;
|
|
843
986
|
}
|
|
844
987
|
}
|
|
@@ -2955,7 +3098,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
2955
3098
|
}, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 }), jsxRuntime.jsx(react.MiniMap, {}), jsxRuntime.jsx(react.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: onCloseMenu }), !!nodeAtMenu && (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: onCloseNodeMenu }))] }) }) }));
|
|
2956
3099
|
});
|
|
2957
3100
|
|
|
2958
|
-
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
3101
|
+
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, backendOptions, overrides, onInit, onChange, }) {
|
|
2959
3102
|
const { wb, runner, registry, def, selectedNodeId, runAutoLayout } = useWorkbenchContext();
|
|
2960
3103
|
const [transportStatus, setTransportStatus] = React.useState({
|
|
2961
3104
|
state: "local",
|
|
@@ -3004,11 +3147,14 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3004
3147
|
return overrides.getExamples(defaultExamples);
|
|
3005
3148
|
return defaultExamples;
|
|
3006
3149
|
}, [overrides, defaultExamples]);
|
|
3007
|
-
const [hydrated, setHydrated] = React.useState(false);
|
|
3008
3150
|
const lastAutoLaunched = React.useRef(undefined);
|
|
3009
3151
|
const autoLayoutRan = React.useRef(false);
|
|
3010
3152
|
const canvasRef = React.useRef(null);
|
|
3011
3153
|
const uploadInputRef = React.useRef(null);
|
|
3154
|
+
const [registryReady, setRegistryReady] = React.useState(() => {
|
|
3155
|
+
// For local backends, registry is always ready
|
|
3156
|
+
return backendKind === "local";
|
|
3157
|
+
});
|
|
3012
3158
|
// Expose init callback with setInitialGraph helper
|
|
3013
3159
|
const initCalled = React.useRef(false);
|
|
3014
3160
|
React.useEffect(() => {
|
|
@@ -3067,7 +3213,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3067
3213
|
const { registry: r, def } = await ex.load();
|
|
3068
3214
|
// Keep registry consistent with backend:
|
|
3069
3215
|
// - For local backend, allow example to provide its own registry
|
|
3070
|
-
// - For remote backend,
|
|
3216
|
+
// - For remote backend, registry is automatically managed by RemoteGraphRunner
|
|
3071
3217
|
if (backendKind === "local") {
|
|
3072
3218
|
if (r) {
|
|
3073
3219
|
setRegistry(r);
|
|
@@ -3180,79 +3326,8 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3180
3326
|
const triggerUpload = React.useCallback(() => {
|
|
3181
3327
|
uploadInputRef.current?.click();
|
|
3182
3328
|
}, []);
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
const transport = kind === "remote-http"
|
|
3186
|
-
? new sparkRemote.HttpPollingTransport(base)
|
|
3187
|
-
: new sparkRemote.WebSocketTransport(base);
|
|
3188
|
-
await transport.connect();
|
|
3189
|
-
const rr = new sparkRemote.RemoteRunner(transport);
|
|
3190
|
-
const desc = await rr.describeRegistry();
|
|
3191
|
-
const r = new sparkGraph.Registry();
|
|
3192
|
-
// Types
|
|
3193
|
-
for (const t of desc.types) {
|
|
3194
|
-
if (t.options) {
|
|
3195
|
-
r.registerEnum({
|
|
3196
|
-
id: t.id,
|
|
3197
|
-
options: t.options,
|
|
3198
|
-
bakeTarget: t.bakeTarget,
|
|
3199
|
-
});
|
|
3200
|
-
}
|
|
3201
|
-
else {
|
|
3202
|
-
r.registerType({
|
|
3203
|
-
id: t.id,
|
|
3204
|
-
displayName: t.displayName,
|
|
3205
|
-
validate: (_v) => true,
|
|
3206
|
-
bakeTarget: t.bakeTarget,
|
|
3207
|
-
});
|
|
3208
|
-
}
|
|
3209
|
-
}
|
|
3210
|
-
// Categories: create placeholders for display name
|
|
3211
|
-
for (const c of desc.categories || []) {
|
|
3212
|
-
// If you later expose real category descriptors, register them here
|
|
3213
|
-
// For now, rely on ComputeCategory for behavior
|
|
3214
|
-
const category = {
|
|
3215
|
-
id: c.id,
|
|
3216
|
-
displayName: c.displayName,
|
|
3217
|
-
createRuntime: () => ({
|
|
3218
|
-
async onInputsChanged() { },
|
|
3219
|
-
}),
|
|
3220
|
-
policy: { asyncConcurrency: "switch" },
|
|
3221
|
-
};
|
|
3222
|
-
r.categories.register(category);
|
|
3223
|
-
}
|
|
3224
|
-
// Coercions (client-side no-op to satisfy validation) if provided
|
|
3225
|
-
for (const c of desc.coercions) {
|
|
3226
|
-
if (c.async) {
|
|
3227
|
-
r.registerAsyncCoercion(c.from, c.to, async (v) => v, {
|
|
3228
|
-
nonTransitive: c.nonTransitive,
|
|
3229
|
-
});
|
|
3230
|
-
}
|
|
3231
|
-
else {
|
|
3232
|
-
r.registerCoercion(c.from, c.to, (v) => v, {
|
|
3233
|
-
nonTransitive: c.nonTransitive,
|
|
3234
|
-
});
|
|
3235
|
-
}
|
|
3236
|
-
}
|
|
3237
|
-
// Nodes (use no-op impl for compute)
|
|
3238
|
-
for (const n of desc.nodes) {
|
|
3239
|
-
r.registerNode({
|
|
3240
|
-
id: n.id,
|
|
3241
|
-
categoryId: n.categoryId,
|
|
3242
|
-
displayName: n.displayName,
|
|
3243
|
-
inputs: n.inputs || {},
|
|
3244
|
-
outputs: n.outputs || {},
|
|
3245
|
-
impl: () => { },
|
|
3246
|
-
});
|
|
3247
|
-
}
|
|
3248
|
-
setRegistry(r);
|
|
3249
|
-
wb.setRegistry(r);
|
|
3250
|
-
await transport.close();
|
|
3251
|
-
}
|
|
3252
|
-
catch (err) {
|
|
3253
|
-
console.error("Failed to hydrate registry from backend:", err);
|
|
3254
|
-
}
|
|
3255
|
-
}, [setRegistry, wb]);
|
|
3329
|
+
// Registry is now automatically fetched by RemoteGraphRunner on first connection
|
|
3330
|
+
// No need for manual hydration
|
|
3256
3331
|
// Ensure initial example is loaded (and sync when example prop changes)
|
|
3257
3332
|
React.useEffect(() => {
|
|
3258
3333
|
if (!example)
|
|
@@ -3263,6 +3338,21 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3263
3338
|
const off = runner.on("transport", (s) => setTransportStatus(s));
|
|
3264
3339
|
return () => off();
|
|
3265
3340
|
}, [runner]);
|
|
3341
|
+
// Track registry readiness for remote backends
|
|
3342
|
+
React.useEffect(() => {
|
|
3343
|
+
// For local backends, registry is always ready
|
|
3344
|
+
if (backendKind === "local") {
|
|
3345
|
+
setRegistryReady(true);
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3348
|
+
// Reset readiness when switching to remote backend
|
|
3349
|
+
setRegistryReady(false);
|
|
3350
|
+
// For remote backends, wait for registry event
|
|
3351
|
+
const off = runner.on("registry", () => {
|
|
3352
|
+
setRegistryReady(true);
|
|
3353
|
+
});
|
|
3354
|
+
return () => off();
|
|
3355
|
+
}, [runner, backendKind]);
|
|
3266
3356
|
React.useEffect(() => {
|
|
3267
3357
|
if (!engine)
|
|
3268
3358
|
return;
|
|
@@ -3284,25 +3374,13 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3284
3374
|
// ignore
|
|
3285
3375
|
}
|
|
3286
3376
|
}, [engine, runner, wb, backendKind]);
|
|
3287
|
-
//
|
|
3288
|
-
|
|
3289
|
-
let hydrate;
|
|
3290
|
-
if (backendKind === "remote-http" && httpBaseUrl) {
|
|
3291
|
-
hydrate = hydrateFromBackend("remote-http", httpBaseUrl);
|
|
3292
|
-
}
|
|
3293
|
-
else if (backendKind === "remote-ws" && wsUrl) {
|
|
3294
|
-
hydrate = hydrateFromBackend("remote-ws", wsUrl);
|
|
3295
|
-
}
|
|
3296
|
-
if (hydrate) {
|
|
3297
|
-
hydrate.then(() => {
|
|
3298
|
-
setHydrated(true);
|
|
3299
|
-
});
|
|
3300
|
-
}
|
|
3301
|
-
}, [backendKind, httpBaseUrl, wsUrl, hydrateFromBackend, setHydrated]);
|
|
3377
|
+
// Registry is automatically fetched by RemoteGraphRunner when it connects
|
|
3378
|
+
// Run auto layout after registry is hydrated (for remote backends)
|
|
3302
3379
|
React.useEffect(() => {
|
|
3303
3380
|
if (autoLayoutRan.current)
|
|
3304
3381
|
return;
|
|
3305
|
-
|
|
3382
|
+
// Wait for registry to be ready for remote backends
|
|
3383
|
+
if (backendKind !== "local" && !registryReady)
|
|
3306
3384
|
return;
|
|
3307
3385
|
const cur = wb.export();
|
|
3308
3386
|
const positions = wb.getPositions();
|
|
@@ -3311,7 +3389,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3311
3389
|
autoLayoutRan.current = true;
|
|
3312
3390
|
runAutoLayout();
|
|
3313
3391
|
}
|
|
3314
|
-
}, [wb, runAutoLayout, backendKind,
|
|
3392
|
+
}, [wb, runAutoLayout, backendKind, registryReady, registry]);
|
|
3315
3393
|
const baseSetInput = React.useCallback((handle, raw) => {
|
|
3316
3394
|
if (!selectedNodeId)
|
|
3317
3395
|
return;
|
|
@@ -3421,7 +3499,9 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3421
3499
|
value !== null &&
|
|
3422
3500
|
"url" in value &&
|
|
3423
3501
|
typeof value.url === "string") {
|
|
3424
|
-
const title =
|
|
3502
|
+
const title = "title" in value && typeof value.title === "string"
|
|
3503
|
+
? value.title
|
|
3504
|
+
: "";
|
|
3425
3505
|
const url = String(value.url || "");
|
|
3426
3506
|
// value.ts handles data URL formatting
|
|
3427
3507
|
return title || url.slice(0, 32) + (url.length > 32 ? "…" : "");
|
|
@@ -3434,7 +3514,11 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3434
3514
|
const round4 = (n) => Math.round(Number(n) * 10000) / 10000;
|
|
3435
3515
|
if (typeId === "base.vec3" && Array.isArray(value)) {
|
|
3436
3516
|
const a = value;
|
|
3437
|
-
return [
|
|
3517
|
+
return [
|
|
3518
|
+
round4(Number(a[0] ?? 0)),
|
|
3519
|
+
round4(Number(a[1] ?? 0)),
|
|
3520
|
+
round4(Number(a[2] ?? 0)),
|
|
3521
|
+
].join(",");
|
|
3438
3522
|
}
|
|
3439
3523
|
const stringifyRounded = (v) => {
|
|
3440
3524
|
try {
|
|
@@ -3519,21 +3603,38 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3519
3603
|
}
|
|
3520
3604
|
}, children: "Download Snapshot" }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: triggerUpload, children: "Upload Graph/Snapshot" }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Debug events" })] }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Show values in nodes" })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement, contextPanel: overrides?.contextPanel })] })] }));
|
|
3521
3605
|
}
|
|
3522
|
-
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, overrides, onInit, onChange, }) {
|
|
3606
|
+
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
|
|
3523
3607
|
const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
|
|
3524
3608
|
const [wb] = React.useState(() => new InMemoryWorkbench({ ui: new DefaultUIExtensionRegistry() }));
|
|
3525
3609
|
const runner = React.useMemo(() => {
|
|
3526
3610
|
if (backendKind === "remote-http") {
|
|
3527
|
-
|
|
3611
|
+
const backend = {
|
|
3528
3612
|
kind: "remote-http",
|
|
3529
3613
|
baseUrl: httpBaseUrl,
|
|
3530
|
-
|
|
3614
|
+
...(backendOptions?.connectOptions && {
|
|
3615
|
+
connectOptions: backendOptions.connectOptions,
|
|
3616
|
+
}),
|
|
3617
|
+
...(backendOptions?.onCustomEvent && {
|
|
3618
|
+
onCustomEvent: backendOptions.onCustomEvent,
|
|
3619
|
+
}),
|
|
3620
|
+
};
|
|
3621
|
+
return new RemoteGraphRunner(registry, backend);
|
|
3531
3622
|
}
|
|
3532
3623
|
if (backendKind === "remote-ws") {
|
|
3533
|
-
|
|
3624
|
+
const backend = {
|
|
3625
|
+
kind: "remote-ws",
|
|
3626
|
+
url: wsUrl,
|
|
3627
|
+
...(backendOptions?.connectOptions && {
|
|
3628
|
+
connectOptions: backendOptions.connectOptions,
|
|
3629
|
+
}),
|
|
3630
|
+
...(backendOptions?.onCustomEvent && {
|
|
3631
|
+
onCustomEvent: backendOptions.onCustomEvent,
|
|
3632
|
+
}),
|
|
3633
|
+
};
|
|
3634
|
+
return new RemoteGraphRunner(registry, backend);
|
|
3534
3635
|
}
|
|
3535
3636
|
return new LocalGraphRunner(registry);
|
|
3536
|
-
}, [registry, backendKind, httpBaseUrl, wsUrl]);
|
|
3637
|
+
}, [registry, backendKind, httpBaseUrl, wsUrl, backendOptions]);
|
|
3537
3638
|
// Allow external UI registration (e.g., node renderers) with access to wb
|
|
3538
3639
|
React.useEffect(() => {
|
|
3539
3640
|
const baseRegisterUI = (_wb) => { };
|
|
@@ -3544,7 +3645,7 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
3544
3645
|
if (runner.isRunning())
|
|
3545
3646
|
runner.dispose();
|
|
3546
3647
|
onBackendKindChange(v);
|
|
3547
|
-
}, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange, overrides: overrides, onInit: onInit, onChange: onChange }) }));
|
|
3648
|
+
}, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange, backendOptions: backendOptions, overrides: overrides, onInit: onInit, onChange: onChange }) }));
|
|
3548
3649
|
}
|
|
3549
3650
|
|
|
3550
3651
|
exports.AbstractWorkbench = AbstractWorkbench;
|