@reekon-tools/boldr-utils 1.26.0 → 1.26.2

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.
Files changed (79) hide show
  1. package/dist/annotation/canvas/pdfCanvasPages.d.ts +7 -4
  2. package/dist/annotation/canvas/pdfCanvasPages.js +93 -14
  3. package/dist/annotation/canvas/tileCalibration.d.ts +16 -0
  4. package/dist/annotation/canvas/tileCalibration.js +91 -0
  5. package/dist/annotation/canvas/useAnnotationCanvasState.js +7 -67
  6. package/dist/annotation/data/hooks/usePdfCanvasDoc.js +24 -7
  7. package/dist/export/pdf/renderCanvasToPdf.js +7 -5
  8. package/dist/export/pdf/types.d.ts +11 -0
  9. package/dist/exports.d.ts +3 -2
  10. package/dist/exports.js +3 -2
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/server.d.ts +3 -2
  14. package/dist/server.js +3 -2
  15. package/dist/types/firestore.d.ts +9 -0
  16. package/dist/types/pdfCanvas.d.ts +12 -1
  17. package/dist/utils/micrometersToUnit.d.ts +11 -0
  18. package/dist/utils/micrometersToUnit.js +27 -0
  19. package/package.json +1 -1
  20. package/dist/canvas/AnnotationCanvas.d.ts +0 -11
  21. package/dist/canvas/AnnotationCanvas.js +0 -10
  22. package/dist/canvas/AnnotationCanvas.native.d.ts +0 -8
  23. package/dist/canvas/AnnotationCanvas.native.js +0 -6
  24. package/dist/canvas/AnnotationCanvasInner.d.ts +0 -39
  25. package/dist/canvas/AnnotationCanvasInner.js +0 -219
  26. package/dist/canvas/AnnotationCanvasInner.native.d.ts +0 -35
  27. package/dist/canvas/AnnotationCanvasInner.native.js +0 -138
  28. package/dist/canvas/AnnotationCanvasSkia.d.ts +0 -27
  29. package/dist/canvas/AnnotationCanvasSkia.js +0 -20
  30. package/dist/canvas/Tool.d.ts +0 -38
  31. package/dist/canvas/Tool.js +0 -1
  32. package/dist/canvas/elements/BackgroundImageElement.d.ts +0 -9
  33. package/dist/canvas/elements/BackgroundImageElement.js +0 -37
  34. package/dist/canvas/elements/MeasurementStampElement.d.ts +0 -13
  35. package/dist/canvas/elements/MeasurementStampElement.js +0 -30
  36. package/dist/canvas/elements/ShapeElement.d.ts +0 -7
  37. package/dist/canvas/elements/ShapeElement.js +0 -62
  38. package/dist/canvas/elements/StrokeElement.d.ts +0 -7
  39. package/dist/canvas/elements/StrokeElement.js +0 -18
  40. package/dist/canvas/measurementPicker.d.ts +0 -10
  41. package/dist/canvas/measurementPicker.js +0 -1
  42. package/dist/canvas/measurementStampOverlay.d.ts +0 -11
  43. package/dist/canvas/measurementStampOverlay.js +0 -1
  44. package/dist/canvas/pointerAdapter.d.ts +0 -3
  45. package/dist/canvas/pointerAdapter.js +0 -19
  46. package/dist/canvas/stampLayout.d.ts +0 -5
  47. package/dist/canvas/stampLayout.js +0 -14
  48. package/dist/canvas/tools/measurementStampTool.d.ts +0 -9
  49. package/dist/canvas/tools/measurementStampTool.js +0 -37
  50. package/dist/canvas/tools/panTool.d.ts +0 -5
  51. package/dist/canvas/tools/panTool.js +0 -25
  52. package/dist/canvas/tools/penTool.d.ts +0 -13
  53. package/dist/canvas/tools/penTool.js +0 -68
  54. package/dist/canvas/tools/selectTool.d.ts +0 -2
  55. package/dist/canvas/tools/selectTool.js +0 -182
  56. package/dist/canvas/useAnnotationCanvasState.d.ts +0 -54
  57. package/dist/canvas/useAnnotationCanvasState.js +0 -210
  58. package/dist/canvas/viewport.d.ts +0 -16
  59. package/dist/canvas/viewport.js +0 -54
  60. package/dist/data/AnnotationDataContext.d.ts +0 -8
  61. package/dist/data/AnnotationDataContext.js +0 -11
  62. package/dist/data/AnnotationDataProvider.d.ts +0 -65
  63. package/dist/data/AnnotationDataProvider.js +0 -4
  64. package/dist/data/InMemoryAnnotationProvider.d.ts +0 -30
  65. package/dist/data/InMemoryAnnotationProvider.js +0 -197
  66. package/dist/data/canvasPersistence.d.ts +0 -3
  67. package/dist/data/canvasPersistence.js +0 -26
  68. package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +0 -33
  69. package/dist/data/hooks/useAnnotationCanvasDoc.js +0 -314
  70. package/dist/data/hooks/useAnnotationDoc.d.ts +0 -7
  71. package/dist/data/hooks/useAnnotationDoc.js +0 -33
  72. package/dist/data/hooks/useAnnotationList.d.ts +0 -7
  73. package/dist/data/hooks/useAnnotationList.js +0 -26
  74. package/dist/data/hooks/useAnnotationMutations.d.ts +0 -9
  75. package/dist/data/hooks/useAnnotationMutations.js +0 -11
  76. package/dist/hooks/useParseMeasurement.d.ts +0 -4
  77. package/dist/hooks/useParseMeasurement.js +0 -14
  78. package/dist/utils/evaluateFormula.d.ts +0 -20
  79. package/dist/utils/evaluateFormula.js +0 -31
