@weasel-js/labkit 1.4.4 → 1.5.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 +13 -2
- package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CqkxkFJW.d.ts} +1 -1
- package/dist/_dts/ToggleBar.d-DbHoWKIA.d.ts +116 -0
- package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-8R6UmlvN.d.ts} +253 -31
- package/dist/_dts/index-B8uu2ba7.d.ts +237 -0
- package/dist/_dts/{index-Blv9uQzu.d.ts → index-retnAut7.d.ts} +18 -10
- package/dist/_dts/{types-B9_zrHmb.d.ts → types-Ca2LCnPq.d.ts} +65 -2
- package/dist/_dts/usePanZoom-nS798rOF.d.ts +160 -0
- package/dist/_dts/useTrialState-jcx_TdU6.d.ts +212 -0
- package/dist/canvas/index.d.ts +17 -128
- package/dist/canvas/index.js +4 -2
- package/dist/chrome/index.d.ts +4 -92
- package/dist/chrome/index.js +9 -5
- package/dist/chunk-67SJLMC7.js +704 -0
- package/dist/chunk-67SJLMC7.js.map +1 -0
- package/dist/chunk-6ZDGOZQV.js +310 -0
- package/dist/chunk-6ZDGOZQV.js.map +1 -0
- package/dist/{chunk-MOM3GOVY.js → chunk-EKIICY6X.js} +111 -25
- package/dist/chunk-EKIICY6X.js.map +1 -0
- package/dist/{chunk-CTRKTLYZ.js → chunk-H6ZAOWNE.js} +11 -11
- package/dist/chunk-H6ZAOWNE.js.map +1 -0
- package/dist/{chunk-SMHP6XZ4.js → chunk-KJALCDWE.js} +9 -7
- package/dist/chunk-KJALCDWE.js.map +1 -0
- package/dist/{chunk-BKFVHKJH.js → chunk-MUKOW3TC.js} +3 -3
- package/dist/{chunk-BKFVHKJH.js.map → chunk-MUKOW3TC.js.map} +1 -1
- package/dist/chunk-NDRYLVOW.js +15 -0
- package/dist/chunk-NDRYLVOW.js.map +1 -0
- package/dist/{chunk-64ZCN3DA.js → chunk-NS64DXMN.js} +226 -159
- package/dist/chunk-NS64DXMN.js.map +1 -0
- package/dist/{chunk-I6JCVE24.js → chunk-PGETSEDK.js} +135 -13
- package/dist/chunk-PGETSEDK.js.map +1 -0
- package/dist/{chunk-FQMJUVHJ.js → chunk-QLSV2N3G.js} +32 -37
- package/dist/chunk-QLSV2N3G.js.map +1 -0
- package/dist/chunk-RWNDDKOH.js +682 -0
- package/dist/chunk-RWNDDKOH.js.map +1 -0
- package/dist/chunk-S2SHZRD7.js +7681 -0
- package/dist/chunk-S2SHZRD7.js.map +1 -0
- package/dist/{chunk-U3IYHIAE.js → chunk-TQBBYTAF.js} +19 -82
- package/dist/chunk-TQBBYTAF.js.map +1 -0
- package/dist/chunk-UU3NJO6N.js +72 -0
- package/dist/chunk-UU3NJO6N.js.map +1 -0
- package/dist/chunk-WIG6XHJ7.js +35 -0
- package/dist/chunk-WIG6XHJ7.js.map +1 -0
- package/dist/{chunk-TJ7QY3OC.js → chunk-ZPH5WTWF.js} +21 -15
- package/dist/chunk-ZPH5WTWF.js.map +1 -0
- package/dist/config/index.d.ts +232 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/controls/index.d.ts +2 -3
- package/dist/controls/index.js +3 -2
- package/dist/dragdrop/index.d.ts +2 -4
- package/dist/index.d.ts +110 -381
- package/dist/index.js +691 -817
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +3 -5
- package/dist/layers/index.js +2 -2
- package/dist/loupe/index.d.ts +6 -8
- package/dist/loupe/index.js +2 -2
- package/dist/passthrough/weasel-ui.d.ts +164 -122
- package/dist/passthrough/weasel-ui.js +1 -1
- package/dist/primitives/index.d.ts +53 -6
- package/dist/primitives/index.js +6 -4
- package/dist/state/index.d.ts +12 -7
- package/dist/state/index.js +16 -13
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +84 -50
- package/dist/surface/index.d.ts +44 -7
- package/dist/surface/index.js +2 -1
- package/dist/ui/layers/index.js +1 -1
- package/dist/undo/index.d.ts +2 -4
- package/package.json +15 -8
- package/src/annotations/AnnotationOverlay.tsx +53 -5
- package/src/annotations/Annotations.overlay.test.tsx +85 -6
- package/src/annotations/drawOne.test.ts +1 -1
- package/src/annotations/drawOne.ts +2 -2
- package/src/annotations/paint.test.ts +2 -2
- package/src/annotations/paint.ts +1 -1
- package/src/annotations/preload.ts +6 -0
- package/src/annotations/store.test.ts +18 -0
- package/src/annotations/store.ts +5 -4
- package/src/annotations/svgNodes.test.ts +2 -2
- package/src/annotations/toolMap.ts +3 -3
- package/src/annotations/types.ts +6 -3
- package/src/canvas/CameraWheelContext.ts +12 -0
- package/src/canvas/CanvasStack.test.tsx +25 -0
- package/src/canvas/CanvasStack.tsx +31 -4
- package/src/canvas/Stage.less +18 -0
- package/src/canvas/Stage.test.tsx +186 -0
- package/src/canvas/Stage.tsx +134 -0
- package/src/canvas/camera.test.ts +17 -2
- package/src/canvas/camera.ts +9 -9
- package/src/canvas/index.ts +4 -0
- package/src/canvas/usePanZoom.ts +10 -5
- package/src/chrome/ChromeRegions.stories.tsx +0 -3
- package/src/chrome/LabChrome.tsx +114 -0
- package/src/chrome/builtins.tsx +20 -31
- package/src/chrome/index.ts +15 -0
- package/src/chrome/labRegions.test.tsx +208 -0
- package/src/chrome/labTypes.ts +50 -0
- package/src/chrome/merge.ts +7 -9
- package/src/chrome/regions/PaletteRegion.test.tsx +6 -4
- package/src/chrome/regions/PaletteRegion.tsx +21 -9
- package/src/chrome/regions/SidebarRegion.tsx +25 -13
- package/src/chrome/regions/StatusRegion.tsx +15 -8
- package/src/chrome/regions/ToolbarRegion.tsx +30 -16
- package/src/chrome/types.ts +47 -20
- package/src/config/builder.test.ts +4 -0
- package/src/config/builder.ts +16 -10
- package/src/config/declarationEmit.fixture.ts +11 -0
- package/src/config/declarationEmit.test.ts +51 -0
- package/src/config/entry.test.ts +46 -0
- package/src/config/index.ts +13 -1
- package/src/config/nodeClasses.test.ts +18 -0
- package/src/config/types.ts +3 -1
- package/src/controls/ControlPanel.test.tsx +16 -0
- package/src/controls/ControlPanel.tsx +5 -2
- package/src/fake-indexeddb-auto.d.ts +5 -0
- package/src/index.test.ts +7 -0
- package/src/index.ts +38 -5
- package/src/instrument/serializers.test.ts +22 -0
- package/src/instrument/serializers.ts +11 -0
- package/src/instrument/types.ts +25 -1
- package/src/lab/Lab.less +4 -13
- package/src/lab/Lab.persist.test.tsx +147 -0
- package/src/lab/Lab.stories.tsx +0 -2
- package/src/lab/Lab.surface.test.tsx +66 -3
- package/src/lab/Lab.test.tsx +91 -17
- package/src/lab/Lab.tsx +337 -124
- package/src/lab/LabFit.stories.less +18 -0
- package/src/lab/LabFit.stories.tsx +219 -0
- package/src/lab/LabFullChrome.stories.tsx +0 -3
- package/src/lab/LabHeader.test.tsx +40 -9
- package/src/lab/LabHeader.tsx +7 -13
- package/src/lab/LabPalette.tsx +13 -26
- package/src/lab/LabShell.less +49 -12
- package/src/lab/LabSwitcher.less +8 -1
- package/src/lab/Workspace.surface.test.tsx +2 -1
- package/src/lab/fitCheck.test.ts +57 -0
- package/src/lab/fitCheck.ts +125 -0
- package/src/loupe/AGENTS.md +1 -1
- package/src/loupe/Loupe.less +1 -1
- package/src/loupe/canvasLens.ts +1 -1
- package/src/loupe/types.ts +1 -1
- package/src/loupe/useLoupe.test.tsx +1 -1
- package/src/loupe/useLoupe.ts +1 -1
- package/src/passthrough/weasel-ui.ts +9 -0
- package/src/primitives/FloatingPanel.stories.tsx +13 -1
- package/src/primitives/FloatingPanel.test.tsx +47 -16
- package/src/primitives/FloatingPanel.tsx +11 -25
- package/src/primitives/Split.less +14 -0
- package/src/primitives/Split.test.tsx +108 -0
- package/src/primitives/Split.tsx +159 -0
- package/src/primitives/ZoomControl.tsx +5 -2
- package/src/primitives/index.ts +2 -0
- package/src/state/Persistence.stories.tsx +75 -0
- package/src/state/Persistence.tsx +33 -0
- package/src/state/SingletonExperiment.test.tsx +47 -53
- package/src/state/SingletonExperiment.tsx +24 -13
- package/src/state/adapterContract.ts +116 -0
- package/src/state/adapters.test.ts +113 -61
- package/src/state/adapters.ts +328 -69
- package/src/state/document.test.ts +71 -96
- package/src/state/document.ts +66 -52
- package/src/state/helpers.test.ts +1 -1
- package/src/state/index.ts +8 -0
- package/src/state/labRecords.test.ts +105 -0
- package/src/state/labRecords.ts +160 -0
- package/src/state/openLabStore.test.ts +658 -0
- package/src/state/openLabStore.ts +403 -0
- package/src/state/records.test.ts +234 -0
- package/src/state/records.ts +219 -0
- package/src/state/store.test.ts +104 -635
- package/src/state/store.ts +116 -157
- package/src/state/toolSlot.test.ts +1 -1
- package/src/state/types.ts +39 -15
- package/src/state/useOpenOnce.ts +68 -0
- package/src/state/usePersistedState.test.tsx +125 -0
- package/src/state/usePersistedState.ts +66 -0
- package/src/state/useTrialState.test.tsx +2 -3
- package/src/state/view.test.ts +49 -15
- package/src/state/view.ts +18 -1
- package/src/styles.less +2 -0
- package/src/surface/AGENTS.md +39 -7
- package/src/surface/SurfaceContext.ts +26 -3
- package/src/surface/index.ts +2 -0
- package/src/surface/useSurfaceTile.test.tsx +2 -1
- package/src/surface/useSurfaceTile.ts +6 -5
- package/src/surface/useTiledSurface.test.tsx +175 -0
- package/src/surface/useTiledSurface.ts +54 -5
- package/src/theme/Interstellar.stories.tsx +7 -7
- package/src/theme/interstellar.test.ts +1 -1
- package/src/theme/interstellar.tokens.json +2 -2
- package/src/tools/labTool.ts +11 -0
- package/src/trial/Trial.annotations.persist.test.tsx +100 -58
- package/src/trial/Trial.annotations.test.tsx +47 -4
- package/src/trial/Trial.job.test.tsx +3 -3
- package/src/trial/Trial.less +1 -26
- package/src/trial/Trial.stories.tsx +1 -2
- package/src/trial/Trial.test.tsx +28 -4
- package/src/trial/Trial.trialId.test.tsx +23 -1
- package/src/trial/Trial.tsx +118 -38
- package/src/trial/TrialBody.tsx +17 -141
- package/src/trial/TrialChrome.tsx +7 -4
- package/src/trial/trialOps.ts +5 -3
- package/dist/_dts/types-BP2OCcpg.d.ts +0 -155
- package/dist/_dts/types-x92Kfeme.d.ts +0 -62
- package/dist/_dts/useTrialState-D6Vb3T-g.d.ts +0 -99
- package/dist/chunk-2P6PP5N4.js +0 -553
- package/dist/chunk-2P6PP5N4.js.map +0 -1
- package/dist/chunk-64ZCN3DA.js.map +0 -1
- package/dist/chunk-CTRKTLYZ.js.map +0 -1
- package/dist/chunk-FQMJUVHJ.js.map +0 -1
- package/dist/chunk-I6JCVE24.js.map +0 -1
- package/dist/chunk-MOM3GOVY.js.map +0 -1
- package/dist/chunk-SMHP6XZ4.js.map +0 -1
- package/dist/chunk-TJ7QY3OC.js.map +0 -1
- package/dist/chunk-U3IYHIAE.js.map +0 -1
- package/dist/chunk-W3ECWC2K.js +0 -7335
- package/dist/chunk-W3ECWC2K.js.map +0 -1
package/src/trial/TrialBody.tsx
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { type ChromeMap, Container, Provider, StrategyRegistryProvider } from 'windease/react';
|
|
4
|
-
|
|
5
|
-
const ZONE_ID = asNodeId('lk-trial-body');
|
|
6
|
-
const SIDEBAR_ID = asNodeId('lk-trial-sidebar');
|
|
7
|
-
const CONTENT_ID = asNodeId('lk-trial-content');
|
|
8
|
-
const STRATEGIES = { strip: stripStrategy as never };
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Split, type SplitProps } from '../primitives/Split';
|
|
9
3
|
|
|
10
4
|
/** Props for `<TrialBody>`. */
|
|
11
|
-
export interface TrialBodyProps
|
|
5
|
+
export interface TrialBodyProps
|
|
6
|
+
extends Omit<SplitProps, 'sidebar' | 'children' | 'className' | 'sidebarClassName' | 'label'> {
|
|
12
7
|
/** The sidebar region's sections. */
|
|
13
8
|
sidebar: ReactNode;
|
|
14
9
|
/** The instrument's own output. */
|
|
@@ -16,140 +11,21 @@ export interface TrialBodyProps {
|
|
|
16
11
|
/** Extra class on the content pane — `Trial` marks a canvas instrument's
|
|
17
12
|
* well flush. */
|
|
18
13
|
contentClassName?: string;
|
|
19
|
-
/** The sidebar's current extent in pixels. Omit for an unpersisted lab: the
|
|
20
|
-
* seam still works, its result just does not outlive the mount. */
|
|
21
|
-
width?: number;
|
|
22
|
-
/** Where the sidebar starts before anyone drags it. */
|
|
23
|
-
defaultWidth?: number;
|
|
24
|
-
minWidth?: number;
|
|
25
|
-
maxWidth?: number;
|
|
26
|
-
/** How little the instrument may be squeezed to. Reported as the seam's
|
|
27
|
-
* reachable maximum, so the drag stops there rather than clamping silently. */
|
|
28
|
-
contentMinWidth?: number;
|
|
29
|
-
/** Fires through the drag, not on release: the pane is already there. */
|
|
30
|
-
onWidthChange?: (width: number) => void;
|
|
31
|
-
/**
|
|
32
|
-
* Fixed extent. Omit in an app — the strip measures its own box. Supply it
|
|
33
|
-
* where nothing measures, notably jsdom, or both panes lay out at zero.
|
|
34
|
-
*/
|
|
35
|
-
viewport?: { w: number; h: number };
|
|
36
14
|
}
|
|
37
15
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
* The seam is `stripStrategy`'s own resize affordance, so it arrives as a
|
|
42
|
-
* `role="separator"` carrying the reachable range, operable by pointer and by
|
|
43
|
-
* arrows / Home / End, and clamped against the content pane's floor rather
|
|
44
|
-
* than against the sidebar's alone.
|
|
45
|
-
*/
|
|
46
|
-
export function TrialBody({
|
|
47
|
-
sidebar,
|
|
48
|
-
children,
|
|
49
|
-
contentClassName,
|
|
50
|
-
width,
|
|
51
|
-
defaultWidth = 320,
|
|
52
|
-
minWidth = 140,
|
|
53
|
-
maxWidth = 720,
|
|
54
|
-
contentMinWidth = 160,
|
|
55
|
-
onWidthChange,
|
|
56
|
-
viewport,
|
|
57
|
-
}: TrialBodyProps) {
|
|
58
|
-
// The cross-axis half of every size hint below is inert: a horizontal strip
|
|
59
|
-
// stretches its panes to the container's height and never reads `h`.
|
|
60
|
-
const storeRef = useRef<Store | null>(null);
|
|
61
|
-
if (storeRef.current === null) {
|
|
62
|
-
const store = new Store();
|
|
63
|
-
store.registerNode(
|
|
64
|
-
createNode({
|
|
65
|
-
kind: 'zone',
|
|
66
|
-
id: ZONE_ID,
|
|
67
|
-
container: { strategyId: 'strip', config: { axis: 'x', resizable: true } },
|
|
68
|
-
}),
|
|
69
|
-
);
|
|
70
|
-
store.registerNode(
|
|
71
|
-
createNode({
|
|
72
|
-
kind: 'sidebar',
|
|
73
|
-
id: SIDEBAR_ID,
|
|
74
|
-
parentId: ZONE_ID,
|
|
75
|
-
meta: { title: 'Sidebar' },
|
|
76
|
-
placement: { size: { w: width ?? defaultWidth } },
|
|
77
|
-
hints: { minSize: { w: minWidth, h: 0 }, maxSize: { w: maxWidth, h: 0 } },
|
|
78
|
-
}),
|
|
79
|
-
);
|
|
80
|
-
store.registerNode(
|
|
81
|
-
createNode({
|
|
82
|
-
kind: 'content',
|
|
83
|
-
id: CONTENT_ID,
|
|
84
|
-
parentId: ZONE_ID,
|
|
85
|
-
meta: { title: 'Content' },
|
|
86
|
-
hints: { minSize: { w: contentMinWidth, h: 0 } },
|
|
87
|
-
}),
|
|
88
|
-
);
|
|
89
|
-
store.showNode(SIDEBAR_ID);
|
|
90
|
-
store.showNode(CONTENT_ID);
|
|
91
|
-
storeRef.current = store;
|
|
92
|
-
}
|
|
93
|
-
const store = storeRef.current;
|
|
94
|
-
|
|
95
|
-
// What the store last held, so the placement event can tell a drag from the
|
|
96
|
-
// echo of our own write and a controlled `width` does not fight the seam.
|
|
97
|
-
const widthRef = useRef(width ?? defaultWidth);
|
|
98
|
-
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
store.setHints(SIDEBAR_ID, {
|
|
101
|
-
minSize: { w: minWidth, h: 0 },
|
|
102
|
-
maxSize: { w: maxWidth, h: 0 },
|
|
103
|
-
});
|
|
104
|
-
store.setHints(CONTENT_ID, { minSize: { w: contentMinWidth, h: 0 } });
|
|
105
|
-
}, [store, minWidth, maxWidth, contentMinWidth]);
|
|
106
|
-
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
if (width === undefined || width === widthRef.current) return;
|
|
109
|
-
widthRef.current = width;
|
|
110
|
-
store.patchPlacement(SIDEBAR_ID, { size: { w: width } });
|
|
111
|
-
}, [store, width]);
|
|
112
|
-
|
|
113
|
-
const onWidthChangeRef = useRef(onWidthChange);
|
|
114
|
-
onWidthChangeRef.current = onWidthChange;
|
|
115
|
-
useEffect(
|
|
116
|
-
() =>
|
|
117
|
-
store.events.on('node.placementChanged', () => {
|
|
118
|
-
const next = (store.getPlacement(SIDEBAR_ID)?.size as { w?: number } | undefined)?.w;
|
|
119
|
-
if (typeof next !== 'number' || next === widthRef.current) return;
|
|
120
|
-
widthRef.current = next;
|
|
121
|
-
onWidthChangeRef.current?.(next);
|
|
122
|
-
}),
|
|
123
|
-
[store],
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
const chrome = useMemo<ChromeMap>(
|
|
127
|
-
() => ({
|
|
128
|
-
sidebar: () => <div className="lk-trial__sidebar">{sidebar}</div>,
|
|
129
|
-
content: () => (
|
|
130
|
-
<div
|
|
131
|
-
className={
|
|
132
|
-
contentClassName ? `lk-trial__content ${contentClassName}` : 'lk-trial__content'
|
|
133
|
-
}
|
|
134
|
-
>
|
|
135
|
-
{children}
|
|
136
|
-
</div>
|
|
137
|
-
),
|
|
138
|
-
}),
|
|
139
|
-
[sidebar, children, contentClassName],
|
|
140
|
-
);
|
|
141
|
-
|
|
16
|
+
/** A trial's sidebar and content as a `Split`, wearing the trial's classes. */
|
|
17
|
+
export function TrialBody({ sidebar, children, contentClassName, ...rest }: TrialBodyProps) {
|
|
142
18
|
return (
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
</
|
|
19
|
+
<Split
|
|
20
|
+
{...rest}
|
|
21
|
+
sidebar={sidebar}
|
|
22
|
+
className="lk-trial__panes"
|
|
23
|
+
sidebarClassName="lk-trial__sidebar"
|
|
24
|
+
contentClassName={
|
|
25
|
+
contentClassName ? `lk-trial__content ${contentClassName}` : 'lk-trial__content'
|
|
26
|
+
}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</Split>
|
|
154
30
|
);
|
|
155
31
|
}
|
|
@@ -17,7 +17,7 @@ import { useLabContext } from '../lab/LabContext';
|
|
|
17
17
|
import { JobProgress } from '../primitives/JobProgress';
|
|
18
18
|
import { LabStoreContext } from '../state/context';
|
|
19
19
|
import type { TrialRecord } from '../state/types';
|
|
20
|
-
import { as2DView } from '../state/view';
|
|
20
|
+
import { as2DView, withZoom } from '../state/view';
|
|
21
21
|
import { TrialBody } from './TrialBody';
|
|
22
22
|
import { TrialTitleBar } from './TrialTitleBar';
|
|
23
23
|
import { UndockedSections } from './UndockedSections';
|
|
@@ -111,13 +111,13 @@ export function TrialChrome({
|
|
|
111
111
|
|
|
112
112
|
const configSchema = useConfigSchema(instrument);
|
|
113
113
|
|
|
114
|
-
const title = record.title ??
|
|
114
|
+
const title = record.title ?? instrument.title ?? instrument.name;
|
|
115
115
|
const collapsedSections = record.collapsedSections ?? NO_SECTIONS;
|
|
116
116
|
|
|
117
117
|
const ctx = useMemo<TrialChromeContext>(() => {
|
|
118
118
|
const setZoom = (z: number): void => {
|
|
119
119
|
if (!view2d) return;
|
|
120
|
-
updateTrialView(trialId,
|
|
120
|
+
updateTrialView(trialId, withZoom(view2d, z));
|
|
121
121
|
};
|
|
122
122
|
return {
|
|
123
123
|
trialId,
|
|
@@ -225,6 +225,7 @@ export function TrialChrome({
|
|
|
225
225
|
return (
|
|
226
226
|
<section
|
|
227
227
|
className="lk-trial"
|
|
228
|
+
data-trial-id={trialId}
|
|
228
229
|
aria-label={`Trial ${title}`}
|
|
229
230
|
tabIndex={-1}
|
|
230
231
|
onKeyDown={handleKeyDown}
|
|
@@ -243,7 +244,9 @@ export function TrialChrome({
|
|
|
243
244
|
<TrialBody
|
|
244
245
|
width={record.sidebarWidth}
|
|
245
246
|
onWidthChange={(w) => updateTrialSidebarWidth(trialId, w)}
|
|
246
|
-
contentClassName={
|
|
247
|
+
contentClassName={
|
|
248
|
+
instrument.canvas || instrument.stage ? 'lk-trial__content--flush' : undefined
|
|
249
|
+
}
|
|
247
250
|
sidebar={
|
|
248
251
|
<>
|
|
249
252
|
<SidebarRegion contributions={inRegion('sidebar')} ctx={ctx} />
|
package/src/trial/trialOps.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fillConfigDefaults } from '../config/path';
|
|
2
2
|
import type { Instrument, InstrumentList } from '../instrument/types';
|
|
3
|
-
import type { TrialRecord } from '../state/types';
|
|
4
3
|
import { newId } from '../state/helpers';
|
|
4
|
+
import type { TrialRecord } from '../state/types';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } } as const;
|
|
7
7
|
|
|
@@ -14,10 +14,12 @@ function findInstrument(instruments: InstrumentList, name: string): Instrument {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function initialView(instrument: Instrument): TrialRecord['view'] {
|
|
17
|
-
const
|
|
17
|
+
const stage = instrument.canvas ? undefined : instrument.stage;
|
|
18
|
+
const declared = instrument.canvas?.initialView ?? stage?.initialView;
|
|
18
19
|
// A function needs the canvas size, which no trial has at creation. `null`
|
|
19
20
|
// is the unplaced marker; `Trial` resolves it on the first non-empty size.
|
|
20
|
-
|
|
21
|
+
// A stage that names no view is fitted, which needs that size too.
|
|
22
|
+
if (typeof declared === 'function' || (stage && declared === undefined)) return null;
|
|
21
23
|
return declared ? structuredClone(declared) : { ...DEFAULT_VIEW, pan: { ...DEFAULT_VIEW.pan } };
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { C as ConfigPath, V as ValueAtPath } from './types-B9_zrHmb.js';
|
|
2
|
-
|
|
3
|
-
/** A sidebar section torn out of its trial, and where it went. */
|
|
4
|
-
interface UndockedPanel {
|
|
5
|
-
trialId: string;
|
|
6
|
-
sectionId: string;
|
|
7
|
-
as: 'tile' | 'floating';
|
|
8
|
-
}
|
|
9
|
-
/** Undocked panels, keyed by {@link panelKey}. */
|
|
10
|
-
type UndockedPanels = Record<string, UndockedPanel>;
|
|
11
|
-
declare function panelKey(trialId: string, sectionId: string): string;
|
|
12
|
-
declare function undockPanel(panels: UndockedPanels, trialId: string, sectionId: string, as?: UndockedPanel['as']): UndockedPanels;
|
|
13
|
-
/** Dock one section back, or — with no `sectionId` — every panel the trial
|
|
14
|
-
* owns, which is what closing a trial wants. */
|
|
15
|
-
declare function dockPanel(panels: UndockedPanels, trialId: string, sectionId?: string): UndockedPanels;
|
|
16
|
-
|
|
17
|
-
/** A trial's undo history, as snapshots of its state either side of the
|
|
18
|
-
* present. */
|
|
19
|
-
interface UndoStack {
|
|
20
|
-
past: unknown[];
|
|
21
|
-
future: unknown[];
|
|
22
|
-
}
|
|
23
|
-
/** The trial a per-trial call is coming from. `id` is the id `useTileId`
|
|
24
|
-
* scopes a surface tile under, so a consumer keying its own per-trial
|
|
25
|
-
* registry and labkit's tiles agree on the key. */
|
|
26
|
-
interface TrialInfo<TV = unknown> {
|
|
27
|
-
id: string;
|
|
28
|
-
/** The trial's view, in whatever shape the instrument keeps it. */
|
|
29
|
-
view: TV;
|
|
30
|
-
}
|
|
31
|
-
/** One trial as the store holds it: which instrument it runs, that
|
|
32
|
-
* instrument's config and state, the camera, and the undo history. */
|
|
33
|
-
interface TrialRecord<TS = unknown, TC = unknown, TV = unknown> {
|
|
34
|
-
id: string;
|
|
35
|
-
instrumentName: string;
|
|
36
|
-
config: TC;
|
|
37
|
-
state: TS;
|
|
38
|
-
/** Opaque to labkit: persisted, restored on Reset and handed to the instrument,
|
|
39
|
-
* but never read into. A 3D lab puts an orbit here and keeps all three. */
|
|
40
|
-
view: TV;
|
|
41
|
-
/** The config `addTrial` opened this trial on, overlaid on the instrument's
|
|
42
|
-
* defaults. Kept so Reset restores the trial's own subject rather than the
|
|
43
|
-
* bare defaults. */
|
|
44
|
-
configSeed?: Partial<TC>;
|
|
45
|
-
/** This trial's own tool slot. Undefined means it reads the lab's. */
|
|
46
|
-
activeToolId?: string | null;
|
|
47
|
-
/** The extent the trial's sidebar was last dragged to, in pixels. Undefined
|
|
48
|
-
* until someone moves the seam. */
|
|
49
|
-
sidebarWidth?: number;
|
|
50
|
-
/** What the title bar reads. Undefined — the state a trial opens in, and the
|
|
51
|
-
* one `setTitle(null)` returns it to — means the instrument's name. */
|
|
52
|
-
title?: string | null;
|
|
53
|
-
/** Which of the trial's collapsible sections are folded, keyed as
|
|
54
|
-
* `TrialChromeContext.collapsedSections` describes. A key that is absent
|
|
55
|
-
* takes the section's own default. */
|
|
56
|
-
collapsedSections?: Record<string, boolean>;
|
|
57
|
-
/** The marks on this trial's annotation targets, as `AnnotationsApi.toJSON`
|
|
58
|
-
* wrote them. Opaque here, and absent for a trial whose instrument declares
|
|
59
|
-
* no `annotations` — or declares its own `storage`. Not in `state`, which
|
|
60
|
-
* belongs to the instrument and is typed as such. */
|
|
61
|
-
annotations?: unknown;
|
|
62
|
-
undoStack: UndoStack;
|
|
63
|
-
}
|
|
64
|
-
/** A named, saved copy of a trial's config and state, restorable into any
|
|
65
|
-
* trial running the same instrument. */
|
|
66
|
-
interface SavedSnapshot {
|
|
67
|
-
id: string;
|
|
68
|
-
name: string;
|
|
69
|
-
trialId: string;
|
|
70
|
-
instrumentName: string;
|
|
71
|
-
config: unknown;
|
|
72
|
-
state: unknown;
|
|
73
|
-
savedAt: number;
|
|
74
|
-
}
|
|
75
|
-
/** `auto` follows the OS; the other two are an explicit choice. */
|
|
76
|
-
type LabMode = 'auto' | 'light' | 'dark';
|
|
77
|
-
/** Everything a lab persists: its trials, its saved snapshots, and the
|
|
78
|
-
* chosen color mode. */
|
|
79
|
-
interface LabStoreState {
|
|
80
|
-
trials: TrialRecord[];
|
|
81
|
-
savedSnapshots: SavedSnapshot[];
|
|
82
|
-
mode: LabMode;
|
|
83
|
-
/** The lab's tool slot — what a trial with no slot of its own resolves to. */
|
|
84
|
-
activeToolId: string | null;
|
|
85
|
-
/** Per-trial tile extents, keyed by trial id. Opaque here — the
|
|
86
|
-
* shape belongs to whatever lays the trials out. */
|
|
87
|
-
layout: Record<string, unknown>;
|
|
88
|
-
/** Sidebar sections torn out of their trial. A panel here is not rendered in
|
|
89
|
-
* its trial's sidebar; the workspace renders it instead. */
|
|
90
|
-
undockedPanels: UndockedPanels;
|
|
91
|
-
}
|
|
92
|
-
/** Where a lab persists itself. Implementations are keyed string storage and
|
|
93
|
-
* nothing more, so the same store works against localStorage, the URL hash,
|
|
94
|
-
* or memory. */
|
|
95
|
-
interface StorageAdapter {
|
|
96
|
-
read(key: string): string | null;
|
|
97
|
-
write(key: string, value: string): void;
|
|
98
|
-
delete?(key: string): void;
|
|
99
|
-
}
|
|
100
|
-
/** What `useTrialState` hands an instrument: its state and config, with
|
|
101
|
-
* a setter for each. */
|
|
102
|
-
interface TrialStateHandle<TS, TC> {
|
|
103
|
-
state: TS;
|
|
104
|
-
setState: (next: TS | ((prev: TS) => TS)) => void;
|
|
105
|
-
config: TC;
|
|
106
|
-
/** Write one config value, by dotted path — `'grid.size'` for a leaf under
|
|
107
|
-
* an `f.group`, `'cellSize'` for one at the root. */
|
|
108
|
-
setConfig: <P extends ConfigPath<TC> & string>(path: P, value: ValueAtPath<TC, P>) => void;
|
|
109
|
-
}
|
|
110
|
-
/** Options for `createLabStore`. `storageKey` namespaces the keys written, so
|
|
111
|
-
* two labs on one origin do not collide. */
|
|
112
|
-
interface CreateLabStoreOptions {
|
|
113
|
-
storageKey: string;
|
|
114
|
-
storage: StorageAdapter;
|
|
115
|
-
initialMode?: LabMode;
|
|
116
|
-
/** Each instrument's default config, keyed by instrument name, used to fill
|
|
117
|
-
* the gaps in a stored one. A config saved before its schema grew a branch
|
|
118
|
-
* arrives holding that branch's defaults rather than `undefined`, and keeps
|
|
119
|
-
* whatever keys the schema has since stopped naming. `<Lab>` collects these
|
|
120
|
-
* off its `instruments`. */
|
|
121
|
-
configDefaults?: Record<string, () => unknown>;
|
|
122
|
-
/** How each instrument's state survives a reload. Hydration is the first
|
|
123
|
-
* thing `createLabStore` does, so these have to arrive with the store —
|
|
124
|
-
* anything registered afterwards is already too late to read the document
|
|
125
|
-
* it was built from. `<Lab>` collects them off its `instruments`. */
|
|
126
|
-
serializers?: InstrumentSerializers;
|
|
127
|
-
}
|
|
128
|
-
/** Per-instrument serialize/deserialize hooks, keyed by instrument name. An
|
|
129
|
-
* instrument whose state is already JSON-safe needs no entry. `deserialize`
|
|
130
|
-
* is handed the config the state was saved against — a trial's own for a
|
|
131
|
-
* reload, the snapshot's for a load — since a state rebuilt without it can
|
|
132
|
-
* disagree with the settings sitting next to it. */
|
|
133
|
-
type InstrumentSerializers = Record<string, {
|
|
134
|
-
serialize?: (state: unknown) => unknown;
|
|
135
|
-
deserialize?: (data: unknown, config: unknown) => unknown;
|
|
136
|
-
} | undefined>;
|
|
137
|
-
/** A trial as it is persisted: everything but the undo history, which is
|
|
138
|
-
* session-only. */
|
|
139
|
-
type SerializedTrial = Omit<TrialRecord, 'undoStack'>;
|
|
140
|
-
/** Everything a lab persists, under one key, at a known version. */
|
|
141
|
-
interface LabDocument {
|
|
142
|
-
version: number;
|
|
143
|
-
trials: SerializedTrial[];
|
|
144
|
-
saves: SavedSnapshot[];
|
|
145
|
-
layout: Record<string, unknown>;
|
|
146
|
-
/** Sidebar sections torn out of their trial, keyed by trial and section. */
|
|
147
|
-
undockedPanels: UndockedPanels;
|
|
148
|
-
mode: LabMode;
|
|
149
|
-
}
|
|
150
|
-
/** Migrates a document one version forward. Index `i` in the chain takes a
|
|
151
|
-
* version-`i` document to version `i + 1`. */
|
|
152
|
-
type Migration = (doc: Record<string, unknown>) => Record<string, unknown>;
|
|
153
|
-
|
|
154
|
-
export { dockPanel as g, panelKey as p, undockPanel as u };
|
|
155
|
-
export type { CreateLabStoreOptions as C, InstrumentSerializers as I, LabDocument as L, Migration as M, SerializedTrial as S, TrialRecord as T, UndoStack as U, StorageAdapter as a, LabStoreState as b, SavedSnapshot as c, TrialStateHandle as d, UndockedPanel as e, UndockedPanels as f, LabMode as h, TrialInfo as i };
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/** Which control a config field is edited with. */
|
|
2
|
-
type ConfigFieldType = 'slider' | 'checkbox' | 'select' | 'number' | 'text' | 'color';
|
|
3
|
-
/** What every config field carries: the config key it writes, the label shown
|
|
4
|
-
* beside it, and which control renders it. */
|
|
5
|
-
interface ConfigFieldBase {
|
|
6
|
-
key: string;
|
|
7
|
-
label: string;
|
|
8
|
-
type: ConfigFieldType;
|
|
9
|
-
}
|
|
10
|
-
/** A bounded number edited by dragging. */
|
|
11
|
-
interface SliderField extends ConfigFieldBase {
|
|
12
|
-
type: 'slider';
|
|
13
|
-
default: number;
|
|
14
|
-
min: number;
|
|
15
|
-
max: number;
|
|
16
|
-
step?: number;
|
|
17
|
-
}
|
|
18
|
-
/** A boolean. */
|
|
19
|
-
interface CheckboxField extends ConfigFieldBase {
|
|
20
|
-
type: 'checkbox';
|
|
21
|
-
default: boolean;
|
|
22
|
-
}
|
|
23
|
-
/** One choice in a select field. */
|
|
24
|
-
interface SelectOption {
|
|
25
|
-
value: string;
|
|
26
|
-
label: string;
|
|
27
|
-
}
|
|
28
|
-
/** A fixed set of labeled choices. */
|
|
29
|
-
interface SelectField extends ConfigFieldBase {
|
|
30
|
-
type: 'select';
|
|
31
|
-
default: string;
|
|
32
|
-
options: SelectOption[];
|
|
33
|
-
}
|
|
34
|
-
/** A number typed directly, optionally bounded. Use a slider field instead
|
|
35
|
-
* when the range matters more than the exact value. */
|
|
36
|
-
interface NumberField extends ConfigFieldBase {
|
|
37
|
-
type: 'number';
|
|
38
|
-
default: number;
|
|
39
|
-
min?: number;
|
|
40
|
-
max?: number;
|
|
41
|
-
step?: number;
|
|
42
|
-
}
|
|
43
|
-
/** A free-text string. Writes are debounced so typing does not re-run the
|
|
44
|
-
* instrument on every keystroke. */
|
|
45
|
-
interface TextField extends ConfigFieldBase {
|
|
46
|
-
type: 'text';
|
|
47
|
-
default: string;
|
|
48
|
-
placeholder?: string;
|
|
49
|
-
maxLength?: number;
|
|
50
|
-
/** Milliseconds to debounce live setConfig calls. Default 150 ms. Set to 0 to disable. */
|
|
51
|
-
debounceMs?: number;
|
|
52
|
-
}
|
|
53
|
-
/** A color, as a CSS color string. */
|
|
54
|
-
interface ColorField extends ConfigFieldBase {
|
|
55
|
-
type: 'color';
|
|
56
|
-
default: string;
|
|
57
|
-
}
|
|
58
|
-
/** One field of an instrument's config schema. The schema is what the control
|
|
59
|
-
* panel renders, and what `validateConfigSchema` checks. */
|
|
60
|
-
type ConfigField = SliderField | CheckboxField | SelectField | NumberField | TextField | ColorField;
|
|
61
|
-
|
|
62
|
-
export type { ConfigField as C, NumberField as N, SelectField as S, TextField as T, CheckboxField as a, ColorField as b, ConfigFieldBase as c, ConfigFieldType as d, SelectOption as e, SliderField as f };
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { a as StorageAdapter, b as LabStoreState, T as TrialRecord, c as SavedSnapshot, h as LabMode, e as UndockedPanel, C as CreateLabStoreOptions, d as TrialStateHandle } from './types-BP2OCcpg.js';
|
|
2
|
-
import * as react from 'react';
|
|
3
|
-
import { ReactNode, ReactElement } from 'react';
|
|
4
|
-
import { StoreApi } from 'zustand/vanilla';
|
|
5
|
-
|
|
6
|
-
/** Persist to `localStorage` — state survives a reload and a new tab. Falls
|
|
7
|
-
* back to no-ops when storage is unavailable. */
|
|
8
|
-
declare const localStorageAdapter: StorageAdapter;
|
|
9
|
-
/** Persist to `sessionStorage` — state survives a reload but not a new tab. */
|
|
10
|
-
declare const sessionStorageAdapter: StorageAdapter;
|
|
11
|
-
/** Persist into the URL fragment, so the page's link carries its state and
|
|
12
|
-
* can be shared or bookmarked. */
|
|
13
|
-
declare const urlHashAdapter: StorageAdapter;
|
|
14
|
-
/** An in-memory store, discarded on reload. For tests, and for labs that
|
|
15
|
-
* should start fresh every time. */
|
|
16
|
-
declare function createMemoryAdapter(): StorageAdapter;
|
|
17
|
-
/** Persists nothing and reads back nothing. */
|
|
18
|
-
declare const noneAdapter: StorageAdapter;
|
|
19
|
-
|
|
20
|
-
/** Every mutation a lab store supports: managing trials, saving and
|
|
21
|
-
* restoring snapshots, and setting the color mode. */
|
|
22
|
-
interface LabStoreActions {
|
|
23
|
-
addTrial: (record: Omit<TrialRecord, 'undoStack'>) => void;
|
|
24
|
-
removeTrial: (id: string) => void;
|
|
25
|
-
updateTrialState: <TS>(id: string, next: TS | ((prev: TS) => TS)) => void;
|
|
26
|
-
/** Write one config value. `path` is dotted for a value nested under an
|
|
27
|
-
* `f.group`, and the bare key for one at the root. */
|
|
28
|
-
updateTrialConfig: (id: string, path: string, value: unknown) => void;
|
|
29
|
-
updateTrialView: (id: string, view: unknown) => void;
|
|
30
|
-
updateTrialSidebarWidth: (id: string, width: number) => void;
|
|
31
|
-
/** Retitle a trial. `null` returns it to its instrument's name. */
|
|
32
|
-
setTrialTitle: (id: string, title: string | null) => void;
|
|
33
|
-
/** Fold or unfold one of a trial's sections. */
|
|
34
|
-
setTrialSectionCollapsed: (id: string, key: string, collapsed: boolean) => void;
|
|
35
|
-
updateTrialAnnotations: (id: string, doc: unknown) => void;
|
|
36
|
-
updateTrialUndoStack: (id: string, next: TrialRecord['undoStack'] | ((prev: TrialRecord['undoStack']) => TrialRecord['undoStack'])) => void;
|
|
37
|
-
setTrialInstrument: (id: string, instrumentName: string) => void;
|
|
38
|
-
saveSnapshot: (trialId: string, name: string) => void;
|
|
39
|
-
loadSnapshot: (snapshotId: string, trialId: string) => void;
|
|
40
|
-
deleteSnapshot: (snapshotId: string) => void;
|
|
41
|
-
listSnapshots: (trialId?: string) => SavedSnapshot[];
|
|
42
|
-
setMode: (mode: LabMode) => void;
|
|
43
|
-
setLabTool: (id: string | null) => void;
|
|
44
|
-
setTrialTool: (trialId: string, id: string | null) => void;
|
|
45
|
-
setLayout: (layout: Record<string, unknown>) => void;
|
|
46
|
-
/** Tear a sidebar section out of its trial. Default target is a workspace tile. */
|
|
47
|
-
undockPanel: (trialId: string, sectionId: string, as?: UndockedPanel['as']) => void;
|
|
48
|
-
/** Put one section back, or — with no `sectionId` — every panel the trial owns. */
|
|
49
|
-
dockPanel: (trialId: string, sectionId?: string) => void;
|
|
50
|
-
}
|
|
51
|
-
/** A lab's store: its state and its actions. How each instrument's state is
|
|
52
|
-
* serialized comes in through `CreateLabStoreOptions.serializers`. */
|
|
53
|
-
type LabStore = StoreApi<LabStoreState & LabStoreActions>;
|
|
54
|
-
/** Build a lab store, hydrating from storage if anything was saved under the
|
|
55
|
-
* same key. Writes back are debounced. */
|
|
56
|
-
declare function createLabStore(options: CreateLabStoreOptions): LabStore;
|
|
57
|
-
|
|
58
|
-
type LabStoreCtx = {
|
|
59
|
-
store: LabStore;
|
|
60
|
-
} | null;
|
|
61
|
-
/** Context carrying the lab store. Prefer `useLabStore`; this is exported for
|
|
62
|
-
* code that needs to read the context without subscribing. */
|
|
63
|
-
declare const LabStoreContext: react.Context<LabStoreCtx>;
|
|
64
|
-
/** Provides a lab store to its subtree. */
|
|
65
|
-
declare function LabStoreProvider({ store, children, }: {
|
|
66
|
-
store: LabStore;
|
|
67
|
-
children: ReactNode;
|
|
68
|
-
}): ReactElement;
|
|
69
|
-
/** Subscribe to the whole lab store. Throws outside a `<LabStoreProvider>`. */
|
|
70
|
-
declare function useLabStore(): LabStoreState & ReturnType<LabStore['getState']>;
|
|
71
|
-
/** Context carrying which trial the subtree belongs to. */
|
|
72
|
-
declare const TrialIdContext: react.Context<string | null>;
|
|
73
|
-
/** Names the trial its subtree belongs to, so an instrument's hooks can
|
|
74
|
-
* find their own record in the store without being passed an id. */
|
|
75
|
-
declare function TrialIdProvider({ trialId, children, }: {
|
|
76
|
-
trialId: string;
|
|
77
|
-
children: ReactNode;
|
|
78
|
-
}): ReactElement;
|
|
79
|
-
/** The id of the trial this component is inside. Throws outside a
|
|
80
|
-
* `<TrialIdProvider>`. */
|
|
81
|
-
declare function useTrialId(): string;
|
|
82
|
-
|
|
83
|
-
/** Bumped whenever the persisted shape changes; every bump needs a migration. */
|
|
84
|
-
declare const CURRENT_DOCUMENT_VERSION = 3;
|
|
85
|
-
/** The one key a lab persists under. The `:doc` suffix keeps it out of the
|
|
86
|
-
* legacy bucket namespace, where a lab named `a:saves` would otherwise write
|
|
87
|
-
* its document over lab `a`'s saves bucket. */
|
|
88
|
-
declare function labDocumentKey(storageKey: string): string;
|
|
89
|
-
/** Where a document that failed to migrate is set aside, so a bad migration
|
|
90
|
-
* loses state loudly rather than silently. */
|
|
91
|
-
declare function quarantineKey(storageKey: string): string;
|
|
92
|
-
|
|
93
|
-
/** An instrument's own state and config, plus setters. Reads the surrounding
|
|
94
|
-
* trial id, so an instrument never has to know which trial it is
|
|
95
|
-
* running in. Throws outside a lab store and trial. */
|
|
96
|
-
declare function useTrialState<TS = unknown, TC = unknown>(): TrialStateHandle<TS, TC>;
|
|
97
|
-
|
|
98
|
-
export { CURRENT_DOCUMENT_VERSION as C, TrialIdContext as T, LabStoreContext as b, LabStoreProvider as c, TrialIdProvider as d, createLabStore as e, createMemoryAdapter as f, localStorageAdapter as g, useLabStore as h, useTrialId as i, useTrialState as j, labDocumentKey as l, noneAdapter as n, quarantineKey as q, sessionStorageAdapter as s, urlHashAdapter as u };
|
|
99
|
-
export type { LabStore as L, LabStoreActions as a };
|