@reekon-tools/boldr-utils 1.19.5 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/annotation/canvas/AnnotationCanvasInner.d.ts +2 -0
- package/dist/annotation/canvas/AnnotationCanvasInner.js +12 -1
- package/dist/annotation/canvas/AnnotationCanvasInner.native.d.ts +2 -0
- package/dist/annotation/canvas/AnnotationCanvasInner.native.js +329 -35
- package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +28 -1
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +99 -14
- package/dist/annotation/canvas/Tool.d.ts +10 -1
- package/dist/annotation/canvas/backgroundLayers.d.ts +20 -1
- package/dist/annotation/canvas/backgroundLayers.js +94 -3
- package/dist/annotation/canvas/elements/BackgroundImageElement.d.ts +2 -1
- package/dist/annotation/canvas/elements/BackgroundImageElement.js +10 -2
- package/dist/annotation/canvas/fileTileSwap.d.ts +12 -0
- package/dist/annotation/canvas/fileTileSwap.js +87 -0
- package/dist/annotation/canvas/snapGeometry.d.ts +32 -0
- package/dist/annotation/canvas/snapGeometry.js +168 -0
- package/dist/annotation/canvas/stampLayout.d.ts +1 -1
- package/dist/annotation/canvas/stampLayout.js +10 -8
- package/dist/annotation/canvas/tools/measurementTool.js +38 -4
- package/dist/annotation/canvas/tools/selectTool.d.ts +18 -0
- package/dist/annotation/canvas/tools/selectTool.js +201 -43
- package/dist/annotation/canvas/tools/shapeTool.js +27 -5
- package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +2 -0
- package/dist/annotation/canvas/useAnnotationCanvasState.js +19 -2
- package/dist/annotation/data/hooks/useAnnotationCanvasDoc.js +5 -1
- package/dist/exports.d.ts +3 -1
- package/dist/exports.js +2 -0
- package/dist/types/annotation.d.ts +10 -0
- package/package.json +1 -1
- package/dist/canvas/AnnotationCanvas.d.ts +0 -11
- package/dist/canvas/AnnotationCanvas.js +0 -10
- package/dist/canvas/AnnotationCanvas.native.d.ts +0 -8
- package/dist/canvas/AnnotationCanvas.native.js +0 -6
- package/dist/canvas/AnnotationCanvasInner.d.ts +0 -39
- package/dist/canvas/AnnotationCanvasInner.js +0 -219
- package/dist/canvas/AnnotationCanvasInner.native.d.ts +0 -35
- 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/Tool.d.ts +0 -38
- package/dist/canvas/Tool.js +0 -1
- package/dist/canvas/elements/BackgroundImageElement.d.ts +0 -9
- package/dist/canvas/elements/BackgroundImageElement.js +0 -37
- 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/ShapeElement.js +0 -62
- package/dist/canvas/elements/StrokeElement.d.ts +0 -7
- package/dist/canvas/elements/StrokeElement.js +0 -18
- package/dist/canvas/measurementPicker.d.ts +0 -10
- package/dist/canvas/measurementPicker.js +0 -1
- package/dist/canvas/measurementStampOverlay.d.ts +0 -11
- package/dist/canvas/measurementStampOverlay.js +0 -1
- package/dist/canvas/pointerAdapter.d.ts +0 -3
- package/dist/canvas/pointerAdapter.js +0 -19
- package/dist/canvas/stampLayout.d.ts +0 -5
- package/dist/canvas/stampLayout.js +0 -14
- package/dist/canvas/tools/measurementStampTool.d.ts +0 -9
- package/dist/canvas/tools/measurementStampTool.js +0 -37
- package/dist/canvas/tools/panTool.d.ts +0 -5
- package/dist/canvas/tools/panTool.js +0 -25
- package/dist/canvas/tools/penTool.d.ts +0 -13
- package/dist/canvas/tools/penTool.js +0 -68
- package/dist/canvas/tools/selectTool.d.ts +0 -2
- package/dist/canvas/tools/selectTool.js +0 -182
- package/dist/canvas/useAnnotationCanvasState.d.ts +0 -54
- package/dist/canvas/useAnnotationCanvasState.js +0 -210
- package/dist/canvas/viewport.d.ts +0 -16
- package/dist/canvas/viewport.js +0 -54
- package/dist/data/AnnotationDataContext.d.ts +0 -8
- package/dist/data/AnnotationDataContext.js +0 -11
- package/dist/data/AnnotationDataProvider.d.ts +0 -65
- package/dist/data/AnnotationDataProvider.js +0 -4
- package/dist/data/InMemoryAnnotationProvider.d.ts +0 -30
- package/dist/data/InMemoryAnnotationProvider.js +0 -197
- package/dist/data/canvasPersistence.d.ts +0 -3
- package/dist/data/canvasPersistence.js +0 -26
- package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +0 -33
- package/dist/data/hooks/useAnnotationCanvasDoc.js +0 -314
- package/dist/data/hooks/useAnnotationDoc.d.ts +0 -7
- package/dist/data/hooks/useAnnotationDoc.js +0 -33
- package/dist/data/hooks/useAnnotationList.d.ts +0 -7
- package/dist/data/hooks/useAnnotationList.js +0 -26
- package/dist/data/hooks/useAnnotationMutations.d.ts +0 -9
- package/dist/data/hooks/useAnnotationMutations.js +0 -11
- package/dist/hooks/useParseMeasurement.d.ts +0 -4
- package/dist/hooks/useParseMeasurement.js +0 -14
- package/dist/utils/evaluateFormula.d.ts +0 -20
- package/dist/utils/evaluateFormula.js +0 -31
|
@@ -7,10 +7,11 @@ import Animated, { runOnJS, runOnUI, useAnimatedStyle, useDerivedValue, useShare
|
|
|
7
7
|
import { stampTileDims } from './stampLayout.js';
|
|
8
8
|
import { DEFAULT_LAYER_ID, } from '../../types/annotation.js';
|
|
9
9
|
import { AnnotationCanvasSkia } from './AnnotationCanvasSkia.js';
|
|
10
|
-
import { backgroundLayersOf, backgroundLayerDocRect, } from './backgroundLayers.js';
|
|
10
|
+
import { MIN_BACKGROUND_DOC_EXTENT, backgroundLayersOf, backgroundLayerDocRect, backgroundLayerFullDocRect, } from './backgroundLayers.js';
|
|
11
11
|
import { BackgroundSkImageLoader } from './elements/BackgroundImageElement.js';
|
|
12
12
|
import { buildRemoveMeasurementOps, linePosOf, } from './measurementGeometry.js';
|
|
13
13
|
import { planeEdgeLine, planeGridSegments, planeGridSegmentsForScale, resolvePlaneGridSpacingUm, } from './planeGeometry.js';
|
|
14
|
+
import { SNAP_RADIUS_PX, SNAP_RING_PX, SNAP_RING_WIDTH_PX, collectSnapTargetsFlat, snapDelta, snapNearest, } from './snapGeometry.js';
|
|
14
15
|
import { buildShapeFromDrag } from './tools/shapeTool.js';
|
|
15
16
|
import { SELECTION_PAD } from './textGeometry.js';
|
|
16
17
|
import { useAnnotationCanvasState, } from './useAnnotationCanvasState.js';
|
|
@@ -64,7 +65,7 @@ const LOUPE_CROSS_WIDTH_PX = 1.5;
|
|
|
64
65
|
// is only re-synced once each gesture ends. The tool/tap path still hops to
|
|
65
66
|
// JS (`runOnJS(true)`) since drawing/selection are React-state operations.
|
|
66
67
|
export const AnnotationCanvasInner = (props) => {
|
|
67
|
-
const { resolveImageUrl, deferBackgroundToResolver, stampFontSource, stampValueFontSize = 14, width, height, style, magnifierTopOffset = 0, renderBackground, } = props;
|
|
68
|
+
const { resolveImageUrl, deferBackgroundToResolver, hideLinkBadges, stampFontSource, stampValueFontSize = 14, width, height, style, magnifierTopOffset = 0, snapToGeometry = false, renderBackground, } = props;
|
|
68
69
|
const valueFont = useFont(stampFontSource, stampValueFontSize);
|
|
69
70
|
// The animated-viewport implementation needs the Reanimated shared values,
|
|
70
71
|
// which are created below AFTER the state hook — so the hook gets a stable
|
|
@@ -293,7 +294,10 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
293
294
|
// owned by the UI thread — the shape twin of `livePoints`. The drag worklet
|
|
294
295
|
// tracks the start/current world points; derived paths below render the
|
|
295
296
|
// live shape. React is untouched until the single commit on release.
|
|
296
|
-
|
|
297
|
+
// `snapR` is the doc-space magnetic-snap radius frozen at press (0 = off)
|
|
298
|
+
// and `snapped` whether the live end is currently locked onto a target
|
|
299
|
+
// (drives the snap ring) — see snapGeometry.ts.
|
|
300
|
+
const liveShape = useSharedValue({ active: false, ax: 0, ay: 0, bx: 0, by: 0, snapR: 0, snapped: false });
|
|
297
301
|
// Released shapes awaiting their committed paint — the shape twin of
|
|
298
302
|
// `handoffStrokes`, with the same lifecycle: appended in the gesture's
|
|
299
303
|
// onEnd worklet tick, released by the paint-watch effect one rAF after the
|
|
@@ -317,6 +321,28 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
317
321
|
width: shapeDraw.width,
|
|
318
322
|
};
|
|
319
323
|
}, [shapeDraw, shapeCfg]);
|
|
324
|
+
// Magnetic snapping for line endpoints (snapGeometry.ts). `snapCfg` mirrors
|
|
325
|
+
// the `snapToGeometry` prop for the worklets (derived values and gesture
|
|
326
|
+
// callbacks are created once and can't close over a changing prop).
|
|
327
|
+
// `snapTargets` is the flat `[x, y, kind, …]` target buffer, written ONCE
|
|
328
|
+
// per drag — from JS in beginSelectDrag for endpoint drags (excluding the
|
|
329
|
+
// grabbed element), and by the effect below for the shape rubber-band,
|
|
330
|
+
// whose onBegin is a worklet with no doc access. The effect only runs while
|
|
331
|
+
// a line-kind shape tool is active, so it can never clobber a select drag's
|
|
332
|
+
// harvest; keyed on the COMMITTED doc so a just-drawn line becomes a target.
|
|
333
|
+
const snapCfg = useSharedValue({
|
|
334
|
+
enabled: false,
|
|
335
|
+
radiusPx: SNAP_RADIUS_PX,
|
|
336
|
+
});
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
snapCfg.value = { enabled: snapToGeometry, radiusPx: SNAP_RADIUS_PX };
|
|
339
|
+
}, [snapToGeometry, snapCfg]);
|
|
340
|
+
const snapTargets = useSharedValue([]);
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
if (!snapToGeometry || !shapeDraw || shapeDraw.kind !== 'line')
|
|
343
|
+
return;
|
|
344
|
+
snapTargets.value = collectSnapTargetsFlat(state.ctx.document);
|
|
345
|
+
}, [snapToGeometry, shapeDraw, state.ctx.document, snapTargets]);
|
|
320
346
|
// Outline of the live rubber-band + every pending handoff shape, as one
|
|
321
347
|
// stroked path. WORKLET TWIN of shapeGeometry.shapePointsFromDrag +
|
|
322
348
|
// ShapeElement's per-kind rendering — keep in sync. Merging into one path
|
|
@@ -456,7 +482,9 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
456
482
|
// (0 = a, 1 = b) is moving, so the Skia line + overlay tile can be driven on
|
|
457
483
|
// the UI thread from `dragX`/`dragY`.
|
|
458
484
|
const [epDragId, setEpDragId] = useState(null);
|
|
459
|
-
|
|
485
|
+
// `snapR`: doc-space magnetic-snap radius frozen at grab (0 = off) — see
|
|
486
|
+
// epLive below and snapGeometry.ts.
|
|
487
|
+
const epCtx = useSharedValue({ ax: 0, ay: 0, bx: 0, by: 0, t0: 0.5, handle: 0, snapR: 0 });
|
|
460
488
|
const epTargetRef = useRef(null);
|
|
461
489
|
// Endpoint drag of a line/arrow SHAPE (vs. a measurement line above).
|
|
462
490
|
// `shapeEpDragId` (React state) tells the Skia layer to render that shape's
|
|
@@ -468,19 +496,32 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
468
496
|
// Live line endpoints during an endpoint drag: the grabbed handle follows the
|
|
469
497
|
// finger (dragX/dragY); the other stays put. Fed to the Skia <Line> for the
|
|
470
498
|
// dragged annotation.
|
|
499
|
+
// The grabbed endpoint's live position: base + drag, then magnetically
|
|
500
|
+
// snapped to the nearest target within epCtx.snapR (0 = off). The SINGLE
|
|
501
|
+
// source for the live line, the tile overlay's follow math, the loupe
|
|
502
|
+
// crosshair and the snap ring — and the select gesture's onEnd recomputes
|
|
503
|
+
// the same snap (snapDelta, same inputs) so the commit matches it exactly.
|
|
504
|
+
const epLive = useDerivedValue(() => {
|
|
505
|
+
'worklet';
|
|
506
|
+
const c = epCtx.value;
|
|
507
|
+
const mx = (c.handle === 0 ? c.ax : c.bx) + dragX.value;
|
|
508
|
+
const my = (c.handle === 0 ? c.ay : c.by) + dragY.value;
|
|
509
|
+
const hit = c.snapR > 0 ? snapNearest(mx, my, snapTargets.value, c.snapR) : null;
|
|
510
|
+
return hit
|
|
511
|
+
? { x: hit.x, y: hit.y, snapped: true }
|
|
512
|
+
: { x: mx, y: my, snapped: false };
|
|
513
|
+
});
|
|
471
514
|
const liveLineP1 = useDerivedValue(() => {
|
|
472
515
|
'worklet';
|
|
473
516
|
const c = epCtx.value;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
: { x: c.ax, y: c.ay };
|
|
517
|
+
const e = epLive.value;
|
|
518
|
+
return c.handle === 0 ? { x: e.x, y: e.y } : { x: c.ax, y: c.ay };
|
|
477
519
|
});
|
|
478
520
|
const liveLineP2 = useDerivedValue(() => {
|
|
479
521
|
'worklet';
|
|
480
522
|
const c = epCtx.value;
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
: { x: c.bx, y: c.by };
|
|
523
|
+
const e = epLive.value;
|
|
524
|
+
return c.handle === 1 ? { x: e.x, y: e.y } : { x: c.bx, y: c.by };
|
|
484
525
|
});
|
|
485
526
|
// Screen-constant endpoint handle radius: a fixed pixel size divided by the
|
|
486
527
|
// live zoom (the handles are drawn inside the zoom-scaled world group).
|
|
@@ -488,6 +529,45 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
488
529
|
'worklet';
|
|
489
530
|
return HANDLE_RADIUS_PX / zoom.value;
|
|
490
531
|
});
|
|
532
|
+
// Snap ring (magnetic endpoint snapping). Three sources, in priority order:
|
|
533
|
+
// the UI-thread shape rubber-band, the UI-thread endpoint drag (gated off
|
|
534
|
+
// once the gesture ends or cancels), and the JS-path tools (measurement /
|
|
535
|
+
// pointer-path drags), whose ctx.snap.setIndicator lands in
|
|
536
|
+
// state.snapIndicator and is mirrored into a shared value here. Visibility
|
|
537
|
+
// is an opacity so nothing remounts per frame.
|
|
538
|
+
const jsSnapIndicator = useSharedValue(null);
|
|
539
|
+
useEffect(() => {
|
|
540
|
+
jsSnapIndicator.value = state.snapIndicator;
|
|
541
|
+
}, [state.snapIndicator, jsSnapIndicator]);
|
|
542
|
+
const snapIndicatorC = useDerivedValue(() => {
|
|
543
|
+
'worklet';
|
|
544
|
+
const sh = liveShape.value;
|
|
545
|
+
if (sh.active && sh.snapped)
|
|
546
|
+
return { x: sh.bx, y: sh.by };
|
|
547
|
+
const e = epLive.value;
|
|
548
|
+
if (e.snapped && !dragEnded.value)
|
|
549
|
+
return { x: e.x, y: e.y };
|
|
550
|
+
const j = jsSnapIndicator.value;
|
|
551
|
+
return j ? { x: j.x, y: j.y } : { x: 0, y: 0 };
|
|
552
|
+
});
|
|
553
|
+
const snapIndicatorOpacity = useDerivedValue(() => {
|
|
554
|
+
'worklet';
|
|
555
|
+
const sh = liveShape.value;
|
|
556
|
+
if (sh.active && sh.snapped)
|
|
557
|
+
return 1;
|
|
558
|
+
const e = epLive.value;
|
|
559
|
+
if (e.snapped && !dragEnded.value)
|
|
560
|
+
return 1;
|
|
561
|
+
return jsSnapIndicator.value ? 1 : 0;
|
|
562
|
+
});
|
|
563
|
+
const snapRingRadius = useDerivedValue(() => {
|
|
564
|
+
'worklet';
|
|
565
|
+
return SNAP_RING_PX / zoom.value;
|
|
566
|
+
});
|
|
567
|
+
const snapRingWidth = useDerivedValue(() => {
|
|
568
|
+
'worklet';
|
|
569
|
+
return SNAP_RING_WIDTH_PX / zoom.value;
|
|
570
|
+
});
|
|
491
571
|
const handleRingWidth = useDerivedValue(() => {
|
|
492
572
|
'worklet';
|
|
493
573
|
return HANDLE_RING_PX / zoom.value;
|
|
@@ -661,6 +741,9 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
661
741
|
// goes through buildResizePatch, which clamps identically.
|
|
662
742
|
const [resizingId, setResizingId] = useState(null);
|
|
663
743
|
const resizeCtx = useSharedValue({ px: 0, py: 0, hx: 0, hy: 0, minS: 1, maxS: 1 });
|
|
744
|
+
// The grab-start geometry rides along to the commit: a background offers
|
|
745
|
+
// four corner handles, each scaling about its opposite corner, so the patch
|
|
746
|
+
// must reuse whichever geometry the grab's hit-test chose.
|
|
664
747
|
const resizeTargetRef = useRef(null);
|
|
665
748
|
const resizeTransform = useDerivedValue(() => {
|
|
666
749
|
'worklet';
|
|
@@ -684,6 +767,60 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
684
767
|
{ translateY: -c.py },
|
|
685
768
|
];
|
|
686
769
|
});
|
|
770
|
+
// Background crop-bar drag. `bgCropId` (React state) gates which layer
|
|
771
|
+
// clips to the live rect; `bgCropCtx` carries the grabbed edge (0 top ·
|
|
772
|
+
// 1 bottom · 2 left · 3 right), the visible doc rect at grab-start and the
|
|
773
|
+
// full (uncropped) image's doc rect it may expand back out to — so the
|
|
774
|
+
// preview is a pure UI-thread rect rebuild driven by dragX/dragY. The
|
|
775
|
+
// commit on release goes through buildBackgroundCropPatch, which clamps
|
|
776
|
+
// identically.
|
|
777
|
+
const [bgCropId, setBgCropId] = useState(null);
|
|
778
|
+
const bgCropTargetRef = useRef(null);
|
|
779
|
+
const bgCropCtx = useSharedValue({ edge: 0, x: 0, y: 0, w: 0, h: 0, bx: 0, by: 0, bw: 0, bh: 0 });
|
|
780
|
+
const liveBgCropRect = useDerivedValue(() => {
|
|
781
|
+
'worklet';
|
|
782
|
+
// WORKLET TWIN of selectTool's cropDragVisibleRect — keep in sync.
|
|
783
|
+
const c = bgCropCtx.value;
|
|
784
|
+
let x = c.x;
|
|
785
|
+
let y = c.y;
|
|
786
|
+
let w = c.w;
|
|
787
|
+
let h = c.h;
|
|
788
|
+
if (c.edge === 0) {
|
|
789
|
+
const newY = Math.min(Math.max(c.y + dragY.value, c.by), c.y + c.h - MIN_BACKGROUND_DOC_EXTENT);
|
|
790
|
+
h = c.y + c.h - newY;
|
|
791
|
+
y = newY;
|
|
792
|
+
}
|
|
793
|
+
else if (c.edge === 1) {
|
|
794
|
+
h = Math.min(Math.max(c.h + dragY.value, MIN_BACKGROUND_DOC_EXTENT), c.by + c.bh - c.y);
|
|
795
|
+
}
|
|
796
|
+
else if (c.edge === 2) {
|
|
797
|
+
const newX = Math.min(Math.max(c.x + dragX.value, c.bx), c.x + c.w - MIN_BACKGROUND_DOC_EXTENT);
|
|
798
|
+
w = c.x + c.w - newX;
|
|
799
|
+
x = newX;
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
w = Math.min(Math.max(c.w + dragX.value, MIN_BACKGROUND_DOC_EXTENT), c.bx + c.bw - c.x);
|
|
803
|
+
}
|
|
804
|
+
return { x, y, width: w, height: h };
|
|
805
|
+
});
|
|
806
|
+
// Per-component slices for the live outline (Skia props take one animated
|
|
807
|
+
// value each; the clip takes the whole rect).
|
|
808
|
+
const liveBgCropX = useDerivedValue(() => {
|
|
809
|
+
'worklet';
|
|
810
|
+
return liveBgCropRect.value.x;
|
|
811
|
+
});
|
|
812
|
+
const liveBgCropY = useDerivedValue(() => {
|
|
813
|
+
'worklet';
|
|
814
|
+
return liveBgCropRect.value.y;
|
|
815
|
+
});
|
|
816
|
+
const liveBgCropW = useDerivedValue(() => {
|
|
817
|
+
'worklet';
|
|
818
|
+
return liveBgCropRect.value.width;
|
|
819
|
+
});
|
|
820
|
+
const liveBgCropH = useDerivedValue(() => {
|
|
821
|
+
'worklet';
|
|
822
|
+
return liveBgCropRect.value.height;
|
|
823
|
+
});
|
|
687
824
|
// Geometric-shape corner resize (rect/ellipse/polygon). `shapeResizeId`
|
|
688
825
|
// (React state) gates which shape renders live; `shapeResizeCtx` carries the
|
|
689
826
|
// fixed (opposite) corner and the grabbed corner's start position (from
|
|
@@ -842,9 +979,9 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
842
979
|
else if (loupeCrossMode === 'endpoint') {
|
|
843
980
|
// Measurement-line and shape line/arrow endpoint drags both stage
|
|
844
981
|
// their line in epCtx; the grabbed end follows the finger.
|
|
845
|
-
const
|
|
846
|
-
wx =
|
|
847
|
-
wy =
|
|
982
|
+
const e = epLive.value;
|
|
983
|
+
wx = e.x;
|
|
984
|
+
wy = e.y;
|
|
848
985
|
}
|
|
849
986
|
else if (loupeCrossMode === 'rect-corner') {
|
|
850
987
|
const c = rectCtx.value;
|
|
@@ -1345,9 +1482,29 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1345
1482
|
.maxPointers(1)
|
|
1346
1483
|
.onBegin((e) => {
|
|
1347
1484
|
'worklet';
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1485
|
+
let wx = e.x / zoom.value + panX.value;
|
|
1486
|
+
let wy = e.y / zoom.value + panY.value;
|
|
1487
|
+
// Magnetic snap (line tool only): freeze the doc-space radius for
|
|
1488
|
+
// this drag and snap the START point too — the WORKLET counterpart
|
|
1489
|
+
// of shapeTool's snapStateForPress/snapWorld (targets come from the
|
|
1490
|
+
// doc-synced `snapTargets` effect).
|
|
1491
|
+
const snapR = cfg.kind === 'line' && snapCfg.value.enabled
|
|
1492
|
+
? snapCfg.value.radiusPx / zoom.value
|
|
1493
|
+
: 0;
|
|
1494
|
+
const hit = snapR > 0 ? snapNearest(wx, wy, snapTargets.value, snapR) : null;
|
|
1495
|
+
if (hit) {
|
|
1496
|
+
wx = hit.x;
|
|
1497
|
+
wy = hit.y;
|
|
1498
|
+
}
|
|
1499
|
+
liveShape.value = {
|
|
1500
|
+
active: true,
|
|
1501
|
+
ax: wx,
|
|
1502
|
+
ay: wy,
|
|
1503
|
+
bx: wx,
|
|
1504
|
+
by: wy,
|
|
1505
|
+
snapR,
|
|
1506
|
+
snapped: hit !== null,
|
|
1507
|
+
};
|
|
1351
1508
|
magTouchX.value = e.x;
|
|
1352
1509
|
magTouchY.value = e.y;
|
|
1353
1510
|
runOnJS(setMagnifying)(true);
|
|
@@ -1359,12 +1516,23 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1359
1516
|
return;
|
|
1360
1517
|
magTouchX.value = e.x;
|
|
1361
1518
|
magTouchY.value = e.y;
|
|
1519
|
+
let bx = e.x / zoom.value + panX.value;
|
|
1520
|
+
let by = e.y / zoom.value + panY.value;
|
|
1521
|
+
const hit = s.snapR > 0
|
|
1522
|
+
? snapNearest(bx, by, snapTargets.value, s.snapR)
|
|
1523
|
+
: null;
|
|
1524
|
+
if (hit) {
|
|
1525
|
+
bx = hit.x;
|
|
1526
|
+
by = hit.y;
|
|
1527
|
+
}
|
|
1362
1528
|
liveShape.value = {
|
|
1363
1529
|
active: true,
|
|
1364
1530
|
ax: s.ax,
|
|
1365
1531
|
ay: s.ay,
|
|
1366
|
-
bx
|
|
1367
|
-
by
|
|
1532
|
+
bx,
|
|
1533
|
+
by,
|
|
1534
|
+
snapR: s.snapR,
|
|
1535
|
+
snapped: hit !== null,
|
|
1368
1536
|
};
|
|
1369
1537
|
})
|
|
1370
1538
|
.onEnd(() => {
|
|
@@ -1377,7 +1545,15 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1377
1545
|
const dxPx = (s.bx - s.ax) * zoom.value;
|
|
1378
1546
|
const dyPx = (s.by - s.ay) * zoom.value;
|
|
1379
1547
|
if (dxPx * dxPx + dyPx * dyPx < minDragSq) {
|
|
1380
|
-
liveShape.value = {
|
|
1548
|
+
liveShape.value = {
|
|
1549
|
+
active: false,
|
|
1550
|
+
ax: 0,
|
|
1551
|
+
ay: 0,
|
|
1552
|
+
bx: 0,
|
|
1553
|
+
by: 0,
|
|
1554
|
+
snapR: 0,
|
|
1555
|
+
snapped: false,
|
|
1556
|
+
};
|
|
1381
1557
|
return;
|
|
1382
1558
|
}
|
|
1383
1559
|
// Hand off in THIS worklet tick (see the stroke onEnd): the live
|
|
@@ -1397,14 +1573,30 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1397
1573
|
by: s.by,
|
|
1398
1574
|
},
|
|
1399
1575
|
];
|
|
1400
|
-
liveShape.value = {
|
|
1576
|
+
liveShape.value = {
|
|
1577
|
+
active: false,
|
|
1578
|
+
ax: 0,
|
|
1579
|
+
ay: 0,
|
|
1580
|
+
bx: 0,
|
|
1581
|
+
by: 0,
|
|
1582
|
+
snapR: 0,
|
|
1583
|
+
snapped: false,
|
|
1584
|
+
};
|
|
1401
1585
|
runOnJS(commitShape)(id, { x: s.ax, y: s.ay }, { x: s.bx, y: s.by });
|
|
1402
1586
|
})
|
|
1403
1587
|
.onFinalize(() => {
|
|
1404
1588
|
'worklet';
|
|
1405
1589
|
// Cancel path (no onEnd): drop the in-flight rubber-band. After a
|
|
1406
1590
|
// normal end the slot is already inactive, so this is a no-op.
|
|
1407
|
-
liveShape.value = {
|
|
1591
|
+
liveShape.value = {
|
|
1592
|
+
active: false,
|
|
1593
|
+
ax: 0,
|
|
1594
|
+
ay: 0,
|
|
1595
|
+
bx: 0,
|
|
1596
|
+
by: 0,
|
|
1597
|
+
snapR: 0,
|
|
1598
|
+
snapped: false,
|
|
1599
|
+
};
|
|
1408
1600
|
runOnJS(setMagnifying)(false);
|
|
1409
1601
|
});
|
|
1410
1602
|
};
|
|
@@ -1430,6 +1622,16 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1430
1622
|
if (handle) {
|
|
1431
1623
|
const m = st.ctx.document.placedMeasurements.find((x) => x.id === selId);
|
|
1432
1624
|
if (m?.line) {
|
|
1625
|
+
// Magnetic snap: harvest every OTHER element's targets once and
|
|
1626
|
+
// freeze the capture radius in doc space at the grab zoom (the
|
|
1627
|
+
// JS twin is selectTool's snapStateForGrab). Same JS tick as the
|
|
1628
|
+
// epCtx write, so both land in the same UI batch.
|
|
1629
|
+
const snapR = st.ctx.snap?.enabled
|
|
1630
|
+
? st.ctx.snap.radiusPx / zoomNow
|
|
1631
|
+
: 0;
|
|
1632
|
+
if (snapR > 0) {
|
|
1633
|
+
snapTargets.value = collectSnapTargetsFlat(st.ctx.document, selId);
|
|
1634
|
+
}
|
|
1433
1635
|
epCtx.value = {
|
|
1434
1636
|
ax: m.line.a.x,
|
|
1435
1637
|
ay: m.line.a.y,
|
|
@@ -1437,6 +1639,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1437
1639
|
by: m.line.b.y,
|
|
1438
1640
|
t0: m.linePos ?? 0.5,
|
|
1439
1641
|
handle: handle === 'a' ? 0 : 1,
|
|
1642
|
+
snapR,
|
|
1440
1643
|
};
|
|
1441
1644
|
epTargetRef.current = { id: selId, handle };
|
|
1442
1645
|
setEpDragId(selId);
|
|
@@ -1452,6 +1655,12 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1452
1655
|
const a = s?.geometry.points[0];
|
|
1453
1656
|
const b = s?.geometry.points[1];
|
|
1454
1657
|
if (a && b) {
|
|
1658
|
+
const snapR = st.ctx.snap?.enabled
|
|
1659
|
+
? st.ctx.snap.radiusPx / zoomNow
|
|
1660
|
+
: 0;
|
|
1661
|
+
if (snapR > 0) {
|
|
1662
|
+
snapTargets.value = collectSnapTargetsFlat(st.ctx.document, selId);
|
|
1663
|
+
}
|
|
1455
1664
|
epCtx.value = {
|
|
1456
1665
|
ax: a.x,
|
|
1457
1666
|
ay: a.y,
|
|
@@ -1459,6 +1668,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1459
1668
|
by: b.y,
|
|
1460
1669
|
t0: 0.5,
|
|
1461
1670
|
handle: shapeHandle === 'a' ? 0 : 1,
|
|
1671
|
+
snapR,
|
|
1462
1672
|
};
|
|
1463
1673
|
shapeEpTargetRef.current = { id: selId, handle: shapeHandle };
|
|
1464
1674
|
setShapeEpDragId(selId);
|
|
@@ -1477,11 +1687,42 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1477
1687
|
minS: resizeGeom.minScale,
|
|
1478
1688
|
maxS: resizeGeom.maxScale,
|
|
1479
1689
|
};
|
|
1480
|
-
resizeTargetRef.current = { id: selId };
|
|
1690
|
+
resizeTargetRef.current = { id: selId, geom: resizeGeom };
|
|
1481
1691
|
setResizingId(selId);
|
|
1482
1692
|
setMagnifying(true);
|
|
1483
1693
|
return;
|
|
1484
1694
|
}
|
|
1695
|
+
// Crop bar on a selected, unlocked background layer — the edge
|
|
1696
|
+
// windows the image (non-destructively) instead of scaling it.
|
|
1697
|
+
const cropEdge = cfg.hitTestBackgroundCropBar?.(st.ctx.document, selId, world, zoomNow);
|
|
1698
|
+
if (cropEdge) {
|
|
1699
|
+
const cropLayer = backgroundLayersOf(st.ctx.document.viewport).find((l) => l.id === selId);
|
|
1700
|
+
if (cropLayer) {
|
|
1701
|
+
const visible = backgroundLayerDocRect(cropLayer);
|
|
1702
|
+
const full = backgroundLayerFullDocRect(cropLayer);
|
|
1703
|
+
bgCropCtx.value = {
|
|
1704
|
+
edge: cropEdge === 'top'
|
|
1705
|
+
? 0
|
|
1706
|
+
: cropEdge === 'bottom'
|
|
1707
|
+
? 1
|
|
1708
|
+
: cropEdge === 'left'
|
|
1709
|
+
? 2
|
|
1710
|
+
: 3,
|
|
1711
|
+
x: visible.x,
|
|
1712
|
+
y: visible.y,
|
|
1713
|
+
w: visible.width,
|
|
1714
|
+
h: visible.height,
|
|
1715
|
+
bx: full.x,
|
|
1716
|
+
by: full.y,
|
|
1717
|
+
bw: full.width,
|
|
1718
|
+
bh: full.height,
|
|
1719
|
+
};
|
|
1720
|
+
bgCropTargetRef.current = { id: selId, edge: cropEdge };
|
|
1721
|
+
setBgCropId(selId);
|
|
1722
|
+
setMagnifying(true);
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1485
1726
|
// Rectangle-annotation corner handle — also selected-element-only.
|
|
1486
1727
|
const rectCornerHit = cfg.hitTestRectCorner?.(st.ctx.document, selId, world, zoomNow);
|
|
1487
1728
|
if (rectCornerHit) {
|
|
@@ -1580,10 +1821,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1580
1821
|
const rT = resizeTargetRef.current;
|
|
1581
1822
|
if (rT) {
|
|
1582
1823
|
if (dx !== 0 || dy !== 0) {
|
|
1583
|
-
const patch = cfg.buildResizePatch?.(st.ctx.document, rT.id, {
|
|
1584
|
-
x: dx,
|
|
1585
|
-
y: dy,
|
|
1586
|
-
});
|
|
1824
|
+
const patch = cfg.buildResizePatch?.(st.ctx.document, rT.id, { x: dx, y: dy }, rT.geom);
|
|
1587
1825
|
if (patch)
|
|
1588
1826
|
st.ctx.commit(patch);
|
|
1589
1827
|
}
|
|
@@ -1591,6 +1829,19 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1591
1829
|
setResizingId(null);
|
|
1592
1830
|
return;
|
|
1593
1831
|
}
|
|
1832
|
+
// Crop commit: window the layer to the live rect's final edges
|
|
1833
|
+
// (clamped in buildBackgroundCropPatch exactly like the live preview).
|
|
1834
|
+
const cropT = bgCropTargetRef.current;
|
|
1835
|
+
if (cropT) {
|
|
1836
|
+
if (dx !== 0 || dy !== 0) {
|
|
1837
|
+
const patch = cfg.buildBackgroundCropPatch?.(st.ctx.document, cropT.id, cropT.edge, { x: dx, y: dy });
|
|
1838
|
+
if (patch)
|
|
1839
|
+
st.ctx.commit(patch);
|
|
1840
|
+
}
|
|
1841
|
+
bgCropTargetRef.current = null;
|
|
1842
|
+
setBgCropId(null);
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1594
1845
|
// Rect-corner commit: drag the grabbed corner by the world delta
|
|
1595
1846
|
// (opposite corner fixed); anchor re-centers in the same patch.
|
|
1596
1847
|
const rectT = rectTargetRef.current;
|
|
@@ -1678,6 +1929,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1678
1929
|
resizeTargetRef.current = null;
|
|
1679
1930
|
rectTargetRef.current = null;
|
|
1680
1931
|
shapeResizeTargetRef.current = null;
|
|
1932
|
+
bgCropTargetRef.current = null;
|
|
1681
1933
|
setDraggingId(null);
|
|
1682
1934
|
setSlidingId(null);
|
|
1683
1935
|
setEpDragId(null);
|
|
@@ -1685,6 +1937,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1685
1937
|
setResizingId(null);
|
|
1686
1938
|
setRectDragId(null);
|
|
1687
1939
|
setShapeResizeId(null);
|
|
1940
|
+
setBgCropId(null);
|
|
1688
1941
|
};
|
|
1689
1942
|
return Gesture.Pan()
|
|
1690
1943
|
.minPointers(1)
|
|
@@ -1694,6 +1947,10 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1694
1947
|
dragEnded.value = false;
|
|
1695
1948
|
dragX.value = 0;
|
|
1696
1949
|
dragY.value = 0;
|
|
1950
|
+
// A stale snap radius from a previous endpoint drag must not bend
|
|
1951
|
+
// a move/slide's delta in onEnd; beginSelectDrag re-arms it below
|
|
1952
|
+
// (queued after this write) when this grab IS an endpoint.
|
|
1953
|
+
epCtx.value = { ...epCtx.value, snapR: 0 };
|
|
1697
1954
|
// The loupe (turned on by beginSelectDrag only for precision sub-
|
|
1698
1955
|
// drags) follows the live finger, not the hit-test origin.
|
|
1699
1956
|
magTouchX.value = e.x;
|
|
@@ -1717,12 +1974,27 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1717
1974
|
.onEnd(() => {
|
|
1718
1975
|
'worklet';
|
|
1719
1976
|
dragEnded.value = true;
|
|
1720
|
-
|
|
1977
|
+
let dx = dragX.value;
|
|
1978
|
+
let dy = dragY.value;
|
|
1979
|
+
// Endpoint drag with snapping armed: hand JS the EFFECTIVE delta
|
|
1980
|
+
// (the one landing on the snapped target) — same inputs as epLive,
|
|
1981
|
+
// so the commit matches the live preview and the delta-based patch
|
|
1982
|
+
// builders stay untouched.
|
|
1983
|
+
const c = epCtx.value;
|
|
1984
|
+
if (c.snapR > 0) {
|
|
1985
|
+
const base = c.handle === 0 ? { x: c.ax, y: c.ay } : { x: c.bx, y: c.by };
|
|
1986
|
+
const r = snapDelta(base, { x: dx, y: dy }, snapTargets.value, c.snapR);
|
|
1987
|
+
dx = r.delta.x;
|
|
1988
|
+
dy = r.delta.y;
|
|
1989
|
+
}
|
|
1990
|
+
runOnJS(endSelectDrag)(dx, dy);
|
|
1721
1991
|
})
|
|
1722
1992
|
.onFinalize(() => {
|
|
1723
1993
|
'worklet';
|
|
1724
1994
|
if (!dragEnded.value)
|
|
1725
1995
|
runOnJS(cancelSelectDrag)();
|
|
1996
|
+
// Also closes the snap-ring gate on the cancel path.
|
|
1997
|
+
dragEnded.value = true;
|
|
1726
1998
|
});
|
|
1727
1999
|
};
|
|
1728
2000
|
// Plane tool: a hybrid pan. Handle drags run on the UI thread (shared
|
|
@@ -1922,6 +2194,8 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1922
2194
|
epCtx,
|
|
1923
2195
|
resizeCtx,
|
|
1924
2196
|
rectCtx,
|
|
2197
|
+
snapCfg,
|
|
2198
|
+
snapTargets,
|
|
1925
2199
|
]);
|
|
1926
2200
|
const activeTool = props.tools.find((t) => t.id === props.activeToolId) ?? null;
|
|
1927
2201
|
const customPreview = activeTool?.renderPreview?.(state.customPreviewState, state.ctx);
|
|
@@ -1950,6 +2224,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1950
2224
|
worldTransform,
|
|
1951
2225
|
resolveImageUrl,
|
|
1952
2226
|
deferBackgroundToResolver,
|
|
2227
|
+
hideLinkBadges,
|
|
1953
2228
|
backgroundSkImages,
|
|
1954
2229
|
// Shared with the loupe overlay below (it re-runs this same scene through a
|
|
1955
2230
|
// magnifying transform), so the lens shows the substrate magnified instead
|
|
@@ -1998,6 +2273,23 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1998
2273
|
shapeEndpointDragId: shapeEpDragId,
|
|
1999
2274
|
liveLineP1,
|
|
2000
2275
|
liveLineP2,
|
|
2276
|
+
// Magnetic-snap ring; rides into the loupe pass via the spread below.
|
|
2277
|
+
snapIndicator: snapToGeometry
|
|
2278
|
+
? {
|
|
2279
|
+
c: snapIndicatorC,
|
|
2280
|
+
r: snapRingRadius,
|
|
2281
|
+
ringWidth: snapRingWidth,
|
|
2282
|
+
opacity: snapIndicatorOpacity,
|
|
2283
|
+
}
|
|
2284
|
+
: null,
|
|
2285
|
+
bgCropId,
|
|
2286
|
+
liveBgCrop: {
|
|
2287
|
+
x: liveBgCropX,
|
|
2288
|
+
y: liveBgCropY,
|
|
2289
|
+
width: liveBgCropW,
|
|
2290
|
+
height: liveBgCropH,
|
|
2291
|
+
},
|
|
2292
|
+
liveBgCropClip: liveBgCropRect,
|
|
2001
2293
|
rectDragId,
|
|
2002
2294
|
liveRect: {
|
|
2003
2295
|
x: liveRectX,
|
|
@@ -2048,7 +2340,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
2048
2340
|
// non-fullscreen canvas (e.g. a diagram strip) escape into surrounding UI.
|
|
2049
2341
|
_jsxs(GestureHandlerRootView, { style: [{ width, height, overflow: 'hidden' }, style], children: [backgroundLayers.map((layer) => (_jsx(BackgroundSkImageLoader, { layer: layer, resolveUrl: resolveImageUrl, deferToResolver: deferBackgroundToResolver, onImage: onBackgroundSkImage }, layer.id))), _jsx(GestureDetector, { gesture: gesture, children: _jsx(View, { style: { width, height }, collapsable: false, children: AnnotationCanvasSkia(skiaProps) }) }), renderMeasurementStamp && (_jsx(View, { pointerEvents: "box-none", style: StyleSheet.absoluteFill, children: state.effectiveCanvas.placedMeasurements.map((placed) => (_jsx(MeasurementStampOverlayItem, { placed: placed, measurement: placed.measurementId
|
|
2050
2342
|
? (state.measurementsById.get(placed.measurementId) ?? null)
|
|
2051
|
-
: (resolveStampMeasurement?.(placed) ?? null), selected: selection?.ids.includes(placed.id) ?? false, dragging: draggingId === placed.id, sliding: slidingId === placed.id, endpointDragging: epDragId === placed.id, rectResizing: rectDragId === placed.id, planeEdgeDragging: planeDrag != null && placed.planeId === planeDrag.planeId, zoomSnapshot: state.viewport.zoom, zoom: zoom, panX: panX, panY: panY, dragX: dragX, dragY: dragY, slideCtx: slideCtx, epCtx: epCtx, rectCtx: rectCtx, planeTileCtx: planeTileCtx, planeDragTX: planeDragTX, planeDragTY: planeDragTY, renderMeasurementStamp: renderMeasurementStamp, tileScaleFactor: state.tileScaleFactor, headerTileScaleFactor: state.headerTileScaleFactor, fileTileScaleFactor: state.fileTileScaleFactor, tileViewportScale: state.tileViewportScale, onStampPress: onMeasurementStampPress, onStampLongPress: onMeasurementStampLongPress, onRemove: onMeasurementStampRemove
|
|
2343
|
+
: (resolveStampMeasurement?.(placed) ?? null), selected: selection?.ids.includes(placed.id) ?? false, dragging: draggingId === placed.id, sliding: slidingId === placed.id, endpointDragging: epDragId === placed.id, rectResizing: rectDragId === placed.id, planeEdgeDragging: planeDrag != null && placed.planeId === planeDrag.planeId, zoomSnapshot: state.viewport.zoom, zoom: zoom, panX: panX, panY: panY, dragX: dragX, dragY: dragY, slideCtx: slideCtx, epCtx: epCtx, epLive: epLive, rectCtx: rectCtx, planeTileCtx: planeTileCtx, planeDragTX: planeDragTX, planeDragTY: planeDragTY, renderMeasurementStamp: renderMeasurementStamp, tileScaleFactor: state.tileScaleFactor, headerTileScaleFactor: state.headerTileScaleFactor, fileTileScaleFactor: state.fileTileScaleFactor, tileViewportScale: state.tileViewportScale, onStampPress: onMeasurementStampPress, onStampLongPress: onMeasurementStampLongPress, onRemove: onMeasurementStampRemove
|
|
2052
2344
|
? () => {
|
|
2053
2345
|
const defaultRemove = () => {
|
|
2054
2346
|
const { ops, keepSelection } = buildRemoveMeasurementOps(placed);
|
|
@@ -2075,7 +2367,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
2075
2367
|
measurementLineScale: 1 / LOUPE_MAGNIFICATION,
|
|
2076
2368
|
}) }))] }));
|
|
2077
2369
|
};
|
|
2078
|
-
const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging, sliding, endpointDragging, rectResizing, zoomSnapshot, zoom, panX, panY, dragX, dragY, slideCtx, epCtx, rectCtx, planeEdgeDragging, planeTileCtx, planeDragTX, planeDragTY, renderMeasurementStamp, tileScaleFactor, headerTileScaleFactor, fileTileScaleFactor, tileViewportScale, onStampPress, onStampLongPress, onRemove, }) => {
|
|
2370
|
+
const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging, sliding, endpointDragging, rectResizing, zoomSnapshot, zoom, panX, panY, dragX, dragY, slideCtx, epCtx, epLive, rectCtx, planeEdgeDragging, planeTileCtx, planeDragTX, planeDragTY, renderMeasurementStamp, tileScaleFactor, headerTileScaleFactor, fileTileScaleFactor, tileViewportScale, onStampPress, onStampLongPress, onRemove, }) => {
|
|
2079
2371
|
// Square tile or wide group-header pill — one footprint source of truth
|
|
2080
2372
|
// shared with the hit-test (stampTileDims).
|
|
2081
2373
|
const { width, height } = stampTileDims(placed, tileScaleFactor, tileViewportScale, headerTileScaleFactor ?? tileScaleFactor, fileTileScaleFactor ?? tileScaleFactor);
|
|
@@ -2151,13 +2443,15 @@ const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging,
|
|
|
2151
2443
|
worldY = c.ay + aby * t;
|
|
2152
2444
|
}
|
|
2153
2445
|
else if (endpointDragging) {
|
|
2154
|
-
// Tile stays at its linePos along the LIVE line:
|
|
2155
|
-
//
|
|
2446
|
+
// Tile stays at its linePos along the LIVE line: the moving endpoint
|
|
2447
|
+
// (handle 0 = a, 1 = b) comes from epLive — already drag-offset and
|
|
2448
|
+
// magnetically snapped — then lerp at t0.
|
|
2156
2449
|
const c = epCtx.value;
|
|
2157
|
-
const
|
|
2158
|
-
const
|
|
2159
|
-
const
|
|
2160
|
-
const
|
|
2450
|
+
const e = epLive.value;
|
|
2451
|
+
const ax = c.handle === 0 ? e.x : c.ax;
|
|
2452
|
+
const ay = c.handle === 0 ? e.y : c.ay;
|
|
2453
|
+
const bx = c.handle === 1 ? e.x : c.bx;
|
|
2454
|
+
const by = c.handle === 1 ? e.y : c.by;
|
|
2161
2455
|
worldX = ax + (bx - ax) * c.t0;
|
|
2162
2456
|
worldY = ay + (by - ay) * c.t0;
|
|
2163
2457
|
}
|
|
@@ -24,6 +24,7 @@ export interface AnnotationCanvasSkiaProps {
|
|
|
24
24
|
viewport?: ViewportState;
|
|
25
25
|
resolveImageUrl?: (storagePath: string) => Promise<string>;
|
|
26
26
|
deferBackgroundToResolver?: boolean;
|
|
27
|
+
hideLinkBadges?: boolean;
|
|
27
28
|
backgroundSkImages?: Record<string, SkImage | null>;
|
|
28
29
|
backgroundContent?: ReactNode;
|
|
29
30
|
valueFont: SkFont | null;
|
|
@@ -74,6 +75,26 @@ export interface AnnotationCanvasSkiaProps {
|
|
|
74
75
|
width: AnimatedNumber;
|
|
75
76
|
height: AnimatedNumber;
|
|
76
77
|
};
|
|
78
|
+
bgCropId?: string | null;
|
|
79
|
+
liveBgCrop?: {
|
|
80
|
+
x: AnimatedNumber;
|
|
81
|
+
y: AnimatedNumber;
|
|
82
|
+
width: AnimatedNumber;
|
|
83
|
+
height: AnimatedNumber;
|
|
84
|
+
};
|
|
85
|
+
liveBgCropClip?: {
|
|
86
|
+
x: number;
|
|
87
|
+
y: number;
|
|
88
|
+
width: number;
|
|
89
|
+
height: number;
|
|
90
|
+
} | {
|
|
91
|
+
value: {
|
|
92
|
+
x: number;
|
|
93
|
+
y: number;
|
|
94
|
+
width: number;
|
|
95
|
+
height: number;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
77
98
|
shapeResizeId?: string | null;
|
|
78
99
|
shapeResizePath?: SkPath | {
|
|
79
100
|
value: SkPath;
|
|
@@ -90,6 +111,12 @@ export interface AnnotationCanvasSkiaProps {
|
|
|
90
111
|
handleRingWidth?: number | {
|
|
91
112
|
value: number;
|
|
92
113
|
};
|
|
114
|
+
snapIndicator?: {
|
|
115
|
+
c: AnimatedPoint;
|
|
116
|
+
r: AnimatedNumber;
|
|
117
|
+
ringWidth: AnimatedNumber;
|
|
118
|
+
opacity?: AnimatedNumber;
|
|
119
|
+
} | null;
|
|
93
120
|
showPlaneHandles?: boolean;
|
|
94
121
|
planeDragId?: string | null;
|
|
95
122
|
planeDragIndex?: number | null;
|
|
@@ -106,5 +133,5 @@ export interface AnnotationCanvasSkiaProps {
|
|
|
106
133
|
wrapContent?: (content: ReactNode) => ReactNode;
|
|
107
134
|
canvasRef?: RefObject<CanvasRef | null>;
|
|
108
135
|
}
|
|
109
|
-
export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, deferBackgroundToResolver, backgroundSkImages, backgroundContent, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragGridPath, planeDragHandle, planeOutlineScale, measurementLineScale, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
|
|
136
|
+
export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, deferBackgroundToResolver, hideLinkBadges, backgroundSkImages, backgroundContent, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, bgCropId, liveBgCrop, liveBgCropClip, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, snapIndicator, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragGridPath, planeDragHandle, planeOutlineScale, measurementLineScale, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
|
|
110
137
|
export {};
|