@@ -435,6 +435,15 @@ interface ColumnConfigBase {
435
435
  export type ColumnConfig = ColumnConfigBase & ({
436
436
  type: ColumnType.Formula;
437
437
  columnData: FormulaColumnData;
438
+ /**
439
+ * Renders the computed result in the decimal/fractional counterpart
440
+ * of each viewer's unit (in_frac ⇄ in, ft_in_frac ⇄ ft_in_decimal;
441
+ * no-op for units with no counterpart). Absent = follow the unit's
442
+ * own format. Lives on the union member — NOT inside columnData,
443
+ * which every formula-editor save rebuilds wholesale. Clearing
444
+ * removes the key.
445
+ */
446
+ resultDisplay?: 'decimal' | 'fractional';
438
447
  } | {
439
448
  type: ColumnType.SingleSelect;
440
449
  columnData: SelectColumnData;
@@ -1,4 +1,4 @@
1
- import type { AnnotationLayer, AnnotationShape, AnnotationStroke, PlacedMeasurementRef, PlaneCalibration } from './annotation.js';
1
+ import type { AnnotationBackgroundLayer, AnnotationLayer, AnnotationShape, AnnotationStroke, PlacedMeasurementRef, PlaneCalibration } from './annotation.js';
2
2
  export type PdfPageRotation = 0 | 90 | 180 | 270;
3
3
  export interface PdfCanvasPage {
4
4
  width: number;
@@ -23,8 +23,18 @@ export interface PdfCanvasData {
23
23
  headerTileScaleFactor?: number;
24
24
  headerTileScaleFactorMobile?: number;
25
25
  tileDocSize?: number;
26
+ pageTileScales?: Record<string, PdfCanvasTileScales>;
26
27
  annotatedPages: number[];
28
+ placementsByPage?: Record<string, PdfCanvasPlacement[]>;
27
29
  }
30
+ export interface PdfCanvasTileScales {
31
+ tileScaleFactor?: number;
32
+ tileScaleFactorMobile?: number;
33
+ headerTileScaleFactor?: number;
34
+ headerTileScaleFactorMobile?: number;
35
+ tileDocSize?: number;
36
+ }
37
+ export type PdfCanvasPlacement = Pick<PlacedMeasurementRef, 'measurementId' | 'measurementPath' | 'groupId' | 'columnId'>;
28
38
  export interface PdfCanvasPageState {
29
39
  schemaVersion: 1;
30
40
  pageIndex: number;
@@ -33,6 +43,7 @@ export interface PdfCanvasPageState {
33
43
  shapes: AnnotationShape[];
34
44
  placedMeasurements: PlacedMeasurementRef[];
35
45
  planes?: PlaneCalibration[];
46
+ backgroundImages?: AnnotationBackgroundLayer[];
36
47
  updatedAt: number;
37
48
  rev?: PdfCanvasRev;
38
49
  }
@@ -1,4 +1,15 @@
1
1
  import { DecimalTolerance, FractionalTolerance, Units } from '../types/firestore.js';
2
+ /**
3
+ * The display unit for a formula column's result under its per-column
4
+ * decimal/fractional switch. Fraction-vs-decimal is decided entirely by the
5
+ * Units value (convertMicrometers has no format parameter), so the switch is
6
+ * a substitution between same-scale counterparts: in_frac ⇄ in and
7
+ * ft_in_frac ⇄ ft_in_decimal. Units with no counterpart — metric, and plain
8
+ * ft (the enum has no fractional-feet form) — pass through unchanged, so the
9
+ * setting is a no-op for those viewers. Same physical scale both ways: this
10
+ * can never change a computed number, only its rendering.
11
+ */
12
+ export declare const resultDisplayUnit: (unit: Units, resultDisplay?: "decimal" | "fractional") => Units;
2
13
  export declare const roundToT1Values: (value: number, u: Units) => number;
3
14
  export declare const convertMicrometers: (micrometers: number | null | undefined, unit: Units, fractionalTolerance?: FractionalTolerance, decimalTolerance?: DecimalTolerance, device?: string) => {
4
15
  value: string;
@@ -1,6 +1,33 @@
1
1
  import { DecimalTolerance, FractionalTolerance, Units, convertUnitsToReadable, } from '../types/firestore.js';
2
2
  import { create, all } from 'mathjs';
3
3
  const math = create(all);
4
+ /**
5
+ * The display unit for a formula column's result under its per-column
6
+ * decimal/fractional switch. Fraction-vs-decimal is decided entirely by the
7
+ * Units value (convertMicrometers has no format parameter), so the switch is
8
+ * a substitution between same-scale counterparts: in_frac ⇄ in and
9
+ * ft_in_frac ⇄ ft_in_decimal. Units with no counterpart — metric, and plain
10
+ * ft (the enum has no fractional-feet form) — pass through unchanged, so the
11
+ * setting is a no-op for those viewers. Same physical scale both ways: this
12
+ * can never change a computed number, only its rendering.
13
+ */
14
+ export const resultDisplayUnit = (unit, resultDisplay) => {
15
+ if (resultDisplay === 'decimal') {
16
+ if (unit === Units.FractionalInches)
17
+ return Units.Inches;
18
+ if (unit === Units.FeetInchesFractional)
19
+ return Units.FeetInchesDecimal;
20
+ return unit;
21
+ }
22
+ if (resultDisplay === 'fractional') {
23
+ if (unit === Units.Inches)
24
+ return Units.FractionalInches;
25
+ if (unit === Units.FeetInchesDecimal)
26
+ return Units.FeetInchesFractional;
27
+ return unit;
28
+ }
29
+ return unit;
30
+ };
4
31
  // The T1 rounds differently depending on the selected measurement unit.
5
32
  // This function mirrors the rounding behavior implemented on the firmware.
6
33
  // The value being rounded is in microns.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.26.0",
3
+ "version": "1.26.2",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",
@@ -1,11 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.js';
3
- export type { AnnotationCanvasHandle, GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
4
- export interface CanvasKitOpts {
5
- locateFile?: (file: string) => string;
6
- }
7
- export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
8
- fallback?: ReactNode;
9
- canvasKitOpts?: CanvasKitOpts;
10
- };
11
- export declare const AnnotationCanvas: ({ fallback, canvasKitOpts, ...rest }: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,10 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { WithSkiaWeb } from '@shopify/react-native-skia/lib/module/web/index.js';
3
- // Web-only entry point: lazy-loads CanvasKit wasm via WithSkiaWeb. Imperative
4
- // API (undo/redo/zoom) is exposed through the `imperativeRef` prop rather
5
- // than React refs, since WithSkiaWeb can't forward refs through its lazy
6
- // component boundary. The native build sits in `AnnotationCanvas.native.tsx`
7
- // and skips WithSkiaWeb entirely (no wasm to load).
8
- export const AnnotationCanvas = ({ fallback, canvasKitOpts, ...rest }) => (_jsx(WithSkiaWeb, { getComponent: () => import('./AnnotationCanvasInner.js').then((m) => ({
9
- default: m.AnnotationCanvasInner,
10
- })), fallback: fallback ?? null, componentProps: rest, opts: canvasKitOpts }));
@@ -1,8 +0,0 @@
1
- import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.native.js';
2
- export type { AnnotationCanvasHandle, } from './useAnnotationCanvasState.js';
3
- export type { GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
4
- export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
5
- fallback?: unknown;
6
- canvasKitOpts?: unknown;
7
- };
8
- export declare const AnnotationCanvas: (props: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,6 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { AnnotationCanvasInner } from './AnnotationCanvasInner.native.js';
3
- export const AnnotationCanvas = (props) => {
4
- const { fallback: _f, canvasKitOpts: _o, ...rest } = props;
5
- return _jsx(AnnotationCanvasInner, { ...rest });
6
- };
@@ -1,39 +0,0 @@
1
- import { type CSSProperties, type MutableRefObject } from 'react';
2
- import type { DecimalTolerance, FractionalTolerance, Measurement, Units } from '../types/firestore.js';
3
- import type { AnnotationCanvasState, AnnotationDocumentPatch, Selection } from '../types/annotation.js';
4
- import type { MeasurementRef } from './measurementPicker.js';
5
- import type { Tool } from './Tool.js';
6
- import { type AnnotationCanvasHandle } from './useAnnotationCanvasState.js';
7
- import { type ViewportState } from './viewport.js';
8
- import type { RenderMeasurementStamp } from './measurementStampOverlay.js';
9
- export type { AnnotationCanvasHandle };
10
- export interface AnnotationCanvasInnerProps {
11
- canvas: AnnotationCanvasState;
12
- onCommit(patch: AnnotationDocumentPatch): void;
13
- tools: Tool[];
14
- activeToolId: string;
15
- selection: Selection | null;
16
- onSelectionChange(selection: Selection | null): void;
17
- measurements?: Measurement[];
18
- fallbackUnit?: Units;
19
- fractionalTolerance?: FractionalTolerance;
20
- decimalTolerance?: DecimalTolerance;
21
- resolveImageUrl?: (storagePath: string) => Promise<string>;
22
- pickMeasurement?: () => Promise<MeasurementRef | null>;
23
- renderMeasurementStamp?: RenderMeasurementStamp;
24
- stampFontSource?: unknown;
25
- stampValueFontSize?: number;
26
- stampLabelFontSize?: number;
27
- gestures?: GestureConfig;
28
- width: number;
29
- height: number;
30
- initialViewport?: ViewportState;
31
- style?: CSSProperties;
32
- imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
33
- }
34
- export type PanTrigger = 'middleMouse' | 'rightMouse' | 'space';
35
- export interface GestureConfig {
36
- wheel?: 'zoom' | 'pan' | 'auto';
37
- panTriggers?: PanTrigger[];
38
- }
39
- export declare const AnnotationCanvasInner: (props: AnnotationCanvasInnerProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,219 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useFont } from '@shopify/react-native-skia';
3
- import { useCallback, useEffect, useRef, } from 'react';
4
- import { AnnotationCanvasSkia } from './AnnotationCanvasSkia.js';
5
- import { useAnnotationCanvasState, } from './useAnnotationCanvasState.js';
6
- import { STAMP_TILE_SIZE } from './stampLayout.js';
7
- const DEFAULT_PAN_TRIGGERS = ['middleMouse', 'space'];
8
- export const AnnotationCanvasInner = (props) => {
9
- const { fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, stampFontSource, stampValueFontSize = 14, stampLabelFontSize = 11, gestures, width, height, style, activeToolId, tools, } = props;
10
- const wheelMode = gestures?.wheel ?? 'auto';
11
- const panTriggers = gestures?.panTriggers ?? DEFAULT_PAN_TRIGGERS;
12
- const allowSpacePan = panTriggers.includes('space');
13
- const allowMiddlePan = panTriggers.includes('middleMouse');
14
- const allowRightPan = panTriggers.includes('rightMouse');
15
- const valueFont = useFont(stampFontSource, stampValueFontSize);
16
- const labelFont = useFont(stampFontSource, stampLabelFontSize);
17
- const state = useAnnotationCanvasState(props);
18
- const containerRef = useRef(null);
19
- const panGestureRef = useRef(null);
20
- const spaceDownRef = useRef(false);
21
- const activeTool = tools.find((t) => t.id === activeToolId) ?? null;
22
- const toCanvasPointer = useCallback((event) => {
23
- const rect = containerRef.current?.getBoundingClientRect();
24
- const screen = {
25
- x: event.clientX - (rect?.left ?? 0),
26
- y: event.clientY - (rect?.top ?? 0),
27
- };
28
- return {
29
- pointerId: event.pointerId,
30
- screen,
31
- world: state.ctx.viewport.screenToWorld(screen),
32
- pressure: event.pressure || undefined,
33
- shiftKey: event.shiftKey,
34
- altKey: event.altKey,
35
- metaKey: event.metaKey,
36
- ctrlKey: event.ctrlKey,
37
- };
38
- }, [state.ctx.viewport]);
39
- const isPanTriggerDown = useCallback((event) => {
40
- if (allowMiddlePan && event.button === 1)
41
- return true;
42
- if (allowRightPan && event.button === 2)
43
- return true;
44
- if (allowSpacePan && event.button === 0 && spaceDownRef.current)
45
- return true;
46
- return false;
47
- }, [allowMiddlePan, allowRightPan, allowSpacePan]);
48
- const handlePointerDown = useCallback((event) => {
49
- const rect = containerRef.current?.getBoundingClientRect();
50
- const screen = {
51
- x: event.clientX - (rect?.left ?? 0),
52
- y: event.clientY - (rect?.top ?? 0),
53
- };
54
- if (isPanTriggerDown(event)) {
55
- panGestureRef.current = { pointerId: event.pointerId, lastScreen: screen };
56
- event.currentTarget.setPointerCapture(event.pointerId);
57
- event.preventDefault();
58
- return;
59
- }
60
- event.currentTarget.setPointerCapture(event.pointerId);
61
- state.dispatchPointerDown(toCanvasPointer(event));
62
- }, [state, toCanvasPointer, isPanTriggerDown]);
63
- const handlePointerMove = useCallback((event) => {
64
- const pan = panGestureRef.current;
65
- if (pan && event.pointerId === pan.pointerId) {
66
- const rect = containerRef.current?.getBoundingClientRect();
67
- const screen = {
68
- x: event.clientX - (rect?.left ?? 0),
69
- y: event.clientY - (rect?.top ?? 0),
70
- };
71
- state.pan({
72
- x: screen.x - pan.lastScreen.x,
73
- y: screen.y - pan.lastScreen.y,
74
- });
75
- panGestureRef.current = { pointerId: pan.pointerId, lastScreen: screen };
76
- return;
77
- }
78
- state.dispatchPointerMove(toCanvasPointer(event));
79
- }, [state, toCanvasPointer]);
80
- const handlePointerUp = useCallback((event) => {
81
- const pan = panGestureRef.current;
82
- if (pan && event.pointerId === pan.pointerId) {
83
- panGestureRef.current = null;
84
- return;
85
- }
86
- state.dispatchPointerUp(toCanvasPointer(event));
87
- }, [state, toCanvasPointer]);
88
- const handlePointerCancel = useCallback(() => {
89
- panGestureRef.current = null;
90
- state.dispatchPointerCancel();
91
- }, [state]);
92
- const handleWheel = useCallback((event) => {
93
- const rect = containerRef.current?.getBoundingClientRect();
94
- const focal = {
95
- x: event.clientX - (rect?.left ?? 0),
96
- y: event.clientY - (rect?.top ?? 0),
97
- };
98
- if (event.ctrlKey || event.metaKey) {
99
- const factor = Math.exp(-event.deltaY / 200);
100
- state.zoom(focal, state.ctx.viewport.state.zoom * factor);
101
- return;
102
- }
103
- if (wheelMode === 'zoom') {
104
- const factor = Math.exp(-event.deltaY / 300);
105
- state.zoom(focal, state.ctx.viewport.state.zoom * factor);
106
- return;
107
- }
108
- if (event.shiftKey) {
109
- state.pan({ x: event.deltaY, y: event.deltaX });
110
- return;
111
- }
112
- state.pan({ x: event.deltaX, y: event.deltaY });
113
- }, [state, wheelMode]);
114
- const handleContextMenu = useCallback((event) => {
115
- if (allowRightPan)
116
- event.preventDefault();
117
- }, [allowRightPan]);
118
- useEffect(() => {
119
- const el = containerRef.current;
120
- if (!el)
121
- return;
122
- const stop = (e) => {
123
- if (e.target === el || el.contains(e.target)) {
124
- e.preventDefault();
125
- }
126
- };
127
- el.addEventListener('wheel', stop, { passive: false });
128
- return () => el.removeEventListener('wheel', stop);
129
- }, []);
130
- useEffect(() => {
131
- if (!allowSpacePan)
132
- return;
133
- const onKeyDown = (e) => {
134
- if (e.code === 'Space' && !e.repeat) {
135
- spaceDownRef.current = true;
136
- if (containerRef.current)
137
- containerRef.current.style.cursor = 'grab';
138
- }
139
- };
140
- const onKeyUp = (e) => {
141
- if (e.code === 'Space') {
142
- spaceDownRef.current = false;
143
- if (containerRef.current)
144
- containerRef.current.style.cursor = '';
145
- }
146
- };
147
- window.addEventListener('keydown', onKeyDown);
148
- window.addEventListener('keyup', onKeyUp);
149
- return () => {
150
- window.removeEventListener('keydown', onKeyDown);
151
- window.removeEventListener('keyup', onKeyUp);
152
- };
153
- }, [allowSpacePan]);
154
- const containerStyle = {
155
- position: 'relative',
156
- width,
157
- height,
158
- overflow: 'hidden',
159
- touchAction: 'none',
160
- userSelect: 'none',
161
- cursor: activeTool?.cursor ?? 'default',
162
- ...style,
163
- };
164
- const customPreview = activeTool?.renderPreview?.(state.customPreviewState, state.ctx);
165
- const { renderMeasurementStamp, selection } = props;
166
- return (_jsxs("div", { ref: containerRef, style: containerStyle, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerCancel, onWheel: handleWheel, onContextMenu: handleContextMenu, children: [AnnotationCanvasSkia({
167
- width,
168
- height,
169
- effectiveCanvas: state.effectiveCanvas,
170
- worldTransform: state.worldTransform,
171
- measurementsById: state.measurementsById,
172
- fallbackUnit,
173
- fractionalTolerance,
174
- decimalTolerance,
175
- resolveImageUrl,
176
- valueFont,
177
- labelFont,
178
- hideMeasurementStamps: !!renderMeasurementStamp,
179
- penDrawingStroke: state.penDrawingStroke,
180
- customPreview,
181
- }), renderMeasurementStamp && (_jsx("div", { style: {
182
- position: 'absolute',
183
- inset: 0,
184
- pointerEvents: 'none',
185
- }, children: state.effectiveCanvas.placedMeasurements.map((placed) => {
186
- const size = STAMP_TILE_SIZE * (placed.scale ?? 1);
187
- const cx = (placed.anchor.x - state.viewport.pan.x) * state.viewport.zoom;
188
- const cy = (placed.anchor.y - state.viewport.pan.y) * state.viewport.zoom;
189
- const isSelected = selection?.ids.includes(placed.id) ?? false;
190
- return (_jsxs("div", { style: {
191
- position: 'absolute',
192
- left: 0,
193
- top: 0,
194
- width: size,
195
- height: size,
196
- transform: `translate(${cx - size / 2}px, ${cy - size / 2}px)`,
197
- }, children: [renderMeasurementStamp({
198
- placed,
199
- measurement: state.measurementsById.get(placed.measurementId) ?? null,
200
- selected: isSelected,
201
- size,
202
- zoom: state.viewport.zoom,
203
- }), isSelected && (_jsx("div", { role: "button", "aria-label": "Remove measurement", onPointerDown: (e) => {
204
- e.stopPropagation();
205
- state.ctx.commit({
206
- ops: [{ op: 'removeMeasurement', id: placed.id }],
207
- });
208
- state.ctx.setSelection(null);
209
- }, style: {
210
- position: 'absolute',
211
- top: -10,
212
- right: -10,
213
- width: 40,
214
- height: 40,
215
- cursor: 'pointer',
216
- pointerEvents: 'auto',
217
- } }))] }, placed.id));
218
- }) }))] }));
219
- };
@@ -1,35 +0,0 @@
1
- import { type MutableRefObject } from 'react';
2
- import { type ViewStyle } from 'react-native';
3
- import type { RenderMeasurementStamp } from './measurementStampOverlay.js';
4
- import type { DecimalTolerance, FractionalTolerance, Measurement, Units } from '../types/firestore.js';
5
- import type { AnnotationCanvasState, AnnotationDocumentPatch, Selection } from '../types/annotation.js';
6
- import type { MeasurementRef } from './measurementPicker.js';
7
- import type { Tool } from './Tool.js';
8
- import { type AnnotationCanvasHandle } from './useAnnotationCanvasState.js';
9
- import type { ViewportState } from './viewport.js';
10
- export type { AnnotationCanvasHandle };
11
- export interface AnnotationCanvasInnerProps {
12
- canvas: AnnotationCanvasState;
13
- onCommit(patch: AnnotationDocumentPatch): void;
14
- tools: Tool[];
15
- activeToolId: string;
16
- selection: Selection | null;
17
- onSelectionChange(selection: Selection | null): void;
18
- measurements?: Measurement[];
19
- fallbackUnit?: Units;
20
- fractionalTolerance?: FractionalTolerance;
21
- decimalTolerance?: DecimalTolerance;
22
- resolveImageUrl?: (storagePath: string) => Promise<string>;
23
- pickMeasurement?: () => Promise<MeasurementRef | null>;
24
- renderMeasurementStamp?: RenderMeasurementStamp;
25
- stampFontSource?: unknown;
26
- stampValueFontSize?: number;
27
- stampLabelFontSize?: number;
28
- gestures?: unknown;
29
- width: number;
30
- height: number;
31
- initialViewport?: ViewportState;
32
- style?: ViewStyle;
33
- imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
34
- }
35
- export declare const AnnotationCanvasInner: (props: AnnotationCanvasInnerProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,138 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useFont } from '@shopify/react-native-skia';
3
- import { useMemo, useRef } from 'react';
4
- import { StyleSheet, TouchableOpacity, View } from 'react-native';
5
- import { Gesture, GestureDetector, GestureHandlerRootView, } from 'react-native-gesture-handler';
6
- import { STAMP_TILE_SIZE } from './stampLayout.js';
7
- import { AnnotationCanvasSkia } from './AnnotationCanvasSkia.js';
8
- import { useAnnotationCanvasState, } from './useAnnotationCanvasState.js';
9
- // Native fingerprint: one finger drives the active tool, two fingers
10
- // pan/zoom the viewport. Tap counts as a brief pointer down+up so tools
11
- // like measurement-stamp (which only listen to onPointerUp) work via tap.
12
- export const AnnotationCanvasInner = (props) => {
13
- const { fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, stampFontSource, stampValueFontSize = 14, stampLabelFontSize = 11, width, height, style, } = props;
14
- const valueFont = useFont(stampFontSource, stampValueFontSize);
15
- const labelFont = useFont(stampFontSource, stampLabelFontSize);
16
- const state = useAnnotationCanvasState(props);
17
- // Per-gesture refs so we always emit a matching down/move/up sequence.
18
- const pointerIdRef = useRef(1);
19
- const inFlightRef = useRef(null);
20
- const pinchStartZoomRef = useRef(1);
21
- const buildEvent = (pointerId, screen) => ({
22
- pointerId,
23
- screen,
24
- world: state.ctx.viewport.screenToWorld(screen),
25
- });
26
- const gesture = useMemo(() => {
27
- const toolPan = Gesture.Pan()
28
- .minPointers(1)
29
- .maxPointers(1)
30
- .runOnJS(true)
31
- .onBegin((e) => {
32
- const id = pointerIdRef.current++;
33
- const screen = { x: e.x, y: e.y };
34
- inFlightRef.current = { id, lastScreen: screen };
35
- state.dispatchPointerDown(buildEvent(id, screen));
36
- })
37
- .onUpdate((e) => {
38
- const f = inFlightRef.current;
39
- if (!f)
40
- return;
41
- const screen = { x: e.x, y: e.y };
42
- f.lastScreen = screen;
43
- state.dispatchPointerMove(buildEvent(f.id, screen));
44
- })
45
- .onEnd((e) => {
46
- const f = inFlightRef.current;
47
- if (!f)
48
- return;
49
- state.dispatchPointerUp(buildEvent(f.id, { x: e.x, y: e.y }));
50
- inFlightRef.current = null;
51
- })
52
- .onFinalize(() => {
53
- if (inFlightRef.current) {
54
- state.dispatchPointerCancel();
55
- inFlightRef.current = null;
56
- }
57
- });
58
- const tap = Gesture.Tap()
59
- .maxDuration(250)
60
- .runOnJS(true)
61
- .onEnd((e) => {
62
- const id = pointerIdRef.current++;
63
- const screen = { x: e.x, y: e.y };
64
- // Synthesize a down+up sequence so tools that only listen to
65
- // onPointerUp (e.g. measurement stamp) still fire.
66
- state.dispatchPointerDown(buildEvent(id, screen));
67
- state.dispatchPointerUp(buildEvent(id, screen));
68
- });
69
- const viewportPan = Gesture.Pan()
70
- .minPointers(2)
71
- .maxPointers(2)
72
- .runOnJS(true)
73
- .onChange((e) => {
74
- state.pan({ x: e.changeX, y: e.changeY });
75
- });
76
- const pinch = Gesture.Pinch()
77
- .runOnJS(true)
78
- .onBegin(() => {
79
- pinchStartZoomRef.current = state.ctx.viewport.state.zoom;
80
- })
81
- .onUpdate((e) => {
82
- state.zoom({ x: e.focalX, y: e.focalY }, pinchStartZoomRef.current * e.scale);
83
- });
84
- return Gesture.Race(tap, Gesture.Simultaneous(viewportPan, pinch), toolPan);
85
- }, [state]);
86
- const activeTool = props.tools.find((t) => t.id === props.activeToolId) ?? null;
87
- const customPreview = activeTool?.renderPreview?.(state.customPreviewState, state.ctx);
88
- const { renderMeasurementStamp, selection } = props;
89
- return (_jsxs(GestureHandlerRootView, { style: [{ width, height }, style], children: [_jsx(GestureDetector, { gesture: gesture, children: _jsx(View, { style: { width, height }, collapsable: false, children: AnnotationCanvasSkia({
90
- width,
91
- height,
92
- effectiveCanvas: state.effectiveCanvas,
93
- worldTransform: state.worldTransform,
94
- measurementsById: state.measurementsById,
95
- fallbackUnit,
96
- fractionalTolerance,
97
- decimalTolerance,
98
- resolveImageUrl,
99
- valueFont,
100
- labelFont,
101
- hideMeasurementStamps: !!renderMeasurementStamp,
102
- penDrawingStroke: state.penDrawingStroke,
103
- customPreview,
104
- }) }) }), renderMeasurementStamp && (_jsx(View, { pointerEvents: "box-none", style: StyleSheet.absoluteFill, children: state.effectiveCanvas.placedMeasurements.map((placed) => {
105
- const size = STAMP_TILE_SIZE * (placed.scale ?? 1);
106
- const cx = (placed.anchor.x - state.viewport.pan.x) * state.viewport.zoom;
107
- const cy = (placed.anchor.y - state.viewport.pan.y) * state.viewport.zoom;
108
- const isSelected = selection?.ids.includes(placed.id) ?? false;
109
- return (_jsxs(View, { pointerEvents: "box-none", style: {
110
- position: 'absolute',
111
- left: 0,
112
- top: 0,
113
- width: size,
114
- height: size,
115
- transform: [
116
- { translateX: cx - size / 2 },
117
- { translateY: cy - size / 2 },
118
- ],
119
- }, children: [_jsx(View, { pointerEvents: "none", style: StyleSheet.absoluteFill, children: renderMeasurementStamp({
120
- placed,
121
- measurement: state.measurementsById.get(placed.measurementId) ?? null,
122
- selected: isSelected,
123
- size,
124
- zoom: state.viewport.zoom,
125
- }) }), isSelected && (_jsx(TouchableOpacity, { accessibilityRole: "button", accessibilityLabel: "Remove measurement", hitSlop: 10, onPress: () => {
126
- state.ctx.commit({
127
- ops: [{ op: 'removeMeasurement', id: placed.id }],
128
- });
129
- state.ctx.setSelection(null);
130
- }, style: {
131
- position: 'absolute',
132
- top: -8,
133
- right: -8,
134
- width: 36,
135
- height: 36,
136
- } }))] }, placed.id));
137
- }) }))] }));
138
- };
@@ -1,27 +0,0 @@
1
- import { type SkFont } from '@shopify/react-native-skia';
2
- import type { ReactNode } from 'react';
3
- import type { AnnotationCanvasState, AnnotationStroke } from '../types/annotation.js';
4
- import { DecimalTolerance, FractionalTolerance, Units, type Measurement } from '../types/firestore.js';
5
- export interface AnnotationCanvasSkiaProps {
6
- width: number;
7
- height: number;
8
- effectiveCanvas: AnnotationCanvasState;
9
- worldTransform: Array<{
10
- scale: number;
11
- } | {
12
- translateX: number;
13
- } | {
14
- translateY: number;
15
- }>;
16
- measurementsById: Map<string, Measurement>;
17
- fallbackUnit?: Units;
18
- fractionalTolerance?: FractionalTolerance;
19
- decimalTolerance?: DecimalTolerance;
20
- resolveImageUrl?: (storagePath: string) => Promise<string>;
21
- valueFont: SkFont | null;
22
- labelFont: SkFont | null;
23
- hideMeasurementStamps?: boolean;
24
- penDrawingStroke: AnnotationStroke | null;
25
- customPreview?: ReactNode;
26
- }
27
- export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, measurementsById, fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, valueFont, labelFont, hideMeasurementStamps, penDrawingStroke, customPreview, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,20 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Canvas, Group } from '@shopify/react-native-skia';
3
- import { Units, } from '../types/firestore.js';
4
- import { BackgroundImageElement } from './elements/BackgroundImageElement.js';
5
- import { MeasurementStampElement } from './elements/MeasurementStampElement.js';
6
- import { ShapeElement } from './elements/ShapeElement.js';
7
- import { StrokeElement } from './elements/StrokeElement.js';
8
- // Platform-agnostic Skia subtree shared by web and native Inners.
9
- //
10
- // Call this as a FUNCTION (`AnnotationCanvasSkia({ ... })`), not as a JSX
11
- // component (`<AnnotationCanvasSkia ... />`). Used as a component, it adds
12
- // a React component boundary between the parent and Skia's `<Canvas>` that
13
- // breaks Skia's reconciler when the page first calls `useFont` — the JS
14
- // thread hangs in `MakeFreeTypeFaceFromData`. Symptoms only appeared on
15
- // Vite's dev server with a symlinked boldr-utils + React Refresh, but
16
- // since the function-call pattern works identically on native we use it
17
- // in both Inners for consistency. Don't add hooks here; this is a plain
18
- // JSX-returning helper, not a component.
19
- export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, measurementsById, fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, valueFont, labelFont, hideMeasurementStamps, penDrawingStroke, customPreview, }) => (_jsx(Canvas, { style: { width, height }, children: _jsxs(Group, { transform: worldTransform, children: [effectiveCanvas.viewport.backgroundImage && (_jsx(BackgroundImageElement, { image: effectiveCanvas.viewport.backgroundImage, docWidth: effectiveCanvas.viewport.width, docHeight: effectiveCanvas.viewport.height, fit: effectiveCanvas.viewport.backgroundFit ?? 'contain', resolveUrl: resolveImageUrl })), effectiveCanvas.strokes.map((stroke) => (_jsx(StrokeElement, { stroke: stroke }, stroke.id))), effectiveCanvas.shapes.map((shape) => (_jsx(ShapeElement, { shape: shape, font: valueFont }, shape.id))), !hideMeasurementStamps &&
20
- effectiveCanvas.placedMeasurements.map((placed) => (_jsx(MeasurementStampElement, { placed: placed, measurement: measurementsById.get(placed.measurementId) ?? null, fallbackUnit: fallbackUnit ?? Units.Millimeters, fractionalTolerance: fractionalTolerance, decimalTolerance: decimalTolerance, valueFont: valueFont, labelFont: labelFont }, placed.id))), penDrawingStroke && _jsx(StrokeElement, { stroke: penDrawingStroke }), customPreview] }) }));