@weasel-js/labkit 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/_dts/{DrawCommand-BkZztJsW.d.ts → DrawCommand-C_XboUpz.d.ts} +48 -6
- package/dist/_dts/{index-iAP6XbH3.d.ts → index-JFAYj5Tv.d.ts} +12 -0
- package/dist/_dts/types-DJ79Tg5J.d.ts +56 -0
- package/dist/_dts/{useTrialState-DYe2vUwN.d.ts → useTrialState-BMNIx3Cy.d.ts} +5 -9
- package/dist/canvas/index.d.ts +2 -1
- package/dist/{chunk-C6GJKPUI.js → chunk-73KA7WBO.js} +69 -63
- package/dist/chunk-73KA7WBO.js.map +1 -0
- package/dist/{chunk-DJLDIRFN.js → chunk-BOHF3PQO.js} +4375 -3720
- package/dist/chunk-BOHF3PQO.js.map +1 -0
- package/dist/{chunk-NRKWVTVT.js → chunk-G5TJVQQT.js} +3 -3
- package/dist/{chunk-NRKWVTVT.js.map → chunk-G5TJVQQT.js.map} +1 -1
- package/dist/chunk-LN6JDUGB.js +106 -0
- package/dist/chunk-LN6JDUGB.js.map +1 -0
- package/dist/chunk-THBG7FQZ.js +167 -0
- package/dist/chunk-THBG7FQZ.js.map +1 -0
- package/dist/{chunk-3TYUJR7Z.js → chunk-VUU5UXHE.js} +6 -3
- package/dist/chunk-VUU5UXHE.js.map +1 -0
- package/dist/dragdrop/index.d.ts +2 -1
- package/dist/index.d.ts +71 -11
- package/dist/index.js +165 -22
- package/dist/index.js.map +1 -1
- package/dist/job/index.d.ts +13 -0
- package/dist/job/index.js +3 -0
- package/dist/job/index.js.map +1 -0
- package/dist/layers/index.d.ts +3 -2
- package/dist/passthrough/weasel-canvas.d.ts +1 -1
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +20 -4
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/state/index.d.ts +2 -2
- package/dist/state/index.js +2 -2
- package/dist/styles.css +10 -0
- package/dist/surface/index.d.ts +77 -0
- package/dist/surface/index.js +3 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/ui/layers/index.js +3 -3
- package/dist/undo/index.d.ts +2 -1
- package/package.json +9 -1
- package/src/canvas/AGENTS.md +8 -0
- package/src/canvas/useOrbit.test.ts +71 -0
- package/src/canvas/useOrbit.ts +149 -0
- package/src/index.test.ts +18 -0
- package/src/index.ts +13 -0
- package/src/instrument/SineWave.smoke.test.tsx +2 -1
- package/src/instrument/types.ts +13 -1
- package/src/job/index.ts +3 -0
- package/src/job/types.ts +47 -0
- package/src/job/useJob.test.tsx +210 -0
- package/src/job/useJob.ts +134 -0
- package/src/lab/Lab.stories.tsx +1 -2
- package/src/lab/Workspace.surface.test.tsx +49 -0
- package/src/lab/Workspace.tsx +14 -1
- package/src/state/store.ts +8 -2
- package/src/state/types.ts +4 -2
- package/src/state/view.test.ts +127 -0
- package/src/state/view.ts +18 -0
- package/src/surface/AGENTS.md +64 -0
- package/src/surface/SurfaceContext.ts +5 -0
- package/src/surface/composeRects.test.ts +50 -0
- package/src/surface/composeRects.ts +19 -0
- package/src/surface/deviceRect.test.ts +40 -0
- package/src/surface/deviceRect.ts +19 -0
- package/src/surface/index.ts +7 -0
- package/src/surface/rect.ts +16 -0
- package/src/surface/useSurfaceTile.test.tsx +67 -0
- package/src/surface/useSurfaceTile.ts +32 -0
- package/src/surface/useTiledSurface.test.tsx +231 -0
- package/src/surface/useTiledSurface.ts +157 -0
- package/src/trial/DefaultStatusBar.tsx +2 -2
- package/src/trial/Trial.job.test.tsx +75 -0
- package/src/trial/Trial.less +12 -0
- package/src/trial/Trial.stories.tsx +8 -1
- package/src/trial/Trial.tsx +32 -6
- package/src/trial/TrialChrome.tsx +33 -6
- package/src/trial/slotTypes.ts +2 -1
- package/dist/chunk-3TYUJR7Z.js.map +0 -1
- package/dist/chunk-C6GJKPUI.js.map +0 -1
- package/dist/chunk-DJLDIRFN.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c, p } from './chunk-
|
|
1
|
+
import { c, p } from './chunk-73KA7WBO.js';
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
@@ -157,5 +157,5 @@ function DragHandleGlyph() {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
export { LayerStack };
|
|
160
|
-
//# sourceMappingURL=chunk-
|
|
161
|
-
//# sourceMappingURL=chunk-
|
|
160
|
+
//# sourceMappingURL=chunk-G5TJVQQT.js.map
|
|
161
|
+
//# sourceMappingURL=chunk-G5TJVQQT.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ui/layers/LayerStack.tsx"],"names":[],"mappings":"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","file":"chunk-NRKWVTVT.js","sourcesContent":["import { type CSSProperties, type ReactNode, type RefCallback, useEffect, useState } from 'react';\nimport { dlog, useReorderDragList } from '../../passthrough/weasel-ui';\n\n/** One card in a layer stack: its identity, the label shown when collapsed,\n * and the optional select hoisted into its header. */\nexport interface LayerStackItem {\n /** Stable id used for keys, onRemove, onReorder. Numeric to match\n * common id-from-nextId conventions; string ids also work. */\n id: number | string;\n /** Short kind label rendered in the header when no primary select\n * is hoisted (e.g. \"shadow\", \"stroke\"). */\n kind: string;\n /** When present, hoist this select into the card header so the user\n * can switch mode/shape without expanding. */\n primaryValue?: string;\n primaryOptions?: string[];\n /** Accent CSS color used as the left border / index-badge fill. */\n accent?: string;\n /** Optional badge text rendered before the primary control\n * (e.g. tail index \"1\", \"2\", \"3\"). When omitted a drag handle\n * glyph renders in its place. */\n badge?: string;\n /** Initial expanded state. Defaults to true for newly-added items. */\n defaultExpanded?: boolean;\n}\n\n/** Props for `<LayerStack>`. */\nexport interface LayerStackProps {\n title: string;\n items: LayerStackItem[];\n /** Kinds the user can add via the header palette. */\n paletteKinds: string[];\n onAdd: (kind: string) => void;\n onRemove: (id: number | string) => void;\n onReorder: (orderedIds: Array<number | string>) => void;\n onPrimaryChange: (id: number | string, nextValue: string) => void;\n /** Render the body controls for each item. */\n renderBody: (item: LayerStackItem) => ReactNode;\n /** Hide the title + palette row (used when an outer wrap renders its\n * own head — see speech-balloons Tails panel). */\n hideHead?: boolean;\n}\n\n/** A drag-reorderable stack of expandable cards, with a palette in the header\n * for adding more. The body of each card is the caller's to render. */\nexport function LayerStack({\n title,\n items,\n paletteKinds,\n onAdd,\n onRemove,\n onReorder,\n onPrimaryChange,\n renderBody,\n hideHead,\n}: LayerStackProps) {\n const [expandedIds, setExpandedIds] = useState<Set<number | string>>(\n () => new Set(items.filter((i) => i.defaultExpanded !== false).map((i) => i.id)),\n );\n\n useEffect(() => {\n setExpandedIds((prev) => {\n let next: Set<number | string> | null = null;\n for (const item of items) {\n if (item.defaultExpanded === false) continue;\n if (prev.has(item.id)) continue;\n if (next === null) next = new Set(prev);\n next.add(item.id);\n }\n return next ?? prev;\n });\n }, [items]);\n\n const dragItems = items.map((it) => ({ id: String(it.id), label: it.kind }));\n const drag = useReorderDragList({\n items: dragItems,\n selectedIds: [],\n onReorder: (ids, targetIndex) => {\n dlog('layer-stack', 'onReorder', { ids, targetIndex });\n const orig = items.map((i) => i.id);\n const moving = new Set(ids);\n const remaining = orig.filter((id) => !moving.has(String(id)));\n const movedIds = items.map((i) => i.id).filter((id) => moving.has(String(id)));\n // weasel reports targetIndex against the original list (which includes\n // the dragged row). Each moved item that originally sat before\n // targetIndex needs to shift the insertion point left by one.\n const shift = orig.filter((id, idx) => moving.has(String(id)) && idx < targetIndex).length;\n const adjusted = targetIndex - shift;\n const out = [...remaining];\n out.splice(adjusted, 0, ...movedIds);\n onReorder(out);\n },\n });\n\n const toggleExpanded = (id: number | string) => {\n setExpandedIds((s) => {\n const n = new Set(s);\n if (n.has(id)) n.delete(id);\n else n.add(id);\n return n;\n });\n };\n\n return (\n <div className=\"lk-layer-stack\">\n {!hideHead && (\n <div className=\"lk-layer-stack__head\">\n <h2 className=\"lk-layer-stack__title\">{title}</h2>\n <div className=\"lk-layer-stack__palette\">\n {paletteKinds.map((k) => (\n <button\n key={k}\n type=\"button\"\n className=\"lk-layer-stack__add\"\n onClick={() => {\n dlog('layer-stack', 'onAdd', { kind: k });\n onAdd(k);\n }}\n aria-label={`Add ${k}`}\n >\n {k}\n </button>\n ))}\n </div>\n </div>\n )}\n <div\n className=\"lk-layer-stack__list\"\n ref={drag.containerProps.ref as RefCallback<HTMLDivElement>}\n onPointerMove={drag.containerProps.onPointerMove}\n onPointerUp={drag.containerProps.onPointerUp}\n onPointerCancel={drag.containerProps.onPointerCancel}\n >\n {items.map((item, i) => {\n const expanded = expandedIds.has(item.id);\n const draggedId = drag.state.draggedIds?.[0];\n const isDragging = draggedId === String(item.id);\n const showHintBefore = drag.state.targetIndex === i && draggedId !== String(item.id);\n const showHintAfter = drag.state.targetIndex === items.length && i === items.length - 1;\n const cardCls = [\n 'lk-layer-card',\n expanded ? 'is-expanded' : 'is-collapsed',\n isDragging ? 'is-dragging' : '',\n item.accent ? 'has-accent' : '',\n ]\n .filter(Boolean)\n .join(' ');\n const cardStyle = item.accent\n ? ({ '--lk-layer-card-accent': item.accent } as CSSProperties)\n : undefined;\n const { onPointerDown } = drag.rowProps(String(item.id), i);\n return (\n <div key={item.id} className=\"lk-layer-card-wrap\">\n {showHintBefore && <div className=\"lk-layer-stack__drop-hint\" />}\n <div className={cardCls} data-testid={`lk-layer-card-${item.id}`} style={cardStyle}>\n <div className=\"lk-layer-card__head\">\n <button\n type=\"button\"\n className=\"lk-layer-card__handle\"\n aria-label={`Drag to reorder layer ${item.id}`}\n onPointerDown={onPointerDown}\n onClick={() => toggleExpanded(item.id)}\n >\n {item.badge ?? <DragHandleGlyph />}\n </button>\n {item.primaryValue !== undefined && item.primaryOptions ? (\n <select\n className=\"lk-layer-card__primary\"\n value={item.primaryValue}\n aria-label={`Primary select for layer ${item.id}`}\n onChange={(e) => onPrimaryChange(item.id, e.target.value)}\n onClick={(e) => e.stopPropagation()}\n >\n {item.primaryOptions.map((o) => (\n <option key={o} value={o}>\n {o}\n </option>\n ))}\n </select>\n ) : (\n <span className=\"lk-layer-card__kind\">{item.kind}</span>\n )}\n <button\n type=\"button\"\n className=\"lk-layer-card__remove\"\n aria-label=\"Remove layer\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove(item.id);\n }}\n >\n ✕\n </button>\n </div>\n {expanded && <div className=\"lk-layer-card__body\">{renderBody(item)}</div>}\n </div>\n {showHintAfter && <div className=\"lk-layer-stack__drop-hint\" />}\n </div>\n );\n })}\n {items.length === 0 && (\n <div className=\"lk-layer-stack__empty\">No layers — add one above.</div>\n )}\n </div>\n </div>\n );\n}\n\nfunction DragHandleGlyph() {\n return (\n <svg width=\"12\" height=\"16\" viewBox=\"0 0 8 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"2\" cy=\"3\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"3\" r=\"1.1\" />\n <circle cx=\"2\" cy=\"8\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"8\" r=\"1.1\" />\n <circle cx=\"2\" cy=\"13\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"13\" r=\"1.1\" />\n </svg>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/ui/layers/LayerStack.tsx"],"names":[],"mappings":"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","file":"chunk-G5TJVQQT.js","sourcesContent":["import { type CSSProperties, type ReactNode, type RefCallback, useEffect, useState } from 'react';\nimport { dlog, useReorderDragList } from '../../passthrough/weasel-ui';\n\n/** One card in a layer stack: its identity, the label shown when collapsed,\n * and the optional select hoisted into its header. */\nexport interface LayerStackItem {\n /** Stable id used for keys, onRemove, onReorder. Numeric to match\n * common id-from-nextId conventions; string ids also work. */\n id: number | string;\n /** Short kind label rendered in the header when no primary select\n * is hoisted (e.g. \"shadow\", \"stroke\"). */\n kind: string;\n /** When present, hoist this select into the card header so the user\n * can switch mode/shape without expanding. */\n primaryValue?: string;\n primaryOptions?: string[];\n /** Accent CSS color used as the left border / index-badge fill. */\n accent?: string;\n /** Optional badge text rendered before the primary control\n * (e.g. tail index \"1\", \"2\", \"3\"). When omitted a drag handle\n * glyph renders in its place. */\n badge?: string;\n /** Initial expanded state. Defaults to true for newly-added items. */\n defaultExpanded?: boolean;\n}\n\n/** Props for `<LayerStack>`. */\nexport interface LayerStackProps {\n title: string;\n items: LayerStackItem[];\n /** Kinds the user can add via the header palette. */\n paletteKinds: string[];\n onAdd: (kind: string) => void;\n onRemove: (id: number | string) => void;\n onReorder: (orderedIds: Array<number | string>) => void;\n onPrimaryChange: (id: number | string, nextValue: string) => void;\n /** Render the body controls for each item. */\n renderBody: (item: LayerStackItem) => ReactNode;\n /** Hide the title + palette row (used when an outer wrap renders its\n * own head — see speech-balloons Tails panel). */\n hideHead?: boolean;\n}\n\n/** A drag-reorderable stack of expandable cards, with a palette in the header\n * for adding more. The body of each card is the caller's to render. */\nexport function LayerStack({\n title,\n items,\n paletteKinds,\n onAdd,\n onRemove,\n onReorder,\n onPrimaryChange,\n renderBody,\n hideHead,\n}: LayerStackProps) {\n const [expandedIds, setExpandedIds] = useState<Set<number | string>>(\n () => new Set(items.filter((i) => i.defaultExpanded !== false).map((i) => i.id)),\n );\n\n useEffect(() => {\n setExpandedIds((prev) => {\n let next: Set<number | string> | null = null;\n for (const item of items) {\n if (item.defaultExpanded === false) continue;\n if (prev.has(item.id)) continue;\n if (next === null) next = new Set(prev);\n next.add(item.id);\n }\n return next ?? prev;\n });\n }, [items]);\n\n const dragItems = items.map((it) => ({ id: String(it.id), label: it.kind }));\n const drag = useReorderDragList({\n items: dragItems,\n selectedIds: [],\n onReorder: (ids, targetIndex) => {\n dlog('layer-stack', 'onReorder', { ids, targetIndex });\n const orig = items.map((i) => i.id);\n const moving = new Set(ids);\n const remaining = orig.filter((id) => !moving.has(String(id)));\n const movedIds = items.map((i) => i.id).filter((id) => moving.has(String(id)));\n // weasel reports targetIndex against the original list (which includes\n // the dragged row). Each moved item that originally sat before\n // targetIndex needs to shift the insertion point left by one.\n const shift = orig.filter((id, idx) => moving.has(String(id)) && idx < targetIndex).length;\n const adjusted = targetIndex - shift;\n const out = [...remaining];\n out.splice(adjusted, 0, ...movedIds);\n onReorder(out);\n },\n });\n\n const toggleExpanded = (id: number | string) => {\n setExpandedIds((s) => {\n const n = new Set(s);\n if (n.has(id)) n.delete(id);\n else n.add(id);\n return n;\n });\n };\n\n return (\n <div className=\"lk-layer-stack\">\n {!hideHead && (\n <div className=\"lk-layer-stack__head\">\n <h2 className=\"lk-layer-stack__title\">{title}</h2>\n <div className=\"lk-layer-stack__palette\">\n {paletteKinds.map((k) => (\n <button\n key={k}\n type=\"button\"\n className=\"lk-layer-stack__add\"\n onClick={() => {\n dlog('layer-stack', 'onAdd', { kind: k });\n onAdd(k);\n }}\n aria-label={`Add ${k}`}\n >\n {k}\n </button>\n ))}\n </div>\n </div>\n )}\n <div\n className=\"lk-layer-stack__list\"\n ref={drag.containerProps.ref as RefCallback<HTMLDivElement>}\n onPointerMove={drag.containerProps.onPointerMove}\n onPointerUp={drag.containerProps.onPointerUp}\n onPointerCancel={drag.containerProps.onPointerCancel}\n >\n {items.map((item, i) => {\n const expanded = expandedIds.has(item.id);\n const draggedId = drag.state.draggedIds?.[0];\n const isDragging = draggedId === String(item.id);\n const showHintBefore = drag.state.targetIndex === i && draggedId !== String(item.id);\n const showHintAfter = drag.state.targetIndex === items.length && i === items.length - 1;\n const cardCls = [\n 'lk-layer-card',\n expanded ? 'is-expanded' : 'is-collapsed',\n isDragging ? 'is-dragging' : '',\n item.accent ? 'has-accent' : '',\n ]\n .filter(Boolean)\n .join(' ');\n const cardStyle = item.accent\n ? ({ '--lk-layer-card-accent': item.accent } as CSSProperties)\n : undefined;\n const { onPointerDown } = drag.rowProps(String(item.id), i);\n return (\n <div key={item.id} className=\"lk-layer-card-wrap\">\n {showHintBefore && <div className=\"lk-layer-stack__drop-hint\" />}\n <div className={cardCls} data-testid={`lk-layer-card-${item.id}`} style={cardStyle}>\n <div className=\"lk-layer-card__head\">\n <button\n type=\"button\"\n className=\"lk-layer-card__handle\"\n aria-label={`Drag to reorder layer ${item.id}`}\n onPointerDown={onPointerDown}\n onClick={() => toggleExpanded(item.id)}\n >\n {item.badge ?? <DragHandleGlyph />}\n </button>\n {item.primaryValue !== undefined && item.primaryOptions ? (\n <select\n className=\"lk-layer-card__primary\"\n value={item.primaryValue}\n aria-label={`Primary select for layer ${item.id}`}\n onChange={(e) => onPrimaryChange(item.id, e.target.value)}\n onClick={(e) => e.stopPropagation()}\n >\n {item.primaryOptions.map((o) => (\n <option key={o} value={o}>\n {o}\n </option>\n ))}\n </select>\n ) : (\n <span className=\"lk-layer-card__kind\">{item.kind}</span>\n )}\n <button\n type=\"button\"\n className=\"lk-layer-card__remove\"\n aria-label=\"Remove layer\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove(item.id);\n }}\n >\n ✕\n </button>\n </div>\n {expanded && <div className=\"lk-layer-card__body\">{renderBody(item)}</div>}\n </div>\n {showHintAfter && <div className=\"lk-layer-stack__drop-hint\" />}\n </div>\n );\n })}\n {items.length === 0 && (\n <div className=\"lk-layer-stack__empty\">No layers — add one above.</div>\n )}\n </div>\n </div>\n );\n}\n\nfunction DragHandleGlyph() {\n return (\n <svg width=\"12\" height=\"16\" viewBox=\"0 0 8 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"2\" cy=\"3\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"3\" r=\"1.1\" />\n <circle cx=\"2\" cy=\"8\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"8\" r=\"1.1\" />\n <circle cx=\"2\" cy=\"13\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"13\" r=\"1.1\" />\n </svg>\n );\n}\n"]}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/job/useJob.ts
|
|
4
|
+
var IDLE = { status: "idle", done: 0, total: null, failures: [], error: null };
|
|
5
|
+
function sameKey(a, b) {
|
|
6
|
+
if (a === null || b === null) return a === b;
|
|
7
|
+
return a.length === b.length && a.every((v, i) => Object.is(v, b[i]));
|
|
8
|
+
}
|
|
9
|
+
function useJob({
|
|
10
|
+
capability,
|
|
11
|
+
config,
|
|
12
|
+
state,
|
|
13
|
+
setState
|
|
14
|
+
}) {
|
|
15
|
+
const [progress, setProgress] = useState(IDLE);
|
|
16
|
+
const token = useRef(0);
|
|
17
|
+
const abort = useRef(null);
|
|
18
|
+
const capRef = useRef(capability);
|
|
19
|
+
capRef.current = capability;
|
|
20
|
+
const configRef = useRef(config);
|
|
21
|
+
configRef.current = config;
|
|
22
|
+
const stateRef = useRef(state);
|
|
23
|
+
stateRef.current = state;
|
|
24
|
+
const setStateRef = useRef(setState);
|
|
25
|
+
setStateRef.current = setState;
|
|
26
|
+
const cancel = useCallback(() => {
|
|
27
|
+
token.current += 1;
|
|
28
|
+
abort.current?.abort();
|
|
29
|
+
abort.current = null;
|
|
30
|
+
setProgress(IDLE);
|
|
31
|
+
}, []);
|
|
32
|
+
const start = useCallback(() => {
|
|
33
|
+
token.current += 1;
|
|
34
|
+
const mine = token.current;
|
|
35
|
+
abort.current?.abort();
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
abort.current = controller;
|
|
38
|
+
setProgress({ ...IDLE, status: "running" });
|
|
39
|
+
void (async () => {
|
|
40
|
+
try {
|
|
41
|
+
const iterable = capRef.current.run({
|
|
42
|
+
config: configRef.current,
|
|
43
|
+
state: stateRef.current,
|
|
44
|
+
signal: controller.signal
|
|
45
|
+
});
|
|
46
|
+
for await (const event of iterable) {
|
|
47
|
+
if (token.current !== mine) return;
|
|
48
|
+
if (event.kind === "total") {
|
|
49
|
+
setProgress((p) => ({ ...p, total: event.total }));
|
|
50
|
+
} else if (event.kind === "failed") {
|
|
51
|
+
setProgress((p) => ({
|
|
52
|
+
...p,
|
|
53
|
+
failures: [...p.failures, { index: event.index, error: event.error }]
|
|
54
|
+
}));
|
|
55
|
+
} else {
|
|
56
|
+
const fold = capRef.current.onItem;
|
|
57
|
+
const item = event.item;
|
|
58
|
+
setStateRef.current((prev) => fold(item, prev));
|
|
59
|
+
setProgress((p) => ({ ...p, done: p.done + 1 }));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (token.current !== mine) return;
|
|
63
|
+
setProgress((p) => ({ ...p, status: "done" }));
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (token.current !== mine) return;
|
|
66
|
+
setProgress((p) => ({
|
|
67
|
+
...p,
|
|
68
|
+
status: "error",
|
|
69
|
+
error: err instanceof Error ? err.message : String(err)
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
}, []);
|
|
74
|
+
const lastKey = useRef(null);
|
|
75
|
+
const started = useRef(false);
|
|
76
|
+
const auto = capability.auto === true;
|
|
77
|
+
const key = capability.key ? capability.key(config, state) : null;
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!auto) return;
|
|
80
|
+
if (started.current && sameKey(lastKey.current, key)) return;
|
|
81
|
+
lastKey.current = key;
|
|
82
|
+
started.current = true;
|
|
83
|
+
start();
|
|
84
|
+
}, [auto, key, start]);
|
|
85
|
+
useEffect(
|
|
86
|
+
() => () => {
|
|
87
|
+
token.current += 1;
|
|
88
|
+
abort.current?.abort();
|
|
89
|
+
abort.current = null;
|
|
90
|
+
},
|
|
91
|
+
[]
|
|
92
|
+
);
|
|
93
|
+
return {
|
|
94
|
+
status: progress.status,
|
|
95
|
+
done: progress.done,
|
|
96
|
+
total: progress.total,
|
|
97
|
+
failures: progress.failures,
|
|
98
|
+
error: progress.error,
|
|
99
|
+
start,
|
|
100
|
+
cancel
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { useJob };
|
|
105
|
+
//# sourceMappingURL=chunk-LN6JDUGB.js.map
|
|
106
|
+
//# sourceMappingURL=chunk-LN6JDUGB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/job/useJob.ts"],"names":[],"mappings":"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","file":"chunk-LN6JDUGB.js","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport type { JobCapability, JobFailure, JobHandle, JobStatus } from './types';\n\nexport interface UseJobOptions<TS, TC, TItem> {\n capability: JobCapability<TS, TC, TItem>;\n config: TC;\n state: TS;\n setState: (next: TS | ((prev: TS) => TS)) => void;\n}\n\ninterface Progress {\n status: JobStatus;\n done: number;\n total: number | null;\n failures: JobFailure[];\n error: string | null;\n}\n\nconst IDLE: Progress = { status: 'idle', done: 0, total: null, failures: [], error: null };\n\nfunction sameKey(a: readonly unknown[] | null, b: readonly unknown[] | null): boolean {\n if (a === null || b === null) return a === b;\n return a.length === b.length && a.every((v, i) => Object.is(v, b[i]));\n}\n\nexport function useJob<TS, TC, TItem>({\n capability,\n config,\n state,\n setState,\n}: UseJobOptions<TS, TC, TItem>): JobHandle {\n const [progress, setProgress] = useState<Progress>(IDLE);\n\n // A run is identified by a token. Every result checks its token before touching\n // state, so a superseded run finishes harmlessly instead of racing the winner.\n const token = useRef(0);\n const abort = useRef<AbortController | null>(null);\n\n // Read through refs: `run` is called once per run and must see the values as of\n // that moment rather than re-subscribing on every render.\n const capRef = useRef(capability);\n capRef.current = capability;\n const configRef = useRef(config);\n configRef.current = config;\n const stateRef = useRef(state);\n stateRef.current = state;\n const setStateRef = useRef(setState);\n setStateRef.current = setState;\n\n const cancel = useCallback(() => {\n token.current += 1;\n abort.current?.abort();\n abort.current = null;\n setProgress(IDLE);\n }, []);\n\n const start = useCallback(() => {\n token.current += 1;\n const mine = token.current;\n abort.current?.abort();\n const controller = new AbortController();\n abort.current = controller;\n setProgress({ ...IDLE, status: 'running' });\n\n void (async () => {\n try {\n const iterable = capRef.current.run({\n config: configRef.current,\n state: stateRef.current,\n signal: controller.signal,\n });\n for await (const event of iterable) {\n if (token.current !== mine) return;\n if (event.kind === 'total') {\n setProgress((p) => ({ ...p, total: event.total }));\n } else if (event.kind === 'failed') {\n setProgress((p) => ({\n ...p,\n failures: [...p.failures, { index: event.index, error: event.error }],\n }));\n } else {\n const fold = capRef.current.onItem;\n const item = event.item;\n setStateRef.current((prev) => fold(item, prev));\n setProgress((p) => ({ ...p, done: p.done + 1 }));\n }\n }\n if (token.current !== mine) return;\n setProgress((p) => ({ ...p, status: 'done' }));\n } catch (err) {\n if (token.current !== mine) return;\n setProgress((p) => ({\n ...p,\n status: 'error',\n error: err instanceof Error ? err.message : String(err),\n }));\n }\n })();\n }, []);\n\n // Re-run when the declared key changes. `auto` covers the first mount too.\n const lastKey = useRef<readonly unknown[] | null>(null);\n const started = useRef(false);\n const auto = capability.auto === true;\n const key = capability.key ? capability.key(config, state) : null;\n // `key` is compared element-wise inside; depending on the array identity here\n // would restart the job on every render.\n useEffect(() => {\n if (!auto) return;\n if (started.current && sameKey(lastKey.current, key)) return;\n lastKey.current = key;\n started.current = true;\n start();\n }, [auto, key, start]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(\n () => () => {\n token.current += 1;\n abort.current?.abort();\n abort.current = null;\n },\n [],\n );\n\n return {\n status: progress.status,\n done: progress.done,\n total: progress.total,\n failures: progress.failures,\n error: progress.error,\n start,\n cancel,\n };\n}\n"]}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { createContext, useContext, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/surface/composeRects.ts
|
|
4
|
+
function composeRects(container, tiles) {
|
|
5
|
+
const out = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const [id, tile] of tiles) {
|
|
7
|
+
out.set(id, {
|
|
8
|
+
x: tile.left - container.left,
|
|
9
|
+
y: tile.top - container.top,
|
|
10
|
+
w: tile.width,
|
|
11
|
+
h: tile.height
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
function rectsEqual(a, b) {
|
|
17
|
+
return a !== void 0 && a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/surface/deviceRect.ts
|
|
21
|
+
function toDeviceRect(rect, surfaceHeight, dpr) {
|
|
22
|
+
const snap = (v) => Math.round(v * dpr) / dpr;
|
|
23
|
+
const x = snap(rect.x);
|
|
24
|
+
const y = snap(surfaceHeight - rect.y - rect.h);
|
|
25
|
+
return {
|
|
26
|
+
x,
|
|
27
|
+
y,
|
|
28
|
+
w: snap(rect.x + rect.w) - x,
|
|
29
|
+
h: snap(surfaceHeight - rect.y) - y
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
var SurfaceContext = createContext(null);
|
|
33
|
+
function useSurfaceOptional() {
|
|
34
|
+
return useContext(SurfaceContext);
|
|
35
|
+
}
|
|
36
|
+
function useSurface() {
|
|
37
|
+
const surface = useContext(SurfaceContext);
|
|
38
|
+
if (!surface) throw new Error("[labkit] useSurface requires a surface owner above it");
|
|
39
|
+
return surface;
|
|
40
|
+
}
|
|
41
|
+
function useSurfaceTile(id) {
|
|
42
|
+
const surface = useSurfaceOptional();
|
|
43
|
+
return useCallback(
|
|
44
|
+
(el) => {
|
|
45
|
+
surface?.registerTile(id, el);
|
|
46
|
+
},
|
|
47
|
+
[surface, id]
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
function useTiledSurface({ onFrame }) {
|
|
51
|
+
const container = useRef(null);
|
|
52
|
+
const tiles = useRef(/* @__PURE__ */ new Map());
|
|
53
|
+
const rects = useRef(/* @__PURE__ */ new Map());
|
|
54
|
+
const dirty = useRef(/* @__PURE__ */ new Set());
|
|
55
|
+
const needsMeasure = useRef(true);
|
|
56
|
+
const raf = useRef(0);
|
|
57
|
+
const observer = useRef(null);
|
|
58
|
+
const lastDpr = useRef(0);
|
|
59
|
+
const onFrameRef = useRef(onFrame);
|
|
60
|
+
onFrameRef.current = onFrame;
|
|
61
|
+
const measure = useCallback(() => {
|
|
62
|
+
const el = container.current;
|
|
63
|
+
if (!el) return false;
|
|
64
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
65
|
+
for (const [id, tile] of tiles.current) boxes.set(id, tile.getBoundingClientRect());
|
|
66
|
+
const next = composeRects(el.getBoundingClientRect(), boxes);
|
|
67
|
+
let changed = next.size !== rects.current.size;
|
|
68
|
+
for (const [id, rect] of next) {
|
|
69
|
+
if (!rectsEqual(rects.current.get(id), rect)) changed = true;
|
|
70
|
+
}
|
|
71
|
+
rects.current = next;
|
|
72
|
+
return changed;
|
|
73
|
+
}, []);
|
|
74
|
+
const schedule = useCallback(() => {
|
|
75
|
+
if (raf.current) return;
|
|
76
|
+
raf.current = requestAnimationFrame(() => {
|
|
77
|
+
raf.current = 0;
|
|
78
|
+
const el = container.current;
|
|
79
|
+
if (!el) {
|
|
80
|
+
dirty.current.clear();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (needsMeasure.current) {
|
|
84
|
+
needsMeasure.current = false;
|
|
85
|
+
if (measure()) for (const id of rects.current.keys()) dirty.current.add(id);
|
|
86
|
+
}
|
|
87
|
+
const dpr = globalThis.devicePixelRatio ?? 1;
|
|
88
|
+
if (dpr !== lastDpr.current) {
|
|
89
|
+
lastDpr.current = dpr;
|
|
90
|
+
for (const id of rects.current.keys()) dirty.current.add(id);
|
|
91
|
+
}
|
|
92
|
+
if (dirty.current.size === 0) return;
|
|
93
|
+
const box = el.getBoundingClientRect();
|
|
94
|
+
onFrameRef.current({
|
|
95
|
+
dirty: new Set(dirty.current),
|
|
96
|
+
rects: new Map(rects.current),
|
|
97
|
+
dpr,
|
|
98
|
+
size: { width: box.width, height: box.height }
|
|
99
|
+
});
|
|
100
|
+
dirty.current.clear();
|
|
101
|
+
});
|
|
102
|
+
}, [measure]);
|
|
103
|
+
const invalidate = useCallback(
|
|
104
|
+
(id) => {
|
|
105
|
+
dirty.current.add(id);
|
|
106
|
+
schedule();
|
|
107
|
+
},
|
|
108
|
+
[schedule]
|
|
109
|
+
);
|
|
110
|
+
const invalidateAll = useCallback(() => {
|
|
111
|
+
for (const id of tiles.current.keys()) dirty.current.add(id);
|
|
112
|
+
schedule();
|
|
113
|
+
}, [schedule]);
|
|
114
|
+
const invalidateRects = useCallback(() => {
|
|
115
|
+
needsMeasure.current = true;
|
|
116
|
+
schedule();
|
|
117
|
+
}, [schedule]);
|
|
118
|
+
const registerTile = useCallback(
|
|
119
|
+
(id, el) => {
|
|
120
|
+
const known = tiles.current.get(id);
|
|
121
|
+
if (known === el) return;
|
|
122
|
+
if (known) observer.current?.unobserve(known);
|
|
123
|
+
if (el) {
|
|
124
|
+
tiles.current.set(id, el);
|
|
125
|
+
observer.current?.observe(el);
|
|
126
|
+
} else {
|
|
127
|
+
tiles.current.delete(id);
|
|
128
|
+
rects.current.delete(id);
|
|
129
|
+
dirty.current.delete(id);
|
|
130
|
+
}
|
|
131
|
+
needsMeasure.current = true;
|
|
132
|
+
schedule();
|
|
133
|
+
},
|
|
134
|
+
[schedule]
|
|
135
|
+
);
|
|
136
|
+
const containerRef = useCallback(
|
|
137
|
+
(el) => {
|
|
138
|
+
if (container.current === el) return;
|
|
139
|
+
if (container.current) observer.current?.unobserve(container.current);
|
|
140
|
+
container.current = el;
|
|
141
|
+
if (el) observer.current?.observe(el);
|
|
142
|
+
needsMeasure.current = true;
|
|
143
|
+
schedule();
|
|
144
|
+
},
|
|
145
|
+
[schedule]
|
|
146
|
+
);
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const ro = new ResizeObserver(() => {
|
|
149
|
+
needsMeasure.current = true;
|
|
150
|
+
schedule();
|
|
151
|
+
});
|
|
152
|
+
observer.current = ro;
|
|
153
|
+
if (container.current) ro.observe(container.current);
|
|
154
|
+
for (const el of tiles.current.values()) ro.observe(el);
|
|
155
|
+
return () => {
|
|
156
|
+
ro.disconnect();
|
|
157
|
+
observer.current = null;
|
|
158
|
+
if (raf.current) cancelAnimationFrame(raf.current);
|
|
159
|
+
raf.current = 0;
|
|
160
|
+
};
|
|
161
|
+
}, [schedule]);
|
|
162
|
+
return { invalidate, invalidateAll, invalidateRects, registerTile, containerRef };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export { SurfaceContext, composeRects, rectsEqual, toDeviceRect, useSurface, useSurfaceOptional, useSurfaceTile, useTiledSurface };
|
|
166
|
+
//# sourceMappingURL=chunk-THBG7FQZ.js.map
|
|
167
|
+
//# sourceMappingURL=chunk-THBG7FQZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/surface/composeRects.ts","../src/surface/deviceRect.ts","../src/surface/SurfaceContext.ts","../src/surface/useSurfaceTile.ts","../src/surface/useTiledSurface.ts"],"names":["useCallback"],"mappings":"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","file":"chunk-THBG7FQZ.js","sourcesContent":["import type { Box, Rect } from './rect';\n\n/** Every tile's box expressed against the container's origin. */\nexport function composeRects(container: Box, tiles: ReadonlyMap<string, Box>): Map<string, Rect> {\n const out = new Map<string, Rect>();\n for (const [id, tile] of tiles) {\n out.set(id, {\n x: tile.left - container.left,\n y: tile.top - container.top,\n w: tile.width,\n h: tile.height,\n });\n }\n return out;\n}\n\nexport function rectsEqual(a: Rect | undefined, b: Rect): boolean {\n return a !== undefined && a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;\n}\n","import type { Rect } from './rect';\n\n/**\n * A DOM rect as a GL viewport rect: origin at the bottom-left, every edge snapped\n * to the device-pixel grid. Still CSS pixels, because three.js applies its own\n * pixel ratio — snapping here is what stops a tile and its neighbour rounding\n * apart and leaving a hairline column between them.\n */\nexport function toDeviceRect(rect: Rect, surfaceHeight: number, dpr: number): Rect {\n const snap = (v: number) => Math.round(v * dpr) / dpr;\n const x = snap(rect.x);\n const y = snap(surfaceHeight - rect.y - rect.h);\n return {\n x,\n y,\n w: snap(rect.x + rect.w) - x,\n h: snap(surfaceHeight - rect.y) - y,\n };\n}\n","import { createContext } from 'react';\nimport type { SurfaceHandle } from './useTiledSurface';\n\n/** Null when no surface owner is above — a lab with no shared surface at all. */\nexport const SurfaceContext = createContext<SurfaceHandle | null>(null);\n","import { useCallback, useContext } from 'react';\nimport { SurfaceContext } from './SurfaceContext';\nimport type { SurfaceHandle } from './useTiledSurface';\n\n/** The surface above, or null. Use this where a surface is genuinely optional. */\nexport function useSurfaceOptional(): SurfaceHandle | null {\n return useContext(SurfaceContext);\n}\n\n/** The surface above. Throws where a caller cannot work without one. */\nexport function useSurface(): SurfaceHandle {\n const surface = useContext(SurfaceContext);\n if (!surface) throw new Error('[labkit] useSurface requires a surface owner above it');\n return surface;\n}\n\n/**\n * A ref callback that publishes this element's rect to the surface under `id`.\n *\n * Attach it to whichever element the surface should draw into — that is not\n * necessarily the trial's own element, since a trial may hold a drawn pane beside\n * an undrawn one, or none at all.\n */\nexport function useSurfaceTile(id: string): (el: HTMLElement | null) => void {\n const surface = useSurfaceOptional();\n return useCallback(\n (el: HTMLElement | null) => {\n surface?.registerTile(id, el);\n },\n [surface, id],\n );\n}\n","import { useCallback, useEffect, useRef } from 'react';\nimport { composeRects, rectsEqual } from './composeRects';\nimport type { Box, Rect } from './rect';\n\n/** What a surface owner is handed once per animation frame. `rects` carries every\n * tile, not only the dirty ones: a scissored draw has to know where it is drawing\n * relative to a surface that may have resized under it. */\nexport interface SurfaceFrame {\n dirty: ReadonlySet<string>;\n rects: ReadonlyMap<string, Rect>;\n dpr: number;\n size: { width: number; height: number };\n}\n\n/** The invalidators and the two ref callbacks that publish geometry. */\nexport interface SurfaceHandle {\n /** Mark one tile for redraw. */\n invalidate: (id: string) => void;\n /** Mark every tile — what a resize or a tile-set change means. */\n invalidateAll: () => void;\n /** Re-measure before the next frame. The escape hatch for a host that knows it\n * moved something a ResizeObserver cannot see. */\n invalidateRects: () => void;\n registerTile: (id: string, el: HTMLElement | null) => void;\n containerRef: (el: HTMLElement | null) => void;\n}\n\nexport interface UseTiledSurfaceOptions {\n onFrame: (frame: SurfaceFrame) => void;\n}\n\nexport function useTiledSurface({ onFrame }: UseTiledSurfaceOptions): SurfaceHandle {\n const container = useRef<HTMLElement | null>(null);\n const tiles = useRef(new Map<string, HTMLElement>());\n const rects = useRef(new Map<string, Rect>());\n const dirty = useRef(new Set<string>());\n const needsMeasure = useRef(true);\n const raf = useRef(0);\n const observer = useRef<ResizeObserver | null>(null);\n const lastDpr = useRef(0);\n\n // Held in a ref so a caller passing an inline closure does not re-create every\n // callback below on each render.\n const onFrameRef = useRef(onFrame);\n onFrameRef.current = onFrame;\n\n const measure = useCallback((): boolean => {\n const el = container.current;\n if (!el) return false;\n const boxes = new Map<string, Box>();\n for (const [id, tile] of tiles.current) boxes.set(id, tile.getBoundingClientRect());\n const next = composeRects(el.getBoundingClientRect(), boxes);\n let changed = next.size !== rects.current.size;\n for (const [id, rect] of next) {\n if (!rectsEqual(rects.current.get(id), rect)) changed = true;\n }\n rects.current = next;\n return changed;\n }, []);\n\n const schedule = useCallback(() => {\n if (raf.current) return;\n raf.current = requestAnimationFrame(() => {\n raf.current = 0;\n const el = container.current;\n if (!el) {\n dirty.current.clear();\n return;\n }\n if (needsMeasure.current) {\n needsMeasure.current = false;\n if (measure()) for (const id of rects.current.keys()) dirty.current.add(id);\n }\n const dpr = globalThis.devicePixelRatio ?? 1;\n if (dpr !== lastDpr.current) {\n lastDpr.current = dpr;\n for (const id of rects.current.keys()) dirty.current.add(id);\n }\n if (dirty.current.size === 0) return;\n const box = el.getBoundingClientRect();\n onFrameRef.current({\n dirty: new Set(dirty.current),\n rects: new Map(rects.current),\n dpr,\n size: { width: box.width, height: box.height },\n });\n dirty.current.clear();\n });\n }, [measure]);\n\n const invalidate = useCallback(\n (id: string) => {\n dirty.current.add(id);\n schedule();\n },\n [schedule],\n );\n\n const invalidateAll = useCallback(() => {\n for (const id of tiles.current.keys()) dirty.current.add(id);\n schedule();\n }, [schedule]);\n\n const invalidateRects = useCallback(() => {\n needsMeasure.current = true;\n schedule();\n }, [schedule]);\n\n const registerTile = useCallback(\n (id: string, el: HTMLElement | null) => {\n const known = tiles.current.get(id);\n if (known === el) return;\n if (known) observer.current?.unobserve(known);\n if (el) {\n tiles.current.set(id, el);\n observer.current?.observe(el);\n } else {\n tiles.current.delete(id);\n rects.current.delete(id);\n dirty.current.delete(id);\n }\n needsMeasure.current = true;\n schedule();\n },\n [schedule],\n );\n\n const containerRef = useCallback(\n (el: HTMLElement | null) => {\n if (container.current === el) return;\n if (container.current) observer.current?.unobserve(container.current);\n container.current = el;\n if (el) observer.current?.observe(el);\n needsMeasure.current = true;\n schedule();\n },\n [schedule],\n );\n\n useEffect(() => {\n const ro = new ResizeObserver(() => {\n needsMeasure.current = true;\n schedule();\n });\n observer.current = ro;\n if (container.current) ro.observe(container.current);\n for (const el of tiles.current.values()) ro.observe(el);\n return () => {\n ro.disconnect();\n observer.current = null;\n if (raf.current) cancelAnimationFrame(raf.current);\n raf.current = 0;\n };\n }, [schedule]);\n\n return { invalidate, invalidateAll, invalidateRects, registerTile, containerRef };\n}\n"]}
|
|
@@ -293,6 +293,7 @@ function createLabStore(options) {
|
|
|
293
293
|
trials: s.trials.map((w) => {
|
|
294
294
|
if (w.id !== id) return w;
|
|
295
295
|
const nextState = typeof next === "function" ? next(w.state) : next;
|
|
296
|
+
if (Object.is(nextState, w.state)) return w;
|
|
296
297
|
return { ...w, state: nextState };
|
|
297
298
|
})
|
|
298
299
|
}));
|
|
@@ -312,7 +313,9 @@ function createLabStore(options) {
|
|
|
312
313
|
},
|
|
313
314
|
updateTrialView: (id, view) => {
|
|
314
315
|
set((s) => ({
|
|
315
|
-
trials: s.trials.map(
|
|
316
|
+
trials: s.trials.map(
|
|
317
|
+
(w) => w.id === id && !Object.is(view, w.view) ? { ...w, view } : w
|
|
318
|
+
)
|
|
316
319
|
}));
|
|
317
320
|
scheduleFlush();
|
|
318
321
|
},
|
|
@@ -484,5 +487,5 @@ function useTrialState() {
|
|
|
484
487
|
}
|
|
485
488
|
|
|
486
489
|
export { CURRENT_DOCUMENT_VERSION, LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, createLabStore, createMemoryAdapter, decodeUrlHash, deserializeTrials, emptyUndoStack, encodeUrlHash, labDocumentKey, labStorageKey, localStorageAdapter, noneAdapter, quarantineKey, serializeTrials, sessionStorageAdapter, urlHashAdapter, useLabStore, useTrialId, useTrialState };
|
|
487
|
-
//# sourceMappingURL=chunk-
|
|
488
|
-
//# sourceMappingURL=chunk-
|
|
490
|
+
//# sourceMappingURL=chunk-VUU5UXHE.js.map
|
|
491
|
+
//# sourceMappingURL=chunk-VUU5UXHE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/state/helpers.ts","../src/state/adapters.ts","../src/state/context.tsx","../src/state/document.ts","../src/state/store.ts","../src/state/useTrialState.ts"],"names":["useContext","useStore"],"mappings":"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","file":"chunk-VUU5UXHE.js","sourcesContent":["import type { InstrumentSerializers, SerializedTrial, TrialRecord, UndoStack } from './types';\n\n/** The storage key a lab writes one of its buckets under. Namespaced by\n * `storageKey` so two labs sharing an origin do not collide. */\nexport function labStorageKey(\n storageKey: string,\n bucket: 'workspaces' | 'saves' | 'theme' | 'layout',\n): string {\n return `lk:${storageKey}:${bucket}`;\n}\n\n/** Encode a string for the URL fragment. */\nexport function encodeUrlHash(value: string): string {\n return btoa(encodeURIComponent(value));\n}\n\n/** Decode a URL fragment written by `encodeUrlHash`, or `null` if it is\n * malformed. */\nexport function decodeUrlHash(hash: string): string | null {\n if (!hash) return null;\n try {\n return decodeURIComponent(atob(hash));\n } catch {\n return null;\n }\n}\n\n/** A fresh, empty undo history. */\nexport function emptyUndoStack(): UndoStack {\n return { past: [], future: [] };\n}\n\n/** Serialize trials for storage, running each instrument's own serializer\n * over its state. Undo history is deliberately dropped — it does not survive\n * a reload. */\nexport function serializeTrials(\n trials: TrialRecord[],\n serializers: InstrumentSerializers,\n): SerializedTrial[] {\n return trials.map(({ undoStack: _undo, ...w }) => {\n const s = serializers[w.instrumentName];\n return { ...w, state: s?.serialize ? s.serialize(w.state) : w.state };\n });\n}\n\n/** Rebuild trials from storage, running each instrument's deserializer\n * over its state and starting each with an empty undo history. Returns an\n * empty list rather than throwing on malformed input. */\nexport function deserializeTrials(\n records: SerializedTrial[],\n deserializers: InstrumentSerializers,\n): TrialRecord[] {\n if (!Array.isArray(records)) {\n console.warn('[labkit] deserializeTrials: not an array, returning empty list');\n return [];\n }\n return records.map((r) => {\n const d = deserializers[r.instrumentName];\n return {\n ...r,\n state: d?.deserialize ? d.deserialize(r.state) : r.state,\n undoStack: emptyUndoStack(),\n };\n });\n}\n","import { decodeUrlHash, encodeUrlHash } from './helpers';\nimport type { StorageAdapter } from './types';\n\n/** Persist to `localStorage` — state survives a reload and a new tab. Falls\n * back to no-ops when storage is unavailable. */\nexport const localStorageAdapter: StorageAdapter = {\n read: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n },\n write: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch (e) {\n console.warn('[labkit] localStorage write failed:', e);\n }\n },\n delete: (key) => {\n try {\n localStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n },\n};\n\n/** Persist to `sessionStorage` — state survives a reload but not a new tab. */\nexport const sessionStorageAdapter: StorageAdapter = {\n read: (key) => {\n try {\n return sessionStorage.getItem(key);\n } catch {\n return null;\n }\n },\n write: (key, value) => {\n try {\n sessionStorage.setItem(key, value);\n } catch (e) {\n console.warn('[labkit] sessionStorage write failed:', e);\n }\n },\n delete: (key) => {\n try {\n sessionStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n },\n};\n\nconst URL_HASH_GUARD = typeof window !== 'undefined';\n\nfunction readHashMap(): Record<string, string> {\n if (!URL_HASH_GUARD) return {};\n const raw = decodeUrlHash(window.location.hash.replace(/^#/, ''));\n if (!raw) return {};\n try {\n return JSON.parse(raw) as Record<string, string>;\n } catch {\n return {};\n }\n}\n\nfunction writeHashMap(map: Record<string, string>): void {\n if (!URL_HASH_GUARD) return;\n const encoded = encodeUrlHash(JSON.stringify(map));\n window.history.replaceState(null, '', `#${encoded}`);\n}\n\n/** Persist into the URL fragment, so the page's link carries its state and\n * can be shared or bookmarked. */\nexport const urlHashAdapter: StorageAdapter = {\n read: (key) => readHashMap()[key] ?? null,\n write: (key, value) => {\n const map = readHashMap();\n map[key] = value;\n writeHashMap(map);\n },\n delete: (key) => {\n const map = readHashMap();\n delete map[key];\n writeHashMap(map);\n },\n};\n\n/** An in-memory store, discarded on reload. For tests, and for labs that\n * should start fresh every time. */\nexport function createMemoryAdapter(): StorageAdapter {\n const store = new Map<string, string>();\n return {\n read: (key) => store.get(key) ?? null,\n write: (key, value) => {\n store.set(key, value);\n },\n delete: (key) => {\n store.delete(key);\n },\n };\n}\n\n/** Persists nothing and reads back nothing. */\nexport const noneAdapter: StorageAdapter = {\n read: () => null,\n write: () => {},\n delete: () => {},\n};\n","import { createContext, type ReactElement, type ReactNode, useContext } from 'react';\nimport { useStore } from 'zustand/react';\nimport type { LabStore } from './store';\nimport type { LabStoreState } from './types';\n\ntype LabStoreCtx = { store: LabStore } | null;\n\n/** Context carrying the lab store. Prefer `useLabStore`; this is exported for\n * code that needs to read the context without subscribing. */\nexport const LabStoreContext = createContext<LabStoreCtx>(null);\n\n/** Provides a lab store to its subtree. */\nexport function LabStoreProvider({\n store,\n children,\n}: {\n store: LabStore;\n children: ReactNode;\n}): ReactElement {\n return <LabStoreContext.Provider value={{ store }}>{children}</LabStoreContext.Provider>;\n}\n\n/** Subscribe to the whole lab store. Throws outside a `<LabStoreProvider>`. */\nexport function useLabStore(): LabStoreState & ReturnType<LabStore['getState']> {\n const ctx = useContext(LabStoreContext);\n if (!ctx) throw new Error('[labkit] useLabStore must be used inside <LabStoreProvider>');\n return useStore(ctx.store);\n}\n\n/** Context carrying which trial the subtree belongs to. */\nexport const TrialIdContext = createContext<string | null>(null);\n\n/** Names the trial its subtree belongs to, so an instrument's hooks can\n * find their own record in the store without being passed an id. */\nexport function TrialIdProvider({\n trialId,\n children,\n}: {\n trialId: string;\n children: ReactNode;\n}): ReactElement {\n return <TrialIdContext.Provider value={trialId}>{children}</TrialIdContext.Provider>;\n}\n\n/** The id of the trial this component is inside. Throws outside a\n * `<TrialIdProvider>`. */\nexport function useTrialId(): string {\n const id = useContext(TrialIdContext);\n if (!id) throw new Error('[labkit] useTrialId must be used inside <TrialIdProvider>');\n return id;\n}\n","import { labStorageKey } from './helpers';\nimport type {\n LabDocument,\n LabMode,\n Migration,\n SavedSnapshot,\n SerializedTrial,\n StorageAdapter,\n} from './types';\n\n/** Bumped whenever the persisted shape changes; every bump needs a migration. */\nexport const CURRENT_DOCUMENT_VERSION = 2;\n\n/** The one key a lab persists under. The `:doc` suffix keeps it out of the\n * legacy bucket namespace, where a lab named `a:saves` would otherwise write\n * its document over lab `a`'s saves bucket. */\nexport function labDocumentKey(storageKey: string): string {\n return `lk:${storageKey}:doc`;\n}\n\n/** Where a document that failed to migrate is set aside, so a bad migration\n * loses state loudly rather than silently. */\nexport function quarantineKey(storageKey: string): string {\n return `lk:${storageKey}:quarantine`;\n}\n\n/** A fresh document at the current version. */\nexport function emptyDocument(mode: LabMode): LabDocument {\n return {\n version: CURRENT_DOCUMENT_VERSION,\n trials: [],\n saves: [],\n layout: {},\n mode,\n };\n}\n\n/** What a migration run produced: the migrated document, or why it refused. */\nexport type MigrationOutcome =\n | { ok: true; doc: Record<string, unknown>; migrated: boolean }\n | { ok: false; reason: 'future' | 'failed'; error?: unknown };\n\n/** Walk a document forward to `target`, one migration at a time. A version\n * above `target` is refused rather than parsed under a shape it may not\n * have. */\nexport function runMigrations(\n raw: Record<string, unknown>,\n migrations: Migration[],\n target: number,\n): MigrationOutcome {\n const from = typeof raw.version === 'number' ? raw.version : 0;\n if (from > target) return { ok: false, reason: 'future' };\n if (from === target) return { ok: true, doc: raw, migrated: false };\n\n let doc = raw;\n try {\n for (let v = from; v < target; v++) {\n const migration = migrations[v];\n if (!migration) throw new Error(`[labkit] no migration from version ${v}`);\n doc = migration(doc);\n }\n } catch (error) {\n return { ok: false, reason: 'failed', error };\n }\n return { ok: true, doc, migrated: true };\n}\n\nconst LEGACY_BUCKETS = ['workspaces', 'saves', 'layout', 'theme'] as const;\n\nfunction parseOr<T>(raw: string | null, fallback: T, bucket: string): T {\n if (raw === null) return fallback;\n try {\n return JSON.parse(raw) as T;\n } catch {\n console.warn(`[labkit] unparseable legacy bucket \"${bucket}\", dropping it`);\n return fallback;\n }\n}\n\n/** Assemble a version-0 document out of the four pre-document keys, or null\n * if none of them is present. `fallbackMode` is used when the theme key is\n * absent or holds something unrecognized; `interstellar` passes through\n * untouched for `migrateV0toV1` to coerce. */\nexport function readLegacyDocument(\n storage: StorageAdapter,\n storageKey: string,\n fallbackMode: LabMode,\n): Record<string, unknown> | null {\n const present = LEGACY_BUCKETS.some((b) => storage.read(labStorageKey(storageKey, b)) !== null);\n if (!present) return null;\n\n const rawMode = storage.read(labStorageKey(storageKey, 'theme'));\n const mode =\n rawMode === 'light' || rawMode === 'dark' || rawMode === 'auto' || rawMode === 'interstellar'\n ? rawMode\n : fallbackMode;\n\n return {\n version: 0,\n workspaces: parseOr(storage.read(labStorageKey(storageKey, 'workspaces')), [], 'workspaces'),\n saves: parseOr(storage.read(labStorageKey(storageKey, 'saves')), [], 'saves'),\n layout: parseOr(storage.read(labStorageKey(storageKey, 'layout')), {}, 'layout'),\n mode,\n };\n}\n\n/** Delete the four pre-document keys, but only once `expectedDocument` is\n * confirmed to be exactly what is stored under the document key — a\n * read-back, not a trust of the write that produced it. On any mismatch\n * (write failed, landed partially, or never happened) it deletes nothing,\n * warns, and returns `false` so the legacy buckets stay as a recoverable\n * copy. Deletion itself is read back too — `StorageAdapter.delete` is\n * optional, so an adapter without it leaves the keys in place — and `true`\n * is returned only once all four are actually gone. */\nexport function deleteLegacyKeys(\n storage: StorageAdapter,\n storageKey: string,\n expectedDocument: string,\n): boolean {\n const actual = storage.read(labDocumentKey(storageKey));\n if (actual !== expectedDocument) {\n console.warn(\n `[labkit] keeping legacy keys for \"${storageKey}\": could not confirm the migrated document was written`,\n );\n return false;\n }\n let allGone = true;\n for (const bucket of LEGACY_BUCKETS) {\n const key = labStorageKey(storageKey, bucket);\n storage.delete?.(key);\n if (storage.read(key) !== null) allGone = false;\n }\n return allGone;\n}\n\n/** Set `raw` aside under the quarantine key, reading it back to confirm it\n * landed. Returns `false` when it did not — a full disk is exactly when a\n * document goes unreadable — so the caller can leave the original alone\n * rather than overwrite the only copy of it. */\nexport function quarantineDocument(\n storage: StorageAdapter,\n storageKey: string,\n raw: string,\n): boolean {\n storage.write(quarantineKey(storageKey), raw);\n return storage.read(quarantineKey(storageKey)) === raw;\n}\n\n/** Fill in whatever a document is missing or holds the wrong shape of, so a\n * hydrated store never sees `undefined` where a section should be. Applied to\n * every document, migrated or already current. */\nexport function normalizeDocument(\n doc: Record<string, unknown>,\n fallbackMode: LabMode,\n): LabDocument {\n const mode =\n doc.mode === 'light' || doc.mode === 'dark' || doc.mode === 'auto' ? doc.mode : fallbackMode;\n return {\n version: CURRENT_DOCUMENT_VERSION,\n trials: Array.isArray(doc.trials) ? (doc.trials as SerializedTrial[]) : [],\n saves: Array.isArray(doc.saves) ? (doc.saves as SavedSnapshot[]) : [],\n layout:\n doc.layout && typeof doc.layout === 'object' ? (doc.layout as Record<string, unknown>) : {},\n mode,\n };\n}\n\n/** Version 0 (four loose keys) to version 1 (one document). Normalizes the\n * mode, including `interstellar` — the dark mode's name back when it was a\n * theme. Coerces against version 1's own field names rather than reusing\n * `normalizeDocument`, which produces the current shape and would drop\n * `workspaces` from a document it labels version 1. */\nexport function migrateV0toV1(doc: Record<string, unknown>): Record<string, unknown> {\n const raw = doc.mode === 'interstellar' ? 'dark' : doc.mode;\n const mode = raw === 'light' || raw === 'dark' || raw === 'auto' ? raw : 'auto';\n return {\n version: 1,\n workspaces: Array.isArray(doc.workspaces) ? doc.workspaces : [],\n saves: Array.isArray(doc.saves) ? doc.saves : [],\n layout: doc.layout && typeof doc.layout === 'object' ? doc.layout : {},\n mode,\n };\n}\n\n/** Version 1 to version 2: the vocabulary refresh renamed a tile from\n * workspace to trial. Only the records field moves; a record's own fields are\n * unchanged. */\nexport function migrateV1toV2(doc: Record<string, unknown>): Record<string, unknown> {\n const { workspaces, ...rest } = doc;\n return { ...rest, trials: Array.isArray(workspaces) ? workspaces : [], version: 2 };\n}\n\n/** Index `i` migrates a version-`i` document to version `i + 1`. */\nexport const MIGRATIONS: Migration[] = [migrateV0toV1, migrateV1toV2];\n","import { createStore, type StoreApi } from 'zustand/vanilla';\nimport {\n CURRENT_DOCUMENT_VERSION,\n deleteLegacyKeys,\n emptyDocument,\n labDocumentKey,\n MIGRATIONS,\n normalizeDocument,\n quarantineDocument,\n readLegacyDocument,\n runMigrations,\n} from './document';\nimport { deserializeTrials, emptyUndoStack, serializeTrials } from './helpers';\nimport type {\n CreateLabStoreOptions,\n InstrumentSerializers,\n LabDocument,\n LabMode,\n LabStoreState,\n SavedSnapshot,\n TrialRecord,\n} from './types';\n\n/** Every mutation a lab store supports: managing trials, saving and\n * restoring snapshots, and setting the color mode. */\nexport interface LabStoreActions {\n addTrial: (record: Omit<TrialRecord, 'undoStack'>) => void;\n removeTrial: (id: string) => void;\n updateTrialState: <TS>(id: string, next: TS | ((prev: TS) => TS)) => void;\n updateTrialConfig: <TC>(id: string, key: keyof TC, value: TC[keyof TC]) => void;\n updateTrialView: (id: string, view: unknown) => void;\n updateTrialUndoStack: (\n id: string,\n next: TrialRecord['undoStack'] | ((prev: TrialRecord['undoStack']) => TrialRecord['undoStack']),\n ) => void;\n setTrialInstrument: (id: string, instrumentName: string) => void;\n saveSnapshot: (trialId: string, name: string) => void;\n loadSnapshot: (snapshotId: string, trialId: string) => void;\n deleteSnapshot: (snapshotId: string) => void;\n listSnapshots: (trialId?: string) => SavedSnapshot[];\n setMode: (mode: LabMode) => void;\n setLayout: (layout: Record<string, unknown>) => void;\n}\n\n/** A lab's store: its state and actions, plus the hook instruments use to\n * register how their state is serialized. */\nexport type LabStore = StoreApi<LabStoreState & LabStoreActions> & {\n registerSerializers: (s: InstrumentSerializers) => void;\n};\n\n/** Build a lab store, hydrating from storage if anything was saved under the\n * same key. Writes back are debounced. */\nexport function createLabStore(options: CreateLabStoreOptions): LabStore {\n let serializers: InstrumentSerializers = {};\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n const hydration = hydrateDocument(options);\n const hydrated = hydration.document;\n const persistDisabled = hydration.persistDisabled;\n // Cleared once the legacy keys are actually gone; see the flush.\n let foldedFromLegacy = hydration.foldedFromLegacy;\n\n const hydratedTrials = deserializeTrials(hydrated.trials, serializers);\n const hydratedSnapshots = hydrated.saves;\n const hydratedLayout = hydrated.layout;\n const hydratedMode = hydrated.mode;\n\n const store = createStore<LabStoreState & LabStoreActions>()((set, get) => ({\n trials: hydratedTrials,\n savedSnapshots: hydratedSnapshots,\n mode: hydratedMode,\n layout: hydratedLayout,\n\n addTrial: (record) => {\n set((s) => ({\n trials: [...s.trials, { ...record, undoStack: emptyUndoStack() }],\n }));\n scheduleFlush();\n },\n\n removeTrial: (id) => {\n set((s) => ({ trials: s.trials.filter((w) => w.id !== id) }));\n scheduleFlush();\n },\n\n updateTrialState: (id, next) => {\n set((s) => ({\n trials: s.trials.map((w) => {\n if (w.id !== id) return w;\n const nextState =\n typeof next === 'function' ? (next as (prev: unknown) => unknown)(w.state) : next;\n // An updater that returns its input means \"nothing changed\", and must not\n // cost a new record: the trial re-renders on record identity, so\n // allocating here turns the standard React bail-out into a render loop.\n if (Object.is(nextState, w.state)) return w;\n return { ...w, state: nextState };\n }),\n }));\n scheduleFlush();\n },\n\n updateTrialConfig: (id, key, value) => {\n set((s) => ({\n trials: s.trials.map((w) => {\n if (w.id !== id) return w;\n return {\n ...w,\n config: { ...(w.config as Record<string, unknown>), [key as string]: value },\n };\n }),\n }));\n scheduleFlush();\n },\n\n updateTrialView: (id, view) => {\n set((s) => ({\n trials: s.trials.map((w) =>\n w.id === id && !Object.is(view, w.view) ? { ...w, view } : w,\n ),\n }));\n scheduleFlush();\n },\n\n updateTrialUndoStack: (id, next) => {\n set((s) => ({\n trials: s.trials.map((w) => {\n if (w.id !== id) return w;\n const undoStack =\n typeof next === 'function'\n ? (next as (prev: TrialRecord['undoStack']) => TrialRecord['undoStack'])(w.undoStack)\n : next;\n return { ...w, undoStack };\n }),\n }));\n },\n\n setTrialInstrument: (id, instrumentName) => {\n set((s) => ({\n trials: s.trials.map((w) => (w.id === id ? { ...w, instrumentName } : w)),\n }));\n scheduleFlush();\n },\n\n saveSnapshot: (trialId, name) => {\n const trial = get().trials.find((w) => w.id === trialId);\n if (!trial) return;\n const reg = serializers[trial.instrumentName];\n const serializedState = reg?.serialize\n ? reg.serialize(trial.state)\n : structuredClone(trial.state);\n const clonedConfig = structuredClone(trial.config);\n const lastAt = get().savedSnapshots.reduce((m, sn) => (sn.savedAt > m ? sn.savedAt : m), 0);\n const savedAt = Math.max(Date.now(), lastAt + 1);\n const snapshot: SavedSnapshot = {\n id: crypto.randomUUID(),\n name,\n trialId,\n instrumentName: trial.instrumentName,\n config: clonedConfig,\n state: serializedState,\n savedAt,\n };\n set((s) => ({ savedSnapshots: [...s.savedSnapshots, snapshot] }));\n scheduleFlush();\n },\n\n loadSnapshot: (snapshotId, trialId) => {\n const snapshot = get().savedSnapshots.find((sn) => sn.id === snapshotId);\n if (!snapshot) return;\n const trial = get().trials.find((w) => w.id === trialId);\n if (!trial) return;\n if (snapshot.instrumentName !== trial.instrumentName) {\n console.warn(\n `[labkit] loadSnapshot: instrument mismatch (snapshot=${snapshot.instrumentName}, trial=${trial.instrumentName}); refusing to load`,\n );\n return;\n }\n const reg = serializers[snapshot.instrumentName];\n const restoredState = reg?.deserialize ? reg.deserialize(snapshot.state) : snapshot.state;\n set((s) => ({\n trials: s.trials.map((w) =>\n w.id === trialId ? { ...w, state: restoredState, config: snapshot.config } : w,\n ),\n }));\n scheduleFlush();\n },\n\n deleteSnapshot: (snapshotId) => {\n set((s) => ({\n savedSnapshots: s.savedSnapshots.filter((sn) => sn.id !== snapshotId),\n }));\n scheduleFlush();\n },\n\n listSnapshots: (trialId) => {\n const all = get().savedSnapshots;\n const filtered = trialId ? all.filter((sn) => sn.trialId === trialId) : all;\n return [...filtered].sort((a, b) => b.savedAt - a.savedAt);\n },\n\n setMode: (mode) => {\n set({ mode });\n scheduleFlush();\n },\n\n setLayout: (layout) => {\n set({ layout });\n scheduleFlush();\n },\n }));\n\n function scheduleFlush(): void {\n if (persistDisabled) return;\n if (flushTimer) clearTimeout(flushTimer);\n flushTimer = setTimeout(() => {\n const s = store.getState();\n const document: LabDocument = {\n version: CURRENT_DOCUMENT_VERSION,\n trials: serializeTrials(s.trials, serializers),\n saves: s.savedSnapshots,\n layout: s.layout,\n mode: s.mode,\n };\n const serialized = JSON.stringify(document);\n options.storage.write(labDocumentKey(options.storageKey), serialized);\n if (foldedFromLegacy && deleteLegacyKeys(options.storage, options.storageKey, serialized)) {\n foldedFromLegacy = false;\n }\n flushTimer = null;\n }, 300);\n }\n\n // A lab opened and closed without a single mutation still completes its\n // fold; the flush is what removes the legacy keys.\n if (foldedFromLegacy) scheduleFlush();\n\n return Object.assign(store, {\n registerSerializers(s: InstrumentSerializers) {\n serializers = s;\n },\n });\n}\n\ninterface HydrateResult {\n document: LabDocument;\n /** True when flushing would destroy the only copy of something: a document\n * newer than this code understands, or an unusable one whose quarantine\n * copy did not land. */\n persistDisabled: boolean;\n foldedFromLegacy: boolean;\n}\n\n/** Copy an unusable document aside, and report whether the store may go on\n * persisting. A failed quarantine write means the copy in storage is the only\n * one there is, so the store must not overwrite it. */\nfunction setAside(\n options: CreateLabStoreOptions,\n raw: string,\n why: string,\n error?: unknown,\n): boolean {\n const quarantined = quarantineDocument(options.storage, options.storageKey, raw);\n const message = quarantined\n ? `[labkit] ${why}; quarantined it and starting empty`\n : `[labkit] ${why} and could not be quarantined; leaving it in place and not persisting`;\n if (error === undefined) console.warn(message);\n else console.warn(message, error);\n return quarantined;\n}\n\nfunction hydrateDocument(options: CreateLabStoreOptions): HydrateResult {\n const fallback = emptyDocument(options.initialMode ?? 'auto');\n const raw = options.storage.read(labDocumentKey(options.storageKey));\n\n let parsed: Record<string, unknown> | null = null;\n if (raw !== null) {\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n const persistDisabled = !setAside(options, raw, 'lab document is unparseable');\n return { document: fallback, persistDisabled, foldedFromLegacy: false };\n }\n } else {\n parsed = readLegacyDocument(options.storage, options.storageKey, options.initialMode ?? 'auto');\n }\n\n if (parsed === null) {\n return { document: fallback, persistDisabled: false, foldedFromLegacy: false };\n }\n\n const foldedFromLegacy = raw === null;\n const outcome = runMigrations(parsed, MIGRATIONS, CURRENT_DOCUMENT_VERSION);\n\n if (!outcome.ok) {\n if (outcome.reason === 'future') {\n console.warn(\n '[labkit] lab document is from a newer version of labkit; starting empty and leaving it alone',\n );\n return { document: fallback, persistDisabled: true, foldedFromLegacy: false };\n }\n const stored = JSON.stringify(parsed);\n const persistDisabled = !setAside(\n options,\n stored,\n 'lab document failed to migrate',\n outcome.error,\n );\n return { document: fallback, persistDisabled, foldedFromLegacy: false };\n }\n\n return {\n document: normalizeDocument(outcome.doc, options.initialMode ?? 'auto'),\n persistDisabled: false,\n foldedFromLegacy,\n };\n}\n","import { useContext } from 'react';\nimport { useStore } from 'zustand/react';\nimport { LabStoreContext, TrialIdContext } from './context';\nimport type { TrialStateHandle } from './types';\n\n/** An instrument's own state and config, plus setters. Reads the surrounding\n * trial id, so an instrument never has to know which trial it is\n * running in. Throws outside a lab store and trial. */\nexport function useTrialState<TS = unknown, TC = unknown>(): TrialStateHandle<TS, TC> {\n const ctx = useContext(LabStoreContext);\n if (!ctx) throw new Error('[labkit] useTrialState must be used inside <LabStoreProvider>');\n\n const trialId = useContext(TrialIdContext);\n if (!trialId) throw new Error('[labkit] useTrialState must be used inside <TrialIdProvider>');\n\n const record = useStore(ctx.store, (s) => s.trials.find((w) => w.id === trialId));\n\n if (!record) throw new Error(`[labkit] No trial found with id \"${trialId}\"`);\n\n const updateTrialState = useStore(ctx.store, (s) => s.updateTrialState);\n const updateTrialConfig = useStore(ctx.store, (s) => s.updateTrialConfig);\n\n return {\n state: record.state as TS,\n config: record.config as TC,\n setState: (next) => updateTrialState(trialId, next as Parameters<typeof updateTrialState>[1]),\n setConfig: (key, value) => updateTrialConfig(trialId, key as never, value as never),\n };\n}\n"]}
|
package/dist/dragdrop/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { D as DragDropCapability, c as DragFeedback, P as PaletteItem } from '../_dts/index-
|
|
1
|
+
export { D as DragDropCapability, c as DragFeedback, P as PaletteItem } from '../_dts/index-JFAYj5Tv.js';
|
|
2
2
|
import 'react';
|
|
3
3
|
import '../_dts/types-x92Kfeme.js';
|
|
4
|
+
import '../_dts/types-DJ79Tg5J.js';
|