@weasel-js/labkit 1.1.0 → 1.3.0-pre.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-DBB45NfN.d.ts} +244 -58
- package/dist/_dts/PrefsForm-BYa6cWnO.d.ts +201 -0
- package/dist/_dts/index-DgcNdEdh.d.ts +314 -0
- package/dist/_dts/types-1Sdxy_Pv.d.ts +90 -0
- package/dist/_dts/types-AHlQxBNN.d.ts +162 -0
- package/dist/_dts/types-DJ79Tg5J.d.ts +56 -0
- package/dist/_dts/{useTrialState-DYe2vUwN.d.ts → useTrialState-CsGMjhu9.d.ts} +6 -92
- package/dist/canvas/index.d.ts +8 -3
- package/dist/canvas/index.js +2 -1
- package/dist/chrome/index.d.ts +86 -0
- package/dist/chrome/index.js +8 -0
- package/dist/chrome/index.js.map +1 -0
- package/dist/{chunk-3TYUJR7Z.js → chunk-4TMMVIDM.js} +9 -3
- package/dist/chunk-4TMMVIDM.js.map +1 -0
- package/dist/chunk-HFDVGF4X.js +170 -0
- package/dist/chunk-HFDVGF4X.js.map +1 -0
- package/dist/chunk-LN6JDUGB.js +106 -0
- package/dist/chunk-LN6JDUGB.js.map +1 -0
- package/dist/{chunk-574LJAV4.js → chunk-NS54R52R.js} +31 -21
- package/dist/chunk-NS54R52R.js.map +1 -0
- package/dist/{chunk-NRKWVTVT.js → chunk-O2BULFHX.js} +5 -14
- package/dist/chunk-O2BULFHX.js.map +1 -0
- package/dist/chunk-O7NVSCLN.js +266 -0
- package/dist/chunk-O7NVSCLN.js.map +1 -0
- package/dist/chunk-PO6L3NSH.js +366 -0
- package/dist/chunk-PO6L3NSH.js.map +1 -0
- package/dist/chunk-PQJ5B2U2.js +27 -0
- package/dist/chunk-PQJ5B2U2.js.map +1 -0
- package/dist/chunk-RVNN4CHQ.js +212 -0
- package/dist/chunk-RVNN4CHQ.js.map +1 -0
- package/dist/chunk-TLGRYALP.js +6151 -0
- package/dist/chunk-TLGRYALP.js.map +1 -0
- package/dist/{chunk-SFL7NFKN.js → chunk-V7PRSIRQ.js} +15 -7
- package/dist/chunk-V7PRSIRQ.js.map +1 -0
- package/dist/{chunk-DJLDIRFN.js → chunk-Z6HJ5FEM.js} +9626 -6050
- package/dist/chunk-Z6HJ5FEM.js.map +1 -0
- package/dist/chunk-ZZAYVX4X.js +505 -0
- package/dist/chunk-ZZAYVX4X.js.map +1 -0
- package/dist/controls/index.d.ts +19 -5
- package/dist/controls/index.js +3 -1
- package/dist/dragdrop/index.d.ts +5 -2
- package/dist/index.d.ts +307 -107
- package/dist/index.js +797 -540
- 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 +6 -3
- package/dist/layers/index.js +2 -1
- package/dist/passthrough/weasel-canvas.d.ts +69 -13
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +3804 -3661
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/primitives/index.d.ts +102 -8
- package/dist/primitives/index.js +4 -1
- package/dist/state/index.d.ts +3 -2
- package/dist/state/index.js +2 -2
- package/dist/styles.css +543 -144
- package/dist/surface/index.d.ts +77 -0
- package/dist/surface/index.js +4 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/ui/layers/index.js +4 -3
- package/dist/undo/index.d.ts +5 -2
- package/package.json +24 -7
- package/src/canvas/AGENTS.md +12 -0
- package/src/canvas/CanvasStack.tsx +6 -2
- package/src/canvas/useLayerScheduler.ts +22 -15
- package/src/canvas/useOrbit.test.ts +71 -0
- package/src/canvas/useOrbit.ts +149 -0
- package/src/canvas/usePanZoom.test.ts +80 -0
- package/src/canvas/usePanZoom.ts +16 -2
- package/src/chrome/ChromeRegions.stories.tsx +106 -0
- package/src/chrome/builtins.test.ts +153 -0
- package/src/chrome/builtins.tsx +205 -0
- package/src/chrome/index.ts +25 -0
- package/src/chrome/merge.test.ts +51 -0
- package/src/chrome/merge.ts +52 -0
- package/src/chrome/regions/PaletteRegion.less +14 -0
- package/src/chrome/regions/PaletteRegion.test.tsx +36 -0
- package/src/chrome/regions/PaletteRegion.tsx +41 -0
- package/src/chrome/regions/SidebarRegion.tsx +56 -0
- package/src/chrome/regions/StatusRegion.tsx +31 -0
- package/src/chrome/regions/TitleBarRegion.tsx +41 -0
- package/src/chrome/regions/ToolbarRegion.test.tsx +82 -0
- package/src/chrome/regions/ToolbarRegion.tsx +71 -0
- package/src/chrome/regions/ViewportRegion.less +42 -0
- package/src/chrome/regions/ViewportRegion.tsx +37 -0
- package/src/chrome/regions/regions.test.tsx +81 -0
- package/src/chrome/types.ts +127 -0
- package/src/config/builder.test.ts +87 -0
- package/src/config/builder.ts +224 -0
- package/src/config/fromConfigField.test.ts +93 -0
- package/src/config/fromConfigField.ts +74 -0
- package/src/config/index.ts +22 -0
- package/src/config/resolve.test.ts +120 -0
- package/src/config/resolve.ts +67 -0
- package/src/config/rules.ts +68 -0
- package/src/config/types.ts +108 -0
- package/src/config/useConfigSchema.ts +30 -0
- package/src/config/visible.ts +20 -0
- package/src/controls/ControlPanel.less +4 -58
- package/src/controls/ControlPanel.stories.tsx +86 -0
- package/src/controls/ControlPanel.test.tsx +223 -10
- package/src/controls/ControlPanel.tsx +202 -203
- package/src/dragdrop/DragDropRuntime.tsx +36 -18
- package/src/dragdrop/DragGhost.less +1 -1
- package/src/dragdrop/Palette.less +1 -1
- package/src/index.test.ts +18 -0
- package/src/index.ts +38 -3
- package/src/instrument/SineWave.smoke.test.tsx +9 -1
- package/src/instrument/defineInstrument.test.ts +38 -1
- package/src/instrument/defineInstrument.ts +31 -4
- package/src/instrument/index.ts +1 -0
- package/src/instrument/types.ts +38 -3
- package/src/instrument/validateConfigSchema.test.ts +13 -3
- package/src/instrument/validateConfigSchema.ts +7 -10
- 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.chrome.test.tsx +65 -0
- package/src/lab/Lab.stories.tsx +1 -2
- package/src/lab/Lab.tsx +58 -15
- package/src/lab/LabContext.ts +6 -0
- package/src/lab/LabFullChrome.stories.tsx +308 -0
- package/src/lab/LabHeader.test.tsx +42 -0
- package/src/lab/LabHeader.tsx +57 -0
- package/src/lab/LabPalette.test.tsx +35 -0
- package/src/lab/LabPalette.tsx +34 -0
- package/src/lab/LabShell.less +50 -3
- package/src/lab/Workspace.less +1 -30
- package/src/lab/Workspace.surface.test.tsx +49 -0
- package/src/lab/Workspace.test.tsx +20 -10
- package/src/lab/Workspace.tsx +19 -9
- package/src/lab/index.ts +2 -0
- package/src/layers/AGENTS.md +1 -1
- package/src/layers/LayerList.less +31 -7
- package/src/layers/LayerList.tsx +17 -4
- package/src/passthrough/weasel-ui.ts +23 -0
- package/src/primitives/DragHandleGlyph.tsx +29 -0
- package/src/primitives/FloatingPanel.less +28 -0
- package/src/primitives/FloatingPanel.stories.tsx +46 -0
- package/src/primitives/FloatingPanel.test.tsx +240 -0
- package/src/primitives/FloatingPanel.tsx +190 -0
- package/src/primitives/FpsMeter.test.tsx +44 -0
- package/src/primitives/FpsMeter.tsx +20 -9
- package/src/primitives/JobProgress.less +69 -0
- package/src/primitives/JobProgress.stories.tsx +48 -0
- package/src/primitives/JobProgress.tsx +65 -0
- package/src/primitives/Legend.less +47 -0
- package/src/primitives/Legend.stories.tsx +32 -0
- package/src/primitives/Legend.test.tsx +57 -0
- package/src/primitives/Legend.tsx +40 -0
- package/src/primitives/Sidebar.less +32 -2
- package/src/primitives/StatusBar.less +7 -0
- package/src/primitives/StatusBar.tsx +10 -3
- package/src/primitives/Toolbar.less +50 -3
- package/src/primitives/Toolbar.test.tsx +67 -1
- package/src/primitives/Toolbar.tsx +72 -6
- package/src/primitives/ZoomControl.less +31 -0
- package/src/primitives/ZoomControl.tsx +76 -0
- package/src/primitives/floating.entry.test.tsx +14 -0
- package/src/primitives/index.ts +10 -2
- package/src/primitives/useRovingTabIndex.test.ts +27 -0
- package/src/primitives/useRovingTabIndex.ts +79 -0
- package/src/state/store.ts +16 -2
- package/src/state/toolSlot.test.ts +36 -0
- package/src/state/types.ts +8 -2
- package/src/state/view.test.ts +127 -0
- package/src/state/view.ts +18 -0
- package/src/styles.less +6 -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 +161 -0
- package/src/test-setup.ts +26 -0
- package/src/theme/base.less +19 -2
- package/src/theme/interstellar.tokens.json +6 -2
- package/src/tools/types.ts +24 -0
- package/src/trial/Trial.config.test.tsx +113 -0
- package/src/trial/Trial.job.test.tsx +73 -0
- package/src/trial/Trial.less +121 -4
- package/src/trial/Trial.stories.tsx +9 -1
- package/src/trial/Trial.test.tsx +67 -96
- package/src/trial/Trial.tsx +112 -45
- package/src/trial/TrialChrome.tsx +110 -64
- package/src/trial/TrialDragContext.ts +10 -0
- package/src/trial/TrialTitleBar.tsx +41 -0
- package/src/trial/index.ts +1 -15
- package/src/trial/trialChrome.borders.test.ts +38 -0
- package/src/ui/layers/LayerStack.less +9 -9
- package/src/ui/layers/LayerStack.tsx +1 -13
- package/src/ui/properties/CurveField.less +2 -2
- package/src/ui/properties/CurveField.test.tsx +11 -11
- package/src/ui/properties/PropertyGroup.less +2 -2
- package/src/ui/properties/PropertyPanel.less +55 -41
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +4 -2
- package/src/ui/properties/storyLayouts.tsx +2 -2
- package/dist/_dts/index-iAP6XbH3.d.ts +0 -132
- package/dist/_dts/types-x92Kfeme.d.ts +0 -62
- package/dist/chunk-3TYUJR7Z.js.map +0 -1
- package/dist/chunk-574LJAV4.js.map +0 -1
- package/dist/chunk-5R2ATYPJ.js +0 -201
- package/dist/chunk-5R2ATYPJ.js.map +0 -1
- package/dist/chunk-C6GJKPUI.js +0 -5986
- package/dist/chunk-C6GJKPUI.js.map +0 -1
- package/dist/chunk-DJLDIRFN.js.map +0 -1
- package/dist/chunk-NRKWVTVT.js.map +0 -1
- package/dist/chunk-PMAU3SEE.js +0 -126
- package/dist/chunk-PMAU3SEE.js.map +0 -1
- package/dist/chunk-SFL7NFKN.js.map +0 -1
- package/src/instrument/capabilityDetector.test.ts +0 -64
- package/src/instrument/capabilityDetector.ts +0 -20
- package/src/trial/DefaultSidebar.tsx +0 -29
- package/src/trial/DefaultStatusBar.tsx +0 -19
- package/src/trial/DefaultToolbar.tsx +0 -92
- package/src/trial/slotTypes.ts +0 -53
package/dist/index.js
CHANGED
|
@@ -1,43 +1,369 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import './chunk-
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
import {
|
|
7
|
-
export {
|
|
8
|
-
import {
|
|
9
|
-
export {
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { useSurfaceOptional } from './chunk-HFDVGF4X.js';
|
|
2
|
+
export { SurfaceContext, composeRects, rectsEqual, toDeviceRect, useSurface, useSurfaceOptional, useSurfaceTile, useTiledSurface } from './chunk-HFDVGF4X.js';
|
|
3
|
+
import { useJob } from './chunk-LN6JDUGB.js';
|
|
4
|
+
export { useJob } from './chunk-LN6JDUGB.js';
|
|
5
|
+
export { LayerStack } from './chunk-O2BULFHX.js';
|
|
6
|
+
import { suppressContributions, mergeContributions, builtinContributions, TitleBarRegion, ToolbarRegion, PaletteRegion, SidebarRegion, ViewportRegion, StatusRegion } from './chunk-PO6L3NSH.js';
|
|
7
|
+
export { PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, builtinContributions, mergeContributions, suppressContributions } from './chunk-PO6L3NSH.js';
|
|
8
|
+
import { JobProgress } from './chunk-RVNN4CHQ.js';
|
|
9
|
+
export { FloatingPanel, JobProgress, Legend, Sidebar } from './chunk-RVNN4CHQ.js';
|
|
10
|
+
export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl } from './chunk-O7NVSCLN.js';
|
|
11
|
+
import { LabStoreContext, createLabStore, noneAdapter } from './chunk-4TMMVIDM.js';
|
|
12
|
+
export { CURRENT_DOCUMENT_VERSION, LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, createMemoryAdapter, labDocumentKey, localStorageAdapter, noneAdapter, quarantineKey, sessionStorageAdapter, urlHashAdapter, useLabStore, useTrialId, useTrialState } from './chunk-4TMMVIDM.js';
|
|
13
|
+
import { fromConfigFields } from './chunk-ZZAYVX4X.js';
|
|
14
|
+
export { CheckboxRow, ColorRow, ControlPanel, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, TextRow, ToggleRow, fromConfigFields, isLeafVisible } from './chunk-ZZAYVX4X.js';
|
|
15
|
+
import { p, F, vt, p2, r } from './chunk-TLGRYALP.js';
|
|
16
|
+
import { CanvasStack, screenToWorld } from './chunk-NS54R52R.js';
|
|
17
|
+
export { CanvasStack, screenToWorld, worldToScreen } from './chunk-NS54R52R.js';
|
|
12
18
|
export { CanvasStackContext } from './chunk-CPUJ3QXL.js';
|
|
13
|
-
import {
|
|
14
|
-
|
|
19
|
+
import { useVisibleRaf } from './chunk-Z6HJ5FEM.js';
|
|
20
|
+
import { LayerList } from './chunk-V7PRSIRQ.js';
|
|
21
|
+
export { LayerList } from './chunk-V7PRSIRQ.js';
|
|
22
|
+
import './chunk-PQJ5B2U2.js';
|
|
15
23
|
import { createEventBus, redo, undo, pushSnapshot } from './chunk-RL2LOLNI.js';
|
|
16
24
|
export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
|
|
17
|
-
import { createContext, useContext, useMemo,
|
|
18
|
-
import {
|
|
25
|
+
import { createContext, useRef, useCallback, useContext, useMemo, useState, Children, useLayoutEffect, useEffect } from 'react';
|
|
26
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
19
27
|
import { useStore } from 'zustand/react';
|
|
20
28
|
import { createPortal } from 'react-dom';
|
|
29
|
+
import { Provider, StrategyRegistryProvider, DragProvider, Container, useDragHandle } from 'windease/react';
|
|
21
30
|
import { asNodeId, Store, createNode, gridStrategy } from 'windease';
|
|
22
|
-
import { DragHandle, Provider, StrategyRegistryProvider, DragProvider, Container } from 'windease/react';
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
var PITCH_LIMIT = Math.PI / 2 - 0.01;
|
|
33
|
+
var YAW_PER_PX = 8e-3;
|
|
34
|
+
var PITCH_PER_PX = 8e-3;
|
|
35
|
+
var DISTANCE_PER_NOTCH = 15e-4;
|
|
36
|
+
var DRAG_THRESHOLD = 3;
|
|
37
|
+
function clampPitch(pitch) {
|
|
38
|
+
return Math.min(PITCH_LIMIT, Math.max(-PITCH_LIMIT, pitch));
|
|
39
|
+
}
|
|
40
|
+
function wrapYaw(yaw) {
|
|
41
|
+
const wrapped = ((yaw + Math.PI) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) - Math.PI;
|
|
42
|
+
return wrapped === -Math.PI ? Math.PI : wrapped;
|
|
43
|
+
}
|
|
44
|
+
function orbitAfterDrag(start, dx, dy) {
|
|
45
|
+
return {
|
|
46
|
+
...start,
|
|
47
|
+
yaw: wrapYaw(start.yaw + dx * YAW_PER_PX),
|
|
48
|
+
pitch: clampPitch(start.pitch + dy * PITCH_PER_PX)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function orbitAfterWheel(view, deltaY, minDistance, maxDistance) {
|
|
52
|
+
const factor = Math.exp(deltaY * DISTANCE_PER_NOTCH);
|
|
26
53
|
return {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
hasDragDrop: instrument.dragDrop != null,
|
|
30
|
-
hasUndo: instrument.undo != null
|
|
54
|
+
...view,
|
|
55
|
+
distance: Math.min(maxDistance, Math.max(minDistance, view.distance * factor))
|
|
31
56
|
};
|
|
32
57
|
}
|
|
58
|
+
function useOrbit({
|
|
59
|
+
view,
|
|
60
|
+
onViewChange,
|
|
61
|
+
home,
|
|
62
|
+
minDistance = 0.1,
|
|
63
|
+
maxDistance = 1e3
|
|
64
|
+
}) {
|
|
65
|
+
const dragRef = useRef(null);
|
|
66
|
+
const viewRef = useRef(view);
|
|
67
|
+
viewRef.current = view;
|
|
68
|
+
const homeRef = useRef(home ?? view);
|
|
69
|
+
if (home) homeRef.current = home;
|
|
70
|
+
const onWheel = useCallback(
|
|
71
|
+
(e) => {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
onViewChange(orbitAfterWheel(viewRef.current, e.deltaY, minDistance, maxDistance));
|
|
74
|
+
},
|
|
75
|
+
[onViewChange, minDistance, maxDistance]
|
|
76
|
+
);
|
|
77
|
+
const onPointerDown = useCallback((e) => {
|
|
78
|
+
if (e.button !== 0) return;
|
|
79
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
80
|
+
dragRef.current = {
|
|
81
|
+
pointerId: e.pointerId,
|
|
82
|
+
startX: e.clientX,
|
|
83
|
+
startY: e.clientY,
|
|
84
|
+
startView: viewRef.current,
|
|
85
|
+
moved: false
|
|
86
|
+
};
|
|
87
|
+
}, []);
|
|
88
|
+
const onPointerMove = useCallback(
|
|
89
|
+
(e) => {
|
|
90
|
+
const drag = dragRef.current;
|
|
91
|
+
if (!drag || drag.pointerId !== e.pointerId) return;
|
|
92
|
+
const dx = e.clientX - drag.startX;
|
|
93
|
+
const dy = e.clientY - drag.startY;
|
|
94
|
+
if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
95
|
+
drag.moved = true;
|
|
96
|
+
onViewChange(orbitAfterDrag(drag.startView, dx, dy));
|
|
97
|
+
},
|
|
98
|
+
[onViewChange]
|
|
99
|
+
);
|
|
100
|
+
const onPointerUp = useCallback((e) => {
|
|
101
|
+
const drag = dragRef.current;
|
|
102
|
+
if (!drag || drag.pointerId !== e.pointerId) return;
|
|
103
|
+
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
|
104
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
105
|
+
}
|
|
106
|
+
dragRef.current = null;
|
|
107
|
+
}, []);
|
|
108
|
+
const onDoubleClick = useCallback(() => onViewChange(homeRef.current), [onViewChange]);
|
|
109
|
+
const isDragging = useCallback(() => dragRef.current?.moved === true, []);
|
|
110
|
+
return { onWheel, onPointerDown, onPointerMove, onPointerUp, onDoubleClick, isDragging };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/config/builder.ts
|
|
114
|
+
var BaseNode = class {
|
|
115
|
+
constructor(defaultValue, annotations = {}, options = {}) {
|
|
116
|
+
this.annotations = annotations;
|
|
117
|
+
this.options = options;
|
|
118
|
+
this.default = defaultValue;
|
|
119
|
+
}
|
|
120
|
+
annotations;
|
|
121
|
+
options;
|
|
122
|
+
// `default` is a reserved word in a parameter position, so the field is
|
|
123
|
+
// declared rather than taken as a parameter property.
|
|
124
|
+
default;
|
|
125
|
+
/** Clone into the same subclass. Subclasses with extra constructor
|
|
126
|
+
* arguments override this. */
|
|
127
|
+
with(annotations, options) {
|
|
128
|
+
const Ctor = this.constructor;
|
|
129
|
+
return new Ctor(this.default, annotations, options);
|
|
130
|
+
}
|
|
131
|
+
ann(patch) {
|
|
132
|
+
return this.with({ ...this.annotations, ...patch }, this.options);
|
|
133
|
+
}
|
|
134
|
+
opt(patch) {
|
|
135
|
+
return this.with(this.annotations, { ...this.options, ...patch });
|
|
136
|
+
}
|
|
137
|
+
/** Human-readable label. Defaults to the key, title-cased. */
|
|
138
|
+
label(name) {
|
|
139
|
+
return this.ann({ name });
|
|
140
|
+
}
|
|
141
|
+
/** Longer help text, surfaced as a tooltip on the row label. */
|
|
142
|
+
describe(description) {
|
|
143
|
+
return this.ann({ description });
|
|
144
|
+
}
|
|
145
|
+
/** Omit from the panel unless it is asked to show hidden leaves. */
|
|
146
|
+
hidden() {
|
|
147
|
+
return this.ann({ hidden: true });
|
|
148
|
+
}
|
|
149
|
+
/** Pair with a sibling sharing this id, side-by-side on one row. */
|
|
150
|
+
pair(pair) {
|
|
151
|
+
return this.ann({ pair });
|
|
152
|
+
}
|
|
153
|
+
/** Render under a named section heading. */
|
|
154
|
+
section(section) {
|
|
155
|
+
return this.opt({ section });
|
|
156
|
+
}
|
|
157
|
+
/** Show this row only while the predicate holds. Presentational — the value
|
|
158
|
+
* stays in config and the instrument still reads it. */
|
|
159
|
+
showIf(predicate) {
|
|
160
|
+
return this.opt({ showIf: predicate });
|
|
161
|
+
}
|
|
162
|
+
/** Draw this one row yourself, keeping the kind, default and validation. */
|
|
163
|
+
render(renderer) {
|
|
164
|
+
return this.opt({ render: renderer });
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var NumberNode = class extends BaseNode {
|
|
168
|
+
kind = "number";
|
|
169
|
+
/** Bound the value. A number with both bounds renders as a slider. */
|
|
170
|
+
range(min, max) {
|
|
171
|
+
return this.ann({ min, max });
|
|
172
|
+
}
|
|
173
|
+
step(step) {
|
|
174
|
+
return this.ann({ step });
|
|
175
|
+
}
|
|
176
|
+
/** Force a slider even without both bounds. */
|
|
177
|
+
slider() {
|
|
178
|
+
return this.ann({ control: "slider" });
|
|
179
|
+
}
|
|
180
|
+
/** Force a typed input even when both bounds are set. */
|
|
181
|
+
input() {
|
|
182
|
+
return this.ann({ control: "input" });
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
var BooleanNode = class extends BaseNode {
|
|
186
|
+
kind = "boolean";
|
|
187
|
+
/** Ask for a switch. `ControlPanel` still draws a checkbox; weasel-ui's
|
|
188
|
+
* `PrefsForm` honors the distinction. */
|
|
189
|
+
toggle() {
|
|
190
|
+
return this.ann({ control: "switch" });
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var StringNode = class extends BaseNode {
|
|
194
|
+
kind = "string";
|
|
195
|
+
placeholder(placeholder) {
|
|
196
|
+
return this.ann({ placeholder });
|
|
197
|
+
}
|
|
198
|
+
maxLength(maxLength) {
|
|
199
|
+
return this.ann({ maxLength });
|
|
200
|
+
}
|
|
201
|
+
/** Milliseconds to debounce live writes. 0 commits every keystroke. */
|
|
202
|
+
debounce(debounceMs) {
|
|
203
|
+
return this.ann({ debounceMs });
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var ColorNode = class extends BaseNode {
|
|
207
|
+
kind = "color";
|
|
208
|
+
};
|
|
209
|
+
var EnumNode = class extends BaseNode {
|
|
210
|
+
kind = "enum";
|
|
211
|
+
/** Render as a segmented control rather than a select. */
|
|
212
|
+
radio() {
|
|
213
|
+
return this.ann({ control: "radio" });
|
|
214
|
+
}
|
|
215
|
+
/** Relabel the choices. Named `labels` rather than `options` because
|
|
216
|
+
* `options` is the node's own extras bag. */
|
|
217
|
+
labels(options) {
|
|
218
|
+
return this.ann({ options });
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
var ValueNode = class extends BaseNode {
|
|
222
|
+
kind = null;
|
|
223
|
+
};
|
|
224
|
+
var CustomNode = class _CustomNode extends BaseNode {
|
|
225
|
+
constructor(kind, defaultValue, annotations = {}, options = {}) {
|
|
226
|
+
super(defaultValue, annotations, options);
|
|
227
|
+
this.kind = kind;
|
|
228
|
+
}
|
|
229
|
+
kind;
|
|
230
|
+
with(annotations, options) {
|
|
231
|
+
return new _CustomNode(this.kind, this.default, annotations, options);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var expand = (options) => options.map((o) => typeof o === "string" ? { value: o, label: o } : o);
|
|
235
|
+
var f = {
|
|
236
|
+
number: (def) => new NumberNode(def),
|
|
237
|
+
boolean: (def) => new BooleanNode(def),
|
|
238
|
+
string: (def) => new StringNode(def),
|
|
239
|
+
color: (def) => new ColorNode(def),
|
|
240
|
+
/** A fixed set of choices. The default's literal type flows into the config
|
|
241
|
+
* type, so `f.enum('fast', ['fast', 'accurate'])` gives
|
|
242
|
+
* `'fast' | 'accurate'` with no `as const`. */
|
|
243
|
+
enum: (def, options) => new EnumNode(def, { options: expand(options) }),
|
|
244
|
+
/** A leaf with no declared kind: the rule chain decides, which is how a lab
|
|
245
|
+
* states a convention like "every `*Color` key is a color picker". */
|
|
246
|
+
value: (def) => new ValueNode(def),
|
|
247
|
+
/** A leaf of a kind a lab supplies the control for, through `controls`. */
|
|
248
|
+
custom: (kind, def, validate) => new CustomNode(kind, def, {}, validate ? { validate } : {}),
|
|
249
|
+
/** Collect leaves into an instrument's config. */
|
|
250
|
+
schema(nodes) {
|
|
251
|
+
return {
|
|
252
|
+
nodes,
|
|
253
|
+
defaults: () => {
|
|
254
|
+
const out = {};
|
|
255
|
+
for (const [key, node] of Object.entries(nodes)) out[key] = node.default;
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// src/config/rules.ts
|
|
263
|
+
function titleCase(key) {
|
|
264
|
+
const words = key.replace(/[_-]+/g, " ").split(/(?<=[a-z0-9])(?=[A-Z])/).join(" ").trim().toLowerCase();
|
|
265
|
+
return words.length === 0 ? words : words[0].toUpperCase() + words.slice(1);
|
|
266
|
+
}
|
|
267
|
+
var kindFromValue = (ctx) => {
|
|
268
|
+
const t = typeof ctx.default;
|
|
269
|
+
if (t === "boolean" || t === "number" || t === "string") return { kind: t };
|
|
270
|
+
return null;
|
|
271
|
+
};
|
|
272
|
+
var labelFromKey = (ctx) => ({ name: titleCase(ctx.key) });
|
|
273
|
+
var sliderWhenBounded = (ctx) => {
|
|
274
|
+
if (ctx.leaf.kind !== "number") return null;
|
|
275
|
+
const bounded = ctx.leaf.min !== void 0 && ctx.leaf.max !== void 0;
|
|
276
|
+
return bounded ? { control: "slider" } : null;
|
|
277
|
+
};
|
|
278
|
+
var descriptionDefault = () => ({ description: "" });
|
|
279
|
+
var builtinRules = [
|
|
280
|
+
kindFromValue,
|
|
281
|
+
labelFromKey,
|
|
282
|
+
sliderWhenBounded,
|
|
283
|
+
descriptionDefault
|
|
284
|
+
];
|
|
285
|
+
function applyRules(seed, ctx, rules) {
|
|
286
|
+
const acc = { ...seed };
|
|
287
|
+
for (const rule of rules) {
|
|
288
|
+
const patch = rule({ ...ctx, leaf: acc });
|
|
289
|
+
if (patch === null) continue;
|
|
290
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
291
|
+
if (v !== void 0 && acc[k] === void 0) {
|
|
292
|
+
acc[k] = v;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return acc;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/config/resolve.ts
|
|
300
|
+
function defined(annotations) {
|
|
301
|
+
const out = {};
|
|
302
|
+
for (const [k, v] of Object.entries(annotations)) if (v !== void 0) out[k] = v;
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
function resolveConfigSchema(schema, rules = []) {
|
|
306
|
+
const children = {};
|
|
307
|
+
const sectionOrder = [];
|
|
308
|
+
const sectionPaths = /* @__PURE__ */ new Map();
|
|
309
|
+
const showIf = /* @__PURE__ */ new Map();
|
|
310
|
+
const renderers = {};
|
|
311
|
+
const chain2 = [...rules, ...builtinRules];
|
|
312
|
+
for (const [key, node] of Object.entries(schema.nodes)) {
|
|
313
|
+
const seed = defined({
|
|
314
|
+
...node.annotations,
|
|
315
|
+
...node.kind === null ? {} : { kind: node.kind }
|
|
316
|
+
});
|
|
317
|
+
const patch = applyRules(seed, { key, path: key, default: node.default }, chain2);
|
|
318
|
+
children[key] = { ...patch, default: node.default };
|
|
319
|
+
const { section, showIf: predicate, render } = node.options;
|
|
320
|
+
if (section !== void 0) {
|
|
321
|
+
if (!sectionPaths.has(section)) {
|
|
322
|
+
sectionOrder.push(section);
|
|
323
|
+
sectionPaths.set(section, []);
|
|
324
|
+
}
|
|
325
|
+
sectionPaths.get(section)?.push(key);
|
|
326
|
+
}
|
|
327
|
+
if (predicate) showIf.set(key, predicate);
|
|
328
|
+
if (render) renderers[key] = render;
|
|
329
|
+
}
|
|
330
|
+
const group = { name: "", children };
|
|
331
|
+
const sections = sectionOrder.map((label) => ({
|
|
332
|
+
label,
|
|
333
|
+
paths: sectionPaths.get(label) ?? []
|
|
334
|
+
}));
|
|
335
|
+
return { group, sections, showIf, renderers };
|
|
336
|
+
}
|
|
337
|
+
var LabContext = createContext(null);
|
|
338
|
+
function useLabContext() {
|
|
339
|
+
const ctx = useContext(LabContext);
|
|
340
|
+
if (ctx === null) {
|
|
341
|
+
throw new Error("useLabContext must be used inside <Lab>");
|
|
342
|
+
}
|
|
343
|
+
return ctx;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/config/useConfigSchema.ts
|
|
347
|
+
var NO_RULES = [];
|
|
348
|
+
function useConfigSchema(instrument) {
|
|
349
|
+
const lab = useContext(LabContext);
|
|
350
|
+
const rules = lab?.configRules ?? NO_RULES;
|
|
351
|
+
return useMemo(
|
|
352
|
+
() => instrument.config ? resolveConfigSchema(instrument.config, rules) : fromConfigFields(instrument.configSchema?.() ?? []),
|
|
353
|
+
[instrument, rules]
|
|
354
|
+
);
|
|
355
|
+
}
|
|
33
356
|
|
|
34
357
|
// src/instrument/defineInstrument.ts
|
|
35
358
|
function defineInstrument(spec) {
|
|
359
|
+
if (spec.config && !("defaultConfig" in spec && spec.defaultConfig)) {
|
|
360
|
+
const schema = spec.config;
|
|
361
|
+
return { ...spec, defaultConfig: () => schema.defaults() };
|
|
362
|
+
}
|
|
36
363
|
return spec;
|
|
37
364
|
}
|
|
38
365
|
|
|
39
366
|
// src/instrument/validateConfigSchema.ts
|
|
40
|
-
var KNOWN_TYPES = ["slider", "checkbox", "select", "number", "text", "color"];
|
|
41
367
|
function validateConfigSchema(fields) {
|
|
42
368
|
const errors = [];
|
|
43
369
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -52,10 +378,6 @@ function validateConfigSchema(fields) {
|
|
|
52
378
|
if (field.label === "") {
|
|
53
379
|
errors.push(`Field "${field.key}" has empty label`);
|
|
54
380
|
}
|
|
55
|
-
if (!KNOWN_TYPES.includes(field.type)) {
|
|
56
|
-
errors.push(`Unknown field type: "${field.type}" on key "${field.key}"`);
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
381
|
if (field.type === "slider") {
|
|
60
382
|
if (!Number.isFinite(field.default)) {
|
|
61
383
|
errors.push(`Field "${field.key}": default must be a finite number`);
|
|
@@ -90,7 +412,7 @@ function validateConfigSchema(fields) {
|
|
|
90
412
|
return { valid: errors.length === 0, errors };
|
|
91
413
|
}
|
|
92
414
|
|
|
93
|
-
// ../theme/dist/chunk-
|
|
415
|
+
// ../theme/dist/chunk-O32P3G22.js
|
|
94
416
|
var THEME_SOURCES = {
|
|
95
417
|
"weasel": {
|
|
96
418
|
"primitives": {
|
|
@@ -276,6 +598,15 @@ var THEME_SOURCES = {
|
|
|
276
598
|
"type": "dimension",
|
|
277
599
|
"value": "5px"
|
|
278
600
|
},
|
|
601
|
+
"radius-lg": {
|
|
602
|
+
"type": "dimension",
|
|
603
|
+
"value": "14px"
|
|
604
|
+
},
|
|
605
|
+
"radius-pill": {
|
|
606
|
+
"type": "dimension",
|
|
607
|
+
"value": "999px",
|
|
608
|
+
"description": "Fully rounded ends \u2014 segmented controls, progress tracks."
|
|
609
|
+
},
|
|
279
610
|
"border-w": {
|
|
280
611
|
"type": "dimension",
|
|
281
612
|
"value": "1px"
|
|
@@ -293,29 +624,47 @@ var THEME_SOURCES = {
|
|
|
293
624
|
"tb-height": {
|
|
294
625
|
"type": "dimension",
|
|
295
626
|
"value": "28px",
|
|
296
|
-
"description": "Height of a horizontal toolbar strip (ActionsBar, OptionsBar,
|
|
297
|
-
},
|
|
298
|
-
"radius-lg": {
|
|
299
|
-
"type": "dimension",
|
|
300
|
-
"value": "14px"
|
|
627
|
+
"description": "Height of a horizontal toolbar strip (ActionsBar, OptionsBar, ToolOptionsBar). Taller than control-h so the controls inside a strip are not flush with its edges."
|
|
301
628
|
},
|
|
302
629
|
"control-h": {
|
|
303
630
|
"type": "dimension",
|
|
304
|
-
"value": "
|
|
305
|
-
"description": "Height of an interactive control \u2014 button, input, select. Distinct from tb-height, which sizes the strip a row of them sits in."
|
|
631
|
+
"value": "24px",
|
|
632
|
+
"description": "Height of an interactive control \u2014 button, input, select, segmented control. Distinct from tb-height, which sizes the strip a row of them sits in."
|
|
306
633
|
},
|
|
307
634
|
"glass-blur": {
|
|
308
635
|
"type": "dimension",
|
|
309
636
|
"value": "3px",
|
|
310
637
|
"description": "Backdrop blur radius for frosted surfaces. Pairs with glass-tint."
|
|
311
638
|
},
|
|
312
|
-
"font-size": {
|
|
639
|
+
"font-size-2xs": {
|
|
640
|
+
"type": "dimension",
|
|
641
|
+
"value": "9px",
|
|
642
|
+
"description": "Shortcut keys and index badges. The floor for chrome text."
|
|
643
|
+
},
|
|
644
|
+
"font-size-xs": {
|
|
313
645
|
"type": "dimension",
|
|
314
|
-
"value": "
|
|
646
|
+
"value": "10px",
|
|
647
|
+
"description": "Tool-button labels under an icon."
|
|
315
648
|
},
|
|
316
649
|
"font-size-sm": {
|
|
317
650
|
"type": "dimension",
|
|
318
|
-
"value": "11px"
|
|
651
|
+
"value": "11px",
|
|
652
|
+
"description": "Chrome labels, status readouts, section headings."
|
|
653
|
+
},
|
|
654
|
+
"font-size": {
|
|
655
|
+
"type": "dimension",
|
|
656
|
+
"value": "13px",
|
|
657
|
+
"description": "Controls and body text."
|
|
658
|
+
},
|
|
659
|
+
"font-size-lg": {
|
|
660
|
+
"type": "dimension",
|
|
661
|
+
"value": "16px",
|
|
662
|
+
"description": "Panel and dialog titles."
|
|
663
|
+
},
|
|
664
|
+
"font-size-xl": {
|
|
665
|
+
"type": "dimension",
|
|
666
|
+
"value": "20px",
|
|
667
|
+
"description": "The largest chrome text \u2014 a lab or app title."
|
|
319
668
|
},
|
|
320
669
|
"space-xs": {
|
|
321
670
|
"type": "dimension",
|
|
@@ -333,6 +682,20 @@ var THEME_SOURCES = {
|
|
|
333
682
|
"type": "dimension",
|
|
334
683
|
"value": "16px"
|
|
335
684
|
},
|
|
685
|
+
"tracking-none": {
|
|
686
|
+
"type": "dimension",
|
|
687
|
+
"value": "0"
|
|
688
|
+
},
|
|
689
|
+
"tracking-wide": {
|
|
690
|
+
"type": "dimension",
|
|
691
|
+
"value": "0.06em",
|
|
692
|
+
"description": "Uppercase section headings."
|
|
693
|
+
},
|
|
694
|
+
"tracking-wider": {
|
|
695
|
+
"type": "dimension",
|
|
696
|
+
"value": "0.08em",
|
|
697
|
+
"description": "Uppercase at 11px and below."
|
|
698
|
+
},
|
|
336
699
|
"z-toolbar": {
|
|
337
700
|
"type": "number",
|
|
338
701
|
"value": 10
|
|
@@ -345,6 +708,21 @@ var THEME_SOURCES = {
|
|
|
345
708
|
"type": "number",
|
|
346
709
|
"value": 30
|
|
347
710
|
},
|
|
711
|
+
"leading-tight": {
|
|
712
|
+
"type": "number",
|
|
713
|
+
"value": 1,
|
|
714
|
+
"description": "Single-line controls; the box sets the height."
|
|
715
|
+
},
|
|
716
|
+
"leading-snug": {
|
|
717
|
+
"type": "number",
|
|
718
|
+
"value": 1.2,
|
|
719
|
+
"description": "Headings and two-line labels."
|
|
720
|
+
},
|
|
721
|
+
"leading": {
|
|
722
|
+
"type": "number",
|
|
723
|
+
"value": 1.4,
|
|
724
|
+
"description": "Body and anything that wraps."
|
|
725
|
+
},
|
|
348
726
|
"backdrop": {
|
|
349
727
|
"type": "gradient",
|
|
350
728
|
"value": "none",
|
|
@@ -497,6 +875,16 @@ var THEME_SOURCES = {
|
|
|
497
875
|
"type": "color",
|
|
498
876
|
"value": "{color.gray-900}",
|
|
499
877
|
"description": "Text drawn on a filled foreground-colored element (e.g. a slider thumb). Flips with the mode, so it is a semantic, not an alias to a primitive."
|
|
878
|
+
},
|
|
879
|
+
"shadow": {
|
|
880
|
+
"type": "color",
|
|
881
|
+
"value": "rgba(0, 0, 0, 0.6)",
|
|
882
|
+
"description": "Elevation. Always darker than any surface \u2014 never derived from fg, which is near-white on dark."
|
|
883
|
+
},
|
|
884
|
+
"border-raised": {
|
|
885
|
+
"type": "color",
|
|
886
|
+
"value": "{color.gray-400}",
|
|
887
|
+
"description": "The line around a raised surface. Clears 3:1 against surface and surface-raised in either mode, which the general-purpose border does not; over surface-sunken it does not, so a panel on a sunken field needs its own answer."
|
|
500
888
|
}
|
|
501
889
|
},
|
|
502
890
|
"light": {
|
|
@@ -541,6 +929,14 @@ var THEME_SOURCES = {
|
|
|
541
929
|
"type": "color",
|
|
542
930
|
"value": "{color.gray-50}",
|
|
543
931
|
"description": "Flips with the mode \u2014 see the dark layer."
|
|
932
|
+
},
|
|
933
|
+
"shadow": {
|
|
934
|
+
"type": "color",
|
|
935
|
+
"value": "rgba(0, 0, 0, 0.18)"
|
|
936
|
+
},
|
|
937
|
+
"border-raised": {
|
|
938
|
+
"type": "color",
|
|
939
|
+
"value": "{color.gray-400}"
|
|
544
940
|
}
|
|
545
941
|
}
|
|
546
942
|
}
|
|
@@ -558,10 +954,10 @@ function hexToRgba(hex, alpha) {
|
|
|
558
954
|
if (!m) throw new Error(`Expected a hex color, got "${hex}"`);
|
|
559
955
|
const body = m[1];
|
|
560
956
|
const full = body.length === 3 ? body.replace(/./g, (c) => c + c) : body;
|
|
561
|
-
const
|
|
957
|
+
const r2 = Number.parseInt(full.slice(0, 2), 16);
|
|
562
958
|
const g = Number.parseInt(full.slice(2, 4), 16);
|
|
563
959
|
const b = Number.parseInt(full.slice(4, 6), 16);
|
|
564
|
-
return `rgba(${
|
|
960
|
+
return `rgba(${r2}, ${g}, ${b}, ${alpha})`;
|
|
565
961
|
}
|
|
566
962
|
var REF = /^\{([^}]+)\}$/;
|
|
567
963
|
function refTarget(value) {
|
|
@@ -573,7 +969,7 @@ function refTarget(value) {
|
|
|
573
969
|
return dot === -1 ? path : path.slice(dot + 1);
|
|
574
970
|
}
|
|
575
971
|
function fontStack(value) {
|
|
576
|
-
return value.map((
|
|
972
|
+
return value.map((f2) => typeof f2 === "string" && /\s/.test(f2) ? `'${f2}'` : String(f2)).join(", ");
|
|
577
973
|
}
|
|
578
974
|
function serialize(type, value) {
|
|
579
975
|
if (Array.isArray(value)) {
|
|
@@ -780,7 +1176,9 @@ var interstellar_tokens_default = {
|
|
|
780
1176
|
"fg-subtle": { $value: "#5a5a78" },
|
|
781
1177
|
accent: { $value: "#b08adb" },
|
|
782
1178
|
"accent-hover": { $value: "#c6a3ee" },
|
|
783
|
-
"focus-ring": { $value: "rgba(176, 138, 219, 0.45)" }
|
|
1179
|
+
"focus-ring": { $value: "rgba(176, 138, 219, 0.45)" },
|
|
1180
|
+
shadow: { $value: "rgba(0, 0, 0, 0.7)" },
|
|
1181
|
+
"border-raised": { $value: "rgba(255, 255, 255, 0.36)" }
|
|
784
1182
|
},
|
|
785
1183
|
gradient: {
|
|
786
1184
|
$type: "gradient",
|
|
@@ -802,7 +1200,9 @@ var interstellar_tokens_default = {
|
|
|
802
1200
|
"fg-subtle": { $value: "#a89b85" },
|
|
803
1201
|
accent: { $value: "#a86f3c" },
|
|
804
1202
|
"accent-hover": { $value: "#c2854f" },
|
|
805
|
-
"focus-ring": { $value: "rgba(168, 111, 60, 0.35)" }
|
|
1203
|
+
"focus-ring": { $value: "rgba(168, 111, 60, 0.35)" },
|
|
1204
|
+
shadow: { $value: "rgba(80, 60, 40, 0.18)" },
|
|
1205
|
+
"border-raised": { $value: "#9e8b71" }
|
|
806
1206
|
}
|
|
807
1207
|
}
|
|
808
1208
|
}
|
|
@@ -835,13 +1235,13 @@ function useDragDrop({
|
|
|
835
1235
|
const [drag, setDrag] = useState(null);
|
|
836
1236
|
const dragRef = useRef(null);
|
|
837
1237
|
dragRef.current = drag;
|
|
838
|
-
const
|
|
1238
|
+
const pendingPos = useRef(null);
|
|
839
1239
|
const isOverCanvas = useCallback(
|
|
840
1240
|
(screenPos) => {
|
|
841
1241
|
const el = canvasContainerRef.current;
|
|
842
1242
|
if (!el) return false;
|
|
843
|
-
const
|
|
844
|
-
return screenPos.x >=
|
|
1243
|
+
const r2 = el.getBoundingClientRect();
|
|
1244
|
+
return screenPos.x >= r2.left && screenPos.x <= r2.right && screenPos.y >= r2.top && screenPos.y <= r2.bottom;
|
|
845
1245
|
},
|
|
846
1246
|
[canvasContainerRef]
|
|
847
1247
|
);
|
|
@@ -849,42 +1249,44 @@ function useDragDrop({
|
|
|
849
1249
|
(screenPos) => {
|
|
850
1250
|
const el = canvasContainerRef.current;
|
|
851
1251
|
if (!el) return null;
|
|
852
|
-
const
|
|
853
|
-
return screenToWorld({ x: screenPos.x -
|
|
1252
|
+
const r2 = el.getBoundingClientRect();
|
|
1253
|
+
return screenToWorld({ x: screenPos.x - r2.left, y: screenPos.y - r2.top }, view);
|
|
854
1254
|
},
|
|
855
1255
|
[canvasContainerRef, view]
|
|
856
1256
|
);
|
|
1257
|
+
const frameLoop = useVisibleRaf(() => {
|
|
1258
|
+
const screenPos = pendingPos.current;
|
|
1259
|
+
pendingPos.current = null;
|
|
1260
|
+
const active = dragRef.current;
|
|
1261
|
+
if (!active || !screenPos) return;
|
|
1262
|
+
let feedback = null;
|
|
1263
|
+
if (capability.onDragOver && isOverCanvas(screenPos)) {
|
|
1264
|
+
const world = screenToWorldFromContainer(screenPos);
|
|
1265
|
+
if (world) feedback = capability.onDragOver(world, active.item, state, config);
|
|
1266
|
+
}
|
|
1267
|
+
setDrag({ ...active, screenPos, feedback });
|
|
1268
|
+
});
|
|
857
1269
|
useEffect(() => {
|
|
858
1270
|
if (!drag) return;
|
|
859
1271
|
const handleMove = (e) => {
|
|
860
1272
|
const screenPos = { x: e.clientX, y: e.clientY };
|
|
861
1273
|
const current = dragRef.current;
|
|
862
1274
|
if (!current) return;
|
|
863
|
-
if (
|
|
1275
|
+
if (pendingPos.current !== null) {
|
|
1276
|
+
pendingPos.current = screenPos;
|
|
864
1277
|
dragRef.current = { ...current, screenPos };
|
|
865
1278
|
setDrag(dragRef.current);
|
|
866
1279
|
return;
|
|
867
1280
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
const active = dragRef.current;
|
|
871
|
-
if (!active) return;
|
|
872
|
-
let feedback = null;
|
|
873
|
-
if (capability.onDragOver && isOverCanvas(screenPos)) {
|
|
874
|
-
const world = screenToWorldFromContainer(screenPos);
|
|
875
|
-
if (world) feedback = capability.onDragOver(world, active.item, state, config);
|
|
876
|
-
}
|
|
877
|
-
setDrag({ ...active, screenPos, feedback });
|
|
878
|
-
});
|
|
1281
|
+
pendingPos.current = screenPos;
|
|
1282
|
+
frameLoop.request();
|
|
879
1283
|
};
|
|
880
1284
|
const handleUp = (e) => {
|
|
881
1285
|
const screenPos = { x: e.clientX, y: e.clientY };
|
|
882
1286
|
const active = dragRef.current;
|
|
883
1287
|
if (!active) return;
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
rafThrottle.current = null;
|
|
887
|
-
}
|
|
1288
|
+
pendingPos.current = null;
|
|
1289
|
+
frameLoop.cancel();
|
|
888
1290
|
if (isOverCanvas(screenPos)) {
|
|
889
1291
|
const world = screenToWorldFromContainer(screenPos);
|
|
890
1292
|
if (world) {
|
|
@@ -901,7 +1303,17 @@ function useDragDrop({
|
|
|
901
1303
|
window.removeEventListener("pointermove", handleMove);
|
|
902
1304
|
window.removeEventListener("pointerup", handleUp);
|
|
903
1305
|
};
|
|
904
|
-
}, [
|
|
1306
|
+
}, [
|
|
1307
|
+
drag,
|
|
1308
|
+
capability,
|
|
1309
|
+
state,
|
|
1310
|
+
config,
|
|
1311
|
+
setState,
|
|
1312
|
+
emit,
|
|
1313
|
+
frameLoop,
|
|
1314
|
+
isOverCanvas,
|
|
1315
|
+
screenToWorldFromContainer
|
|
1316
|
+
]);
|
|
905
1317
|
const startDrag = useCallback((item, originScreenPos) => {
|
|
906
1318
|
setDrag({ item, screenPos: originScreenPos, feedback: null });
|
|
907
1319
|
}, []);
|
|
@@ -926,119 +1338,51 @@ function Palette({ items, onDragStart, className }) {
|
|
|
926
1338
|
item.id
|
|
927
1339
|
)) });
|
|
928
1340
|
}
|
|
929
|
-
var LabContext = createContext(null);
|
|
930
|
-
function useLabContext() {
|
|
931
|
-
const ctx = useContext(LabContext);
|
|
932
|
-
if (ctx === null) {
|
|
933
|
-
throw new Error("useLabContext must be used inside <Lab>");
|
|
934
|
-
}
|
|
935
|
-
return ctx;
|
|
936
|
-
}
|
|
937
|
-
function DefaultSidebar({ ctx }) {
|
|
938
|
-
if (ctx.configFields.length === 0) {
|
|
939
|
-
return /* @__PURE__ */ jsx(Sidebar, { title: ctx.instrumentName, children: /* @__PURE__ */ jsxs("div", { className: "lk-sidebar__placeholder", children: [
|
|
940
|
-
ctx.instrumentName,
|
|
941
|
-
" has no config fields."
|
|
942
|
-
] }) });
|
|
943
|
-
}
|
|
944
|
-
return /* @__PURE__ */ jsx(Sidebar, { title: ctx.instrumentName, children: /* @__PURE__ */ jsx(
|
|
945
|
-
ControlPanel,
|
|
946
|
-
{
|
|
947
|
-
fields: ctx.configFields,
|
|
948
|
-
config: ctx.config,
|
|
949
|
-
setConfig: (key, value) => ctx.setConfig(key, value)
|
|
950
|
-
}
|
|
951
|
-
) });
|
|
952
|
-
}
|
|
953
1341
|
|
|
954
|
-
// src/
|
|
955
|
-
var
|
|
956
|
-
function
|
|
957
|
-
if (
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
return
|
|
1342
|
+
// src/state/view.ts
|
|
1343
|
+
var DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } };
|
|
1344
|
+
function as2DView(view) {
|
|
1345
|
+
if (typeof view !== "object" || view === null) return null;
|
|
1346
|
+
const v = view;
|
|
1347
|
+
if (typeof v.zoom !== "number") return null;
|
|
1348
|
+
if (typeof v.pan !== "object" || v.pan === null) return null;
|
|
1349
|
+
if (typeof v.pan.x !== "number" || typeof v.pan.y !== "number") return null;
|
|
1350
|
+
return { zoom: v.zoom, pan: { x: v.pan.x, y: v.pan.y } };
|
|
963
1351
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
if (zoom >= 100) return `${Math.round(zoom).toLocaleString("en-US")}x`;
|
|
968
|
-
return `${formatNumber(Math.round(zoom * 10) / 10)}x`;
|
|
1352
|
+
var TrialDragContext = createContext(null);
|
|
1353
|
+
function useTrialDrag() {
|
|
1354
|
+
return useContext(TrialDragContext);
|
|
969
1355
|
}
|
|
970
|
-
function
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
/* @__PURE__ */ jsx(
|
|
1356
|
+
function Draggable({ nodeId, title, children }) {
|
|
1357
|
+
const handlers = useDragHandle(nodeId);
|
|
1358
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-trial__titlebar lk-trial__titlebar--draggable", ...handlers, children: [
|
|
1359
|
+
/* @__PURE__ */ jsx("span", { className: "lk-trial__title", children: title }),
|
|
1360
|
+
children
|
|
974
1361
|
] });
|
|
975
1362
|
}
|
|
976
|
-
function
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
onClick: ctx.redo,
|
|
984
|
-
disabled: !ctx.canRedo,
|
|
985
|
-
title: "Redo (Cmd/Ctrl+Shift+Z)",
|
|
986
|
-
children: "Redo"
|
|
987
|
-
}
|
|
988
|
-
),
|
|
989
|
-
/* @__PURE__ */ jsx(Toolbar.Spacer, {})
|
|
990
|
-
] }),
|
|
991
|
-
ctx.hasCanvas && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
992
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.zoomOut, title: "Zoom out", children: "\u2212" }),
|
|
993
|
-
/* @__PURE__ */ jsx("span", { className: "lk-toolbar__zoom-label", children: formatZoom(ctx.zoom) }),
|
|
994
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.zoomIn, title: "Zoom in", children: "+" }),
|
|
995
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.resetZoom, title: "Reset zoom", children: "1:1" }),
|
|
996
|
-
/* @__PURE__ */ jsx(Toolbar.Spacer, {})
|
|
997
|
-
] }),
|
|
998
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: () => ctx.saveSnapshot(), title: "Save snapshot (Cmd/Ctrl+S)", children: "Save" }),
|
|
999
|
-
ctx.savedSnapshots.length > 0 && /* @__PURE__ */ jsxs(
|
|
1000
|
-
"select",
|
|
1001
|
-
{
|
|
1002
|
-
className: "lk-toolbar__load-select",
|
|
1003
|
-
"aria-label": "Load snapshot",
|
|
1004
|
-
defaultValue: "",
|
|
1005
|
-
onChange: (e) => {
|
|
1006
|
-
const id = e.target.value;
|
|
1007
|
-
if (id) {
|
|
1008
|
-
ctx.loadSnapshot(id);
|
|
1009
|
-
e.target.value = "";
|
|
1010
|
-
}
|
|
1011
|
-
},
|
|
1012
|
-
children: [
|
|
1013
|
-
/* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Load\u2026" }),
|
|
1014
|
-
ctx.savedSnapshots.map((sn) => /* @__PURE__ */ jsx("option", { value: sn.id, children: sn.name }, sn.id))
|
|
1015
|
-
]
|
|
1016
|
-
}
|
|
1017
|
-
),
|
|
1018
|
-
/* @__PURE__ */ jsx(Toolbar.Spacer, {}),
|
|
1019
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.clone, title: "Clone trial", children: "Clone" }),
|
|
1020
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.reset, title: "Reset trial", children: "Reset" }),
|
|
1021
|
-
/* @__PURE__ */ jsx(
|
|
1022
|
-
Toolbar.Button,
|
|
1023
|
-
{
|
|
1024
|
-
onClick: ctx.close,
|
|
1025
|
-
disabled: ctx.isLastTrial,
|
|
1026
|
-
title: ctx.isLastTrial ? "Cannot close the last trial" : "Close trial",
|
|
1027
|
-
children: "Close"
|
|
1028
|
-
}
|
|
1029
|
-
)
|
|
1363
|
+
function TrialTitleBar({ title, children }) {
|
|
1364
|
+
const drag = useTrialDrag();
|
|
1365
|
+
if (drag)
|
|
1366
|
+
return /* @__PURE__ */ jsx(Draggable, { nodeId: drag.nodeId, title, children });
|
|
1367
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-trial__titlebar", children: [
|
|
1368
|
+
/* @__PURE__ */ jsx("span", { className: "lk-trial__title", children: title }),
|
|
1369
|
+
children
|
|
1030
1370
|
] });
|
|
1031
1371
|
}
|
|
1372
|
+
var NO_OP = () => {
|
|
1373
|
+
};
|
|
1032
1374
|
function TrialChrome({
|
|
1033
1375
|
trialId,
|
|
1034
1376
|
record,
|
|
1035
1377
|
instrument,
|
|
1036
1378
|
isLastTrial,
|
|
1037
|
-
toolbar,
|
|
1038
|
-
sidebar,
|
|
1039
|
-
statusBar,
|
|
1040
1379
|
undoBindings,
|
|
1041
|
-
|
|
1380
|
+
job,
|
|
1381
|
+
trialChrome,
|
|
1382
|
+
chrome,
|
|
1383
|
+
suppress,
|
|
1384
|
+
activeToolId = null,
|
|
1385
|
+
setActiveTool = NO_OP,
|
|
1042
1386
|
children
|
|
1043
1387
|
}) {
|
|
1044
1388
|
const lab = useLabContext();
|
|
@@ -1047,40 +1391,25 @@ function TrialChrome({
|
|
|
1047
1391
|
const updateTrialView = useStore(storeCtx.store, (s) => s.updateTrialView);
|
|
1048
1392
|
const updateTrialConfig = useStore(storeCtx.store, (s) => s.updateTrialConfig);
|
|
1049
1393
|
const updateTrialState = useStore(storeCtx.store, (s) => s.updateTrialState);
|
|
1050
|
-
const
|
|
1394
|
+
const view2d = as2DView(record.view);
|
|
1395
|
+
const configSchema = useConfigSchema(instrument);
|
|
1396
|
+
const ctx = useMemo(() => {
|
|
1051
1397
|
const setZoom = (z) => {
|
|
1052
|
-
|
|
1398
|
+
if (!view2d) return;
|
|
1399
|
+
updateTrialView(trialId, { ...view2d, zoom: z });
|
|
1053
1400
|
};
|
|
1054
1401
|
return {
|
|
1055
1402
|
trialId,
|
|
1056
1403
|
instrumentName: record.instrumentName,
|
|
1057
|
-
|
|
1404
|
+
isLastTrial,
|
|
1405
|
+
zoom: view2d ? view2d.zoom : null,
|
|
1406
|
+
setZoom,
|
|
1058
1407
|
canUndo: undoBindings?.canUndo ?? false,
|
|
1059
1408
|
canRedo: undoBindings?.canRedo ?? false,
|
|
1060
|
-
undo: undoBindings?.undo ??
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
zoom: record.view.zoom,
|
|
1065
|
-
setZoom,
|
|
1066
|
-
zoomIn: () => setZoom(record.view.zoom * 1.25),
|
|
1067
|
-
zoomOut: () => setZoom(record.view.zoom * 0.8),
|
|
1068
|
-
resetZoom: () => setZoom(1),
|
|
1069
|
-
hasCanvas: instrument.canvas != null,
|
|
1070
|
-
savedSnapshots: lab.savedSnapshots.filter((s) => s.trialId === trialId),
|
|
1071
|
-
saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
|
|
1072
|
-
loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
|
|
1073
|
-
clone: () => lab.cloneTrial(trialId),
|
|
1074
|
-
reset: () => lab.resetTrial(trialId),
|
|
1075
|
-
close: () => lab.closeTrial(trialId),
|
|
1076
|
-
isLastTrial
|
|
1077
|
-
};
|
|
1078
|
-
}, [trialId, record, instrument, lab, isLastTrial, updateTrialView, undoBindings]);
|
|
1079
|
-
const sidebarCtx = useMemo(
|
|
1080
|
-
() => ({
|
|
1081
|
-
trialId,
|
|
1082
|
-
instrumentName: record.instrumentName,
|
|
1083
|
-
configFields: instrument.configSchema?.() ?? [],
|
|
1409
|
+
undo: undoBindings?.undo ?? NO_OP,
|
|
1410
|
+
redo: undoBindings?.redo ?? NO_OP,
|
|
1411
|
+
configSchema,
|
|
1412
|
+
configFields: instrument.config ? [] : instrument.configSchema?.() ?? [],
|
|
1084
1413
|
config: record.config,
|
|
1085
1414
|
setConfig: (key, value) => {
|
|
1086
1415
|
const prevConfig = record.config;
|
|
@@ -1095,25 +1424,54 @@ function TrialChrome({
|
|
|
1095
1424
|
const nextState = instrument.onConfigChange(nextConfig, prevConfig, record.state);
|
|
1096
1425
|
updateTrialState(trialId, nextState);
|
|
1097
1426
|
}
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1427
|
+
},
|
|
1428
|
+
savedSnapshots: lab.savedSnapshots.filter((s) => s.trialId === trialId),
|
|
1429
|
+
saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
|
|
1430
|
+
loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
|
|
1431
|
+
clone: () => lab.cloneTrial(trialId),
|
|
1432
|
+
reset: () => lab.resetTrial(trialId),
|
|
1433
|
+
close: () => lab.closeTrial(trialId),
|
|
1434
|
+
activeToolId,
|
|
1435
|
+
setActiveTool
|
|
1436
|
+
};
|
|
1437
|
+
}, [
|
|
1103
1438
|
trialId,
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1439
|
+
record,
|
|
1440
|
+
instrument,
|
|
1441
|
+
lab,
|
|
1442
|
+
isLastTrial,
|
|
1443
|
+
updateTrialView,
|
|
1444
|
+
updateTrialConfig,
|
|
1445
|
+
updateTrialState,
|
|
1446
|
+
undoBindings,
|
|
1447
|
+
view2d,
|
|
1448
|
+
activeToolId,
|
|
1449
|
+
setActiveTool,
|
|
1450
|
+
configSchema
|
|
1451
|
+
]);
|
|
1452
|
+
const contributions = useMemo(
|
|
1453
|
+
() => suppressContributions(
|
|
1454
|
+
mergeContributions(
|
|
1455
|
+
builtinContributions(instrument, ctx, lab.controls),
|
|
1456
|
+
[...instrument.chrome ?? []],
|
|
1457
|
+
[...trialChrome ?? []],
|
|
1458
|
+
[...chrome ?? []]
|
|
1459
|
+
),
|
|
1460
|
+
suppress ?? []
|
|
1461
|
+
),
|
|
1462
|
+
[instrument, ctx, trialChrome, chrome, suppress, lab.controls]
|
|
1463
|
+
);
|
|
1464
|
+
const inRegion = (region) => contributions.filter((c) => c.region === region);
|
|
1107
1465
|
const handleKeyDown = (e) => {
|
|
1108
1466
|
const mod = e.metaKey || e.ctrlKey;
|
|
1109
1467
|
if (!mod) return;
|
|
1110
1468
|
if (e.key === "z" || e.key === "Z") {
|
|
1111
1469
|
e.preventDefault();
|
|
1112
|
-
if (e.shiftKey)
|
|
1113
|
-
else
|
|
1470
|
+
if (e.shiftKey) ctx.redo();
|
|
1471
|
+
else ctx.undo();
|
|
1114
1472
|
} else if (e.key === "s" || e.key === "S") {
|
|
1115
1473
|
e.preventDefault();
|
|
1116
|
-
|
|
1474
|
+
ctx.saveSnapshot();
|
|
1117
1475
|
}
|
|
1118
1476
|
};
|
|
1119
1477
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1121,22 +1479,28 @@ function TrialChrome({
|
|
|
1121
1479
|
{
|
|
1122
1480
|
className: "lk-trial",
|
|
1123
1481
|
"aria-label": `Trial ${record.instrumentName}`,
|
|
1482
|
+
tabIndex: -1,
|
|
1124
1483
|
onKeyDown: handleKeyDown,
|
|
1125
1484
|
children: [
|
|
1126
|
-
/* @__PURE__ */ jsx(
|
|
1485
|
+
/* @__PURE__ */ jsx(TrialTitleBar, { title: record.instrumentName, children: /* @__PURE__ */ jsx(TitleBarRegion, { contributions: inRegion("titlebar"), ctx }) }),
|
|
1486
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial__toolbar", children: /* @__PURE__ */ jsx(ToolbarRegion, { contributions: inRegion("toolbar"), ctx }) }),
|
|
1127
1487
|
/* @__PURE__ */ jsxs("div", { className: "lk-trial__body", children: [
|
|
1128
|
-
/* @__PURE__ */
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1488
|
+
/* @__PURE__ */ jsx(PaletteRegion, { contributions: inRegion("palette"), ctx }),
|
|
1489
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial__sidebar", children: /* @__PURE__ */ jsx(SidebarRegion, { contributions: inRegion("sidebar"), ctx }) }),
|
|
1490
|
+
/* @__PURE__ */ jsxs("div", { className: `lk-trial__content${instrument.canvas ? " lk-trial__content--flush" : ""}`, children: [
|
|
1491
|
+
children,
|
|
1492
|
+
/* @__PURE__ */ jsx(ViewportRegion, { contributions: inRegion("viewport"), ctx })
|
|
1493
|
+
] })
|
|
1133
1494
|
] }),
|
|
1134
|
-
/* @__PURE__ */
|
|
1495
|
+
/* @__PURE__ */ jsxs("div", { className: "lk-trial__status", children: [
|
|
1496
|
+
job ? /* @__PURE__ */ jsx(JobProgress, { job }) : null,
|
|
1497
|
+
/* @__PURE__ */ jsx(StatusRegion, { contributions: inRegion("status"), ctx })
|
|
1498
|
+
] })
|
|
1135
1499
|
]
|
|
1136
1500
|
}
|
|
1137
1501
|
);
|
|
1138
1502
|
}
|
|
1139
|
-
function Trial({ id }) {
|
|
1503
|
+
function Trial({ id, chrome, suppress }) {
|
|
1140
1504
|
const lab = useLabContext();
|
|
1141
1505
|
const storeCtx = useContext(LabStoreContext);
|
|
1142
1506
|
if (!storeCtx) throw new Error("[labkit] <Trial> requires <LabStoreProvider>");
|
|
@@ -1160,16 +1524,21 @@ function Trial({ id }) {
|
|
|
1160
1524
|
record,
|
|
1161
1525
|
instrument,
|
|
1162
1526
|
store: storeCtx.store,
|
|
1163
|
-
isLast: lab.trials.length <= 1
|
|
1527
|
+
isLast: lab.trials.length <= 1,
|
|
1528
|
+
chrome,
|
|
1529
|
+
suppress
|
|
1164
1530
|
}
|
|
1165
1531
|
);
|
|
1166
1532
|
}
|
|
1167
|
-
function TrialRuntime({ record, instrument, store, isLast }) {
|
|
1533
|
+
function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
1168
1534
|
const canvasContainerRef = useRef(null);
|
|
1169
1535
|
const updateTrialState = useStore(store, (s) => s.updateTrialState);
|
|
1170
1536
|
const updateTrialConfig = useStore(store, (s) => s.updateTrialConfig);
|
|
1171
1537
|
const updateTrialView = useStore(store, (s) => s.updateTrialView);
|
|
1172
1538
|
const updateTrialUndoStack = useStore(store, (s) => s.updateTrialUndoStack);
|
|
1539
|
+
const labToolId = useStore(store, (s) => s.activeToolId);
|
|
1540
|
+
const setLabTool = useStore(store, (s) => s.setLabTool);
|
|
1541
|
+
const setTrialTool = useStore(store, (s) => s.setTrialTool);
|
|
1173
1542
|
const busRef = useRef(null);
|
|
1174
1543
|
if (busRef.current === null) busRef.current = createEventBus();
|
|
1175
1544
|
const bus = busRef.current;
|
|
@@ -1186,6 +1555,21 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1186
1555
|
updateTrialUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
|
|
1187
1556
|
};
|
|
1188
1557
|
const setView = (v) => updateTrialView(record.id, v);
|
|
1558
|
+
const declaresTools = instrument.tools != null;
|
|
1559
|
+
const resolvedToolId = declaresTools ? record.activeToolId ?? instrument.tools?.initial ?? instrument.tools?.tools[0]?.id ?? null : labToolId;
|
|
1560
|
+
const setActiveTool = (id) => {
|
|
1561
|
+
if (declaresTools) setTrialTool(record.id, id);
|
|
1562
|
+
else setLabTool(id);
|
|
1563
|
+
};
|
|
1564
|
+
const view2d = as2DView(record.view);
|
|
1565
|
+
const jobCap = instrument.job;
|
|
1566
|
+
const job = useJob({
|
|
1567
|
+
capability: jobCap ?? { run: async function* () {
|
|
1568
|
+
}, onItem: (_i, st) => st },
|
|
1569
|
+
config: record.config,
|
|
1570
|
+
state: record.state,
|
|
1571
|
+
setState: (next) => updateTrialState(record.id, next)
|
|
1572
|
+
});
|
|
1189
1573
|
const renderCtx = {
|
|
1190
1574
|
state: record.state,
|
|
1191
1575
|
config: record.config,
|
|
@@ -1204,13 +1588,20 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1204
1588
|
},
|
|
1205
1589
|
trial: {
|
|
1206
1590
|
id: record.id,
|
|
1207
|
-
|
|
1208
|
-
|
|
1591
|
+
view: record.view,
|
|
1592
|
+
setView,
|
|
1593
|
+
zoom: view2d?.zoom ?? 1,
|
|
1594
|
+
setZoom: (z) => {
|
|
1595
|
+
if (!view2d) return;
|
|
1596
|
+
updateTrialView(record.id, { ...view2d, zoom: z });
|
|
1597
|
+
},
|
|
1598
|
+
activeToolId: resolvedToolId
|
|
1209
1599
|
},
|
|
1210
1600
|
emit: (event) => {
|
|
1211
1601
|
snapshotIfNeeded(event);
|
|
1212
1602
|
bus.emit(event);
|
|
1213
|
-
}
|
|
1603
|
+
},
|
|
1604
|
+
job: jobCap ? job : void 0
|
|
1214
1605
|
};
|
|
1215
1606
|
const undoBindings = undoCap ? {
|
|
1216
1607
|
canUndo: record.undoStack.past.length > 0,
|
|
@@ -1244,12 +1635,12 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1244
1635
|
}, [instrument.canvas, record.state, record.config, layerVisibility, layerOrder]);
|
|
1245
1636
|
const layerDescriptors = useMemo(() => {
|
|
1246
1637
|
if (!instrument.layers) return [];
|
|
1247
|
-
return instrument.layers.ids.map((
|
|
1638
|
+
return instrument.layers.ids.map((l) => typeof l === "string" ? { id: l, label: l } : l);
|
|
1248
1639
|
}, [instrument.layers]);
|
|
1249
1640
|
const dragDropResult = useDragDrop({
|
|
1250
1641
|
capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s) => s },
|
|
1251
1642
|
canvasContainerRef,
|
|
1252
|
-
view:
|
|
1643
|
+
view: view2d ?? DEFAULT_VIEW,
|
|
1253
1644
|
state: record.state,
|
|
1254
1645
|
config: record.config,
|
|
1255
1646
|
setState: (next) => {
|
|
@@ -1263,8 +1654,8 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1263
1654
|
});
|
|
1264
1655
|
const paletteItems = useMemo(() => {
|
|
1265
1656
|
if (!instrument.dragDrop) return [];
|
|
1266
|
-
const
|
|
1267
|
-
return typeof
|
|
1657
|
+
const p3 = instrument.dragDrop.palette;
|
|
1658
|
+
return typeof p3 === "function" ? p3(record.state, record.config) : p3;
|
|
1268
1659
|
}, [instrument.dragDrop, record.state, record.config]);
|
|
1269
1660
|
const layersWithFeedback = useMemo(() => {
|
|
1270
1661
|
if (!dragDropResult.drag?.feedback) return canvasLayers;
|
|
@@ -1278,12 +1669,12 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1278
1669
|
render: (ctx) => {
|
|
1279
1670
|
const el = canvasContainerRef.current;
|
|
1280
1671
|
if (!el) return;
|
|
1281
|
-
const
|
|
1672
|
+
const r2 = el.getBoundingClientRect();
|
|
1282
1673
|
ctx.save();
|
|
1283
1674
|
ctx.strokeStyle = fb.ok ? "#3a7" : "#c44";
|
|
1284
1675
|
ctx.lineWidth = 2;
|
|
1285
1676
|
ctx.beginPath();
|
|
1286
|
-
ctx.arc(screen.x -
|
|
1677
|
+
ctx.arc(screen.x - r2.left, screen.y - r2.top, 16, 0, Math.PI * 2);
|
|
1287
1678
|
ctx.stroke();
|
|
1288
1679
|
ctx.restore();
|
|
1289
1680
|
}
|
|
@@ -1292,56 +1683,85 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1292
1683
|
}, [canvasLayers, dragDropResult.drag]);
|
|
1293
1684
|
let body;
|
|
1294
1685
|
if (instrument.canvas) {
|
|
1295
|
-
body = /* @__PURE__ */ jsxs(
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1686
|
+
body = /* @__PURE__ */ jsxs("div", { ref: canvasContainerRef, className: "lk-trial__canvas-host", children: [
|
|
1687
|
+
/* @__PURE__ */ jsx(
|
|
1688
|
+
CanvasStack,
|
|
1689
|
+
{
|
|
1690
|
+
layers: layersWithFeedback,
|
|
1691
|
+
view: view2d ?? DEFAULT_VIEW,
|
|
1692
|
+
onViewChange: setView,
|
|
1693
|
+
minZoom: instrument.canvas.minZoom,
|
|
1694
|
+
maxZoom: instrument.canvas.maxZoom,
|
|
1695
|
+
children: instrument.render(renderCtx)
|
|
1696
|
+
}
|
|
1697
|
+
),
|
|
1698
|
+
/* @__PURE__ */ jsx(DragOverlay, { drag: dragDropResult.drag })
|
|
1699
|
+
] });
|
|
1307
1700
|
} else {
|
|
1308
1701
|
body = instrument.render(renderCtx);
|
|
1309
1702
|
}
|
|
1310
|
-
const
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1703
|
+
const startDrag = dragDropResult.startDrag;
|
|
1704
|
+
const paletteNode = useMemo(
|
|
1705
|
+
() => paletteItems.length > 0 ? /* @__PURE__ */ jsx(Palette, { items: paletteItems, onDragStart: startDrag }) : null,
|
|
1706
|
+
[paletteItems, startDrag]
|
|
1707
|
+
);
|
|
1708
|
+
const layerListNode = useMemo(
|
|
1709
|
+
() => instrument.layers && layerDescriptors.length > 0 ? /* @__PURE__ */ jsx(
|
|
1710
|
+
LayerList,
|
|
1711
|
+
{
|
|
1712
|
+
layers: layerDescriptors,
|
|
1713
|
+
visibility: layerVisibility,
|
|
1714
|
+
onReorder: (next) => {
|
|
1715
|
+
setLayerOrder(next.map((l) => l.id));
|
|
1716
|
+
bus.emit("layers.reorder");
|
|
1717
|
+
},
|
|
1718
|
+
onToggle: (lid, visible) => {
|
|
1719
|
+
setLayerVisibility((prev) => ({ ...prev, [lid]: visible }));
|
|
1720
|
+
bus.emit("layers.toggle");
|
|
1721
|
+
}
|
|
1323
1722
|
}
|
|
1723
|
+
) : null,
|
|
1724
|
+
[instrument.layers, layerDescriptors, layerVisibility, bus]
|
|
1725
|
+
);
|
|
1726
|
+
const extraChrome = useMemo(() => {
|
|
1727
|
+
const out = [];
|
|
1728
|
+
if (paletteNode) {
|
|
1729
|
+
out.push({
|
|
1730
|
+
id: "dragdrop-palette",
|
|
1731
|
+
region: "sidebar",
|
|
1732
|
+
item: { title: "Parts", body: paletteNode }
|
|
1733
|
+
});
|
|
1324
1734
|
}
|
|
1325
|
-
|
|
1735
|
+
if (layerListNode) {
|
|
1736
|
+
out.push({
|
|
1737
|
+
id: "layer-list",
|
|
1738
|
+
region: "sidebar",
|
|
1739
|
+
item: { title: "Layers", body: layerListNode }
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
return out;
|
|
1743
|
+
}, [paletteNode, layerListNode]);
|
|
1326
1744
|
return /* @__PURE__ */ jsx(
|
|
1327
1745
|
TrialChrome,
|
|
1328
1746
|
{
|
|
1747
|
+
job: jobCap ? job : void 0,
|
|
1329
1748
|
trialId: record.id,
|
|
1330
1749
|
record,
|
|
1331
1750
|
instrument,
|
|
1332
1751
|
isLastTrial: isLast,
|
|
1333
1752
|
undoBindings,
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1753
|
+
trialChrome: extraChrome,
|
|
1754
|
+
chrome,
|
|
1755
|
+
suppress,
|
|
1756
|
+
activeToolId: resolvedToolId,
|
|
1757
|
+
setActiveTool,
|
|
1338
1758
|
children: body
|
|
1339
1759
|
}
|
|
1340
1760
|
);
|
|
1341
1761
|
}
|
|
1342
1762
|
|
|
1343
1763
|
// src/trial/trialOps.ts
|
|
1344
|
-
var
|
|
1764
|
+
var DEFAULT_VIEW2 = { zoom: 1, pan: { x: 0, y: 0 } };
|
|
1345
1765
|
function findInstrument(instruments, name) {
|
|
1346
1766
|
const found = instruments.find((i) => i.name === name);
|
|
1347
1767
|
if (!found) {
|
|
@@ -1350,7 +1770,7 @@ function findInstrument(instruments, name) {
|
|
|
1350
1770
|
return found;
|
|
1351
1771
|
}
|
|
1352
1772
|
function initialView(instrument) {
|
|
1353
|
-
return instrument.canvas?.initialView ? structuredClone(instrument.canvas.initialView) : { ...
|
|
1773
|
+
return instrument.canvas?.initialView ? structuredClone(instrument.canvas.initialView) : { ...DEFAULT_VIEW2, pan: { ...DEFAULT_VIEW2.pan } };
|
|
1354
1774
|
}
|
|
1355
1775
|
function addTrial(trials, instruments, instrumentName) {
|
|
1356
1776
|
const instrument = findInstrument(instruments, instrumentName);
|
|
@@ -1407,6 +1827,74 @@ function reorderTrials(trials, ids) {
|
|
|
1407
1827
|
const rest = trials.filter((w) => !seen.has(w.id));
|
|
1408
1828
|
return [...named, ...rest];
|
|
1409
1829
|
}
|
|
1830
|
+
var MODES = [
|
|
1831
|
+
{ value: "auto", label: "Auto" },
|
|
1832
|
+
{ value: "light", label: "Light" },
|
|
1833
|
+
{ value: "dark", label: "Dark" }
|
|
1834
|
+
];
|
|
1835
|
+
function LabHeader() {
|
|
1836
|
+
const lab = useLabContext();
|
|
1837
|
+
const only = lab.instruments.length === 1 ? lab.instruments[0] : null;
|
|
1838
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1839
|
+
only ? /* @__PURE__ */ jsxs(
|
|
1840
|
+
"button",
|
|
1841
|
+
{
|
|
1842
|
+
type: "button",
|
|
1843
|
+
className: "lk-lab-header__add",
|
|
1844
|
+
onClick: () => lab.addTrial(only.name),
|
|
1845
|
+
children: [
|
|
1846
|
+
/* @__PURE__ */ jsx(vt, { size: 16 }),
|
|
1847
|
+
/* @__PURE__ */ jsx("span", { children: "Add trial" })
|
|
1848
|
+
]
|
|
1849
|
+
}
|
|
1850
|
+
) : (
|
|
1851
|
+
// Held at null so the control stays an "add one" action rather than
|
|
1852
|
+
// reading as the current instrument.
|
|
1853
|
+
/* @__PURE__ */ jsx(
|
|
1854
|
+
p2,
|
|
1855
|
+
{
|
|
1856
|
+
className: "lk-lab-header__add-select",
|
|
1857
|
+
"aria-label": "Add trial",
|
|
1858
|
+
placeholder: "Add trial\u2026",
|
|
1859
|
+
selectedKey: null,
|
|
1860
|
+
options: lab.instruments.map((i) => ({ value: i.name, label: i.name })),
|
|
1861
|
+
onSelectionChange: (name) => {
|
|
1862
|
+
if (name != null) lab.addTrial(String(name));
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
)
|
|
1866
|
+
),
|
|
1867
|
+
/* @__PURE__ */ jsx(
|
|
1868
|
+
r,
|
|
1869
|
+
{
|
|
1870
|
+
className: "lk-lab-header__mode",
|
|
1871
|
+
ariaLabel: "Color mode",
|
|
1872
|
+
items: MODES,
|
|
1873
|
+
value: lab.mode,
|
|
1874
|
+
onChange: (next) => {
|
|
1875
|
+
if (next) lab.setMode(next);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
)
|
|
1879
|
+
] });
|
|
1880
|
+
}
|
|
1881
|
+
function LabPalette({ tools }) {
|
|
1882
|
+
const storeCtx = useContext(LabStoreContext);
|
|
1883
|
+
if (!storeCtx) throw new Error("[labkit] LabPalette requires <LabStoreProvider>");
|
|
1884
|
+
const activeToolId = useStore(storeCtx.store, (s) => s.activeToolId);
|
|
1885
|
+
const setLabTool = useStore(storeCtx.store, (s) => s.setLabTool);
|
|
1886
|
+
const contributions = tools.map((t) => ({
|
|
1887
|
+
id: t.id,
|
|
1888
|
+
region: "palette",
|
|
1889
|
+
group: t.group,
|
|
1890
|
+
item: { icon: t.icon, label: t.label, shortcut: t.shortcut }
|
|
1891
|
+
}));
|
|
1892
|
+
const ctx = {
|
|
1893
|
+
activeToolId,
|
|
1894
|
+
setActiveTool: setLabTool
|
|
1895
|
+
};
|
|
1896
|
+
return /* @__PURE__ */ jsx(PaletteRegion, { contributions, ctx });
|
|
1897
|
+
}
|
|
1410
1898
|
function query() {
|
|
1411
1899
|
if (typeof window === "undefined" || !window.matchMedia) return null;
|
|
1412
1900
|
return window.matchMedia("(prefers-color-scheme: light)");
|
|
@@ -1442,11 +1930,11 @@ var ZONE_ID = asNodeId("lk-workspace");
|
|
|
1442
1930
|
var STRATEGIES = { grid: gridStrategy };
|
|
1443
1931
|
var KIND = "trial";
|
|
1444
1932
|
function extentOf(store, id) {
|
|
1445
|
-
const
|
|
1446
|
-
if (!
|
|
1933
|
+
const p3 = store.getNode(id)?.membership?.placement;
|
|
1934
|
+
if (!p3) return null;
|
|
1447
1935
|
const out = {};
|
|
1448
|
-
if (
|
|
1449
|
-
if (
|
|
1936
|
+
if (p3.size) out.size = p3.size;
|
|
1937
|
+
if (p3.span) out.span = p3.span;
|
|
1450
1938
|
return Object.keys(out).length > 0 ? out : null;
|
|
1451
1939
|
}
|
|
1452
1940
|
function Workspace({
|
|
@@ -1481,6 +1969,7 @@ function Workspace({
|
|
|
1481
1969
|
storeRef.current = store2;
|
|
1482
1970
|
}
|
|
1483
1971
|
const store = storeRef.current;
|
|
1972
|
+
const surface = useSurfaceOptional();
|
|
1484
1973
|
useLayoutEffect(() => {
|
|
1485
1974
|
store.updateContainerConfig(ZONE_ID, { resizable, gap, padding });
|
|
1486
1975
|
}, [store, resizable, gap, padding]);
|
|
@@ -1499,6 +1988,11 @@ function Workspace({
|
|
|
1499
1988
|
}
|
|
1500
1989
|
store.setChildOrder(ZONE_ID, [...nodeIds]);
|
|
1501
1990
|
}, [store, nodeIds]);
|
|
1991
|
+
useEffect(() => {
|
|
1992
|
+
if (!surface) return;
|
|
1993
|
+
surface.invalidateRects();
|
|
1994
|
+
return store.events.on("node.placementChanged", () => surface.invalidateRects());
|
|
1995
|
+
}, [store, surface, nodeIds]);
|
|
1502
1996
|
useEffect(() => {
|
|
1503
1997
|
if (!onLayoutChange) return;
|
|
1504
1998
|
return store.events.on("node.placementChanged", () => {
|
|
@@ -1520,10 +2014,10 @@ function Workspace({
|
|
|
1520
2014
|
[KIND]: ({ node }) => {
|
|
1521
2015
|
const content = byId.get(node.id) ?? null;
|
|
1522
2016
|
if (!reorderable) return content;
|
|
1523
|
-
return
|
|
1524
|
-
|
|
1525
|
-
/* @__PURE__ */ jsx("div", { className: "lk-trial-
|
|
1526
|
-
|
|
2017
|
+
return (
|
|
2018
|
+
// No grip strip: the trial's own title bar is the drag surface.
|
|
2019
|
+
/* @__PURE__ */ jsx(TrialDragContext.Provider, { value: { nodeId: node.id }, children: /* @__PURE__ */ jsx("div", { className: "lk-trial-tile", children: content }) })
|
|
2020
|
+
);
|
|
1527
2021
|
}
|
|
1528
2022
|
};
|
|
1529
2023
|
}, [nodeIds, items, reorderable]);
|
|
@@ -1561,8 +2055,8 @@ var NEBULA_SLOTS = [
|
|
|
1561
2055
|
];
|
|
1562
2056
|
function buildNebula(colors) {
|
|
1563
2057
|
const blobs = colors.map((c, i) => {
|
|
1564
|
-
const
|
|
1565
|
-
return `radial-gradient(ellipse ${
|
|
2058
|
+
const p3 = NEBULA_SLOTS[i % NEBULA_SLOTS.length];
|
|
2059
|
+
return `radial-gradient(ellipse ${p3.sx} ${p3.sy} at ${p3.cx} ${p3.cy}, color-mix(in srgb, ${c} 22%, transparent), transparent ${p3.stop})`;
|
|
1566
2060
|
});
|
|
1567
2061
|
blobs.push("radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)");
|
|
1568
2062
|
return blobs.join(", ");
|
|
@@ -1575,6 +2069,12 @@ function Lab({
|
|
|
1575
2069
|
mode,
|
|
1576
2070
|
nebula,
|
|
1577
2071
|
title,
|
|
2072
|
+
footer,
|
|
2073
|
+
chrome,
|
|
2074
|
+
suppress,
|
|
2075
|
+
tools,
|
|
2076
|
+
configRules,
|
|
2077
|
+
controls,
|
|
1578
2078
|
children
|
|
1579
2079
|
}) {
|
|
1580
2080
|
if (process.env.NODE_ENV !== "production" && instruments.length === 0) {
|
|
@@ -1639,7 +2139,8 @@ function Lab({
|
|
|
1639
2139
|
},
|
|
1640
2140
|
savedSnapshots,
|
|
1641
2141
|
saveSnapshot: (trialId, name) => {
|
|
1642
|
-
store.getState().
|
|
2142
|
+
const taken = store.getState().savedSnapshots.filter((s) => s.trialId === trialId).length;
|
|
2143
|
+
store.getState().saveSnapshot(trialId, name ?? `Snapshot ${taken + 1}`);
|
|
1643
2144
|
},
|
|
1644
2145
|
loadSnapshot: (trialId, snapshotId) => {
|
|
1645
2146
|
store.getState().loadSnapshot(snapshotId, trialId);
|
|
@@ -1650,9 +2151,11 @@ function Lab({
|
|
|
1650
2151
|
mode: modeValue,
|
|
1651
2152
|
setMode: (m) => {
|
|
1652
2153
|
store.getState().setMode(m);
|
|
1653
|
-
}
|
|
2154
|
+
},
|
|
2155
|
+
configRules,
|
|
2156
|
+
controls
|
|
1654
2157
|
};
|
|
1655
|
-
}, [instruments, trials, savedSnapshots, modeValue, store]);
|
|
2158
|
+
}, [instruments, trials, savedSnapshots, modeValue, store, configRules, controls]);
|
|
1656
2159
|
const backdropStyle = resolvedMode === "dark" && nebula && nebula.length > 0 ? { ["--wzl-backdrop"]: buildNebula(nebula) } : void 0;
|
|
1657
2160
|
return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
1658
2161
|
ThemeProvider,
|
|
@@ -1661,18 +2164,33 @@ function Lab({
|
|
|
1661
2164
|
mode: resolvedMode,
|
|
1662
2165
|
className: "lk-lab",
|
|
1663
2166
|
style: backdropStyle,
|
|
1664
|
-
children: /* @__PURE__ */ jsx(
|
|
1665
|
-
|
|
2167
|
+
children: /* @__PURE__ */ jsx(
|
|
2168
|
+
LabShell,
|
|
1666
2169
|
{
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
2170
|
+
title: title ?? "Labkit",
|
|
2171
|
+
mode: modeValue,
|
|
2172
|
+
footer,
|
|
2173
|
+
header: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2174
|
+
/* @__PURE__ */ jsx(LabHeader, {}),
|
|
2175
|
+
children
|
|
2176
|
+
] }),
|
|
2177
|
+
children: /* @__PURE__ */ jsxs("div", { className: "lk-lab__body", children: [
|
|
2178
|
+
tools ? /* @__PURE__ */ jsx(LabPalette, { tools }) : null,
|
|
2179
|
+
/* @__PURE__ */ jsx(
|
|
2180
|
+
Workspace,
|
|
2181
|
+
{
|
|
2182
|
+
ids: trials.map((w) => w.id),
|
|
2183
|
+
resizable: true,
|
|
2184
|
+
reorderable: true,
|
|
2185
|
+
onReorder: (ids) => contextValue.reorderTrials(ids),
|
|
2186
|
+
layout,
|
|
2187
|
+
onLayoutChange: (next) => store.getState().setLayout(next),
|
|
2188
|
+
children: trials.map((w) => /* @__PURE__ */ jsx(Trial, { id: w.id, chrome, suppress }, w.id))
|
|
2189
|
+
}
|
|
2190
|
+
)
|
|
2191
|
+
] })
|
|
1674
2192
|
}
|
|
1675
|
-
)
|
|
2193
|
+
)
|
|
1676
2194
|
}
|
|
1677
2195
|
) }) });
|
|
1678
2196
|
}
|
|
@@ -1717,7 +2235,7 @@ function CurveField({
|
|
|
1717
2235
|
}
|
|
1718
2236
|
flipped.sort((a, b) => a.x - b.x);
|
|
1719
2237
|
const out = [];
|
|
1720
|
-
for (const
|
|
2238
|
+
for (const p3 of flipped) out.push(p3.x, p3.y);
|
|
1721
2239
|
onChange(out);
|
|
1722
2240
|
}, [values, onChange]);
|
|
1723
2241
|
const handleFlipVertical = useCallback(() => {
|
|
@@ -2021,268 +2539,7 @@ function EffectCardList({
|
|
|
2021
2539
|
] }, String(item.id));
|
|
2022
2540
|
}) });
|
|
2023
2541
|
}
|
|
2024
|
-
function PropertyGroup({
|
|
2025
|
-
title,
|
|
2026
|
-
hidden,
|
|
2027
|
-
children,
|
|
2028
|
-
className,
|
|
2029
|
-
pack = "auto-color"
|
|
2030
|
-
}) {
|
|
2031
|
-
if (hidden) return null;
|
|
2032
|
-
const packClass = pack === "pairs" ? " lk-property-group--pairs" : "";
|
|
2033
|
-
const cls = `lk-property-group${packClass}${className ? ` ${className}` : ""}`;
|
|
2034
|
-
return /* @__PURE__ */ jsxs("div", { className: cls, children: [
|
|
2035
|
-
/* @__PURE__ */ jsxs("h3", { className: "lk-property-group__title", children: [
|
|
2036
|
-
/* @__PURE__ */ jsx("hr", {}),
|
|
2037
|
-
/* @__PURE__ */ jsx("span", { children: title }),
|
|
2038
|
-
/* @__PURE__ */ jsx("hr", {})
|
|
2039
|
-
] }),
|
|
2040
|
-
/* @__PURE__ */ jsx("div", { className: "lk-property-group__body", children })
|
|
2041
|
-
] });
|
|
2042
|
-
}
|
|
2043
|
-
function PropertyPanel({ title, children, className }) {
|
|
2044
|
-
const cls = className ? `lk-property-panel ${className}` : "lk-property-panel";
|
|
2045
|
-
return /* @__PURE__ */ jsxs("div", { className: cls, children: [
|
|
2046
|
-
title != null && /* @__PURE__ */ jsx("h2", { className: "lk-property-panel__title", children: title }),
|
|
2047
|
-
children
|
|
2048
|
-
] });
|
|
2049
|
-
}
|
|
2050
|
-
function PropertyList({ children, className, pack = "auto-color" }) {
|
|
2051
|
-
const packClass = pack === "pairs" ? " lk-property-list--pairs" : "";
|
|
2052
|
-
const cls = `lk-property-list${packClass}${className ? ` ${className}` : ""}`;
|
|
2053
|
-
return /* @__PURE__ */ jsx("div", { className: cls, children });
|
|
2054
|
-
}
|
|
2055
|
-
function PropertyRow({
|
|
2056
|
-
label,
|
|
2057
|
-
readout,
|
|
2058
|
-
variant = "default",
|
|
2059
|
-
layout = "block",
|
|
2060
|
-
children,
|
|
2061
|
-
htmlFor,
|
|
2062
|
-
className
|
|
2063
|
-
}) {
|
|
2064
|
-
const variantClass = variant === "default" ? "" : ` lk-property-row--${variant}`;
|
|
2065
|
-
const layoutClass = variant === "default" && layout === "inline" ? " lk-property-row--inline" : "";
|
|
2066
|
-
const cls = `lk-property-row${variantClass}${layoutClass}${className ? ` ${className}` : ""}`;
|
|
2067
|
-
return /* @__PURE__ */ jsxs("label", { className: cls, htmlFor, children: [
|
|
2068
|
-
/* @__PURE__ */ jsxs("span", { className: "lk-property-row__label", children: [
|
|
2069
|
-
label,
|
|
2070
|
-
readout != null && /* @__PURE__ */ jsx("em", { className: "lk-property-row__readout", children: readout })
|
|
2071
|
-
] }),
|
|
2072
|
-
children
|
|
2073
|
-
] });
|
|
2074
|
-
}
|
|
2075
|
-
function SliderRow({
|
|
2076
|
-
label,
|
|
2077
|
-
value,
|
|
2078
|
-
min,
|
|
2079
|
-
max,
|
|
2080
|
-
step = 1,
|
|
2081
|
-
onChange,
|
|
2082
|
-
format,
|
|
2083
|
-
unit,
|
|
2084
|
-
layout
|
|
2085
|
-
}) {
|
|
2086
|
-
const decimals = step >= 1 ? 0 : Math.min(6, Math.max(0, Math.ceil(-Math.log10(step))));
|
|
2087
|
-
const effectiveFormat = format ?? ((n) => formatNumber(n, decimals));
|
|
2088
|
-
return /* @__PURE__ */ jsx(
|
|
2089
|
-
PropertyRow,
|
|
2090
|
-
{
|
|
2091
|
-
label,
|
|
2092
|
-
readout: /* @__PURE__ */ jsx(
|
|
2093
|
-
EditableReadout,
|
|
2094
|
-
{
|
|
2095
|
-
value,
|
|
2096
|
-
min,
|
|
2097
|
-
max,
|
|
2098
|
-
format: effectiveFormat,
|
|
2099
|
-
unit,
|
|
2100
|
-
onCommit: onChange
|
|
2101
|
-
}
|
|
2102
|
-
),
|
|
2103
|
-
layout,
|
|
2104
|
-
children: /* @__PURE__ */ jsx(
|
|
2105
|
-
"input",
|
|
2106
|
-
{
|
|
2107
|
-
type: "range",
|
|
2108
|
-
tabIndex: -1,
|
|
2109
|
-
min,
|
|
2110
|
-
max,
|
|
2111
|
-
step,
|
|
2112
|
-
value,
|
|
2113
|
-
onChange: (e) => {
|
|
2114
|
-
const v = Number(e.target.value);
|
|
2115
|
-
p("property-panel", "slider", { label, value: v });
|
|
2116
|
-
onChange(v);
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
)
|
|
2120
|
-
}
|
|
2121
|
-
);
|
|
2122
|
-
}
|
|
2123
|
-
function EditableReadout({ value, min, max, format, unit, onCommit }) {
|
|
2124
|
-
const [draft, setDraft] = useState(null);
|
|
2125
|
-
const fmt = (n) => format ? format(n) : formatNumber(n);
|
|
2126
|
-
const displayValue = draft !== null ? draft : (() => {
|
|
2127
|
-
const formatted = fmt(value);
|
|
2128
|
-
return typeof formatted === "string" ? formatted : String(formatted);
|
|
2129
|
-
})();
|
|
2130
|
-
const suffix = unit == null ? null : typeof unit === "string" ? /* @__PURE__ */ jsx("span", { className: "lk-property-row__readout-unit", children: unit }) : unit;
|
|
2131
|
-
const commit = () => {
|
|
2132
|
-
if (draft !== null) {
|
|
2133
|
-
const n = parseSignedNumber(draft);
|
|
2134
|
-
if (Number.isFinite(n)) onCommit(Math.min(max, Math.max(min, n)));
|
|
2135
|
-
}
|
|
2136
|
-
setDraft(null);
|
|
2137
|
-
};
|
|
2138
|
-
return /* @__PURE__ */ jsxs("span", { className: "lk-property-row__readout-group", children: [
|
|
2139
|
-
/* @__PURE__ */ jsx(
|
|
2140
|
-
"input",
|
|
2141
|
-
{
|
|
2142
|
-
type: "text",
|
|
2143
|
-
inputMode: "decimal",
|
|
2144
|
-
className: "lk-property-row__readout-input",
|
|
2145
|
-
value: displayValue,
|
|
2146
|
-
onFocus: (e) => {
|
|
2147
|
-
const formatted = fmt(value);
|
|
2148
|
-
setDraft(typeof formatted === "string" ? formatted : String(formatted));
|
|
2149
|
-
e.currentTarget.select();
|
|
2150
|
-
},
|
|
2151
|
-
onChange: (e) => setDraft(e.target.value.replace(/-/g, "\u2212")),
|
|
2152
|
-
onBlur: commit,
|
|
2153
|
-
onClick: (e) => {
|
|
2154
|
-
e.preventDefault();
|
|
2155
|
-
e.stopPropagation();
|
|
2156
|
-
e.currentTarget.focus();
|
|
2157
|
-
},
|
|
2158
|
-
onMouseDown: (e) => e.stopPropagation(),
|
|
2159
|
-
onKeyDown: (e) => {
|
|
2160
|
-
if (e.key === "Enter") {
|
|
2161
|
-
commit();
|
|
2162
|
-
e.currentTarget.blur();
|
|
2163
|
-
} else if (e.key === "Escape") {
|
|
2164
|
-
setDraft(null);
|
|
2165
|
-
e.currentTarget.blur();
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
),
|
|
2170
|
-
suffix
|
|
2171
|
-
] });
|
|
2172
|
-
}
|
|
2173
|
-
function ColorRow({
|
|
2174
|
-
label,
|
|
2175
|
-
value,
|
|
2176
|
-
onChange,
|
|
2177
|
-
alpha,
|
|
2178
|
-
onAlphaChange,
|
|
2179
|
-
alphaDisabled
|
|
2180
|
-
}) {
|
|
2181
|
-
const showAlpha = alpha != null;
|
|
2182
|
-
const className = alphaDisabled ? "lk-property-row--alpha-disabled" : void 0;
|
|
2183
|
-
return /* @__PURE__ */ jsxs(PropertyRow, { label, variant: "color", className, children: [
|
|
2184
|
-
/* @__PURE__ */ jsx("input", { type: "color", value, onChange: (e) => onChange(e.target.value) }),
|
|
2185
|
-
showAlpha && /* @__PURE__ */ jsx(
|
|
2186
|
-
"input",
|
|
2187
|
-
{
|
|
2188
|
-
type: "range",
|
|
2189
|
-
className: "lk-property-row__alpha",
|
|
2190
|
-
min: 0,
|
|
2191
|
-
max: 1,
|
|
2192
|
-
step: 0.01,
|
|
2193
|
-
value: alpha,
|
|
2194
|
-
disabled: alphaDisabled,
|
|
2195
|
-
onChange: (e) => onAlphaChange?.(Number(e.target.value))
|
|
2196
|
-
}
|
|
2197
|
-
)
|
|
2198
|
-
] });
|
|
2199
|
-
}
|
|
2200
|
-
function CheckboxRow({ label, value, onChange }) {
|
|
2201
|
-
return /* @__PURE__ */ jsx(PropertyRow, { label, variant: "checkbox", children: /* @__PURE__ */ jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }) });
|
|
2202
|
-
}
|
|
2203
|
-
function TextRow({ label, value, onChange, placeholder, maxLength, layout }) {
|
|
2204
|
-
return /* @__PURE__ */ jsx(PropertyRow, { label, layout, children: /* @__PURE__ */ jsx(
|
|
2205
|
-
"input",
|
|
2206
|
-
{
|
|
2207
|
-
type: "text",
|
|
2208
|
-
value,
|
|
2209
|
-
placeholder,
|
|
2210
|
-
maxLength,
|
|
2211
|
-
onChange: (e) => onChange(e.target.value)
|
|
2212
|
-
}
|
|
2213
|
-
) });
|
|
2214
|
-
}
|
|
2215
|
-
function NumberRow({
|
|
2216
|
-
label,
|
|
2217
|
-
value,
|
|
2218
|
-
onChange,
|
|
2219
|
-
min,
|
|
2220
|
-
max,
|
|
2221
|
-
step,
|
|
2222
|
-
placeholder,
|
|
2223
|
-
layout
|
|
2224
|
-
}) {
|
|
2225
|
-
return /* @__PURE__ */ jsx(PropertyRow, { label, layout, children: /* @__PURE__ */ jsx(
|
|
2226
|
-
"input",
|
|
2227
|
-
{
|
|
2228
|
-
type: "number",
|
|
2229
|
-
value,
|
|
2230
|
-
min,
|
|
2231
|
-
max,
|
|
2232
|
-
step,
|
|
2233
|
-
placeholder,
|
|
2234
|
-
onChange: (e) => {
|
|
2235
|
-
const raw = e.target.value;
|
|
2236
|
-
if (raw === "") return;
|
|
2237
|
-
const n = Number(raw);
|
|
2238
|
-
if (Number.isFinite(n)) onChange(n);
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
) });
|
|
2242
|
-
}
|
|
2243
|
-
function SelectRow({
|
|
2244
|
-
label,
|
|
2245
|
-
value,
|
|
2246
|
-
options,
|
|
2247
|
-
onChange,
|
|
2248
|
-
layout
|
|
2249
|
-
}) {
|
|
2250
|
-
return /* @__PURE__ */ jsx(PropertyRow, { label, layout, children: /* @__PURE__ */ jsx(
|
|
2251
|
-
"select",
|
|
2252
|
-
{
|
|
2253
|
-
value,
|
|
2254
|
-
onChange: (e) => {
|
|
2255
|
-
const v = e.target.value;
|
|
2256
|
-
p("property-panel", "select", { label, value: v });
|
|
2257
|
-
onChange(v);
|
|
2258
|
-
},
|
|
2259
|
-
children: options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
|
|
2260
|
-
}
|
|
2261
|
-
) });
|
|
2262
|
-
}
|
|
2263
|
-
function ToggleRow({
|
|
2264
|
-
label,
|
|
2265
|
-
value,
|
|
2266
|
-
options,
|
|
2267
|
-
onChange,
|
|
2268
|
-
layout
|
|
2269
|
-
}) {
|
|
2270
|
-
return /* @__PURE__ */ jsx(PropertyRow, { label, layout, children: /* @__PURE__ */ jsx("div", { className: "lk-property-row__toggle", children: options.map((opt) => {
|
|
2271
|
-
const selected = opt.value === value;
|
|
2272
|
-
return /* @__PURE__ */ jsx(
|
|
2273
|
-
"button",
|
|
2274
|
-
{
|
|
2275
|
-
type: "button",
|
|
2276
|
-
"aria-pressed": selected,
|
|
2277
|
-
className: selected ? "lk-property-row__toggle-button lk-property-row__toggle-button--selected" : "lk-property-row__toggle-button",
|
|
2278
|
-
onClick: () => onChange(opt.value),
|
|
2279
|
-
children: opt.label
|
|
2280
|
-
},
|
|
2281
|
-
opt.value
|
|
2282
|
-
);
|
|
2283
|
-
}) }) });
|
|
2284
|
-
}
|
|
2285
2542
|
|
|
2286
|
-
export {
|
|
2543
|
+
export { CurveField, DEFAULT_VIEW, EffectCard, EffectCardList, Lab, LabContext, LabPalette, LabShell, PITCH_LIMIT, Subpanel, Trial, TrialChrome, Workspace, addTrial, applyRules, as2DView, builtinRules, clampPitch, cloneTrial, closeTrial, defineInstrument, f, interstellarTheme, orbitAfterDrag, orbitAfterWheel, reorderTrials, resetTrial, resolveConfigSchema, titleCase, useConfigSchema, useLabContext, useOrbit, validateConfigSchema, wrapYaw };
|
|
2287
2544
|
//# sourceMappingURL=index.js.map
|
|
2288
2545
|
//# sourceMappingURL=index.js.map
|