@weasel-js/labkit 1.3.0-pre.0 → 1.4.0-pre.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_dts/CanvasStackContext-DtAn_3O-.d.ts +42 -0
- package/dist/_dts/DrawCommand-B3bskUsC.d.ts +564 -0
- package/dist/_dts/{PrefsForm-BYa6cWnO.d.ts → PrefsForm-BkUJZx0A.d.ts} +4 -1
- package/dist/_dts/fitViewToBounds-dZ2UDB6e.d.ts +21 -0
- package/dist/_dts/frac-Cp3NivlC.d.ts +728 -0
- package/dist/_dts/index-BDVzvRzQ.d.ts +236 -0
- package/dist/_dts/shapeKinds-Cx_rxwsa.d.ts +87 -0
- package/dist/_dts/{DrawCommand-DBB45NfN.d.ts → types-C-gh9Ap-.d.ts} +1 -582
- package/dist/_dts/{types-AHlQxBNN.d.ts → types-D6s4b7if.d.ts} +2 -2
- package/dist/_dts/{types-1Sdxy_Pv.d.ts → types-DYMaEvM5.d.ts} +26 -1
- package/dist/_dts/{useTrialState-CsGMjhu9.d.ts → useTrialState-gmMvZqPc.d.ts} +7 -2
- package/dist/canvas/index.d.ts +43 -25
- package/dist/canvas/index.js +5 -3
- package/dist/chrome/index.d.ts +9 -6
- package/dist/chrome/index.js +7 -6
- package/dist/chunk-2AYGEN57.js +32 -0
- package/dist/chunk-2AYGEN57.js.map +1 -0
- package/dist/{chunk-RVNN4CHQ.js → chunk-3P5TTTA7.js} +16 -8
- package/dist/chunk-3P5TTTA7.js.map +1 -0
- package/dist/{chunk-NS54R52R.js → chunk-3ZELRRBV.js} +45 -40
- package/dist/chunk-3ZELRRBV.js.map +1 -0
- package/dist/{chunk-HFDVGF4X.js → chunk-4EY67BKJ.js} +32 -7
- package/dist/chunk-4EY67BKJ.js.map +1 -0
- package/dist/chunk-5TCEVMPI.js +140 -0
- package/dist/chunk-5TCEVMPI.js.map +1 -0
- package/dist/{chunk-O7NVSCLN.js → chunk-6JMU56CU.js} +133 -134
- package/dist/chunk-6JMU56CU.js.map +1 -0
- package/dist/{chunk-CPUJ3QXL.js → chunk-CQLQPQ4P.js} +2 -2
- package/dist/chunk-CQLQPQ4P.js.map +1 -0
- package/dist/chunk-CTRKTLYZ.js +33 -0
- package/dist/chunk-CTRKTLYZ.js.map +1 -0
- package/dist/chunk-EQIB2PGC.js +458 -0
- package/dist/chunk-EQIB2PGC.js.map +1 -0
- package/dist/chunk-HEORBLVT.js +7128 -0
- package/dist/chunk-HEORBLVT.js.map +1 -0
- package/dist/{chunk-V7PRSIRQ.js → chunk-JDDIZSLL.js} +4 -4
- package/dist/chunk-JDDIZSLL.js.map +1 -0
- package/dist/chunk-LITE5YFB.js +669 -0
- package/dist/chunk-LITE5YFB.js.map +1 -0
- package/dist/{chunk-Z6HJ5FEM.js → chunk-ROQDRXKW.js} +732 -315
- package/dist/chunk-ROQDRXKW.js.map +1 -0
- package/dist/chunk-SBXNQW4G.js +3 -0
- package/dist/chunk-SBXNQW4G.js.map +1 -0
- package/dist/{chunk-4TMMVIDM.js → chunk-XJ6N32QP.js} +50 -6
- package/dist/chunk-XJ6N32QP.js.map +1 -0
- package/dist/chunk-YQJNT4XQ.js +293 -0
- package/dist/chunk-YQJNT4XQ.js.map +1 -0
- package/dist/controls/index.d.ts +4 -28
- package/dist/controls/index.js +3 -3
- package/dist/dragdrop/index.d.ts +50 -5
- package/dist/dragdrop/index.js +3 -1
- package/dist/index.d.ts +660 -302
- package/dist/index.js +1793 -587
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +6 -5
- package/dist/layers/index.js +4 -2
- package/dist/loupe/index.d.ts +187 -0
- package/dist/loupe/index.js +7 -0
- package/dist/loupe/index.js.map +1 -0
- package/dist/passthrough/weasel-canvas.d.ts +3 -1
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +151 -91
- package/dist/passthrough/weasel-ui.js +3 -2
- package/dist/primitives/index.d.ts +3 -1
- package/dist/primitives/index.js +6 -5
- package/dist/state/index.d.ts +3 -3
- package/dist/state/index.js +2 -2
- package/dist/styles.css +229 -703
- package/dist/surface/index.d.ts +18 -2
- package/dist/surface/index.js +2 -2
- package/dist/ui/layers/index.d.ts +8 -2
- package/dist/ui/layers/index.js +2 -4
- package/dist/undo/index.d.ts +5 -4
- package/package.json +14 -7
- package/src/annotations/AnnotationOverlay.tsx +205 -0
- package/src/annotations/AnnotationTargets.tsx +37 -0
- package/src/annotations/Annotations.less +129 -0
- package/src/annotations/Annotations.meaning.test.tsx +111 -0
- package/src/annotations/Annotations.overlay.test.tsx +142 -0
- package/src/annotations/AnnotationsContext.ts +49 -0
- package/src/annotations/ExportMenu.test.tsx +86 -0
- package/src/annotations/ExportMenu.tsx +182 -0
- package/src/annotations/MarkList.tsx +75 -0
- package/src/annotations/capture.test.ts +140 -0
- package/src/annotations/capture.ts +237 -0
- package/src/annotations/drawOne.test.ts +56 -0
- package/src/annotations/drawOne.ts +47 -0
- package/src/annotations/frac.test.ts +59 -0
- package/src/annotations/frac.ts +66 -0
- package/src/annotations/history.test.ts +82 -0
- package/src/annotations/history.ts +118 -0
- package/src/annotations/index.ts +37 -0
- package/src/annotations/paint.test.ts +125 -0
- package/src/annotations/paint.ts +130 -0
- package/src/annotations/staleness.test.ts +56 -0
- package/src/annotations/staleness.ts +41 -0
- package/src/annotations/store.test.ts +175 -0
- package/src/annotations/store.ts +317 -0
- package/src/annotations/svgNodes.test.ts +77 -0
- package/src/annotations/svgNodes.ts +74 -0
- package/src/annotations/toolMap.test.ts +36 -0
- package/src/annotations/toolMap.ts +54 -0
- package/src/annotations/types.ts +222 -0
- package/src/annotations/view.test.ts +37 -0
- package/src/annotations/view.ts +40 -0
- package/src/canvas/AGENTS.md +55 -5
- package/src/canvas/CanvasStack.test.tsx +27 -1
- package/src/canvas/CanvasStack.tsx +29 -4
- package/src/canvas/CanvasStackContext.ts +23 -4
- package/src/canvas/camera.test.ts +78 -0
- package/src/canvas/camera.ts +62 -0
- package/src/canvas/canvasCoords.test.ts +26 -0
- package/src/canvas/canvasCoords.ts +20 -7
- package/src/canvas/index.ts +5 -1
- package/src/canvas/useLayerScheduler.ts +7 -3
- package/src/canvas/usePanZoom.test.ts +36 -5
- package/src/canvas/usePanZoom.ts +20 -15
- package/src/canvas/worldSpec.test.ts +63 -0
- package/src/canvas/worldSpec.ts +48 -0
- package/src/chrome/builtins.test.ts +31 -1
- package/src/chrome/builtins.tsx +63 -19
- package/src/chrome/regions/PaletteRegion.test.tsx +22 -1
- package/src/chrome/regions/PaletteRegion.tsx +4 -0
- package/src/chrome/regions/SidebarRegion.tsx +37 -13
- package/src/chrome/regions/ToolbarRegion.tsx +2 -1
- package/src/chrome/regions/ViewportRegion.tsx +9 -1
- package/src/chrome/regions/regions.test.tsx +6 -1
- package/src/chrome/types.ts +20 -0
- package/src/config/builder.test.ts +7 -1
- package/src/config/builder.ts +1 -5
- package/src/config/resolve.test.ts +1 -2
- package/src/controls/ControlPanel.stories.tsx +41 -2
- package/src/controls/ControlPanel.test.tsx +69 -9
- package/src/controls/ControlPanel.tsx +106 -25
- package/src/dragdrop/DragDropRuntime.tsx +8 -2
- package/src/dragdrop/dragDrop.test.tsx +29 -2
- package/src/dragdrop/index.ts +11 -0
- package/src/index.test.ts +22 -0
- package/src/index.ts +136 -4
- package/src/instrument/SineWave.smoke.test.tsx +1 -0
- package/src/instrument/types.ts +24 -1
- package/src/lab/Lab.surface.test.tsx +162 -0
- package/src/lab/Lab.tsx +102 -17
- package/src/lab/Lab.undock.test.tsx +85 -0
- package/src/lab/LabFullChrome.stories.tsx +20 -0
- package/src/lab/LabShell.less +11 -0
- package/src/lab/Workspace.less +73 -0
- package/src/lab/Workspace.surface.test.tsx +2 -0
- package/src/lab/Workspace.tsx +97 -8
- package/src/lab/index.ts +1 -1
- package/src/lab/panelHost.ts +37 -0
- package/src/layers/AGENTS.md +1 -1
- package/src/layers/LayerList.tsx +1 -1
- package/src/loupe/AGENTS.md +49 -0
- package/src/loupe/CanvasLoupe.tsx +83 -0
- package/src/loupe/DomLoupe.tsx +62 -0
- package/src/loupe/Loupe.less +38 -0
- package/src/loupe/LoupeBubble.tsx +32 -0
- package/src/loupe/TrialLoupe.tsx +93 -0
- package/src/loupe/canvasLens.test.ts +205 -0
- package/src/loupe/canvasLens.ts +141 -0
- package/src/loupe/index.ts +19 -0
- package/src/loupe/types.test.ts +29 -0
- package/src/loupe/types.ts +90 -0
- package/src/loupe/useHostSize.ts +30 -0
- package/src/loupe/useLoupe.test.tsx +197 -0
- package/src/loupe/useLoupe.ts +189 -0
- package/src/passthrough/weasel-ui.ts +25 -17
- package/src/primitives/FloatingPanel.test.tsx +76 -1
- package/src/primitives/FloatingPanel.tsx +19 -3
- package/src/primitives/ScaleIndicator.test.tsx +7 -2
- package/src/primitives/Sidebar.less +18 -0
- package/src/primitives/Toolbar.less +8 -0
- package/src/primitives/Toolbar.tsx +5 -0
- package/src/primitives/useRovingTabIndex.test.ts +21 -0
- package/src/primitives/useRovingTabIndex.ts +26 -17
- package/src/state/document.test.ts +23 -0
- package/src/state/document.ts +14 -2
- package/src/state/index.ts +2 -0
- package/src/state/store.test.ts +24 -0
- package/src/state/store.ts +35 -1
- package/src/state/types.ts +11 -0
- package/src/state/undock.test.ts +40 -0
- package/src/state/undock.ts +39 -0
- package/src/styles.less +2 -4
- package/src/surface/SurfaceContext.ts +4 -0
- package/src/surface/index.ts +8 -3
- package/src/surface/useSurfaceTile.test.tsx +2 -0
- package/src/surface/useSurfaceTile.ts +7 -1
- package/src/surface/useTiledSurface.test.tsx +58 -0
- package/src/surface/useTiledSurface.ts +38 -3
- package/src/theme/Interstellar.stories.tsx +1 -1
- package/src/trial/Trial.annotations.persist.test.tsx +166 -0
- package/src/trial/Trial.annotations.test.tsx +121 -0
- package/src/trial/Trial.canvas.test.tsx +76 -1
- package/src/trial/Trial.config.test.tsx +3 -8
- package/src/trial/Trial.less +19 -0
- package/src/trial/Trial.loupe.test.tsx +128 -0
- package/src/trial/Trial.stories.tsx +1 -0
- package/src/trial/Trial.test.tsx +12 -0
- package/src/trial/Trial.trialId.test.tsx +36 -0
- package/src/trial/Trial.tsx +215 -40
- package/src/trial/TrialChrome.tsx +35 -0
- package/src/trial/UndockedSections.tsx +65 -0
- package/src/trial/trialOps.test.ts +21 -0
- package/src/trial/trialOps.ts +5 -3
- package/src/ui/layers/index.ts +5 -1
- package/dist/_dts/index-DgcNdEdh.d.ts +0 -314
- package/dist/chunk-4TMMVIDM.js.map +0 -1
- package/dist/chunk-CPUJ3QXL.js.map +0 -1
- package/dist/chunk-HFDVGF4X.js.map +0 -1
- package/dist/chunk-NS54R52R.js.map +0 -1
- package/dist/chunk-O2BULFHX.js +0 -152
- package/dist/chunk-O2BULFHX.js.map +0 -1
- package/dist/chunk-O7NVSCLN.js.map +0 -1
- package/dist/chunk-PO6L3NSH.js +0 -366
- package/dist/chunk-PO6L3NSH.js.map +0 -1
- package/dist/chunk-PQJ5B2U2.js +0 -27
- package/dist/chunk-PQJ5B2U2.js.map +0 -1
- package/dist/chunk-RVNN4CHQ.js.map +0 -1
- package/dist/chunk-TLGRYALP.js +0 -6151
- package/dist/chunk-TLGRYALP.js.map +0 -1
- package/dist/chunk-V7PRSIRQ.js.map +0 -1
- package/dist/chunk-Z6HJ5FEM.js.map +0 -1
- package/dist/chunk-ZZAYVX4X.js +0 -505
- package/dist/chunk-ZZAYVX4X.js.map +0 -1
- package/src/primitives/DragHandleGlyph.tsx +0 -29
- package/src/ui/format.test.ts +0 -33
- package/src/ui/format.ts +0 -30
- package/src/ui/layers/LayerStack.less +0 -143
- package/src/ui/layers/LayerStack.stories.tsx +0 -45
- package/src/ui/layers/LayerStack.test.tsx +0 -170
- package/src/ui/layers/LayerStack.tsx +0 -208
- package/src/ui/properties/CheckboxRow.stories.tsx +0 -27
- package/src/ui/properties/ColorRow.stories.tsx +0 -106
- package/src/ui/properties/CurveField.less +0 -48
- package/src/ui/properties/CurveField.stories.tsx +0 -20
- package/src/ui/properties/CurveField.test.tsx +0 -127
- package/src/ui/properties/CurveField.tsx +0 -184
- package/src/ui/properties/EffectCard.tsx +0 -324
- package/src/ui/properties/Gallery.stories.tsx +0 -178
- package/src/ui/properties/NumberRow.stories.tsx +0 -65
- package/src/ui/properties/PropertyGroup.less +0 -50
- package/src/ui/properties/PropertyGroup.stories.tsx +0 -34
- package/src/ui/properties/PropertyGroup.test.tsx +0 -33
- package/src/ui/properties/PropertyGroup.tsx +0 -45
- package/src/ui/properties/PropertyList.stories.tsx +0 -83
- package/src/ui/properties/PropertyPanel.less +0 -606
- package/src/ui/properties/PropertyPanel.stories.tsx +0 -190
- package/src/ui/properties/PropertyPanel.test.tsx +0 -193
- package/src/ui/properties/PropertyPanel.tsx +0 -469
- package/src/ui/properties/PropertyRow.stories.tsx +0 -53
- package/src/ui/properties/SelectRow.stories.tsx +0 -58
- package/src/ui/properties/SliderRow.stories.tsx +0 -74
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +0 -398
- package/src/ui/properties/TextRow.stories.tsx +0 -62
- package/src/ui/properties/ToggleRow.stories.tsx +0 -57
- package/src/ui/properties/index.ts +0 -39
- package/src/ui/properties/storyLayouts.tsx +0 -36
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
import { ComponentType, ReactNode, RefObject } from 'react';
|
|
2
|
+
import { R as ResolvedConfig, C as ConfigField, a as ConfigSchema } from './types-D6s4b7if.js';
|
|
3
|
+
import { c as SavedSnapshot } from './types-DYMaEvM5.js';
|
|
4
|
+
import { J as JobHandle, a as JobCapability } from './types-DJ79Tg5J.js';
|
|
5
|
+
import { S as Scene } from './types-C-gh9Ap-.js';
|
|
6
|
+
|
|
7
|
+
/** How an instrument's world sits on the canvas, independent of the camera.
|
|
8
|
+
* The defaults reproduce the convention labkit shipped before this existed:
|
|
9
|
+
* world (0,0) at the element's top-left, y growing downward. */
|
|
10
|
+
interface WorldSpec {
|
|
11
|
+
/** Where world (0,0) sits at `pan` zero, as a fraction of the viewport.
|
|
12
|
+
* `{x:0,y:0}` top-left (default); `{x:0.5,y:0.5}` centre. */
|
|
13
|
+
origin?: Point;
|
|
14
|
+
/** Which way the world y axis runs on screen. Default `'down'`. */
|
|
15
|
+
yAxis?: 'down' | 'up';
|
|
16
|
+
}
|
|
17
|
+
/** A viewport's CSS-pixel size. */
|
|
18
|
+
interface ViewportSize {
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
}
|
|
22
|
+
/** A `WorldSpec` resolved against a viewport: what the coordinate helpers,
|
|
23
|
+
* the camera and the wheel all read. */
|
|
24
|
+
interface WorldFrame {
|
|
25
|
+
/** Screen position of world (0,0) at `pan` zero. */
|
|
26
|
+
originPx: Point;
|
|
27
|
+
yDir: 1 | -1;
|
|
28
|
+
}
|
|
29
|
+
declare const DEFAULT_FRAME: WorldFrame;
|
|
30
|
+
declare function resolveFrame(spec: WorldSpec | undefined, size: ViewportSize): WorldFrame;
|
|
31
|
+
/** Put a 2D context into the instrument's world coordinates, so a layer's
|
|
32
|
+
* `draw` works in them. Must stay the exact inverse of `screenToWorld`. */
|
|
33
|
+
declare function applyCamera(ctx: CanvasRenderingContext2D, view: ViewTransform, frame?: WorldFrame): void;
|
|
34
|
+
|
|
35
|
+
/** A named position in a trial's chrome. Content is not a region — that is
|
|
36
|
+
* the instrument. */
|
|
37
|
+
type TrialRegion = 'titlebar' | 'toolbar' | 'palette' | 'sidebar' | 'viewport' | 'status';
|
|
38
|
+
/** An icon component taking a pixel size, as `@weasel-js/ui` glyphs do. */
|
|
39
|
+
type IconComponent = ComponentType<{
|
|
40
|
+
size?: number;
|
|
41
|
+
}>;
|
|
42
|
+
/** A button in the trial toolbar. */
|
|
43
|
+
interface ToolbarItem {
|
|
44
|
+
icon: IconComponent;
|
|
45
|
+
label: string;
|
|
46
|
+
/** Shown in the tooltip. Not bound here — the trial owns its keymap. */
|
|
47
|
+
shortcut?: string;
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
/** Reddens on hover. For actions that discard work. */
|
|
50
|
+
danger?: boolean;
|
|
51
|
+
/** A toggle rather than a command: renders `aria-pressed` and reads as held
|
|
52
|
+
* down while true. */
|
|
53
|
+
pressed?: boolean;
|
|
54
|
+
/** Render the label beside the glyph rather than only in the tooltip. */
|
|
55
|
+
showLabel?: boolean;
|
|
56
|
+
onActivate: () => void;
|
|
57
|
+
}
|
|
58
|
+
/** A selectable tool in the palette region. */
|
|
59
|
+
interface ToolItem {
|
|
60
|
+
icon: IconComponent;
|
|
61
|
+
label: string;
|
|
62
|
+
shortcut?: string;
|
|
63
|
+
disabled?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** A titled block in the sidebar. */
|
|
66
|
+
interface SidebarSection {
|
|
67
|
+
title: string;
|
|
68
|
+
/** Starts collapsed. The open/closed state itself is the region's. */
|
|
69
|
+
defaultCollapsed?: boolean;
|
|
70
|
+
/** Offer the tear-out control. On by default; a section that only makes
|
|
71
|
+
* sense beside its trial sets this false. */
|
|
72
|
+
undockable?: boolean;
|
|
73
|
+
/** Where the tear-out control sends it. Default `'tile'`. */
|
|
74
|
+
undockAs?: 'tile' | 'floating';
|
|
75
|
+
body: ReactNode;
|
|
76
|
+
}
|
|
77
|
+
/** A control acting on the view of the trial, not on the trial. */
|
|
78
|
+
interface ViewportControl {
|
|
79
|
+
icon: IconComponent;
|
|
80
|
+
label: string;
|
|
81
|
+
disabled?: boolean;
|
|
82
|
+
onActivate: () => void;
|
|
83
|
+
}
|
|
84
|
+
/** A readout in the status bar. */
|
|
85
|
+
interface StatusReadout {
|
|
86
|
+
/** Short enough for a status bar. Rendered as text. */
|
|
87
|
+
text: string;
|
|
88
|
+
/** Tooltip. */
|
|
89
|
+
title?: string;
|
|
90
|
+
}
|
|
91
|
+
/** What every contribution shares. */
|
|
92
|
+
interface ContributionBase {
|
|
93
|
+
id: string;
|
|
94
|
+
/** Groups sort by first appearance; items sort within a group by
|
|
95
|
+
* declaration order. Contributions with no group sort after grouped ones. */
|
|
96
|
+
group?: string;
|
|
97
|
+
/** Pushes this contribution, and its group, to the far end of the region. */
|
|
98
|
+
end?: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A contribution is data the chrome renders, keyed to a region. Supplying
|
|
102
|
+
* `render` instead of `item` opts out of the chrome's layout — deliberate,
|
|
103
|
+
* and visible in the declaration.
|
|
104
|
+
*/
|
|
105
|
+
type TrialContribution = (ContributionBase & {
|
|
106
|
+
region: 'titlebar';
|
|
107
|
+
item: ToolbarItem;
|
|
108
|
+
render?: never;
|
|
109
|
+
}) | (ContributionBase & {
|
|
110
|
+
region: 'toolbar';
|
|
111
|
+
item: ToolbarItem;
|
|
112
|
+
render?: never;
|
|
113
|
+
}) | (ContributionBase & {
|
|
114
|
+
region: 'palette';
|
|
115
|
+
item: ToolItem;
|
|
116
|
+
render?: never;
|
|
117
|
+
}) | (ContributionBase & {
|
|
118
|
+
region: 'sidebar';
|
|
119
|
+
item: SidebarSection;
|
|
120
|
+
render?: never;
|
|
121
|
+
}) | (ContributionBase & {
|
|
122
|
+
region: 'viewport';
|
|
123
|
+
item: ViewportControl;
|
|
124
|
+
render?: never;
|
|
125
|
+
}) | (ContributionBase & {
|
|
126
|
+
region: 'status';
|
|
127
|
+
item: StatusReadout;
|
|
128
|
+
render?: never;
|
|
129
|
+
}) | (ContributionBase & {
|
|
130
|
+
region: TrialRegion;
|
|
131
|
+
item?: never;
|
|
132
|
+
render: (ctx: TrialChromeContext) => ReactNode;
|
|
133
|
+
});
|
|
134
|
+
/**
|
|
135
|
+
* Everything a contribution can read about the trial it is being rendered
|
|
136
|
+
* into. Replaces the three separate slot contexts, which each carried a
|
|
137
|
+
* hand-picked subset.
|
|
138
|
+
*/
|
|
139
|
+
interface TrialChromeContext {
|
|
140
|
+
trialId: string;
|
|
141
|
+
instrumentName: string;
|
|
142
|
+
isLastTrial: boolean;
|
|
143
|
+
/** Null when the trial holds a view that is not the 2D one. */
|
|
144
|
+
zoom: number | null;
|
|
145
|
+
setZoom: (z: number) => void;
|
|
146
|
+
canUndo: boolean;
|
|
147
|
+
canRedo: boolean;
|
|
148
|
+
undo: () => void;
|
|
149
|
+
redo: () => void;
|
|
150
|
+
/** Whether the trial's loupe is turned on. False for an instrument that
|
|
151
|
+
* declares none, whose chrome offers no way to turn one on. */
|
|
152
|
+
loupeOn: boolean;
|
|
153
|
+
toggleLoupe: () => void;
|
|
154
|
+
/** The instrument's controls, resolved against the lab's rules. Always
|
|
155
|
+
* populated: a legacy `configSchema()` is adapted into the same shape. */
|
|
156
|
+
configSchema: ResolvedConfig;
|
|
157
|
+
/** @deprecated Read `configSchema`. Empty for an instrument declaring
|
|
158
|
+
* `config`, since a builder schema has no `ConfigField[]` form. */
|
|
159
|
+
configFields: ConfigField[];
|
|
160
|
+
config: unknown;
|
|
161
|
+
setConfig: (key: string, value: unknown) => void;
|
|
162
|
+
/** Section ids this trial currently has torn out of its sidebar. */
|
|
163
|
+
undockedPanels: readonly string[];
|
|
164
|
+
/** Tear a sidebar section out into the workspace. */
|
|
165
|
+
undockPanel: (sectionId: string, as?: 'tile' | 'floating') => void;
|
|
166
|
+
/** Put a torn-out section back in the sidebar. */
|
|
167
|
+
dockPanel: (sectionId: string) => void;
|
|
168
|
+
savedSnapshots: SavedSnapshot[];
|
|
169
|
+
saveSnapshot: (name?: string) => void;
|
|
170
|
+
loadSnapshot: (snapshotId: string) => void;
|
|
171
|
+
clone: () => void;
|
|
172
|
+
reset: () => void;
|
|
173
|
+
close: () => void;
|
|
174
|
+
/** Resolved active tool: the trial's slot, or the lab's when the trial has
|
|
175
|
+
* none. Null when neither holds one. */
|
|
176
|
+
activeToolId: string | null;
|
|
177
|
+
setActiveTool: (id: string) => void;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** How a loupe magnifies. `'vector'` re-renders the source through a zoomed-in
|
|
181
|
+
* view, so content stays sharp at any factor; `'pixel'` blows up the actual
|
|
182
|
+
* pixels the surface presented. */
|
|
183
|
+
type LoupeMode = 'vector' | 'pixel';
|
|
184
|
+
|
|
185
|
+
/** What a DOM instrument's loupe `render` is handed: the same data its own
|
|
186
|
+
* `render` gets, and the camera to draw it through. */
|
|
187
|
+
interface LoupeRenderArgs<TS = unknown, TC = unknown> {
|
|
188
|
+
state: TS;
|
|
189
|
+
config: TC;
|
|
190
|
+
/** The trial's own camera composed with the magnification, about the aimed
|
|
191
|
+
* point — so drawing the same content through it magnifies in place. */
|
|
192
|
+
view: ViewTransform;
|
|
193
|
+
/** The magnification on its own, for whatever must not scale with the
|
|
194
|
+
* camera. */
|
|
195
|
+
factor: number;
|
|
196
|
+
mode: LoupeMode;
|
|
197
|
+
/** The size of the viewport `view` is written for: the trial's content well,
|
|
198
|
+
* not the lens. The lens shows a circle cut out of it. */
|
|
199
|
+
size: ViewportSize;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Declares that an instrument can be magnified.
|
|
203
|
+
*
|
|
204
|
+
* With no `render`, the loupe re-draws the instrument's canvas layers through a
|
|
205
|
+
* zoomed camera, so it stays sharp at any factor. An instrument whose content is
|
|
206
|
+
* DOM supplies `render` instead: given a camera, draw me again.
|
|
207
|
+
*/
|
|
208
|
+
interface LoupeCapability<TS = unknown, TC = unknown> {
|
|
209
|
+
render?: (args: LoupeRenderArgs<TS, TC>) => ReactNode;
|
|
210
|
+
/** Opening magnification, clamped to the bounds below. Default 6. */
|
|
211
|
+
factor?: number;
|
|
212
|
+
/** What the wheel clamps to. Defaults 2 and 32. */
|
|
213
|
+
minFactor?: number;
|
|
214
|
+
maxFactor?: number;
|
|
215
|
+
/** `'vector'` (default) re-renders the content magnified; `'pixel'` blows up
|
|
216
|
+
* the pixels the instrument presented. A `render` loupe is always vector —
|
|
217
|
+
* DOM has no framebuffer to enlarge. */
|
|
218
|
+
mode?: LoupeMode;
|
|
219
|
+
/** Lens diameter in CSS px. Default 200. */
|
|
220
|
+
diameter?: number;
|
|
221
|
+
/** Held for a momentary peek while the loupe is off. Default `'Alt'`; `null`
|
|
222
|
+
* turns hold-to-peek off. Matched against `KeyboardEvent.key`. */
|
|
223
|
+
peekKey?: string | null;
|
|
224
|
+
/** Called with the colour under the aim, wherever the surface can say. The
|
|
225
|
+
* canvas painter reads it back; a DOM loupe has no pixels to sample. */
|
|
226
|
+
onColorChange?: (hex: string) => void;
|
|
227
|
+
}
|
|
228
|
+
/** An instrument's loupe declaration. `true` takes every default. */
|
|
229
|
+
type LoupeDeclaration<TS = unknown, TC = unknown> = true | LoupeCapability<TS, TC>;
|
|
230
|
+
/** A {@link LoupeCapability} with every default filled in. */
|
|
231
|
+
interface ResolvedLoupe<TS = unknown, TC = unknown> {
|
|
232
|
+
render?: (args: LoupeRenderArgs<TS, TC>) => ReactNode;
|
|
233
|
+
onColorChange?: (hex: string) => void;
|
|
234
|
+
factor: number;
|
|
235
|
+
minFactor: number;
|
|
236
|
+
maxFactor: number;
|
|
237
|
+
mode: LoupeMode;
|
|
238
|
+
diameter: number;
|
|
239
|
+
peekKey: string | null;
|
|
240
|
+
}
|
|
241
|
+
declare const LOUPE_DEFAULTS: {
|
|
242
|
+
readonly factor: 6;
|
|
243
|
+
readonly minFactor: 2;
|
|
244
|
+
readonly maxFactor: 32;
|
|
245
|
+
readonly mode: "vector";
|
|
246
|
+
readonly diameter: 200;
|
|
247
|
+
readonly peekKey: "Alt";
|
|
248
|
+
};
|
|
249
|
+
declare function resolveLoupe<TS, TC>(declared: LoupeDeclaration<TS, TC>): ResolvedLoupe<TS, TC>;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* A tool a trial can be in. labkit's own — core's `ToolsApi` carries hotkey
|
|
253
|
+
* slots, ambient tools, eligibility tiers and canvas overlay layers, all bound
|
|
254
|
+
* to the gesture dispatcher, and a labkit instrument is an arbitrary canvas or
|
|
255
|
+
* DOM tree rather than a weasel scene.
|
|
256
|
+
*/
|
|
257
|
+
interface TrialTool {
|
|
258
|
+
id: string;
|
|
259
|
+
label: string;
|
|
260
|
+
icon: IconComponent;
|
|
261
|
+
/** Shown in the tooltip. Not bound here — the instrument owns its keymap. */
|
|
262
|
+
shortcut?: string;
|
|
263
|
+
/** Presentation grouping in the palette. Ungrouped tools sort after grouped. */
|
|
264
|
+
group?: string;
|
|
265
|
+
}
|
|
266
|
+
/** What an instrument declares to get a palette region. */
|
|
267
|
+
interface ToolCapability {
|
|
268
|
+
tools: TrialTool[];
|
|
269
|
+
/** Which tool a fresh trial starts in. Defaults to the first. */
|
|
270
|
+
initial?: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** What an instrument's `render` is handed: its state and config, the setters
|
|
274
|
+
* for both, the trial it is mounted in, and a way to emit named events. */
|
|
275
|
+
interface RenderContext<TS = unknown, TC = unknown> {
|
|
276
|
+
state: TS;
|
|
277
|
+
config: TC;
|
|
278
|
+
setState: (next: TS | ((prev: TS) => TS)) => void;
|
|
279
|
+
setConfig: (key: keyof TC, value: unknown) => void;
|
|
280
|
+
trial: {
|
|
281
|
+
id: string;
|
|
282
|
+
/** The trial's view, in whatever shape this instrument chose. labkit persists
|
|
283
|
+
* it and restores it on Reset without ever reading into it. */
|
|
284
|
+
view: unknown;
|
|
285
|
+
setView: (next: unknown) => void;
|
|
286
|
+
/** 2D convenience over `view`. Reads 1 and writes nothing when the trial holds
|
|
287
|
+
* a view that is not the 2D one. */
|
|
288
|
+
zoom: number;
|
|
289
|
+
setZoom: (z: number) => void;
|
|
290
|
+
/** Resolved active tool: this trial's slot, or the lab's. Null when neither
|
|
291
|
+
* holds one. */
|
|
292
|
+
activeToolId: string | null;
|
|
293
|
+
/** The canvas layers currently shown, in declaration order. labkit skips a
|
|
294
|
+
* hidden layer's `draw`, so this is the only way to know what was
|
|
295
|
+
* painted — a legend listing rows the view did not draw needs it. */
|
|
296
|
+
visibleLayers: readonly string[];
|
|
297
|
+
};
|
|
298
|
+
emit: (event: string) => void;
|
|
299
|
+
/** Present only when the instrument declares a `job`. */
|
|
300
|
+
job?: JobHandle;
|
|
301
|
+
}
|
|
302
|
+
/** One 2D canvas layer of an instrument, drawn in declaration order.
|
|
303
|
+
*
|
|
304
|
+
* `draw` is called with the camera already applied, so it works in world
|
|
305
|
+
* coordinates. `zoom` is passed for the things that must not scale with it —
|
|
306
|
+
* set `ctx.lineWidth = 1 / zoom` to keep a hairline hairline. */
|
|
307
|
+
interface CanvasLayer<TS = unknown, TC = unknown> {
|
|
308
|
+
id: string;
|
|
309
|
+
draw: (ctx: CanvasRenderingContext2D, args: {
|
|
310
|
+
state: TS;
|
|
311
|
+
config: TC;
|
|
312
|
+
zoom: number;
|
|
313
|
+
}) => void;
|
|
314
|
+
}
|
|
315
|
+
/** Declares that an instrument draws to a canvas: its layers, and where the
|
|
316
|
+
* view starts. */
|
|
317
|
+
interface CanvasCapability<TS = unknown, TC = unknown> {
|
|
318
|
+
layers: CanvasLayer<TS, TC>[];
|
|
319
|
+
/** The coordinate system this instrument's world is in. Omitted, world (0,0)
|
|
320
|
+
* sits at the canvas top-left with y running down — what labkit assumed
|
|
321
|
+
* before an instrument could say otherwise. */
|
|
322
|
+
worldSpec?: WorldSpec;
|
|
323
|
+
/** Where the view starts. A function is called once the canvas knows its
|
|
324
|
+
* size, so an instrument can frame content it can only place in terms of
|
|
325
|
+
* the viewport; until then the trial's view is `null`. */
|
|
326
|
+
initialView?: ViewTransform | ((size: ViewportSize) => ViewTransform);
|
|
327
|
+
/** Widens `usePanZoom`'s default clamp; the opening zoom stays reachable
|
|
328
|
+
* regardless of these. */
|
|
329
|
+
minZoom?: number;
|
|
330
|
+
maxZoom?: number;
|
|
331
|
+
}
|
|
332
|
+
/** Declares which of an instrument's layers the trial should offer
|
|
333
|
+
* show/hide controls for. */
|
|
334
|
+
interface LayerCapability {
|
|
335
|
+
/** In list order. A bare string is a layer id that doubles as its own label;
|
|
336
|
+
* give a descriptor instead to label a layer or mark it `alwaysOn`. */
|
|
337
|
+
ids: readonly (string | LayerDescriptor)[];
|
|
338
|
+
}
|
|
339
|
+
/** Declares that an instrument accepts items dragged from a palette: what the
|
|
340
|
+
* palette offers, what a drop does to the state, and — optionally — live
|
|
341
|
+
* feedback during the drag and the ability to drag existing items back out. */
|
|
342
|
+
interface DragDropCapability<TS = unknown, TC = unknown> {
|
|
343
|
+
palette: PaletteItem[] | ((state: TS, config: TC) => PaletteItem[]);
|
|
344
|
+
onDrop: (worldPos: Point, item: PaletteItem, state: TS, config: TC) => TS;
|
|
345
|
+
onDragOver?: (worldPos: Point, item: PaletteItem, state: TS, config: TC) => DragFeedback | null;
|
|
346
|
+
pickUp?: (hit: HitResult, state: TS, config: TC) => {
|
|
347
|
+
item: PaletteItem;
|
|
348
|
+
state: TS;
|
|
349
|
+
} | null;
|
|
350
|
+
}
|
|
351
|
+
/** Declares that an instrument's state is undoable: which emitted events
|
|
352
|
+
* snapshot it, and how many snapshots to keep. */
|
|
353
|
+
interface UndoCapability {
|
|
354
|
+
snapshotOn?: string[];
|
|
355
|
+
maxDepth?: number;
|
|
356
|
+
}
|
|
357
|
+
/** The name of an event an instrument emits through `RenderContext.emit`. */
|
|
358
|
+
type SystemEvent = string;
|
|
359
|
+
/** A point in world coordinates. */
|
|
360
|
+
type Point = {
|
|
361
|
+
x: number;
|
|
362
|
+
y: number;
|
|
363
|
+
};
|
|
364
|
+
/** What a hit-test found, and where. */
|
|
365
|
+
type HitResult = {
|
|
366
|
+
hit: boolean;
|
|
367
|
+
layerId?: string;
|
|
368
|
+
pointId?: string;
|
|
369
|
+
};
|
|
370
|
+
/** A trial's camera. */
|
|
371
|
+
type ViewTransform = {
|
|
372
|
+
zoom: number;
|
|
373
|
+
pan: Point;
|
|
374
|
+
};
|
|
375
|
+
/** A layer as the layer list shows it. `alwaysOn` layers cannot be hidden. */
|
|
376
|
+
type LayerDescriptor = {
|
|
377
|
+
id: string;
|
|
378
|
+
label: string;
|
|
379
|
+
alwaysOn?: boolean;
|
|
380
|
+
};
|
|
381
|
+
/** One draggable entry in an instrument's palette. */
|
|
382
|
+
type PaletteItem = {
|
|
383
|
+
id: string;
|
|
384
|
+
label: string;
|
|
385
|
+
data?: unknown;
|
|
386
|
+
};
|
|
387
|
+
/** Whether a drop would be accepted at the current position, and why not if
|
|
388
|
+
* it would not. */
|
|
389
|
+
type DragFeedback = {
|
|
390
|
+
ok: boolean;
|
|
391
|
+
reason?: string;
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* An instrument: one self-contained interactive experiment a lab can host.
|
|
395
|
+
*
|
|
396
|
+
* It owns two pieces of data — `config`, the settings the control panel edits,
|
|
397
|
+
* and `state`, what the experiment is currently doing — and renders from both.
|
|
398
|
+
* The optional capability fields declare what else it wants from the runtime:
|
|
399
|
+
* a canvas, a layer list, palette drag-and-drop, undo. Declaring a capability
|
|
400
|
+
* is what makes the trial provide the corresponding chrome.
|
|
401
|
+
*/
|
|
402
|
+
interface Instrument<TS = unknown, TC = unknown, TItem = unknown> {
|
|
403
|
+
name: string;
|
|
404
|
+
defaultConfig: () => TC;
|
|
405
|
+
initialState: (config: TC) => TS;
|
|
406
|
+
/** The instrument's config, declared once: values, types and controls.
|
|
407
|
+
* Supplying this makes `defaultConfig` optional — `defineInstrument`
|
|
408
|
+
* synthesizes it. Prefer it over `defaultConfig` + `configSchema`. */
|
|
409
|
+
config?: ConfigSchema<TC>;
|
|
410
|
+
/** @deprecated Declare `config` instead; this repeats what `TC` already
|
|
411
|
+
* says and nothing holds the two to one answer. */
|
|
412
|
+
configSchema?: () => ConfigField[];
|
|
413
|
+
/** The instrument's DOM. With `canvas`, this renders as an overlay above the
|
|
414
|
+
* layers rather than instead of them; return `null` for canvas only. */
|
|
415
|
+
render: (ctx: RenderContext<TS, TC>) => ReactNode;
|
|
416
|
+
onConfigChange?: (config: TC, prev: TC, state: TS) => TS;
|
|
417
|
+
serialize?: (state: TS) => unknown;
|
|
418
|
+
deserialize?: (data: unknown, config: TC) => TS;
|
|
419
|
+
canvas?: CanvasCapability<TS, TC>;
|
|
420
|
+
layers?: LayerCapability;
|
|
421
|
+
dragDrop?: DragDropCapability<TS, TC>;
|
|
422
|
+
undo?: UndoCapability;
|
|
423
|
+
/** Tools this instrument offers. Declaring them gives the trial a palette
|
|
424
|
+
* region and its own tool slot. */
|
|
425
|
+
tools?: ToolCapability;
|
|
426
|
+
/** Regions of this instrument that accept marks, and optionally what a mark
|
|
427
|
+
* is allowed to mean. Declaring it is what makes the trial provide the
|
|
428
|
+
* annotation overlay and its chrome. */
|
|
429
|
+
annotations?: AnnotationsCapability<TS, TC>;
|
|
430
|
+
/** Magnification. `true` takes every default and re-draws the instrument's
|
|
431
|
+
* canvas layers through a zoomed camera; an instrument whose content is DOM
|
|
432
|
+
* gives a `render` that draws it again at a camera it is handed. A function
|
|
433
|
+
* is re-read as the config changes, so a setting can drive the lens. */
|
|
434
|
+
loupe?: LoupeDeclaration<TS, TC> | ((config: TC) => LoupeDeclaration<TS, TC>);
|
|
435
|
+
/** Chrome this instrument contributes beyond what its capabilities imply. */
|
|
436
|
+
chrome?: TrialContribution[];
|
|
437
|
+
/** Work too slow to do during a render. The runtime starts it, aborts it on
|
|
438
|
+
* unmount and on a `key` change, and renders progress into the trial. */
|
|
439
|
+
job?: JobCapability<TS, TC, TItem>;
|
|
440
|
+
}
|
|
441
|
+
/** Instruments as a lab receives them. `any` rather than `unknown` because
|
|
442
|
+
* parameter contravariance keeps a `defineInstrument<TS, TC>` result out of
|
|
443
|
+
* an `Instrument<unknown, unknown>[]`; it is contained to this alias. */
|
|
444
|
+
type InstrumentList = readonly Instrument<any, any, any>[];
|
|
445
|
+
|
|
446
|
+
/** A mark's box in its target's world. Matches weasel's default `RectPose`. */
|
|
447
|
+
type MarkPose = WorldRect;
|
|
448
|
+
/** The scene one target's marks live in. */
|
|
449
|
+
type MarkScene = Scene<AnnotationData, 'marks', MarkPose>;
|
|
450
|
+
/** A mark scene: one layer, because marks do not stack in tiers. */
|
|
451
|
+
declare function createAnnotationScene(): MarkScene;
|
|
452
|
+
interface AnnotationStoreOptions {
|
|
453
|
+
/** Re-read on every call, so a target resizing or gaining a dependency takes
|
|
454
|
+
* effect without rebuilding the store. */
|
|
455
|
+
targets: () => readonly AnnotationTargetInfo[];
|
|
456
|
+
/** Serialized scenes from a previous `toJSON`, keyed by target. */
|
|
457
|
+
restore?: Readonly<Record<string, unknown>>;
|
|
458
|
+
/** The instrument's vocabulary, so an export draws a mark in the colour its
|
|
459
|
+
* status gives it. */
|
|
460
|
+
meaning?: AnnotationMeaning;
|
|
461
|
+
/** The trial's live config. A getter for the same reason `targets` is: the
|
|
462
|
+
* store is built once and the config changes under it. */
|
|
463
|
+
config?: () => unknown;
|
|
464
|
+
/** Notified after every finished export. */
|
|
465
|
+
onCapture?: (result: CaptureResult) => void;
|
|
466
|
+
}
|
|
467
|
+
/** Everything a capture needs that is not the store's own state. Split out so
|
|
468
|
+
* `capture.ts` takes data rather than reaching back into the store. */
|
|
469
|
+
interface CaptureDeps {
|
|
470
|
+
scene: MarkScene;
|
|
471
|
+
target: AnnotationTargetInfo;
|
|
472
|
+
meaning?: AnnotationMeaning;
|
|
473
|
+
config: unknown;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* A facade over one weasel scene per target: the scenes are the truth, this
|
|
477
|
+
* answers questions about them. Everything crossing this boundary is in
|
|
478
|
+
* fractions of a target's content box; the scenes hold world units.
|
|
479
|
+
*
|
|
480
|
+
* A scene per target rather than one scene with a `target` field, because a
|
|
481
|
+
* pane's hit-test, marquee and paint all walk the whole scene it is given and
|
|
482
|
+
* take no filter — one shared scene puts every other pane's marks under the
|
|
483
|
+
* pointer.
|
|
484
|
+
*/
|
|
485
|
+
declare function createAnnotationStore(opts: AnnotationStoreOptions): AnnotationsApi;
|
|
486
|
+
/** Rebuild a store from what `toJSON` wrote. Unknown or future versions give
|
|
487
|
+
* an empty store rather than throwing: a lab that cannot read its marks
|
|
488
|
+
* should still open. */
|
|
489
|
+
declare function annotationsFromJSON(raw: unknown, targets: () => readonly AnnotationTargetInfo[], rest?: Omit<AnnotationStoreOptions, 'targets' | 'restore'>): AnnotationsApi;
|
|
490
|
+
|
|
491
|
+
/** A point in fractions of a target's content box. */
|
|
492
|
+
interface FracPoint {
|
|
493
|
+
x: number;
|
|
494
|
+
y: number;
|
|
495
|
+
}
|
|
496
|
+
/** A rectangle in fractions of a target's content box.
|
|
497
|
+
*
|
|
498
|
+
* Fractions rather than pixels because a mark must survive a change of render
|
|
499
|
+
* resolution: the picture gets bigger, the mark stays on the same feature. */
|
|
500
|
+
interface FracRect {
|
|
501
|
+
x: number;
|
|
502
|
+
y: number;
|
|
503
|
+
w: number;
|
|
504
|
+
h: number;
|
|
505
|
+
}
|
|
506
|
+
/** What shape a mark is. The kinds map onto weasel's own tools; an arrow is a
|
|
507
|
+
* line carrying an end marker, not a separate geometry. */
|
|
508
|
+
type AnnotationKind = 'stroke' | 'line' | 'arrow' | 'rect' | 'ellipse' | 'text';
|
|
509
|
+
/** One selectable status in an instrument's meaning tier. */
|
|
510
|
+
interface AnnotationStatus {
|
|
511
|
+
id: string;
|
|
512
|
+
label: string;
|
|
513
|
+
/** What a mark in this status is drawn in. Omitted, it takes the default
|
|
514
|
+
* mark colour — a status is allowed to be a label and nothing more. */
|
|
515
|
+
color?: string;
|
|
516
|
+
}
|
|
517
|
+
/** The optional meaning tier: what a mark means, as opposed to where it is.
|
|
518
|
+
* An instrument declaring it gets labkit's own chrome for the vocabulary;
|
|
519
|
+
* one that omits it keeps `meta` and owns meaning entirely. */
|
|
520
|
+
interface AnnotationMeaning {
|
|
521
|
+
statuses?: readonly AnnotationStatus[];
|
|
522
|
+
}
|
|
523
|
+
/** What labkit keeps in a mark's scene-node data. */
|
|
524
|
+
interface AnnotationData {
|
|
525
|
+
target: string;
|
|
526
|
+
kind: AnnotationKind;
|
|
527
|
+
/** Vertices for the kinds a bounding box cannot describe — a line's two
|
|
528
|
+
* ends, a stroke's path. In fractions, like the bounds. */
|
|
529
|
+
points?: readonly FracPoint[];
|
|
530
|
+
title?: string;
|
|
531
|
+
status?: string;
|
|
532
|
+
tags?: readonly string[];
|
|
533
|
+
meta?: unknown;
|
|
534
|
+
/** The target's `positionDependsOn` values when the mark was made. Compared
|
|
535
|
+
* against the live config to answer whether the mark still describes the
|
|
536
|
+
* same picture. */
|
|
537
|
+
seen?: Readonly<Record<string, unknown>>;
|
|
538
|
+
}
|
|
539
|
+
/** A mark, as the store reports it. `id` is `<target>/<node>`: one scene per
|
|
540
|
+
* target means a node id is only unique within one. */
|
|
541
|
+
interface Annotation extends AnnotationData {
|
|
542
|
+
id: string;
|
|
543
|
+
/** Bounds in fractions of the target's content box. */
|
|
544
|
+
frac: FracRect;
|
|
545
|
+
}
|
|
546
|
+
/** A new mark, before the store gives it an id and dates it. */
|
|
547
|
+
interface AnnotationInit {
|
|
548
|
+
target: string;
|
|
549
|
+
kind: AnnotationKind;
|
|
550
|
+
frac: FracRect;
|
|
551
|
+
points?: readonly FracPoint[];
|
|
552
|
+
title?: string;
|
|
553
|
+
status?: string;
|
|
554
|
+
tags?: readonly string[];
|
|
555
|
+
meta?: unknown;
|
|
556
|
+
}
|
|
557
|
+
/** Filters, ANDed. A mark matches `tags` when it carries every tag listed. */
|
|
558
|
+
interface AnnotationQuery {
|
|
559
|
+
target?: string;
|
|
560
|
+
kind?: AnnotationKind;
|
|
561
|
+
status?: string;
|
|
562
|
+
tags?: readonly string[];
|
|
563
|
+
where?: (a: Annotation) => boolean;
|
|
564
|
+
}
|
|
565
|
+
/** What a mark's meaning can be patched to. Geometry moves through `frac` /
|
|
566
|
+
* `points`; everything else is the meaning tier. */
|
|
567
|
+
type AnnotationPatch = Partial<Pick<Annotation, 'frac' | 'points' | 'title' | 'status' | 'tags' | 'meta'>>;
|
|
568
|
+
/** A target's own picture, handed over for an export to draw marks on top of.
|
|
569
|
+
* labkit cannot rasterize it: it is the consumer's DOM. `svg` is the one that
|
|
570
|
+
* keeps the export vector all the way through. */
|
|
571
|
+
type CaptureSource = {
|
|
572
|
+
kind: 'svg';
|
|
573
|
+
markup: string;
|
|
574
|
+
} | {
|
|
575
|
+
kind: 'image';
|
|
576
|
+
src: string;
|
|
577
|
+
} | {
|
|
578
|
+
kind: 'canvas';
|
|
579
|
+
canvas: HTMLCanvasElement;
|
|
580
|
+
};
|
|
581
|
+
/** What an export produces, and at what resolution. */
|
|
582
|
+
interface CaptureOptions {
|
|
583
|
+
/** `png` rasterizes; `svg` stays vector, which needs the base to be one too
|
|
584
|
+
* — a raster base embeds as an `<image>`. Default `png`. */
|
|
585
|
+
format?: 'png' | 'svg';
|
|
586
|
+
/** Output pixels per unit of the target's content box. Default 2. */
|
|
587
|
+
scale?: number;
|
|
588
|
+
}
|
|
589
|
+
/** A finished export. `width`/`height` are the output's, not the content
|
|
590
|
+
* box's — `content × scale`. */
|
|
591
|
+
interface CaptureResult {
|
|
592
|
+
target: string;
|
|
593
|
+
blob: Blob;
|
|
594
|
+
format: 'png' | 'svg';
|
|
595
|
+
width: number;
|
|
596
|
+
height: number;
|
|
597
|
+
}
|
|
598
|
+
/** What the store needs to know about a target: enough to convert a position
|
|
599
|
+
* and to date a mark. `AnnotationTarget` adds what only the overlay reads. */
|
|
600
|
+
interface AnnotationTargetInfo {
|
|
601
|
+
id: string;
|
|
602
|
+
/** Intrinsic content size in CSS pixels at zoom 1 — the box fractions are
|
|
603
|
+
* fractions *of*, and the target's world. */
|
|
604
|
+
content: {
|
|
605
|
+
w: number;
|
|
606
|
+
h: number;
|
|
607
|
+
};
|
|
608
|
+
/** Config keys whose change means a stored position no longer refers to the
|
|
609
|
+
* same picture. labkit snapshots and compares them without knowing what any
|
|
610
|
+
* of them mean. */
|
|
611
|
+
positionDependsOn?: readonly string[];
|
|
612
|
+
/** The target's own picture, for an export to draw marks over. A target
|
|
613
|
+
* declaring none exports its marks on transparency, which fails visibly
|
|
614
|
+
* rather than producing a blank brick.
|
|
615
|
+
*
|
|
616
|
+
* Here rather than on `AnnotationTarget` because the store is what calls
|
|
617
|
+
* it: `ref` and `view` are the React-shaped half only the overlay reads. */
|
|
618
|
+
base?: () => CaptureSource | Promise<CaptureSource>;
|
|
619
|
+
}
|
|
620
|
+
/** One region of an instrument that accepts marks. */
|
|
621
|
+
interface AnnotationTarget extends AnnotationTargetInfo {
|
|
622
|
+
/** The element the overlay tracks and takes input from. */
|
|
623
|
+
ref: RefObject<HTMLElement | null>;
|
|
624
|
+
/** The pane's camera, mirrored so marks pan and zoom with what they mark. */
|
|
625
|
+
view?: ViewTransform;
|
|
626
|
+
}
|
|
627
|
+
/** Where an instrument keeps its own marks. Declaring this means labkit never
|
|
628
|
+
* writes its trial slot — for an instrument whose marks belong in a format it
|
|
629
|
+
* already owns. Both halves are called outside React; `save` is already
|
|
630
|
+
* debounced by the time it arrives. */
|
|
631
|
+
interface AnnotationStorage {
|
|
632
|
+
load: () => SerializedAnnotations | null | undefined;
|
|
633
|
+
save: (doc: SerializedAnnotations) => void;
|
|
634
|
+
}
|
|
635
|
+
/** Declares that an instrument accepts marks: which regions take them,
|
|
636
|
+
* optionally what a mark is allowed to mean, and optionally where they live. */
|
|
637
|
+
interface AnnotationsCapability<TS = unknown, TC = unknown> {
|
|
638
|
+
targets: (state: TS, config: TC) => readonly AnnotationTarget[];
|
|
639
|
+
meaning?: AnnotationMeaning;
|
|
640
|
+
storage?: AnnotationStorage;
|
|
641
|
+
/** Fires after every finished export, labkit's own chrome included. A
|
|
642
|
+
* notification, not an interception: a host wanting its own flow calls
|
|
643
|
+
* `capture()` from its own UI, which is the surface the chrome uses. */
|
|
644
|
+
onCapture?: (result: CaptureResult) => void;
|
|
645
|
+
}
|
|
646
|
+
/** A persisted mark set. Versioned by this arc rather than by labkit's
|
|
647
|
+
* document migrations, which only ever reach top-level document sections and
|
|
648
|
+
* never into a trial's state. */
|
|
649
|
+
interface SerializedAnnotations {
|
|
650
|
+
version: 1;
|
|
651
|
+
/** One serialized scene per target that has ever held a mark. */
|
|
652
|
+
scenes: Record<string, unknown>;
|
|
653
|
+
}
|
|
654
|
+
/** Everything a host can ask or tell labkit about the marks on its targets. */
|
|
655
|
+
interface AnnotationsApi {
|
|
656
|
+
/** The scene a target's marks live in, created on first ask. One per target
|
|
657
|
+
* because a pane's hit-test, marquee and paint walk the whole scene they
|
|
658
|
+
* are given: a shared one would put a neighbour's marks under the pointer. */
|
|
659
|
+
sceneFor(target: string): MarkScene;
|
|
660
|
+
/** The targets the instrument declares, in declaration order. Chrome needs
|
|
661
|
+
* the list and cannot get it from the capability, which wants instrument
|
|
662
|
+
* state the chrome context does not carry. */
|
|
663
|
+
targets(): readonly AnnotationTargetInfo[];
|
|
664
|
+
get(id: string): Annotation | undefined;
|
|
665
|
+
/** Every mark matching the filters, in scene order. Omit `q` for all. */
|
|
666
|
+
query(q?: AnnotationQuery): Annotation[];
|
|
667
|
+
/** Marks whose bounds contain `pt`, topmost first. `tol` widens the hit in
|
|
668
|
+
* fractions, so a hairline mark stays reachable. */
|
|
669
|
+
hitTest(target: string, pt: FracPoint, tol?: number): Annotation[];
|
|
670
|
+
/** Marks wholly inside `box` — a marquee, not a brush. */
|
|
671
|
+
within(target: string, box: FracRect): Annotation[];
|
|
672
|
+
/** Whether `a`'s position still describes the picture `config` produces. */
|
|
673
|
+
isStale(a: Annotation, config: unknown): boolean;
|
|
674
|
+
/** Fires after every mutation. No delta: the scene does not diff, so
|
|
675
|
+
* re-query rather than expecting a change payload. */
|
|
676
|
+
subscribe(fn: () => void): () => void;
|
|
677
|
+
/** Whether the last mark change on any target can be taken back. Weasel
|
|
678
|
+
* history is the authority; this only decides *which* target's. */
|
|
679
|
+
canUndo(): boolean;
|
|
680
|
+
canRedo(): boolean;
|
|
681
|
+
/** Take back the most recent mark change, wherever it was made. */
|
|
682
|
+
undo(): boolean;
|
|
683
|
+
redo(): boolean;
|
|
684
|
+
/** Export a target's picture with its marks on it. Rejects on an id the
|
|
685
|
+
* instrument does not declare. */
|
|
686
|
+
capture(target: string, opts?: CaptureOptions): Promise<CaptureResult>;
|
|
687
|
+
add(init: AnnotationInit, config?: unknown): string;
|
|
688
|
+
update(id: string, patch: AnnotationPatch): void;
|
|
689
|
+
setMeta(id: string, meta: unknown): void;
|
|
690
|
+
remove(id: string): void;
|
|
691
|
+
/** A JSON-safe snapshot for `record.state`. */
|
|
692
|
+
toJSON(): SerializedAnnotations;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** A mark's box in its target's world: CSS pixels at zoom 1. Matches weasel's
|
|
696
|
+
* default `RectPose`, which is what the scene stores. */
|
|
697
|
+
interface WorldRect {
|
|
698
|
+
x: number;
|
|
699
|
+
y: number;
|
|
700
|
+
width: number;
|
|
701
|
+
height: number;
|
|
702
|
+
}
|
|
703
|
+
/** Where a fraction lands on a content box of this size. */
|
|
704
|
+
declare function fracToWorld(f: FracRect, content: {
|
|
705
|
+
w: number;
|
|
706
|
+
h: number;
|
|
707
|
+
}): WorldRect;
|
|
708
|
+
/** What fraction of the content box a world rect covers.
|
|
709
|
+
*
|
|
710
|
+
* A pane measured before layout is 0×0, and dividing by that would seed every
|
|
711
|
+
* stored position with `NaN` — which compares false against everything and so
|
|
712
|
+
* fails silently rather than loudly. Zero sides give zeros. */
|
|
713
|
+
declare function worldToFrac(r: WorldRect, content: {
|
|
714
|
+
w: number;
|
|
715
|
+
h: number;
|
|
716
|
+
}): FracRect;
|
|
717
|
+
/** Snap to 4dp. Positions are diffed by humans in stored documents, so the
|
|
718
|
+
* last digits of a float are noise that hides the change that matters. */
|
|
719
|
+
declare function roundFrac(f: FracRect): FracRect;
|
|
720
|
+
/** Whether `pt` falls in `box`, widened by `tol` on every side so a hairline
|
|
721
|
+
* mark stays reachable. */
|
|
722
|
+
declare function fracContains(box: FracRect, pt: FracPoint, tol?: number): boolean;
|
|
723
|
+
/** Whether `outer` wholly encloses `inner`. A marquee takes what it encloses,
|
|
724
|
+
* not what it grazes — brushing selection is a different gesture. */
|
|
725
|
+
declare function fracIntersects(outer: FracRect, inner: FracRect): boolean;
|
|
726
|
+
|
|
727
|
+
export { LOUPE_DEFAULTS as L, annotationsFromJSON as a3, applyCamera as a4, createAnnotationScene as a5, createAnnotationStore as a6, fracContains as a7, fracIntersects as a8, fracToWorld as a9, resolveFrame as aa, resolveLoupe as ab, roundFrac as ac, worldToFrac as ad, DEFAULT_FRAME as x };
|
|
728
|
+
export type { TrialRegion as $, AnnotationData as A, LayerCapability as B, CaptureSource as C, DragFeedback as D, LayerDescriptor as E, FracPoint as F, LoupeCapability as G, HitResult as H, Instrument as I, LoupeDeclaration as J, LoupeRenderArgs as K, MarkScene as M, ResolvedLoupe as N, SidebarSection as O, PaletteItem as P, StatusReadout as Q, RenderContext as R, SerializedAnnotations as S, TrialTool as T, SystemEvent as U, ViewTransform as V, WorldFrame as W, ToolCapability as X, ToolItem as Y, ToolbarItem as Z, TrialChromeContext as _, Point as a, UndoCapability as a0, ViewportControl as a1, ViewportSize as a2, LoupeMode as ae, DragDropCapability as b, WorldSpec as c, WorldRect as d, AnnotationMeaning as e, CaptureOptions as f, CaptureResult as g, AnnotationTarget as h, AnnotationsApi as i, AnnotationsCapability as j, AnnotationKind as k, InstrumentList as l, TrialContribution as m, Annotation as n, AnnotationInit as o, AnnotationPatch as p, AnnotationQuery as q, AnnotationStatus as r, AnnotationStoreOptions as s, AnnotationTargetInfo as t, CanvasCapability as u, CanvasLayer as v, CaptureDeps as w, FracRect as y, IconComponent as z };
|