@reekon-tools/boldr-utils 1.6.10 → 1.6.12
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/{canvas → annotation/canvas}/AnnotationCanvasInner.d.ts +2 -2
- package/dist/{canvas → annotation/canvas}/AnnotationCanvasInner.js +19 -14
- package/dist/{canvas → annotation/canvas}/AnnotationCanvasInner.native.d.ts +2 -2
- package/dist/annotation/canvas/AnnotationCanvasInner.native.js +668 -0
- package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +46 -0
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +129 -0
- package/dist/{canvas → annotation/canvas}/Tool.d.ts +23 -1
- package/dist/{canvas → annotation/canvas}/elements/BackgroundImageElement.d.ts +2 -2
- package/dist/{canvas → annotation/canvas}/elements/BackgroundImageElement.js +13 -6
- package/dist/annotation/canvas/elements/ShapeElement.d.ts +7 -0
- package/dist/{canvas → annotation/canvas}/elements/ShapeElement.js +5 -3
- package/dist/annotation/canvas/elements/StrokeElement.d.ts +7 -0
- package/dist/annotation/canvas/elements/StrokeElement.js +40 -0
- package/dist/annotation/canvas/measurementGeometry.d.ts +23 -0
- package/dist/annotation/canvas/measurementGeometry.js +74 -0
- package/dist/{canvas → annotation/canvas}/measurementPicker.d.ts +1 -1
- package/dist/{canvas → annotation/canvas}/measurementStampOverlay.d.ts +2 -2
- package/dist/annotation/canvas/stampLayout.d.ts +1 -0
- package/dist/annotation/canvas/stampLayout.js +11 -0
- package/dist/annotation/canvas/strokeGeometry.d.ts +4 -0
- package/dist/annotation/canvas/strokeGeometry.js +33 -0
- package/dist/{canvas → annotation/canvas}/tools/measurementStampTool.d.ts +1 -1
- package/dist/{canvas → annotation/canvas}/tools/measurementStampTool.js +1 -1
- package/dist/{canvas → annotation/canvas}/tools/panTool.js +3 -0
- package/dist/{canvas → annotation/canvas}/tools/penTool.d.ts +2 -1
- package/dist/{canvas → annotation/canvas}/tools/penTool.js +32 -5
- package/dist/annotation/canvas/tools/selectTool.js +310 -0
- package/dist/{canvas → annotation/canvas}/useAnnotationCanvasState.d.ts +9 -2
- package/dist/{canvas → annotation/canvas}/useAnnotationCanvasState.js +115 -1
- package/dist/{canvas → annotation/canvas}/viewport.d.ts +1 -1
- package/dist/{data → annotation/data}/AnnotationDataProvider.d.ts +1 -1
- package/dist/{data → annotation/data}/InMemoryAnnotationProvider.d.ts +1 -1
- package/dist/{data → annotation/data}/InMemoryAnnotationProvider.js +1 -1
- package/dist/{data → annotation/data}/canvasPersistence.d.ts +1 -1
- package/dist/{data → annotation/data}/canvasPersistence.js +1 -1
- package/dist/{data → annotation/data}/hooks/useAnnotationCanvasDoc.d.ts +1 -1
- package/dist/{data → annotation/data}/hooks/useAnnotationCanvasDoc.js +2 -2
- package/dist/exports.d.ts +21 -19
- package/dist/exports.js +16 -14
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.native.d.ts +1 -1
- package/dist/index.native.js +1 -1
- package/dist/types/annotation.d.ts +15 -3
- package/dist/{hooks → utils}/useParseMeasurement.js +1 -1
- package/package.json +1 -1
- package/dist/canvas/AnnotationCanvasInner.native.js +0 -138
- package/dist/canvas/AnnotationCanvasSkia.d.ts +0 -27
- package/dist/canvas/AnnotationCanvasSkia.js +0 -20
- package/dist/canvas/elements/MeasurementStampElement.d.ts +0 -13
- package/dist/canvas/elements/MeasurementStampElement.js +0 -30
- package/dist/canvas/elements/ShapeElement.d.ts +0 -7
- package/dist/canvas/elements/StrokeElement.d.ts +0 -7
- package/dist/canvas/elements/StrokeElement.js +0 -18
- package/dist/canvas/stampLayout.d.ts +0 -5
- package/dist/canvas/stampLayout.js +0 -14
- package/dist/canvas/tools/selectTool.js +0 -182
- package/dist/utils/evaluateFormula.d.ts +0 -20
- package/dist/utils/evaluateFormula.js +0 -31
- /package/dist/{canvas → annotation/canvas}/AnnotationCanvas.d.ts +0 -0
- /package/dist/{canvas → annotation/canvas}/AnnotationCanvas.js +0 -0
- /package/dist/{canvas → annotation/canvas}/AnnotationCanvas.native.d.ts +0 -0
- /package/dist/{canvas → annotation/canvas}/AnnotationCanvas.native.js +0 -0
- /package/dist/{canvas → annotation/canvas}/Tool.js +0 -0
- /package/dist/{canvas → annotation/canvas}/measurementPicker.js +0 -0
- /package/dist/{canvas → annotation/canvas}/measurementStampOverlay.js +0 -0
- /package/dist/{canvas → annotation/canvas}/pointerAdapter.d.ts +0 -0
- /package/dist/{canvas → annotation/canvas}/pointerAdapter.js +0 -0
- /package/dist/{canvas → annotation/canvas}/tools/panTool.d.ts +0 -0
- /package/dist/{canvas → annotation/canvas}/tools/selectTool.d.ts +0 -0
- /package/dist/{canvas → annotation/canvas}/viewport.js +0 -0
- /package/dist/{data → annotation/data}/AnnotationDataContext.d.ts +0 -0
- /package/dist/{data → annotation/data}/AnnotationDataContext.js +0 -0
- /package/dist/{data → annotation/data}/AnnotationDataProvider.js +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationDoc.d.ts +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationDoc.js +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationList.d.ts +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationList.js +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationMutations.d.ts +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationMutations.js +0 -0
- /package/dist/{hooks → utils}/useParseMeasurement.d.ts +0 -0
package/dist/index.native.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './exports.js';
|
|
2
|
-
export { AnnotationCanvas, type AnnotationCanvasProps, } from './canvas/AnnotationCanvas.native.js';
|
|
2
|
+
export { AnnotationCanvas, type AnnotationCanvasProps, } from './annotation/canvas/AnnotationCanvas.native.js';
|
|
3
3
|
export interface CanvasKitOpts {
|
|
4
4
|
locateFile?: (file: string) => string;
|
|
5
5
|
}
|
package/dist/index.native.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
// WithSkiaWeb / canvaskit-wasm), avoiding the `fs` import that Metro
|
|
4
4
|
// chokes on.
|
|
5
5
|
export * from './exports.js';
|
|
6
|
-
export { AnnotationCanvas, } from './canvas/AnnotationCanvas.native.js';
|
|
6
|
+
export { AnnotationCanvas, } from './annotation/canvas/AnnotationCanvas.native.js';
|
|
@@ -5,12 +5,14 @@ export interface Vec2 {
|
|
|
5
5
|
x: number;
|
|
6
6
|
y: number;
|
|
7
7
|
}
|
|
8
|
+
export type StrokeCap = 'butt' | 'round' | 'square' | 'arrow';
|
|
8
9
|
export interface AnnotationStroke {
|
|
9
10
|
id: AnnotationElementId;
|
|
10
11
|
layerId: AnnotationLayerId;
|
|
11
12
|
tool: 'pen' | 'marker' | 'highlighter';
|
|
12
13
|
color: string;
|
|
13
14
|
width: number;
|
|
15
|
+
cap?: StrokeCap;
|
|
14
16
|
points: number[];
|
|
15
17
|
pressure?: number[];
|
|
16
18
|
createdAt: number;
|
|
@@ -35,13 +37,23 @@ export interface AnnotationShape {
|
|
|
35
37
|
text?: string;
|
|
36
38
|
createdAt: number;
|
|
37
39
|
}
|
|
40
|
+
export type MeasurementPlacement = 'none' | 'line' | 'rectangle';
|
|
38
41
|
export interface PlacedMeasurementRef {
|
|
39
42
|
id: AnnotationElementId;
|
|
40
43
|
layerId: AnnotationLayerId;
|
|
41
|
-
measurementPath
|
|
42
|
-
measurementId
|
|
43
|
-
groupId
|
|
44
|
+
measurementPath?: string;
|
|
45
|
+
measurementId?: string;
|
|
46
|
+
groupId?: string;
|
|
44
47
|
anchor: Vec2;
|
|
48
|
+
placement?: MeasurementPlacement;
|
|
49
|
+
line?: {
|
|
50
|
+
a: Vec2;
|
|
51
|
+
b: Vec2;
|
|
52
|
+
};
|
|
53
|
+
linePos?: number;
|
|
54
|
+
lineColor?: string;
|
|
55
|
+
lineWidth?: number;
|
|
56
|
+
lineCap?: StrokeCap;
|
|
45
57
|
leader?: {
|
|
46
58
|
from: Vec2;
|
|
47
59
|
to: Vec2;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { parseMeasurement } from '
|
|
2
|
+
import { parseMeasurement } from './parseMeasurement.js';
|
|
3
3
|
export const useParseMeasurement = () => {
|
|
4
4
|
const [error, setError] = useState(null);
|
|
5
5
|
const parseMeasurementInput = (input, defaultUnit = 'mm') => {
|
package/package.json
CHANGED
|
@@ -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] }) }));
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type SkFont } from '@shopify/react-native-skia';
|
|
2
|
-
import type { PlacedMeasurementRef } from '../../types/annotation.js';
|
|
3
|
-
import { DecimalTolerance, FractionalTolerance, type Measurement, type Units } from '../../types/firestore.js';
|
|
4
|
-
export interface MeasurementStampElementProps {
|
|
5
|
-
placed: PlacedMeasurementRef;
|
|
6
|
-
measurement: Measurement | null;
|
|
7
|
-
fallbackUnit: Units;
|
|
8
|
-
fractionalTolerance?: FractionalTolerance;
|
|
9
|
-
decimalTolerance?: DecimalTolerance;
|
|
10
|
-
valueFont?: SkFont | null;
|
|
11
|
-
labelFont?: SkFont | null;
|
|
12
|
-
}
|
|
13
|
-
export declare const MeasurementStampElement: ({ placed, measurement, fallbackUnit, fractionalTolerance, decimalTolerance, valueFont, labelFont, }: MeasurementStampElementProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Group, Line, RoundedRect, Text, } from '@shopify/react-native-skia';
|
|
3
|
-
import { DecimalTolerance, FractionalTolerance, } from '../../types/firestore.js';
|
|
4
|
-
import { convertMicrometers } from '../../utils/micrometersToUnit.js';
|
|
5
|
-
import { STAMP_HEIGHT, STAMP_PADDING_X, STAMP_PADDING_Y, STAMP_WIDTH, } from '../stampLayout.js';
|
|
6
|
-
const VALUE_FONT_BASELINE_OFFSET = 14;
|
|
7
|
-
const LABEL_FONT_BASELINE_OFFSET = 24;
|
|
8
|
-
const formatValue = (measurement, unit, fractionalTolerance, decimalTolerance) => {
|
|
9
|
-
if (!measurement)
|
|
10
|
-
return '—';
|
|
11
|
-
const result = convertMicrometers(measurement.value, unit, fractionalTolerance, decimalTolerance);
|
|
12
|
-
return `${result.value}${result.unit ? ` ${result.unit}` : ''}`;
|
|
13
|
-
};
|
|
14
|
-
export const MeasurementStampElement = ({ placed, measurement, fallbackUnit, fractionalTolerance = FractionalTolerance.Sixteenth, decimalTolerance = DecimalTolerance.Hundredth, valueFont, labelFont, }) => {
|
|
15
|
-
const unit = placed.unitOverride ?? measurement?.unit ?? fallbackUnit;
|
|
16
|
-
const valueText = formatValue(measurement, unit, fractionalTolerance, decimalTolerance);
|
|
17
|
-
const label = placed.labelOverride ??
|
|
18
|
-
measurement?.label ??
|
|
19
|
-
(measurement
|
|
20
|
-
? `M${measurement.measurementIndex ?? ''}`
|
|
21
|
-
: '');
|
|
22
|
-
const showLabel = placed.showLabel !== false && !!label;
|
|
23
|
-
const showValue = placed.showValue !== false;
|
|
24
|
-
const scale = placed.scale ?? 1;
|
|
25
|
-
// Fixed-size stamp — no measureText calls. Keeps rendering deterministic
|
|
26
|
-
// even when no font is loaded (browser/native both).
|
|
27
|
-
const baseX = placed.anchor.x - (STAMP_WIDTH * scale) / 2;
|
|
28
|
-
const baseY = placed.anchor.y - (STAMP_HEIGHT * scale) / 2;
|
|
29
|
-
return (_jsxs(Group, { transform: [{ translateX: baseX }, { translateY: baseY }, { scale }], children: [placed.leader && (_jsx(Line, { p1: placed.leader.from, p2: placed.leader.to, color: "#3B82F6", style: "stroke", strokeWidth: 1.5 })), _jsx(RoundedRect, { x: 0, y: 0, width: STAMP_WIDTH, height: STAMP_HEIGHT, r: 6, color: "#FFFFFFEE" }), _jsx(RoundedRect, { x: 0, y: 0, width: STAMP_WIDTH, height: STAMP_HEIGHT, r: 6, color: "#3B82F6", style: "stroke", strokeWidth: 1.25 }), showValue && valueFont && (_jsx(Text, { x: STAMP_PADDING_X, y: STAMP_PADDING_Y + VALUE_FONT_BASELINE_OFFSET, text: valueText, font: valueFont, color: "#111827" })), showLabel && labelFont && (_jsx(Text, { x: STAMP_PADDING_X, y: STAMP_PADDING_Y + LABEL_FONT_BASELINE_OFFSET, text: label, font: labelFont, color: "#6B7280" }))] }));
|
|
30
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type SkFont } from '@shopify/react-native-skia';
|
|
2
|
-
import type { AnnotationShape } from '../../types/annotation.js';
|
|
3
|
-
export interface ShapeElementProps {
|
|
4
|
-
shape: AnnotationShape;
|
|
5
|
-
font?: SkFont | null;
|
|
6
|
-
}
|
|
7
|
-
export declare const ShapeElement: ({ shape, font }: ShapeElementProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type SkPath } from '@shopify/react-native-skia';
|
|
2
|
-
import type { AnnotationStroke } from '../../types/annotation.js';
|
|
3
|
-
export declare const pointsToSkPath: (points: number[]) => SkPath;
|
|
4
|
-
export interface StrokeElementProps {
|
|
5
|
-
stroke: AnnotationStroke;
|
|
6
|
-
}
|
|
7
|
-
export declare const StrokeElement: ({ stroke }: StrokeElementProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Path, Skia } from '@shopify/react-native-skia';
|
|
3
|
-
import { useMemo } from 'react';
|
|
4
|
-
export const pointsToSkPath = (points) => {
|
|
5
|
-
const path = Skia.Path.Make();
|
|
6
|
-
if (points.length < 2)
|
|
7
|
-
return path;
|
|
8
|
-
path.moveTo(points[0], points[1]);
|
|
9
|
-
for (let i = 2; i < points.length; i += 2) {
|
|
10
|
-
path.lineTo(points[i], points[i + 1]);
|
|
11
|
-
}
|
|
12
|
-
return path;
|
|
13
|
-
};
|
|
14
|
-
export const StrokeElement = ({ stroke }) => {
|
|
15
|
-
const path = useMemo(() => pointsToSkPath(stroke.points), [stroke.points]);
|
|
16
|
-
const opacity = stroke.tool === 'highlighter' ? 0.3 : 1;
|
|
17
|
-
return (_jsx(Path, { path: path, color: stroke.color, style: "stroke", strokeWidth: stroke.width, strokeCap: "round", strokeJoin: "round", opacity: opacity }));
|
|
18
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// Shared layout constants for the placed measurement stamp. Lives in a
|
|
2
|
-
// Skia-free module so both render (MeasurementStampElement) and hit-test
|
|
3
|
-
// (selectTool) can import it without dragging @shopify/react-native-skia
|
|
4
|
-
// into the consumer's static import graph.
|
|
5
|
-
export const STAMP_WIDTH = 120;
|
|
6
|
-
export const STAMP_HEIGHT = 44;
|
|
7
|
-
export const STAMP_PADDING_X = 10;
|
|
8
|
-
export const STAMP_PADDING_Y = 6;
|
|
9
|
-
// Constant SCREEN-space edge length of a placed measurement rendered as a
|
|
10
|
-
// square tile (the overlay path — see measurementStampOverlay.ts). The tile
|
|
11
|
-
// is a fixed-size pin: its on-screen size is this times `placed.scale` and
|
|
12
|
-
// does NOT change with zoom (only its position tracks the canvas). Also drives
|
|
13
|
-
// the select-tool hit box, which converts it back to doc space via the zoom.
|
|
14
|
-
export const STAMP_TILE_SIZE = 120;
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { STAMP_TILE_SIZE } from '../stampLayout.js';
|
|
2
|
-
const HIT_PADDING = 6;
|
|
3
|
-
// Hit-test in doc-space. Crude but fast — good enough for v1; tools can
|
|
4
|
-
// override via `hitTest` for more precision later.
|
|
5
|
-
const hitStroke = (stroke, p) => {
|
|
6
|
-
const r = stroke.width / 2 + HIT_PADDING;
|
|
7
|
-
const r2 = r * r;
|
|
8
|
-
for (let i = 0; i < stroke.points.length - 2; i += 2) {
|
|
9
|
-
if (segmentDistanceSq(p.x, p.y, stroke.points[i], stroke.points[i + 1], stroke.points[i + 2], stroke.points[i + 3]) <= r2) {
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
return false;
|
|
14
|
-
};
|
|
15
|
-
const hitMeasurement = (m, p, zoom = 1) => {
|
|
16
|
-
// The stamp renders as a constant *screen*-size square centered on the
|
|
17
|
-
// anchor, so its doc-space footprint shrinks as you zoom in. Convert the
|
|
18
|
-
// screen-space half-extent (+ padding) back to doc space via the zoom so
|
|
19
|
-
// the hit box always matches what's drawn.
|
|
20
|
-
const scale = m.scale ?? 1;
|
|
21
|
-
const half = ((STAMP_TILE_SIZE * scale) / 2 + HIT_PADDING) / zoom;
|
|
22
|
-
const dx = Math.abs(p.x - m.anchor.x);
|
|
23
|
-
const dy = Math.abs(p.y - m.anchor.y);
|
|
24
|
-
return dx <= half && dy <= half;
|
|
25
|
-
};
|
|
26
|
-
const segmentDistanceSq = (px, py, ax, ay, bx, by) => {
|
|
27
|
-
const abx = bx - ax;
|
|
28
|
-
const aby = by - ay;
|
|
29
|
-
const lenSq = abx * abx + aby * aby;
|
|
30
|
-
let t = lenSq === 0 ? 0 : ((px - ax) * abx + (py - ay) * aby) / lenSq;
|
|
31
|
-
t = Math.max(0, Math.min(1, t));
|
|
32
|
-
const cx = ax + t * abx;
|
|
33
|
-
const cy = ay + t * aby;
|
|
34
|
-
const dx = px - cx;
|
|
35
|
-
const dy = py - cy;
|
|
36
|
-
return dx * dx + dy * dy;
|
|
37
|
-
};
|
|
38
|
-
const findHit = (doc, world, zoom) => {
|
|
39
|
-
// Hit-test in z-order (top first): measurements > shapes > strokes.
|
|
40
|
-
for (let i = doc.placedMeasurements.length - 1; i >= 0; i--) {
|
|
41
|
-
const m = doc.placedMeasurements[i];
|
|
42
|
-
if (hitMeasurement(m, world, zoom))
|
|
43
|
-
return { id: m.id, kind: 'measurement' };
|
|
44
|
-
}
|
|
45
|
-
for (let i = doc.shapes.length - 1; i >= 0; i--) {
|
|
46
|
-
// Default shape hit test: bounding box of the shape's points + padding.
|
|
47
|
-
const s = doc.shapes[i];
|
|
48
|
-
const pts = s.geometry.points;
|
|
49
|
-
if (pts.length === 0)
|
|
50
|
-
continue;
|
|
51
|
-
let minX = pts[0].x;
|
|
52
|
-
let maxX = pts[0].x;
|
|
53
|
-
let minY = pts[0].y;
|
|
54
|
-
let maxY = pts[0].y;
|
|
55
|
-
for (let j = 1; j < pts.length; j++) {
|
|
56
|
-
const p = pts[j];
|
|
57
|
-
if (p.x < minX)
|
|
58
|
-
minX = p.x;
|
|
59
|
-
if (p.x > maxX)
|
|
60
|
-
maxX = p.x;
|
|
61
|
-
if (p.y < minY)
|
|
62
|
-
minY = p.y;
|
|
63
|
-
if (p.y > maxY)
|
|
64
|
-
maxY = p.y;
|
|
65
|
-
}
|
|
66
|
-
if (world.x >= minX - HIT_PADDING &&
|
|
67
|
-
world.x <= maxX + HIT_PADDING &&
|
|
68
|
-
world.y >= minY - HIT_PADDING &&
|
|
69
|
-
world.y <= maxY + HIT_PADDING) {
|
|
70
|
-
return { id: s.id, kind: 'shape' };
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
for (let i = doc.strokes.length - 1; i >= 0; i--) {
|
|
74
|
-
const s = doc.strokes[i];
|
|
75
|
-
if (hitStroke(s, world))
|
|
76
|
-
return { id: s.id, kind: 'stroke' };
|
|
77
|
-
}
|
|
78
|
-
return null;
|
|
79
|
-
};
|
|
80
|
-
const translatePatch = (elementKind, id, doc, delta) => {
|
|
81
|
-
if (elementKind === 'measurement') {
|
|
82
|
-
const m = doc.placedMeasurements.find((x) => x.id === id);
|
|
83
|
-
if (!m)
|
|
84
|
-
return null;
|
|
85
|
-
return {
|
|
86
|
-
op: 'updateMeasurement',
|
|
87
|
-
id,
|
|
88
|
-
patch: {
|
|
89
|
-
anchor: { x: m.anchor.x + delta.x, y: m.anchor.y + delta.y },
|
|
90
|
-
leader: m.leader
|
|
91
|
-
? {
|
|
92
|
-
from: {
|
|
93
|
-
x: m.leader.from.x + delta.x,
|
|
94
|
-
y: m.leader.from.y + delta.y,
|
|
95
|
-
},
|
|
96
|
-
to: {
|
|
97
|
-
x: m.leader.to.x + delta.x,
|
|
98
|
-
y: m.leader.to.y + delta.y,
|
|
99
|
-
},
|
|
100
|
-
}
|
|
101
|
-
: undefined,
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
if (elementKind === 'shape') {
|
|
106
|
-
const s = doc.shapes.find((x) => x.id === id);
|
|
107
|
-
if (!s)
|
|
108
|
-
return null;
|
|
109
|
-
return {
|
|
110
|
-
op: 'updateShape',
|
|
111
|
-
id,
|
|
112
|
-
patch: {
|
|
113
|
-
geometry: {
|
|
114
|
-
...s.geometry,
|
|
115
|
-
points: s.geometry.points.map((p) => ({
|
|
116
|
-
x: p.x + delta.x,
|
|
117
|
-
y: p.y + delta.y,
|
|
118
|
-
})),
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
const stroke = doc.strokes.find((x) => x.id === id);
|
|
124
|
-
if (!stroke)
|
|
125
|
-
return null;
|
|
126
|
-
const points = stroke.points.slice();
|
|
127
|
-
for (let i = 0; i < points.length; i += 2) {
|
|
128
|
-
points[i] = points[i] + delta.x;
|
|
129
|
-
points[i + 1] = points[i + 1] + delta.y;
|
|
130
|
-
}
|
|
131
|
-
return { op: 'updateStroke', id, patch: { points } };
|
|
132
|
-
};
|
|
133
|
-
export const createSelectTool = () => ({
|
|
134
|
-
id: 'select',
|
|
135
|
-
label: 'Select',
|
|
136
|
-
cursor: 'default',
|
|
137
|
-
onPointerDown(event, ctx) {
|
|
138
|
-
const hit = findHit(ctx.document, event.world, ctx.viewport.state.zoom);
|
|
139
|
-
if (!hit) {
|
|
140
|
-
ctx.setSelection(null);
|
|
141
|
-
return { kind: 'idle' };
|
|
142
|
-
}
|
|
143
|
-
ctx.setSelection({ ids: [hit.id] });
|
|
144
|
-
return {
|
|
145
|
-
kind: 'dragging',
|
|
146
|
-
id: hit.id,
|
|
147
|
-
elementKind: hit.kind,
|
|
148
|
-
start: event.world,
|
|
149
|
-
delta: { x: 0, y: 0 },
|
|
150
|
-
};
|
|
151
|
-
},
|
|
152
|
-
onPointerMove(event, ctx, state) {
|
|
153
|
-
const s = state;
|
|
154
|
-
if (s?.kind !== 'dragging')
|
|
155
|
-
return s;
|
|
156
|
-
const delta = { x: event.world.x - s.start.x, y: event.world.y - s.start.y };
|
|
157
|
-
const op = translatePatch(s.elementKind, s.id, ctx.document, delta);
|
|
158
|
-
if (op)
|
|
159
|
-
ctx.preview({ ops: [op] });
|
|
160
|
-
return { ...s, delta };
|
|
161
|
-
},
|
|
162
|
-
onPointerUp(_event, ctx, state) {
|
|
163
|
-
const s = state;
|
|
164
|
-
if (s?.kind !== 'dragging')
|
|
165
|
-
return;
|
|
166
|
-
if (s.delta.x === 0 && s.delta.y === 0)
|
|
167
|
-
return;
|
|
168
|
-
const op = translatePatch(s.elementKind, s.id, ctx.document, s.delta);
|
|
169
|
-
if (op)
|
|
170
|
-
ctx.commit({ ops: [op] });
|
|
171
|
-
},
|
|
172
|
-
onCancel(_state, ctx) {
|
|
173
|
-
ctx.preview({ ops: [] });
|
|
174
|
-
},
|
|
175
|
-
hitTest(element, p) {
|
|
176
|
-
if (element.kind === 'measurement')
|
|
177
|
-
return hitMeasurement(element, p);
|
|
178
|
-
if (element.kind === 'stroke')
|
|
179
|
-
return hitStroke(element, p);
|
|
180
|
-
return false;
|
|
181
|
-
},
|
|
182
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
type MeasurementMap = Map<string, {
|
|
2
|
-
id: string;
|
|
3
|
-
value: number;
|
|
4
|
-
}[]>;
|
|
5
|
-
interface FormulaEvaluationOptions {
|
|
6
|
-
expression: string;
|
|
7
|
-
mappings: Record<string, string>;
|
|
8
|
-
group: {
|
|
9
|
-
id: string;
|
|
10
|
-
columns: Map<string, any>;
|
|
11
|
-
};
|
|
12
|
-
columns: {
|
|
13
|
-
id: string;
|
|
14
|
-
type: string;
|
|
15
|
-
}[];
|
|
16
|
-
measurementMap: MeasurementMap;
|
|
17
|
-
defaultUnit?: string;
|
|
18
|
-
}
|
|
19
|
-
export declare function evaluateFormula({ expression, mappings, group, columns, measurementMap, defaultUnit, }: FormulaEvaluationOptions): number | string;
|
|
20
|
-
export {};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { create, all } from 'mathjs';
|
|
2
|
-
const math = create(all);
|
|
3
|
-
export function evaluateFormula({ expression, mappings, group, columns, measurementMap, defaultUnit = 'um', }) {
|
|
4
|
-
const scope = {};
|
|
5
|
-
for (const [variable, columnId] of Object.entries(mappings)) {
|
|
6
|
-
const rawValue = group.columns.get(columnId);
|
|
7
|
-
const columnDef = columns.find((c) => c.id === columnId);
|
|
8
|
-
if (columnDef?.type === 'Measurement') {
|
|
9
|
-
const groupMeasurements = measurementMap.get(group.id) || [];
|
|
10
|
-
const measurement = groupMeasurements.find((m) => m.id === rawValue);
|
|
11
|
-
if (measurement?.value !== undefined) {
|
|
12
|
-
scope[variable] = math.unit(measurement.value, defaultUnit);
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
const parsedValue = typeof rawValue === 'number' ? rawValue : parseFloat(rawValue || '');
|
|
17
|
-
scope[variable] = isNaN(parsedValue) ? 0 : parsedValue;
|
|
18
|
-
}
|
|
19
|
-
try {
|
|
20
|
-
const compiled = math.compile(expression);
|
|
21
|
-
const result = compiled.evaluate(scope);
|
|
22
|
-
if (math.typeOf(result) === 'Unit') {
|
|
23
|
-
return result.toNumber(defaultUnit);
|
|
24
|
-
}
|
|
25
|
-
return result;
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
console.error(`Formula evaluation failed:`, err);
|
|
29
|
-
return 'Error';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|