@weasel-js/labkit 1.4.4 → 1.5.1
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 +54 -6
- package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CJNMS6mz.d.ts} +1 -1
- package/dist/_dts/{PrefsForm.d-DDHFANkc.d.ts → PrefsForm.d-DPy9I8It.d.ts} +4 -0
- package/dist/_dts/Select.d-Gwr8fLpK.d.ts +453 -0
- package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-B4okC2lX.d.ts} +291 -31
- package/dist/_dts/{index-Blv9uQzu.d.ts → index-BNdz-tNd.d.ts} +105 -17
- package/dist/_dts/index-CSJoj4LC.d.ts +248 -0
- package/dist/_dts/{types-B9_zrHmb.d.ts → types-DzoNY1pO.d.ts} +91 -4
- package/dist/_dts/usePanZoom-Pzf9f2jh.d.ts +160 -0
- package/dist/_dts/useTrialState-uLlmaccn.d.ts +212 -0
- package/dist/canvas/index.d.ts +18 -129
- package/dist/canvas/index.js +4 -2
- package/dist/chrome/index.d.ts +5 -93
- package/dist/chrome/index.js +10 -6
- package/dist/chunk-7PFH3CNM.js +150 -0
- package/dist/chunk-7PFH3CNM.js.map +1 -0
- package/dist/{chunk-I6JCVE24.js → chunk-CEIUBPPW.js} +143 -17
- package/dist/chunk-CEIUBPPW.js.map +1 -0
- package/dist/chunk-EH7UEQV7.js +590 -0
- package/dist/chunk-EH7UEQV7.js.map +1 -0
- package/dist/{chunk-MOM3GOVY.js → chunk-EHGVROVV.js} +125 -27
- package/dist/chunk-EHGVROVV.js.map +1 -0
- package/dist/{chunk-TJ7QY3OC.js → chunk-ERFMC6WU.js} +160 -78
- package/dist/chunk-ERFMC6WU.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-64ZCN3DA.js → chunk-IHUVQ3TX.js} +250 -160
- package/dist/chunk-IHUVQ3TX.js.map +1 -0
- package/dist/{chunk-FQMJUVHJ.js → chunk-IJJUK4FU.js} +53 -46
- package/dist/chunk-IJJUK4FU.js.map +1 -0
- package/dist/chunk-IXTQI7JX.js +737 -0
- package/dist/chunk-IXTQI7JX.js.map +1 -0
- package/dist/{chunk-BKFVHKJH.js → chunk-MDVTK5U3.js} +4 -4
- package/dist/{chunk-BKFVHKJH.js.map → chunk-MDVTK5U3.js.map} +1 -1
- package/dist/chunk-PHK4W562.js +72 -0
- package/dist/chunk-PHK4W562.js.map +1 -0
- package/dist/chunk-PJ7WEQBJ.js +18 -0
- package/dist/chunk-PJ7WEQBJ.js.map +1 -0
- package/dist/chunk-RWNDDKOH.js +682 -0
- package/dist/chunk-RWNDDKOH.js.map +1 -0
- package/dist/chunk-T3EC2DXD.js +12628 -0
- package/dist/chunk-T3EC2DXD.js.map +1 -0
- package/dist/{chunk-SMHP6XZ4.js → chunk-UN4U4P63.js} +10 -8
- package/dist/chunk-UN4U4P63.js.map +1 -0
- package/dist/chunk-WIG6XHJ7.js +35 -0
- package/dist/chunk-WIG6XHJ7.js.map +1 -0
- package/dist/chunk-Z76KEZAX.js +15 -0
- package/dist/chunk-Z76KEZAX.js.map +1 -0
- package/dist/chunk-ZDHY47EV.js +363 -0
- package/dist/chunk-ZDHY47EV.js.map +1 -0
- package/dist/config/index.d.ts +276 -0
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -0
- package/dist/controls/index.d.ts +3 -4
- package/dist/controls/index.js +4 -3
- package/dist/dragdrop/index.d.ts +3 -5
- package/dist/index.d.ts +266 -413
- package/dist/index.js +1775 -2251
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +4 -6
- package/dist/layers/index.js +2 -2
- package/dist/loupe/index.d.ts +69 -20
- package/dist/loupe/index.js +2 -2
- package/dist/passthrough/weasel-ui.d.ts +175 -309
- package/dist/passthrough/weasel-ui.js +1 -1
- package/dist/primitives/index.d.ts +55 -6
- package/dist/primitives/index.js +6 -4
- package/dist/state/index.d.ts +13 -8
- package/dist/state/index.js +18 -13
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +223 -76
- 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 +3 -5
- package/package.json +15 -8
- package/src/annotations/AnnotationOverlay.tsx +56 -5
- package/src/annotations/Annotations.less +4 -0
- package/src/annotations/Annotations.overlay.test.tsx +105 -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/svgNodes.ts +1 -0
- package/src/annotations/toolMap.test.ts +9 -5
- package/src/annotations/toolMap.ts +6 -3
- package/src/annotations/types.ts +6 -3
- package/src/canvas/CameraWheelContext.ts +12 -0
- package/src/canvas/CanvasStack.stories.tsx +1 -1
- 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.test.ts +28 -0
- package/src/canvas/usePanZoom.ts +21 -9
- package/src/chrome/ChromeRegions.stories.tsx +1 -4
- package/src/chrome/LabChrome.tsx +130 -0
- package/src/chrome/builtins.test.ts +1 -0
- package/src/chrome/builtins.tsx +21 -31
- package/src/chrome/index.ts +16 -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 +16 -9
- package/src/chrome/regions/SidebarRegion.stories.tsx +44 -0
- package/src/chrome/regions/SidebarRegion.tsx +25 -13
- package/src/chrome/regions/StatusRegion.tsx +15 -8
- package/src/chrome/regions/ToolbarRegion.tsx +34 -16
- package/src/chrome/types.ts +50 -20
- package/src/config/auto.test.ts +12 -0
- package/src/config/auto.ts +15 -0
- package/src/config/autoConfig.test.ts +107 -0
- package/src/config/autoConfig.ts +153 -0
- package/src/config/builder.test.ts +42 -0
- package/src/config/builder.ts +68 -17
- 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 +16 -1
- package/src/config/nodeClasses.test.ts +18 -0
- package/src/config/path.ts +7 -2
- package/src/config/resolve.test.ts +25 -0
- package/src/config/resolve.ts +17 -1
- package/src/config/types.ts +34 -2
- package/src/config/useResolvedConfig.ts +26 -0
- package/src/controls/AutoControls.stories.tsx +88 -0
- package/src/controls/ControlPanel.stories.tsx +42 -1
- package/src/controls/ControlPanel.test.tsx +530 -4
- package/src/controls/ControlPanel.tsx +562 -82
- package/src/fake-indexeddb-auto.d.ts +5 -0
- package/src/index.test.ts +7 -0
- package/src/index.ts +66 -5
- package/src/instrument/serializers.test.ts +22 -0
- package/src/instrument/serializers.ts +40 -1
- package/src/instrument/types.ts +25 -1
- package/src/lab/Lab.chrome.test.tsx +16 -0
- package/src/lab/Lab.less +4 -13
- package/src/lab/Lab.persist.test.tsx +147 -0
- package/src/lab/Lab.stories.tsx +1 -3
- package/src/lab/Lab.surface.test.tsx +71 -3
- package/src/lab/Lab.test.tsx +168 -16
- package/src/lab/Lab.tsx +434 -129
- package/src/lab/LabContext.ts +8 -0
- package/src/lab/LabFit.stories.less +18 -0
- package/src/lab/LabFit.stories.tsx +219 -0
- package/src/lab/LabFullChrome.stories.tsx +1 -4
- package/src/lab/LabHeader.test.tsx +40 -9
- package/src/lab/LabHeader.tsx +7 -14
- package/src/lab/LabPalette.tsx +13 -26
- package/src/lab/LabRoot.test.tsx +56 -0
- package/src/lab/LabRoot.tsx +47 -0
- package/src/lab/LabShell.less +73 -15
- package/src/lab/LabShell.stories.tsx +50 -1
- package/src/lab/LabShell.tsx +18 -27
- package/src/lab/LabSwitcher.less +18 -11
- package/src/lab/LabSwitcher.test.tsx +14 -9
- package/src/lab/LabSwitcher.tsx +8 -4
- package/src/lab/Workspace.less +1 -1
- package/src/lab/Workspace.stories.tsx +1 -1
- package/src/lab/Workspace.surface.test.tsx +1 -0
- package/src/lab/fitCheck.test.ts +57 -0
- package/src/lab/fitCheck.ts +125 -0
- package/src/lab/index.ts +2 -0
- package/src/layers/LayerList.stories.tsx +1 -1
- package/src/loupe/AGENTS.md +17 -5
- package/src/loupe/Loupe.less +1 -1
- package/src/loupe/LoupeGestures.tsx +67 -0
- package/src/loupe/TrialLoupe.tsx +43 -28
- package/src/loupe/canvasLens.ts +1 -1
- package/src/loupe/index.ts +4 -0
- package/src/loupe/loupeActions.ts +98 -0
- package/src/loupe/types.ts +1 -1
- package/src/loupe/useLoupe.test.tsx +60 -3
- package/src/loupe/useLoupe.ts +29 -49
- package/src/passthrough/weasel-ui.ts +9 -0
- package/src/primitives/FloatingPanel.stories.tsx +14 -2
- package/src/primitives/FloatingPanel.test.tsx +47 -16
- package/src/primitives/FloatingPanel.tsx +11 -25
- package/src/primitives/FpsMeter.stories.tsx +1 -1
- package/src/primitives/JobProgress.stories.tsx +1 -1
- package/src/primitives/Legend.stories.tsx +1 -1
- package/src/primitives/ScaleIndicator.stories.tsx +1 -1
- package/src/primitives/Sidebar.less +7 -0
- package/src/primitives/Sidebar.stories.tsx +1 -1
- package/src/primitives/Split.less +14 -0
- package/src/primitives/Split.test.tsx +133 -0
- package/src/primitives/Split.tsx +175 -0
- package/src/primitives/StatusBar.stories.tsx +1 -1
- package/src/primitives/Toolbar.stories.tsx +1 -1
- package/src/primitives/ZoomControl.tsx +5 -2
- package/src/primitives/index.ts +2 -0
- package/src/specimen/Specimen.less +95 -0
- package/src/specimen/Specimen.stories.tsx +13 -0
- package/src/specimen/Specimen.test.tsx +13 -0
- package/src/specimen/Specimen.tsx +888 -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 +169 -635
- package/src/state/store.ts +146 -163
- package/src/state/toolSlot.test.ts +1 -1
- package/src/state/types.ts +65 -17
- 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 +87 -3
- package/src/state/useTrialState.ts +9 -1
- package/src/state/view.test.ts +49 -15
- package/src/state/view.ts +18 -1
- package/src/styles.less +3 -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 +1 -0
- package/src/surface/useSurfaceTile.ts +6 -5
- package/src/surface/useTiledSurface.test.tsx +231 -1
- package/src/surface/useTiledSurface.ts +76 -15
- package/src/theme/Interstellar.stories.tsx +9 -9
- package/src/theme/interstellar.test.ts +17 -6
- package/src/theme/interstellar.theme.json +180 -0
- package/src/theme/interstellar.ts +8 -5
- package/src/tools/labTool.ts +11 -0
- package/src/trial/Trial.annotations.persist.test.tsx +100 -58
- package/src/trial/Trial.annotations.test.tsx +59 -7
- package/src/trial/Trial.canvas.test.tsx +32 -0
- package/src/trial/Trial.config.test.tsx +75 -2
- package/src/trial/Trial.job.test.tsx +3 -3
- package/src/trial/Trial.less +5 -26
- package/src/trial/Trial.stories.tsx +5 -3
- package/src/trial/Trial.test.tsx +31 -4
- package/src/trial/Trial.trialId.test.tsx +23 -1
- package/src/trial/Trial.tsx +139 -52
- package/src/trial/TrialBody.tsx +17 -141
- package/src/trial/TrialChrome.tsx +24 -7
- package/src/trial/trialOps.test.ts +101 -1
- package/src/trial/trialOps.ts +67 -7
- 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-E44SU6XS.js +0 -50
- package/dist/chunk-E44SU6XS.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 +0 -343
- 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/theme/interstellar.tokens.json +0 -61
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { auto } from '../config/auto';
|
|
3
|
+
import { f } from '../config/builder';
|
|
4
|
+
import { defineInstrument } from '../instrument/defineInstrument';
|
|
2
5
|
import type { Instrument } from '../instrument/types';
|
|
3
6
|
import type { TrialRecord } from '../state/types';
|
|
4
|
-
import { addTrial, cloneTrial, closeTrial, reorderTrials, resetTrial } from './trialOps';
|
|
7
|
+
import { addTrial, cloneTrial, closeTrial, reorderTrials, resetTrial, swapTrial } from './trialOps';
|
|
5
8
|
|
|
6
9
|
interface CounterState {
|
|
7
10
|
count: number;
|
|
@@ -38,10 +41,22 @@ const subjectInstrument: Instrument<{ label: string }, SubjectConfig> = {
|
|
|
38
41
|
render: () => null,
|
|
39
42
|
};
|
|
40
43
|
|
|
44
|
+
const schemaInstrument = defineInstrument({
|
|
45
|
+
name: 'Schemed',
|
|
46
|
+
config: f.schema({
|
|
47
|
+
cols: f.number(3).initial(auto),
|
|
48
|
+
gap: f.number(12),
|
|
49
|
+
grid: f.group({ size: f.number(4) }),
|
|
50
|
+
}),
|
|
51
|
+
initialState: () => ({}),
|
|
52
|
+
render: () => null,
|
|
53
|
+
});
|
|
54
|
+
|
|
41
55
|
const instruments: Instrument[] = [
|
|
42
56
|
counter as Instrument,
|
|
43
57
|
counterWithCanvas as Instrument,
|
|
44
58
|
subjectInstrument as Instrument,
|
|
59
|
+
schemaInstrument as Instrument,
|
|
45
60
|
];
|
|
46
61
|
|
|
47
62
|
function head<T>(arr: T[]): T {
|
|
@@ -107,6 +122,38 @@ describe('addTrial', () => {
|
|
|
107
122
|
const out = addTrial([], instruments, 'Subject', { config: { subject: undefined } });
|
|
108
123
|
expect(out[0]?.config).toEqual({ subject: 'none', zoom: 1 });
|
|
109
124
|
});
|
|
125
|
+
|
|
126
|
+
it('opens a trial with the schema-declared auto paths already unpinned', () => {
|
|
127
|
+
const out = addTrial([], instruments, 'Schemed');
|
|
128
|
+
expect(out[0]?.auto).toEqual(['cols']);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('honors auto in a seed config and keeps the sentinel out of the record', () => {
|
|
132
|
+
const out = addTrial([], instruments, 'Schemed', { config: { gap: auto } });
|
|
133
|
+
expect(out[0]?.auto).toContain('gap');
|
|
134
|
+
expect(out[0]?.autoSeed).toEqual(['gap']);
|
|
135
|
+
expect((out[0]?.config as { gap: number }).gap).toBe(12);
|
|
136
|
+
expect(out[0]?.configSeed).not.toHaveProperty('gap');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('keeps a seed value that is an object but not a config branch', () => {
|
|
140
|
+
const when = new Date('2020-01-01T00:00:00Z');
|
|
141
|
+
const out = addTrial([], instruments, 'Counter', {
|
|
142
|
+
config: { at: when } as unknown as Partial<CounterConfig>,
|
|
143
|
+
});
|
|
144
|
+
expect((out[0]?.config as { at: unknown }).at).toBe(when);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('honors auto at a nested seed path', () => {
|
|
148
|
+
const out = addTrial([], instruments, 'Schemed', { config: { grid: { size: auto } } });
|
|
149
|
+
expect(out[0]?.auto).toContain('grid.size');
|
|
150
|
+
expect((out[0]?.config as { grid: { size: number } }).grid.size).toBe(4);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('leaves a trial with no auto field when nothing is unpinned', () => {
|
|
154
|
+
const out = addTrial([], instruments, 'Counter');
|
|
155
|
+
expect(out[0]?.auto).toBeUndefined();
|
|
156
|
+
});
|
|
110
157
|
});
|
|
111
158
|
|
|
112
159
|
describe('cloneTrial', () => {
|
|
@@ -187,6 +234,20 @@ describe('resetTrial', () => {
|
|
|
187
234
|
expect(reset[0]?.state).toEqual({ label: 'ONE' });
|
|
188
235
|
expect(reset[1]?.config).toEqual({ subject: 'two', zoom: 1 });
|
|
189
236
|
});
|
|
237
|
+
|
|
238
|
+
it('un-pins the paths the trial opened auto again, from schema and from seed', () => {
|
|
239
|
+
const arr = addTrial([], instruments, 'Schemed', { config: { gap: auto } });
|
|
240
|
+
head(arr).auto = [];
|
|
241
|
+
const reset = resetTrial(arr, head(arr).id, instruments);
|
|
242
|
+
expect([...(reset[0]?.auto ?? [])].sort()).toEqual(['cols', 'gap']);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('pins a path the user un-pinned that the trial did not open on', () => {
|
|
246
|
+
const arr = addTrial([], instruments, 'Schemed');
|
|
247
|
+
head(arr).auto = ['cols', 'grid.size'];
|
|
248
|
+
const reset = resetTrial(arr, head(arr).id, instruments);
|
|
249
|
+
expect(reset[0]?.auto).toEqual(['cols']);
|
|
250
|
+
});
|
|
190
251
|
});
|
|
191
252
|
|
|
192
253
|
describe('reorderTrials', () => {
|
|
@@ -236,3 +297,42 @@ describe('a size-aware initial view', () => {
|
|
|
236
297
|
expect(trial?.view).toBeNull();
|
|
237
298
|
});
|
|
238
299
|
});
|
|
300
|
+
|
|
301
|
+
describe('swapTrial', () => {
|
|
302
|
+
const instruments = [counter, subjectInstrument];
|
|
303
|
+
const three = (): TrialRecord[] =>
|
|
304
|
+
addTrial(
|
|
305
|
+
addTrial(addTrial([], instruments, 'Counter'), instruments, 'Counter'),
|
|
306
|
+
instruments,
|
|
307
|
+
'Counter',
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
it('puts a fresh trial of the other instrument in the same place', () => {
|
|
311
|
+
const trials = three();
|
|
312
|
+
const target = trials[1] as TrialRecord;
|
|
313
|
+
const next = swapTrial(trials, instruments, target.id, 'Subject', {
|
|
314
|
+
config: { subject: 'moon' },
|
|
315
|
+
});
|
|
316
|
+
expect(next).toHaveLength(3);
|
|
317
|
+
expect(next[0]).toBe(trials[0]);
|
|
318
|
+
expect(next[2]).toBe(trials[2]);
|
|
319
|
+
const swapped = next[1] as TrialRecord;
|
|
320
|
+
expect(swapped.id).not.toBe(target.id);
|
|
321
|
+
expect(swapped.instrumentName).toBe('Subject');
|
|
322
|
+
expect(swapped.config).toEqual({ subject: 'moon', zoom: 1 });
|
|
323
|
+
expect(swapped.state).toEqual({ label: 'MOON' });
|
|
324
|
+
expect(swapped.configSeed).toEqual({ subject: 'moon' });
|
|
325
|
+
expect(swapped.undoStack).toEqual({ past: [], future: [] });
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('keeps the width the trial’s sidebar was dragged to', () => {
|
|
329
|
+
const trials = three().map((t, i) => (i === 1 ? { ...t, sidebarWidth: 312 } : t));
|
|
330
|
+
const next = swapTrial(trials, instruments, (trials[1] as TrialRecord).id, 'Subject');
|
|
331
|
+
expect(next[1]?.sidebarWidth).toBe(312);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('returns the list untouched for an id it does not hold', () => {
|
|
335
|
+
const trials = three();
|
|
336
|
+
expect(swapTrial(trials, instruments, 'nope', 'Subject')).toBe(trials);
|
|
337
|
+
});
|
|
338
|
+
});
|
package/src/trial/trialOps.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isAuto } from '../config/auto';
|
|
2
|
+
import { autoPathsOf } from '../config/autoConfig';
|
|
3
|
+
import { fillConfigDefaults, isRecord } from '../config/path';
|
|
4
|
+
import { unruledConfigSchema } from '../instrument/serializers';
|
|
2
5
|
import type { Instrument, InstrumentList } from '../instrument/types';
|
|
3
|
-
import type { TrialRecord } from '../state/types';
|
|
4
6
|
import { newId } from '../state/helpers';
|
|
7
|
+
import type { TrialRecord } from '../state/types';
|
|
5
8
|
|
|
6
9
|
const DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } } as const;
|
|
7
10
|
|
|
@@ -14,10 +17,12 @@ function findInstrument(instruments: InstrumentList, name: string): Instrument {
|
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
function initialView(instrument: Instrument): TrialRecord['view'] {
|
|
17
|
-
const
|
|
20
|
+
const stage = instrument.canvas ? undefined : instrument.stage;
|
|
21
|
+
const declared = instrument.canvas?.initialView ?? stage?.initialView;
|
|
18
22
|
// A function needs the canvas size, which no trial has at creation. `null`
|
|
19
23
|
// is the unplaced marker; `Trial` resolves it on the first non-empty size.
|
|
20
|
-
|
|
24
|
+
// A stage that names no view is fitted, which needs that size too.
|
|
25
|
+
if (typeof declared === 'function' || (stage && declared === undefined)) return null;
|
|
21
26
|
return declared ? structuredClone(declared) : { ...DEFAULT_VIEW, pan: { ...DEFAULT_VIEW.pan } };
|
|
22
27
|
}
|
|
23
28
|
|
|
@@ -35,6 +40,35 @@ function seedConfig<TC>(defaults: TC, seed: Partial<TC> | undefined): TC {
|
|
|
35
40
|
return seed ? fillConfigDefaults(seed, defaults) : defaults;
|
|
36
41
|
}
|
|
37
42
|
|
|
43
|
+
/** Splits a seed config into its ordinary values and the dotted paths it wrote
|
|
44
|
+
* as `auto`, so the sentinel never reaches the record. */
|
|
45
|
+
function splitAutoSeed(
|
|
46
|
+
seed: Record<string, unknown> | undefined,
|
|
47
|
+
at = '',
|
|
48
|
+
): { config: Record<string, unknown>; autoPaths: string[] } {
|
|
49
|
+
const config: Record<string, unknown> = {};
|
|
50
|
+
const autoPaths: string[] = [];
|
|
51
|
+
for (const [key, value] of Object.entries(seed ?? {})) {
|
|
52
|
+
const path = at === '' ? key : `${at}.${key}`;
|
|
53
|
+
if (isAuto(value)) {
|
|
54
|
+
autoPaths.push(path);
|
|
55
|
+
} else if (isRecord(value)) {
|
|
56
|
+
const inner = splitAutoSeed(value as Record<string, unknown>, path);
|
|
57
|
+
config[key] = inner.config;
|
|
58
|
+
autoPaths.push(...inner.autoPaths);
|
|
59
|
+
} else {
|
|
60
|
+
config[key] = value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { config, autoPaths };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The paths an instrument's own schema declares as starting auto. */
|
|
67
|
+
function schemaAutoPaths(instrument: Instrument): string[] {
|
|
68
|
+
const schema = unruledConfigSchema(instrument);
|
|
69
|
+
return schema ? autoPathsOf(schema) : [];
|
|
70
|
+
}
|
|
71
|
+
|
|
38
72
|
/** Append a new trial running `instrumentName`, at that instrument's default
|
|
39
73
|
* config — with `options.config` written over it — and the initial state
|
|
40
74
|
* that config produces. */
|
|
@@ -45,7 +79,9 @@ export function addTrial(
|
|
|
45
79
|
options: AddTrialOptions = {},
|
|
46
80
|
): TrialRecord[] {
|
|
47
81
|
const instrument = findInstrument(instruments, instrumentName);
|
|
48
|
-
const
|
|
82
|
+
const seeded = splitAutoSeed(options.config as Record<string, unknown> | undefined);
|
|
83
|
+
const autoPaths = [...new Set([...schemaAutoPaths(instrument), ...seeded.autoPaths])];
|
|
84
|
+
const config = seedConfig(instrument.defaultConfig(), seeded.config);
|
|
49
85
|
const state = instrument.initialState(config);
|
|
50
86
|
const record: TrialRecord = {
|
|
51
87
|
id: newId(),
|
|
@@ -55,7 +91,9 @@ export function addTrial(
|
|
|
55
91
|
view: initialView(instrument),
|
|
56
92
|
undoStack: { past: [], future: [] },
|
|
57
93
|
};
|
|
58
|
-
if (options.config) record.configSeed =
|
|
94
|
+
if (options.config) record.configSeed = seeded.config;
|
|
95
|
+
if (seeded.autoPaths.length > 0) record.autoSeed = seeded.autoPaths;
|
|
96
|
+
if (autoPaths.length > 0) record.auto = autoPaths;
|
|
59
97
|
return [...trials, record];
|
|
60
98
|
}
|
|
61
99
|
|
|
@@ -97,15 +135,37 @@ export function resetTrial(
|
|
|
97
135
|
const instrument = findInstrument(instruments, current.instrumentName);
|
|
98
136
|
const config = seedConfig(instrument.defaultConfig(), current.configSeed);
|
|
99
137
|
const state = instrument.initialState(config);
|
|
138
|
+
const autoPaths = [...new Set([...schemaAutoPaths(instrument), ...(current.autoSeed ?? [])])];
|
|
139
|
+
const { auto: _unpinnedNow, ...kept } = current;
|
|
100
140
|
const reset: TrialRecord = {
|
|
101
|
-
...
|
|
141
|
+
...kept,
|
|
102
142
|
config,
|
|
103
143
|
state,
|
|
104
144
|
view: initialView(instrument),
|
|
105
145
|
};
|
|
146
|
+
if (autoPaths.length > 0) reset.auto = autoPaths;
|
|
106
147
|
return [...trials.slice(0, idx), reset, ...trials.slice(idx + 1)];
|
|
107
148
|
}
|
|
108
149
|
|
|
150
|
+
/** Put a fresh trial running `instrumentName` where trial `id` is. It gets a
|
|
151
|
+
* new id, since the history, snapshots and marks keyed by the old one belong
|
|
152
|
+
* to what that trial was running, but keeps the width its sidebar was
|
|
153
|
+
* dragged to. */
|
|
154
|
+
export function swapTrial(
|
|
155
|
+
trials: TrialRecord[],
|
|
156
|
+
instruments: InstrumentList,
|
|
157
|
+
id: string,
|
|
158
|
+
instrumentName: string,
|
|
159
|
+
options: AddTrialOptions = {},
|
|
160
|
+
): TrialRecord[] {
|
|
161
|
+
const idx = trials.findIndex((w) => w.id === id);
|
|
162
|
+
const current = trials[idx];
|
|
163
|
+
if (!current) return trials;
|
|
164
|
+
const [record] = addTrial([], instruments, instrumentName, options) as [TrialRecord];
|
|
165
|
+
if (current.sidebarWidth !== undefined) record.sidebarWidth = current.sidebarWidth;
|
|
166
|
+
return [...trials.slice(0, idx), record, ...trials.slice(idx + 1)];
|
|
167
|
+
}
|
|
168
|
+
|
|
109
169
|
/**
|
|
110
170
|
* Reorder to match `ids`. Ids the list doesn't mention keep their relative
|
|
111
171
|
* order at the end, and ids it names that no longer exist are dropped — a
|
|
@@ -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 };
|