@weasel-js/labkit 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_dts/{CanvasStackContext-DQgcH1ny.d.ts → CanvasStackContext-Wo6OwIVM.d.ts} +1 -1
- package/dist/_dts/FloatingPanel-7j0IcXwz.d.ts +30 -0
- package/dist/_dts/{LayerList.d-2c9yup5Y.d.ts → LayerList.d-BoELAMsM.d.ts} +2 -2
- package/dist/_dts/{index-DINmCuLA.d.ts → PropertyField.d-D0ndfIIt.d.ts} +34 -83
- package/dist/_dts/{Select.d-BchGMC2Y.d.ts → Select.d-Cr8-zoup.d.ts} +4 -5
- package/dist/_dts/{usePanZoom-Bq73pwFe.d.ts → Stage-DWBBi8IU.d.ts} +80 -35
- package/dist/_dts/{index-B0--8PEM.d.ts → index-CKyX0ME_.d.ts} +12 -13
- package/dist/_dts/{frac-DTRq7Xpb.d.ts → types-B7yBTTF9.d.ts} +699 -649
- package/dist/_dts/{types-B7y8hX1D.d.ts → types-DTuViBvc.d.ts} +20 -1
- package/dist/_dts/{useTrialState-CP4jfuKl.d.ts → useTrialState-CMaV6UD4.d.ts} +2 -3
- package/dist/canvas/index.d.ts +39 -7
- package/dist/canvas/index.js +5 -3
- package/dist/chrome/index.d.ts +4 -5
- package/dist/chrome/index.js +8 -6
- package/dist/chunk-24W5VEJW.js +18 -0
- package/dist/chunk-24W5VEJW.js.map +1 -0
- package/dist/chunk-3RY6WL5T.js +38 -0
- package/dist/chunk-3RY6WL5T.js.map +1 -0
- package/dist/{chunk-H5VIV7MQ.js → chunk-4TT3PNPR.js} +60 -51
- package/dist/chunk-4TT3PNPR.js.map +1 -0
- package/dist/{chunk-ZORVNOKX.js → chunk-6FLXL3I7.js} +9 -145
- package/dist/chunk-6FLXL3I7.js.map +1 -0
- package/dist/{chunk-HFRE4QQV.js → chunk-ASOK76ZH.js} +53 -22
- package/dist/chunk-ASOK76ZH.js.map +1 -0
- package/dist/chunk-AWQG7FCC.js +19 -0
- package/dist/chunk-AWQG7FCC.js.map +1 -0
- package/dist/chunk-CGHF6RZV.js +300 -0
- package/dist/chunk-CGHF6RZV.js.map +1 -0
- package/dist/chunk-DWMSB4CA.js +228 -0
- package/dist/chunk-DWMSB4CA.js.map +1 -0
- package/dist/{chunk-USO6TERP.js → chunk-GHGO52SO.js} +11 -9
- package/dist/chunk-GHGO52SO.js.map +1 -0
- package/dist/{chunk-MOLIUNVH.js → chunk-HKV7LRHY.js} +4 -4
- package/dist/{chunk-MOLIUNVH.js.map → chunk-HKV7LRHY.js.map} +1 -1
- package/dist/{chunk-VQ47KWD5.js → chunk-IRTGUQ5O.js} +5 -4
- package/dist/chunk-IRTGUQ5O.js.map +1 -0
- package/dist/{chunk-H6ZAOWNE.js → chunk-JVBYV6TL.js} +3 -3
- package/dist/{chunk-H6ZAOWNE.js.map → chunk-JVBYV6TL.js.map} +1 -1
- package/dist/chunk-KHK5FGER.js +145 -0
- package/dist/chunk-KHK5FGER.js.map +1 -0
- package/dist/chunk-NEXR37YJ.js +144 -0
- package/dist/chunk-NEXR37YJ.js.map +1 -0
- package/dist/{chunk-BVL7Y574.js → chunk-O6FFFOHS.js} +14 -6
- package/dist/chunk-O6FFFOHS.js.map +1 -0
- package/dist/chunk-QGMOA2YE.js +731 -0
- package/dist/chunk-QGMOA2YE.js.map +1 -0
- package/dist/{chunk-ERFMC6WU.js → chunk-S4NCI32V.js} +19 -10
- package/dist/chunk-S4NCI32V.js.map +1 -0
- package/dist/{chunk-3FI7Y4R4.js → chunk-SLAQPUI4.js} +4 -4
- package/dist/{chunk-3FI7Y4R4.js.map → chunk-SLAQPUI4.js.map} +1 -1
- package/dist/{chunk-AWH3SRPW.js → chunk-TJ77A4A3.js} +3 -31
- package/dist/chunk-TJ77A4A3.js.map +1 -0
- package/dist/{chunk-WUJXJC5O.js → chunk-TYN6MLXX.js} +691 -1395
- package/dist/chunk-TYN6MLXX.js.map +1 -0
- package/dist/{chunk-RS3HRQWU.js → chunk-WPLAANH4.js} +4 -3
- package/dist/chunk-WPLAANH4.js.map +1 -0
- package/dist/config/index.d.ts +36 -35
- package/dist/config/index.js +4 -5
- package/dist/controls/index.d.ts +91 -7
- package/dist/controls/index.js +5 -4
- package/dist/dragdrop/index.d.ts +8 -6
- package/dist/dragdrop/index.js +3 -2
- package/dist/index.d.ts +50 -77
- package/dist/index.js +138 -254
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +3 -4
- package/dist/layers/index.js +2 -1
- package/dist/loupe/index.d.ts +19 -16
- package/dist/loupe/index.js +5 -3
- package/dist/overview/index.d.ts +56 -0
- package/dist/overview/index.js +288 -0
- package/dist/overview/index.js.map +1 -0
- package/dist/passthrough/weasel-ui.d.ts +38 -38
- package/dist/passthrough/weasel-ui.js +2 -1
- package/dist/primitives/index.d.ts +19 -43
- package/dist/primitives/index.js +5 -3
- package/dist/state/index.d.ts +7 -7
- package/dist/state/index.js +4 -5
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +63 -1
- package/dist/undo/index.d.ts +3 -3
- package/package.json +13 -9
- package/src/annotations/index.ts +1 -0
- package/src/annotations/store.test.ts +26 -0
- package/src/annotations/store.ts +25 -6
- package/src/annotations/toolMap.test.ts +37 -2
- package/src/annotations/toolMap.ts +13 -0
- package/src/annotations/types.ts +27 -0
- package/src/canvas/AGENTS.md +8 -10
- package/src/canvas/CameraInput.test.tsx +136 -0
- package/src/canvas/CameraInput.tsx +311 -0
- package/src/canvas/CanvasStack.tsx +42 -58
- package/src/canvas/LinkedCursor.less +16 -0
- package/src/canvas/LinkedCursor.tsx +54 -0
- package/src/canvas/Stage.tsx +42 -37
- package/src/canvas/cameraView.test.ts +60 -0
- package/src/canvas/cameraView.ts +61 -0
- package/src/canvas/index.ts +11 -2
- package/src/chrome/regions/SidebarRegion.test.tsx +9 -2
- package/src/config/builder.ts +3 -3
- package/src/config/entry.test.ts +3 -1
- package/src/config/fromConfigField.ts +1 -0
- package/src/config/index.ts +4 -1
- package/src/config/resolve.ts +11 -1
- package/src/config/sectionTree.test.ts +12 -6
- package/src/config/sectionTree.ts +15 -5
- package/src/config/types.ts +21 -0
- package/src/controls/ControlPanel.test.tsx +12 -2
- package/src/controls/ControlPanel.tsx +28 -9
- package/src/controls/inDialog.test.tsx +18 -0
- package/src/controls/inDialog.tsx +2 -10
- package/src/index.test.ts +1 -1
- package/src/index.ts +8 -4
- package/src/instrument/SineWave.smoke.test.tsx +2 -0
- package/src/instrument/types.ts +10 -1
- package/src/lab/Lab.nebula.test.tsx +11 -2
- package/src/lab/Lab.test.tsx +3 -1
- package/src/lab/Lab.tsx +4 -4
- package/src/loupe/AGENTS.md +7 -6
- package/src/loupe/LoupeGestures.tsx +14 -9
- package/src/loupe/TrialLoupe.tsx +5 -5
- package/src/loupe/index.ts +1 -0
- package/src/loupe/loupeActions.ts +6 -2
- package/src/overview/AGENTS.md +38 -0
- package/src/overview/OverviewMarks.test.tsx +100 -0
- package/src/overview/OverviewMarks.tsx +93 -0
- package/src/overview/TrialOverview.less +53 -0
- package/src/overview/TrialOverview.stories.tsx +50 -0
- package/src/overview/TrialOverview.test.tsx +116 -0
- package/src/overview/TrialOverview.tsx +374 -0
- package/src/overview/index.ts +2 -0
- package/src/passthrough/weasel-ui.ts +7 -0
- package/src/specimen/Specimen.tsx +19 -2
- package/src/state/labRecords.test.ts +4 -1
- package/src/state/store.test.ts +4 -4
- package/src/state/useTrialState.test.tsx +1 -1
- package/src/styles.less +2 -0
- package/src/theme/Interstellar.stories.tsx +1 -5
- package/src/tools/labTool.ts +6 -2
- package/src/trial/Trial.annotations.test.tsx +16 -0
- package/src/trial/Trial.loupe.test.tsx +73 -1
- package/src/trial/Trial.stories.tsx +2 -0
- package/src/trial/Trial.tsx +17 -1
- package/src/trial/trialOps.test.ts +4 -4
- package/dist/_dts/inDialog-D5ZKGjiD.d.ts +0 -23
- package/dist/_dts/number.d-OeQYQoUO.d.ts +0 -28
- package/dist/chunk-2AYGEN57.js +0 -32
- package/dist/chunk-2AYGEN57.js.map +0 -1
- package/dist/chunk-AWH3SRPW.js.map +0 -1
- package/dist/chunk-BVL7Y574.js.map +0 -1
- package/dist/chunk-ERFMC6WU.js.map +0 -1
- package/dist/chunk-H5VIV7MQ.js.map +0 -1
- package/dist/chunk-HFRE4QQV.js.map +0 -1
- package/dist/chunk-RS3HRQWU.js.map +0 -1
- package/dist/chunk-TT5C5S6R.js +0 -368
- package/dist/chunk-TT5C5S6R.js.map +0 -1
- package/dist/chunk-USO6TERP.js.map +0 -1
- package/dist/chunk-VQ47KWD5.js.map +0 -1
- package/dist/chunk-WUJXJC5O.js.map +0 -1
- package/dist/chunk-ZORVNOKX.js.map +0 -1
- package/src/canvas/usePanZoom.test.ts +0 -244
- package/src/canvas/usePanZoom.ts +0 -147
package/dist/layers/index.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
export { L as LayerList, a as LayerListItem, b as LayerListProps, c as LayerMove, m as moveLayers } from '../_dts/LayerList.d-
|
|
2
|
-
export { L as LayerCapability, a as LayerDescriptor } from '../_dts/
|
|
3
|
-
import 'react/jsx-runtime';
|
|
1
|
+
export { L as LayerList, a as LayerListItem, b as LayerListProps, c as LayerMove, m as moveLayers } from '../_dts/LayerList.d-BoELAMsM.js';
|
|
2
|
+
export { L as LayerCapability, a as LayerDescriptor } from '../_dts/types-B7yBTTF9.js';
|
|
4
3
|
import 'react';
|
|
5
4
|
import '../_dts/PrefsRow.d-DFpPIeIC.js';
|
|
6
5
|
import '@weasel-js/theme';
|
|
7
6
|
import '@weasel-js/core';
|
|
8
|
-
import '../_dts/types-
|
|
7
|
+
import '../_dts/types-DTuViBvc.js';
|
|
9
8
|
import '../_dts/types-DJ79Tg5J.js';
|
package/dist/layers/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import '../chunk-2RVCB2PT.js';
|
|
2
|
-
export { D as LayerList, S2 as moveLayers } from '../chunk-
|
|
2
|
+
export { D as LayerList, S2 as moveLayers } from '../chunk-TYN6MLXX.js';
|
|
3
|
+
import '../chunk-QGMOA2YE.js';
|
|
3
4
|
//# sourceMappingURL=index.js.map
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
package/dist/loupe/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { ai as LoupePoint, ah as LoupeMode, aj as LoupeRenderArgs, am as ResolvedLoupe } from '../_dts/types-B7yBTTF9.js';
|
|
2
|
+
import { WorldFrame, WorldSpec, ViewportSize } from '../canvas/index.js';
|
|
3
|
+
import { ViewTransform } from '../index.js';
|
|
4
|
+
export { ae as LOUPE_DEFAULTS, af as LoupeCapability, ag as LoupeDeclaration, aK as resolveLoupe } from '../_dts/types-B7yBTTF9.js';
|
|
5
|
+
import * as react from 'react';
|
|
5
6
|
import { RefObject, ReactNode } from 'react';
|
|
7
|
+
import { CanvasStackSurface, CanvasLayerDescriptor } from '../canvas/index.js';
|
|
6
8
|
import { Action } from '@weasel-js/core';
|
|
7
9
|
import '../_dts/PrefsRow.d-DFpPIeIC.js';
|
|
8
10
|
import '@weasel-js/theme';
|
|
9
|
-
import '../_dts/types-
|
|
11
|
+
import '../_dts/types-DTuViBvc.js';
|
|
10
12
|
import '../_dts/types-DJ79Tg5J.js';
|
|
11
13
|
|
|
12
14
|
/** Props for `<CanvasLoupe>`. */
|
|
@@ -26,7 +28,7 @@ interface CanvasLoupeProps {
|
|
|
26
28
|
* a zoomed camera — so the magnified content is as sharp as the original, at
|
|
27
29
|
* any factor. `pixel` mode enlarges what the stack presented instead.
|
|
28
30
|
*/
|
|
29
|
-
declare function CanvasLoupe({ aim, factor, mode, diameter, surface, view, frame, worldSpec, }: CanvasLoupeProps):
|
|
31
|
+
declare function CanvasLoupe({ aim, factor, mode, diameter, surface, view, frame, worldSpec, }: CanvasLoupeProps): react.JSX.Element;
|
|
30
32
|
|
|
31
33
|
/** The camera a round lens of `diameter` shows a magnified region through, and
|
|
32
34
|
* the coordinate system it is read in — the instrument's own `WorldSpec`,
|
|
@@ -99,7 +101,7 @@ interface DomLoupeProps {
|
|
|
99
101
|
* where it already was, and the stage is then slid by `diameter / 2 - aim` to
|
|
100
102
|
* bring it to the middle of the lens.
|
|
101
103
|
*/
|
|
102
|
-
declare function DomLoupe({ aim, factor, mode, diameter, size, view, frame, state, config, render, }: DomLoupeProps):
|
|
104
|
+
declare function DomLoupe({ aim, factor, mode, diameter, size, view, frame, state, config, render, }: DomLoupeProps): react.JSX.Element;
|
|
103
105
|
|
|
104
106
|
/** Props for `<LoupeBubble>`. */
|
|
105
107
|
interface LoupeBubbleProps {
|
|
@@ -117,7 +119,7 @@ interface LoupeBubbleProps {
|
|
|
117
119
|
* keep working while it is up — and it is `aria-hidden`, since it magnifies
|
|
118
120
|
* content already on the page rather than adding any.
|
|
119
121
|
*/
|
|
120
|
-
declare function LoupeBubble({ aim, diameter, hostRef, children }: LoupeBubbleProps):
|
|
122
|
+
declare function LoupeBubble({ aim, diameter, hostRef, children }: LoupeBubbleProps): react.JSX.Element;
|
|
121
123
|
|
|
122
124
|
/**
|
|
123
125
|
* The loupe's two bindable interactions, as `Action` descriptors the gesture
|
|
@@ -136,8 +138,8 @@ declare const LOUPE_MAGNIFY_ID = "loupe.magnify";
|
|
|
136
138
|
* registration. */
|
|
137
139
|
interface LoupeInputApi {
|
|
138
140
|
/** Whether the lens is up. The wheel belongs to the loupe only while it is;
|
|
139
|
-
* otherwise `loupe.magnify` declines and the dispatcher
|
|
140
|
-
*
|
|
141
|
+
* otherwise `loupe.magnify` declines and the dispatcher falls through to
|
|
142
|
+
* the trial camera's zoom. */
|
|
141
143
|
shown: () => boolean;
|
|
142
144
|
/** Raise or drop the hold-to-peek flag. */
|
|
143
145
|
setPeeking: (on: boolean) => void;
|
|
@@ -160,11 +162,12 @@ interface LoupeGesturesProps {
|
|
|
160
162
|
peekKey: string | null;
|
|
161
163
|
}
|
|
162
164
|
/**
|
|
163
|
-
* The loupe's input, routed.
|
|
164
|
-
*
|
|
165
|
-
*
|
|
165
|
+
* The loupe's input, routed. Inside a camera (`<CanvasStack>`, `<Stage>`) its
|
|
166
|
+
* actions join the camera's dispatcher, which already listens on the host;
|
|
167
|
+
* elsewhere it mounts a dispatcher of its own. With no registry in scope it
|
|
168
|
+
* renders nothing rather than registering actions that could never fire.
|
|
166
169
|
*/
|
|
167
|
-
declare function LoupeGestures(props: LoupeGesturesProps):
|
|
170
|
+
declare function LoupeGestures(props: LoupeGesturesProps): react.JSX.Element | null;
|
|
168
171
|
|
|
169
172
|
/** Props for `<TrialLoupe>`. */
|
|
170
173
|
interface TrialLoupeProps {
|
|
@@ -185,7 +188,7 @@ interface TrialLoupeProps {
|
|
|
185
188
|
* stack it is mounted inside, or the instrument's own `render` at a magnified
|
|
186
189
|
* camera.
|
|
187
190
|
*/
|
|
188
|
-
declare function TrialLoupe({ capability, enabled, state, config, view, worldSpec, hostRef, }: TrialLoupeProps):
|
|
191
|
+
declare function TrialLoupe({ capability, enabled, state, config, view, worldSpec, hostRef, }: TrialLoupeProps): react.JSX.Element;
|
|
189
192
|
|
|
190
193
|
/** Options for {@link useLoupe}. */
|
|
191
194
|
interface UseLoupeOptions {
|
|
@@ -228,5 +231,5 @@ interface LoupeState {
|
|
|
228
231
|
*/
|
|
229
232
|
declare function useLoupe({ capability, hostRef, enabled, sample }: UseLoupeOptions): LoupeState;
|
|
230
233
|
|
|
231
|
-
export { CanvasLoupe, DomLoupe, LOUPE_MAGNIFY_ID, LOUPE_PEEK_ID, LoupeBubble, LoupeGestures, LoupeRenderArgs, ResolvedLoupe, TrialLoupe, createLoupeActions, drawCanvasLens, lensCamera, lensSourceRect, sampleStack, useLoupe };
|
|
234
|
+
export { CanvasLoupe, DomLoupe, LOUPE_MAGNIFY_ID, LOUPE_PEEK_ID, LoupeBubble, LoupeGestures, LoupeMode, LoupePoint, LoupeRenderArgs, ResolvedLoupe, TrialLoupe, createLoupeActions, drawCanvasLens, lensCamera, lensSourceRect, sampleStack, useLoupe };
|
|
232
235
|
export type { CanvasLoupeProps, DomLoupeProps, LensCamera, LoupeBubbleProps, LoupeGesturesProps, LoupeInputApi, LoupeState, SourceRect, TrialLoupeProps, UseLoupeOptions };
|
package/dist/loupe/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LOUPE_MAGNIFY_ID, LOUPE_PEEK_ID, LoupeBubble, LoupeGestures, TrialLoupe, createLoupeActions, drawCanvasLens, lensCamera, lensSourceRect, resolveLoupe, sampleStack, useLoupe } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
3
|
-
import '../chunk-
|
|
1
|
+
export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LOUPE_MAGNIFY_ID, LOUPE_PEEK_ID, LoupeBubble, LoupeGestures, TrialLoupe, createLoupeActions, drawCanvasLens, lensCamera, lensSourceRect, resolveLoupe, sampleStack, useLoupe } from '../chunk-S4NCI32V.js';
|
|
2
|
+
import '../chunk-JVBYV6TL.js';
|
|
3
|
+
import '../chunk-AWQG7FCC.js';
|
|
4
|
+
import '../chunk-DWMSB4CA.js';
|
|
5
|
+
import '../chunk-24W5VEJW.js';
|
|
4
6
|
import '../chunk-CQLQPQ4P.js';
|
|
5
7
|
//# sourceMappingURL=index.js.map
|
|
6
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { ViewportSize } from '../canvas/index.js';
|
|
4
|
+
import { FloatingPanelProps } from '../index.js';
|
|
5
|
+
import '@weasel-js/core';
|
|
6
|
+
import '../_dts/PrefsRow.d-DFpPIeIC.js';
|
|
7
|
+
import '@weasel-js/theme';
|
|
8
|
+
import '../_dts/types-DTuViBvc.js';
|
|
9
|
+
import '../_dts/types-DJ79Tg5J.js';
|
|
10
|
+
import 'windease';
|
|
11
|
+
|
|
12
|
+
/** The view id the overview publishes its pointer under. */
|
|
13
|
+
declare const OVERVIEW_VIEW_ID = "overview";
|
|
14
|
+
/** A rect in the instrument's world. */
|
|
15
|
+
interface OverviewBounds {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
}
|
|
21
|
+
/** What an overview's `render` is handed. */
|
|
22
|
+
interface OverviewRenderArgs {
|
|
23
|
+
/** The content's own size in CSS pixels, at zoom 1 — lay out at this size
|
|
24
|
+
* and the overview scales it to fit. */
|
|
25
|
+
size: ViewportSize;
|
|
26
|
+
}
|
|
27
|
+
/** Props for `<TrialOverview>`. */
|
|
28
|
+
interface TrialOverviewProps {
|
|
29
|
+
/** The overview box, in CSS pixels. */
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
/**
|
|
33
|
+
* DOM content for the overview, laid out at the content's own size — for a
|
|
34
|
+
* `stage` instrument, typically a lighter copy of what its `render` draws.
|
|
35
|
+
* Never the instrument's own `render`: its effects would run twice. A canvas
|
|
36
|
+
* instrument omits it, and the overview redraws the canvas's layers.
|
|
37
|
+
*/
|
|
38
|
+
render?: (args: OverviewRenderArgs) => ReactNode;
|
|
39
|
+
/** What to frame, in the instrument's world. Defaults to a stage's content
|
|
40
|
+
* rect; a canvas instrument, whose world has no edge, names one. */
|
|
41
|
+
bounds?: OverviewBounds;
|
|
42
|
+
/** Panel title. */
|
|
43
|
+
title?: string;
|
|
44
|
+
anchor?: FloatingPanelProps['anchor'];
|
|
45
|
+
persist?: string;
|
|
46
|
+
className?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A trial's overview. Mount it from an instrument's `render` (a canvas
|
|
50
|
+
* instrument) or `stage.overlay` (a DOM stage) — anywhere inside the trial's
|
|
51
|
+
* camera.
|
|
52
|
+
*/
|
|
53
|
+
declare function TrialOverview(props: TrialOverviewProps): react.JSX.Element | null;
|
|
54
|
+
|
|
55
|
+
export { OVERVIEW_VIEW_ID, TrialOverview };
|
|
56
|
+
export type { OverviewBounds, OverviewRenderArgs, TrialOverviewProps };
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { markSvgNodes } from '../chunk-KHK5FGER.js';
|
|
2
|
+
import { useAnnotationsOptional } from '../chunk-3RY6WL5T.js';
|
|
3
|
+
import { FloatingPanel } from '../chunk-NEXR37YJ.js';
|
|
4
|
+
import '../chunk-RWNDDKOH.js';
|
|
5
|
+
import { CameraContext, fromCameraView, pointerElsewhere, usePublishPointer } from '../chunk-DWMSB4CA.js';
|
|
6
|
+
import '../chunk-24W5VEJW.js';
|
|
7
|
+
import { CanvasStackContext } from '../chunk-CQLQPQ4P.js';
|
|
8
|
+
import '../chunk-2YILQ7Y5.js';
|
|
9
|
+
import { serializeSvg } from '../chunk-QGMOA2YE.js';
|
|
10
|
+
import { usePointerPosition, crosshairRects, DepRegistryProvider, ActionsProvider, ActiveToolContextProvider, useActionsRegistry, useDepSource, minimapCenterAction, minimapPanAction, useGestureDispatcher, MINIMAP_CENTER, MINIMAP_PAN } from '@weasel-js/core';
|
|
11
|
+
import { useContext, useMemo, useRef, useLayoutEffect, useEffect, useReducer } from 'react';
|
|
12
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
|
+
|
|
14
|
+
function localRectOf(el, camera) {
|
|
15
|
+
const host = camera.element();
|
|
16
|
+
if (!host) return null;
|
|
17
|
+
const r = el.getBoundingClientRect();
|
|
18
|
+
const h = host.getBoundingClientRect();
|
|
19
|
+
const v = camera.view.get();
|
|
20
|
+
return {
|
|
21
|
+
x: (r.left - h.left) / v.scale.x + v.x,
|
|
22
|
+
y: (r.top - h.top) / v.scale.y + v.y,
|
|
23
|
+
w: r.width / v.scale.x,
|
|
24
|
+
h: r.height / v.scale.y
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function elementOf(target) {
|
|
28
|
+
return target.ref?.current ?? null;
|
|
29
|
+
}
|
|
30
|
+
function markupOf(api, target, content) {
|
|
31
|
+
const painted = api.paintedMarks(target);
|
|
32
|
+
if (painted.length === 0) return "";
|
|
33
|
+
const nodes = painted.flatMap(({ mark, style }) => markSvgNodes(mark, content, style));
|
|
34
|
+
return serializeSvg(nodes, { viewBox: { x: 0, y: 0, width: content.w, height: content.h } });
|
|
35
|
+
}
|
|
36
|
+
function OverviewMarks({ fit, camera }) {
|
|
37
|
+
const api = useAnnotationsOptional();
|
|
38
|
+
const [, redraw] = useReducer((n) => n + 1, 0);
|
|
39
|
+
useEffect(() => api?.subscribe(redraw), [api]);
|
|
40
|
+
if (!api) return null;
|
|
41
|
+
const s = fit.scale.x;
|
|
42
|
+
return /* @__PURE__ */ jsx(Fragment, { children: api.targets().map((t) => {
|
|
43
|
+
const el = elementOf(t);
|
|
44
|
+
const box = el ? localRectOf(el, camera) : null;
|
|
45
|
+
const markup = box ? markupOf(api, t.id, t.content) : "";
|
|
46
|
+
if (!box || !markup) return null;
|
|
47
|
+
const place = {
|
|
48
|
+
["--lk-overview-mx"]: `${(box.x - fit.x) * s}px`,
|
|
49
|
+
["--lk-overview-my"]: `${(box.y - fit.y) * s}px`,
|
|
50
|
+
["--lk-overview-mw"]: `${box.w * s}px`,
|
|
51
|
+
["--lk-overview-mh"]: `${box.h * s}px`
|
|
52
|
+
};
|
|
53
|
+
return /* @__PURE__ */ jsx(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
className: "lk-overview__marks",
|
|
57
|
+
style: place,
|
|
58
|
+
"data-target": t.id,
|
|
59
|
+
dangerouslySetInnerHTML: { __html: markup }
|
|
60
|
+
},
|
|
61
|
+
t.id
|
|
62
|
+
);
|
|
63
|
+
}) });
|
|
64
|
+
}
|
|
65
|
+
var OVERVIEW_VIEW_ID = "overview";
|
|
66
|
+
var PADDING = 6;
|
|
67
|
+
var OVERVIEW_INPUT = {
|
|
68
|
+
id: "overview",
|
|
69
|
+
eligibility: { always: true },
|
|
70
|
+
bindings: [
|
|
71
|
+
{ spec: { kind: "pointerDown" }, actionId: MINIMAP_CENTER },
|
|
72
|
+
{ spec: { kind: "drag" }, actionId: MINIMAP_PAN }
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
var TOOLS_BY_ID = /* @__PURE__ */ new Map([[OVERVIEW_INPUT.id, OVERVIEW_INPUT]]);
|
|
76
|
+
var CHANNELS = { wheel: false, pinch: false, contextMenu: false, ingest: false };
|
|
77
|
+
function toLocalRect(b, frame) {
|
|
78
|
+
if (frame.yDir === 1) return b;
|
|
79
|
+
return { x: b.x, y: -(b.y + b.height), width: b.width, height: b.height };
|
|
80
|
+
}
|
|
81
|
+
function fitRect(rect, size) {
|
|
82
|
+
const s = Math.min(
|
|
83
|
+
Math.max(1, size.width - 2 * PADDING) / Math.max(rect.width, 1e-9),
|
|
84
|
+
Math.max(1, size.height - 2 * PADDING) / Math.max(rect.height, 1e-9)
|
|
85
|
+
);
|
|
86
|
+
return {
|
|
87
|
+
x: rect.x + rect.width / 2 - size.width / (2 * s),
|
|
88
|
+
y: rect.y + rect.height / 2 - size.height / (2 * s),
|
|
89
|
+
scale: { x: s, y: s }
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function prepare(canvas, size) {
|
|
93
|
+
const ctx = canvas?.getContext("2d") ?? null;
|
|
94
|
+
if (!canvas || !ctx) return null;
|
|
95
|
+
const dpr = window.devicePixelRatio || 1;
|
|
96
|
+
const w = Math.round(size.width * dpr);
|
|
97
|
+
const h = Math.round(size.height * dpr);
|
|
98
|
+
if (canvas.width !== w) canvas.width = w;
|
|
99
|
+
if (canvas.height !== h) canvas.height = h;
|
|
100
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
101
|
+
ctx.clearRect(0, 0, size.width, size.height);
|
|
102
|
+
return ctx;
|
|
103
|
+
}
|
|
104
|
+
function cssVar(el, name, fallback) {
|
|
105
|
+
if (!el) return fallback;
|
|
106
|
+
return getComputedStyle(el).getPropertyValue(name).trim() || fallback;
|
|
107
|
+
}
|
|
108
|
+
function TrialOverview(props) {
|
|
109
|
+
const camera = useContext(CameraContext);
|
|
110
|
+
if (!camera) return null;
|
|
111
|
+
return /* @__PURE__ */ jsx(Overview, { ...props, camera });
|
|
112
|
+
}
|
|
113
|
+
function Overview({
|
|
114
|
+
width,
|
|
115
|
+
height,
|
|
116
|
+
render,
|
|
117
|
+
bounds,
|
|
118
|
+
title = "Overview",
|
|
119
|
+
anchor = "bottom-left",
|
|
120
|
+
persist,
|
|
121
|
+
className,
|
|
122
|
+
camera
|
|
123
|
+
}) {
|
|
124
|
+
const size = useMemo(() => ({ width, height }), [width, height]);
|
|
125
|
+
const content = bounds ? toLocalRect(bounds, camera.frame) : camera.content;
|
|
126
|
+
const contentKey = content ? `${content.x},${content.y},${content.width},${content.height}` : "";
|
|
127
|
+
const fit = useMemo(
|
|
128
|
+
() => content ? fitRect(content, size) : { x: 0, y: 0, scale: { x: 1, y: 1 } },
|
|
129
|
+
[contentKey, size]
|
|
130
|
+
);
|
|
131
|
+
const boxRef = useRef(null);
|
|
132
|
+
return /* @__PURE__ */ jsxs(
|
|
133
|
+
FloatingPanel,
|
|
134
|
+
{
|
|
135
|
+
anchor,
|
|
136
|
+
className: className ? `lk-overview ${className}` : "lk-overview",
|
|
137
|
+
...persist ? { persist } : {},
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ jsx("div", { className: "lk-overview__title", children: title }),
|
|
140
|
+
/* @__PURE__ */ jsxs("div", { ref: boxRef, className: "lk-overview__box", "data-no-drag": true, style: boxVars(size), children: [
|
|
141
|
+
render && content ? /* @__PURE__ */ jsx("div", { className: "lk-overview__content", style: contentVars(fit, content), children: render({ size: { width: content.width, height: content.height } }) }) : null,
|
|
142
|
+
render ? null : /* @__PURE__ */ jsx(LayersCanvas, { fit, size, frame: camera.frame }),
|
|
143
|
+
/* @__PURE__ */ jsx(OverviewMarks, { fit, camera }),
|
|
144
|
+
/* @__PURE__ */ jsx(ChromeCanvas, { fit, size, camera }),
|
|
145
|
+
/* @__PURE__ */ jsx(OverviewInput, { boxRef, fit, camera })
|
|
146
|
+
] })
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
function boxVars(size) {
|
|
152
|
+
return {
|
|
153
|
+
["--lk-overview-w"]: `${size.width}px`,
|
|
154
|
+
["--lk-overview-h"]: `${size.height}px`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function contentVars(fit, content) {
|
|
158
|
+
const s = fit.scale.x;
|
|
159
|
+
return {
|
|
160
|
+
["--lk-overview-x"]: `${(content.x - fit.x) * s}px`,
|
|
161
|
+
["--lk-overview-y"]: `${(content.y - fit.y) * s}px`,
|
|
162
|
+
["--lk-overview-zoom"]: String(s),
|
|
163
|
+
["--lk-overview-cw"]: `${content.width}px`,
|
|
164
|
+
["--lk-overview-ch"]: `${content.height}px`
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function LayersCanvas({ fit, size, frame }) {
|
|
168
|
+
const stack = useContext(CanvasStackContext);
|
|
169
|
+
const ref = useRef(null);
|
|
170
|
+
const layers = stack?.surface?.layers;
|
|
171
|
+
useLayoutEffect(() => {
|
|
172
|
+
const ctx = prepare(ref.current, size);
|
|
173
|
+
if (!ctx || !layers) return;
|
|
174
|
+
const local = { originPx: { x: 0, y: 0 }, yDir: frame.yDir };
|
|
175
|
+
const view = fromCameraView(fit, local);
|
|
176
|
+
for (const layer of layers) {
|
|
177
|
+
if (!layer.visible || layer.id.startsWith("__lk_")) continue;
|
|
178
|
+
ctx.save();
|
|
179
|
+
layer.render(ctx, view, local);
|
|
180
|
+
ctx.restore();
|
|
181
|
+
}
|
|
182
|
+
}, [layers, fit, size, frame.yDir]);
|
|
183
|
+
return /* @__PURE__ */ jsx("canvas", { ref, className: "lk-overview__canvas" });
|
|
184
|
+
}
|
|
185
|
+
function ChromeCanvas({
|
|
186
|
+
fit,
|
|
187
|
+
size,
|
|
188
|
+
camera
|
|
189
|
+
}) {
|
|
190
|
+
const ref = useRef(null);
|
|
191
|
+
const pointer = usePointerPosition();
|
|
192
|
+
const root = camera.view.get();
|
|
193
|
+
const host = camera.view.hostSize?.() ?? null;
|
|
194
|
+
useLayoutEffect(() => {
|
|
195
|
+
const ctx = prepare(ref.current, size);
|
|
196
|
+
if (!ctx) return;
|
|
197
|
+
const s = fit.scale.x;
|
|
198
|
+
const px = (x, y) => ({ x: (x - fit.x) * s, y: (y - fit.y) * s });
|
|
199
|
+
if (host) {
|
|
200
|
+
const a = px(root.x, root.y);
|
|
201
|
+
ctx.strokeStyle = cssVar(ref.current, "--wzl-fg", "#ffffff");
|
|
202
|
+
ctx.lineWidth = 1;
|
|
203
|
+
ctx.setLineDash([2, 3]);
|
|
204
|
+
ctx.strokeRect(
|
|
205
|
+
a.x + 0.5,
|
|
206
|
+
a.y + 0.5,
|
|
207
|
+
host.width / root.scale.x * s,
|
|
208
|
+
host.height / root.scale.y * s
|
|
209
|
+
);
|
|
210
|
+
ctx.setLineDash([]);
|
|
211
|
+
}
|
|
212
|
+
const world = pointerElsewhere(pointer, OVERVIEW_VIEW_ID, camera.frame);
|
|
213
|
+
if (world) {
|
|
214
|
+
const at = px(world.x, world.y * camera.frame.yDir);
|
|
215
|
+
const { halo, bars } = crosshairRects(at.x, at.y, 1);
|
|
216
|
+
ctx.fillStyle = "rgb(0 0 0 / 60%)";
|
|
217
|
+
for (const r of halo) ctx.fillRect(r.x, r.y, r.w, r.h);
|
|
218
|
+
ctx.fillStyle = cssVar(ref.current, "--wzl-accent", "#4c8dff");
|
|
219
|
+
for (const r of bars) ctx.fillRect(r.x, r.y, r.w, r.h);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
return /* @__PURE__ */ jsx("canvas", { ref, className: "lk-overview__canvas lk-overview__chrome" });
|
|
223
|
+
}
|
|
224
|
+
function OverviewInput(props) {
|
|
225
|
+
return /* @__PURE__ */ jsx(DepRegistryProvider, { children: /* @__PURE__ */ jsx(ActionsProvider, { children: /* @__PURE__ */ jsx(ActiveToolContextProvider, { children: /* @__PURE__ */ jsx(OverviewDispatch, { ...props }) }) }) });
|
|
226
|
+
}
|
|
227
|
+
function OverviewDispatch({
|
|
228
|
+
boxRef,
|
|
229
|
+
fit,
|
|
230
|
+
camera
|
|
231
|
+
}) {
|
|
232
|
+
const registry = useActionsRegistry();
|
|
233
|
+
const fitRef = useRef(fit);
|
|
234
|
+
fitRef.current = fit;
|
|
235
|
+
const root = camera.view;
|
|
236
|
+
useDepSource("rootView", () => root);
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
if (!registry) return;
|
|
239
|
+
const offs = [registry.register(minimapCenterAction()), registry.register(minimapPanAction())];
|
|
240
|
+
return () => {
|
|
241
|
+
for (const off of offs) off();
|
|
242
|
+
};
|
|
243
|
+
}, [registry]);
|
|
244
|
+
const clientToLocal = useMemo(
|
|
245
|
+
() => (cx, cy) => {
|
|
246
|
+
const r = boxRef.current?.getBoundingClientRect();
|
|
247
|
+
const f = fitRef.current;
|
|
248
|
+
return {
|
|
249
|
+
x: (cx - (r?.left ?? 0)) / f.scale.x + f.x,
|
|
250
|
+
y: (cy - (r?.top ?? 0)) / f.scale.y + f.y
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
[boxRef]
|
|
254
|
+
);
|
|
255
|
+
useGestureDispatcher({
|
|
256
|
+
canvasRef: boxRef,
|
|
257
|
+
// Always present: `OverviewInput` mounts the provider directly above.
|
|
258
|
+
actions: registry,
|
|
259
|
+
toolsById: TOOLS_BY_ID,
|
|
260
|
+
clientToWorld: clientToLocal,
|
|
261
|
+
keyboard: false,
|
|
262
|
+
channels: CHANNELS
|
|
263
|
+
});
|
|
264
|
+
usePublishPointer(boxRef, clientToLocal, camera.frame, OVERVIEW_VIEW_ID);
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
const el = boxRef.current;
|
|
267
|
+
if (!el) return;
|
|
268
|
+
const stop = (e) => e.stopPropagation();
|
|
269
|
+
const kinds = [
|
|
270
|
+
"pointerdown",
|
|
271
|
+
"pointermove",
|
|
272
|
+
"pointerup",
|
|
273
|
+
"click",
|
|
274
|
+
"wheel",
|
|
275
|
+
"dblclick",
|
|
276
|
+
"contextmenu"
|
|
277
|
+
];
|
|
278
|
+
for (const k of kinds) el.addEventListener(k, stop);
|
|
279
|
+
return () => {
|
|
280
|
+
for (const k of kinds) el.removeEventListener(k, stop);
|
|
281
|
+
};
|
|
282
|
+
}, [boxRef]);
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export { OVERVIEW_VIEW_ID, TrialOverview };
|
|
287
|
+
//# sourceMappingURL=index.js.map
|
|
288
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/overview/OverviewMarks.tsx","../../src/overview/TrialOverview.tsx"],"names":["jsx","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Every annotation target's marks, drawn read-only on the overview through the\n * export's own vector path: `markSvgNodes`, then `serializeSvg`.\n */\nimport type { View } from '@weasel-js/core';\nimport { serializeSvg } from '@weasel-js/svg';\nimport { type CSSProperties, type RefObject, useEffect, useReducer } from 'react';\nimport { useAnnotationsOptional } from '../annotations/AnnotationsContext';\nimport { markSvgNodes } from '../annotations/svgNodes';\nimport type { AnnotationsApi } from '../annotations/types';\nimport type { CameraContextValue } from '../canvas/CameraInput';\n\n/** A target's box, in the camera's frame-local units. */\ninterface LocalRect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/** Where `el` sits in the camera's frame-local units: its screen rect,\n * measured against the camera's element, through the camera. */\nfunction localRectOf(el: HTMLElement, camera: CameraContextValue): LocalRect | null {\n const host = camera.element();\n if (!host) return null;\n const r = el.getBoundingClientRect();\n const h = host.getBoundingClientRect();\n const v = camera.view.get();\n return {\n x: (r.left - h.left) / v.scale.x + v.x,\n y: (r.top - h.top) / v.scale.y + v.y,\n w: r.width / v.scale.x,\n h: r.height / v.scale.y,\n };\n}\n\n/** The element a target's marks sit on. The capability's targets carry their\n * `ref`; the store's type names only the half it reads. */\nfunction elementOf(target: object): HTMLElement | null {\n return (target as { ref?: RefObject<HTMLElement | null> }).ref?.current ?? null;\n}\n\nfunction markupOf(api: AnnotationsApi, target: string, content: { w: number; h: number }): string {\n const painted = api.paintedMarks(target);\n if (painted.length === 0) return '';\n const nodes = painted.flatMap(({ mark, style }) => markSvgNodes(mark, content, style));\n return serializeSvg(nodes, { viewBox: { x: 0, y: 0, width: content.w, height: content.h } });\n}\n\n/** Props for `<OverviewMarks>`. */\nexport interface OverviewMarksProps {\n fit: View;\n camera: CameraContextValue;\n}\n\n/**\n * The marks of every target in the annotations store in scope, placed where\n * each target's element sits on the stage. Draws nothing outside an annotating\n * trial, and takes no input.\n */\nexport function OverviewMarks({ fit, camera }: OverviewMarksProps) {\n const api = useAnnotationsOptional();\n const [, redraw] = useReducer((n: number) => n + 1, 0);\n useEffect(() => api?.subscribe(redraw), [api]);\n if (!api) return null;\n const s = fit.scale.x;\n return (\n <>\n {api.targets().map((t) => {\n const el = elementOf(t);\n const box = el ? localRectOf(el, camera) : null;\n const markup = box ? markupOf(api, t.id, t.content) : '';\n if (!box || !markup) return null;\n const place = {\n ['--lk-overview-mx' as string]: `${(box.x - fit.x) * s}px`,\n ['--lk-overview-my' as string]: `${(box.y - fit.y) * s}px`,\n ['--lk-overview-mw' as string]: `${box.w * s}px`,\n ['--lk-overview-mh' as string]: `${box.h * s}px`,\n } as CSSProperties;\n return (\n <div\n key={t.id}\n className=\"lk-overview__marks\"\n style={place}\n data-target={t.id}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: the export's SVG serializer is the one vector path marks have; re-deriving React nodes would be a second.\n dangerouslySetInnerHTML={{ __html: markup }}\n />\n );\n })}\n </>\n );\n}\n","/**\n * `<TrialOverview>` — the whole of a trial's content at a glance, in a panel\n * that floats over the stage: the visible rect, a crosshair where the pointer\n * is on the stage, and a press or drag that moves the stage's camera there.\n *\n * It is the labkit form of core's minimap contribution: the same\n * `minimap.center` / `minimap.pan` actions, run on a dispatcher of its own with\n * the trial's camera as `rootView`, and the same crosshair geometry.\n */\nimport {\n ActionsProvider,\n ActiveToolContextProvider,\n crosshairRects,\n DepRegistryProvider,\n MINIMAP_CENTER,\n MINIMAP_PAN,\n minimapCenterAction,\n minimapPanAction,\n type Tool,\n useActionsRegistry,\n useDepSource,\n useGestureDispatcher,\n usePointerPosition,\n type View,\n type ViewApi,\n} from '@weasel-js/core';\nimport {\n type CSSProperties,\n type ReactNode,\n type RefObject,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n} from 'react';\nimport {\n CameraContext,\n type CameraContextValue,\n pointerElsewhere,\n usePublishPointer,\n} from '../canvas/CameraInput';\nimport { CanvasStackContext } from '../canvas/CanvasStackContext';\nimport { fromCameraView } from '../canvas/cameraView';\nimport type { ViewportSize, WorldFrame } from '../canvas/worldSpec';\nimport { FloatingPanel, type FloatingPanelProps } from '../primitives/FloatingPanel';\nimport { OverviewMarks } from './OverviewMarks';\n\n/** The view id the overview publishes its pointer under. */\nexport const OVERVIEW_VIEW_ID = 'overview';\n\n/** A rect in the instrument's world. */\nexport interface OverviewBounds {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** What an overview's `render` is handed. */\nexport interface OverviewRenderArgs {\n /** The content's own size in CSS pixels, at zoom 1 — lay out at this size\n * and the overview scales it to fit. */\n size: ViewportSize;\n}\n\n/** Props for `<TrialOverview>`. */\nexport interface TrialOverviewProps {\n /** The overview box, in CSS pixels. */\n width: number;\n height: number;\n /**\n * DOM content for the overview, laid out at the content's own size — for a\n * `stage` instrument, typically a lighter copy of what its `render` draws.\n * Never the instrument's own `render`: its effects would run twice. A canvas\n * instrument omits it, and the overview redraws the canvas's layers.\n */\n render?: (args: OverviewRenderArgs) => ReactNode;\n /** What to frame, in the instrument's world. Defaults to a stage's content\n * rect; a canvas instrument, whose world has no edge, names one. */\n bounds?: OverviewBounds;\n /** Panel title. */\n title?: string;\n anchor?: FloatingPanelProps['anchor'];\n persist?: string;\n className?: string;\n}\n\nconst PADDING = 6;\nconst OVERVIEW_INPUT: Tool = {\n id: 'overview',\n eligibility: { always: true },\n bindings: [\n { spec: { kind: 'pointerDown' }, actionId: MINIMAP_CENTER },\n { spec: { kind: 'drag' }, actionId: MINIMAP_PAN },\n ],\n};\nconst TOOLS_BY_ID: ReadonlyMap<string, Tool> = new Map([[OVERVIEW_INPUT.id, OVERVIEW_INPUT]]);\nconst CHANNELS = { wheel: false, pinch: false, contextMenu: false, ingest: false } as const;\n\n/** A world rect as a frame-local one. */\nfunction toLocalRect(b: OverviewBounds, frame: WorldFrame): OverviewBounds {\n if (frame.yDir === 1) return b;\n return { x: b.x, y: -(b.y + b.height), width: b.width, height: b.height };\n}\n\n/** The camera that fits `rect` into `size`, `PADDING` in from every edge. */\nfunction fitRect(rect: OverviewBounds, size: ViewportSize): View {\n const s = Math.min(\n Math.max(1, size.width - 2 * PADDING) / Math.max(rect.width, 1e-9),\n Math.max(1, size.height - 2 * PADDING) / Math.max(rect.height, 1e-9),\n );\n return {\n x: rect.x + rect.width / 2 - size.width / (2 * s),\n y: rect.y + rect.height / 2 - size.height / (2 * s),\n scale: { x: s, y: s },\n };\n}\n\n/** A canvas's 2D context sized to `size` at the device pixel ratio. */\nfunction prepare(\n canvas: HTMLCanvasElement | null,\n size: ViewportSize,\n): CanvasRenderingContext2D | null {\n const ctx = canvas?.getContext('2d') ?? null;\n if (!canvas || !ctx) return null;\n const dpr = window.devicePixelRatio || 1;\n const w = Math.round(size.width * dpr);\n const h = Math.round(size.height * dpr);\n if (canvas.width !== w) canvas.width = w;\n if (canvas.height !== h) canvas.height = h;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, size.width, size.height);\n return ctx;\n}\n\nfunction cssVar(el: Element | null, name: string, fallback: string): string {\n if (!el) return fallback;\n return getComputedStyle(el).getPropertyValue(name).trim() || fallback;\n}\n\n/**\n * A trial's overview. Mount it from an instrument's `render` (a canvas\n * instrument) or `stage.overlay` (a DOM stage) — anywhere inside the trial's\n * camera.\n */\nexport function TrialOverview(props: TrialOverviewProps) {\n const camera = useContext(CameraContext);\n if (!camera) return null;\n return <Overview {...props} camera={camera} />;\n}\n\nfunction Overview({\n width,\n height,\n render,\n bounds,\n title = 'Overview',\n anchor = 'bottom-left',\n persist,\n className,\n camera,\n}: TrialOverviewProps & { camera: CameraContextValue }) {\n const size = useMemo(() => ({ width, height }), [width, height]);\n const content = bounds ? toLocalRect(bounds, camera.frame) : camera.content;\n const contentKey = content ? `${content.x},${content.y},${content.width},${content.height}` : '';\n // biome-ignore lint/correctness/useExhaustiveDependencies: `content` is rebuilt every render; its numbers, in `contentKey`, are what the fit depends on.\n const fit = useMemo(\n () => (content ? fitRect(content, size) : { x: 0, y: 0, scale: { x: 1, y: 1 } }),\n [contentKey, size],\n );\n const boxRef = useRef<HTMLDivElement | null>(null);\n\n return (\n <FloatingPanel\n anchor={anchor}\n className={className ? `lk-overview ${className}` : 'lk-overview'}\n {...(persist ? { persist } : {})}\n >\n <div className=\"lk-overview__title\">{title}</div>\n <div ref={boxRef} className=\"lk-overview__box\" data-no-drag style={boxVars(size)}>\n {render && content ? (\n <div className=\"lk-overview__content\" style={contentVars(fit, content)}>\n {render({ size: { width: content.width, height: content.height } })}\n </div>\n ) : null}\n {render ? null : <LayersCanvas fit={fit} size={size} frame={camera.frame} />}\n <OverviewMarks fit={fit} camera={camera} />\n <ChromeCanvas fit={fit} size={size} camera={camera} />\n <OverviewInput boxRef={boxRef} fit={fit} camera={camera} />\n </div>\n </FloatingPanel>\n );\n}\n\n// The box's size and the content's camera change with props, and a transform\n// has nowhere to live but the element's own style: set as custom properties\n// the stylesheet reads, as `<Stage>` does.\nfunction boxVars(size: ViewportSize): CSSProperties {\n return {\n ['--lk-overview-w' as string]: `${size.width}px`,\n ['--lk-overview-h' as string]: `${size.height}px`,\n } as CSSProperties;\n}\n\nfunction contentVars(fit: View, content: OverviewBounds): CSSProperties {\n const s = fit.scale.x;\n return {\n ['--lk-overview-x' as string]: `${(content.x - fit.x) * s}px`,\n ['--lk-overview-y' as string]: `${(content.y - fit.y) * s}px`,\n ['--lk-overview-zoom' as string]: String(s),\n ['--lk-overview-cw' as string]: `${content.width}px`,\n ['--lk-overview-ch' as string]: `${content.height}px`,\n } as CSSProperties;\n}\n\n/** A canvas instrument's layers, redrawn through the fit camera. */\nfunction LayersCanvas({ fit, size, frame }: { fit: View; size: ViewportSize; frame: WorldFrame }) {\n const stack = useContext(CanvasStackContext);\n const ref = useRef<HTMLCanvasElement | null>(null);\n const layers = stack?.surface?.layers;\n useLayoutEffect(() => {\n const ctx = prepare(ref.current, size);\n if (!ctx || !layers) return;\n const local: WorldFrame = { originPx: { x: 0, y: 0 }, yDir: frame.yDir };\n const view = fromCameraView(fit, local);\n for (const layer of layers) {\n if (!layer.visible || layer.id.startsWith('__lk_')) continue;\n ctx.save();\n layer.render(ctx, view, local);\n ctx.restore();\n }\n }, [layers, fit, size, frame.yDir]);\n return <canvas ref={ref} className=\"lk-overview__canvas\" />;\n}\n\n/** The visible-rect indicator and the linked crosshair. */\nfunction ChromeCanvas({\n fit,\n size,\n camera,\n}: {\n fit: View;\n size: ViewportSize;\n camera: CameraContextValue;\n}) {\n const ref = useRef<HTMLCanvasElement | null>(null);\n const pointer = usePointerPosition();\n const root = camera.view.get();\n const host = camera.view.hostSize?.() ?? null;\n // No dependency list: this renders only when the pointer or the trial's\n // camera moved, and either one moves the chrome.\n useLayoutEffect(() => {\n const ctx = prepare(ref.current, size);\n if (!ctx) return;\n const s = fit.scale.x;\n const px = (x: number, y: number) => ({ x: (x - fit.x) * s, y: (y - fit.y) * s });\n if (host) {\n const a = px(root.x, root.y);\n ctx.strokeStyle = cssVar(ref.current, '--wzl-fg', '#ffffff');\n ctx.lineWidth = 1;\n ctx.setLineDash([2, 3]);\n ctx.strokeRect(\n a.x + 0.5,\n a.y + 0.5,\n (host.width / root.scale.x) * s,\n (host.height / root.scale.y) * s,\n );\n ctx.setLineDash([]);\n }\n const world = pointerElsewhere(pointer, OVERVIEW_VIEW_ID, camera.frame);\n if (world) {\n // Back to frame-local: the map is its own inverse.\n const at = px(world.x, world.y * camera.frame.yDir);\n const { halo, bars } = crosshairRects(at.x, at.y, 1);\n ctx.fillStyle = 'rgb(0 0 0 / 60%)';\n for (const r of halo) ctx.fillRect(r.x, r.y, r.w, r.h);\n ctx.fillStyle = cssVar(ref.current, '--wzl-accent', '#4c8dff');\n for (const r of bars) ctx.fillRect(r.x, r.y, r.w, r.h);\n }\n });\n return <canvas ref={ref} className=\"lk-overview__canvas lk-overview__chrome\" />;\n}\n\n/**\n * The overview's input, on a dispatcher of its own: its actions and deps are\n * isolated from the trial's, its pointer store is shared with it.\n */\nfunction OverviewInput(props: {\n boxRef: RefObject<HTMLDivElement | null>;\n fit: View;\n camera: CameraContextValue;\n}) {\n return (\n <DepRegistryProvider>\n <ActionsProvider>\n <ActiveToolContextProvider>\n <OverviewDispatch {...props} />\n </ActiveToolContextProvider>\n </ActionsProvider>\n </DepRegistryProvider>\n );\n}\n\nfunction OverviewDispatch({\n boxRef,\n fit,\n camera,\n}: {\n boxRef: RefObject<HTMLDivElement | null>;\n fit: View;\n camera: CameraContextValue;\n}) {\n const registry = useActionsRegistry();\n const fitRef = useRef(fit);\n fitRef.current = fit;\n const root = camera.view;\n useDepSource('rootView', () => root as ViewApi);\n\n useEffect(() => {\n if (!registry) return;\n const offs = [registry.register(minimapCenterAction()), registry.register(minimapPanAction())];\n return () => {\n for (const off of offs) off();\n };\n }, [registry]);\n\n const clientToLocal = useMemo(\n () => (cx: number, cy: number) => {\n const r = boxRef.current?.getBoundingClientRect();\n const f = fitRef.current;\n return {\n x: (cx - (r?.left ?? 0)) / f.scale.x + f.x,\n y: (cy - (r?.top ?? 0)) / f.scale.y + f.y,\n };\n },\n [boxRef],\n );\n\n useGestureDispatcher({\n canvasRef: boxRef,\n // Always present: `OverviewInput` mounts the provider directly above.\n actions: registry as NonNullable<typeof registry>,\n toolsById: TOOLS_BY_ID,\n clientToWorld: clientToLocal,\n keyboard: false,\n channels: CHANNELS,\n });\n\n usePublishPointer(boxRef, clientToLocal, camera.frame, OVERVIEW_VIEW_ID);\n\n // The box sits inside the stage's element, whose own dispatcher, loupe and\n // pointer publisher would otherwise take every event that bubbles out of it.\n useEffect(() => {\n const el = boxRef.current;\n if (!el) return;\n const stop = (e: Event): void => e.stopPropagation();\n const kinds = [\n 'pointerdown',\n 'pointermove',\n 'pointerup',\n 'click',\n 'wheel',\n 'dblclick',\n 'contextmenu',\n ];\n for (const k of kinds) el.addEventListener(k, stop);\n return () => {\n for (const k of kinds) el.removeEventListener(k, stop);\n };\n }, [boxRef]);\n\n return null;\n}\n"]}
|