@weasel-js/labkit 1.0.3 → 1.1.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 +6 -6
- package/dist/_dts/{DrawCommand-uKHt4Vul.d.ts → DrawCommand-BkZztJsW.d.ts} +3 -1
- package/dist/_dts/{index-CFlDPeZh.d.ts → index-iAP6XbH3.d.ts} +17 -7
- package/dist/_dts/{useExperimentState-vrttakTt.d.ts → useTrialState-DYe2vUwN.d.ts} +62 -34
- package/dist/canvas/index.d.ts +1 -1
- package/dist/chunk-3TYUJR7Z.js +488 -0
- package/dist/chunk-3TYUJR7Z.js.map +1 -0
- package/dist/{chunk-N5KTQKQA.js → chunk-C6GJKPUI.js} +835 -770
- package/dist/chunk-C6GJKPUI.js.map +1 -0
- package/dist/{chunk-3WPOGKUP.js → chunk-DJLDIRFN.js} +543 -226
- package/dist/chunk-DJLDIRFN.js.map +1 -0
- package/dist/{chunk-73PXCRCR.js → chunk-NRKWVTVT.js} +6 -6
- package/dist/chunk-NRKWVTVT.js.map +1 -0
- package/dist/dragdrop/index.d.ts +1 -1
- package/dist/index.d.ts +197 -148
- package/dist/index.js +275 -161
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +2 -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 +10 -3
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/state/index.d.ts +14 -14
- package/dist/state/index.js +5 -5
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +179 -14
- package/dist/ui/layers/index.js +3 -3
- package/dist/undo/index.d.ts +1 -1
- package/package.json +2 -1
- package/src/canvas/AGENTS.md +6 -2
- package/src/dragdrop/dragDrop.test.tsx +4 -7
- package/src/index.test.ts +52 -0
- package/src/index.ts +11 -7
- package/src/instrument/SineWave.smoke.test.tsx +1 -1
- package/src/instrument/capabilityDetector.ts +1 -1
- package/src/instrument/types.ts +18 -6
- package/src/lab/Lab.less +16 -0
- package/src/lab/Lab.stories.tsx +5 -5
- package/src/lab/Lab.test.tsx +25 -25
- package/src/lab/Lab.tsx +56 -44
- package/src/lab/LabContext.ts +12 -11
- package/src/lab/LabShell.tsx +1 -1
- package/src/lab/Workspace.less +40 -0
- package/src/lab/{WorkspaceGrid.stories.tsx → Workspace.stories.tsx} +27 -11
- package/src/lab/Workspace.test.tsx +114 -0
- package/src/lab/Workspace.tsx +196 -0
- package/src/lab/index.ts +2 -4
- 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 +23 -31
- package/src/state/index.ts +16 -8
- package/src/state/store.test.ts +440 -40
- package/src/state/store.ts +166 -90
- package/src/state/types.ts +30 -10
- package/src/state/{useExperimentState.test.tsx → useTrialState.test.tsx} +18 -18
- package/src/state/useTrialState.ts +29 -0
- package/src/styles.less +2 -2
- package/src/test-setup.ts +19 -0
- package/src/theme/base.less +1 -1
- package/src/{workspace → trial}/DefaultSidebar.tsx +3 -3
- package/src/{workspace → trial}/DefaultStatusBar.tsx +5 -4
- package/src/{workspace → trial}/DefaultToolbar.tsx +9 -8
- package/src/trial/Trial.canvas.test.tsx +80 -0
- package/src/{workspace/Workspace.less → trial/Trial.less} +1 -1
- package/src/{workspace/Workspace.stories.tsx → trial/Trial.stories.tsx} +13 -17
- package/src/{workspace/Workspace.test.tsx → trial/Trial.test.tsx} +26 -25
- package/src/{workspace/Workspace.tsx → trial/Trial.tsx} +48 -43
- package/src/{workspace/WorkspaceChrome.tsx → trial/TrialChrome.tsx} +42 -42
- package/src/{workspace → trial}/index.ts +13 -12
- package/src/{workspace → trial}/slotTypes.ts +16 -16
- package/src/{workspace/workspaceOps.test.ts → trial/trialOps.test.ts} +60 -33
- 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/chunk-3WPOGKUP.js.map +0 -1
- package/dist/chunk-73PXCRCR.js.map +0 -1
- package/dist/chunk-N5KTQKQA.js.map +0 -1
- package/dist/chunk-T7OKNJTY.js +0 -351
- package/dist/chunk-T7OKNJTY.js.map +0 -1
- package/src/lab/WorkspaceGrid.less +0 -8
- package/src/lab/WorkspaceGrid.test.tsx +0 -40
- package/src/lab/WorkspaceGrid.tsx +0 -22
- package/src/lab/gridDims.test.ts +0 -35
- package/src/lab/gridDims.ts +0 -13
- package/src/state/useExperimentState.ts +0 -31
- package/src/workspace/workspaceOps.ts +0 -86
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export { LayerStack } from './chunk-
|
|
2
|
-
import { p, F } from './chunk-
|
|
3
|
-
import './chunk-
|
|
1
|
+
export { LayerStack } from './chunk-NRKWVTVT.js';
|
|
2
|
+
import { p, F } from './chunk-C6GJKPUI.js';
|
|
3
|
+
import './chunk-DJLDIRFN.js';
|
|
4
4
|
import { Sidebar, StatusBar, Toolbar } from './chunk-PMAU3SEE.js';
|
|
5
5
|
export { FpsMeter, ScaleIndicator, Sidebar, StatusBar, Toolbar } from './chunk-PMAU3SEE.js';
|
|
6
|
-
import { LabStoreContext, createLabStore, noneAdapter } from './chunk-
|
|
7
|
-
export { LabStoreContext, LabStoreProvider,
|
|
6
|
+
import { LabStoreContext, createLabStore, noneAdapter } from './chunk-3TYUJR7Z.js';
|
|
7
|
+
export { CURRENT_DOCUMENT_VERSION, LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, createMemoryAdapter, labDocumentKey, localStorageAdapter, noneAdapter, quarantineKey, sessionStorageAdapter, urlHashAdapter, useLabStore, useTrialId, useTrialState } from './chunk-3TYUJR7Z.js';
|
|
8
8
|
import { ControlPanel } from './chunk-5R2ATYPJ.js';
|
|
9
9
|
export { ControlPanel } from './chunk-5R2ATYPJ.js';
|
|
10
10
|
import { CanvasStack, screenToWorld } from './chunk-574LJAV4.js';
|
|
@@ -14,10 +14,12 @@ import { LayerList } from './chunk-SFL7NFKN.js';
|
|
|
14
14
|
export { LayerList } from './chunk-SFL7NFKN.js';
|
|
15
15
|
import { createEventBus, redo, undo, pushSnapshot } from './chunk-RL2LOLNI.js';
|
|
16
16
|
export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
|
|
17
|
-
import { createContext, useContext, useMemo, useRef, useState, Children, useEffect, useCallback
|
|
17
|
+
import { createContext, useContext, useMemo, useRef, useState, Children, useLayoutEffect, useEffect, useCallback } from 'react';
|
|
18
18
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
19
19
|
import { useStore } from 'zustand/react';
|
|
20
20
|
import { createPortal } from 'react-dom';
|
|
21
|
+
import { asNodeId, Store, createNode, gridStrategy } from 'windease';
|
|
22
|
+
import { DragHandle, Provider, StrategyRegistryProvider, DragProvider, Container } from 'windease/react';
|
|
21
23
|
|
|
22
24
|
// src/instrument/capabilityDetector.ts
|
|
23
25
|
function detectCapabilities(instrument) {
|
|
@@ -88,15 +90,7 @@ function validateConfigSchema(fields) {
|
|
|
88
90
|
return { valid: errors.length === 0, errors };
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
//
|
|
92
|
-
function gridDims(count) {
|
|
93
|
-
if (count <= 1) return { cols: 1, rows: 1 };
|
|
94
|
-
const cols = Math.ceil(Math.sqrt(count));
|
|
95
|
-
const rows = Math.ceil(count / cols);
|
|
96
|
-
return { cols, rows };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ../theme/dist/chunk-STFENPNX.js
|
|
93
|
+
// ../theme/dist/chunk-E3FOFGMD.js
|
|
100
94
|
var THEME_SOURCES = {
|
|
101
95
|
"weasel": {
|
|
102
96
|
"primitives": {
|
|
@@ -636,19 +630,20 @@ function resolveTheme(theme, mode) {
|
|
|
636
630
|
return out;
|
|
637
631
|
}
|
|
638
632
|
var STYLE_ID = "wzl-themes";
|
|
639
|
-
var emitted = /* @__PURE__ */ new
|
|
633
|
+
var emitted = /* @__PURE__ */ new Map();
|
|
640
634
|
var sheet = null;
|
|
641
635
|
var styleEl = null;
|
|
642
636
|
function canAdopt() {
|
|
643
637
|
return typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype && Array.isArray(document.adoptedStyleSheets);
|
|
644
638
|
}
|
|
645
|
-
function
|
|
639
|
+
function flushRules() {
|
|
640
|
+
const css = [...emitted.values()].join("\n");
|
|
646
641
|
if (canAdopt()) {
|
|
647
642
|
if (!sheet) {
|
|
648
643
|
sheet = new CSSStyleSheet();
|
|
649
644
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
650
645
|
}
|
|
651
|
-
sheet.
|
|
646
|
+
sheet.replaceSync(css);
|
|
652
647
|
return;
|
|
653
648
|
}
|
|
654
649
|
if (!styleEl) {
|
|
@@ -656,17 +651,17 @@ function appendRule(css) {
|
|
|
656
651
|
styleEl.id = STYLE_ID;
|
|
657
652
|
document.head.appendChild(styleEl);
|
|
658
653
|
}
|
|
659
|
-
styleEl.textContent
|
|
660
|
-
`;
|
|
654
|
+
styleEl.textContent = css;
|
|
661
655
|
}
|
|
662
656
|
function applyTheme(el, theme, mode) {
|
|
663
657
|
if (typeof document === "undefined") return;
|
|
664
658
|
const key = `${theme.name}::${mode}`;
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
emitted.
|
|
659
|
+
const resolved = resolveTheme(theme, mode);
|
|
660
|
+
const body = Object.entries(resolved).map(([name, value]) => `${name}: ${value};`).join(" ");
|
|
661
|
+
const rule = `[data-wzl-theme='${theme.name}'][data-wzl-mode='${mode}'] { ${body} }`;
|
|
662
|
+
if (emitted.get(key) !== rule) {
|
|
663
|
+
emitted.set(key, rule);
|
|
664
|
+
flushRules();
|
|
670
665
|
}
|
|
671
666
|
el.setAttribute("data-wzl-theme", theme.name);
|
|
672
667
|
el.setAttribute("data-wzl-mode", mode);
|
|
@@ -955,13 +950,27 @@ function DefaultSidebar({ ctx }) {
|
|
|
955
950
|
}
|
|
956
951
|
) });
|
|
957
952
|
}
|
|
953
|
+
|
|
954
|
+
// src/ui/format.ts
|
|
955
|
+
var MINUS = "\u2212";
|
|
956
|
+
function formatNumber(n, fractionDigits) {
|
|
957
|
+
if (!Number.isFinite(n)) return String(n);
|
|
958
|
+
const s = fractionDigits == null ? String(n) : n.toFixed(fractionDigits);
|
|
959
|
+
return s.startsWith("-") ? MINUS + s.slice(1) : s;
|
|
960
|
+
}
|
|
961
|
+
function parseSignedNumber(s) {
|
|
962
|
+
return Number(s.replace(MINUS, "-"));
|
|
963
|
+
}
|
|
964
|
+
function formatZoom(zoom) {
|
|
965
|
+
if (!Number.isFinite(zoom)) return String(zoom);
|
|
966
|
+
if (zoom <= 2) return `${formatNumber(Math.round(zoom * 100))}%`;
|
|
967
|
+
if (zoom >= 100) return `${Math.round(zoom).toLocaleString("en-US")}x`;
|
|
968
|
+
return `${formatNumber(Math.round(zoom * 10) / 10)}x`;
|
|
969
|
+
}
|
|
958
970
|
function DefaultStatusBar({ ctx }) {
|
|
959
971
|
return /* @__PURE__ */ jsxs(StatusBar, { children: [
|
|
960
972
|
/* @__PURE__ */ jsx(StatusBar.Section, { children: ctx.instrumentName }),
|
|
961
|
-
/* @__PURE__ */
|
|
962
|
-
Math.round(ctx.zoom * 100),
|
|
963
|
-
"%"
|
|
964
|
-
] })
|
|
973
|
+
/* @__PURE__ */ jsx(StatusBar.Section, { children: formatZoom(ctx.zoom) })
|
|
965
974
|
] });
|
|
966
975
|
}
|
|
967
976
|
function DefaultToolbar({ ctx }) {
|
|
@@ -981,10 +990,7 @@ function DefaultToolbar({ ctx }) {
|
|
|
981
990
|
] }),
|
|
982
991
|
ctx.hasCanvas && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
983
992
|
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.zoomOut, title: "Zoom out", children: "\u2212" }),
|
|
984
|
-
/* @__PURE__ */
|
|
985
|
-
Math.round(ctx.zoom * 100),
|
|
986
|
-
"%"
|
|
987
|
-
] }),
|
|
993
|
+
/* @__PURE__ */ jsx("span", { className: "lk-toolbar__zoom-label", children: formatZoom(ctx.zoom) }),
|
|
988
994
|
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.zoomIn, title: "Zoom in", children: "+" }),
|
|
989
995
|
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.resetZoom, title: "Reset zoom", children: "1:1" }),
|
|
990
996
|
/* @__PURE__ */ jsx(Toolbar.Spacer, {})
|
|
@@ -1010,24 +1016,24 @@ function DefaultToolbar({ ctx }) {
|
|
|
1010
1016
|
}
|
|
1011
1017
|
),
|
|
1012
1018
|
/* @__PURE__ */ jsx(Toolbar.Spacer, {}),
|
|
1013
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.clone, title: "Clone
|
|
1014
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.reset, title: "Reset
|
|
1019
|
+
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.clone, title: "Clone trial", children: "Clone" }),
|
|
1020
|
+
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.reset, title: "Reset trial", children: "Reset" }),
|
|
1015
1021
|
/* @__PURE__ */ jsx(
|
|
1016
1022
|
Toolbar.Button,
|
|
1017
1023
|
{
|
|
1018
1024
|
onClick: ctx.close,
|
|
1019
|
-
disabled: ctx.
|
|
1020
|
-
title: ctx.
|
|
1025
|
+
disabled: ctx.isLastTrial,
|
|
1026
|
+
title: ctx.isLastTrial ? "Cannot close the last trial" : "Close trial",
|
|
1021
1027
|
children: "Close"
|
|
1022
1028
|
}
|
|
1023
1029
|
)
|
|
1024
1030
|
] });
|
|
1025
1031
|
}
|
|
1026
|
-
function
|
|
1027
|
-
|
|
1032
|
+
function TrialChrome({
|
|
1033
|
+
trialId,
|
|
1028
1034
|
record,
|
|
1029
1035
|
instrument,
|
|
1030
|
-
|
|
1036
|
+
isLastTrial,
|
|
1031
1037
|
toolbar,
|
|
1032
1038
|
sidebar,
|
|
1033
1039
|
statusBar,
|
|
@@ -1037,16 +1043,16 @@ function WorkspaceChrome({
|
|
|
1037
1043
|
}) {
|
|
1038
1044
|
const lab = useLabContext();
|
|
1039
1045
|
const storeCtx = useContext(LabStoreContext);
|
|
1040
|
-
if (!storeCtx) throw new Error("[labkit]
|
|
1041
|
-
const
|
|
1042
|
-
const
|
|
1043
|
-
const
|
|
1046
|
+
if (!storeCtx) throw new Error("[labkit] TrialChrome requires <LabStoreProvider>");
|
|
1047
|
+
const updateTrialView = useStore(storeCtx.store, (s) => s.updateTrialView);
|
|
1048
|
+
const updateTrialConfig = useStore(storeCtx.store, (s) => s.updateTrialConfig);
|
|
1049
|
+
const updateTrialState = useStore(storeCtx.store, (s) => s.updateTrialState);
|
|
1044
1050
|
const toolbarCtx = useMemo(() => {
|
|
1045
1051
|
const setZoom = (z) => {
|
|
1046
|
-
|
|
1052
|
+
updateTrialView(trialId, { ...record.view, zoom: z });
|
|
1047
1053
|
};
|
|
1048
1054
|
return {
|
|
1049
|
-
|
|
1055
|
+
trialId,
|
|
1050
1056
|
instrumentName: record.instrumentName,
|
|
1051
1057
|
hasUndo: instrument.undo != null,
|
|
1052
1058
|
canUndo: undoBindings?.canUndo ?? false,
|
|
@@ -1061,18 +1067,18 @@ function WorkspaceChrome({
|
|
|
1061
1067
|
zoomOut: () => setZoom(record.view.zoom * 0.8),
|
|
1062
1068
|
resetZoom: () => setZoom(1),
|
|
1063
1069
|
hasCanvas: instrument.canvas != null,
|
|
1064
|
-
savedSnapshots: lab.savedSnapshots.filter((s) => s.
|
|
1065
|
-
saveSnapshot: (name) => lab.saveSnapshot(
|
|
1066
|
-
loadSnapshot: (snapshotId) => lab.loadSnapshot(
|
|
1067
|
-
clone: () => lab.
|
|
1068
|
-
reset: () => lab.
|
|
1069
|
-
close: () => lab.
|
|
1070
|
-
|
|
1070
|
+
savedSnapshots: lab.savedSnapshots.filter((s) => s.trialId === trialId),
|
|
1071
|
+
saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
|
|
1072
|
+
loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
|
|
1073
|
+
clone: () => lab.cloneTrial(trialId),
|
|
1074
|
+
reset: () => lab.resetTrial(trialId),
|
|
1075
|
+
close: () => lab.closeTrial(trialId),
|
|
1076
|
+
isLastTrial
|
|
1071
1077
|
};
|
|
1072
|
-
}, [
|
|
1078
|
+
}, [trialId, record, instrument, lab, isLastTrial, updateTrialView, undoBindings]);
|
|
1073
1079
|
const sidebarCtx = useMemo(
|
|
1074
1080
|
() => ({
|
|
1075
|
-
|
|
1081
|
+
trialId,
|
|
1076
1082
|
instrumentName: record.instrumentName,
|
|
1077
1083
|
configFields: instrument.configSchema?.() ?? [],
|
|
1078
1084
|
config: record.config,
|
|
@@ -1083,18 +1089,18 @@ function WorkspaceChrome({
|
|
|
1083
1089
|
`[labkit] setConfig: unknown key "${key}" for instrument "${record.instrumentName}"`
|
|
1084
1090
|
);
|
|
1085
1091
|
}
|
|
1086
|
-
|
|
1092
|
+
updateTrialConfig(trialId, key, value);
|
|
1087
1093
|
if (instrument.onConfigChange) {
|
|
1088
1094
|
const nextConfig = { ...prevConfig, [key]: value };
|
|
1089
1095
|
const nextState = instrument.onConfigChange(nextConfig, prevConfig, record.state);
|
|
1090
|
-
|
|
1096
|
+
updateTrialState(trialId, nextState);
|
|
1091
1097
|
}
|
|
1092
1098
|
}
|
|
1093
1099
|
}),
|
|
1094
|
-
[
|
|
1100
|
+
[trialId, record, instrument, updateTrialConfig, updateTrialState]
|
|
1095
1101
|
);
|
|
1096
1102
|
const statusCtx = {
|
|
1097
|
-
|
|
1103
|
+
trialId,
|
|
1098
1104
|
instrumentName: record.instrumentName,
|
|
1099
1105
|
zoom: record.view.zoom
|
|
1100
1106
|
};
|
|
@@ -1113,57 +1119,57 @@ function WorkspaceChrome({
|
|
|
1113
1119
|
return /* @__PURE__ */ jsxs(
|
|
1114
1120
|
"section",
|
|
1115
1121
|
{
|
|
1116
|
-
className: "lk-
|
|
1117
|
-
"aria-label": `
|
|
1122
|
+
className: "lk-trial",
|
|
1123
|
+
"aria-label": `Trial ${record.instrumentName}`,
|
|
1118
1124
|
onKeyDown: handleKeyDown,
|
|
1119
1125
|
children: [
|
|
1120
|
-
/* @__PURE__ */ jsx("div", { className: "lk-
|
|
1121
|
-
/* @__PURE__ */ jsxs("div", { className: "lk-
|
|
1122
|
-
/* @__PURE__ */ jsxs("div", { className: "lk-
|
|
1126
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial__toolbar", children: toolbar ? toolbar(toolbarCtx) : /* @__PURE__ */ jsx(DefaultToolbar, { ctx: toolbarCtx }) }),
|
|
1127
|
+
/* @__PURE__ */ jsxs("div", { className: "lk-trial__body", children: [
|
|
1128
|
+
/* @__PURE__ */ jsxs("div", { className: "lk-trial__sidebar", children: [
|
|
1123
1129
|
sidebar ? sidebar(sidebarCtx) : /* @__PURE__ */ jsx(DefaultSidebar, { ctx: sidebarCtx }),
|
|
1124
1130
|
sidebarExtras
|
|
1125
1131
|
] }),
|
|
1126
|
-
/* @__PURE__ */ jsx("div", { className: "lk-
|
|
1132
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial__content", children })
|
|
1127
1133
|
] }),
|
|
1128
|
-
/* @__PURE__ */ jsx("div", { className: "lk-
|
|
1134
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial__status", children: statusBar ? statusBar(statusCtx) : /* @__PURE__ */ jsx(DefaultStatusBar, { ctx: statusCtx }) })
|
|
1129
1135
|
]
|
|
1130
1136
|
}
|
|
1131
1137
|
);
|
|
1132
1138
|
}
|
|
1133
|
-
function
|
|
1139
|
+
function Trial({ id }) {
|
|
1134
1140
|
const lab = useLabContext();
|
|
1135
1141
|
const storeCtx = useContext(LabStoreContext);
|
|
1136
|
-
if (!storeCtx) throw new Error("[labkit] <
|
|
1137
|
-
const record = useStore(storeCtx.store, (s) => s.
|
|
1142
|
+
if (!storeCtx) throw new Error("[labkit] <Trial> requires <LabStoreProvider>");
|
|
1143
|
+
const record = useStore(storeCtx.store, (s) => s.trials.find((w) => w.id === id));
|
|
1138
1144
|
if (!record) {
|
|
1139
|
-
return /* @__PURE__ */ jsxs("div", { className: "lk-
|
|
1140
|
-
"
|
|
1145
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
|
|
1146
|
+
"Trial not found: ",
|
|
1141
1147
|
id
|
|
1142
1148
|
] });
|
|
1143
1149
|
}
|
|
1144
1150
|
const instrument = lab.instruments.find((i) => i.name === record.instrumentName);
|
|
1145
1151
|
if (!instrument) {
|
|
1146
|
-
return /* @__PURE__ */ jsxs("div", { className: "lk-
|
|
1152
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
|
|
1147
1153
|
"Unknown instrument: ",
|
|
1148
1154
|
record.instrumentName
|
|
1149
1155
|
] });
|
|
1150
1156
|
}
|
|
1151
1157
|
return /* @__PURE__ */ jsx(
|
|
1152
|
-
|
|
1158
|
+
TrialRuntime,
|
|
1153
1159
|
{
|
|
1154
1160
|
record,
|
|
1155
1161
|
instrument,
|
|
1156
1162
|
store: storeCtx.store,
|
|
1157
|
-
isLast: lab.
|
|
1163
|
+
isLast: lab.trials.length <= 1
|
|
1158
1164
|
}
|
|
1159
1165
|
);
|
|
1160
1166
|
}
|
|
1161
|
-
function
|
|
1167
|
+
function TrialRuntime({ record, instrument, store, isLast }) {
|
|
1162
1168
|
const canvasContainerRef = useRef(null);
|
|
1163
|
-
const
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1166
|
-
const
|
|
1169
|
+
const updateTrialState = useStore(store, (s) => s.updateTrialState);
|
|
1170
|
+
const updateTrialConfig = useStore(store, (s) => s.updateTrialConfig);
|
|
1171
|
+
const updateTrialView = useStore(store, (s) => s.updateTrialView);
|
|
1172
|
+
const updateTrialUndoStack = useStore(store, (s) => s.updateTrialUndoStack);
|
|
1167
1173
|
const busRef = useRef(null);
|
|
1168
1174
|
if (busRef.current === null) busRef.current = createEventBus();
|
|
1169
1175
|
const bus = busRef.current;
|
|
@@ -1174,32 +1180,32 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1174
1180
|
const maxDepth = undoCap?.maxDepth ?? 50;
|
|
1175
1181
|
const snapshotIfNeeded = (event) => {
|
|
1176
1182
|
if (!undoCap || !undoEvents.has(event)) return;
|
|
1177
|
-
const current = store.getState().
|
|
1183
|
+
const current = store.getState().trials.find((w) => w.id === record.id);
|
|
1178
1184
|
if (!current) return;
|
|
1179
1185
|
const snap = structuredClone(current.state);
|
|
1180
|
-
|
|
1186
|
+
updateTrialUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
|
|
1181
1187
|
};
|
|
1182
|
-
const setView = (v) =>
|
|
1188
|
+
const setView = (v) => updateTrialView(record.id, v);
|
|
1183
1189
|
const renderCtx = {
|
|
1184
1190
|
state: record.state,
|
|
1185
1191
|
config: record.config,
|
|
1186
1192
|
setState: (next) => {
|
|
1187
1193
|
snapshotIfNeeded("state.change");
|
|
1188
|
-
|
|
1194
|
+
updateTrialState(record.id, next);
|
|
1189
1195
|
bus.emit("state.change");
|
|
1190
1196
|
},
|
|
1191
1197
|
setConfig: (key, value) => {
|
|
1192
1198
|
const evt = `config.change:${String(key)}`;
|
|
1193
1199
|
snapshotIfNeeded("config.change");
|
|
1194
1200
|
snapshotIfNeeded(evt);
|
|
1195
|
-
|
|
1201
|
+
updateTrialConfig(record.id, key, value);
|
|
1196
1202
|
bus.emit("config.change");
|
|
1197
1203
|
bus.emit(evt);
|
|
1198
1204
|
},
|
|
1199
|
-
|
|
1205
|
+
trial: {
|
|
1200
1206
|
id: record.id,
|
|
1201
1207
|
zoom: record.view.zoom,
|
|
1202
|
-
setZoom: (z) =>
|
|
1208
|
+
setZoom: (z) => updateTrialView(record.id, { ...record.view, zoom: z })
|
|
1203
1209
|
},
|
|
1204
1210
|
emit: (event) => {
|
|
1205
1211
|
snapshotIfNeeded(event);
|
|
@@ -1212,14 +1218,14 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1212
1218
|
undo: () => {
|
|
1213
1219
|
const result = undo(record.undoStack, structuredClone(record.state));
|
|
1214
1220
|
if (!result) return;
|
|
1215
|
-
|
|
1216
|
-
|
|
1221
|
+
updateTrialState(record.id, result.snapshot);
|
|
1222
|
+
updateTrialUndoStack(record.id, result.stack);
|
|
1217
1223
|
},
|
|
1218
1224
|
redo: () => {
|
|
1219
1225
|
const result = redo(record.undoStack, structuredClone(record.state));
|
|
1220
1226
|
if (!result) return;
|
|
1221
|
-
|
|
1222
|
-
|
|
1227
|
+
updateTrialState(record.id, result.snapshot);
|
|
1228
|
+
updateTrialUndoStack(record.id, result.stack);
|
|
1223
1229
|
}
|
|
1224
1230
|
} : void 0;
|
|
1225
1231
|
const canvasLayers = useMemo(() => {
|
|
@@ -1229,7 +1235,11 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1229
1235
|
return ordered.map((layer) => ({
|
|
1230
1236
|
id: layer.id,
|
|
1231
1237
|
visible: layerVisibility[layer.id] !== false,
|
|
1232
|
-
render: (ctx, view) =>
|
|
1238
|
+
render: (ctx, view) => {
|
|
1239
|
+
ctx.translate(view.pan.x, view.pan.y);
|
|
1240
|
+
ctx.scale(view.zoom, view.zoom);
|
|
1241
|
+
layer.draw(ctx, { state: record.state, config: record.config, zoom: view.zoom });
|
|
1242
|
+
}
|
|
1233
1243
|
}));
|
|
1234
1244
|
}, [instrument.canvas, record.state, record.config, layerVisibility, layerOrder]);
|
|
1235
1245
|
const layerDescriptors = useMemo(() => {
|
|
@@ -1244,7 +1254,7 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1244
1254
|
config: record.config,
|
|
1245
1255
|
setState: (next) => {
|
|
1246
1256
|
snapshotIfNeeded("canvas.itemAdded");
|
|
1247
|
-
|
|
1257
|
+
updateTrialState(record.id, next);
|
|
1248
1258
|
},
|
|
1249
1259
|
emit: (evt) => {
|
|
1250
1260
|
snapshotIfNeeded(evt);
|
|
@@ -1286,10 +1296,10 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1286
1296
|
"div",
|
|
1287
1297
|
{
|
|
1288
1298
|
ref: canvasContainerRef,
|
|
1289
|
-
className: "lk-
|
|
1299
|
+
className: "lk-trial__canvas-host",
|
|
1290
1300
|
style: { width: "100%", height: "100%", position: "relative" },
|
|
1291
1301
|
children: [
|
|
1292
|
-
/* @__PURE__ */ jsx(CanvasStack, { layers: layersWithFeedback, view: record.view, onViewChange: setView }),
|
|
1302
|
+
/* @__PURE__ */ jsx(CanvasStack, { layers: layersWithFeedback, view: record.view, onViewChange: setView, children: instrument.render(renderCtx) }),
|
|
1293
1303
|
/* @__PURE__ */ jsx(DragOverlay, { drag: dragDropResult.drag })
|
|
1294
1304
|
]
|
|
1295
1305
|
}
|
|
@@ -1314,12 +1324,12 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1314
1324
|
}
|
|
1315
1325
|
) : null;
|
|
1316
1326
|
return /* @__PURE__ */ jsx(
|
|
1317
|
-
|
|
1327
|
+
TrialChrome,
|
|
1318
1328
|
{
|
|
1319
|
-
|
|
1329
|
+
trialId: record.id,
|
|
1320
1330
|
record,
|
|
1321
1331
|
instrument,
|
|
1322
|
-
|
|
1332
|
+
isLastTrial: isLast,
|
|
1323
1333
|
undoBindings,
|
|
1324
1334
|
sidebarExtras: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1325
1335
|
paletteNode,
|
|
@@ -1330,7 +1340,7 @@ function WorkspaceRuntime({ record, instrument, store, isLast }) {
|
|
|
1330
1340
|
);
|
|
1331
1341
|
}
|
|
1332
1342
|
|
|
1333
|
-
// src/
|
|
1343
|
+
// src/trial/trialOps.ts
|
|
1334
1344
|
var DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } };
|
|
1335
1345
|
function findInstrument(instruments, name) {
|
|
1336
1346
|
const found = instruments.find((i) => i.name === name);
|
|
@@ -1342,7 +1352,7 @@ function findInstrument(instruments, name) {
|
|
|
1342
1352
|
function initialView(instrument) {
|
|
1343
1353
|
return instrument.canvas?.initialView ? structuredClone(instrument.canvas.initialView) : { ...DEFAULT_VIEW, pan: { ...DEFAULT_VIEW.pan } };
|
|
1344
1354
|
}
|
|
1345
|
-
function
|
|
1355
|
+
function addTrial(trials, instruments, instrumentName) {
|
|
1346
1356
|
const instrument = findInstrument(instruments, instrumentName);
|
|
1347
1357
|
const config = instrument.defaultConfig();
|
|
1348
1358
|
const state = instrument.initialState(config);
|
|
@@ -1354,12 +1364,12 @@ function addWorkspace(workspaces, instruments, instrumentName) {
|
|
|
1354
1364
|
view: initialView(instrument),
|
|
1355
1365
|
undoStack: { past: [], future: [] }
|
|
1356
1366
|
};
|
|
1357
|
-
return [...
|
|
1367
|
+
return [...trials, record];
|
|
1358
1368
|
}
|
|
1359
|
-
function
|
|
1360
|
-
const sourceIdx =
|
|
1361
|
-
const source =
|
|
1362
|
-
if (!source) return
|
|
1369
|
+
function cloneTrial(trials, id) {
|
|
1370
|
+
const sourceIdx = trials.findIndex((w) => w.id === id);
|
|
1371
|
+
const source = trials[sourceIdx];
|
|
1372
|
+
if (!source) return trials;
|
|
1363
1373
|
const clone = {
|
|
1364
1374
|
...source,
|
|
1365
1375
|
id: crypto.randomUUID(),
|
|
@@ -1368,17 +1378,17 @@ function cloneWorkspace(workspaces, id) {
|
|
|
1368
1378
|
view: structuredClone(source.view),
|
|
1369
1379
|
undoStack: { past: [], future: [] }
|
|
1370
1380
|
};
|
|
1371
|
-
return [...
|
|
1381
|
+
return [...trials.slice(0, sourceIdx + 1), clone, ...trials.slice(sourceIdx + 1)];
|
|
1372
1382
|
}
|
|
1373
|
-
function
|
|
1374
|
-
if (
|
|
1375
|
-
const next =
|
|
1376
|
-
return next.length ===
|
|
1383
|
+
function closeTrial(trials, id) {
|
|
1384
|
+
if (trials.length <= 1) return trials;
|
|
1385
|
+
const next = trials.filter((w) => w.id !== id);
|
|
1386
|
+
return next.length === trials.length ? trials : next;
|
|
1377
1387
|
}
|
|
1378
|
-
function
|
|
1379
|
-
const idx =
|
|
1380
|
-
const current =
|
|
1381
|
-
if (!current) return
|
|
1388
|
+
function resetTrial(trials, id, instruments) {
|
|
1389
|
+
const idx = trials.findIndex((w) => w.id === id);
|
|
1390
|
+
const current = trials[idx];
|
|
1391
|
+
if (!current) return trials;
|
|
1382
1392
|
const instrument = findInstrument(instruments, current.instrumentName);
|
|
1383
1393
|
const config = instrument.defaultConfig();
|
|
1384
1394
|
const state = instrument.initialState(config);
|
|
@@ -1388,7 +1398,14 @@ function resetWorkspace(workspaces, id, instruments) {
|
|
|
1388
1398
|
state,
|
|
1389
1399
|
view: initialView(instrument)
|
|
1390
1400
|
};
|
|
1391
|
-
return [...
|
|
1401
|
+
return [...trials.slice(0, idx), reset, ...trials.slice(idx + 1)];
|
|
1402
|
+
}
|
|
1403
|
+
function reorderTrials(trials, ids) {
|
|
1404
|
+
const byId = new Map(trials.map((w) => [w.id, w]));
|
|
1405
|
+
const named = ids.map((id) => byId.get(id)).filter((w) => w !== void 0);
|
|
1406
|
+
const seen = new Set(named.map((w) => w.id));
|
|
1407
|
+
const rest = trials.filter((w) => !seen.has(w.id));
|
|
1408
|
+
return [...named, ...rest];
|
|
1392
1409
|
}
|
|
1393
1410
|
function query() {
|
|
1394
1411
|
if (typeof window === "undefined" || !window.matchMedia) return null;
|
|
@@ -1421,23 +1438,116 @@ function LabShell({ title, children, header, footer, mode = "auto" }) {
|
|
|
1421
1438
|
] });
|
|
1422
1439
|
return outer ? shell : /* @__PURE__ */ jsx(ThemeProvider, { theme: interstellarTheme, mode: resolved, children: shell });
|
|
1423
1440
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
};
|
|
1431
|
-
|
|
1441
|
+
var ZONE_ID = asNodeId("lk-workspace");
|
|
1442
|
+
var STRATEGIES = { grid: gridStrategy };
|
|
1443
|
+
var KIND = "trial";
|
|
1444
|
+
function extentOf(store, id) {
|
|
1445
|
+
const p2 = store.getNode(id)?.membership?.placement;
|
|
1446
|
+
if (!p2) return null;
|
|
1447
|
+
const out = {};
|
|
1448
|
+
if (p2.size) out.size = p2.size;
|
|
1449
|
+
if (p2.span) out.span = p2.span;
|
|
1450
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
1451
|
+
}
|
|
1452
|
+
function Workspace({
|
|
1453
|
+
children,
|
|
1454
|
+
ids,
|
|
1455
|
+
resizable = false,
|
|
1456
|
+
reorderable = false,
|
|
1457
|
+
onReorder,
|
|
1458
|
+
layout,
|
|
1459
|
+
onLayoutChange,
|
|
1460
|
+
gap = 12,
|
|
1461
|
+
padding = 0,
|
|
1462
|
+
viewport
|
|
1463
|
+
}) {
|
|
1464
|
+
const items = Children.toArray(children);
|
|
1465
|
+
const idKey = ids ? ids.join(",") : `#${items.length}`;
|
|
1466
|
+
const nodeIds = useMemo(() => items.map((_, i) => asNodeId(ids?.[i] ?? `lk-ws-${i}`)), [idKey]);
|
|
1467
|
+
const layoutRef = useRef(layout);
|
|
1468
|
+
layoutRef.current = layout;
|
|
1469
|
+
const onLayoutChangeRef = useRef(onLayoutChange);
|
|
1470
|
+
onLayoutChangeRef.current = onLayoutChange;
|
|
1471
|
+
const storeRef = useRef(null);
|
|
1472
|
+
if (storeRef.current === null) {
|
|
1473
|
+
const store2 = new Store();
|
|
1474
|
+
store2.registerNode(
|
|
1475
|
+
createNode({
|
|
1476
|
+
kind: "zone",
|
|
1477
|
+
id: ZONE_ID,
|
|
1478
|
+
container: { strategyId: "grid", config: { resizable, gap, padding } }
|
|
1479
|
+
})
|
|
1480
|
+
);
|
|
1481
|
+
storeRef.current = store2;
|
|
1482
|
+
}
|
|
1483
|
+
const store = storeRef.current;
|
|
1484
|
+
useLayoutEffect(() => {
|
|
1485
|
+
store.updateContainerConfig(ZONE_ID, { resizable, gap, padding });
|
|
1486
|
+
}, [store, resizable, gap, padding]);
|
|
1487
|
+
useLayoutEffect(() => {
|
|
1488
|
+
const present = new Set(store.getContainerView(ZONE_ID)?.childOrder ?? []);
|
|
1489
|
+
const wanted = new Set(nodeIds);
|
|
1490
|
+
for (const id of present) {
|
|
1491
|
+
if (!wanted.has(id)) store.unregisterNode(id);
|
|
1492
|
+
}
|
|
1493
|
+
for (const id of nodeIds) {
|
|
1494
|
+
if (present.has(id)) continue;
|
|
1495
|
+
store.registerNode(createNode({ kind: KIND, id, parentId: ZONE_ID, focus: true }));
|
|
1496
|
+
store.showNode(id);
|
|
1497
|
+
const saved = layoutRef.current?.[id];
|
|
1498
|
+
if (saved) store.patchPlacement(id, saved);
|
|
1499
|
+
}
|
|
1500
|
+
store.setChildOrder(ZONE_ID, [...nodeIds]);
|
|
1501
|
+
}, [store, nodeIds]);
|
|
1502
|
+
useEffect(() => {
|
|
1503
|
+
if (!onLayoutChange) return;
|
|
1504
|
+
return store.events.on("node.placementChanged", () => {
|
|
1505
|
+
const next = {};
|
|
1506
|
+
for (const child of store.getChildren(ZONE_ID)) {
|
|
1507
|
+
const extent = extentOf(store, child.id);
|
|
1508
|
+
if (extent) next[child.id] = extent;
|
|
1509
|
+
}
|
|
1510
|
+
onLayoutChangeRef.current?.(next);
|
|
1511
|
+
});
|
|
1512
|
+
}, [store, onLayoutChange]);
|
|
1513
|
+
const commitOrder = useCallback(
|
|
1514
|
+
(nextIds) => onReorder?.(nextIds.map(String)),
|
|
1515
|
+
[onReorder]
|
|
1516
|
+
);
|
|
1517
|
+
const chrome = useMemo(() => {
|
|
1518
|
+
const byId = new Map(nodeIds.map((id, i) => [id, items[i]]));
|
|
1519
|
+
return {
|
|
1520
|
+
[KIND]: ({ node }) => {
|
|
1521
|
+
const content = byId.get(node.id) ?? null;
|
|
1522
|
+
if (!reorderable) return content;
|
|
1523
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-trial-tile", children: [
|
|
1524
|
+
/* @__PURE__ */ jsx(DragHandle, { nodeId: node.id, className: "lk-trial-tile__grip", children: /* @__PURE__ */ jsx("span", { className: "lk-trial-tile__grip-dots", "aria-hidden": "true" }) }),
|
|
1525
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial-tile__body", children: content })
|
|
1526
|
+
] });
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
}, [nodeIds, items, reorderable]);
|
|
1530
|
+
const grid = /* @__PURE__ */ jsx(
|
|
1531
|
+
Container,
|
|
1532
|
+
{
|
|
1533
|
+
parentId: ZONE_ID,
|
|
1534
|
+
chrome,
|
|
1535
|
+
className: "lk-workspace windease-zone",
|
|
1536
|
+
affordances: resizable,
|
|
1537
|
+
viewport,
|
|
1538
|
+
onChildOrderChange: reorderable ? commitOrder : void 0
|
|
1539
|
+
}
|
|
1540
|
+
);
|
|
1541
|
+
return /* @__PURE__ */ jsx(Provider, { store, children: /* @__PURE__ */ jsx(StrategyRegistryProvider, { strategies: STRATEGIES, children: reorderable ? /* @__PURE__ */ jsx(DragProvider, { children: grid }) : grid }) });
|
|
1432
1542
|
}
|
|
1433
1543
|
function buildStore(instruments, defaultInstrument, storage, storageKey, initialMode) {
|
|
1434
1544
|
const store = createLabStore({ storageKey, storage, initialMode });
|
|
1435
|
-
if (store.getState().
|
|
1436
|
-
const seeded =
|
|
1545
|
+
if (store.getState().trials.length === 0) {
|
|
1546
|
+
const seeded = addTrial([], instruments, defaultInstrument);
|
|
1437
1547
|
const record = seeded[0];
|
|
1438
1548
|
if (record) {
|
|
1439
1549
|
const { undoStack: _undoStack, ...rest } = record;
|
|
1440
|
-
store.getState().
|
|
1550
|
+
store.getState().addTrial(rest);
|
|
1441
1551
|
}
|
|
1442
1552
|
}
|
|
1443
1553
|
return store;
|
|
@@ -1481,9 +1591,10 @@ function Lab({
|
|
|
1481
1591
|
);
|
|
1482
1592
|
}
|
|
1483
1593
|
const store = storeRef.current;
|
|
1484
|
-
const
|
|
1594
|
+
const trials = useStore(store, (s) => s.trials);
|
|
1485
1595
|
const savedSnapshots = useStore(store, (s) => s.savedSnapshots);
|
|
1486
1596
|
const modeValue = useStore(store, (s) => s.mode);
|
|
1597
|
+
const layout = useStore(store, (s) => s.layout);
|
|
1487
1598
|
const resolvedMode = useResolvedMode(modeValue);
|
|
1488
1599
|
useEffect(() => {
|
|
1489
1600
|
if (mode && mode !== store.getState().mode) {
|
|
@@ -1491,44 +1602,47 @@ function Lab({
|
|
|
1491
1602
|
}
|
|
1492
1603
|
}, [mode, store]);
|
|
1493
1604
|
const contextValue = useMemo(() => {
|
|
1494
|
-
const
|
|
1495
|
-
const currentById = new Map(store.getState().
|
|
1605
|
+
const replaceTrials = (next) => {
|
|
1606
|
+
const currentById = new Map(store.getState().trials.map((w) => [w.id, w]));
|
|
1496
1607
|
const merged = next.map((w) => currentById.get(w.id) ?? w);
|
|
1497
|
-
store.setState({
|
|
1608
|
+
store.setState({ trials: merged });
|
|
1498
1609
|
store.getState().setMode(store.getState().mode);
|
|
1499
1610
|
};
|
|
1500
1611
|
return {
|
|
1501
1612
|
instruments,
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
const next =
|
|
1505
|
-
|
|
1613
|
+
trials,
|
|
1614
|
+
addTrial: (instrumentName) => {
|
|
1615
|
+
const next = addTrial(store.getState().trials, instruments, instrumentName);
|
|
1616
|
+
replaceTrials(next);
|
|
1506
1617
|
},
|
|
1507
|
-
|
|
1508
|
-
const next =
|
|
1509
|
-
|
|
1618
|
+
cloneTrial: (id) => {
|
|
1619
|
+
const next = cloneTrial(store.getState().trials, id);
|
|
1620
|
+
replaceTrials(next);
|
|
1510
1621
|
},
|
|
1511
|
-
|
|
1512
|
-
const next =
|
|
1513
|
-
|
|
1622
|
+
closeTrial: (id) => {
|
|
1623
|
+
const next = closeTrial(store.getState().trials, id);
|
|
1624
|
+
replaceTrials(next);
|
|
1514
1625
|
},
|
|
1515
|
-
|
|
1516
|
-
|
|
1626
|
+
reorderTrials: (ids) => {
|
|
1627
|
+
replaceTrials(reorderTrials(store.getState().trials, ids));
|
|
1628
|
+
},
|
|
1629
|
+
resetTrial: (id) => {
|
|
1630
|
+
const next = resetTrial(store.getState().trials, id, instruments);
|
|
1517
1631
|
const record = next.find((w) => w.id === id);
|
|
1518
1632
|
if (!record) return;
|
|
1519
1633
|
store.setState((s) => ({
|
|
1520
|
-
|
|
1634
|
+
trials: s.trials.map(
|
|
1521
1635
|
(w) => w.id === id ? { ...w, config: record.config, state: record.state, view: record.view } : w
|
|
1522
1636
|
)
|
|
1523
1637
|
}));
|
|
1524
|
-
store.getState().
|
|
1638
|
+
store.getState().updateTrialView(id, record.view);
|
|
1525
1639
|
},
|
|
1526
1640
|
savedSnapshots,
|
|
1527
|
-
saveSnapshot: (
|
|
1528
|
-
store.getState().saveSnapshot(
|
|
1641
|
+
saveSnapshot: (trialId, name) => {
|
|
1642
|
+
store.getState().saveSnapshot(trialId, name ?? `Save ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
1529
1643
|
},
|
|
1530
|
-
loadSnapshot: (
|
|
1531
|
-
store.getState().loadSnapshot(snapshotId,
|
|
1644
|
+
loadSnapshot: (trialId, snapshotId) => {
|
|
1645
|
+
store.getState().loadSnapshot(snapshotId, trialId);
|
|
1532
1646
|
},
|
|
1533
1647
|
deleteSnapshot: (snapshotId) => {
|
|
1534
1648
|
store.getState().deleteSnapshot(snapshotId);
|
|
@@ -1538,7 +1652,7 @@ function Lab({
|
|
|
1538
1652
|
store.getState().setMode(m);
|
|
1539
1653
|
}
|
|
1540
1654
|
};
|
|
1541
|
-
}, [instruments,
|
|
1655
|
+
}, [instruments, trials, savedSnapshots, modeValue, store]);
|
|
1542
1656
|
const backdropStyle = resolvedMode === "dark" && nebula && nebula.length > 0 ? { ["--wzl-backdrop"]: buildNebula(nebula) } : void 0;
|
|
1543
1657
|
return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
1544
1658
|
ThemeProvider,
|
|
@@ -1547,7 +1661,18 @@ function Lab({
|
|
|
1547
1661
|
mode: resolvedMode,
|
|
1548
1662
|
className: "lk-lab",
|
|
1549
1663
|
style: backdropStyle,
|
|
1550
|
-
children: /* @__PURE__ */ jsx(LabShell, { title: title ?? "Labkit", mode: modeValue, header: children, children: /* @__PURE__ */ jsx(
|
|
1664
|
+
children: /* @__PURE__ */ jsx(LabShell, { title: title ?? "Labkit", mode: modeValue, header: children, children: /* @__PURE__ */ jsx(
|
|
1665
|
+
Workspace,
|
|
1666
|
+
{
|
|
1667
|
+
ids: trials.map((w) => w.id),
|
|
1668
|
+
resizable: true,
|
|
1669
|
+
reorderable: true,
|
|
1670
|
+
onReorder: (ids) => contextValue.reorderTrials(ids),
|
|
1671
|
+
layout,
|
|
1672
|
+
onLayoutChange: (next) => store.getState().setLayout(next),
|
|
1673
|
+
children: trials.map((w) => /* @__PURE__ */ jsx(Trial, { id: w.id }, w.id))
|
|
1674
|
+
}
|
|
1675
|
+
) })
|
|
1551
1676
|
}
|
|
1552
1677
|
) }) });
|
|
1553
1678
|
}
|
|
@@ -1915,17 +2040,6 @@ function PropertyGroup({
|
|
|
1915
2040
|
/* @__PURE__ */ jsx("div", { className: "lk-property-group__body", children })
|
|
1916
2041
|
] });
|
|
1917
2042
|
}
|
|
1918
|
-
|
|
1919
|
-
// src/ui/format.ts
|
|
1920
|
-
var MINUS = "\u2212";
|
|
1921
|
-
function formatNumber(n, fractionDigits) {
|
|
1922
|
-
if (!Number.isFinite(n)) return String(n);
|
|
1923
|
-
const s = fractionDigits == null ? String(n) : n.toFixed(fractionDigits);
|
|
1924
|
-
return s.startsWith("-") ? MINUS + s.slice(1) : s;
|
|
1925
|
-
}
|
|
1926
|
-
function parseSignedNumber(s) {
|
|
1927
|
-
return Number(s.replace(MINUS, "-"));
|
|
1928
|
-
}
|
|
1929
2043
|
function PropertyPanel({ title, children, className }) {
|
|
1930
2044
|
const cls = className ? `lk-property-panel ${className}` : "lk-property-panel";
|
|
1931
2045
|
return /* @__PURE__ */ jsxs("div", { className: cls, children: [
|
|
@@ -2169,6 +2283,6 @@ function ToggleRow({
|
|
|
2169
2283
|
}) }) });
|
|
2170
2284
|
}
|
|
2171
2285
|
|
|
2172
|
-
export { CheckboxRow, ColorRow, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Lab, LabContext, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, Subpanel, TextRow, ToggleRow,
|
|
2286
|
+
export { CheckboxRow, ColorRow, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Lab, LabContext, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, Subpanel, TextRow, ToggleRow, Trial, TrialChrome, Workspace, addTrial, cloneTrial, closeTrial, defineInstrument, detectCapabilities, interstellarTheme, reorderTrials, resetTrial, useLabContext, validateConfigSchema };
|
|
2173
2287
|
//# sourceMappingURL=index.js.map
|
|
2174
2288
|
//# sourceMappingURL=index.js.map
|