@weasel-js/labkit 1.0.4 → 1.2.0
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/README.md +33 -6
- package/dist/_dts/{DrawCommand-BkZztJsW.d.ts → DrawCommand-C_XboUpz.d.ts} +48 -6
- package/dist/_dts/{index-CFlDPeZh.d.ts → index-JFAYj5Tv.d.ts} +29 -7
- package/dist/_dts/types-DJ79Tg5J.d.ts +56 -0
- package/dist/_dts/useTrialState-BMNIx3Cy.d.ts +171 -0
- package/dist/canvas/index.d.ts +2 -1
- package/dist/{chunk-BAPZPDDA.js → chunk-73KA7WBO.js} +425 -394
- package/dist/chunk-73KA7WBO.js.map +1 -0
- package/dist/{chunk-DWV7SFKR.js → chunk-BOHF3PQO.js} +4397 -3726
- package/dist/chunk-BOHF3PQO.js.map +1 -0
- package/dist/{chunk-DEWXYFEU.js → chunk-G5TJVQQT.js} +3 -3
- package/dist/{chunk-DEWXYFEU.js.map → chunk-G5TJVQQT.js.map} +1 -1
- package/dist/chunk-LN6JDUGB.js +106 -0
- package/dist/chunk-LN6JDUGB.js.map +1 -0
- package/dist/chunk-THBG7FQZ.js +167 -0
- package/dist/chunk-THBG7FQZ.js.map +1 -0
- package/dist/chunk-VUU5UXHE.js +491 -0
- package/dist/chunk-VUU5UXHE.js.map +1 -0
- package/dist/dragdrop/index.d.ts +2 -1
- package/dist/index.d.ts +211 -151
- package/dist/index.js +317 -169
- package/dist/index.js.map +1 -1
- package/dist/job/index.d.ts +13 -0
- package/dist/job/index.js +3 -0
- package/dist/job/index.js.map +1 -0
- package/dist/layers/index.d.ts +3 -2
- package/dist/passthrough/weasel-canvas.d.ts +1 -1
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +29 -6
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/state/index.d.ts +12 -12
- package/dist/state/index.js +5 -5
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +31 -13
- package/dist/surface/index.d.ts +77 -0
- package/dist/surface/index.js +3 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/ui/layers/index.js +3 -3
- package/dist/undo/index.d.ts +2 -1
- package/package.json +9 -1
- package/src/canvas/AGENTS.md +14 -2
- package/src/canvas/useOrbit.test.ts +71 -0
- package/src/canvas/useOrbit.ts +149 -0
- package/src/dragdrop/dragDrop.test.tsx +4 -7
- package/src/index.test.ts +70 -0
- package/src/index.ts +24 -7
- package/src/instrument/SineWave.smoke.test.tsx +2 -1
- package/src/instrument/capabilityDetector.ts +1 -1
- package/src/instrument/types.ts +30 -6
- package/src/job/index.ts +3 -0
- package/src/job/types.ts +47 -0
- package/src/job/useJob.test.tsx +210 -0
- package/src/job/useJob.ts +134 -0
- package/src/lab/Lab.less +16 -0
- package/src/lab/Lab.stories.tsx +6 -7
- package/src/lab/Lab.test.tsx +25 -25
- package/src/lab/Lab.tsx +50 -50
- package/src/lab/LabContext.ts +12 -12
- package/src/lab/LabShell.tsx +1 -1
- package/src/lab/{WorkspaceGrid.less → Workspace.less} +2 -2
- package/src/lab/{WorkspaceGrid.stories.tsx → Workspace.stories.tsx} +13 -13
- package/src/lab/Workspace.surface.test.tsx +49 -0
- package/src/lab/{WorkspaceGrid.test.tsx → Workspace.test.tsx} +24 -24
- package/src/lab/{WorkspaceGrid.tsx → Workspace.tsx} +32 -19
- package/src/lab/index.ts +2 -2
- package/src/layers/AGENTS.md +3 -3
- package/src/primitives/Toolbar.stories.tsx +2 -2
- package/src/state/SingletonExperiment.test.tsx +8 -8
- package/src/state/SingletonExperiment.tsx +9 -9
- package/src/state/context.tsx +12 -12
- package/src/state/document.test.ts +369 -0
- package/src/state/document.ts +194 -0
- package/src/state/helpers.test.ts +74 -46
- package/src/state/helpers.ts +21 -29
- package/src/state/index.ts +16 -8
- package/src/state/store.test.ts +440 -41
- package/src/state/store.ts +165 -102
- package/src/state/types.ts +32 -13
- package/src/state/{useExperimentState.test.tsx → useTrialState.test.tsx} +18 -18
- package/src/state/useTrialState.ts +29 -0
- package/src/state/view.test.ts +127 -0
- package/src/state/view.ts +18 -0
- package/src/styles.less +2 -2
- package/src/surface/AGENTS.md +64 -0
- package/src/surface/SurfaceContext.ts +5 -0
- package/src/surface/composeRects.test.ts +50 -0
- package/src/surface/composeRects.ts +19 -0
- package/src/surface/deviceRect.test.ts +40 -0
- package/src/surface/deviceRect.ts +19 -0
- package/src/surface/index.ts +7 -0
- package/src/surface/rect.ts +16 -0
- package/src/surface/useSurfaceTile.test.tsx +67 -0
- package/src/surface/useSurfaceTile.ts +32 -0
- package/src/surface/useTiledSurface.test.tsx +231 -0
- package/src/surface/useTiledSurface.ts +157 -0
- package/src/theme/base.less +1 -1
- package/src/{workspace → trial}/DefaultSidebar.tsx +3 -3
- package/src/trial/DefaultStatusBar.tsx +19 -0
- package/src/{workspace → trial}/DefaultToolbar.tsx +9 -8
- package/src/trial/Trial.canvas.test.tsx +80 -0
- package/src/trial/Trial.job.test.tsx +75 -0
- package/src/{workspace/Workspace.less → trial/Trial.less} +13 -1
- package/src/{workspace/Workspace.stories.tsx → trial/Trial.stories.tsx} +20 -18
- package/src/{workspace/Workspace.test.tsx → trial/Trial.test.tsx} +26 -26
- package/src/{workspace/Workspace.tsx → trial/Trial.tsx} +77 -46
- package/src/{workspace/WorkspaceChrome.tsx → trial/TrialChrome.tsx} +73 -46
- package/src/{workspace → trial}/index.ts +13 -13
- package/src/{workspace → trial}/slotTypes.ts +18 -17
- package/src/{workspace/workspaceOps.test.ts → trial/trialOps.test.ts} +38 -44
- package/src/trial/trialOps.ts +99 -0
- package/src/ui/format.test.ts +33 -0
- package/src/ui/format.ts +11 -0
- package/src/ui/properties/CurveField.tsx +6 -6
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +3 -3
- package/dist/_dts/useExperimentState-D7EQnnwJ.d.ts +0 -151
- package/dist/chunk-BAPZPDDA.js.map +0 -1
- package/dist/chunk-DWV7SFKR.js.map +0 -1
- package/dist/chunk-KSTEW2AF.js +0 -367
- package/dist/chunk-KSTEW2AF.js.map +0 -1
- package/src/state/useExperimentState.ts +0 -31
- package/src/workspace/DefaultStatusBar.tsx +0 -18
- package/src/workspace/workspaceOps.ts +0 -102
package/dist/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import './chunk-
|
|
1
|
+
import { useSurfaceOptional } from './chunk-THBG7FQZ.js';
|
|
2
|
+
export { SurfaceContext, composeRects, rectsEqual, toDeviceRect, useSurface, useSurfaceOptional, useSurfaceTile, useTiledSurface } from './chunk-THBG7FQZ.js';
|
|
3
|
+
import { useJob } from './chunk-LN6JDUGB.js';
|
|
4
|
+
export { useJob } from './chunk-LN6JDUGB.js';
|
|
5
|
+
export { LayerStack } from './chunk-G5TJVQQT.js';
|
|
6
|
+
import { p, F } from './chunk-73KA7WBO.js';
|
|
7
|
+
import './chunk-BOHF3PQO.js';
|
|
4
8
|
import { Sidebar, StatusBar, Toolbar } from './chunk-PMAU3SEE.js';
|
|
5
9
|
export { FpsMeter, ScaleIndicator, Sidebar, StatusBar, Toolbar } from './chunk-PMAU3SEE.js';
|
|
6
|
-
import { LabStoreContext, createLabStore, noneAdapter } from './chunk-
|
|
7
|
-
export { LabStoreContext, LabStoreProvider,
|
|
10
|
+
import { LabStoreContext, createLabStore, noneAdapter } from './chunk-VUU5UXHE.js';
|
|
11
|
+
export { CURRENT_DOCUMENT_VERSION, LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, createMemoryAdapter, labDocumentKey, localStorageAdapter, noneAdapter, quarantineKey, sessionStorageAdapter, urlHashAdapter, useLabStore, useTrialId, useTrialState } from './chunk-VUU5UXHE.js';
|
|
8
12
|
import { ControlPanel } from './chunk-5R2ATYPJ.js';
|
|
9
13
|
export { ControlPanel } from './chunk-5R2ATYPJ.js';
|
|
10
14
|
import { CanvasStack, screenToWorld } from './chunk-574LJAV4.js';
|
|
@@ -14,13 +18,94 @@ import { LayerList } from './chunk-SFL7NFKN.js';
|
|
|
14
18
|
export { LayerList } from './chunk-SFL7NFKN.js';
|
|
15
19
|
import { createEventBus, redo, undo, pushSnapshot } from './chunk-RL2LOLNI.js';
|
|
16
20
|
export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
|
|
17
|
-
import { createContext, useContext, useMemo,
|
|
21
|
+
import { createContext, useRef, useCallback, useContext, useMemo, useState, Children, useLayoutEffect, useEffect } from 'react';
|
|
18
22
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
19
23
|
import { useStore } from 'zustand/react';
|
|
20
24
|
import { createPortal } from 'react-dom';
|
|
21
25
|
import { asNodeId, Store, createNode, gridStrategy } from 'windease';
|
|
22
26
|
import { DragHandle, Provider, StrategyRegistryProvider, DragProvider, Container } from 'windease/react';
|
|
23
27
|
|
|
28
|
+
var PITCH_LIMIT = Math.PI / 2 - 0.01;
|
|
29
|
+
var YAW_PER_PX = 8e-3;
|
|
30
|
+
var PITCH_PER_PX = 8e-3;
|
|
31
|
+
var DISTANCE_PER_NOTCH = 15e-4;
|
|
32
|
+
var DRAG_THRESHOLD = 3;
|
|
33
|
+
function clampPitch(pitch) {
|
|
34
|
+
return Math.min(PITCH_LIMIT, Math.max(-PITCH_LIMIT, pitch));
|
|
35
|
+
}
|
|
36
|
+
function wrapYaw(yaw) {
|
|
37
|
+
const wrapped = ((yaw + Math.PI) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) - Math.PI;
|
|
38
|
+
return wrapped === -Math.PI ? Math.PI : wrapped;
|
|
39
|
+
}
|
|
40
|
+
function orbitAfterDrag(start, dx, dy) {
|
|
41
|
+
return {
|
|
42
|
+
...start,
|
|
43
|
+
yaw: wrapYaw(start.yaw + dx * YAW_PER_PX),
|
|
44
|
+
pitch: clampPitch(start.pitch + dy * PITCH_PER_PX)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function orbitAfterWheel(view, deltaY, minDistance, maxDistance) {
|
|
48
|
+
const factor = Math.exp(deltaY * DISTANCE_PER_NOTCH);
|
|
49
|
+
return {
|
|
50
|
+
...view,
|
|
51
|
+
distance: Math.min(maxDistance, Math.max(minDistance, view.distance * factor))
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function useOrbit({
|
|
55
|
+
view,
|
|
56
|
+
onViewChange,
|
|
57
|
+
home,
|
|
58
|
+
minDistance = 0.1,
|
|
59
|
+
maxDistance = 1e3
|
|
60
|
+
}) {
|
|
61
|
+
const dragRef = useRef(null);
|
|
62
|
+
const viewRef = useRef(view);
|
|
63
|
+
viewRef.current = view;
|
|
64
|
+
const homeRef = useRef(home ?? view);
|
|
65
|
+
if (home) homeRef.current = home;
|
|
66
|
+
const onWheel = useCallback(
|
|
67
|
+
(e) => {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
onViewChange(orbitAfterWheel(viewRef.current, e.deltaY, minDistance, maxDistance));
|
|
70
|
+
},
|
|
71
|
+
[onViewChange, minDistance, maxDistance]
|
|
72
|
+
);
|
|
73
|
+
const onPointerDown = useCallback((e) => {
|
|
74
|
+
if (e.button !== 0) return;
|
|
75
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
76
|
+
dragRef.current = {
|
|
77
|
+
pointerId: e.pointerId,
|
|
78
|
+
startX: e.clientX,
|
|
79
|
+
startY: e.clientY,
|
|
80
|
+
startView: viewRef.current,
|
|
81
|
+
moved: false
|
|
82
|
+
};
|
|
83
|
+
}, []);
|
|
84
|
+
const onPointerMove = useCallback(
|
|
85
|
+
(e) => {
|
|
86
|
+
const drag = dragRef.current;
|
|
87
|
+
if (!drag || drag.pointerId !== e.pointerId) return;
|
|
88
|
+
const dx = e.clientX - drag.startX;
|
|
89
|
+
const dy = e.clientY - drag.startY;
|
|
90
|
+
if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
91
|
+
drag.moved = true;
|
|
92
|
+
onViewChange(orbitAfterDrag(drag.startView, dx, dy));
|
|
93
|
+
},
|
|
94
|
+
[onViewChange]
|
|
95
|
+
);
|
|
96
|
+
const onPointerUp = useCallback((e) => {
|
|
97
|
+
const drag = dragRef.current;
|
|
98
|
+
if (!drag || drag.pointerId !== e.pointerId) return;
|
|
99
|
+
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
|
100
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
101
|
+
}
|
|
102
|
+
dragRef.current = null;
|
|
103
|
+
}, []);
|
|
104
|
+
const onDoubleClick = useCallback(() => onViewChange(homeRef.current), [onViewChange]);
|
|
105
|
+
const isDragging = useCallback(() => dragRef.current?.moved === true, []);
|
|
106
|
+
return { onWheel, onPointerDown, onPointerMove, onPointerUp, onDoubleClick, isDragging };
|
|
107
|
+
}
|
|
108
|
+
|
|
24
109
|
// src/instrument/capabilityDetector.ts
|
|
25
110
|
function detectCapabilities(instrument) {
|
|
26
111
|
return {
|
|
@@ -90,7 +175,7 @@ function validateConfigSchema(fields) {
|
|
|
90
175
|
return { valid: errors.length === 0, errors };
|
|
91
176
|
}
|
|
92
177
|
|
|
93
|
-
// ../theme/dist/chunk-
|
|
178
|
+
// ../theme/dist/chunk-E3FOFGMD.js
|
|
94
179
|
var THEME_SOURCES = {
|
|
95
180
|
"weasel": {
|
|
96
181
|
"primitives": {
|
|
@@ -630,19 +715,20 @@ function resolveTheme(theme, mode) {
|
|
|
630
715
|
return out;
|
|
631
716
|
}
|
|
632
717
|
var STYLE_ID = "wzl-themes";
|
|
633
|
-
var emitted = /* @__PURE__ */ new
|
|
718
|
+
var emitted = /* @__PURE__ */ new Map();
|
|
634
719
|
var sheet = null;
|
|
635
720
|
var styleEl = null;
|
|
636
721
|
function canAdopt() {
|
|
637
722
|
return typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype && Array.isArray(document.adoptedStyleSheets);
|
|
638
723
|
}
|
|
639
|
-
function
|
|
724
|
+
function flushRules() {
|
|
725
|
+
const css = [...emitted.values()].join("\n");
|
|
640
726
|
if (canAdopt()) {
|
|
641
727
|
if (!sheet) {
|
|
642
728
|
sheet = new CSSStyleSheet();
|
|
643
729
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
644
730
|
}
|
|
645
|
-
sheet.
|
|
731
|
+
sheet.replaceSync(css);
|
|
646
732
|
return;
|
|
647
733
|
}
|
|
648
734
|
if (!styleEl) {
|
|
@@ -650,17 +736,17 @@ function appendRule(css) {
|
|
|
650
736
|
styleEl.id = STYLE_ID;
|
|
651
737
|
document.head.appendChild(styleEl);
|
|
652
738
|
}
|
|
653
|
-
styleEl.textContent
|
|
654
|
-
`;
|
|
739
|
+
styleEl.textContent = css;
|
|
655
740
|
}
|
|
656
741
|
function applyTheme(el, theme, mode) {
|
|
657
742
|
if (typeof document === "undefined") return;
|
|
658
743
|
const key = `${theme.name}::${mode}`;
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
emitted.
|
|
744
|
+
const resolved = resolveTheme(theme, mode);
|
|
745
|
+
const body = Object.entries(resolved).map(([name, value]) => `${name}: ${value};`).join(" ");
|
|
746
|
+
const rule = `[data-wzl-theme='${theme.name}'][data-wzl-mode='${mode}'] { ${body} }`;
|
|
747
|
+
if (emitted.get(key) !== rule) {
|
|
748
|
+
emitted.set(key, rule);
|
|
749
|
+
flushRules();
|
|
664
750
|
}
|
|
665
751
|
el.setAttribute("data-wzl-theme", theme.name);
|
|
666
752
|
el.setAttribute("data-wzl-mode", mode);
|
|
@@ -933,6 +1019,17 @@ function useLabContext() {
|
|
|
933
1019
|
}
|
|
934
1020
|
return ctx;
|
|
935
1021
|
}
|
|
1022
|
+
|
|
1023
|
+
// src/state/view.ts
|
|
1024
|
+
var DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } };
|
|
1025
|
+
function as2DView(view) {
|
|
1026
|
+
if (typeof view !== "object" || view === null) return null;
|
|
1027
|
+
const v = view;
|
|
1028
|
+
if (typeof v.zoom !== "number") return null;
|
|
1029
|
+
if (typeof v.pan !== "object" || v.pan === null) return null;
|
|
1030
|
+
if (typeof v.pan.x !== "number" || typeof v.pan.y !== "number") return null;
|
|
1031
|
+
return { zoom: v.zoom, pan: { x: v.pan.x, y: v.pan.y } };
|
|
1032
|
+
}
|
|
936
1033
|
function DefaultSidebar({ ctx }) {
|
|
937
1034
|
if (ctx.configFields.length === 0) {
|
|
938
1035
|
return /* @__PURE__ */ jsx(Sidebar, { title: ctx.instrumentName, children: /* @__PURE__ */ jsxs("div", { className: "lk-sidebar__placeholder", children: [
|
|
@@ -949,13 +1046,27 @@ function DefaultSidebar({ ctx }) {
|
|
|
949
1046
|
}
|
|
950
1047
|
) });
|
|
951
1048
|
}
|
|
1049
|
+
|
|
1050
|
+
// src/ui/format.ts
|
|
1051
|
+
var MINUS = "\u2212";
|
|
1052
|
+
function formatNumber(n, fractionDigits) {
|
|
1053
|
+
if (!Number.isFinite(n)) return String(n);
|
|
1054
|
+
const s = fractionDigits == null ? String(n) : n.toFixed(fractionDigits);
|
|
1055
|
+
return s.startsWith("-") ? MINUS + s.slice(1) : s;
|
|
1056
|
+
}
|
|
1057
|
+
function parseSignedNumber(s) {
|
|
1058
|
+
return Number(s.replace(MINUS, "-"));
|
|
1059
|
+
}
|
|
1060
|
+
function formatZoom(zoom) {
|
|
1061
|
+
if (!Number.isFinite(zoom)) return String(zoom);
|
|
1062
|
+
if (zoom <= 2) return `${formatNumber(Math.round(zoom * 100))}%`;
|
|
1063
|
+
if (zoom >= 100) return `${Math.round(zoom).toLocaleString("en-US")}x`;
|
|
1064
|
+
return `${formatNumber(Math.round(zoom * 10) / 10)}x`;
|
|
1065
|
+
}
|
|
952
1066
|
function DefaultStatusBar({ ctx }) {
|
|
953
1067
|
return /* @__PURE__ */ jsxs(StatusBar, { children: [
|
|
954
1068
|
/* @__PURE__ */ jsx(StatusBar.Section, { children: ctx.instrumentName }),
|
|
955
|
-
/* @__PURE__ */
|
|
956
|
-
Math.round(ctx.zoom * 100),
|
|
957
|
-
"%"
|
|
958
|
-
] })
|
|
1069
|
+
ctx.zoom === null ? null : /* @__PURE__ */ jsx(StatusBar.Section, { children: formatZoom(ctx.zoom) })
|
|
959
1070
|
] });
|
|
960
1071
|
}
|
|
961
1072
|
function DefaultToolbar({ ctx }) {
|
|
@@ -975,10 +1086,7 @@ function DefaultToolbar({ ctx }) {
|
|
|
975
1086
|
] }),
|
|
976
1087
|
ctx.hasCanvas && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
977
1088
|
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.zoomOut, title: "Zoom out", children: "\u2212" }),
|
|
978
|
-
/* @__PURE__ */
|
|
979
|
-
Math.round(ctx.zoom * 100),
|
|
980
|
-
"%"
|
|
981
|
-
] }),
|
|
1089
|
+
/* @__PURE__ */ jsx("span", { className: "lk-toolbar__zoom-label", children: formatZoom(ctx.zoom) }),
|
|
982
1090
|
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.zoomIn, title: "Zoom in", children: "+" }),
|
|
983
1091
|
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.resetZoom, title: "Reset zoom", children: "1:1" }),
|
|
984
1092
|
/* @__PURE__ */ jsx(Toolbar.Spacer, {})
|
|
@@ -1004,43 +1112,46 @@ function DefaultToolbar({ ctx }) {
|
|
|
1004
1112
|
}
|
|
1005
1113
|
),
|
|
1006
1114
|
/* @__PURE__ */ jsx(Toolbar.Spacer, {}),
|
|
1007
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.clone, title: "Clone
|
|
1008
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.reset, title: "Reset
|
|
1115
|
+
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.clone, title: "Clone trial", children: "Clone" }),
|
|
1116
|
+
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.reset, title: "Reset trial", children: "Reset" }),
|
|
1009
1117
|
/* @__PURE__ */ jsx(
|
|
1010
1118
|
Toolbar.Button,
|
|
1011
1119
|
{
|
|
1012
1120
|
onClick: ctx.close,
|
|
1013
|
-
disabled: ctx.
|
|
1014
|
-
title: ctx.
|
|
1121
|
+
disabled: ctx.isLastTrial,
|
|
1122
|
+
title: ctx.isLastTrial ? "Cannot close the last trial" : "Close trial",
|
|
1015
1123
|
children: "Close"
|
|
1016
1124
|
}
|
|
1017
1125
|
)
|
|
1018
1126
|
] });
|
|
1019
1127
|
}
|
|
1020
|
-
function
|
|
1021
|
-
|
|
1128
|
+
function TrialChrome({
|
|
1129
|
+
trialId,
|
|
1022
1130
|
record,
|
|
1023
1131
|
instrument,
|
|
1024
|
-
|
|
1132
|
+
isLastTrial,
|
|
1025
1133
|
toolbar,
|
|
1026
1134
|
sidebar,
|
|
1027
1135
|
statusBar,
|
|
1028
1136
|
undoBindings,
|
|
1137
|
+
job,
|
|
1029
1138
|
sidebarExtras,
|
|
1030
1139
|
children
|
|
1031
1140
|
}) {
|
|
1032
1141
|
const lab = useLabContext();
|
|
1033
1142
|
const storeCtx = useContext(LabStoreContext);
|
|
1034
|
-
if (!storeCtx) throw new Error("[labkit]
|
|
1035
|
-
const
|
|
1036
|
-
const
|
|
1037
|
-
const
|
|
1143
|
+
if (!storeCtx) throw new Error("[labkit] TrialChrome requires <LabStoreProvider>");
|
|
1144
|
+
const updateTrialView = useStore(storeCtx.store, (s) => s.updateTrialView);
|
|
1145
|
+
const updateTrialConfig = useStore(storeCtx.store, (s) => s.updateTrialConfig);
|
|
1146
|
+
const updateTrialState = useStore(storeCtx.store, (s) => s.updateTrialState);
|
|
1147
|
+
const view2d = as2DView(record.view);
|
|
1038
1148
|
const toolbarCtx = useMemo(() => {
|
|
1039
1149
|
const setZoom = (z) => {
|
|
1040
|
-
|
|
1150
|
+
if (!view2d) return;
|
|
1151
|
+
updateTrialView(trialId, { ...view2d, zoom: z });
|
|
1041
1152
|
};
|
|
1042
1153
|
return {
|
|
1043
|
-
|
|
1154
|
+
trialId,
|
|
1044
1155
|
instrumentName: record.instrumentName,
|
|
1045
1156
|
hasUndo: instrument.undo != null,
|
|
1046
1157
|
canUndo: undoBindings?.canUndo ?? false,
|
|
@@ -1049,24 +1160,24 @@ function WorkspaceChrome({
|
|
|
1049
1160
|
}),
|
|
1050
1161
|
redo: undoBindings?.redo ?? (() => {
|
|
1051
1162
|
}),
|
|
1052
|
-
zoom:
|
|
1163
|
+
zoom: view2d?.zoom ?? 1,
|
|
1053
1164
|
setZoom,
|
|
1054
|
-
zoomIn: () => setZoom(
|
|
1055
|
-
zoomOut: () => setZoom(
|
|
1165
|
+
zoomIn: () => setZoom((view2d?.zoom ?? 1) * 1.25),
|
|
1166
|
+
zoomOut: () => setZoom((view2d?.zoom ?? 1) * 0.8),
|
|
1056
1167
|
resetZoom: () => setZoom(1),
|
|
1057
1168
|
hasCanvas: instrument.canvas != null,
|
|
1058
|
-
savedSnapshots: lab.savedSnapshots.filter((s) => s.
|
|
1059
|
-
saveSnapshot: (name) => lab.saveSnapshot(
|
|
1060
|
-
loadSnapshot: (snapshotId) => lab.loadSnapshot(
|
|
1061
|
-
clone: () => lab.
|
|
1062
|
-
reset: () => lab.
|
|
1063
|
-
close: () => lab.
|
|
1064
|
-
|
|
1169
|
+
savedSnapshots: lab.savedSnapshots.filter((s) => s.trialId === trialId),
|
|
1170
|
+
saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
|
|
1171
|
+
loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
|
|
1172
|
+
clone: () => lab.cloneTrial(trialId),
|
|
1173
|
+
reset: () => lab.resetTrial(trialId),
|
|
1174
|
+
close: () => lab.closeTrial(trialId),
|
|
1175
|
+
isLastTrial
|
|
1065
1176
|
};
|
|
1066
|
-
}, [
|
|
1177
|
+
}, [trialId, record, instrument, lab, isLastTrial, updateTrialView, undoBindings, view2d]);
|
|
1067
1178
|
const sidebarCtx = useMemo(
|
|
1068
1179
|
() => ({
|
|
1069
|
-
|
|
1180
|
+
trialId,
|
|
1070
1181
|
instrumentName: record.instrumentName,
|
|
1071
1182
|
configFields: instrument.configSchema?.() ?? [],
|
|
1072
1183
|
config: record.config,
|
|
@@ -1077,20 +1188,20 @@ function WorkspaceChrome({
|
|
|
1077
1188
|
`[labkit] setConfig: unknown key "${key}" for instrument "${record.instrumentName}"`
|
|
1078
1189
|
);
|
|
1079
1190
|
}
|
|
1080
|
-
|
|
1191
|
+
updateTrialConfig(trialId, key, value);
|
|
1081
1192
|
if (instrument.onConfigChange) {
|
|
1082
1193
|
const nextConfig = { ...prevConfig, [key]: value };
|
|
1083
1194
|
const nextState = instrument.onConfigChange(nextConfig, prevConfig, record.state);
|
|
1084
|
-
|
|
1195
|
+
updateTrialState(trialId, nextState);
|
|
1085
1196
|
}
|
|
1086
1197
|
}
|
|
1087
1198
|
}),
|
|
1088
|
-
[
|
|
1199
|
+
[trialId, record, instrument, updateTrialConfig, updateTrialState]
|
|
1089
1200
|
);
|
|
1090
1201
|
const statusCtx = {
|
|
1091
|
-
|
|
1202
|
+
trialId,
|
|
1092
1203
|
instrumentName: record.instrumentName,
|
|
1093
|
-
zoom:
|
|
1204
|
+
zoom: view2d ? view2d.zoom : null
|
|
1094
1205
|
};
|
|
1095
1206
|
const handleKeyDown = (e) => {
|
|
1096
1207
|
const mod = e.metaKey || e.ctrlKey;
|
|
@@ -1107,57 +1218,71 @@ function WorkspaceChrome({
|
|
|
1107
1218
|
return /* @__PURE__ */ jsxs(
|
|
1108
1219
|
"section",
|
|
1109
1220
|
{
|
|
1110
|
-
className: "lk-
|
|
1111
|
-
"aria-label": `
|
|
1221
|
+
className: "lk-trial",
|
|
1222
|
+
"aria-label": `Trial ${record.instrumentName}`,
|
|
1112
1223
|
onKeyDown: handleKeyDown,
|
|
1113
1224
|
children: [
|
|
1114
|
-
/* @__PURE__ */ jsx("div", { className: "lk-
|
|
1115
|
-
/* @__PURE__ */ jsxs("div", { className: "lk-
|
|
1116
|
-
/* @__PURE__ */ jsxs("div", { className: "lk-
|
|
1225
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial__toolbar", children: toolbar ? toolbar(toolbarCtx) : /* @__PURE__ */ jsx(DefaultToolbar, { ctx: toolbarCtx }) }),
|
|
1226
|
+
/* @__PURE__ */ jsxs("div", { className: "lk-trial__body", children: [
|
|
1227
|
+
/* @__PURE__ */ jsxs("div", { className: "lk-trial__sidebar", children: [
|
|
1117
1228
|
sidebar ? sidebar(sidebarCtx) : /* @__PURE__ */ jsx(DefaultSidebar, { ctx: sidebarCtx }),
|
|
1118
1229
|
sidebarExtras
|
|
1119
1230
|
] }),
|
|
1120
|
-
/* @__PURE__ */ jsx("div", { className: "lk-
|
|
1231
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial__content", children })
|
|
1121
1232
|
] }),
|
|
1122
|
-
/* @__PURE__ */
|
|
1233
|
+
/* @__PURE__ */ jsxs("div", { className: "lk-trial__status", children: [
|
|
1234
|
+
job ? /* @__PURE__ */ jsxs("div", { className: "lk-trial__job", children: [
|
|
1235
|
+
/* @__PURE__ */ jsxs("span", { className: "lk-trial__job-count", children: [
|
|
1236
|
+
job.done,
|
|
1237
|
+
job.total === null ? "" : ` / ${job.total}`
|
|
1238
|
+
] }),
|
|
1239
|
+
job.failures.length > 0 ? /* @__PURE__ */ jsxs("span", { className: "lk-trial__job-failures", children: [
|
|
1240
|
+
job.failures.length,
|
|
1241
|
+
" failed"
|
|
1242
|
+
] }) : null,
|
|
1243
|
+
job.error ? /* @__PURE__ */ jsx("span", { className: "lk-trial__job-error", children: job.error }) : null,
|
|
1244
|
+
job.status === "running" ? /* @__PURE__ */ jsx("button", { type: "button", className: "lk-trial__job-cancel", onClick: job.cancel, children: "Cancel" }) : null
|
|
1245
|
+
] }) : null,
|
|
1246
|
+
statusBar ? statusBar(statusCtx) : /* @__PURE__ */ jsx(DefaultStatusBar, { ctx: statusCtx })
|
|
1247
|
+
] })
|
|
1123
1248
|
]
|
|
1124
1249
|
}
|
|
1125
1250
|
);
|
|
1126
1251
|
}
|
|
1127
|
-
function
|
|
1252
|
+
function Trial({ id }) {
|
|
1128
1253
|
const lab = useLabContext();
|
|
1129
1254
|
const storeCtx = useContext(LabStoreContext);
|
|
1130
|
-
if (!storeCtx) throw new Error("[labkit] <
|
|
1131
|
-
const record = useStore(storeCtx.store, (s) => s.
|
|
1255
|
+
if (!storeCtx) throw new Error("[labkit] <Trial> requires <LabStoreProvider>");
|
|
1256
|
+
const record = useStore(storeCtx.store, (s) => s.trials.find((w) => w.id === id));
|
|
1132
1257
|
if (!record) {
|
|
1133
|
-
return /* @__PURE__ */ jsxs("div", { className: "lk-
|
|
1134
|
-
"
|
|
1258
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
|
|
1259
|
+
"Trial not found: ",
|
|
1135
1260
|
id
|
|
1136
1261
|
] });
|
|
1137
1262
|
}
|
|
1138
1263
|
const instrument = lab.instruments.find((i) => i.name === record.instrumentName);
|
|
1139
1264
|
if (!instrument) {
|
|
1140
|
-
return /* @__PURE__ */ jsxs("div", { className: "lk-
|
|
1265
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
|
|
1141
1266
|
"Unknown instrument: ",
|
|
1142
1267
|
record.instrumentName
|
|
1143
1268
|
] });
|
|
1144
1269
|
}
|
|
1145
1270
|
return /* @__PURE__ */ jsx(
|
|
1146
|
-
|
|
1271
|
+
TrialRuntime,
|
|
1147
1272
|
{
|
|
1148
1273
|
record,
|
|
1149
1274
|
instrument,
|
|
1150
1275
|
store: storeCtx.store,
|
|
1151
|
-
isLast: lab.
|
|
1276
|
+
isLast: lab.trials.length <= 1
|
|
1152
1277
|
}
|
|
1153
1278
|
);
|
|
1154
1279
|
}
|
|
1155
|
-
function
|
|
1280
|
+
function TrialRuntime({ record, instrument, store, isLast }) {
|
|
1156
1281
|
const canvasContainerRef = useRef(null);
|
|
1157
|
-
const
|
|
1158
|
-
const
|
|
1159
|
-
const
|
|
1160
|
-
const
|
|
1282
|
+
const updateTrialState = useStore(store, (s) => s.updateTrialState);
|
|
1283
|
+
const updateTrialConfig = useStore(store, (s) => s.updateTrialConfig);
|
|
1284
|
+
const updateTrialView = useStore(store, (s) => s.updateTrialView);
|
|
1285
|
+
const updateTrialUndoStack = useStore(store, (s) => s.updateTrialUndoStack);
|
|
1161
1286
|
const busRef = useRef(null);
|
|
1162
1287
|
if (busRef.current === null) busRef.current = createEventBus();
|
|
1163
1288
|
const bus = busRef.current;
|
|
@@ -1168,37 +1293,52 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1168
1293
|
const maxDepth = undoCap?.maxDepth ?? 50;
|
|
1169
1294
|
const snapshotIfNeeded = (event) => {
|
|
1170
1295
|
if (!undoCap || !undoEvents.has(event)) return;
|
|
1171
|
-
const current = store.getState().
|
|
1296
|
+
const current = store.getState().trials.find((w) => w.id === record.id);
|
|
1172
1297
|
if (!current) return;
|
|
1173
1298
|
const snap = structuredClone(current.state);
|
|
1174
|
-
|
|
1299
|
+
updateTrialUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
|
|
1175
1300
|
};
|
|
1176
|
-
const setView = (v) =>
|
|
1301
|
+
const setView = (v) => updateTrialView(record.id, v);
|
|
1302
|
+
const view2d = as2DView(record.view);
|
|
1303
|
+
const jobCap = instrument.job;
|
|
1304
|
+
const job = useJob({
|
|
1305
|
+
capability: jobCap ?? { run: async function* () {
|
|
1306
|
+
}, onItem: (_i, st) => st },
|
|
1307
|
+
config: record.config,
|
|
1308
|
+
state: record.state,
|
|
1309
|
+
setState: (next) => updateTrialState(record.id, next)
|
|
1310
|
+
});
|
|
1177
1311
|
const renderCtx = {
|
|
1178
1312
|
state: record.state,
|
|
1179
1313
|
config: record.config,
|
|
1180
1314
|
setState: (next) => {
|
|
1181
1315
|
snapshotIfNeeded("state.change");
|
|
1182
|
-
|
|
1316
|
+
updateTrialState(record.id, next);
|
|
1183
1317
|
bus.emit("state.change");
|
|
1184
1318
|
},
|
|
1185
1319
|
setConfig: (key, value) => {
|
|
1186
1320
|
const evt = `config.change:${String(key)}`;
|
|
1187
1321
|
snapshotIfNeeded("config.change");
|
|
1188
1322
|
snapshotIfNeeded(evt);
|
|
1189
|
-
|
|
1323
|
+
updateTrialConfig(record.id, key, value);
|
|
1190
1324
|
bus.emit("config.change");
|
|
1191
1325
|
bus.emit(evt);
|
|
1192
1326
|
},
|
|
1193
|
-
|
|
1327
|
+
trial: {
|
|
1194
1328
|
id: record.id,
|
|
1195
|
-
|
|
1196
|
-
|
|
1329
|
+
view: record.view,
|
|
1330
|
+
setView,
|
|
1331
|
+
zoom: view2d?.zoom ?? 1,
|
|
1332
|
+
setZoom: (z) => {
|
|
1333
|
+
if (!view2d) return;
|
|
1334
|
+
updateTrialView(record.id, { ...view2d, zoom: z });
|
|
1335
|
+
}
|
|
1197
1336
|
},
|
|
1198
1337
|
emit: (event) => {
|
|
1199
1338
|
snapshotIfNeeded(event);
|
|
1200
1339
|
bus.emit(event);
|
|
1201
|
-
}
|
|
1340
|
+
},
|
|
1341
|
+
job: jobCap ? job : void 0
|
|
1202
1342
|
};
|
|
1203
1343
|
const undoBindings = undoCap ? {
|
|
1204
1344
|
canUndo: record.undoStack.past.length > 0,
|
|
@@ -1206,14 +1346,14 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1206
1346
|
undo: () => {
|
|
1207
1347
|
const result = undo(record.undoStack, structuredClone(record.state));
|
|
1208
1348
|
if (!result) return;
|
|
1209
|
-
|
|
1210
|
-
|
|
1349
|
+
updateTrialState(record.id, result.snapshot);
|
|
1350
|
+
updateTrialUndoStack(record.id, result.stack);
|
|
1211
1351
|
},
|
|
1212
1352
|
redo: () => {
|
|
1213
1353
|
const result = redo(record.undoStack, structuredClone(record.state));
|
|
1214
1354
|
if (!result) return;
|
|
1215
|
-
|
|
1216
|
-
|
|
1355
|
+
updateTrialState(record.id, result.snapshot);
|
|
1356
|
+
updateTrialUndoStack(record.id, result.stack);
|
|
1217
1357
|
}
|
|
1218
1358
|
} : void 0;
|
|
1219
1359
|
const canvasLayers = useMemo(() => {
|
|
@@ -1223,7 +1363,11 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1223
1363
|
return ordered.map((layer) => ({
|
|
1224
1364
|
id: layer.id,
|
|
1225
1365
|
visible: layerVisibility[layer.id] !== false,
|
|
1226
|
-
render: (ctx, view) =>
|
|
1366
|
+
render: (ctx, view) => {
|
|
1367
|
+
ctx.translate(view.pan.x, view.pan.y);
|
|
1368
|
+
ctx.scale(view.zoom, view.zoom);
|
|
1369
|
+
layer.draw(ctx, { state: record.state, config: record.config, zoom: view.zoom });
|
|
1370
|
+
}
|
|
1227
1371
|
}));
|
|
1228
1372
|
}, [instrument.canvas, record.state, record.config, layerVisibility, layerOrder]);
|
|
1229
1373
|
const layerDescriptors = useMemo(() => {
|
|
@@ -1233,12 +1377,12 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1233
1377
|
const dragDropResult = useDragDrop({
|
|
1234
1378
|
capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s) => s },
|
|
1235
1379
|
canvasContainerRef,
|
|
1236
|
-
view:
|
|
1380
|
+
view: view2d ?? DEFAULT_VIEW,
|
|
1237
1381
|
state: record.state,
|
|
1238
1382
|
config: record.config,
|
|
1239
1383
|
setState: (next) => {
|
|
1240
1384
|
snapshotIfNeeded("canvas.itemAdded");
|
|
1241
|
-
|
|
1385
|
+
updateTrialState(record.id, next);
|
|
1242
1386
|
},
|
|
1243
1387
|
emit: (evt) => {
|
|
1244
1388
|
snapshotIfNeeded(evt);
|
|
@@ -1280,10 +1424,18 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1280
1424
|
"div",
|
|
1281
1425
|
{
|
|
1282
1426
|
ref: canvasContainerRef,
|
|
1283
|
-
className: "lk-
|
|
1427
|
+
className: "lk-trial__canvas-host",
|
|
1284
1428
|
style: { width: "100%", height: "100%", position: "relative" },
|
|
1285
1429
|
children: [
|
|
1286
|
-
/* @__PURE__ */ jsx(
|
|
1430
|
+
/* @__PURE__ */ jsx(
|
|
1431
|
+
CanvasStack,
|
|
1432
|
+
{
|
|
1433
|
+
layers: layersWithFeedback,
|
|
1434
|
+
view: view2d ?? DEFAULT_VIEW,
|
|
1435
|
+
onViewChange: setView,
|
|
1436
|
+
children: instrument.render(renderCtx)
|
|
1437
|
+
}
|
|
1438
|
+
),
|
|
1287
1439
|
/* @__PURE__ */ jsx(DragOverlay, { drag: dragDropResult.drag })
|
|
1288
1440
|
]
|
|
1289
1441
|
}
|
|
@@ -1308,12 +1460,13 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1308
1460
|
}
|
|
1309
1461
|
) : null;
|
|
1310
1462
|
return /* @__PURE__ */ jsx(
|
|
1311
|
-
|
|
1463
|
+
TrialChrome,
|
|
1312
1464
|
{
|
|
1313
|
-
|
|
1465
|
+
job: jobCap ? job : void 0,
|
|
1466
|
+
trialId: record.id,
|
|
1314
1467
|
record,
|
|
1315
1468
|
instrument,
|
|
1316
|
-
|
|
1469
|
+
isLastTrial: isLast,
|
|
1317
1470
|
undoBindings,
|
|
1318
1471
|
sidebarExtras: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1319
1472
|
paletteNode,
|
|
@@ -1324,8 +1477,8 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1324
1477
|
);
|
|
1325
1478
|
}
|
|
1326
1479
|
|
|
1327
|
-
// src/
|
|
1328
|
-
var
|
|
1480
|
+
// src/trial/trialOps.ts
|
|
1481
|
+
var DEFAULT_VIEW2 = { zoom: 1, pan: { x: 0, y: 0 } };
|
|
1329
1482
|
function findInstrument(instruments, name) {
|
|
1330
1483
|
const found = instruments.find((i) => i.name === name);
|
|
1331
1484
|
if (!found) {
|
|
@@ -1334,9 +1487,9 @@ function findInstrument(instruments, name) {
|
|
|
1334
1487
|
return found;
|
|
1335
1488
|
}
|
|
1336
1489
|
function initialView(instrument) {
|
|
1337
|
-
return instrument.canvas?.initialView ? structuredClone(instrument.canvas.initialView) : { ...
|
|
1490
|
+
return instrument.canvas?.initialView ? structuredClone(instrument.canvas.initialView) : { ...DEFAULT_VIEW2, pan: { ...DEFAULT_VIEW2.pan } };
|
|
1338
1491
|
}
|
|
1339
|
-
function
|
|
1492
|
+
function addTrial(trials, instruments, instrumentName) {
|
|
1340
1493
|
const instrument = findInstrument(instruments, instrumentName);
|
|
1341
1494
|
const config = instrument.defaultConfig();
|
|
1342
1495
|
const state = instrument.initialState(config);
|
|
@@ -1348,12 +1501,12 @@ function addWorkspace(workspaces, instruments, instrumentName) {
|
|
|
1348
1501
|
view: initialView(instrument),
|
|
1349
1502
|
undoStack: { past: [], future: [] }
|
|
1350
1503
|
};
|
|
1351
|
-
return [...
|
|
1504
|
+
return [...trials, record];
|
|
1352
1505
|
}
|
|
1353
|
-
function
|
|
1354
|
-
const sourceIdx =
|
|
1355
|
-
const source =
|
|
1356
|
-
if (!source) return
|
|
1506
|
+
function cloneTrial(trials, id) {
|
|
1507
|
+
const sourceIdx = trials.findIndex((w) => w.id === id);
|
|
1508
|
+
const source = trials[sourceIdx];
|
|
1509
|
+
if (!source) return trials;
|
|
1357
1510
|
const clone = {
|
|
1358
1511
|
...source,
|
|
1359
1512
|
id: crypto.randomUUID(),
|
|
@@ -1362,17 +1515,17 @@ function cloneWorkspace(workspaces, id) {
|
|
|
1362
1515
|
view: structuredClone(source.view),
|
|
1363
1516
|
undoStack: { past: [], future: [] }
|
|
1364
1517
|
};
|
|
1365
|
-
return [...
|
|
1518
|
+
return [...trials.slice(0, sourceIdx + 1), clone, ...trials.slice(sourceIdx + 1)];
|
|
1366
1519
|
}
|
|
1367
|
-
function
|
|
1368
|
-
if (
|
|
1369
|
-
const next =
|
|
1370
|
-
return next.length ===
|
|
1520
|
+
function closeTrial(trials, id) {
|
|
1521
|
+
if (trials.length <= 1) return trials;
|
|
1522
|
+
const next = trials.filter((w) => w.id !== id);
|
|
1523
|
+
return next.length === trials.length ? trials : next;
|
|
1371
1524
|
}
|
|
1372
|
-
function
|
|
1373
|
-
const idx =
|
|
1374
|
-
const current =
|
|
1375
|
-
if (!current) return
|
|
1525
|
+
function resetTrial(trials, id, instruments) {
|
|
1526
|
+
const idx = trials.findIndex((w) => w.id === id);
|
|
1527
|
+
const current = trials[idx];
|
|
1528
|
+
if (!current) return trials;
|
|
1376
1529
|
const instrument = findInstrument(instruments, current.instrumentName);
|
|
1377
1530
|
const config = instrument.defaultConfig();
|
|
1378
1531
|
const state = instrument.initialState(config);
|
|
@@ -1382,13 +1535,13 @@ function resetWorkspace(workspaces, id, instruments) {
|
|
|
1382
1535
|
state,
|
|
1383
1536
|
view: initialView(instrument)
|
|
1384
1537
|
};
|
|
1385
|
-
return [...
|
|
1538
|
+
return [...trials.slice(0, idx), reset, ...trials.slice(idx + 1)];
|
|
1386
1539
|
}
|
|
1387
|
-
function
|
|
1388
|
-
const byId = new Map(
|
|
1540
|
+
function reorderTrials(trials, ids) {
|
|
1541
|
+
const byId = new Map(trials.map((w) => [w.id, w]));
|
|
1389
1542
|
const named = ids.map((id) => byId.get(id)).filter((w) => w !== void 0);
|
|
1390
1543
|
const seen = new Set(named.map((w) => w.id));
|
|
1391
|
-
const rest =
|
|
1544
|
+
const rest = trials.filter((w) => !seen.has(w.id));
|
|
1392
1545
|
return [...named, ...rest];
|
|
1393
1546
|
}
|
|
1394
1547
|
function query() {
|
|
@@ -1422,9 +1575,9 @@ function LabShell({ title, children, header, footer, mode = "auto" }) {
|
|
|
1422
1575
|
] });
|
|
1423
1576
|
return outer ? shell : /* @__PURE__ */ jsx(ThemeProvider, { theme: interstellarTheme, mode: resolved, children: shell });
|
|
1424
1577
|
}
|
|
1425
|
-
var ZONE_ID = asNodeId("lk-
|
|
1578
|
+
var ZONE_ID = asNodeId("lk-workspace");
|
|
1426
1579
|
var STRATEGIES = { grid: gridStrategy };
|
|
1427
|
-
var KIND = "
|
|
1580
|
+
var KIND = "trial";
|
|
1428
1581
|
function extentOf(store, id) {
|
|
1429
1582
|
const p2 = store.getNode(id)?.membership?.placement;
|
|
1430
1583
|
if (!p2) return null;
|
|
@@ -1433,7 +1586,7 @@ function extentOf(store, id) {
|
|
|
1433
1586
|
if (p2.span) out.span = p2.span;
|
|
1434
1587
|
return Object.keys(out).length > 0 ? out : null;
|
|
1435
1588
|
}
|
|
1436
|
-
function
|
|
1589
|
+
function Workspace({
|
|
1437
1590
|
children,
|
|
1438
1591
|
ids,
|
|
1439
1592
|
resizable = false,
|
|
@@ -1465,6 +1618,7 @@ function WorkspaceGrid({
|
|
|
1465
1618
|
storeRef.current = store2;
|
|
1466
1619
|
}
|
|
1467
1620
|
const store = storeRef.current;
|
|
1621
|
+
const surface = useSurfaceOptional();
|
|
1468
1622
|
useLayoutEffect(() => {
|
|
1469
1623
|
store.updateContainerConfig(ZONE_ID, { resizable, gap, padding });
|
|
1470
1624
|
}, [store, resizable, gap, padding]);
|
|
@@ -1483,6 +1637,11 @@ function WorkspaceGrid({
|
|
|
1483
1637
|
}
|
|
1484
1638
|
store.setChildOrder(ZONE_ID, [...nodeIds]);
|
|
1485
1639
|
}, [store, nodeIds]);
|
|
1640
|
+
useEffect(() => {
|
|
1641
|
+
if (!surface) return;
|
|
1642
|
+
surface.invalidateRects();
|
|
1643
|
+
return store.events.on("node.placementChanged", () => surface.invalidateRects());
|
|
1644
|
+
}, [store, surface, nodeIds]);
|
|
1486
1645
|
useEffect(() => {
|
|
1487
1646
|
if (!onLayoutChange) return;
|
|
1488
1647
|
return store.events.on("node.placementChanged", () => {
|
|
@@ -1504,9 +1663,9 @@ function WorkspaceGrid({
|
|
|
1504
1663
|
[KIND]: ({ node }) => {
|
|
1505
1664
|
const content = byId.get(node.id) ?? null;
|
|
1506
1665
|
if (!reorderable) return content;
|
|
1507
|
-
return /* @__PURE__ */ jsxs("div", { className: "lk-
|
|
1508
|
-
/* @__PURE__ */ jsx(DragHandle, { nodeId: node.id, className: "lk-
|
|
1509
|
-
/* @__PURE__ */ jsx("div", { className: "lk-
|
|
1666
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-trial-tile", children: [
|
|
1667
|
+
/* @__PURE__ */ jsx(DragHandle, { nodeId: node.id, className: "lk-trial-tile__grip", children: /* @__PURE__ */ jsx("span", { className: "lk-trial-tile__grip-dots", "aria-hidden": "true" }) }),
|
|
1668
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial-tile__body", children: content })
|
|
1510
1669
|
] });
|
|
1511
1670
|
}
|
|
1512
1671
|
};
|
|
@@ -1516,7 +1675,7 @@ function WorkspaceGrid({
|
|
|
1516
1675
|
{
|
|
1517
1676
|
parentId: ZONE_ID,
|
|
1518
1677
|
chrome,
|
|
1519
|
-
className: "lk-workspace
|
|
1678
|
+
className: "lk-workspace windease-zone",
|
|
1520
1679
|
affordances: resizable,
|
|
1521
1680
|
viewport,
|
|
1522
1681
|
onChildOrderChange: reorderable ? commitOrder : void 0
|
|
@@ -1526,12 +1685,12 @@ function WorkspaceGrid({
|
|
|
1526
1685
|
}
|
|
1527
1686
|
function buildStore(instruments, defaultInstrument, storage, storageKey, initialMode) {
|
|
1528
1687
|
const store = createLabStore({ storageKey, storage, initialMode });
|
|
1529
|
-
if (store.getState().
|
|
1530
|
-
const seeded =
|
|
1688
|
+
if (store.getState().trials.length === 0) {
|
|
1689
|
+
const seeded = addTrial([], instruments, defaultInstrument);
|
|
1531
1690
|
const record = seeded[0];
|
|
1532
1691
|
if (record) {
|
|
1533
1692
|
const { undoStack: _undoStack, ...rest } = record;
|
|
1534
|
-
store.getState().
|
|
1693
|
+
store.getState().addTrial(rest);
|
|
1535
1694
|
}
|
|
1536
1695
|
}
|
|
1537
1696
|
return store;
|
|
@@ -1575,7 +1734,7 @@ function Lab({
|
|
|
1575
1734
|
);
|
|
1576
1735
|
}
|
|
1577
1736
|
const store = storeRef.current;
|
|
1578
|
-
const
|
|
1737
|
+
const trials = useStore(store, (s) => s.trials);
|
|
1579
1738
|
const savedSnapshots = useStore(store, (s) => s.savedSnapshots);
|
|
1580
1739
|
const modeValue = useStore(store, (s) => s.mode);
|
|
1581
1740
|
const layout = useStore(store, (s) => s.layout);
|
|
@@ -1586,47 +1745,47 @@ function Lab({
|
|
|
1586
1745
|
}
|
|
1587
1746
|
}, [mode, store]);
|
|
1588
1747
|
const contextValue = useMemo(() => {
|
|
1589
|
-
const
|
|
1590
|
-
const currentById = new Map(store.getState().
|
|
1748
|
+
const replaceTrials = (next) => {
|
|
1749
|
+
const currentById = new Map(store.getState().trials.map((w) => [w.id, w]));
|
|
1591
1750
|
const merged = next.map((w) => currentById.get(w.id) ?? w);
|
|
1592
|
-
store.setState({
|
|
1751
|
+
store.setState({ trials: merged });
|
|
1593
1752
|
store.getState().setMode(store.getState().mode);
|
|
1594
1753
|
};
|
|
1595
1754
|
return {
|
|
1596
1755
|
instruments,
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
const next =
|
|
1600
|
-
|
|
1756
|
+
trials,
|
|
1757
|
+
addTrial: (instrumentName) => {
|
|
1758
|
+
const next = addTrial(store.getState().trials, instruments, instrumentName);
|
|
1759
|
+
replaceTrials(next);
|
|
1601
1760
|
},
|
|
1602
|
-
|
|
1603
|
-
const next =
|
|
1604
|
-
|
|
1761
|
+
cloneTrial: (id) => {
|
|
1762
|
+
const next = cloneTrial(store.getState().trials, id);
|
|
1763
|
+
replaceTrials(next);
|
|
1605
1764
|
},
|
|
1606
|
-
|
|
1607
|
-
const next =
|
|
1608
|
-
|
|
1765
|
+
closeTrial: (id) => {
|
|
1766
|
+
const next = closeTrial(store.getState().trials, id);
|
|
1767
|
+
replaceTrials(next);
|
|
1609
1768
|
},
|
|
1610
|
-
|
|
1611
|
-
|
|
1769
|
+
reorderTrials: (ids) => {
|
|
1770
|
+
replaceTrials(reorderTrials(store.getState().trials, ids));
|
|
1612
1771
|
},
|
|
1613
|
-
|
|
1614
|
-
const next =
|
|
1772
|
+
resetTrial: (id) => {
|
|
1773
|
+
const next = resetTrial(store.getState().trials, id, instruments);
|
|
1615
1774
|
const record = next.find((w) => w.id === id);
|
|
1616
1775
|
if (!record) return;
|
|
1617
1776
|
store.setState((s) => ({
|
|
1618
|
-
|
|
1777
|
+
trials: s.trials.map(
|
|
1619
1778
|
(w) => w.id === id ? { ...w, config: record.config, state: record.state, view: record.view } : w
|
|
1620
1779
|
)
|
|
1621
1780
|
}));
|
|
1622
|
-
store.getState().
|
|
1781
|
+
store.getState().updateTrialView(id, record.view);
|
|
1623
1782
|
},
|
|
1624
1783
|
savedSnapshots,
|
|
1625
|
-
saveSnapshot: (
|
|
1626
|
-
store.getState().saveSnapshot(
|
|
1784
|
+
saveSnapshot: (trialId, name) => {
|
|
1785
|
+
store.getState().saveSnapshot(trialId, name ?? `Save ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
1627
1786
|
},
|
|
1628
|
-
loadSnapshot: (
|
|
1629
|
-
store.getState().loadSnapshot(snapshotId,
|
|
1787
|
+
loadSnapshot: (trialId, snapshotId) => {
|
|
1788
|
+
store.getState().loadSnapshot(snapshotId, trialId);
|
|
1630
1789
|
},
|
|
1631
1790
|
deleteSnapshot: (snapshotId) => {
|
|
1632
1791
|
store.getState().deleteSnapshot(snapshotId);
|
|
@@ -1636,7 +1795,7 @@ function Lab({
|
|
|
1636
1795
|
store.getState().setMode(m);
|
|
1637
1796
|
}
|
|
1638
1797
|
};
|
|
1639
|
-
}, [instruments,
|
|
1798
|
+
}, [instruments, trials, savedSnapshots, modeValue, store]);
|
|
1640
1799
|
const backdropStyle = resolvedMode === "dark" && nebula && nebula.length > 0 ? { ["--wzl-backdrop"]: buildNebula(nebula) } : void 0;
|
|
1641
1800
|
return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
1642
1801
|
ThemeProvider,
|
|
@@ -1646,15 +1805,15 @@ function Lab({
|
|
|
1646
1805
|
className: "lk-lab",
|
|
1647
1806
|
style: backdropStyle,
|
|
1648
1807
|
children: /* @__PURE__ */ jsx(LabShell, { title: title ?? "Labkit", mode: modeValue, header: children, children: /* @__PURE__ */ jsx(
|
|
1649
|
-
|
|
1808
|
+
Workspace,
|
|
1650
1809
|
{
|
|
1651
|
-
ids:
|
|
1810
|
+
ids: trials.map((w) => w.id),
|
|
1652
1811
|
resizable: true,
|
|
1653
1812
|
reorderable: true,
|
|
1654
|
-
onReorder: (ids) => contextValue.
|
|
1813
|
+
onReorder: (ids) => contextValue.reorderTrials(ids),
|
|
1655
1814
|
layout,
|
|
1656
1815
|
onLayoutChange: (next) => store.getState().setLayout(next),
|
|
1657
|
-
children:
|
|
1816
|
+
children: trials.map((w) => /* @__PURE__ */ jsx(Trial, { id: w.id }, w.id))
|
|
1658
1817
|
}
|
|
1659
1818
|
) })
|
|
1660
1819
|
}
|
|
@@ -2024,17 +2183,6 @@ function PropertyGroup({
|
|
|
2024
2183
|
/* @__PURE__ */ jsx("div", { className: "lk-property-group__body", children })
|
|
2025
2184
|
] });
|
|
2026
2185
|
}
|
|
2027
|
-
|
|
2028
|
-
// src/ui/format.ts
|
|
2029
|
-
var MINUS = "\u2212";
|
|
2030
|
-
function formatNumber(n, fractionDigits) {
|
|
2031
|
-
if (!Number.isFinite(n)) return String(n);
|
|
2032
|
-
const s = fractionDigits == null ? String(n) : n.toFixed(fractionDigits);
|
|
2033
|
-
return s.startsWith("-") ? MINUS + s.slice(1) : s;
|
|
2034
|
-
}
|
|
2035
|
-
function parseSignedNumber(s) {
|
|
2036
|
-
return Number(s.replace(MINUS, "-"));
|
|
2037
|
-
}
|
|
2038
2186
|
function PropertyPanel({ title, children, className }) {
|
|
2039
2187
|
const cls = className ? `lk-property-panel ${className}` : "lk-property-panel";
|
|
2040
2188
|
return /* @__PURE__ */ jsxs("div", { className: cls, children: [
|
|
@@ -2278,6 +2426,6 @@ function ToggleRow({
|
|
|
2278
2426
|
}) }) });
|
|
2279
2427
|
}
|
|
2280
2428
|
|
|
2281
|
-
export { CheckboxRow, ColorRow, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Lab, LabContext, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, Subpanel, TextRow, ToggleRow, Workspace,
|
|
2429
|
+
export { CheckboxRow, ColorRow, CurveField, DEFAULT_VIEW, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Lab, LabContext, LabShell, NumberRow, PITCH_LIMIT, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, Subpanel, TextRow, ToggleRow, Trial, TrialChrome, Workspace, addTrial, as2DView, clampPitch, cloneTrial, closeTrial, defineInstrument, detectCapabilities, interstellarTheme, orbitAfterDrag, orbitAfterWheel, reorderTrials, resetTrial, useLabContext, useOrbit, validateConfigSchema, wrapYaw };
|
|
2282
2430
|
//# sourceMappingURL=index.js.map
|
|
2283
2431
|
//# sourceMappingURL=index.js.map
|