@weasel-js/labkit 1.4.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CqkxkFJW.d.ts} +1 -1
- package/dist/_dts/ToggleBar.d-DbHoWKIA.d.ts +116 -0
- package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-8R6UmlvN.d.ts} +253 -31
- package/dist/_dts/index-B8uu2ba7.d.ts +237 -0
- package/dist/_dts/{index-Blv9uQzu.d.ts → index-retnAut7.d.ts} +18 -10
- package/dist/_dts/{types-B9_zrHmb.d.ts → types-Ca2LCnPq.d.ts} +65 -2
- package/dist/_dts/usePanZoom-nS798rOF.d.ts +160 -0
- package/dist/_dts/useTrialState-jcx_TdU6.d.ts +212 -0
- package/dist/canvas/index.d.ts +17 -128
- package/dist/canvas/index.js +4 -2
- package/dist/chrome/index.d.ts +4 -92
- package/dist/chrome/index.js +9 -5
- package/dist/chunk-67SJLMC7.js +704 -0
- package/dist/chunk-67SJLMC7.js.map +1 -0
- package/dist/chunk-6ZDGOZQV.js +310 -0
- package/dist/chunk-6ZDGOZQV.js.map +1 -0
- package/dist/{chunk-MOM3GOVY.js → chunk-EKIICY6X.js} +111 -25
- package/dist/chunk-EKIICY6X.js.map +1 -0
- package/dist/{chunk-CTRKTLYZ.js → chunk-H6ZAOWNE.js} +11 -11
- package/dist/chunk-H6ZAOWNE.js.map +1 -0
- package/dist/{chunk-SMHP6XZ4.js → chunk-KJALCDWE.js} +9 -7
- package/dist/chunk-KJALCDWE.js.map +1 -0
- package/dist/{chunk-BKFVHKJH.js → chunk-MUKOW3TC.js} +3 -3
- package/dist/{chunk-BKFVHKJH.js.map → chunk-MUKOW3TC.js.map} +1 -1
- package/dist/chunk-NDRYLVOW.js +15 -0
- package/dist/chunk-NDRYLVOW.js.map +1 -0
- package/dist/{chunk-64ZCN3DA.js → chunk-NS64DXMN.js} +226 -159
- package/dist/chunk-NS64DXMN.js.map +1 -0
- package/dist/{chunk-I6JCVE24.js → chunk-PGETSEDK.js} +135 -13
- package/dist/chunk-PGETSEDK.js.map +1 -0
- package/dist/{chunk-FQMJUVHJ.js → chunk-QLSV2N3G.js} +32 -37
- package/dist/chunk-QLSV2N3G.js.map +1 -0
- package/dist/chunk-RWNDDKOH.js +682 -0
- package/dist/chunk-RWNDDKOH.js.map +1 -0
- package/dist/chunk-S2SHZRD7.js +7681 -0
- package/dist/chunk-S2SHZRD7.js.map +1 -0
- package/dist/{chunk-U3IYHIAE.js → chunk-TQBBYTAF.js} +19 -82
- package/dist/chunk-TQBBYTAF.js.map +1 -0
- package/dist/chunk-UU3NJO6N.js +72 -0
- package/dist/chunk-UU3NJO6N.js.map +1 -0
- package/dist/chunk-WIG6XHJ7.js +35 -0
- package/dist/chunk-WIG6XHJ7.js.map +1 -0
- package/dist/{chunk-TJ7QY3OC.js → chunk-ZPH5WTWF.js} +21 -15
- package/dist/chunk-ZPH5WTWF.js.map +1 -0
- package/dist/config/index.d.ts +232 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/controls/index.d.ts +2 -3
- package/dist/controls/index.js +3 -2
- package/dist/dragdrop/index.d.ts +2 -4
- package/dist/index.d.ts +110 -381
- package/dist/index.js +691 -817
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +3 -5
- package/dist/layers/index.js +2 -2
- package/dist/loupe/index.d.ts +6 -8
- package/dist/loupe/index.js +2 -2
- package/dist/passthrough/weasel-ui.d.ts +164 -122
- package/dist/passthrough/weasel-ui.js +1 -1
- package/dist/primitives/index.d.ts +53 -6
- package/dist/primitives/index.js +6 -4
- package/dist/state/index.d.ts +12 -7
- package/dist/state/index.js +16 -13
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +84 -50
- package/dist/surface/index.d.ts +44 -7
- package/dist/surface/index.js +2 -1
- package/dist/ui/layers/index.js +1 -1
- package/dist/undo/index.d.ts +2 -4
- package/package.json +15 -8
- package/src/annotations/AnnotationOverlay.tsx +53 -5
- package/src/annotations/Annotations.overlay.test.tsx +85 -6
- package/src/annotations/drawOne.test.ts +1 -1
- package/src/annotations/drawOne.ts +2 -2
- package/src/annotations/paint.test.ts +2 -2
- package/src/annotations/paint.ts +1 -1
- package/src/annotations/preload.ts +6 -0
- package/src/annotations/store.test.ts +18 -0
- package/src/annotations/store.ts +5 -4
- package/src/annotations/svgNodes.test.ts +2 -2
- package/src/annotations/toolMap.ts +3 -3
- package/src/annotations/types.ts +6 -3
- package/src/canvas/CameraWheelContext.ts +12 -0
- package/src/canvas/CanvasStack.test.tsx +25 -0
- package/src/canvas/CanvasStack.tsx +31 -4
- package/src/canvas/Stage.less +18 -0
- package/src/canvas/Stage.test.tsx +186 -0
- package/src/canvas/Stage.tsx +134 -0
- package/src/canvas/camera.test.ts +17 -2
- package/src/canvas/camera.ts +9 -9
- package/src/canvas/index.ts +4 -0
- package/src/canvas/usePanZoom.ts +10 -5
- package/src/chrome/ChromeRegions.stories.tsx +0 -3
- package/src/chrome/LabChrome.tsx +114 -0
- package/src/chrome/builtins.tsx +20 -31
- package/src/chrome/index.ts +15 -0
- package/src/chrome/labRegions.test.tsx +208 -0
- package/src/chrome/labTypes.ts +50 -0
- package/src/chrome/merge.ts +7 -9
- package/src/chrome/regions/PaletteRegion.test.tsx +6 -4
- package/src/chrome/regions/PaletteRegion.tsx +21 -9
- package/src/chrome/regions/SidebarRegion.tsx +25 -13
- package/src/chrome/regions/StatusRegion.tsx +15 -8
- package/src/chrome/regions/ToolbarRegion.tsx +30 -16
- package/src/chrome/types.ts +47 -20
- package/src/config/builder.test.ts +4 -0
- package/src/config/builder.ts +16 -10
- package/src/config/declarationEmit.fixture.ts +11 -0
- package/src/config/declarationEmit.test.ts +51 -0
- package/src/config/entry.test.ts +46 -0
- package/src/config/index.ts +13 -1
- package/src/config/nodeClasses.test.ts +18 -0
- package/src/config/types.ts +3 -1
- package/src/controls/ControlPanel.test.tsx +16 -0
- package/src/controls/ControlPanel.tsx +5 -2
- package/src/fake-indexeddb-auto.d.ts +5 -0
- package/src/index.test.ts +7 -0
- package/src/index.ts +38 -5
- package/src/instrument/serializers.test.ts +22 -0
- package/src/instrument/serializers.ts +11 -0
- package/src/instrument/types.ts +25 -1
- package/src/lab/Lab.less +4 -13
- package/src/lab/Lab.persist.test.tsx +147 -0
- package/src/lab/Lab.stories.tsx +0 -2
- package/src/lab/Lab.surface.test.tsx +66 -3
- package/src/lab/Lab.test.tsx +91 -17
- package/src/lab/Lab.tsx +337 -124
- package/src/lab/LabFit.stories.less +18 -0
- package/src/lab/LabFit.stories.tsx +219 -0
- package/src/lab/LabFullChrome.stories.tsx +0 -3
- package/src/lab/LabHeader.test.tsx +40 -9
- package/src/lab/LabHeader.tsx +7 -13
- package/src/lab/LabPalette.tsx +13 -26
- package/src/lab/LabShell.less +49 -12
- package/src/lab/LabSwitcher.less +8 -1
- package/src/lab/Workspace.surface.test.tsx +2 -1
- package/src/lab/fitCheck.test.ts +57 -0
- package/src/lab/fitCheck.ts +125 -0
- package/src/loupe/AGENTS.md +1 -1
- package/src/loupe/Loupe.less +1 -1
- package/src/loupe/canvasLens.ts +1 -1
- package/src/loupe/types.ts +1 -1
- package/src/loupe/useLoupe.test.tsx +1 -1
- package/src/loupe/useLoupe.ts +1 -1
- package/src/passthrough/weasel-ui.ts +9 -0
- package/src/primitives/FloatingPanel.stories.tsx +13 -1
- package/src/primitives/FloatingPanel.test.tsx +47 -16
- package/src/primitives/FloatingPanel.tsx +11 -25
- package/src/primitives/Split.less +14 -0
- package/src/primitives/Split.test.tsx +108 -0
- package/src/primitives/Split.tsx +159 -0
- package/src/primitives/ZoomControl.tsx +5 -2
- package/src/primitives/index.ts +2 -0
- package/src/state/Persistence.stories.tsx +75 -0
- package/src/state/Persistence.tsx +33 -0
- package/src/state/SingletonExperiment.test.tsx +47 -53
- package/src/state/SingletonExperiment.tsx +24 -13
- package/src/state/adapterContract.ts +116 -0
- package/src/state/adapters.test.ts +113 -61
- package/src/state/adapters.ts +328 -69
- package/src/state/document.test.ts +71 -96
- package/src/state/document.ts +66 -52
- package/src/state/helpers.test.ts +1 -1
- package/src/state/index.ts +8 -0
- package/src/state/labRecords.test.ts +105 -0
- package/src/state/labRecords.ts +160 -0
- package/src/state/openLabStore.test.ts +658 -0
- package/src/state/openLabStore.ts +403 -0
- package/src/state/records.test.ts +234 -0
- package/src/state/records.ts +219 -0
- package/src/state/store.test.ts +104 -635
- package/src/state/store.ts +116 -157
- package/src/state/toolSlot.test.ts +1 -1
- package/src/state/types.ts +39 -15
- package/src/state/useOpenOnce.ts +68 -0
- package/src/state/usePersistedState.test.tsx +125 -0
- package/src/state/usePersistedState.ts +66 -0
- package/src/state/useTrialState.test.tsx +2 -3
- package/src/state/view.test.ts +49 -15
- package/src/state/view.ts +18 -1
- package/src/styles.less +2 -0
- package/src/surface/AGENTS.md +39 -7
- package/src/surface/SurfaceContext.ts +26 -3
- package/src/surface/index.ts +2 -0
- package/src/surface/useSurfaceTile.test.tsx +2 -1
- package/src/surface/useSurfaceTile.ts +6 -5
- package/src/surface/useTiledSurface.test.tsx +175 -0
- package/src/surface/useTiledSurface.ts +54 -5
- package/src/theme/Interstellar.stories.tsx +7 -7
- package/src/theme/interstellar.test.ts +1 -1
- package/src/theme/interstellar.tokens.json +2 -2
- package/src/tools/labTool.ts +11 -0
- package/src/trial/Trial.annotations.persist.test.tsx +100 -58
- package/src/trial/Trial.annotations.test.tsx +47 -4
- package/src/trial/Trial.job.test.tsx +3 -3
- package/src/trial/Trial.less +1 -26
- package/src/trial/Trial.stories.tsx +1 -2
- package/src/trial/Trial.test.tsx +28 -4
- package/src/trial/Trial.trialId.test.tsx +23 -1
- package/src/trial/Trial.tsx +118 -38
- package/src/trial/TrialBody.tsx +17 -141
- package/src/trial/TrialChrome.tsx +7 -4
- package/src/trial/trialOps.ts +5 -3
- package/dist/_dts/types-BP2OCcpg.d.ts +0 -155
- package/dist/_dts/types-x92Kfeme.d.ts +0 -62
- package/dist/_dts/useTrialState-D6Vb3T-g.d.ts +0 -99
- package/dist/chunk-2P6PP5N4.js +0 -553
- package/dist/chunk-2P6PP5N4.js.map +0 -1
- package/dist/chunk-64ZCN3DA.js.map +0 -1
- package/dist/chunk-CTRKTLYZ.js.map +0 -1
- package/dist/chunk-FQMJUVHJ.js.map +0 -1
- package/dist/chunk-I6JCVE24.js.map +0 -1
- package/dist/chunk-MOM3GOVY.js.map +0 -1
- package/dist/chunk-SMHP6XZ4.js.map +0 -1
- package/dist/chunk-TJ7QY3OC.js.map +0 -1
- package/dist/chunk-U3IYHIAE.js.map +0 -1
- package/dist/chunk-W3ECWC2K.js +0 -7335
- package/dist/chunk-W3ECWC2K.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,54 +1,62 @@
|
|
|
1
|
+
import { resolveLoupe, TrialLoupe } from './chunk-ZPH5WTWF.js';
|
|
2
|
+
export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LoupeBubble, TrialLoupe, drawCanvasLens, lensCamera, lensSourceRect, resolveLoupe, sampleStack, useLoupe } from './chunk-ZPH5WTWF.js';
|
|
1
3
|
import { createEventBus, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
|
|
2
4
|
export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
|
|
3
5
|
import { useDragDrop, DragOverlay, Palette } from './chunk-RS3HRQWU.js';
|
|
4
|
-
import {
|
|
5
|
-
export {
|
|
6
|
+
import { useTiledSurface } from './chunk-QLSV2N3G.js';
|
|
7
|
+
export { composeRects, rectsEqual, toDeviceRect, useTiledSurface } from './chunk-QLSV2N3G.js';
|
|
6
8
|
import { useJob } from './chunk-HXXTULDN.js';
|
|
7
9
|
export { useJob } from './chunk-HXXTULDN.js';
|
|
8
|
-
import {
|
|
9
|
-
export {
|
|
10
|
-
import { JobProgress } from './chunk-
|
|
11
|
-
export { FloatingPanel, JobProgress, Legend, Sidebar } from './chunk-
|
|
12
|
-
export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl } from './chunk-
|
|
13
|
-
import {
|
|
14
|
-
export { CURRENT_DOCUMENT_VERSION,
|
|
15
|
-
import {
|
|
16
|
-
export {
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
import { suppressContributions, mergeContributions, builtinContributions, TitleBarRegion, ToolbarRegion, PaletteRegion, ViewportRegion, SidebarRegion, StatusRegion, resolveLabTool, AnnotationsContext, useLabChromeContext, labContributions, LabHeaderRegion, LabFooterRegion, LabSidebarRegion } from './chunk-NS64DXMN.js';
|
|
11
|
+
export { AnnotationsContext, ExportMenu, LabFooterRegion, LabHeaderRegion, LabSidebarRegion, MarkList, PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, builtinContributions, contributionsIn, labContributions, mergeContributions, suppressContributions, toolContributions, useAnnotations, useAnnotationsOptional, useLabChromeContext } from './chunk-NS64DXMN.js';
|
|
12
|
+
import { Split, JobProgress } from './chunk-EKIICY6X.js';
|
|
13
|
+
export { FloatingPanel, JobProgress, Legend, Sidebar, Split } from './chunk-EKIICY6X.js';
|
|
14
|
+
export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl } from './chunk-KJALCDWE.js';
|
|
15
|
+
import { createLabStore, openLabStore, panelKey } from './chunk-67SJLMC7.js';
|
|
16
|
+
export { CURRENT_DOCUMENT_VERSION, dockPanel, labDocumentKey, openLabStore, panelKey, quarantineKey, undockPanel, useTrialState } from './chunk-67SJLMC7.js';
|
|
17
|
+
import { newId, useWarnIgnoredChange, useOpenOnce, PersistenceContext, createRecordCache, noneAdapter, defaultStorage, usePersistedState } from './chunk-RWNDDKOH.js';
|
|
18
|
+
export { Persistence, createIndexedDbAdapter, createMemoryAdapter, indexedDbAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, usePersistedState } from './chunk-RWNDDKOH.js';
|
|
19
|
+
export { ControlPanel } from './chunk-TQBBYTAF.js';
|
|
20
|
+
import { useConfigSchema } from './chunk-6ZDGOZQV.js';
|
|
21
|
+
export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useConfigSchema } from './chunk-6ZDGOZQV.js';
|
|
22
|
+
import { useLabContext, LabContext } from './chunk-NDRYLVOW.js';
|
|
23
|
+
export { LabContext, useLabContext } from './chunk-NDRYLVOW.js';
|
|
24
|
+
export { fromConfigFields, isLeafVisible } from './chunk-UU3NJO6N.js';
|
|
19
25
|
import { hasConfigPath, schemaNodeAtPath, withValueAtPath, fillConfigDefaults } from './chunk-E44SU6XS.js';
|
|
20
26
|
export { fillConfigDefaults, hasConfigPath, schemaNodeAtPath, valueAtPath, withValueAtPath } from './chunk-E44SU6XS.js';
|
|
21
|
-
import { CanvasStack } from './chunk-
|
|
22
|
-
export { CanvasStack, PITCH_LIMIT, clampPitch, orbitAfterDrag, orbitAfterWheel, useOrbit, usePanZoom, wrapYaw } from './chunk-
|
|
23
|
-
|
|
24
|
-
export { LayerList } from './chunk-BKFVHKJH.js';
|
|
25
|
-
import './chunk-SBXNQW4G.js';
|
|
26
|
-
import { E, w, T, C, g, r6 } from './chunk-W3ECWC2K.js';
|
|
27
|
-
export { N2 as CheckboxRow, M2 as ColorRow, y3 as CurveField, z as EffectCard, V as EffectCardList, c3 as LayerStack, F as NumberRow, H as PropertyGroup, T2 as PropertyList, w2 as PropertyPanel, D as PropertyRow, E2 as PropertySpan, I as SelectRow, A2 as SliderRow, R as Subpanel, P2 as TextRow, L2 as ToggleRow } from './chunk-W3ECWC2K.js';
|
|
28
|
-
import { resolveLoupe, TrialLoupe } from './chunk-TJ7QY3OC.js';
|
|
29
|
-
export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LoupeBubble, TrialLoupe, drawCanvasLens, lensCamera, lensSourceRect, resolveLoupe, sampleStack, useLoupe } from './chunk-TJ7QY3OC.js';
|
|
30
|
-
export { centerOn, zoomAt } from './chunk-CTRKTLYZ.js';
|
|
27
|
+
import { CameraWheelContext, as2DView, DEFAULT_VIEW, CanvasStack, Stage, withZoom, fitStage } from './chunk-PGETSEDK.js';
|
|
28
|
+
export { CanvasStack, DEFAULT_VIEW, PITCH_LIMIT, Stage, as2DView, clampPitch, fitStage, orbitAfterDrag, orbitAfterWheel, useOrbit, usePanZoom, wrapYaw } from './chunk-PGETSEDK.js';
|
|
29
|
+
export { centerOn, zoomAt } from './chunk-H6ZAOWNE.js';
|
|
31
30
|
import { applyCamera } from './chunk-2AYGEN57.js';
|
|
32
31
|
export { DEFAULT_FRAME, applyCamera, resolveFrame, screenToWorld, worldToScreen } from './chunk-2AYGEN57.js';
|
|
32
|
+
import { useSurfaceOptional, useSurfaceCanvas, useTileId, SurfaceContext, SurfaceCanvasContext } from './chunk-WIG6XHJ7.js';
|
|
33
|
+
export { SurfaceCanvasContext, SurfaceContext, useSurface, useSurfaceCanvas, useSurfaceOptional, useSurfaceTile, useTileId } from './chunk-WIG6XHJ7.js';
|
|
33
34
|
export { CanvasStackContext } from './chunk-CQLQPQ4P.js';
|
|
34
|
-
import {
|
|
35
|
+
import { LabStoreContext, TrialIdProvider } from './chunk-2YILQ7Y5.js';
|
|
36
|
+
export { LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, useLabStore, useTrialId } from './chunk-2YILQ7Y5.js';
|
|
37
|
+
import { LayerList } from './chunk-MUKOW3TC.js';
|
|
38
|
+
export { LayerList } from './chunk-MUKOW3TC.js';
|
|
39
|
+
import './chunk-SBXNQW4G.js';
|
|
40
|
+
import { E, w, T, p, u, c, n, f, r2, h, C, l5, r6 } from './chunk-S2SHZRD7.js';
|
|
41
|
+
export { a3 as Button, P3 as CheckboxRow, N2 as ColorRow, b3 as CurveField, B as EffectCard, H as EffectCardList, c3 as LayerStack, I as NumberRow, U2 as PropertyGroup, E2 as PropertyList, T2 as PropertyPanel, O as PropertyRow, D as PropertySpan, L as SelectRow, j2 as SliderRow, z as Subpanel, F as TextRow, r6 as ToggleBar, R as ToggleRow } from './chunk-S2SHZRD7.js';
|
|
42
|
+
import { textCommand, linePath, markerDrawCommands, ellipsePath, rectPath, createScene, asNodeId as asNodeId$1, WeaselProvider, SceneCanvas, sceneFromJSON, PathBuilder, renderSceneToPixels, useActiveToolContext, getMarker, getPaintKind, boundsOfPath, PATH_M, PATH_L, PATH_C, PATH_Q, PATH_Z, resolveAlign, SCRIPT_METRICS } from '@weasel-js/core';
|
|
35
43
|
import { tileGeometry } from '@weasel-js/core/patterns-builtin';
|
|
36
|
-
import { createContext, useState, useRef, useEffect, useContext, useMemo, useId,
|
|
37
|
-
import { createPortal } from 'react-dom';
|
|
44
|
+
import { createContext, useState, useRef, useEffect, useCallback, useContext, useMemo, useId, Children, useLayoutEffect, useSyncExternalStore } from 'react';
|
|
45
|
+
import { flushSync, createPortal } from 'react-dom';
|
|
38
46
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
39
47
|
import { useStore } from 'zustand/react';
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
48
|
+
import { Provider, StrategyRegistryProvider, DragProvider, Container, useDragHandle } from 'windease/react';
|
|
49
|
+
import { asNodeId, Store, createNode, floatingStrategy, gridStrategy } from 'windease';
|
|
42
50
|
|
|
43
51
|
var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
|
|
44
|
-
function multiply(
|
|
52
|
+
function multiply(a2, b2) {
|
|
45
53
|
return [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
a2[0] * b2[0] + a2[2] * b2[1],
|
|
55
|
+
a2[1] * b2[0] + a2[3] * b2[1],
|
|
56
|
+
a2[0] * b2[2] + a2[2] * b2[3],
|
|
57
|
+
a2[1] * b2[2] + a2[3] * b2[3],
|
|
58
|
+
a2[0] * b2[4] + a2[2] * b2[5] + a2[4],
|
|
59
|
+
a2[1] * b2[4] + a2[3] * b2[5] + a2[5]
|
|
52
60
|
];
|
|
53
61
|
}
|
|
54
62
|
function isIdentity(m, eps = 1e-9) {
|
|
@@ -56,13 +64,17 @@ function isIdentity(m, eps = 1e-9) {
|
|
|
56
64
|
}
|
|
57
65
|
function formatMatrix(m) {
|
|
58
66
|
if (isIdentity(m)) return null;
|
|
59
|
-
return `matrix(${m.map((
|
|
67
|
+
return `matrix(${m.map((n2) => trimNumber(n2)).join(" ")})`;
|
|
60
68
|
}
|
|
61
|
-
function trimNumber(
|
|
62
|
-
if (!Number.isFinite(
|
|
63
|
-
const
|
|
64
|
-
if (Number.isInteger(
|
|
65
|
-
return String(
|
|
69
|
+
function trimNumber(n2) {
|
|
70
|
+
if (!Number.isFinite(n2)) return "0";
|
|
71
|
+
const r3 = Math.round(n2 * 1e6) / 1e6;
|
|
72
|
+
if (Number.isInteger(r3)) return String(r3);
|
|
73
|
+
return String(r3);
|
|
74
|
+
}
|
|
75
|
+
function anchorOffset(style, width) {
|
|
76
|
+
const edge = resolveAlign(style?.align ?? "left", style?.direction ?? "ltr");
|
|
77
|
+
return edge === "center" ? width / 2 : edge === "right" ? width : 0;
|
|
66
78
|
}
|
|
67
79
|
function patternSpecOf(paint) {
|
|
68
80
|
return "tile" in paint.pattern ? paint.pattern : null;
|
|
@@ -110,32 +122,32 @@ function escapeAttr(s) {
|
|
|
110
122
|
}
|
|
111
123
|
function serializePathD(path) {
|
|
112
124
|
if (path.kind === "rect") {
|
|
113
|
-
const { x, y
|
|
114
|
-
return `M${trimNumber(x)} ${trimNumber(
|
|
125
|
+
const { x, y, width, height } = path;
|
|
126
|
+
return `M${trimNumber(x)} ${trimNumber(y)}h${trimNumber(width)}v${trimNumber(height)}h${trimNumber(-width)}Z`;
|
|
115
127
|
}
|
|
116
128
|
const cmds = path.commands;
|
|
117
129
|
const xs = path.coords;
|
|
118
130
|
const out = [];
|
|
119
131
|
let ci = 0;
|
|
120
132
|
for (let i = 0; i < cmds.length; i++) {
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
133
|
+
const c3 = cmds[i];
|
|
134
|
+
if (c3 === PATH_M) {
|
|
123
135
|
out.push(`M${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
|
|
124
136
|
ci += 2;
|
|
125
|
-
} else if (
|
|
137
|
+
} else if (c3 === PATH_L) {
|
|
126
138
|
out.push(`L${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
|
|
127
139
|
ci += 2;
|
|
128
|
-
} else if (
|
|
140
|
+
} else if (c3 === PATH_C) {
|
|
129
141
|
out.push(
|
|
130
142
|
`C${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])} ${trimNumber(xs[ci + 4])} ${trimNumber(xs[ci + 5])}`
|
|
131
143
|
);
|
|
132
144
|
ci += 6;
|
|
133
|
-
} else if (
|
|
145
|
+
} else if (c3 === PATH_Q) {
|
|
134
146
|
out.push(
|
|
135
147
|
`Q${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])}`
|
|
136
148
|
);
|
|
137
149
|
ci += 4;
|
|
138
|
-
} else if (
|
|
150
|
+
} else if (c3 === PATH_Z) {
|
|
139
151
|
out.push("Z");
|
|
140
152
|
}
|
|
141
153
|
}
|
|
@@ -259,7 +271,7 @@ function serializeSvg(nodes, opts = {}) {
|
|
|
259
271
|
}
|
|
260
272
|
}
|
|
261
273
|
const defsXml = registry.toDefsXml(opts.onWarn);
|
|
262
|
-
const bodyXml = nodes.map((
|
|
274
|
+
const bodyXml = nodes.map((n2) => nodeXml(n2, registry, namespaces)).join("");
|
|
263
275
|
const titleXml = opts.title && opts.title.length > 0 ? `<title>${escapeText(opts.title)}</title>` : "";
|
|
264
276
|
return `<svg ${rootAttrs.join(" ")}>${titleXml}${defsXml}${docMetaXml}${bodyXml}</svg>`;
|
|
265
277
|
}
|
|
@@ -304,18 +316,18 @@ function metaElementsXml(meta, namespaces) {
|
|
|
304
316
|
return out;
|
|
305
317
|
}
|
|
306
318
|
function registerPaintServers(nodes, registry) {
|
|
307
|
-
for (const
|
|
308
|
-
if (
|
|
309
|
-
registerPaintServers(
|
|
310
|
-
} else if (
|
|
311
|
-
if (
|
|
312
|
-
if (
|
|
313
|
-
registerMarkers(
|
|
314
|
-
} else if (
|
|
315
|
-
registerTextPaint(
|
|
316
|
-
registerTextPaint(
|
|
317
|
-
registerMarkers(
|
|
318
|
-
for (const run of
|
|
319
|
+
for (const n2 of nodes) {
|
|
320
|
+
if (n2.kind === "group") {
|
|
321
|
+
registerPaintServers(n2.children, registry);
|
|
322
|
+
} else if (n2.kind === "path") {
|
|
323
|
+
if (n2.fill.kind === "gradient") registry.register(n2.fill.paint);
|
|
324
|
+
if (n2.stroke && n2.stroke.paint.kind === "gradient") registry.register(n2.stroke.paint.paint);
|
|
325
|
+
registerMarkers(n2.stroke, registry);
|
|
326
|
+
} else if (n2.kind === "text") {
|
|
327
|
+
registerTextPaint(n2.fill, registry);
|
|
328
|
+
registerTextPaint(n2.stroke?.paint, registry);
|
|
329
|
+
registerMarkers(n2.stroke, registry);
|
|
330
|
+
for (const run of n2.runs ?? []) {
|
|
319
331
|
registerTextPaint(run.fill, registry);
|
|
320
332
|
registerTextPaint(run.stroke?.paint, registry);
|
|
321
333
|
}
|
|
@@ -377,19 +389,19 @@ function groupXml(node, registry, namespaces) {
|
|
|
377
389
|
}
|
|
378
390
|
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
379
391
|
const head = attrs.length > 0 ? `<g ${attrs.join(" ")}${metaAttrs}>` : `<g${metaAttrs}>`;
|
|
380
|
-
const body = node.children.map((
|
|
392
|
+
const body = node.children.map((c3) => nodeXml(c3, registry, namespaces)).join("");
|
|
381
393
|
return `${head}${body}${metaElementsXml(node.meta, namespaces)}</g>`;
|
|
382
394
|
}
|
|
383
395
|
function pathXml(node, registry, namespaces) {
|
|
384
396
|
const attrs = [`d="${serializePathD(node.path)}"`];
|
|
385
397
|
const fillAttrs = paintAttrs(node.fill, "fill", registry);
|
|
386
|
-
for (const
|
|
398
|
+
for (const a2 of fillAttrs) attrs.push(a2);
|
|
387
399
|
if (node.path.kind === "polygon" && node.path.fillRule === "evenodd") {
|
|
388
400
|
attrs.push(`fill-rule="evenodd"`);
|
|
389
401
|
}
|
|
390
402
|
if (node.stroke) {
|
|
391
403
|
const strokeAttrs = strokeAttrsFor(node.stroke, registry);
|
|
392
|
-
for (const
|
|
404
|
+
for (const a2 of strokeAttrs) attrs.push(a2);
|
|
393
405
|
} else {
|
|
394
406
|
attrs.push('stroke="none"');
|
|
395
407
|
}
|
|
@@ -397,9 +409,9 @@ function pathXml(node, registry, namespaces) {
|
|
|
397
409
|
attrs.push(`opacity="${trimNumber(node.opacity)}"`);
|
|
398
410
|
}
|
|
399
411
|
if (node.rotation != null && node.rotation !== 0) {
|
|
400
|
-
const
|
|
401
|
-
const cx =
|
|
402
|
-
const cy =
|
|
412
|
+
const b2 = pathBounds(node.path);
|
|
413
|
+
const cx = b2.minX + (b2.maxX - b2.minX) / 2;
|
|
414
|
+
const cy = b2.minY + (b2.maxY - b2.minY) / 2;
|
|
403
415
|
const deg = node.rotation * 180 / Math.PI;
|
|
404
416
|
attrs.push(`transform="rotate(${trimNumber(deg)} ${trimNumber(cx)} ${trimNumber(cy)})"`);
|
|
405
417
|
}
|
|
@@ -495,42 +507,42 @@ function strokeAttrsFor(stroke, registry) {
|
|
|
495
507
|
}
|
|
496
508
|
function computeBounds(nodes) {
|
|
497
509
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
498
|
-
const visit = (
|
|
499
|
-
if (
|
|
500
|
-
const childCtm =
|
|
501
|
-
|
|
510
|
+
const visit = (n2, ctm) => {
|
|
511
|
+
if (n2.kind === "group") {
|
|
512
|
+
const childCtm = n2.transform ? multiply(ctm, n2.transform) : ctm;
|
|
513
|
+
n2.children.forEach((c3) => visit(c3, childCtm));
|
|
502
514
|
return;
|
|
503
515
|
}
|
|
504
|
-
const
|
|
505
|
-
const local =
|
|
506
|
-
for (const [x,
|
|
507
|
-
const px = local[0] * x + local[2] *
|
|
508
|
-
const py = local[1] * x + local[3] *
|
|
516
|
+
const b2 = n2.kind === "text" || n2.kind === "image" ? { minX: n2.x, minY: n2.y, maxX: n2.x + n2.width, maxY: n2.y + n2.height } : pathBounds(n2.path);
|
|
517
|
+
const local = n2.rotation ? rotateAboutCenter(ctm, b2, n2.rotation) : ctm;
|
|
518
|
+
for (const [x, y] of corners(b2)) {
|
|
519
|
+
const px = local[0] * x + local[2] * y + local[4];
|
|
520
|
+
const py = local[1] * x + local[3] * y + local[5];
|
|
509
521
|
if (px < minX) minX = px;
|
|
510
522
|
if (py < minY) minY = py;
|
|
511
523
|
if (px > maxX) maxX = px;
|
|
512
524
|
if (py > maxY) maxY = py;
|
|
513
525
|
}
|
|
514
526
|
};
|
|
515
|
-
nodes.forEach((
|
|
527
|
+
nodes.forEach((n2) => visit(n2, IDENTITY_MATRIX));
|
|
516
528
|
if (!Number.isFinite(minX)) {
|
|
517
529
|
return { x: 0, y: 0, width: 0, height: 0 };
|
|
518
530
|
}
|
|
519
531
|
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
520
532
|
}
|
|
521
|
-
function corners(
|
|
522
|
-
return [[
|
|
533
|
+
function corners(b2) {
|
|
534
|
+
return [[b2.minX, b2.minY], [b2.maxX, b2.minY], [b2.maxX, b2.maxY], [b2.minX, b2.maxY]];
|
|
523
535
|
}
|
|
524
|
-
function rotateAboutCenter(ctm,
|
|
525
|
-
const cx = (
|
|
526
|
-
const cy = (
|
|
527
|
-
const
|
|
536
|
+
function rotateAboutCenter(ctm, b2, angle) {
|
|
537
|
+
const cx = (b2.minX + b2.maxX) / 2;
|
|
538
|
+
const cy = (b2.minY + b2.maxY) / 2;
|
|
539
|
+
const c3 = Math.cos(angle);
|
|
528
540
|
const s = Math.sin(angle);
|
|
529
|
-
return multiply(ctm, [
|
|
541
|
+
return multiply(ctm, [c3, s, -s, c3, cx - cx * c3 + cy * s, cy - cx * s - cy * c3]);
|
|
530
542
|
}
|
|
531
543
|
function textXml(node, registry, namespaces) {
|
|
532
544
|
const attrs = [
|
|
533
|
-
`x="${trimNumber(node.x)}"`,
|
|
545
|
+
`x="${trimNumber(node.x + anchorOffset(node.style, node.width))}"`,
|
|
534
546
|
`y="${trimNumber(node.y)}"`,
|
|
535
547
|
`dominant-baseline="text-before-edge"`,
|
|
536
548
|
// The runs model stores real line breaks; SVG's default whitespace
|
|
@@ -556,6 +568,7 @@ function textXml(node, registry, namespaces) {
|
|
|
556
568
|
}
|
|
557
569
|
const decoration = textDecorationValue(style?.underline, style?.strikethrough, style?.overline);
|
|
558
570
|
if (decoration) attrs.push(`text-decoration="${decoration}"`);
|
|
571
|
+
if (style?.wrap) attrs.push('data-weasel-wrap="true"');
|
|
559
572
|
if (node.fill === null) {
|
|
560
573
|
attrs.push('fill="none"');
|
|
561
574
|
} else if (node.fill) {
|
|
@@ -569,7 +582,7 @@ function textXml(node, registry, namespaces) {
|
|
|
569
582
|
attrs.push(`fill="url(#${id})"`);
|
|
570
583
|
}
|
|
571
584
|
}
|
|
572
|
-
for (const
|
|
585
|
+
for (const a2 of coreStrokeAttrs(node.stroke, registry)) attrs.push(a2);
|
|
573
586
|
if (node.opacity != null && node.opacity !== 1) {
|
|
574
587
|
attrs.push(`opacity="${trimNumber(node.opacity)}"`);
|
|
575
588
|
}
|
|
@@ -579,7 +592,7 @@ function textXml(node, registry, namespaces) {
|
|
|
579
592
|
const deg = node.rotation * 180 / Math.PI;
|
|
580
593
|
attrs.push(`transform="rotate(${trimNumber(deg)} ${trimNumber(cx)} ${trimNumber(cy)})"`);
|
|
581
594
|
}
|
|
582
|
-
const body = node.runs && node.runs.length > 0 ? node.runs.map((
|
|
595
|
+
const body = node.runs && node.runs.length > 0 ? node.runs.map((r3) => runXml(r3, registry)).join("") : escapeText(node.text);
|
|
583
596
|
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
584
597
|
const metaEls = metaElementsXml(node.meta, namespaces);
|
|
585
598
|
return `<text ${attrs.join(" ")}${metaAttrs}>${body}${metaEls}</text>`;
|
|
@@ -611,7 +624,7 @@ function runXml(run, registry) {
|
|
|
611
624
|
attrs.push(`fill="url(#${id})"`);
|
|
612
625
|
}
|
|
613
626
|
}
|
|
614
|
-
for (const
|
|
627
|
+
for (const a2 of coreStrokeAttrs(run.stroke, registry)) attrs.push(a2);
|
|
615
628
|
const head = attrs.length > 0 ? `<tspan ${attrs.join(" ")}>` : "<tspan>";
|
|
616
629
|
return `${head}${escapeText(run.text)}</tspan>`;
|
|
617
630
|
}
|
|
@@ -637,32 +650,32 @@ function pathBounds(path) {
|
|
|
637
650
|
maxY: path.y + path.height
|
|
638
651
|
};
|
|
639
652
|
}
|
|
640
|
-
const
|
|
641
|
-
return { minX:
|
|
653
|
+
const b2 = boundsOfPath(path);
|
|
654
|
+
return { minX: b2.x, minY: b2.y, maxX: b2.x + b2.width, maxY: b2.y + b2.height };
|
|
642
655
|
}
|
|
643
656
|
var MARK_COLOR = "#e5484d";
|
|
644
657
|
var MARK_WIDTH = 2;
|
|
645
658
|
var TEXT_SIZE = 14;
|
|
646
|
-
var toWorld = (
|
|
647
|
-
x:
|
|
648
|
-
y:
|
|
659
|
+
var toWorld = (p2, content) => ({
|
|
660
|
+
x: p2.x * content.w,
|
|
661
|
+
y: p2.y * content.h
|
|
649
662
|
});
|
|
650
663
|
function vertices(m, content) {
|
|
651
664
|
const stored = m.data.points;
|
|
652
|
-
if (stored && stored.length >= 2) return stored.map((
|
|
653
|
-
const { x, y
|
|
665
|
+
if (stored && stored.length >= 2) return stored.map((p2) => toWorld(p2, content));
|
|
666
|
+
const { x, y, width, height } = m.pose;
|
|
654
667
|
return [
|
|
655
|
-
{ x, y
|
|
656
|
-
{ x: x + width, y:
|
|
668
|
+
{ x, y },
|
|
669
|
+
{ x: x + width, y: y + height }
|
|
657
670
|
];
|
|
658
671
|
}
|
|
659
672
|
function polyline(points) {
|
|
660
|
-
const
|
|
673
|
+
const b2 = new PathBuilder();
|
|
661
674
|
const first = points[0];
|
|
662
675
|
if (!first) return rectPath(0, 0, 0, 0);
|
|
663
|
-
|
|
664
|
-
for (const
|
|
665
|
-
return
|
|
676
|
+
b2.moveTo(first.x, first.y);
|
|
677
|
+
for (const p2 of points.slice(1)) b2.lineTo(p2.x, p2.y);
|
|
678
|
+
return b2.build();
|
|
666
679
|
}
|
|
667
680
|
function markCommands(m, content, style = {}) {
|
|
668
681
|
const color = style.color ?? MARK_COLOR;
|
|
@@ -681,13 +694,13 @@ function markCommands(m, content, style = {}) {
|
|
|
681
694
|
case "ellipse":
|
|
682
695
|
return [{ kind: "path", path: ellipsePath(m.pose), stroke }];
|
|
683
696
|
case "line": {
|
|
684
|
-
const [
|
|
685
|
-
return [{ kind: "path", path: linePath(
|
|
697
|
+
const [a2, b2] = vertices(m, content);
|
|
698
|
+
return [{ kind: "path", path: linePath(a2, b2), stroke }];
|
|
686
699
|
}
|
|
687
700
|
case "arrow": {
|
|
688
|
-
const [
|
|
701
|
+
const [a2, b2] = vertices(m, content);
|
|
689
702
|
const marked = { ...stroke, markerEnd: "arrow" };
|
|
690
|
-
const path = linePath(
|
|
703
|
+
const path = linePath(a2, b2);
|
|
691
704
|
return [
|
|
692
705
|
{ kind: "path", path, stroke: marked },
|
|
693
706
|
...markerDrawCommands(path, marked, MARK_WIDTH, void 0)
|
|
@@ -806,7 +819,7 @@ function capturePlan(base, format) {
|
|
|
806
819
|
if (format === "svg") return "svg-document";
|
|
807
820
|
return base?.kind === "svg" ? "svg-document" : "raster-stack";
|
|
808
821
|
}
|
|
809
|
-
function nestBaseSvg(markup,
|
|
822
|
+
function nestBaseSvg(markup, w2, h2) {
|
|
810
823
|
const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
|
|
811
824
|
const root = doc.documentElement;
|
|
812
825
|
if (doc.getElementsByTagName("parsererror").length > 0 || root.localName !== "svg") {
|
|
@@ -814,17 +827,17 @@ function nestBaseSvg(markup, w3, h) {
|
|
|
814
827
|
}
|
|
815
828
|
root.setAttribute("x", "0");
|
|
816
829
|
root.setAttribute("y", "0");
|
|
817
|
-
root.setAttribute("width", String(
|
|
818
|
-
root.setAttribute("height", String(
|
|
830
|
+
root.setAttribute("width", String(w2));
|
|
831
|
+
root.setAttribute("height", String(h2));
|
|
819
832
|
return new XMLSerializer().serializeToString(root);
|
|
820
833
|
}
|
|
821
|
-
function embedRasterBase(base,
|
|
834
|
+
function embedRasterBase(base, w2, h2, onWarn) {
|
|
822
835
|
const href = base.kind === "canvas" ? base.canvas.toDataURL() : base.kind === "image" ? base.src : "";
|
|
823
836
|
if (base.kind === "image" && !href.startsWith("data:")) {
|
|
824
837
|
onWarn?.(`base image '${href}' is a reference, so this SVG is not self-contained`);
|
|
825
838
|
}
|
|
826
|
-
return serializeSvg([{ kind: "image", href, x: 0, y: 0, width:
|
|
827
|
-
viewBox: { x: 0, y: 0, width:
|
|
839
|
+
return serializeSvg([{ kind: "image", href, x: 0, y: 0, width: w2, height: h2 }], {
|
|
840
|
+
viewBox: { x: 0, y: 0, width: w2, height: h2 }
|
|
828
841
|
});
|
|
829
842
|
}
|
|
830
843
|
function markNodesOf(scene, draw) {
|
|
@@ -840,15 +853,15 @@ function markNodesOf(scene, draw) {
|
|
|
840
853
|
}
|
|
841
854
|
function composeCaptureSvg(args) {
|
|
842
855
|
const { base, scene, draw, scale, onWarn } = args;
|
|
843
|
-
const { w:
|
|
844
|
-
const baseXml = base === void 0 ? "" : base.kind === "svg" ? nestBaseSvg(base.markup,
|
|
856
|
+
const { w: w2, h: h2 } = draw.content;
|
|
857
|
+
const baseXml = base === void 0 ? "" : base.kind === "svg" ? nestBaseSvg(base.markup, w2, h2) : embedRasterBase(base, w2, h2, onWarn);
|
|
845
858
|
const marksXml = serializeSvg(markNodesOf(scene, draw), {
|
|
846
|
-
viewBox: { x: 0, y: 0, width:
|
|
859
|
+
viewBox: { x: 0, y: 0, width: w2, height: h2 },
|
|
847
860
|
onWarn
|
|
848
861
|
});
|
|
849
|
-
const outW = Math.max(1, Math.round(
|
|
850
|
-
const outH = Math.max(1, Math.round(
|
|
851
|
-
return `<svg xmlns="${SVG_NS}" width="${outW}" height="${outH}" viewBox="0 0 ${
|
|
862
|
+
const outW = Math.max(1, Math.round(w2 * scale));
|
|
863
|
+
const outH = Math.max(1, Math.round(h2 * scale));
|
|
864
|
+
return `<svg xmlns="${SVG_NS}" width="${outW}" height="${outH}" viewBox="0 0 ${w2} ${h2}">${baseXml}${marksXml}</svg>`;
|
|
852
865
|
}
|
|
853
866
|
function context2d(width, height) {
|
|
854
867
|
const canvas = document.createElement("canvas");
|
|
@@ -942,26 +955,26 @@ async function captureTarget(args, opts = {}) {
|
|
|
942
955
|
}
|
|
943
956
|
|
|
944
957
|
// src/annotations/frac.ts
|
|
945
|
-
function fracToWorld(
|
|
958
|
+
function fracToWorld(f3, content) {
|
|
946
959
|
return {
|
|
947
|
-
x:
|
|
948
|
-
y:
|
|
949
|
-
width:
|
|
950
|
-
height:
|
|
960
|
+
x: f3.x * content.w,
|
|
961
|
+
y: f3.y * content.h,
|
|
962
|
+
width: f3.w * content.w,
|
|
963
|
+
height: f3.h * content.h
|
|
951
964
|
};
|
|
952
965
|
}
|
|
953
|
-
function worldToFrac(
|
|
966
|
+
function worldToFrac(r3, content) {
|
|
954
967
|
const sx = content.w === 0 ? 0 : 1 / content.w;
|
|
955
968
|
const sy = content.h === 0 ? 0 : 1 / content.h;
|
|
956
|
-
return { x:
|
|
969
|
+
return { x: r3.x * sx, y: r3.y * sy, w: r3.width * sx, h: r3.height * sy };
|
|
957
970
|
}
|
|
958
971
|
var DP = 1e4;
|
|
959
|
-
function roundFrac(
|
|
972
|
+
function roundFrac(f3) {
|
|
960
973
|
return {
|
|
961
|
-
x: Math.round(
|
|
962
|
-
y: Math.round(
|
|
963
|
-
w: Math.round(
|
|
964
|
-
h: Math.round(
|
|
974
|
+
x: Math.round(f3.x * DP) / DP,
|
|
975
|
+
y: Math.round(f3.y * DP) / DP,
|
|
976
|
+
w: Math.round(f3.w * DP) / DP,
|
|
977
|
+
h: Math.round(f3.h * DP) / DP
|
|
965
978
|
};
|
|
966
979
|
}
|
|
967
980
|
function fracContains(box, pt, tol = 0) {
|
|
@@ -1104,21 +1117,21 @@ function createAnnotationStore(opts) {
|
|
|
1104
1117
|
if (!scene) return [];
|
|
1105
1118
|
const out = [];
|
|
1106
1119
|
for (const id of scene.renderOrder()) {
|
|
1107
|
-
const
|
|
1108
|
-
if (
|
|
1120
|
+
const a2 = project(target, String(id));
|
|
1121
|
+
if (a2) out.push(a2);
|
|
1109
1122
|
}
|
|
1110
1123
|
return out;
|
|
1111
1124
|
};
|
|
1112
1125
|
const all = () => liveTargets().flatMap(marksOn);
|
|
1113
|
-
const matches = (
|
|
1114
|
-
if (q.target !== void 0 &&
|
|
1115
|
-
if (q.kind !== void 0 &&
|
|
1116
|
-
if (q.status !== void 0 &&
|
|
1126
|
+
const matches = (a2, q) => {
|
|
1127
|
+
if (q.target !== void 0 && a2.target !== q.target) return false;
|
|
1128
|
+
if (q.kind !== void 0 && a2.kind !== q.kind) return false;
|
|
1129
|
+
if (q.status !== void 0 && a2.status !== q.status) return false;
|
|
1117
1130
|
if (q.tags !== void 0) {
|
|
1118
|
-
const have =
|
|
1131
|
+
const have = a2.tags ?? [];
|
|
1119
1132
|
for (const t of q.tags) if (!have.includes(t)) return false;
|
|
1120
1133
|
}
|
|
1121
|
-
if (q.where && !q.where(
|
|
1134
|
+
if (q.where && !q.where(a2)) return false;
|
|
1122
1135
|
return true;
|
|
1123
1136
|
};
|
|
1124
1137
|
const nodeOf = (id) => {
|
|
@@ -1136,17 +1149,17 @@ function createAnnotationStore(opts) {
|
|
|
1136
1149
|
return parts ? project(parts.target, parts.node) : void 0;
|
|
1137
1150
|
},
|
|
1138
1151
|
query(q) {
|
|
1139
|
-
return q ? all().filter((
|
|
1152
|
+
return q ? all().filter((a2) => matches(a2, q)) : all();
|
|
1140
1153
|
},
|
|
1141
1154
|
hitTest(target, pt, tol = 0) {
|
|
1142
|
-
return marksOn(target).filter((
|
|
1155
|
+
return marksOn(target).filter((a2) => fracContains(a2.frac, pt, tol)).reverse();
|
|
1143
1156
|
},
|
|
1144
1157
|
within(target, box) {
|
|
1145
|
-
return marksOn(target).filter((
|
|
1158
|
+
return marksOn(target).filter((a2) => fracEncloses(box, a2.frac));
|
|
1146
1159
|
},
|
|
1147
|
-
isStale(
|
|
1148
|
-
const keys = targetOf(
|
|
1149
|
-
return isStale(
|
|
1160
|
+
isStale(a2, config) {
|
|
1161
|
+
const keys = targetOf(a2.target)?.positionDependsOn ?? [];
|
|
1162
|
+
return isStale(a2.seen, config, keys);
|
|
1150
1163
|
},
|
|
1151
1164
|
canUndo() {
|
|
1152
1165
|
return history.canUndo(historySceneAt);
|
|
@@ -1184,7 +1197,7 @@ function createAnnotationStore(opts) {
|
|
|
1184
1197
|
for (const [target, scene] of scenes) {
|
|
1185
1198
|
const next = wanted.get(target) ?? [];
|
|
1186
1199
|
const now = scene.getSelection();
|
|
1187
|
-
if (now.length === next.length && next.every((
|
|
1200
|
+
if (now.length === next.length && next.every((n2, i) => String(now[i]) === n2)) continue;
|
|
1188
1201
|
scene.setSelection(next.map(asNodeId$1));
|
|
1189
1202
|
}
|
|
1190
1203
|
},
|
|
@@ -1246,7 +1259,7 @@ function createAnnotationStore(opts) {
|
|
|
1246
1259
|
content: info.content,
|
|
1247
1260
|
positionDependsOn: info.positionDependsOn,
|
|
1248
1261
|
config: opts.config?.(),
|
|
1249
|
-
meaning: opts.meaning
|
|
1262
|
+
meaning: typeof opts.meaning === "function" ? opts.meaning() : opts.meaning
|
|
1250
1263
|
},
|
|
1251
1264
|
base: info.base,
|
|
1252
1265
|
onWarn: (message) => console.warn(`[labkit] capture: ${message}`)
|
|
@@ -1269,6 +1282,32 @@ function annotationsFromJSON(raw, targets, rest = {}) {
|
|
|
1269
1282
|
return createAnnotationStore({ targets, restore: doc.scenes, ...rest });
|
|
1270
1283
|
}
|
|
1271
1284
|
|
|
1285
|
+
// src/annotations/toolMap.ts
|
|
1286
|
+
var TOOLS = {
|
|
1287
|
+
select: { weaselTool: "select" },
|
|
1288
|
+
stroke: { weaselTool: "pencil", kind: "stroke" },
|
|
1289
|
+
line: { weaselTool: "line", kind: "line" },
|
|
1290
|
+
arrow: { weaselTool: "line", kind: "arrow" },
|
|
1291
|
+
rect: { weaselTool: "rect", kind: "rect" },
|
|
1292
|
+
ellipse: { weaselTool: "ellipse", kind: "ellipse" },
|
|
1293
|
+
text: { weaselTool: "text", kind: "text" }
|
|
1294
|
+
};
|
|
1295
|
+
var ANNOTATION_TOOLS = [
|
|
1296
|
+
{ id: "select", label: "Select", icon: p, group: "annotate" },
|
|
1297
|
+
{ id: "stroke", label: "Freehand", icon: u, group: "annotate" },
|
|
1298
|
+
{ id: "line", label: "Line", icon: c, group: "annotate" },
|
|
1299
|
+
{ id: "arrow", label: "Arrow", icon: n, group: "annotate" },
|
|
1300
|
+
{ id: "rect", label: "Rectangle", icon: f, group: "annotate" },
|
|
1301
|
+
{ id: "ellipse", label: "Ellipse", icon: r2, group: "annotate" },
|
|
1302
|
+
{ id: "text", label: "Text", icon: h, group: "annotate" }
|
|
1303
|
+
];
|
|
1304
|
+
function annotationToolInfo(id) {
|
|
1305
|
+
return id === null ? void 0 : TOOLS[id];
|
|
1306
|
+
}
|
|
1307
|
+
var ANNOTATION_WEASEL_TOOLS = [
|
|
1308
|
+
...new Set(Object.values(TOOLS).map((t) => t.weaselTool))
|
|
1309
|
+
];
|
|
1310
|
+
|
|
1272
1311
|
// src/annotations/view.ts
|
|
1273
1312
|
function toWeaselView(v) {
|
|
1274
1313
|
return { x: v.pan.x, y: v.pan.y, scale: { x: v.zoom, y: v.zoom } };
|
|
@@ -1280,7 +1319,7 @@ function fitView(content, pane) {
|
|
|
1280
1319
|
const zoom = content.w > 0 && content.h > 0 ? Math.min(pane.w / content.w, pane.h / content.h) : 1;
|
|
1281
1320
|
return { zoom: Number.isFinite(zoom) && zoom > 0 ? zoom : 1, pan: { x: 0, y: 0 } };
|
|
1282
1321
|
}
|
|
1283
|
-
var sameRect = (
|
|
1322
|
+
var sameRect = (a2, b2) => a2 !== null && a2.x === b2.x && a2.y === b2.y && a2.w === b2.w && a2.h === b2.h;
|
|
1284
1323
|
function ToolBridge({ toolId }) {
|
|
1285
1324
|
const tools = useActiveToolContext();
|
|
1286
1325
|
const setActive = tools.setActive;
|
|
@@ -1290,9 +1329,9 @@ function ToolBridge({ toolId }) {
|
|
|
1290
1329
|
return null;
|
|
1291
1330
|
}
|
|
1292
1331
|
function pointsOf(extras, content) {
|
|
1293
|
-
const toFrac = (
|
|
1294
|
-
x: content.w === 0 ? 0 :
|
|
1295
|
-
y: content.h === 0 ? 0 :
|
|
1332
|
+
const toFrac = (p2) => ({
|
|
1333
|
+
x: content.w === 0 ? 0 : p2.x / content.w,
|
|
1334
|
+
y: content.h === 0 ? 0 : p2.y / content.h
|
|
1296
1335
|
});
|
|
1297
1336
|
if (extras.kind === "line") {
|
|
1298
1337
|
return [toFrac(extras.a), toFrac(extras.b)];
|
|
@@ -1334,9 +1373,40 @@ function AnnotationOverlay({
|
|
|
1334
1373
|
},
|
|
1335
1374
|
[surface, tileId]
|
|
1336
1375
|
);
|
|
1376
|
+
const painted = useRef(false);
|
|
1377
|
+
const canvasRef = useRef(canvas);
|
|
1378
|
+
if (canvasRef.current !== canvas) painted.current = false;
|
|
1379
|
+
canvasRef.current = canvas;
|
|
1380
|
+
const unsubscribeFrame = useRef(null);
|
|
1381
|
+
const attachSceneCanvas = useCallback((api) => {
|
|
1382
|
+
sceneCanvas.current = api;
|
|
1383
|
+
unsubscribeFrame.current?.();
|
|
1384
|
+
unsubscribeFrame.current = api ? api.subscribeFrame(() => {
|
|
1385
|
+
painted.current = true;
|
|
1386
|
+
}) : null;
|
|
1387
|
+
}, []);
|
|
1388
|
+
useEffect(() => {
|
|
1389
|
+
if (!surface) return;
|
|
1390
|
+
return surface.registerClear(tileId, (size, dpr) => {
|
|
1391
|
+
if (!painted.current) return;
|
|
1392
|
+
const gl = canvasRef.current?.getContext("webgl2");
|
|
1393
|
+
if (!gl) return;
|
|
1394
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
1395
|
+
gl.viewport(0, 0, Math.round(size.width * dpr), Math.round(size.height * dpr));
|
|
1396
|
+
gl.clearColor(0, 0, 0, 0);
|
|
1397
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
|
|
1398
|
+
});
|
|
1399
|
+
}, [surface, tileId]);
|
|
1400
|
+
const wheelSlot = useContext(CameraWheelContext);
|
|
1401
|
+
useEffect(() => {
|
|
1402
|
+
if (!input || !wheelSlot) return;
|
|
1403
|
+
const wheel = (e) => wheelSlot.current?.(e);
|
|
1404
|
+
input.addEventListener("wheel", wheel, { passive: false });
|
|
1405
|
+
return () => input.removeEventListener("wheel", wheel);
|
|
1406
|
+
}, [input, wheelSlot]);
|
|
1337
1407
|
useEffect(
|
|
1338
1408
|
() => surface?.registerPainter(tileId, (next) => {
|
|
1339
|
-
setRect((prev) => sameRect(prev, next) ? prev : next);
|
|
1409
|
+
flushSync(() => setRect((prev) => sameRect(prev, next) ? prev : next));
|
|
1340
1410
|
sceneCanvas.current?.requestRedraw();
|
|
1341
1411
|
}),
|
|
1342
1412
|
[surface, tileId]
|
|
@@ -1394,7 +1464,7 @@ function AnnotationOverlay({
|
|
|
1394
1464
|
/* @__PURE__ */ jsx(
|
|
1395
1465
|
SceneCanvas,
|
|
1396
1466
|
{
|
|
1397
|
-
ref:
|
|
1467
|
+
ref: attachSceneCanvas,
|
|
1398
1468
|
scene,
|
|
1399
1469
|
width: rect.w,
|
|
1400
1470
|
height: rect.h,
|
|
@@ -1434,314 +1504,6 @@ function AnnotationTargets({
|
|
|
1434
1504
|
)) });
|
|
1435
1505
|
}
|
|
1436
1506
|
|
|
1437
|
-
// src/config/builder.ts
|
|
1438
|
-
function isConfigBranch(entry) {
|
|
1439
|
-
return "children" in entry;
|
|
1440
|
-
}
|
|
1441
|
-
var BaseNode = class {
|
|
1442
|
-
constructor(defaultValue, annotations = {}, options = {}) {
|
|
1443
|
-
this.annotations = annotations;
|
|
1444
|
-
this.options = options;
|
|
1445
|
-
this.default = defaultValue;
|
|
1446
|
-
}
|
|
1447
|
-
annotations;
|
|
1448
|
-
options;
|
|
1449
|
-
// `default` is a reserved word in a parameter position, so the field is
|
|
1450
|
-
// declared rather than taken as a parameter property.
|
|
1451
|
-
default;
|
|
1452
|
-
/** Clone into the same subclass. Subclasses with extra constructor
|
|
1453
|
-
* arguments override this. */
|
|
1454
|
-
with(annotations, options) {
|
|
1455
|
-
const Ctor = this.constructor;
|
|
1456
|
-
return new Ctor(this.default, annotations, options);
|
|
1457
|
-
}
|
|
1458
|
-
ann(patch) {
|
|
1459
|
-
return this.with({ ...this.annotations, ...patch }, this.options);
|
|
1460
|
-
}
|
|
1461
|
-
opt(patch) {
|
|
1462
|
-
return this.with(this.annotations, { ...this.options, ...patch });
|
|
1463
|
-
}
|
|
1464
|
-
/** Human-readable label. Defaults to the key, title-cased. */
|
|
1465
|
-
label(name) {
|
|
1466
|
-
return this.ann({ name });
|
|
1467
|
-
}
|
|
1468
|
-
/** Longer help text, surfaced as a tooltip on the row label. */
|
|
1469
|
-
describe(description) {
|
|
1470
|
-
return this.ann({ description });
|
|
1471
|
-
}
|
|
1472
|
-
/** Omit from the panel unless it is asked to show hidden leaves. */
|
|
1473
|
-
hidden() {
|
|
1474
|
-
return this.ann({ hidden: true });
|
|
1475
|
-
}
|
|
1476
|
-
/** Pair with a sibling sharing this id, side-by-side on one row. */
|
|
1477
|
-
pair(pair) {
|
|
1478
|
-
return this.ann({ pair });
|
|
1479
|
-
}
|
|
1480
|
-
/** Render under a named section heading. `collapsed` opens the section
|
|
1481
|
-
* folded — say it on any one of the section's leaves. */
|
|
1482
|
-
section(label, opts = {}) {
|
|
1483
|
-
return this.opt({ section: { label, ...opts } });
|
|
1484
|
-
}
|
|
1485
|
-
/** Show this row only while the predicate holds. Presentational — the value
|
|
1486
|
-
* stays in config and the instrument still reads it. */
|
|
1487
|
-
showIf(predicate) {
|
|
1488
|
-
return this.opt({ showIf: predicate });
|
|
1489
|
-
}
|
|
1490
|
-
/** Draw this one row yourself, keeping the kind, default and validation. */
|
|
1491
|
-
render(renderer) {
|
|
1492
|
-
return this.opt({ render: renderer });
|
|
1493
|
-
}
|
|
1494
|
-
};
|
|
1495
|
-
var NumberNode = class extends BaseNode {
|
|
1496
|
-
kind = "number";
|
|
1497
|
-
/** Bound the value. A number with both bounds renders as a slider. */
|
|
1498
|
-
range(min, max) {
|
|
1499
|
-
return this.ann({ min, max });
|
|
1500
|
-
}
|
|
1501
|
-
step(step) {
|
|
1502
|
-
return this.ann({ step });
|
|
1503
|
-
}
|
|
1504
|
-
/** A display suffix for the value — `'px'`, `'ms'`, `'°'`. Presentation only:
|
|
1505
|
-
* it is never parsed, and the stored value stays a plain number. */
|
|
1506
|
-
suffix(suffix) {
|
|
1507
|
-
return this.ann({ suffix });
|
|
1508
|
-
}
|
|
1509
|
-
/** Force a slider even without both bounds. */
|
|
1510
|
-
slider() {
|
|
1511
|
-
return this.ann({ control: "slider" });
|
|
1512
|
-
}
|
|
1513
|
-
/** Force a typed input even when both bounds are set. */
|
|
1514
|
-
input() {
|
|
1515
|
-
return this.ann({ control: "input" });
|
|
1516
|
-
}
|
|
1517
|
-
};
|
|
1518
|
-
var BooleanNode = class extends BaseNode {
|
|
1519
|
-
kind = "boolean";
|
|
1520
|
-
/** Ask for a switch. `ControlPanel` still draws a checkbox; weasel-ui's
|
|
1521
|
-
* `PrefsForm` honors the distinction. */
|
|
1522
|
-
toggle() {
|
|
1523
|
-
return this.ann({ control: "switch" });
|
|
1524
|
-
}
|
|
1525
|
-
};
|
|
1526
|
-
var StringNode = class extends BaseNode {
|
|
1527
|
-
kind = "string";
|
|
1528
|
-
placeholder(placeholder) {
|
|
1529
|
-
return this.ann({ placeholder });
|
|
1530
|
-
}
|
|
1531
|
-
maxLength(maxLength) {
|
|
1532
|
-
return this.ann({ maxLength });
|
|
1533
|
-
}
|
|
1534
|
-
/** Milliseconds to debounce live writes. 0 commits every keystroke. */
|
|
1535
|
-
debounce(debounceMs) {
|
|
1536
|
-
return this.ann({ debounceMs });
|
|
1537
|
-
}
|
|
1538
|
-
};
|
|
1539
|
-
var ColorNode = class extends BaseNode {
|
|
1540
|
-
kind = "color";
|
|
1541
|
-
};
|
|
1542
|
-
var EnumNode = class extends BaseNode {
|
|
1543
|
-
kind = "enum";
|
|
1544
|
-
/** Render as a segmented control rather than a select. */
|
|
1545
|
-
radio() {
|
|
1546
|
-
return this.ann({ control: "radio" });
|
|
1547
|
-
}
|
|
1548
|
-
/** Relabel the choices. Named `labels` rather than `options` because
|
|
1549
|
-
* `options` is the node's own extras bag. */
|
|
1550
|
-
labels(options) {
|
|
1551
|
-
return this.ann({ options });
|
|
1552
|
-
}
|
|
1553
|
-
};
|
|
1554
|
-
var ValueNode = class extends BaseNode {
|
|
1555
|
-
kind = null;
|
|
1556
|
-
};
|
|
1557
|
-
var CustomNode = class _CustomNode extends BaseNode {
|
|
1558
|
-
constructor(kind, defaultValue, annotations = {}, options = {}) {
|
|
1559
|
-
super(defaultValue, annotations, options);
|
|
1560
|
-
this.kind = kind;
|
|
1561
|
-
}
|
|
1562
|
-
kind;
|
|
1563
|
-
with(annotations, options) {
|
|
1564
|
-
return new _CustomNode(this.kind, this.default, annotations, options);
|
|
1565
|
-
}
|
|
1566
|
-
};
|
|
1567
|
-
var GroupNode = class _GroupNode {
|
|
1568
|
-
constructor(children, annotations = {}, options = {}) {
|
|
1569
|
-
this.children = children;
|
|
1570
|
-
this.annotations = annotations;
|
|
1571
|
-
this.options = options;
|
|
1572
|
-
}
|
|
1573
|
-
children;
|
|
1574
|
-
annotations;
|
|
1575
|
-
options;
|
|
1576
|
-
with(annotations, options) {
|
|
1577
|
-
return new _GroupNode(this.children, annotations, options);
|
|
1578
|
-
}
|
|
1579
|
-
/** Heading for the group's rows. Defaults to the key, title-cased. */
|
|
1580
|
-
label(name) {
|
|
1581
|
-
return this.with({ ...this.annotations, name }, this.options);
|
|
1582
|
-
}
|
|
1583
|
-
/** Longer help text. Carried on the resolved `PrefGroup`, where weasel-ui's
|
|
1584
|
-
* `PrefsForm` draws it under the heading; `ControlPanel` has no place for
|
|
1585
|
-
* it yet and shows the heading alone. */
|
|
1586
|
-
describe(description) {
|
|
1587
|
-
return this.with({ ...this.annotations, description }, this.options);
|
|
1588
|
-
}
|
|
1589
|
-
/** Render this whole group under a named section heading. */
|
|
1590
|
-
section(label, opts = {}) {
|
|
1591
|
-
return this.with(this.annotations, { ...this.options, section: { label, ...opts } });
|
|
1592
|
-
}
|
|
1593
|
-
/** Show this group only while the predicate holds. Presentational — every
|
|
1594
|
-
* value beneath it stays in config and the instrument still reads it. */
|
|
1595
|
-
showIf(predicate) {
|
|
1596
|
-
return this.with(this.annotations, { ...this.options, showIf: predicate });
|
|
1597
|
-
}
|
|
1598
|
-
};
|
|
1599
|
-
function shapeDefaults(shape) {
|
|
1600
|
-
const out = {};
|
|
1601
|
-
for (const [key, entry] of Object.entries(shape)) {
|
|
1602
|
-
out[key] = isConfigBranch(entry) ? shapeDefaults(entry.children) : entry.default;
|
|
1603
|
-
}
|
|
1604
|
-
return out;
|
|
1605
|
-
}
|
|
1606
|
-
var expand = (options) => options.map((o) => typeof o === "string" ? { value: o, label: o } : o);
|
|
1607
|
-
var f = {
|
|
1608
|
-
number: (def) => new NumberNode(def),
|
|
1609
|
-
boolean: (def) => new BooleanNode(def),
|
|
1610
|
-
string: (def) => new StringNode(def),
|
|
1611
|
-
color: (def) => new ColorNode(def),
|
|
1612
|
-
/** A fixed set of choices. The default's literal type flows into the config
|
|
1613
|
-
* type, so `f.enum('fast', ['fast', 'accurate'])` gives
|
|
1614
|
-
* `'fast' | 'accurate'` with no `as const`. */
|
|
1615
|
-
enum: (def, options) => new EnumNode(def, { options: expand(options) }),
|
|
1616
|
-
/** A leaf with no declared kind: the rule chain decides, which is how a lab
|
|
1617
|
-
* states a convention like "every `*Color` key is a color picker". */
|
|
1618
|
-
value: (def) => new ValueNode(def),
|
|
1619
|
-
/** A branch: its children nest under this key, so `grid: f.group({ size })`
|
|
1620
|
-
* puts the value at `config.grid.size` and addresses it as `'grid.size'`.
|
|
1621
|
-
* Groups nest as deeply as the schema wants. */
|
|
1622
|
-
group: (children) => new GroupNode(children),
|
|
1623
|
-
/** A leaf of a kind a lab supplies the control for, through `controls`. */
|
|
1624
|
-
custom: (kind, def, validate) => new CustomNode(kind, def, {}, validate ? { validate } : {}),
|
|
1625
|
-
/** Collect leaves and groups into an instrument's config. */
|
|
1626
|
-
schema(nodes) {
|
|
1627
|
-
return { nodes, defaults: () => shapeDefaults(nodes) };
|
|
1628
|
-
}
|
|
1629
|
-
};
|
|
1630
|
-
|
|
1631
|
-
// src/config/rules.ts
|
|
1632
|
-
function titleCase(key) {
|
|
1633
|
-
const words = key.replace(/[_-]+/g, " ").split(/(?<=[a-z0-9])(?=[A-Z])/).join(" ").trim().toLowerCase();
|
|
1634
|
-
return words.length === 0 ? words : words[0].toUpperCase() + words.slice(1);
|
|
1635
|
-
}
|
|
1636
|
-
var kindFromValue = (ctx) => {
|
|
1637
|
-
const t = typeof ctx.default;
|
|
1638
|
-
if (t === "boolean" || t === "number" || t === "string") return { kind: t };
|
|
1639
|
-
return null;
|
|
1640
|
-
};
|
|
1641
|
-
var labelFromKey = (ctx) => ({ name: titleCase(ctx.key) });
|
|
1642
|
-
var sliderWhenBounded = (ctx) => {
|
|
1643
|
-
if (ctx.leaf.kind !== "number") return null;
|
|
1644
|
-
const bounded = ctx.leaf.min !== void 0 && ctx.leaf.max !== void 0;
|
|
1645
|
-
return bounded ? { control: "slider" } : null;
|
|
1646
|
-
};
|
|
1647
|
-
var descriptionDefault = () => ({ description: "" });
|
|
1648
|
-
var builtinRules = [
|
|
1649
|
-
kindFromValue,
|
|
1650
|
-
labelFromKey,
|
|
1651
|
-
sliderWhenBounded,
|
|
1652
|
-
descriptionDefault
|
|
1653
|
-
];
|
|
1654
|
-
function applyRules(seed, ctx, rules) {
|
|
1655
|
-
const acc = { ...seed };
|
|
1656
|
-
for (const rule of rules) {
|
|
1657
|
-
const patch = rule({ ...ctx, leaf: acc });
|
|
1658
|
-
if (patch === null) continue;
|
|
1659
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
1660
|
-
if (v !== void 0 && acc[k] === void 0) {
|
|
1661
|
-
acc[k] = v;
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
return acc;
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
// src/config/resolve.ts
|
|
1669
|
-
function defined(annotations) {
|
|
1670
|
-
const out = {};
|
|
1671
|
-
for (const [k, v] of Object.entries(annotations)) if (v !== void 0) out[k] = v;
|
|
1672
|
-
return out;
|
|
1673
|
-
}
|
|
1674
|
-
function resolveConfigSchema(schema, rules = []) {
|
|
1675
|
-
const sink = {
|
|
1676
|
-
sections: [],
|
|
1677
|
-
showIf: /* @__PURE__ */ new Map(),
|
|
1678
|
-
renderers: {},
|
|
1679
|
-
chain: [...rules, ...builtinRules]
|
|
1680
|
-
};
|
|
1681
|
-
const group = resolveShape(schema.nodes, "", "", sink);
|
|
1682
|
-
return { group, sections: sink.sections, showIf: sink.showIf, renderers: sink.renderers };
|
|
1683
|
-
}
|
|
1684
|
-
function resolveShape(shape, at, name, sink) {
|
|
1685
|
-
const children = {};
|
|
1686
|
-
const sections = /* @__PURE__ */ new Map();
|
|
1687
|
-
for (const [key, entry] of Object.entries(shape)) {
|
|
1688
|
-
const path = at === "" ? key : `${at}.${key}`;
|
|
1689
|
-
children[key] = resolveEntry(entry, key, path, sink);
|
|
1690
|
-
const { section, showIf: predicate } = entry.options;
|
|
1691
|
-
if (section !== void 0) {
|
|
1692
|
-
let spec = sections.get(section.label);
|
|
1693
|
-
if (!spec) {
|
|
1694
|
-
spec = { at, label: section.label, paths: [] };
|
|
1695
|
-
sections.set(section.label, spec);
|
|
1696
|
-
sink.sections.push(spec);
|
|
1697
|
-
}
|
|
1698
|
-
spec.paths.push(path);
|
|
1699
|
-
if (section.collapsed !== void 0) {
|
|
1700
|
-
spec.collapsed = (spec.collapsed ?? false) || section.collapsed;
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
if (predicate) sink.showIf.set(path, predicate);
|
|
1704
|
-
if (!isConfigBranch(entry) && entry.options.render) sink.renderers[path] = entry.options.render;
|
|
1705
|
-
}
|
|
1706
|
-
return { name, children };
|
|
1707
|
-
}
|
|
1708
|
-
function resolveEntry(entry, key, path, sink) {
|
|
1709
|
-
if (isConfigBranch(entry)) {
|
|
1710
|
-
const group = resolveShape(
|
|
1711
|
-
entry.children,
|
|
1712
|
-
path,
|
|
1713
|
-
entry.annotations.name ?? titleCase(key),
|
|
1714
|
-
sink
|
|
1715
|
-
);
|
|
1716
|
-
return entry.annotations.description === void 0 ? group : { ...group, description: entry.annotations.description };
|
|
1717
|
-
}
|
|
1718
|
-
const seed = defined({
|
|
1719
|
-
...entry.annotations,
|
|
1720
|
-
...entry.kind === null ? {} : { kind: entry.kind }
|
|
1721
|
-
});
|
|
1722
|
-
const patch = applyRules(seed, { key, path, default: entry.default }, sink.chain);
|
|
1723
|
-
return { ...patch, default: entry.default };
|
|
1724
|
-
}
|
|
1725
|
-
var LabContext = createContext(null);
|
|
1726
|
-
function useLabContext() {
|
|
1727
|
-
const ctx = useContext(LabContext);
|
|
1728
|
-
if (ctx === null) {
|
|
1729
|
-
throw new Error("useLabContext must be used inside <Lab>");
|
|
1730
|
-
}
|
|
1731
|
-
return ctx;
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
// src/config/useConfigSchema.ts
|
|
1735
|
-
var NO_RULES = [];
|
|
1736
|
-
function useConfigSchema(instrument) {
|
|
1737
|
-
const lab = useContext(LabContext);
|
|
1738
|
-
const rules = lab?.configRules ?? NO_RULES;
|
|
1739
|
-
return useMemo(
|
|
1740
|
-
() => instrument.config ? resolveConfigSchema(instrument.config, rules) : fromConfigFields(instrument.configSchema?.() ?? []),
|
|
1741
|
-
[instrument, rules]
|
|
1742
|
-
);
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
1507
|
// src/instrument/defineInstrument.ts
|
|
1746
1508
|
function defineInstrument(spec) {
|
|
1747
1509
|
if (spec.config && !("defaultConfig" in spec && spec.defaultConfig)) {
|
|
@@ -1800,7 +1562,7 @@ function validateConfigSchema(fields) {
|
|
|
1800
1562
|
return { valid: errors.length === 0, errors };
|
|
1801
1563
|
}
|
|
1802
1564
|
|
|
1803
|
-
// ../theme/dist/chunk-
|
|
1565
|
+
// ../theme/dist/chunk-VKZ4JIVX.js
|
|
1804
1566
|
var THEME_SOURCES = {
|
|
1805
1567
|
"weasel": {
|
|
1806
1568
|
"primitives": {
|
|
@@ -1937,46 +1699,46 @@ var THEME_SOURCES = {
|
|
|
1937
1699
|
"alpha": 0.18,
|
|
1938
1700
|
"description": "Pressed wash over a raised surface."
|
|
1939
1701
|
},
|
|
1940
|
-
"swatch-
|
|
1702
|
+
"swatch-fuchsia": {
|
|
1941
1703
|
"type": "color",
|
|
1942
|
-
"value": "#
|
|
1943
|
-
"description": "First of ten categorical spot colors for plotting, status and data viz.
|
|
1704
|
+
"value": "#f641f7",
|
|
1705
|
+
"description": "First of ten categorical spot colors for plotting, status and data viz. Take them in sequence: the order is farthest-point over hue, so the first N are as separated as N colors can be (first three 87\xB0 apart, all ten 32\xB0). Mode-invariant on purpose \u2014 a series keeps its color when the mode flips, which is why they are tuned for a dark surface and clear only 1.5:1 on a light one."
|
|
1944
1706
|
},
|
|
1945
|
-
"swatch-
|
|
1707
|
+
"swatch-green": {
|
|
1946
1708
|
"type": "color",
|
|
1947
|
-
"value": "#
|
|
1709
|
+
"value": "#48e628"
|
|
1948
1710
|
},
|
|
1949
|
-
"swatch-
|
|
1711
|
+
"swatch-sky": {
|
|
1950
1712
|
"type": "color",
|
|
1951
|
-
"value": "#
|
|
1713
|
+
"value": "#36bff2"
|
|
1952
1714
|
},
|
|
1953
|
-
"swatch-
|
|
1715
|
+
"swatch-amber": {
|
|
1954
1716
|
"type": "color",
|
|
1955
|
-
"value": "#
|
|
1717
|
+
"value": "#e69628"
|
|
1956
1718
|
},
|
|
1957
|
-
"swatch-
|
|
1719
|
+
"swatch-teal": {
|
|
1958
1720
|
"type": "color",
|
|
1959
|
-
"value": "#
|
|
1721
|
+
"value": "#3ee1cb"
|
|
1960
1722
|
},
|
|
1961
|
-
"swatch-
|
|
1723
|
+
"swatch-red": {
|
|
1962
1724
|
"type": "color",
|
|
1963
|
-
"value": "#
|
|
1725
|
+
"value": "#f75034"
|
|
1964
1726
|
},
|
|
1965
|
-
"swatch-
|
|
1727
|
+
"swatch-blue": {
|
|
1966
1728
|
"type": "color",
|
|
1967
|
-
"value": "#
|
|
1729
|
+
"value": "#407ef7"
|
|
1968
1730
|
},
|
|
1969
|
-
"swatch-
|
|
1731
|
+
"swatch-citron": {
|
|
1970
1732
|
"type": "color",
|
|
1971
|
-
"value": "#
|
|
1733
|
+
"value": "#d5d23a"
|
|
1972
1734
|
},
|
|
1973
|
-
"swatch-
|
|
1735
|
+
"swatch-rose": {
|
|
1974
1736
|
"type": "color",
|
|
1975
|
-
"value": "#
|
|
1737
|
+
"value": "#fa4590"
|
|
1976
1738
|
},
|
|
1977
|
-
"swatch-
|
|
1739
|
+
"swatch-violet": {
|
|
1978
1740
|
"type": "color",
|
|
1979
|
-
"value": "#
|
|
1741
|
+
"value": "#955cf9"
|
|
1980
1742
|
},
|
|
1981
1743
|
"radius-sm": {
|
|
1982
1744
|
"type": "dimension",
|
|
@@ -2277,7 +2039,8 @@ var THEME_SOURCES = {
|
|
|
2277
2039
|
},
|
|
2278
2040
|
"border-strong": {
|
|
2279
2041
|
"type": "color",
|
|
2280
|
-
"value": "{color.gray-
|
|
2042
|
+
"value": "{color.gray-400}",
|
|
2043
|
+
"description": "The line that has to be seen: a control's edge, a raised surface's outline. Clears WCAG 1.4.11's 3:1 against every surface in both modes."
|
|
2281
2044
|
},
|
|
2282
2045
|
"accent-fg": {
|
|
2283
2046
|
"type": "color",
|
|
@@ -2293,11 +2056,6 @@ var THEME_SOURCES = {
|
|
|
2293
2056
|
"type": "color",
|
|
2294
2057
|
"value": "rgba(0, 0, 0, 0.6)",
|
|
2295
2058
|
"description": "Elevation. Always darker than any surface \u2014 never derived from fg, which is near-white on dark."
|
|
2296
|
-
},
|
|
2297
|
-
"border-raised": {
|
|
2298
|
-
"type": "color",
|
|
2299
|
-
"value": "{color.gray-400}",
|
|
2300
|
-
"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."
|
|
2301
2059
|
}
|
|
2302
2060
|
},
|
|
2303
2061
|
"light": {
|
|
@@ -2331,7 +2089,7 @@ var THEME_SOURCES = {
|
|
|
2331
2089
|
},
|
|
2332
2090
|
"border-strong": {
|
|
2333
2091
|
"type": "color",
|
|
2334
|
-
"value": "{color.gray-
|
|
2092
|
+
"value": "{color.gray-500}"
|
|
2335
2093
|
},
|
|
2336
2094
|
"accent-fg": {
|
|
2337
2095
|
"type": "color",
|
|
@@ -2346,10 +2104,6 @@ var THEME_SOURCES = {
|
|
|
2346
2104
|
"shadow": {
|
|
2347
2105
|
"type": "color",
|
|
2348
2106
|
"value": "rgba(0, 0, 0, 0.18)"
|
|
2349
|
-
},
|
|
2350
|
-
"border-raised": {
|
|
2351
|
-
"type": "color",
|
|
2352
|
-
"value": "{color.gray-400}"
|
|
2353
2107
|
}
|
|
2354
2108
|
}
|
|
2355
2109
|
}
|
|
@@ -2366,11 +2120,11 @@ function hexToRgba(hex, alpha) {
|
|
|
2366
2120
|
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
2367
2121
|
if (!m) throw new Error(`Expected a hex color, got "${hex}"`);
|
|
2368
2122
|
const body = m[1];
|
|
2369
|
-
const full = body.length === 3 ? body.replace(/./g, (
|
|
2370
|
-
const
|
|
2371
|
-
const
|
|
2372
|
-
const
|
|
2373
|
-
return `rgba(${
|
|
2123
|
+
const full = body.length === 3 ? body.replace(/./g, (c3) => c3 + c3) : body;
|
|
2124
|
+
const r3 = Number.parseInt(full.slice(0, 2), 16);
|
|
2125
|
+
const g = Number.parseInt(full.slice(2, 4), 16);
|
|
2126
|
+
const b2 = Number.parseInt(full.slice(4, 6), 16);
|
|
2127
|
+
return `rgba(${r3}, ${g}, ${b2}, ${alpha})`;
|
|
2374
2128
|
}
|
|
2375
2129
|
var REF = /^\{([^}]+)\}$/;
|
|
2376
2130
|
function refTarget(value) {
|
|
@@ -2382,7 +2136,7 @@ function refTarget(value) {
|
|
|
2382
2136
|
return dot === -1 ? path : path.slice(dot + 1);
|
|
2383
2137
|
}
|
|
2384
2138
|
function fontStack(value) {
|
|
2385
|
-
return value.map((
|
|
2139
|
+
return value.map((f3) => typeof f3 === "string" && /\s/.test(f3) ? `'${f3}'` : String(f3)).join(", ");
|
|
2386
2140
|
}
|
|
2387
2141
|
function serialize(type, value) {
|
|
2388
2142
|
if (Array.isArray(value)) {
|
|
@@ -2507,25 +2261,7 @@ function ThemeProvider({
|
|
|
2507
2261
|
function useThemeOptional() {
|
|
2508
2262
|
return useContext(ThemeContext);
|
|
2509
2263
|
}
|
|
2510
|
-
|
|
2511
|
-
// src/instrument/serializers.ts
|
|
2512
|
-
function configDefaultsOf(instruments) {
|
|
2513
|
-
const out = {};
|
|
2514
|
-
for (const instrument of instruments) out[instrument.name] = () => instrument.defaultConfig();
|
|
2515
|
-
return out;
|
|
2516
|
-
}
|
|
2517
|
-
function serializersOf(instruments) {
|
|
2518
|
-
const out = {};
|
|
2519
|
-
for (const instrument of instruments) {
|
|
2520
|
-
const { name, serialize: serialize2, deserialize } = instrument;
|
|
2521
|
-
if (!serialize2 && !deserialize) continue;
|
|
2522
|
-
out[name] = {
|
|
2523
|
-
...serialize2 ? { serialize: (state) => serialize2(state) } : {},
|
|
2524
|
-
...deserialize ? { deserialize: (data, config) => deserialize(data, config) } : {}
|
|
2525
|
-
};
|
|
2526
|
-
}
|
|
2527
|
-
return out;
|
|
2528
|
-
}
|
|
2264
|
+
var AnnotationPreloadContext = createContext(null);
|
|
2529
2265
|
|
|
2530
2266
|
// ../theme/dist/index.js
|
|
2531
2267
|
var ALPHA_EXT = "com.weasel.alpha";
|
|
@@ -2535,9 +2271,9 @@ function flattenTokens(doc) {
|
|
|
2535
2271
|
for (const [groupName, group] of Object.entries(doc)) {
|
|
2536
2272
|
if (groupName.startsWith("$")) continue;
|
|
2537
2273
|
if (typeof group !== "object" || group === null) continue;
|
|
2538
|
-
const
|
|
2539
|
-
const groupType =
|
|
2540
|
-
for (const [leaf, node] of Object.entries(
|
|
2274
|
+
const g = group;
|
|
2275
|
+
const groupType = g.$type;
|
|
2276
|
+
for (const [leaf, node] of Object.entries(g)) {
|
|
2541
2277
|
if (leaf.startsWith("$")) continue;
|
|
2542
2278
|
if (!isToken(node)) continue;
|
|
2543
2279
|
const type = node.$type ?? groupType;
|
|
@@ -2610,7 +2346,7 @@ var interstellar_tokens_default = {
|
|
|
2610
2346
|
"accent-hover": { $value: "#c6a3ee" },
|
|
2611
2347
|
"focus-ring": { $value: "rgba(176, 138, 219, 0.45)" },
|
|
2612
2348
|
shadow: { $value: "rgba(0, 0, 0, 0.7)" },
|
|
2613
|
-
"border-
|
|
2349
|
+
"border-strong": { $value: "rgba(255, 255, 255, 0.36)" }
|
|
2614
2350
|
},
|
|
2615
2351
|
gradient: {
|
|
2616
2352
|
$type: "gradient",
|
|
@@ -2634,7 +2370,7 @@ var interstellar_tokens_default = {
|
|
|
2634
2370
|
"accent-hover": { $value: "#c2854f" },
|
|
2635
2371
|
"focus-ring": { $value: "rgba(168, 111, 60, 0.35)" },
|
|
2636
2372
|
shadow: { $value: "rgba(80, 60, 40, 0.18)" },
|
|
2637
|
-
"border-
|
|
2373
|
+
"border-strong": { $value: "#9e8b71" }
|
|
2638
2374
|
}
|
|
2639
2375
|
}
|
|
2640
2376
|
}
|
|
@@ -2642,114 +2378,18 @@ var interstellar_tokens_default = {
|
|
|
2642
2378
|
|
|
2643
2379
|
// src/theme/interstellar.ts
|
|
2644
2380
|
var interstellarTheme = loadDTCG(interstellar_tokens_default);
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
function as2DView(view) {
|
|
2649
|
-
if (typeof view !== "object" || view === null) return null;
|
|
2650
|
-
const v = view;
|
|
2651
|
-
if (typeof v.zoom !== "number") return null;
|
|
2652
|
-
if (typeof v.pan !== "object" || v.pan === null) return null;
|
|
2653
|
-
if (typeof v.pan.x !== "number" || typeof v.pan.y !== "number") return null;
|
|
2654
|
-
return { zoom: v.zoom, pan: { x: v.pan.x, y: v.pan.y } };
|
|
2655
|
-
}
|
|
2656
|
-
var ZONE_ID = asNodeId("lk-trial-body");
|
|
2657
|
-
var SIDEBAR_ID = asNodeId("lk-trial-sidebar");
|
|
2658
|
-
var CONTENT_ID = asNodeId("lk-trial-content");
|
|
2659
|
-
var STRATEGIES = { strip: stripStrategy };
|
|
2660
|
-
function TrialBody({
|
|
2661
|
-
sidebar,
|
|
2662
|
-
children,
|
|
2663
|
-
contentClassName,
|
|
2664
|
-
width,
|
|
2665
|
-
defaultWidth = 320,
|
|
2666
|
-
minWidth = 140,
|
|
2667
|
-
maxWidth = 720,
|
|
2668
|
-
contentMinWidth = 160,
|
|
2669
|
-
onWidthChange,
|
|
2670
|
-
viewport
|
|
2671
|
-
}) {
|
|
2672
|
-
const storeRef = useRef(null);
|
|
2673
|
-
if (storeRef.current === null) {
|
|
2674
|
-
const store2 = new Store();
|
|
2675
|
-
store2.registerNode(
|
|
2676
|
-
createNode({
|
|
2677
|
-
kind: "zone",
|
|
2678
|
-
id: ZONE_ID,
|
|
2679
|
-
container: { strategyId: "strip", config: { axis: "x", resizable: true } }
|
|
2680
|
-
})
|
|
2681
|
-
);
|
|
2682
|
-
store2.registerNode(
|
|
2683
|
-
createNode({
|
|
2684
|
-
kind: "sidebar",
|
|
2685
|
-
id: SIDEBAR_ID,
|
|
2686
|
-
parentId: ZONE_ID,
|
|
2687
|
-
meta: { title: "Sidebar" },
|
|
2688
|
-
placement: { size: { w: width ?? defaultWidth } },
|
|
2689
|
-
hints: { minSize: { w: minWidth, h: 0 }, maxSize: { w: maxWidth, h: 0 } }
|
|
2690
|
-
})
|
|
2691
|
-
);
|
|
2692
|
-
store2.registerNode(
|
|
2693
|
-
createNode({
|
|
2694
|
-
kind: "content",
|
|
2695
|
-
id: CONTENT_ID,
|
|
2696
|
-
parentId: ZONE_ID,
|
|
2697
|
-
meta: { title: "Content" },
|
|
2698
|
-
hints: { minSize: { w: contentMinWidth, h: 0 } }
|
|
2699
|
-
})
|
|
2700
|
-
);
|
|
2701
|
-
store2.showNode(SIDEBAR_ID);
|
|
2702
|
-
store2.showNode(CONTENT_ID);
|
|
2703
|
-
storeRef.current = store2;
|
|
2704
|
-
}
|
|
2705
|
-
const store = storeRef.current;
|
|
2706
|
-
const widthRef = useRef(width ?? defaultWidth);
|
|
2707
|
-
useEffect(() => {
|
|
2708
|
-
store.setHints(SIDEBAR_ID, {
|
|
2709
|
-
minSize: { w: minWidth, h: 0 },
|
|
2710
|
-
maxSize: { w: maxWidth, h: 0 }
|
|
2711
|
-
});
|
|
2712
|
-
store.setHints(CONTENT_ID, { minSize: { w: contentMinWidth, h: 0 } });
|
|
2713
|
-
}, [store, minWidth, maxWidth, contentMinWidth]);
|
|
2714
|
-
useEffect(() => {
|
|
2715
|
-
if (width === void 0 || width === widthRef.current) return;
|
|
2716
|
-
widthRef.current = width;
|
|
2717
|
-
store.patchPlacement(SIDEBAR_ID, { size: { w: width } });
|
|
2718
|
-
}, [store, width]);
|
|
2719
|
-
const onWidthChangeRef = useRef(onWidthChange);
|
|
2720
|
-
onWidthChangeRef.current = onWidthChange;
|
|
2721
|
-
useEffect(
|
|
2722
|
-
() => store.events.on("node.placementChanged", () => {
|
|
2723
|
-
const next = store.getPlacement(SIDEBAR_ID)?.size?.w;
|
|
2724
|
-
if (typeof next !== "number" || next === widthRef.current) return;
|
|
2725
|
-
widthRef.current = next;
|
|
2726
|
-
onWidthChangeRef.current?.(next);
|
|
2727
|
-
}),
|
|
2728
|
-
[store]
|
|
2729
|
-
);
|
|
2730
|
-
const chrome = useMemo(
|
|
2731
|
-
() => ({
|
|
2732
|
-
sidebar: () => /* @__PURE__ */ jsx("div", { className: "lk-trial__sidebar", children: sidebar }),
|
|
2733
|
-
content: () => /* @__PURE__ */ jsx(
|
|
2734
|
-
"div",
|
|
2735
|
-
{
|
|
2736
|
-
className: contentClassName ? `lk-trial__content ${contentClassName}` : "lk-trial__content",
|
|
2737
|
-
children
|
|
2738
|
-
}
|
|
2739
|
-
)
|
|
2740
|
-
}),
|
|
2741
|
-
[sidebar, children, contentClassName]
|
|
2742
|
-
);
|
|
2743
|
-
return /* @__PURE__ */ jsx(Provider, { store, children: /* @__PURE__ */ jsx(StrategyRegistryProvider, { strategies: STRATEGIES, children: /* @__PURE__ */ jsx(
|
|
2744
|
-
Container,
|
|
2381
|
+
function TrialBody({ sidebar, children, contentClassName, ...rest }) {
|
|
2382
|
+
return /* @__PURE__ */ jsx(
|
|
2383
|
+
Split,
|
|
2745
2384
|
{
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
className: "lk-trial__panes
|
|
2749
|
-
|
|
2750
|
-
|
|
2385
|
+
...rest,
|
|
2386
|
+
sidebar,
|
|
2387
|
+
className: "lk-trial__panes",
|
|
2388
|
+
sidebarClassName: "lk-trial__sidebar",
|
|
2389
|
+
contentClassName: contentClassName ? `lk-trial__content ${contentClassName}` : "lk-trial__content",
|
|
2390
|
+
children
|
|
2751
2391
|
}
|
|
2752
|
-
)
|
|
2392
|
+
);
|
|
2753
2393
|
}
|
|
2754
2394
|
var TrialDragContext = createContext(null);
|
|
2755
2395
|
function useTrialDrag() {
|
|
@@ -2834,7 +2474,7 @@ function Portalled({
|
|
|
2834
2474
|
}
|
|
2835
2475
|
function UndockedSections({ trialId, sections, onDock }) {
|
|
2836
2476
|
if (sections.length === 0) return null;
|
|
2837
|
-
return /* @__PURE__ */ jsx(Fragment, { children: sections.map((
|
|
2477
|
+
return /* @__PURE__ */ jsx(Fragment, { children: sections.map((c3) => /* @__PURE__ */ jsx(Portalled, { trialId, contribution: c3, onDock }, c3.id)) });
|
|
2838
2478
|
}
|
|
2839
2479
|
var NO_OP = () => {
|
|
2840
2480
|
};
|
|
@@ -2867,17 +2507,17 @@ function TrialChrome({
|
|
|
2867
2507
|
const dockPanelAction = useStore(storeCtx.store, (s) => s.dockPanel);
|
|
2868
2508
|
const undockedPanels = useStore(storeCtx.store, (s) => s.undockedPanels);
|
|
2869
2509
|
const undockedIds = useMemo(
|
|
2870
|
-
() => Object.values(undockedPanels).filter((
|
|
2510
|
+
() => Object.values(undockedPanels).filter((p2) => p2.trialId === trialId).map((p2) => p2.sectionId),
|
|
2871
2511
|
[undockedPanels, trialId]
|
|
2872
2512
|
);
|
|
2873
2513
|
const view2d = as2DView(record.view);
|
|
2874
2514
|
const configSchema = useConfigSchema(instrument);
|
|
2875
|
-
const title = record.title ??
|
|
2515
|
+
const title = record.title ?? instrument.title ?? instrument.name;
|
|
2876
2516
|
const collapsedSections = record.collapsedSections ?? NO_SECTIONS;
|
|
2877
2517
|
const ctx = useMemo(() => {
|
|
2878
2518
|
const setZoom = (z2) => {
|
|
2879
2519
|
if (!view2d) return;
|
|
2880
|
-
updateTrialView(trialId,
|
|
2520
|
+
updateTrialView(trialId, withZoom(view2d, z2));
|
|
2881
2521
|
};
|
|
2882
2522
|
return {
|
|
2883
2523
|
trialId,
|
|
@@ -2959,7 +2599,7 @@ function TrialChrome({
|
|
|
2959
2599
|
),
|
|
2960
2600
|
[instrument, ctx, trialChrome, chrome, suppress, lab.controls]
|
|
2961
2601
|
);
|
|
2962
|
-
const inRegion = (region) => contributions.filter((
|
|
2602
|
+
const inRegion = (region) => contributions.filter((c3) => c3.region === region);
|
|
2963
2603
|
const handleKeyDown = (e) => {
|
|
2964
2604
|
const mod = e.metaKey || e.ctrlKey;
|
|
2965
2605
|
if (!mod) return;
|
|
@@ -2976,6 +2616,7 @@ function TrialChrome({
|
|
|
2976
2616
|
"section",
|
|
2977
2617
|
{
|
|
2978
2618
|
className: "lk-trial",
|
|
2619
|
+
"data-trial-id": trialId,
|
|
2979
2620
|
"aria-label": `Trial ${title}`,
|
|
2980
2621
|
tabIndex: -1,
|
|
2981
2622
|
onKeyDown: handleKeyDown,
|
|
@@ -2995,15 +2636,15 @@ function TrialChrome({
|
|
|
2995
2636
|
TrialBody,
|
|
2996
2637
|
{
|
|
2997
2638
|
width: record.sidebarWidth,
|
|
2998
|
-
onWidthChange: (
|
|
2999
|
-
contentClassName: instrument.canvas ? "lk-trial__content--flush" : void 0,
|
|
2639
|
+
onWidthChange: (w2) => updateTrialSidebarWidth(trialId, w2),
|
|
2640
|
+
contentClassName: instrument.canvas || instrument.stage ? "lk-trial__content--flush" : void 0,
|
|
3000
2641
|
sidebar: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3001
2642
|
/* @__PURE__ */ jsx(SidebarRegion, { contributions: inRegion("sidebar"), ctx }),
|
|
3002
2643
|
/* @__PURE__ */ jsx(
|
|
3003
2644
|
UndockedSections,
|
|
3004
2645
|
{
|
|
3005
2646
|
trialId,
|
|
3006
|
-
sections: inRegion("sidebar").filter((
|
|
2647
|
+
sections: inRegion("sidebar").filter((c3) => undockedIds.includes(c3.id)),
|
|
3007
2648
|
onDock: ctx.dockPanel
|
|
3008
2649
|
}
|
|
3009
2650
|
)
|
|
@@ -3027,25 +2668,30 @@ function Trial({ id, chrome, suppress }) {
|
|
|
3027
2668
|
const lab = useLabContext();
|
|
3028
2669
|
const storeCtx = useContext(LabStoreContext);
|
|
3029
2670
|
if (!storeCtx) throw new Error("[labkit] <Trial> requires <LabStoreProvider>");
|
|
3030
|
-
const
|
|
2671
|
+
const instruments = useStore(storeCtx.store, (s) => s.instruments) ?? lab.instruments;
|
|
2672
|
+
const record = useStore(storeCtx.store, (s) => s.trials.find((w2) => w2.id === id));
|
|
2673
|
+
const instrument = record ? instruments.find((i) => i.name === record.instrumentName) : void 0;
|
|
2674
|
+
const kept = useKeptMarks(instrument?.annotations?.storage, id);
|
|
3031
2675
|
if (!record) {
|
|
3032
2676
|
return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
|
|
3033
2677
|
"Trial not found: ",
|
|
3034
2678
|
id
|
|
3035
2679
|
] });
|
|
3036
2680
|
}
|
|
3037
|
-
const instrument = lab.instruments.find((i) => i.name === record.instrumentName);
|
|
3038
2681
|
if (!instrument) {
|
|
3039
2682
|
return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
|
|
3040
2683
|
"Unknown instrument: ",
|
|
3041
2684
|
record.instrumentName
|
|
3042
2685
|
] });
|
|
3043
2686
|
}
|
|
2687
|
+
if (!kept.ready) return /* @__PURE__ */ jsx("div", { className: "lk-trial lk-trial--loading" });
|
|
3044
2688
|
return /* @__PURE__ */ jsx(
|
|
3045
2689
|
TrialRuntime,
|
|
3046
2690
|
{
|
|
2691
|
+
keptMarks: kept.value,
|
|
3047
2692
|
record,
|
|
3048
2693
|
instrument,
|
|
2694
|
+
instruments,
|
|
3049
2695
|
store: storeCtx.store,
|
|
3050
2696
|
isLast: lab.trials.length <= 1,
|
|
3051
2697
|
chrome,
|
|
@@ -3053,7 +2699,41 @@ function Trial({ id, chrome, suppress }) {
|
|
|
3053
2699
|
}
|
|
3054
2700
|
);
|
|
3055
2701
|
}
|
|
3056
|
-
function
|
|
2702
|
+
function useKeptMarks(storage, trialId) {
|
|
2703
|
+
const preload = useContext(AnnotationPreloadContext);
|
|
2704
|
+
const [loaded, setLoaded] = useState(
|
|
2705
|
+
() => storage && preload?.has(trialId) ? { value: preload.get(trialId) } : null
|
|
2706
|
+
);
|
|
2707
|
+
useEffect(() => {
|
|
2708
|
+
if (!storage || loaded) return;
|
|
2709
|
+
let live = true;
|
|
2710
|
+
storage.load().then(
|
|
2711
|
+
(value) => {
|
|
2712
|
+
if (live) setLoaded({ value });
|
|
2713
|
+
},
|
|
2714
|
+
(error) => {
|
|
2715
|
+
console.warn(`[labkit] could not load the marks of trial "${trialId}"`, error);
|
|
2716
|
+
if (live) setLoaded({ value: null });
|
|
2717
|
+
}
|
|
2718
|
+
);
|
|
2719
|
+
return () => {
|
|
2720
|
+
live = false;
|
|
2721
|
+
};
|
|
2722
|
+
}, [storage, loaded, trialId]);
|
|
2723
|
+
if (!storage) return { ready: true, value: void 0 };
|
|
2724
|
+
return loaded ? { ready: true, value: loaded.value } : { ready: false, value: void 0 };
|
|
2725
|
+
}
|
|
2726
|
+
function TrialRuntime({
|
|
2727
|
+
keptMarks,
|
|
2728
|
+
record,
|
|
2729
|
+
instrument,
|
|
2730
|
+
instruments,
|
|
2731
|
+
store,
|
|
2732
|
+
isLast,
|
|
2733
|
+
chrome,
|
|
2734
|
+
suppress
|
|
2735
|
+
}) {
|
|
2736
|
+
const wheelSlot = useRef(null);
|
|
3057
2737
|
const canvasContainerRef = useRef(null);
|
|
3058
2738
|
const loupeHostRef = useRef(null);
|
|
3059
2739
|
const updateTrialState = useStore(store, (s) => s.updateTrialState);
|
|
@@ -3071,7 +2751,7 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3071
2751
|
const [layerOrder, setLayerOrder] = useState(null);
|
|
3072
2752
|
const [loupeOn, setLoupeOn] = useState(false);
|
|
3073
2753
|
const visibleLayers = useMemo(
|
|
3074
|
-
() => (instrument.canvas?.layers ?? []).filter((
|
|
2754
|
+
() => (instrument.canvas?.layers ?? []).filter((l2) => layerVisibility[l2.id] !== false).map((l2) => l2.id),
|
|
3075
2755
|
[instrument.canvas, layerVisibility]
|
|
3076
2756
|
);
|
|
3077
2757
|
const undoCap = instrument.undo;
|
|
@@ -3079,14 +2759,15 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3079
2759
|
const maxDepth = undoCap?.maxDepth ?? 50;
|
|
3080
2760
|
const snapshotIfNeeded = (event) => {
|
|
3081
2761
|
if (!undoCap || !undoEvents.has(event)) return;
|
|
3082
|
-
const current = store.getState().trials.find((
|
|
2762
|
+
const current = store.getState().trials.find((w2) => w2.id === record.id);
|
|
3083
2763
|
if (!current) return;
|
|
3084
2764
|
const snap = structuredClone(current.state);
|
|
3085
2765
|
updateTrialUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
|
|
3086
2766
|
};
|
|
3087
2767
|
const setView = (v) => updateTrialView(record.id, v);
|
|
2768
|
+
const stage = instrument.canvas ? void 0 : instrument.stage;
|
|
3088
2769
|
const placeView = (size) => {
|
|
3089
|
-
const declared = instrument.canvas?.initialView;
|
|
2770
|
+
const declared = instrument.canvas?.initialView ?? (stage ? stage.initialView ?? ((vp) => fitStage(stage.size, vp)) : void 0);
|
|
3090
2771
|
if (typeof declared !== "function") return;
|
|
3091
2772
|
if (record.view != null) return;
|
|
3092
2773
|
setView(declared(size));
|
|
@@ -3097,12 +2778,14 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3097
2778
|
const annotationsRef = useRef(null);
|
|
3098
2779
|
const configRef = useRef(record.config);
|
|
3099
2780
|
configRef.current = record.config;
|
|
2781
|
+
const capRef = useRef(annotationsCap);
|
|
2782
|
+
capRef.current = annotationsCap;
|
|
3100
2783
|
if (annotationsRef.current === null) {
|
|
3101
|
-
const kept = annotationsCap?.storage ?
|
|
2784
|
+
const kept = annotationsCap?.storage ? keptMarks : record.annotations;
|
|
3102
2785
|
annotationsRef.current = annotationsFromJSON(kept, () => targetsRef.current(), {
|
|
3103
|
-
meaning:
|
|
2786
|
+
meaning: () => capRef.current?.meaning,
|
|
3104
2787
|
config: () => configRef.current,
|
|
3105
|
-
onCapture: (result) =>
|
|
2788
|
+
onCapture: (result) => capRef.current?.onCapture?.(result)
|
|
3106
2789
|
});
|
|
3107
2790
|
}
|
|
3108
2791
|
const annotations = annotationsRef.current;
|
|
@@ -3146,8 +2829,9 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3146
2829
|
flush();
|
|
3147
2830
|
};
|
|
3148
2831
|
}, [annotations, annotationsCap]);
|
|
3149
|
-
const declaresTools = instrument.tools != null
|
|
3150
|
-
const
|
|
2832
|
+
const declaresTools = instrument.tools != null;
|
|
2833
|
+
const labTool = resolveLabTool(labToolId, instruments);
|
|
2834
|
+
const resolvedToolId = declaresTools ? record.activeToolId ?? instrument.tools?.initial ?? instrument.tools?.tools[0]?.id ?? null : labTool;
|
|
3151
2835
|
const setActiveTool = (id) => {
|
|
3152
2836
|
if (declaresTools) setTrialTool(record.id, id);
|
|
3153
2837
|
else setLabTool(id);
|
|
@@ -3184,7 +2868,7 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3184
2868
|
zoom: view2d?.zoom ?? 1,
|
|
3185
2869
|
setZoom: (z2) => {
|
|
3186
2870
|
if (!view2d) return;
|
|
3187
|
-
updateTrialView(record.id,
|
|
2871
|
+
updateTrialView(record.id, withZoom(view2d, z2));
|
|
3188
2872
|
},
|
|
3189
2873
|
activeToolId: resolvedToolId,
|
|
3190
2874
|
visibleLayers
|
|
@@ -3228,7 +2912,7 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3228
2912
|
const canvasLayers = useMemo(() => {
|
|
3229
2913
|
if (!instrument.canvas) return [];
|
|
3230
2914
|
const baseLayers = instrument.canvas.layers;
|
|
3231
|
-
const ordered = layerOrder ? [...baseLayers].sort((
|
|
2915
|
+
const ordered = layerOrder ? [...baseLayers].sort((a2, b2) => layerOrder.indexOf(a2.id) - layerOrder.indexOf(b2.id)) : baseLayers;
|
|
3232
2916
|
return ordered.map((layer) => ({
|
|
3233
2917
|
id: layer.id,
|
|
3234
2918
|
visible: layerVisibility[layer.id] !== false,
|
|
@@ -3240,7 +2924,7 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3240
2924
|
}, [instrument.canvas, record.state, record.config, layerVisibility, layerOrder]);
|
|
3241
2925
|
const layerDescriptors = useMemo(() => {
|
|
3242
2926
|
if (!instrument.layers) return [];
|
|
3243
|
-
return instrument.layers.ids.map((
|
|
2927
|
+
return instrument.layers.ids.map((l2) => typeof l2 === "string" ? { id: l2, label: l2 } : l2);
|
|
3244
2928
|
}, [instrument.layers]);
|
|
3245
2929
|
const dragDropResult = useDragDrop({
|
|
3246
2930
|
capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s) => s },
|
|
@@ -3260,8 +2944,8 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3260
2944
|
});
|
|
3261
2945
|
const paletteItems = useMemo(() => {
|
|
3262
2946
|
if (!instrument.dragDrop) return [];
|
|
3263
|
-
const
|
|
3264
|
-
return typeof
|
|
2947
|
+
const p2 = instrument.dragDrop.palette;
|
|
2948
|
+
return typeof p2 === "function" ? p2(record.state, record.config) : p2;
|
|
3265
2949
|
}, [instrument.dragDrop, record.state, record.config]);
|
|
3266
2950
|
const layersWithFeedback = useMemo(() => {
|
|
3267
2951
|
if (!dragDropResult.drag?.feedback) return canvasLayers;
|
|
@@ -3275,12 +2959,12 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3275
2959
|
render: (ctx) => {
|
|
3276
2960
|
const el = canvasContainerRef.current;
|
|
3277
2961
|
if (!el) return;
|
|
3278
|
-
const
|
|
2962
|
+
const r3 = el.getBoundingClientRect();
|
|
3279
2963
|
ctx.save();
|
|
3280
2964
|
ctx.strokeStyle = fb.ok ? "#3a7" : "#c44";
|
|
3281
2965
|
ctx.lineWidth = 2;
|
|
3282
2966
|
ctx.beginPath();
|
|
3283
|
-
ctx.arc(screen.x -
|
|
2967
|
+
ctx.arc(screen.x - r3.left, screen.y - r3.top, 16, 0, Math.PI * 2);
|
|
3284
2968
|
ctx.stroke();
|
|
3285
2969
|
ctx.restore();
|
|
3286
2970
|
}
|
|
@@ -3320,6 +3004,21 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3320
3004
|
),
|
|
3321
3005
|
/* @__PURE__ */ jsx(DragOverlay, { drag: dragDropResult.drag })
|
|
3322
3006
|
] });
|
|
3007
|
+
} else if (stage) {
|
|
3008
|
+
body = /* @__PURE__ */ jsx(
|
|
3009
|
+
Stage,
|
|
3010
|
+
{
|
|
3011
|
+
size: stage.size,
|
|
3012
|
+
view: view2d ?? DEFAULT_VIEW,
|
|
3013
|
+
onViewChange: setView,
|
|
3014
|
+
onResize: placeView,
|
|
3015
|
+
minZoom: stage.minZoom,
|
|
3016
|
+
maxZoom: stage.maxZoom,
|
|
3017
|
+
hostRef: loupeHostRef,
|
|
3018
|
+
overlay: lens,
|
|
3019
|
+
children: instrument.render(renderCtx)
|
|
3020
|
+
}
|
|
3021
|
+
);
|
|
3323
3022
|
} else if (lens) {
|
|
3324
3023
|
body = /* @__PURE__ */ jsxs("div", { ref: loupeHostRef, className: "lk-trial__loupe-host", children: [
|
|
3325
3024
|
instrument.render(renderCtx),
|
|
@@ -3340,7 +3039,7 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3340
3039
|
layers: layerDescriptors,
|
|
3341
3040
|
visibility: layerVisibility,
|
|
3342
3041
|
onReorder: (next) => {
|
|
3343
|
-
setLayerOrder(next.map((
|
|
3042
|
+
setLayerOrder(next.map((l2) => l2.id));
|
|
3344
3043
|
bus.emit("layers.reorder");
|
|
3345
3044
|
},
|
|
3346
3045
|
onToggle: (lid, visible) => {
|
|
@@ -3377,10 +3076,10 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3377
3076
|
config: record.config,
|
|
3378
3077
|
trial: { id: record.id, view: record.view },
|
|
3379
3078
|
annotations,
|
|
3380
|
-
activeToolId:
|
|
3079
|
+
activeToolId: labTool
|
|
3381
3080
|
}
|
|
3382
3081
|
) : null;
|
|
3383
|
-
return /* @__PURE__ */ jsx(TrialIdProvider, { trialId: record.id, children: /* @__PURE__ */ jsx(AnnotationsContext.Provider, { value: annotationsCap ? annotations : null, children: /* @__PURE__ */ jsxs(
|
|
3082
|
+
return /* @__PURE__ */ jsx(TrialIdProvider, { trialId: record.id, children: /* @__PURE__ */ jsx(AnnotationsContext.Provider, { value: annotationsCap ? annotations : null, children: /* @__PURE__ */ jsx(CameraWheelContext.Provider, { value: wheelSlot, children: /* @__PURE__ */ jsxs(
|
|
3384
3083
|
TrialChrome,
|
|
3385
3084
|
{
|
|
3386
3085
|
job: jobCap ? job : void 0,
|
|
@@ -3400,7 +3099,7 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
|
3400
3099
|
annotationOverlays
|
|
3401
3100
|
]
|
|
3402
3101
|
}
|
|
3403
|
-
) }) });
|
|
3102
|
+
) }) }) });
|
|
3404
3103
|
}
|
|
3405
3104
|
|
|
3406
3105
|
// src/trial/trialOps.ts
|
|
@@ -3413,8 +3112,9 @@ function findInstrument(instruments, name) {
|
|
|
3413
3112
|
return found;
|
|
3414
3113
|
}
|
|
3415
3114
|
function initialView(instrument) {
|
|
3416
|
-
const
|
|
3417
|
-
|
|
3115
|
+
const stage = instrument.canvas ? void 0 : instrument.stage;
|
|
3116
|
+
const declared = instrument.canvas?.initialView ?? stage?.initialView;
|
|
3117
|
+
if (typeof declared === "function" || stage && declared === void 0) return null;
|
|
3418
3118
|
return declared ? structuredClone(declared) : { ...DEFAULT_VIEW2, pan: { ...DEFAULT_VIEW2.pan } };
|
|
3419
3119
|
}
|
|
3420
3120
|
function seedConfig(defaults, seed) {
|
|
@@ -3436,7 +3136,7 @@ function addTrial(trials, instruments, instrumentName, options = {}) {
|
|
|
3436
3136
|
return [...trials, record];
|
|
3437
3137
|
}
|
|
3438
3138
|
function cloneTrial(trials, id) {
|
|
3439
|
-
const sourceIdx = trials.findIndex((
|
|
3139
|
+
const sourceIdx = trials.findIndex((w2) => w2.id === id);
|
|
3440
3140
|
const source = trials[sourceIdx];
|
|
3441
3141
|
if (!source) return trials;
|
|
3442
3142
|
const clone = {
|
|
@@ -3451,11 +3151,11 @@ function cloneTrial(trials, id) {
|
|
|
3451
3151
|
}
|
|
3452
3152
|
function closeTrial(trials, id) {
|
|
3453
3153
|
if (trials.length <= 1) return trials;
|
|
3454
|
-
const next = trials.filter((
|
|
3154
|
+
const next = trials.filter((w2) => w2.id !== id);
|
|
3455
3155
|
return next.length === trials.length ? trials : next;
|
|
3456
3156
|
}
|
|
3457
3157
|
function resetTrial(trials, id, instruments) {
|
|
3458
|
-
const idx = trials.findIndex((
|
|
3158
|
+
const idx = trials.findIndex((w2) => w2.id === id);
|
|
3459
3159
|
const current = trials[idx];
|
|
3460
3160
|
if (!current) return trials;
|
|
3461
3161
|
const instrument = findInstrument(instruments, current.instrumentName);
|
|
@@ -3470,12 +3170,92 @@ function resetTrial(trials, id, instruments) {
|
|
|
3470
3170
|
return [...trials.slice(0, idx), reset, ...trials.slice(idx + 1)];
|
|
3471
3171
|
}
|
|
3472
3172
|
function reorderTrials(trials, ids) {
|
|
3473
|
-
const byId = new Map(trials.map((
|
|
3474
|
-
const named = ids.map((id) => byId.get(id)).filter((
|
|
3475
|
-
const seen = new Set(named.map((
|
|
3476
|
-
const rest = trials.filter((
|
|
3173
|
+
const byId = new Map(trials.map((w2) => [w2.id, w2]));
|
|
3174
|
+
const named = ids.map((id) => byId.get(id)).filter((w2) => w2 !== void 0);
|
|
3175
|
+
const seen = new Set(named.map((w2) => w2.id));
|
|
3176
|
+
const rest = trials.filter((w2) => !seen.has(w2.id));
|
|
3477
3177
|
return [...named, ...rest];
|
|
3478
3178
|
}
|
|
3179
|
+
function findOvershoot(bound, items, tolerance = 1) {
|
|
3180
|
+
let worst = null;
|
|
3181
|
+
for (const { item, edges } of items) {
|
|
3182
|
+
const y = edges.bottom - bound.bottom;
|
|
3183
|
+
const x = edges.right - bound.right;
|
|
3184
|
+
const by = Math.max(x, y);
|
|
3185
|
+
if (by <= tolerance) continue;
|
|
3186
|
+
if (!worst || by > worst.by) worst = { item, by, axis: y >= x ? "y" : "x" };
|
|
3187
|
+
}
|
|
3188
|
+
return worst;
|
|
3189
|
+
}
|
|
3190
|
+
function tagAndClasses(el) {
|
|
3191
|
+
return el.tagName.toLowerCase() + [...el.classList].map((c3) => `.${c3}`).join("");
|
|
3192
|
+
}
|
|
3193
|
+
function describeElement(el) {
|
|
3194
|
+
if (el.classList.length > 0) return tagAndClasses(el);
|
|
3195
|
+
let up = el.parentElement;
|
|
3196
|
+
while (up && up.classList.length === 0) up = up.parentElement;
|
|
3197
|
+
return up ? `${tagAndClasses(el)} in ${tagAndClasses(up)}` : tagAndClasses(el);
|
|
3198
|
+
}
|
|
3199
|
+
function edgesOf(el) {
|
|
3200
|
+
const r3 = el.getBoundingClientRect();
|
|
3201
|
+
return { top: r3.top, left: r3.left, bottom: r3.bottom, right: r3.right };
|
|
3202
|
+
}
|
|
3203
|
+
function* candidates(root) {
|
|
3204
|
+
yield { item: root, edges: edgesOf(root) };
|
|
3205
|
+
for (const el of root.querySelectorAll("*")) yield { item: el, edges: edgesOf(el) };
|
|
3206
|
+
}
|
|
3207
|
+
function overflows(el) {
|
|
3208
|
+
return el.scrollHeight > el.clientHeight + 1 || el.scrollWidth > el.clientWidth + 1;
|
|
3209
|
+
}
|
|
3210
|
+
function explainLabScroll(lab, shellBody) {
|
|
3211
|
+
if (overflows(shellBody)) {
|
|
3212
|
+
const r4 = shellBody.getBoundingClientRect();
|
|
3213
|
+
const bound2 = {
|
|
3214
|
+
top: r4.top,
|
|
3215
|
+
left: r4.left,
|
|
3216
|
+
bottom: r4.top + shellBody.clientHeight,
|
|
3217
|
+
right: r4.left + shellBody.clientWidth
|
|
3218
|
+
};
|
|
3219
|
+
const hit2 = findOvershoot(bound2, candidates(shellBody));
|
|
3220
|
+
return hit2 ? `.lk-shell-body scrolls: ${describeElement(hit2.item)} reaches ${Math.round(hit2.by)}px past it (${hit2.axis})` : ".lk-shell-body scrolls";
|
|
3221
|
+
}
|
|
3222
|
+
const doc = document.scrollingElement ?? document.documentElement;
|
|
3223
|
+
if (!overflows(doc)) return null;
|
|
3224
|
+
const r3 = lab.getBoundingClientRect();
|
|
3225
|
+
const bound = {
|
|
3226
|
+
top: r3.top,
|
|
3227
|
+
left: r3.left,
|
|
3228
|
+
bottom: r3.top + Math.min(r3.height, doc.clientHeight),
|
|
3229
|
+
right: r3.left + Math.min(r3.width, doc.clientWidth)
|
|
3230
|
+
};
|
|
3231
|
+
const hit = findOvershoot(bound, candidates(lab));
|
|
3232
|
+
return hit ? `the page scrolls: ${describeElement(hit.item)} reaches ${Math.round(hit.by)}px past the lab's box (${hit.axis})` : null;
|
|
3233
|
+
}
|
|
3234
|
+
var SETTLE_MS = 300;
|
|
3235
|
+
function useLabFitWarning(labBody) {
|
|
3236
|
+
useEffect(() => {
|
|
3237
|
+
if (process.env.NODE_ENV === "production") return;
|
|
3238
|
+
if (!labBody || typeof ResizeObserver === "undefined") return;
|
|
3239
|
+
const lab = labBody.closest(".lk-lab");
|
|
3240
|
+
const shellBody = labBody.closest(".lk-shell-body");
|
|
3241
|
+
if (!lab || !shellBody) return;
|
|
3242
|
+
let timer;
|
|
3243
|
+
const observer = new ResizeObserver(() => {
|
|
3244
|
+
clearTimeout(timer);
|
|
3245
|
+
timer = setTimeout(() => {
|
|
3246
|
+
const why = explainLabScroll(lab, shellBody);
|
|
3247
|
+
if (!why) return;
|
|
3248
|
+
observer.disconnect();
|
|
3249
|
+
console.warn(`[labkit] <Lab> should fit its container without scrolling, but ${why}.`);
|
|
3250
|
+
}, SETTLE_MS);
|
|
3251
|
+
});
|
|
3252
|
+
for (const el of [document.documentElement, lab, shellBody, labBody]) observer.observe(el);
|
|
3253
|
+
return () => {
|
|
3254
|
+
clearTimeout(timer);
|
|
3255
|
+
observer.disconnect();
|
|
3256
|
+
};
|
|
3257
|
+
}, [labBody]);
|
|
3258
|
+
}
|
|
3479
3259
|
var MODES = [
|
|
3480
3260
|
{ value: "auto", label: "Auto", glyph: /* @__PURE__ */ jsx(E, { size: 14 }) },
|
|
3481
3261
|
{ value: "light", label: "Light", glyph: /* @__PURE__ */ jsx(w, { size: 14 }) },
|
|
@@ -3486,11 +3266,11 @@ var MODE_ITEMS = MODES.map(({ value, label, glyph }) => ({
|
|
|
3486
3266
|
label: glyph,
|
|
3487
3267
|
ariaLabel: label
|
|
3488
3268
|
}));
|
|
3489
|
-
function LabHeader() {
|
|
3269
|
+
function LabHeader({ addTrial: addTrial2 = true }) {
|
|
3490
3270
|
const lab = useLabContext();
|
|
3491
3271
|
const only = lab.instruments.length === 1 ? lab.instruments[0] : null;
|
|
3492
3272
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3493
|
-
only ? /* @__PURE__ */ jsxs(
|
|
3273
|
+
!addTrial2 ? null : only ? /* @__PURE__ */ jsxs(
|
|
3494
3274
|
"button",
|
|
3495
3275
|
{
|
|
3496
3276
|
type: "button",
|
|
@@ -3501,22 +3281,14 @@ function LabHeader() {
|
|
|
3501
3281
|
/* @__PURE__ */ jsx("span", { children: "Add trial" })
|
|
3502
3282
|
]
|
|
3503
3283
|
}
|
|
3504
|
-
) : (
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
{
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
placeholder: "Add trial\u2026",
|
|
3513
|
-
selectedKey: null,
|
|
3514
|
-
options: lab.instruments.map((i) => ({ value: i.name, label: i.name })),
|
|
3515
|
-
onSelectionChange: (name) => {
|
|
3516
|
-
if (name != null) lab.addTrial(String(name));
|
|
3517
|
-
}
|
|
3518
|
-
}
|
|
3519
|
-
)
|
|
3284
|
+
) : /* @__PURE__ */ jsx(
|
|
3285
|
+
l5,
|
|
3286
|
+
{
|
|
3287
|
+
"aria-label": "Add trial",
|
|
3288
|
+
label: "Add trial\u2026",
|
|
3289
|
+
items: lab.instruments.map((i) => ({ value: i.name, label: i.title ?? i.name })),
|
|
3290
|
+
onAction: (name) => lab.addTrial(name)
|
|
3291
|
+
}
|
|
3520
3292
|
),
|
|
3521
3293
|
/* @__PURE__ */ jsx(
|
|
3522
3294
|
r6,
|
|
@@ -3534,26 +3306,14 @@ function LabHeader() {
|
|
|
3534
3306
|
)
|
|
3535
3307
|
] });
|
|
3536
3308
|
}
|
|
3537
|
-
function LabPalette({ tools }) {
|
|
3538
|
-
const
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
const setLabTool = useStore(storeCtx.store, (s) => s.setLabTool);
|
|
3542
|
-
const contributions = tools.map((t) => ({
|
|
3543
|
-
id: t.id,
|
|
3544
|
-
region: "palette",
|
|
3545
|
-
group: t.group,
|
|
3546
|
-
item: { icon: t.icon, label: t.label, shortcut: t.shortcut }
|
|
3547
|
-
}));
|
|
3548
|
-
const ctx = {
|
|
3549
|
-
activeToolId,
|
|
3550
|
-
setActiveTool: setLabTool
|
|
3551
|
-
};
|
|
3552
|
-
return /* @__PURE__ */ jsx(PaletteRegion, { contributions, ctx });
|
|
3309
|
+
function LabPalette({ tools, contributions }) {
|
|
3310
|
+
const ctx = useLabChromeContext();
|
|
3311
|
+
const all = labContributions(tools, contributions);
|
|
3312
|
+
return /* @__PURE__ */ jsx(PaletteRegion, { contributions: all.filter((c3) => c3.region === "palette"), ctx });
|
|
3553
3313
|
}
|
|
3554
3314
|
function currentPage(path, pages) {
|
|
3555
3315
|
const here = path.split("?")[0].split("#")[0].replace(/\/$/, "").replace(/\.html$/, "");
|
|
3556
|
-
return pages.findIndex((
|
|
3316
|
+
return pages.findIndex((p2) => here.endsWith(p2.href.replace(/\.html$/, "")));
|
|
3557
3317
|
}
|
|
3558
3318
|
function LabSwitcher({ title, pages, path, className }) {
|
|
3559
3319
|
const [open, setOpen] = useState(false);
|
|
@@ -3663,17 +3423,17 @@ function LabShell({
|
|
|
3663
3423
|
);
|
|
3664
3424
|
return outer ? shell : /* @__PURE__ */ jsx(ThemeProvider, { theme: interstellarTheme, mode: resolved, children: shell });
|
|
3665
3425
|
}
|
|
3666
|
-
var
|
|
3667
|
-
var
|
|
3426
|
+
var ZONE_ID = asNodeId("lk-workspace");
|
|
3427
|
+
var STRATEGIES = { grid: gridStrategy, floating: floatingStrategy };
|
|
3668
3428
|
var FLOAT_ZONE_ID = asNodeId("lk-workspace-floating");
|
|
3669
3429
|
var PANEL_KIND = "panel";
|
|
3670
3430
|
var KIND = "trial";
|
|
3671
3431
|
function extentOf(store, id) {
|
|
3672
|
-
const
|
|
3673
|
-
if (!
|
|
3432
|
+
const p2 = store.getNode(id)?.membership?.placement;
|
|
3433
|
+
if (!p2) return null;
|
|
3674
3434
|
const out = {};
|
|
3675
|
-
if (
|
|
3676
|
-
if (
|
|
3435
|
+
if (p2.size) out.size = p2.size;
|
|
3436
|
+
if (p2.span) out.span = p2.span;
|
|
3677
3437
|
return Object.keys(out).length > 0 ? out : null;
|
|
3678
3438
|
}
|
|
3679
3439
|
function PanelFrame({ panel, floating = false }) {
|
|
@@ -3694,11 +3454,11 @@ function Workspace({
|
|
|
3694
3454
|
viewport
|
|
3695
3455
|
}) {
|
|
3696
3456
|
const items = Children.toArray(children);
|
|
3697
|
-
const tilePanels = useMemo(() => (panels ?? []).filter((
|
|
3698
|
-
const floatPanels = useMemo(() => (panels ?? []).filter((
|
|
3457
|
+
const tilePanels = useMemo(() => (panels ?? []).filter((p2) => p2.as === "tile"), [panels]);
|
|
3458
|
+
const floatPanels = useMemo(() => (panels ?? []).filter((p2) => p2.as === "floating"), [panels]);
|
|
3699
3459
|
const wantedIds = [
|
|
3700
3460
|
...items.map((_, i) => ids?.[i] ?? `lk-ws-${i}`),
|
|
3701
|
-
...tilePanels.map((
|
|
3461
|
+
...tilePanels.map((p2) => `lk-panel-${p2.key}`)
|
|
3702
3462
|
];
|
|
3703
3463
|
const idKey = wantedIds.join(",");
|
|
3704
3464
|
const idKeyRef = useRef(null);
|
|
@@ -3718,7 +3478,7 @@ function Workspace({
|
|
|
3718
3478
|
store2.registerNode(
|
|
3719
3479
|
createNode({
|
|
3720
3480
|
kind: "zone",
|
|
3721
|
-
id:
|
|
3481
|
+
id: ZONE_ID,
|
|
3722
3482
|
container: { strategyId: "grid", config: { resizable, gap, padding } }
|
|
3723
3483
|
})
|
|
3724
3484
|
);
|
|
@@ -3734,10 +3494,10 @@ function Workspace({
|
|
|
3734
3494
|
const store = storeRef.current;
|
|
3735
3495
|
const surface = useSurfaceOptional();
|
|
3736
3496
|
useLayoutEffect(() => {
|
|
3737
|
-
store.updateContainerConfig(
|
|
3497
|
+
store.updateContainerConfig(ZONE_ID, { resizable, gap, padding });
|
|
3738
3498
|
}, [store, resizable, gap, padding]);
|
|
3739
3499
|
useLayoutEffect(() => {
|
|
3740
|
-
const present = new Set(store.getContainerView(
|
|
3500
|
+
const present = new Set(store.getContainerView(ZONE_ID)?.childOrder ?? []);
|
|
3741
3501
|
const wanted = new Set(nodeIds);
|
|
3742
3502
|
for (const id of present) {
|
|
3743
3503
|
if (!wanted.has(id)) store.unregisterNode(id);
|
|
@@ -3745,12 +3505,12 @@ function Workspace({
|
|
|
3745
3505
|
for (const id of nodeIds) {
|
|
3746
3506
|
if (present.has(id)) continue;
|
|
3747
3507
|
const kind = String(id).startsWith("lk-panel-") ? PANEL_KIND : KIND;
|
|
3748
|
-
store.registerNode(createNode({ kind, id, parentId:
|
|
3508
|
+
store.registerNode(createNode({ kind, id, parentId: ZONE_ID, focus: true }));
|
|
3749
3509
|
store.showNode(id);
|
|
3750
3510
|
const saved = layoutRef.current?.[id];
|
|
3751
3511
|
if (saved) store.patchPlacement(id, saved);
|
|
3752
3512
|
}
|
|
3753
|
-
store.setChildOrder(
|
|
3513
|
+
store.setChildOrder(ZONE_ID, [...nodeIds]);
|
|
3754
3514
|
}, [store, nodeIds]);
|
|
3755
3515
|
useEffect(() => {
|
|
3756
3516
|
if (!surface) return;
|
|
@@ -3761,7 +3521,7 @@ function Workspace({
|
|
|
3761
3521
|
if (!onLayoutChange) return;
|
|
3762
3522
|
return store.events.on("node.placementChanged", () => {
|
|
3763
3523
|
const next = {};
|
|
3764
|
-
for (const child of store.getChildren(
|
|
3524
|
+
for (const child of store.getChildren(ZONE_ID)) {
|
|
3765
3525
|
const extent = extentOf(store, child.id);
|
|
3766
3526
|
if (extent) next[child.id] = extent;
|
|
3767
3527
|
}
|
|
@@ -3775,7 +3535,7 @@ function Workspace({
|
|
|
3775
3535
|
const chrome = useMemo(() => {
|
|
3776
3536
|
const byId = new Map(nodeIds.map((id, i) => [id, items[i]]));
|
|
3777
3537
|
const panelByNode = new Map(
|
|
3778
|
-
tilePanels.map((
|
|
3538
|
+
tilePanels.map((p2) => [`lk-panel-${p2.key}`, p2])
|
|
3779
3539
|
);
|
|
3780
3540
|
return {
|
|
3781
3541
|
[PANEL_KIND]: ({ node }) => {
|
|
@@ -3793,11 +3553,11 @@ function Workspace({
|
|
|
3793
3553
|
}
|
|
3794
3554
|
};
|
|
3795
3555
|
}, [nodeIds, items, reorderable, tilePanels]);
|
|
3796
|
-
const floatingLayer = floatPanels.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "lk-workspace__floating", children: floatPanels.map((
|
|
3556
|
+
const floatingLayer = floatPanels.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "lk-workspace__floating", children: floatPanels.map((p2) => /* @__PURE__ */ jsx(PanelFrame, { panel: p2, floating: true }, p2.key)) });
|
|
3797
3557
|
const grid = /* @__PURE__ */ jsx(
|
|
3798
3558
|
Container,
|
|
3799
3559
|
{
|
|
3800
|
-
parentId:
|
|
3560
|
+
parentId: ZONE_ID,
|
|
3801
3561
|
chrome,
|
|
3802
3562
|
className: "lk-workspace windease-zone",
|
|
3803
3563
|
affordances: resizable,
|
|
@@ -3805,7 +3565,7 @@ function Workspace({
|
|
|
3805
3565
|
onChildOrderChange: reorderable ? commitOrder : void 0
|
|
3806
3566
|
}
|
|
3807
3567
|
);
|
|
3808
|
-
return /* @__PURE__ */ jsx(Provider, { store, children: /* @__PURE__ */ jsx(StrategyRegistryProvider, { strategies:
|
|
3568
|
+
return /* @__PURE__ */ jsx(Provider, { store, children: /* @__PURE__ */ jsx(StrategyRegistryProvider, { strategies: STRATEGIES, children: floatingLayer ? (
|
|
3809
3569
|
// The floating layer insets against the workspace, so it needs a
|
|
3810
3570
|
// positioned box around both. Without one it insets against the page
|
|
3811
3571
|
// and lands over the lab header.
|
|
@@ -3815,23 +3575,59 @@ function Workspace({
|
|
|
3815
3575
|
] })
|
|
3816
3576
|
) : reorderable ? /* @__PURE__ */ jsx(DragProvider, { children: grid }) : grid }) });
|
|
3817
3577
|
}
|
|
3818
|
-
function
|
|
3819
|
-
|
|
3578
|
+
function seedDefaultTrial(store, instruments, defaultInstrument) {
|
|
3579
|
+
if (store.getState().trials.length > 0) return;
|
|
3580
|
+
const record = addTrial([], instruments, defaultInstrument)[0];
|
|
3581
|
+
if (!record) return;
|
|
3582
|
+
const { undoStack: _undoStack, ...rest } = record;
|
|
3583
|
+
store.getState().addTrial(rest);
|
|
3584
|
+
}
|
|
3585
|
+
function openUnstoredLab({ instruments, defaultInstrument, mode }) {
|
|
3586
|
+
const store = createLabStore({ initialMode: mode ?? "auto", instruments });
|
|
3587
|
+
seedDefaultTrial(store, instruments, defaultInstrument);
|
|
3588
|
+
const records = createRecordCache({ storage: noneAdapter, prefix: "", writable: false });
|
|
3589
|
+
return { store, records, close: () => records.close(), marks: /* @__PURE__ */ new Map() };
|
|
3590
|
+
}
|
|
3591
|
+
async function openStoredLab({ instruments, defaultInstrument, mode }, storageKey, storage) {
|
|
3592
|
+
const opened = await openLabStore({
|
|
3820
3593
|
storageKey,
|
|
3821
|
-
storage,
|
|
3822
|
-
initialMode,
|
|
3823
|
-
|
|
3824
|
-
configDefaults: configDefaultsOf(instruments)
|
|
3594
|
+
storage: storage ?? await defaultStorage(),
|
|
3595
|
+
initialMode: mode ?? "auto",
|
|
3596
|
+
instruments
|
|
3825
3597
|
});
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
const
|
|
3831
|
-
|
|
3598
|
+
seedDefaultTrial(opened.store, instruments, defaultInstrument);
|
|
3599
|
+
const marks = /* @__PURE__ */ new Map();
|
|
3600
|
+
await Promise.all(
|
|
3601
|
+
opened.store.getState().trials.map(async (trial) => {
|
|
3602
|
+
const kept = instruments.find((i) => i.name === trial.instrumentName)?.annotations?.storage;
|
|
3603
|
+
if (!kept) return;
|
|
3604
|
+
try {
|
|
3605
|
+
marks.set(trial.id, await kept.load());
|
|
3606
|
+
} catch (error) {
|
|
3607
|
+
console.warn(`[labkit] could not load the marks of trial "${trial.id}"`, error);
|
|
3608
|
+
marks.set(trial.id, null);
|
|
3609
|
+
}
|
|
3610
|
+
})
|
|
3611
|
+
);
|
|
3612
|
+
return { ...opened, marks };
|
|
3613
|
+
}
|
|
3614
|
+
function LabFallback({
|
|
3615
|
+
title,
|
|
3616
|
+
mode,
|
|
3617
|
+
pages,
|
|
3618
|
+
path
|
|
3619
|
+
}) {
|
|
3620
|
+
const resolvedMode = useResolvedMode(mode ?? "auto");
|
|
3621
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { theme: interstellarTheme, mode: resolvedMode, className: "lk-lab", children: /* @__PURE__ */ jsx(
|
|
3622
|
+
LabShell,
|
|
3623
|
+
{
|
|
3624
|
+
title: title ?? "Labkit",
|
|
3625
|
+
mode,
|
|
3626
|
+
...pages ? { pages } : {},
|
|
3627
|
+
...path !== void 0 ? { path } : {},
|
|
3628
|
+
children: null
|
|
3832
3629
|
}
|
|
3833
|
-
}
|
|
3834
|
-
return store;
|
|
3630
|
+
) });
|
|
3835
3631
|
}
|
|
3836
3632
|
var NEBULA_SLOTS = [
|
|
3837
3633
|
{ cx: "18%", cy: "30%", sx: "60%", sy: "80%", stop: "70%" },
|
|
@@ -3841,43 +3637,77 @@ var NEBULA_SLOTS = [
|
|
|
3841
3637
|
{ cx: "85%", cy: "18%", sx: "55%", sy: "80%", stop: "70%" }
|
|
3842
3638
|
];
|
|
3843
3639
|
function buildNebula(colors) {
|
|
3844
|
-
const blobs = colors.map((
|
|
3845
|
-
const
|
|
3846
|
-
return `radial-gradient(ellipse ${
|
|
3640
|
+
const blobs = colors.map((c3, i) => {
|
|
3641
|
+
const p2 = NEBULA_SLOTS[i % NEBULA_SLOTS.length];
|
|
3642
|
+
return `radial-gradient(ellipse ${p2.sx} ${p2.sy} at ${p2.cx} ${p2.cy}, color-mix(in srgb, ${c3} 22%, transparent), transparent ${p2.stop})`;
|
|
3847
3643
|
});
|
|
3848
3644
|
blobs.push("radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)");
|
|
3849
3645
|
return blobs.join(", ");
|
|
3850
3646
|
}
|
|
3851
|
-
function
|
|
3647
|
+
function LabPanes({
|
|
3648
|
+
contributions,
|
|
3649
|
+
children
|
|
3650
|
+
}) {
|
|
3651
|
+
const surface = useSurfaceOptional();
|
|
3652
|
+
const [sidebarWidth, setSidebarWidth] = usePersistedState(
|
|
3653
|
+
"lk-lab-sidebar-width",
|
|
3654
|
+
void 0,
|
|
3655
|
+
{ scope: "lab" }
|
|
3656
|
+
);
|
|
3657
|
+
return /* @__PURE__ */ jsx(
|
|
3658
|
+
Split,
|
|
3659
|
+
{
|
|
3660
|
+
className: "lk-lab__panes",
|
|
3661
|
+
sidebarClassName: "lk-lab__sidebar",
|
|
3662
|
+
contentClassName: "lk-lab__main",
|
|
3663
|
+
label: "Lab sidebar",
|
|
3664
|
+
defaultWidth: 260,
|
|
3665
|
+
width: sidebarWidth,
|
|
3666
|
+
onWidthChange: (w2) => {
|
|
3667
|
+
setSidebarWidth(w2);
|
|
3668
|
+
surface?.invalidateRects();
|
|
3669
|
+
},
|
|
3670
|
+
sidebar: /* @__PURE__ */ jsx(LabSidebarRegion, { contributions }),
|
|
3671
|
+
children
|
|
3672
|
+
}
|
|
3673
|
+
);
|
|
3674
|
+
}
|
|
3675
|
+
function Lab(props) {
|
|
3676
|
+
if (process.env.NODE_ENV !== "production" && props.instruments.length === 0) {
|
|
3677
|
+
throw new Error("[labkit] <Lab> requires a non-empty `instruments` array");
|
|
3678
|
+
}
|
|
3679
|
+
const { storageKey, storage } = props;
|
|
3680
|
+
useWarnIgnoredChange("<Lab>", { storageKey, storage });
|
|
3681
|
+
const opened = useOpenOnce(
|
|
3682
|
+
() => storageKey === void 0 ? openUnstoredLab(props) : openStoredLab(props, storageKey, storage)
|
|
3683
|
+
);
|
|
3684
|
+
if (!opened) {
|
|
3685
|
+
return props.fallback !== void 0 ? /* @__PURE__ */ jsx(Fragment, { children: props.fallback }) : /* @__PURE__ */ jsx(LabFallback, { title: props.title, mode: props.mode, pages: props.pages, path: props.path });
|
|
3686
|
+
}
|
|
3687
|
+
return /* @__PURE__ */ jsx(LabRuntime, { ...props, opened });
|
|
3688
|
+
}
|
|
3689
|
+
function LabRuntime({
|
|
3852
3690
|
instruments,
|
|
3853
|
-
defaultInstrument,
|
|
3854
|
-
storage,
|
|
3855
|
-
storageKey,
|
|
3856
3691
|
mode,
|
|
3857
3692
|
nebula,
|
|
3858
3693
|
title,
|
|
3694
|
+
pages,
|
|
3695
|
+
path,
|
|
3859
3696
|
footer,
|
|
3860
3697
|
chrome,
|
|
3698
|
+
labChrome,
|
|
3861
3699
|
suppress,
|
|
3700
|
+
addTrial: addTrial2,
|
|
3862
3701
|
tools,
|
|
3863
3702
|
configRules,
|
|
3864
3703
|
controls,
|
|
3865
|
-
children
|
|
3704
|
+
children,
|
|
3705
|
+
opened
|
|
3866
3706
|
}) {
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
if (storeRef.current === null) {
|
|
3872
|
-
storeRef.current = buildStore(
|
|
3873
|
-
instruments,
|
|
3874
|
-
defaultInstrument,
|
|
3875
|
-
storage ?? noneAdapter,
|
|
3876
|
-
storageKey ?? "labkit",
|
|
3877
|
-
mode ?? "auto"
|
|
3878
|
-
);
|
|
3879
|
-
}
|
|
3880
|
-
const store = storeRef.current;
|
|
3707
|
+
const { store } = opened;
|
|
3708
|
+
useLayoutEffect(() => {
|
|
3709
|
+
store.getState().setInstruments(instruments);
|
|
3710
|
+
}, [instruments, store]);
|
|
3881
3711
|
const trials = useStore(store, (s) => s.trials);
|
|
3882
3712
|
const savedSnapshots = useStore(store, (s) => s.savedSnapshots);
|
|
3883
3713
|
const modeValue = useStore(store, (s) => s.mode);
|
|
@@ -3886,28 +3716,48 @@ function Lab({
|
|
|
3886
3716
|
const panelHostsRef = useRef(null);
|
|
3887
3717
|
if (panelHostsRef.current === null) panelHostsRef.current = createPanelHostRegistry();
|
|
3888
3718
|
const outerSurface = useSurfaceOptional();
|
|
3889
|
-
const
|
|
3890
|
-
const
|
|
3891
|
-
const
|
|
3719
|
+
const outerOver = useSurfaceCanvas("over");
|
|
3720
|
+
const outerUnder = useSurfaceCanvas("under");
|
|
3721
|
+
const [ownOver, setOwnOver] = useState(null);
|
|
3722
|
+
const [ownUnder, setOwnUnder] = useState(null);
|
|
3723
|
+
const ownOverRef = useRef(null);
|
|
3724
|
+
const ownUnderRef = useRef(null);
|
|
3892
3725
|
const bufferRef = useRef({ w: 0, h: 0 });
|
|
3893
3726
|
const surfaceRef = useRef(null);
|
|
3894
3727
|
const onFrame = useCallback((frame) => {
|
|
3895
|
-
const
|
|
3896
|
-
if (
|
|
3897
|
-
const
|
|
3898
|
-
const
|
|
3899
|
-
if (bufferRef.current.w
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3728
|
+
const canvases = [ownUnderRef.current, ownOverRef.current].filter((c3) => c3 !== null);
|
|
3729
|
+
if (canvases.length === 0) return;
|
|
3730
|
+
const w2 = Math.round(frame.size.width * frame.dpr);
|
|
3731
|
+
const h2 = Math.round(frame.size.height * frame.dpr);
|
|
3732
|
+
if (bufferRef.current.w !== w2 || bufferRef.current.h !== h2) {
|
|
3733
|
+
bufferRef.current = { w: w2, h: h2 };
|
|
3734
|
+
for (const c3 of canvases) {
|
|
3735
|
+
c3.width = w2;
|
|
3736
|
+
c3.height = h2;
|
|
3737
|
+
c3.style.width = `${frame.size.width}px`;
|
|
3738
|
+
c3.style.height = `${frame.size.height}px`;
|
|
3739
|
+
}
|
|
3740
|
+
surfaceRef.current?.invalidateAll();
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3906
3743
|
}, []);
|
|
3907
3744
|
const ownSurface = useTiledSurface({ onFrame });
|
|
3908
3745
|
surfaceRef.current = ownSurface;
|
|
3746
|
+
const [labBody, setLabBody] = useState(null);
|
|
3747
|
+
const attachOwnSurface = ownSurface.containerRef;
|
|
3748
|
+
const labBodyRef = useCallback(
|
|
3749
|
+
(el) => {
|
|
3750
|
+
setLabBody(el);
|
|
3751
|
+
if (!outerSurface) attachOwnSurface(el);
|
|
3752
|
+
},
|
|
3753
|
+
[outerSurface, attachOwnSurface]
|
|
3754
|
+
);
|
|
3755
|
+
useLabFitWarning(labBody);
|
|
3909
3756
|
const surface = outerSurface ?? ownSurface;
|
|
3910
|
-
const
|
|
3757
|
+
const surfaceCanvases = useMemo(
|
|
3758
|
+
() => outerSurface ? { over: outerOver, under: outerUnder } : { over: ownOver, under: ownUnder },
|
|
3759
|
+
[outerSurface, outerOver, outerUnder, ownOver, ownUnder]
|
|
3760
|
+
);
|
|
3911
3761
|
const workspacePanels = useMemo(
|
|
3912
3762
|
() => Object.entries(undockedPanels).map(([key, panel]) => ({
|
|
3913
3763
|
key,
|
|
@@ -3917,6 +3767,13 @@ function Lab({
|
|
|
3917
3767
|
[undockedPanels]
|
|
3918
3768
|
);
|
|
3919
3769
|
const resolvedMode = useResolvedMode(modeValue);
|
|
3770
|
+
const annotates = instruments.some((i) => i.annotations != null);
|
|
3771
|
+
const labChromeAll = useMemo(
|
|
3772
|
+
() => labContributions([...annotates ? ANNOTATION_TOOLS : [], ...tools ?? []], labChrome),
|
|
3773
|
+
[annotates, tools, labChrome]
|
|
3774
|
+
);
|
|
3775
|
+
const hasFooterChrome = labChromeAll.some((c3) => c3.region === "footer");
|
|
3776
|
+
const hasSidebarChrome = labChromeAll.some((c3) => c3.region === "sidebar");
|
|
3920
3777
|
useEffect(() => {
|
|
3921
3778
|
if (mode && mode !== store.getState().mode) {
|
|
3922
3779
|
store.getState().setMode(mode);
|
|
@@ -3924,10 +3781,9 @@ function Lab({
|
|
|
3924
3781
|
}, [mode, store]);
|
|
3925
3782
|
const contextValue = useMemo(() => {
|
|
3926
3783
|
const replaceTrials = (next) => {
|
|
3927
|
-
const currentById = new Map(store.getState().trials.map((
|
|
3928
|
-
const merged = next.map((
|
|
3784
|
+
const currentById = new Map(store.getState().trials.map((w2) => [w2.id, w2]));
|
|
3785
|
+
const merged = next.map((w2) => currentById.get(w2.id) ?? w2);
|
|
3929
3786
|
store.setState({ trials: merged });
|
|
3930
|
-
store.getState().setMode(store.getState().mode);
|
|
3931
3787
|
};
|
|
3932
3788
|
return {
|
|
3933
3789
|
instruments,
|
|
@@ -3949,14 +3805,13 @@ function Lab({
|
|
|
3949
3805
|
},
|
|
3950
3806
|
resetTrial: (id) => {
|
|
3951
3807
|
const next = resetTrial(store.getState().trials, id, instruments);
|
|
3952
|
-
const record = next.find((
|
|
3808
|
+
const record = next.find((w2) => w2.id === id);
|
|
3953
3809
|
if (!record) return;
|
|
3954
3810
|
store.setState((s) => ({
|
|
3955
3811
|
trials: s.trials.map(
|
|
3956
|
-
(
|
|
3812
|
+
(w2) => w2.id === id ? { ...w2, config: record.config, state: record.state, view: record.view } : w2
|
|
3957
3813
|
)
|
|
3958
3814
|
}));
|
|
3959
|
-
store.getState().updateTrialView(id, record.view);
|
|
3960
3815
|
},
|
|
3961
3816
|
savedSnapshots,
|
|
3962
3817
|
saveSnapshot: (trialId, name) => {
|
|
@@ -3978,7 +3833,20 @@ function Lab({
|
|
|
3978
3833
|
};
|
|
3979
3834
|
}, [instruments, trials, savedSnapshots, modeValue, store, configRules, controls]);
|
|
3980
3835
|
const backdropStyle = resolvedMode === "dark" && nebula && nebula.length > 0 ? { ["--wzl-backdrop"]: buildNebula(nebula) } : void 0;
|
|
3981
|
-
|
|
3836
|
+
const workspace = /* @__PURE__ */ jsx(
|
|
3837
|
+
Workspace,
|
|
3838
|
+
{
|
|
3839
|
+
panels: workspacePanels,
|
|
3840
|
+
ids: trials.map((w2) => w2.id),
|
|
3841
|
+
resizable: true,
|
|
3842
|
+
reorderable: true,
|
|
3843
|
+
onReorder: (ids) => contextValue.reorderTrials(ids),
|
|
3844
|
+
layout,
|
|
3845
|
+
onLayoutChange: (next) => store.getState().setLayout(next),
|
|
3846
|
+
children: trials.map((w2) => /* @__PURE__ */ jsx(Trial, { id: w2.id, chrome, suppress }, w2.id))
|
|
3847
|
+
}
|
|
3848
|
+
);
|
|
3849
|
+
return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(PersistenceContext.Provider, { value: opened.records, children: /* @__PURE__ */ jsx(AnnotationPreloadContext.Provider, { value: opened.marks, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
3982
3850
|
ThemeProvider,
|
|
3983
3851
|
{
|
|
3984
3852
|
theme: interstellarTheme,
|
|
@@ -3990,55 +3858,61 @@ function Lab({
|
|
|
3990
3858
|
{
|
|
3991
3859
|
title: title ?? "Labkit",
|
|
3992
3860
|
mode: modeValue,
|
|
3993
|
-
|
|
3861
|
+
...pages ? { pages } : {},
|
|
3862
|
+
...path !== void 0 ? { path } : {},
|
|
3863
|
+
footer: hasFooterChrome ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3864
|
+
footer,
|
|
3865
|
+
/* @__PURE__ */ jsx(LabFooterRegion, { contributions: labChromeAll })
|
|
3866
|
+
] }) : footer,
|
|
3994
3867
|
header: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3995
|
-
/* @__PURE__ */ jsx(LabHeader, {}),
|
|
3996
|
-
children
|
|
3868
|
+
/* @__PURE__ */ jsx(LabHeader, { ...addTrial2 !== void 0 ? { addTrial: addTrial2 } : {} }),
|
|
3869
|
+
children,
|
|
3870
|
+
/* @__PURE__ */ jsx(LabHeaderRegion, { contributions: labChromeAll })
|
|
3997
3871
|
] }),
|
|
3998
|
-
children: /* @__PURE__ */ jsx(PanelHostContext.Provider, { value: panelHostsRef.current, children: /* @__PURE__ */ jsx(SurfaceContext.Provider, { value: surface, children: /* @__PURE__ */ jsx(SurfaceCanvasContext.Provider, { value:
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
"
|
|
4010
|
-
{
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
ownCanvasRef.current = el;
|
|
4014
|
-
setOwnCanvas(el);
|
|
4015
|
-
}
|
|
3872
|
+
children: /* @__PURE__ */ jsx(PanelHostContext.Provider, { value: panelHostsRef.current, children: /* @__PURE__ */ jsx(SurfaceContext.Provider, { value: surface, children: /* @__PURE__ */ jsx(SurfaceCanvasContext.Provider, { value: surfaceCanvases, children: /* @__PURE__ */ jsxs("div", { className: "lk-lab__body", ref: labBodyRef, children: [
|
|
3873
|
+
outerSurface ? null : (
|
|
3874
|
+
// Two buffers stacked around the trials, both inert —
|
|
3875
|
+
// each tile takes input from its own box. A tile's marks
|
|
3876
|
+
// annotate the instrument's DOM from over it; an opaque
|
|
3877
|
+
// renderer sits under it, so the pane can still hold a
|
|
3878
|
+
// label. The under one is first so it paints first.
|
|
3879
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3880
|
+
/* @__PURE__ */ jsx(
|
|
3881
|
+
"canvas",
|
|
3882
|
+
{
|
|
3883
|
+
className: "lk-lab__surface lk-lab__surface--under",
|
|
3884
|
+
ref: (el) => {
|
|
3885
|
+
ownUnderRef.current = el;
|
|
3886
|
+
setOwnUnder(el);
|
|
4016
3887
|
}
|
|
4017
|
-
|
|
3888
|
+
}
|
|
4018
3889
|
),
|
|
4019
|
-
tools ? /* @__PURE__ */ jsx(LabPalette, { tools }) : null,
|
|
4020
3890
|
/* @__PURE__ */ jsx(
|
|
4021
|
-
|
|
3891
|
+
"canvas",
|
|
4022
3892
|
{
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
layout,
|
|
4029
|
-
onLayoutChange: (next) => store.getState().setLayout(next),
|
|
4030
|
-
children: trials.map((w3) => /* @__PURE__ */ jsx(Trial, { id: w3.id, chrome, suppress }, w3.id))
|
|
3893
|
+
className: "lk-lab__surface lk-lab__surface--over",
|
|
3894
|
+
ref: (el) => {
|
|
3895
|
+
ownOverRef.current = el;
|
|
3896
|
+
setOwnOver(el);
|
|
3897
|
+
}
|
|
4031
3898
|
}
|
|
4032
3899
|
)
|
|
4033
|
-
]
|
|
4034
|
-
|
|
4035
|
-
|
|
3900
|
+
] })
|
|
3901
|
+
),
|
|
3902
|
+
hasSidebarChrome ? /* @__PURE__ */ jsxs(LabPanes, { contributions: labChromeAll, children: [
|
|
3903
|
+
/* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
|
|
3904
|
+
workspace
|
|
3905
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3906
|
+
/* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
|
|
3907
|
+
workspace
|
|
3908
|
+
] })
|
|
3909
|
+
] }) }) }) })
|
|
4036
3910
|
}
|
|
4037
3911
|
)
|
|
4038
3912
|
}
|
|
4039
|
-
) }) });
|
|
3913
|
+
) }) }) }) });
|
|
4040
3914
|
}
|
|
4041
3915
|
|
|
4042
|
-
export {
|
|
3916
|
+
export { ANNOTATION_TOOLS, ANNOTATION_WEASEL_TOOLS, AnnotationOverlay, AnnotationTargets, Lab, LabPalette, LabShell, LabSwitcher, Trial, TrialBody, TrialChrome, Workspace, addTrial, annotationToolInfo, annotationsFromJSON, capturePlan, captureTarget, cloneTrial, closeTrial, composeCaptureSvg, createAnnotationScene, createAnnotationStore, createMarkDrawOne, currentPage, defineInstrument, fitView, fracContains, fracEncloses, fracToWorld, fromWeaselView, interstellarTheme, isStale, markCommands, markSvgNodes, reorderTrials, resetTrial, resolveMarkStyle, roundFrac, seenFrom, toWeaselView, validateConfigSchema, worldToFrac };
|
|
4043
3917
|
//# sourceMappingURL=index.js.map
|
|
4044
3918
|
//# sourceMappingURL=index.js.map
|