@weasel-js/labkit 1.5.2 → 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/README.md +3 -7
- package/dist/_dts/{CanvasStackContext-CMMo180l.d.ts → CanvasStackContext-Wo6OwIVM.d.ts} +1 -1
- package/dist/_dts/FloatingPanel-7j0IcXwz.d.ts +30 -0
- package/dist/_dts/LayerList.d-BoELAMsM.d.ts +83 -0
- package/dist/_dts/{PrefsForm.d-DPy9I8It.d.ts → PrefsRow.d-DFpPIeIC.d.ts} +16 -1
- package/dist/_dts/PropertyField.d-D0ndfIIt.d.ts +404 -0
- package/dist/_dts/{Select.d-ZTiwzYyT.d.ts → Select.d-Cr8-zoup.d.ts} +105 -15
- package/dist/_dts/{usePanZoom-SSTZe410.d.ts → Stage-DWBBi8IU.d.ts} +80 -35
- package/dist/_dts/{index-DlKolEIZ.d.ts → index-CKyX0ME_.d.ts} +12 -13
- package/dist/_dts/{frac-B4hFJMiN.d.ts → types-B7yBTTF9.d.ts} +709 -651
- package/dist/_dts/{types-Db-3NNk1.d.ts → types-DTuViBvc.d.ts} +21 -2
- package/dist/_dts/{useTrialState-pnAE-UBD.d.ts → useTrialState-CMaV6UD4.d.ts} +2 -3
- package/dist/canvas/index.d.ts +41 -8
- package/dist/canvas/index.js +5 -3
- package/dist/chrome/index.d.ts +6 -6
- package/dist/chrome/index.js +9 -7
- package/dist/chunk-24W5VEJW.js +18 -0
- package/dist/chunk-24W5VEJW.js.map +1 -0
- package/dist/chunk-2RVCB2PT.js +3 -0
- package/dist/chunk-2RVCB2PT.js.map +1 -0
- package/dist/chunk-3RY6WL5T.js +38 -0
- package/dist/chunk-3RY6WL5T.js.map +1 -0
- package/dist/{chunk-CKUKF72R.js → chunk-4TT3PNPR.js} +68 -55
- package/dist/chunk-4TT3PNPR.js.map +1 -0
- package/dist/{chunk-KJCUERQF.js → chunk-6FLXL3I7.js} +9 -164
- package/dist/chunk-6FLXL3I7.js.map +1 -0
- package/dist/chunk-ASOK76ZH.js +431 -0
- 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-GHGO52SO.js +92 -0
- package/dist/chunk-GHGO52SO.js.map +1 -0
- package/dist/{chunk-LRI3MBGR.js → chunk-HKV7LRHY.js} +4 -4
- package/dist/{chunk-LRI3MBGR.js.map → chunk-HKV7LRHY.js.map} +1 -1
- package/dist/{chunk-EMXAIRUJ.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-A2ACAGW6.js → chunk-O6FFFOHS.js} +32 -7
- 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-753Y6HE6.js → chunk-SLAQPUI4.js} +4 -4
- package/dist/chunk-SLAQPUI4.js.map +1 -0
- package/dist/{chunk-REOBGYMC.js → chunk-TJ77A4A3.js} +3 -5
- package/dist/chunk-TJ77A4A3.js.map +1 -0
- package/dist/chunk-TYN6MLXX.js +13864 -0
- 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 +51 -7
- package/dist/config/index.js +4 -5
- package/dist/controls/index.d.ts +92 -5
- package/dist/controls/index.js +5 -4
- package/dist/dragdrop/index.d.ts +9 -6
- package/dist/dragdrop/index.js +3 -2
- package/dist/index.d.ts +108 -154
- package/dist/index.js +456 -600
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +5 -35
- package/dist/layers/index.js +3 -3
- package/dist/loupe/index.d.ts +21 -17
- 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 +434 -231
- package/dist/passthrough/weasel-ui.js +2 -1
- package/dist/primitives/index.d.ts +19 -54
- package/dist/primitives/index.js +6 -4
- package/dist/state/index.d.ts +8 -7
- package/dist/state/index.js +4 -5
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +360 -174
- package/dist/undo/index.d.ts +5 -4
- package/package.json +13 -13
- package/src/annotations/frac.test.ts +7 -0
- package/src/annotations/index.ts +1 -0
- package/src/annotations/paint.test.ts +21 -0
- package/src/annotations/paint.ts +12 -0
- package/src/annotations/store.test.ts +57 -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 +30 -2
- 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.test.tsx +18 -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.less +197 -0
- package/src/chrome/regions/SidebarRegion.test.tsx +27 -0
- package/src/chrome/regions/SidebarRegion.tsx +7 -2
- package/src/chrome/types.ts +5 -2
- package/src/config/autoConfig.ts +2 -2
- package/src/config/builder.ts +21 -2
- package/src/config/entry.test.ts +10 -0
- package/src/config/fromConfigField.ts +1 -0
- package/src/config/index.ts +5 -0
- package/src/config/resolve.ts +11 -1
- package/src/config/rules.ts +2 -0
- package/src/config/sectionTree.test.ts +152 -0
- package/src/config/sectionTree.ts +134 -0
- package/src/config/types.ts +21 -0
- package/src/controls/ControlPanel.less +4 -0
- package/src/controls/ControlPanel.stories.tsx +55 -2
- package/src/controls/ControlPanel.test.tsx +40 -3
- package/src/controls/ControlPanel.tsx +134 -336
- package/src/controls/inDialog.test.tsx +100 -0
- package/src/controls/inDialog.tsx +38 -0
- package/src/controls/index.ts +1 -0
- package/src/index.test.ts +1 -1
- package/src/index.ts +18 -29
- package/src/instrument/SineWave.smoke.test.tsx +2 -0
- package/src/instrument/types.ts +15 -3
- package/src/lab/Lab.nebula.test.tsx +40 -0
- package/src/lab/Lab.test.tsx +3 -1
- package/src/lab/Lab.tsx +42 -19
- package/src/lab/LabBoundary.test.tsx +66 -0
- package/src/lab/LabBoundary.tsx +56 -0
- package/src/lab/LabHeader.tsx +4 -23
- package/src/lab/LabRoot.tsx +2 -3
- package/src/lab/LabShell.less +0 -1
- package/src/lab/LabShell.stories.tsx +15 -0
- package/src/lab/LabShell.test.tsx +18 -0
- package/src/lab/LabShell.tsx +14 -1
- package/src/lab/LabSwitcher.less +1 -1
- package/src/lab/LabSwitcher.test.tsx +41 -0
- package/src/lab/LabSwitcher.tsx +30 -8
- package/src/lab/index.ts +1 -0
- package/src/layers/AGENTS.md +18 -74
- package/src/layers/index.ts +4 -2
- 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 +32 -10
- package/src/primitives/index.ts +0 -2
- package/src/specimen/Specimen.tsx +59 -56
- package/src/state/labRecords.test.ts +4 -1
- package/src/state/store.test.ts +4 -4
- package/src/state/types.ts +2 -1
- package/src/state/useTrialState.test.tsx +1 -1
- package/src/styles.less +6 -2
- package/src/theme/Interstellar.stories.tsx +8 -13
- package/src/theme/base.less +3 -20
- package/src/theme/interstellar.test.ts +6 -3
- package/src/theme/interstellar.theme.json +0 -56
- package/src/theme/interstellar.ts +2 -2
- package/src/tools/labTool.ts +6 -2
- package/src/trial/Trial.annotations.test.tsx +16 -0
- package/src/trial/Trial.canvas.test.tsx +1 -1
- package/src/trial/Trial.loupe.test.tsx +73 -1
- package/src/trial/Trial.stories.tsx +2 -0
- package/src/trial/Trial.tsx +47 -9
- package/src/trial/trialOps.test.ts +4 -4
- package/dist/_dts/index-8LiKe3A-.d.ts +0 -410
- package/dist/chunk-2AYGEN57.js +0 -32
- package/dist/chunk-2AYGEN57.js.map +0 -1
- package/dist/chunk-6BUQ77ZV.js +0 -18
- package/dist/chunk-6BUQ77ZV.js.map +0 -1
- package/dist/chunk-753Y6HE6.js.map +0 -1
- package/dist/chunk-A2ACAGW6.js.map +0 -1
- package/dist/chunk-CKUKF72R.js.map +0 -1
- package/dist/chunk-D35PFTS7.js +0 -170
- package/dist/chunk-D35PFTS7.js.map +0 -1
- package/dist/chunk-EMXAIRUJ.js.map +0 -1
- package/dist/chunk-ERFMC6WU.js.map +0 -1
- package/dist/chunk-FKLT3ITC.js +0 -590
- package/dist/chunk-FKLT3ITC.js.map +0 -1
- package/dist/chunk-KJCUERQF.js.map +0 -1
- package/dist/chunk-LASCBWW7.js +0 -13456
- package/dist/chunk-LASCBWW7.js.map +0 -1
- package/dist/chunk-REOBGYMC.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/ui/layers/index.d.ts +0 -66
- package/dist/ui/layers/index.js +0 -3
- package/dist/ui/layers/index.js.map +0 -1
- package/src/canvas/usePanZoom.test.ts +0 -244
- package/src/canvas/usePanZoom.ts +0 -147
- package/src/lab/useSystemMode.ts +0 -30
- package/src/layers/LayerList.less +0 -93
- package/src/layers/LayerList.stories.tsx +0 -66
- package/src/layers/LayerList.test.tsx +0 -232
- package/src/layers/LayerList.tsx +0 -268
- package/src/primitives/Sidebar.less +0 -112
- package/src/primitives/Sidebar.stories.tsx +0 -38
- package/src/primitives/Sidebar.test.tsx +0 -74
- package/src/primitives/Sidebar.tsx +0 -35
- package/src/ui/layers/index.ts +0 -5
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<TrialOverview>` — the whole of a trial's content at a glance, in a panel
|
|
3
|
+
* that floats over the stage: the visible rect, a crosshair where the pointer
|
|
4
|
+
* is on the stage, and a press or drag that moves the stage's camera there.
|
|
5
|
+
*
|
|
6
|
+
* It is the labkit form of core's minimap contribution: the same
|
|
7
|
+
* `minimap.center` / `minimap.pan` actions, run on a dispatcher of its own with
|
|
8
|
+
* the trial's camera as `rootView`, and the same crosshair geometry.
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
ActionsProvider,
|
|
12
|
+
ActiveToolContextProvider,
|
|
13
|
+
crosshairRects,
|
|
14
|
+
DepRegistryProvider,
|
|
15
|
+
MINIMAP_CENTER,
|
|
16
|
+
MINIMAP_PAN,
|
|
17
|
+
minimapCenterAction,
|
|
18
|
+
minimapPanAction,
|
|
19
|
+
type Tool,
|
|
20
|
+
useActionsRegistry,
|
|
21
|
+
useDepSource,
|
|
22
|
+
useGestureDispatcher,
|
|
23
|
+
usePointerPosition,
|
|
24
|
+
type View,
|
|
25
|
+
type ViewApi,
|
|
26
|
+
} from '@weasel-js/core';
|
|
27
|
+
import {
|
|
28
|
+
type CSSProperties,
|
|
29
|
+
type ReactNode,
|
|
30
|
+
type RefObject,
|
|
31
|
+
useContext,
|
|
32
|
+
useEffect,
|
|
33
|
+
useLayoutEffect,
|
|
34
|
+
useMemo,
|
|
35
|
+
useRef,
|
|
36
|
+
} from 'react';
|
|
37
|
+
import {
|
|
38
|
+
CameraContext,
|
|
39
|
+
type CameraContextValue,
|
|
40
|
+
pointerElsewhere,
|
|
41
|
+
usePublishPointer,
|
|
42
|
+
} from '../canvas/CameraInput';
|
|
43
|
+
import { CanvasStackContext } from '../canvas/CanvasStackContext';
|
|
44
|
+
import { fromCameraView } from '../canvas/cameraView';
|
|
45
|
+
import type { ViewportSize, WorldFrame } from '../canvas/worldSpec';
|
|
46
|
+
import { FloatingPanel, type FloatingPanelProps } from '../primitives/FloatingPanel';
|
|
47
|
+
import { OverviewMarks } from './OverviewMarks';
|
|
48
|
+
|
|
49
|
+
/** The view id the overview publishes its pointer under. */
|
|
50
|
+
export const OVERVIEW_VIEW_ID = 'overview';
|
|
51
|
+
|
|
52
|
+
/** A rect in the instrument's world. */
|
|
53
|
+
export interface OverviewBounds {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** What an overview's `render` is handed. */
|
|
61
|
+
export interface OverviewRenderArgs {
|
|
62
|
+
/** The content's own size in CSS pixels, at zoom 1 — lay out at this size
|
|
63
|
+
* and the overview scales it to fit. */
|
|
64
|
+
size: ViewportSize;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Props for `<TrialOverview>`. */
|
|
68
|
+
export interface TrialOverviewProps {
|
|
69
|
+
/** The overview box, in CSS pixels. */
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
/**
|
|
73
|
+
* DOM content for the overview, laid out at the content's own size — for a
|
|
74
|
+
* `stage` instrument, typically a lighter copy of what its `render` draws.
|
|
75
|
+
* Never the instrument's own `render`: its effects would run twice. A canvas
|
|
76
|
+
* instrument omits it, and the overview redraws the canvas's layers.
|
|
77
|
+
*/
|
|
78
|
+
render?: (args: OverviewRenderArgs) => ReactNode;
|
|
79
|
+
/** What to frame, in the instrument's world. Defaults to a stage's content
|
|
80
|
+
* rect; a canvas instrument, whose world has no edge, names one. */
|
|
81
|
+
bounds?: OverviewBounds;
|
|
82
|
+
/** Panel title. */
|
|
83
|
+
title?: string;
|
|
84
|
+
anchor?: FloatingPanelProps['anchor'];
|
|
85
|
+
persist?: string;
|
|
86
|
+
className?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const PADDING = 6;
|
|
90
|
+
const OVERVIEW_INPUT: Tool = {
|
|
91
|
+
id: 'overview',
|
|
92
|
+
eligibility: { always: true },
|
|
93
|
+
bindings: [
|
|
94
|
+
{ spec: { kind: 'pointerDown' }, actionId: MINIMAP_CENTER },
|
|
95
|
+
{ spec: { kind: 'drag' }, actionId: MINIMAP_PAN },
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
const TOOLS_BY_ID: ReadonlyMap<string, Tool> = new Map([[OVERVIEW_INPUT.id, OVERVIEW_INPUT]]);
|
|
99
|
+
const CHANNELS = { wheel: false, pinch: false, contextMenu: false, ingest: false } as const;
|
|
100
|
+
|
|
101
|
+
/** A world rect as a frame-local one. */
|
|
102
|
+
function toLocalRect(b: OverviewBounds, frame: WorldFrame): OverviewBounds {
|
|
103
|
+
if (frame.yDir === 1) return b;
|
|
104
|
+
return { x: b.x, y: -(b.y + b.height), width: b.width, height: b.height };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The camera that fits `rect` into `size`, `PADDING` in from every edge. */
|
|
108
|
+
function fitRect(rect: OverviewBounds, size: ViewportSize): View {
|
|
109
|
+
const s = Math.min(
|
|
110
|
+
Math.max(1, size.width - 2 * PADDING) / Math.max(rect.width, 1e-9),
|
|
111
|
+
Math.max(1, size.height - 2 * PADDING) / Math.max(rect.height, 1e-9),
|
|
112
|
+
);
|
|
113
|
+
return {
|
|
114
|
+
x: rect.x + rect.width / 2 - size.width / (2 * s),
|
|
115
|
+
y: rect.y + rect.height / 2 - size.height / (2 * s),
|
|
116
|
+
scale: { x: s, y: s },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** A canvas's 2D context sized to `size` at the device pixel ratio. */
|
|
121
|
+
function prepare(
|
|
122
|
+
canvas: HTMLCanvasElement | null,
|
|
123
|
+
size: ViewportSize,
|
|
124
|
+
): CanvasRenderingContext2D | null {
|
|
125
|
+
const ctx = canvas?.getContext('2d') ?? null;
|
|
126
|
+
if (!canvas || !ctx) return null;
|
|
127
|
+
const dpr = window.devicePixelRatio || 1;
|
|
128
|
+
const w = Math.round(size.width * dpr);
|
|
129
|
+
const h = Math.round(size.height * dpr);
|
|
130
|
+
if (canvas.width !== w) canvas.width = w;
|
|
131
|
+
if (canvas.height !== h) canvas.height = h;
|
|
132
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
133
|
+
ctx.clearRect(0, 0, size.width, size.height);
|
|
134
|
+
return ctx;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function cssVar(el: Element | null, name: string, fallback: string): string {
|
|
138
|
+
if (!el) return fallback;
|
|
139
|
+
return getComputedStyle(el).getPropertyValue(name).trim() || fallback;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* A trial's overview. Mount it from an instrument's `render` (a canvas
|
|
144
|
+
* instrument) or `stage.overlay` (a DOM stage) — anywhere inside the trial's
|
|
145
|
+
* camera.
|
|
146
|
+
*/
|
|
147
|
+
export function TrialOverview(props: TrialOverviewProps) {
|
|
148
|
+
const camera = useContext(CameraContext);
|
|
149
|
+
if (!camera) return null;
|
|
150
|
+
return <Overview {...props} camera={camera} />;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function Overview({
|
|
154
|
+
width,
|
|
155
|
+
height,
|
|
156
|
+
render,
|
|
157
|
+
bounds,
|
|
158
|
+
title = 'Overview',
|
|
159
|
+
anchor = 'bottom-left',
|
|
160
|
+
persist,
|
|
161
|
+
className,
|
|
162
|
+
camera,
|
|
163
|
+
}: TrialOverviewProps & { camera: CameraContextValue }) {
|
|
164
|
+
const size = useMemo(() => ({ width, height }), [width, height]);
|
|
165
|
+
const content = bounds ? toLocalRect(bounds, camera.frame) : camera.content;
|
|
166
|
+
const contentKey = content ? `${content.x},${content.y},${content.width},${content.height}` : '';
|
|
167
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `content` is rebuilt every render; its numbers, in `contentKey`, are what the fit depends on.
|
|
168
|
+
const fit = useMemo(
|
|
169
|
+
() => (content ? fitRect(content, size) : { x: 0, y: 0, scale: { x: 1, y: 1 } }),
|
|
170
|
+
[contentKey, size],
|
|
171
|
+
);
|
|
172
|
+
const boxRef = useRef<HTMLDivElement | null>(null);
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<FloatingPanel
|
|
176
|
+
anchor={anchor}
|
|
177
|
+
className={className ? `lk-overview ${className}` : 'lk-overview'}
|
|
178
|
+
{...(persist ? { persist } : {})}
|
|
179
|
+
>
|
|
180
|
+
<div className="lk-overview__title">{title}</div>
|
|
181
|
+
<div ref={boxRef} className="lk-overview__box" data-no-drag style={boxVars(size)}>
|
|
182
|
+
{render && content ? (
|
|
183
|
+
<div className="lk-overview__content" style={contentVars(fit, content)}>
|
|
184
|
+
{render({ size: { width: content.width, height: content.height } })}
|
|
185
|
+
</div>
|
|
186
|
+
) : null}
|
|
187
|
+
{render ? null : <LayersCanvas fit={fit} size={size} frame={camera.frame} />}
|
|
188
|
+
<OverviewMarks fit={fit} camera={camera} />
|
|
189
|
+
<ChromeCanvas fit={fit} size={size} camera={camera} />
|
|
190
|
+
<OverviewInput boxRef={boxRef} fit={fit} camera={camera} />
|
|
191
|
+
</div>
|
|
192
|
+
</FloatingPanel>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// The box's size and the content's camera change with props, and a transform
|
|
197
|
+
// has nowhere to live but the element's own style: set as custom properties
|
|
198
|
+
// the stylesheet reads, as `<Stage>` does.
|
|
199
|
+
function boxVars(size: ViewportSize): CSSProperties {
|
|
200
|
+
return {
|
|
201
|
+
['--lk-overview-w' as string]: `${size.width}px`,
|
|
202
|
+
['--lk-overview-h' as string]: `${size.height}px`,
|
|
203
|
+
} as CSSProperties;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function contentVars(fit: View, content: OverviewBounds): CSSProperties {
|
|
207
|
+
const s = fit.scale.x;
|
|
208
|
+
return {
|
|
209
|
+
['--lk-overview-x' as string]: `${(content.x - fit.x) * s}px`,
|
|
210
|
+
['--lk-overview-y' as string]: `${(content.y - fit.y) * s}px`,
|
|
211
|
+
['--lk-overview-zoom' as string]: String(s),
|
|
212
|
+
['--lk-overview-cw' as string]: `${content.width}px`,
|
|
213
|
+
['--lk-overview-ch' as string]: `${content.height}px`,
|
|
214
|
+
} as CSSProperties;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** A canvas instrument's layers, redrawn through the fit camera. */
|
|
218
|
+
function LayersCanvas({ fit, size, frame }: { fit: View; size: ViewportSize; frame: WorldFrame }) {
|
|
219
|
+
const stack = useContext(CanvasStackContext);
|
|
220
|
+
const ref = useRef<HTMLCanvasElement | null>(null);
|
|
221
|
+
const layers = stack?.surface?.layers;
|
|
222
|
+
useLayoutEffect(() => {
|
|
223
|
+
const ctx = prepare(ref.current, size);
|
|
224
|
+
if (!ctx || !layers) return;
|
|
225
|
+
const local: WorldFrame = { originPx: { x: 0, y: 0 }, yDir: frame.yDir };
|
|
226
|
+
const view = fromCameraView(fit, local);
|
|
227
|
+
for (const layer of layers) {
|
|
228
|
+
if (!layer.visible || layer.id.startsWith('__lk_')) continue;
|
|
229
|
+
ctx.save();
|
|
230
|
+
layer.render(ctx, view, local);
|
|
231
|
+
ctx.restore();
|
|
232
|
+
}
|
|
233
|
+
}, [layers, fit, size, frame.yDir]);
|
|
234
|
+
return <canvas ref={ref} className="lk-overview__canvas" />;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** The visible-rect indicator and the linked crosshair. */
|
|
238
|
+
function ChromeCanvas({
|
|
239
|
+
fit,
|
|
240
|
+
size,
|
|
241
|
+
camera,
|
|
242
|
+
}: {
|
|
243
|
+
fit: View;
|
|
244
|
+
size: ViewportSize;
|
|
245
|
+
camera: CameraContextValue;
|
|
246
|
+
}) {
|
|
247
|
+
const ref = useRef<HTMLCanvasElement | null>(null);
|
|
248
|
+
const pointer = usePointerPosition();
|
|
249
|
+
const root = camera.view.get();
|
|
250
|
+
const host = camera.view.hostSize?.() ?? null;
|
|
251
|
+
// No dependency list: this renders only when the pointer or the trial's
|
|
252
|
+
// camera moved, and either one moves the chrome.
|
|
253
|
+
useLayoutEffect(() => {
|
|
254
|
+
const ctx = prepare(ref.current, size);
|
|
255
|
+
if (!ctx) return;
|
|
256
|
+
const s = fit.scale.x;
|
|
257
|
+
const px = (x: number, y: number) => ({ x: (x - fit.x) * s, y: (y - fit.y) * s });
|
|
258
|
+
if (host) {
|
|
259
|
+
const a = px(root.x, root.y);
|
|
260
|
+
ctx.strokeStyle = cssVar(ref.current, '--wzl-fg', '#ffffff');
|
|
261
|
+
ctx.lineWidth = 1;
|
|
262
|
+
ctx.setLineDash([2, 3]);
|
|
263
|
+
ctx.strokeRect(
|
|
264
|
+
a.x + 0.5,
|
|
265
|
+
a.y + 0.5,
|
|
266
|
+
(host.width / root.scale.x) * s,
|
|
267
|
+
(host.height / root.scale.y) * s,
|
|
268
|
+
);
|
|
269
|
+
ctx.setLineDash([]);
|
|
270
|
+
}
|
|
271
|
+
const world = pointerElsewhere(pointer, OVERVIEW_VIEW_ID, camera.frame);
|
|
272
|
+
if (world) {
|
|
273
|
+
// Back to frame-local: the map is its own inverse.
|
|
274
|
+
const at = px(world.x, world.y * camera.frame.yDir);
|
|
275
|
+
const { halo, bars } = crosshairRects(at.x, at.y, 1);
|
|
276
|
+
ctx.fillStyle = 'rgb(0 0 0 / 60%)';
|
|
277
|
+
for (const r of halo) ctx.fillRect(r.x, r.y, r.w, r.h);
|
|
278
|
+
ctx.fillStyle = cssVar(ref.current, '--wzl-accent', '#4c8dff');
|
|
279
|
+
for (const r of bars) ctx.fillRect(r.x, r.y, r.w, r.h);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
return <canvas ref={ref} className="lk-overview__canvas lk-overview__chrome" />;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* The overview's input, on a dispatcher of its own: its actions and deps are
|
|
287
|
+
* isolated from the trial's, its pointer store is shared with it.
|
|
288
|
+
*/
|
|
289
|
+
function OverviewInput(props: {
|
|
290
|
+
boxRef: RefObject<HTMLDivElement | null>;
|
|
291
|
+
fit: View;
|
|
292
|
+
camera: CameraContextValue;
|
|
293
|
+
}) {
|
|
294
|
+
return (
|
|
295
|
+
<DepRegistryProvider>
|
|
296
|
+
<ActionsProvider>
|
|
297
|
+
<ActiveToolContextProvider>
|
|
298
|
+
<OverviewDispatch {...props} />
|
|
299
|
+
</ActiveToolContextProvider>
|
|
300
|
+
</ActionsProvider>
|
|
301
|
+
</DepRegistryProvider>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function OverviewDispatch({
|
|
306
|
+
boxRef,
|
|
307
|
+
fit,
|
|
308
|
+
camera,
|
|
309
|
+
}: {
|
|
310
|
+
boxRef: RefObject<HTMLDivElement | null>;
|
|
311
|
+
fit: View;
|
|
312
|
+
camera: CameraContextValue;
|
|
313
|
+
}) {
|
|
314
|
+
const registry = useActionsRegistry();
|
|
315
|
+
const fitRef = useRef(fit);
|
|
316
|
+
fitRef.current = fit;
|
|
317
|
+
const root = camera.view;
|
|
318
|
+
useDepSource('rootView', () => root as ViewApi);
|
|
319
|
+
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
if (!registry) return;
|
|
322
|
+
const offs = [registry.register(minimapCenterAction()), registry.register(minimapPanAction())];
|
|
323
|
+
return () => {
|
|
324
|
+
for (const off of offs) off();
|
|
325
|
+
};
|
|
326
|
+
}, [registry]);
|
|
327
|
+
|
|
328
|
+
const clientToLocal = useMemo(
|
|
329
|
+
() => (cx: number, cy: number) => {
|
|
330
|
+
const r = boxRef.current?.getBoundingClientRect();
|
|
331
|
+
const f = fitRef.current;
|
|
332
|
+
return {
|
|
333
|
+
x: (cx - (r?.left ?? 0)) / f.scale.x + f.x,
|
|
334
|
+
y: (cy - (r?.top ?? 0)) / f.scale.y + f.y,
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
[boxRef],
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
useGestureDispatcher({
|
|
341
|
+
canvasRef: boxRef,
|
|
342
|
+
// Always present: `OverviewInput` mounts the provider directly above.
|
|
343
|
+
actions: registry as NonNullable<typeof registry>,
|
|
344
|
+
toolsById: TOOLS_BY_ID,
|
|
345
|
+
clientToWorld: clientToLocal,
|
|
346
|
+
keyboard: false,
|
|
347
|
+
channels: CHANNELS,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
usePublishPointer(boxRef, clientToLocal, camera.frame, OVERVIEW_VIEW_ID);
|
|
351
|
+
|
|
352
|
+
// The box sits inside the stage's element, whose own dispatcher, loupe and
|
|
353
|
+
// pointer publisher would otherwise take every event that bubbles out of it.
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
const el = boxRef.current;
|
|
356
|
+
if (!el) return;
|
|
357
|
+
const stop = (e: Event): void => e.stopPropagation();
|
|
358
|
+
const kinds = [
|
|
359
|
+
'pointerdown',
|
|
360
|
+
'pointermove',
|
|
361
|
+
'pointerup',
|
|
362
|
+
'click',
|
|
363
|
+
'wheel',
|
|
364
|
+
'dblclick',
|
|
365
|
+
'contextmenu',
|
|
366
|
+
];
|
|
367
|
+
for (const k of kinds) el.addEventListener(k, stop);
|
|
368
|
+
return () => {
|
|
369
|
+
for (const k of kinds) el.removeEventListener(k, stop);
|
|
370
|
+
};
|
|
371
|
+
}, [boxRef]);
|
|
372
|
+
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
@@ -8,11 +8,6 @@
|
|
|
8
8
|
export {
|
|
9
9
|
ActionBar,
|
|
10
10
|
type ActionBarProps,
|
|
11
|
-
ActionsBar,
|
|
12
|
-
type ActionsBarItem,
|
|
13
|
-
type ActionsBarProps,
|
|
14
|
-
type ActionsBarSize,
|
|
15
|
-
type ActionsBarVariant,
|
|
16
11
|
type AddPointMode,
|
|
17
12
|
type AnchorRenderProps,
|
|
18
13
|
type AsyncOption,
|
|
@@ -22,12 +17,17 @@ export {
|
|
|
22
17
|
type BadgeProps,
|
|
23
18
|
type BadgeShape,
|
|
24
19
|
type BadgeSize,
|
|
25
|
-
type
|
|
20
|
+
type BadgeStatus,
|
|
26
21
|
type BadgeVariant,
|
|
27
22
|
type BoundsCtx,
|
|
28
23
|
type BuiltInEdgeName,
|
|
29
24
|
type BuiltinPref,
|
|
30
25
|
Button,
|
|
26
|
+
ButtonBar,
|
|
27
|
+
type ButtonBarItem,
|
|
28
|
+
type ButtonBarProps,
|
|
29
|
+
type ButtonBarSize,
|
|
30
|
+
type ButtonBarVariant,
|
|
31
31
|
type ButtonProps,
|
|
32
32
|
type ButtonSize,
|
|
33
33
|
type ButtonVariant,
|
|
@@ -38,6 +38,8 @@ export {
|
|
|
38
38
|
type CheckboxProps,
|
|
39
39
|
type ChromaCurve,
|
|
40
40
|
type ChromaCurvePoint,
|
|
41
|
+
ColorModeControl,
|
|
42
|
+
type ColorModeControlProps,
|
|
41
43
|
ComboBox,
|
|
42
44
|
type ComboBoxCommit,
|
|
43
45
|
type ComboBoxFilter,
|
|
@@ -56,10 +58,13 @@ export {
|
|
|
56
58
|
Dialog,
|
|
57
59
|
type DialogProps,
|
|
58
60
|
Disclosure,
|
|
59
|
-
|
|
61
|
+
DisclosureMark,
|
|
62
|
+
type DisclosureMarkProps,
|
|
60
63
|
type DisclosureProps,
|
|
61
64
|
DisclosureRow,
|
|
62
65
|
type DisclosureRowProps,
|
|
66
|
+
DragGhost,
|
|
67
|
+
type DragGhostProps,
|
|
63
68
|
DragHandleGlyph,
|
|
64
69
|
type DragHandleGlyphProps,
|
|
65
70
|
detectPlatform,
|
|
@@ -98,16 +103,20 @@ export {
|
|
|
98
103
|
keyGlyph,
|
|
99
104
|
keySpecFromKey,
|
|
100
105
|
keySpecsFromMods,
|
|
106
|
+
keySpecsFromShortcut,
|
|
107
|
+
LayerList,
|
|
101
108
|
type LayerListItem,
|
|
102
|
-
|
|
103
|
-
type
|
|
104
|
-
|
|
109
|
+
type LayerListProps,
|
|
110
|
+
type LayerMove,
|
|
111
|
+
ListEditor,
|
|
112
|
+
type ListEditorProps,
|
|
105
113
|
type LogicalMod,
|
|
106
114
|
type LogicalModSpec,
|
|
107
115
|
MenuButton,
|
|
108
116
|
type MenuButtonItem,
|
|
109
117
|
type MenuButtonProps,
|
|
110
118
|
MINUS_SIGN,
|
|
119
|
+
moveLayers,
|
|
111
120
|
NumberField,
|
|
112
121
|
type NumberFieldProps,
|
|
113
122
|
OptionsBar,
|
|
@@ -115,6 +124,7 @@ export {
|
|
|
115
124
|
type OptionsBarProps,
|
|
116
125
|
type OptionsBarSize,
|
|
117
126
|
type OptionsBarVariant,
|
|
127
|
+
type OverlayPortalProps,
|
|
118
128
|
oklchToHex,
|
|
119
129
|
type Platform,
|
|
120
130
|
Plot2D,
|
|
@@ -145,6 +155,14 @@ export {
|
|
|
145
155
|
type PrefRenderer,
|
|
146
156
|
type PrefString,
|
|
147
157
|
type PrefStringControl,
|
|
158
|
+
PrefsDialog,
|
|
159
|
+
type PrefsDialogProps,
|
|
160
|
+
PrefsForm,
|
|
161
|
+
type PrefsFormProps,
|
|
162
|
+
type PrefsLayout,
|
|
163
|
+
type PropertyAlign,
|
|
164
|
+
type PropertyDensity,
|
|
165
|
+
type PropertyMetricProps,
|
|
148
166
|
paintGradientTrack,
|
|
149
167
|
prefValueAtPath,
|
|
150
168
|
Radio,
|
|
@@ -155,8 +173,10 @@ export {
|
|
|
155
173
|
type RangeSliderProps,
|
|
156
174
|
type ReorderDragHandlers,
|
|
157
175
|
type ReorderDragState,
|
|
176
|
+
type ReorderGhost,
|
|
158
177
|
type RovingItem,
|
|
159
178
|
type RovingTabIndex,
|
|
179
|
+
type SegmentTooltipFields,
|
|
160
180
|
Select,
|
|
161
181
|
SelectItem,
|
|
162
182
|
type SelectItemProps,
|
|
@@ -168,6 +188,7 @@ export {
|
|
|
168
188
|
type SidebarProps,
|
|
169
189
|
Slider,
|
|
170
190
|
type SliderProps,
|
|
191
|
+
type StanceProps,
|
|
171
192
|
Switch,
|
|
172
193
|
type SwitchProps,
|
|
173
194
|
Tab,
|
|
@@ -200,4 +221,5 @@ export {
|
|
|
200
221
|
useReorderDragList,
|
|
201
222
|
useRovingTabIndex,
|
|
202
223
|
visiblePrefSubtree,
|
|
224
|
+
type WithoutPortalTarget,
|
|
203
225
|
} from '@weasel-js/ui';
|
package/src/primitives/index.ts
CHANGED
|
@@ -7,8 +7,6 @@ export type { LegendEntry, LegendMark, LegendProps } from './Legend';
|
|
|
7
7
|
export { Legend } from './Legend';
|
|
8
8
|
export type { ScaleIndicatorProps } from './ScaleIndicator';
|
|
9
9
|
export { ScaleIndicator } from './ScaleIndicator';
|
|
10
|
-
export type { SidebarProps } from './Sidebar';
|
|
11
|
-
export { Sidebar } from './Sidebar';
|
|
12
10
|
export type { SplitProps } from './Split';
|
|
13
11
|
export { Split } from './Split';
|
|
14
12
|
export type { StatusBarProps, StatusBarSectionProps } from './StatusBar';
|