@reekon-tools/boldr-utils 1.19.1 → 1.19.3

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.
@@ -492,17 +492,6 @@ export const AnnotationCanvasInner = (props) => {
492
492
  'worklet';
493
493
  return HANDLE_RING_PX / zoom.value;
494
494
  });
495
- // Loupe variants: divided by the loupe's extra magnification too, so handles
496
- // stay screen-constant INSIDE the lens as well — chrome shouldn't magnify
497
- // with the content, and at 2× it covers the very point being placed.
498
- const loupeHandleRadius = useDerivedValue(() => {
499
- 'worklet';
500
- return HANDLE_RADIUS_PX / zoom.value / LOUPE_MAGNIFICATION;
501
- });
502
- const loupeHandleRingWidth = useDerivedValue(() => {
503
- 'worklet';
504
- return HANDLE_RING_PX / zoom.value / LOUPE_MAGNIFICATION;
505
- });
506
495
  // ---- Plane-calibration handle drag (UI thread; Tool.planeEdit) ----------
507
496
  // The plane tool's JS preview path re-rendered the whole canvas (and
508
497
  // recomputed the photo-spanning grid) per pointer-move, dropping JS frames
@@ -538,29 +527,9 @@ export const AnnotationCanvasInner = (props) => {
538
527
  y: planeDragStartY.value + planeDragTY.value / zoom.value,
539
528
  };
540
529
  });
541
- // Black crosshair marking the dragged handle inside the lens, replacing its
542
- // disc there (the loupe render passes planeDragHandleHidden). Built in
543
- // SCREEN space and drawn as lens chrome inside the clip (see loupeWrap), so
544
- // its size is fixed pixels: project the handle through the same math as
545
- // loupeTransform — lensCenter + MAG · (screen(handle) − finger). That
546
- // offset from center is the grab offset the drag preserves, which is why a
547
- // fixed center mark sat visibly OFF the point and was removed.
548
- const loupeCrossPath = useDerivedValue(() => {
549
- 'worklet';
550
- const path = Skia.Path.Make();
551
- if (planeDragMode.value !== 1)
552
- return path;
553
- const p = planeDragHandle.value;
554
- const sx = (p.x - panX.value) * zoom.value;
555
- const sy = (p.y - panY.value) * zoom.value;
556
- const x = loupeCx + LOUPE_MAGNIFICATION * (sx - magTouchX.value);
557
- const y = loupeCy + LOUPE_MAGNIFICATION * (sy - magTouchY.value);
558
- path.moveTo(x - LOUPE_CROSS_ARM_PX, y);
559
- path.lineTo(x + LOUPE_CROSS_ARM_PX, y);
560
- path.moveTo(x, y - LOUPE_CROSS_ARM_PX);
561
- path.lineTo(x, y + LOUPE_CROSS_ARM_PX);
562
- return path;
563
- }, [loupeCx, loupeCy]);
530
+ // (The lens crosshair marking the dragged point — loupeCrossPath — is
531
+ // declared after the resize sections below: it mirrors every precision
532
+ // drag's live-point math, not just this plane handle's.)
564
533
  // WORKLET TWIN of PlaneElement's outline path (quad ring / reference line;
565
534
  // the scale ticks are skipped live and reappear on commit). The committed
566
535
  // plane + dragged index are captured from React state — setPlaneDrag
@@ -833,6 +802,91 @@ export const AnnotationCanvasInner = (props) => {
833
802
  const nmy = c.fy + (c.my - c.fy) * shapeResizeScale.value.sy;
834
803
  return Math.abs(nmy - c.fy) + SELECTION_PAD * 2;
835
804
  });
805
+ // Black crosshair marking the DRAGGED POINT inside the lens — every
806
+ // precision drag the loupe shows, not a fixed center mark: the lens center
807
+ // is the FINGER, which sits off the dragged point by the grab offset the
808
+ // drags deliberately preserve, so a center mark pointed at the wrong pixel
809
+ // (doubly wrong at 2×). Built in SCREEN space and drawn as lens chrome
810
+ // inside the clip (see loupeWrap), so its size is fixed pixels: the point
811
+ // projects through the same math as loupeTransform —
812
+ // lensCenter + MAG · (screen(point) − finger). Each branch is a one-line
813
+ // mirror of that drag's worklet twin above; 'center' covers the pointer-path
814
+ // drag-to-draw tools, whose tip IS the finger (no grab offset). The mode is
815
+ // React state captured at drag start — set before the loupe mounts.
816
+ const loupeCrossMode = planeDrag
817
+ ? 'plane'
818
+ : epDragId || shapeEpDragId
819
+ ? 'endpoint'
820
+ : rectDragId
821
+ ? 'rect-corner'
822
+ : shapeResizeId
823
+ ? 'shape-corner'
824
+ : resizingId
825
+ ? 'text-resize'
826
+ : 'center';
827
+ const loupeCrossPath = useDerivedValue(() => {
828
+ 'worklet';
829
+ const path = Skia.Path.Make();
830
+ let x = loupeCx;
831
+ let y = loupeCy;
832
+ if (loupeCrossMode !== 'center') {
833
+ let wx = 0;
834
+ let wy = 0;
835
+ if (loupeCrossMode === 'plane') {
836
+ if (planeDragMode.value !== 1)
837
+ return path;
838
+ const p = planeDragHandle.value;
839
+ wx = p.x;
840
+ wy = p.y;
841
+ }
842
+ else if (loupeCrossMode === 'endpoint') {
843
+ // Measurement-line and shape line/arrow endpoint drags both stage
844
+ // their line in epCtx; the grabbed end follows the finger.
845
+ const c = epCtx.value;
846
+ wx = (c.handle === 0 ? c.ax : c.bx) + dragX.value;
847
+ wy = (c.handle === 0 ? c.ay : c.by) + dragY.value;
848
+ }
849
+ else if (loupeCrossMode === 'rect-corner') {
850
+ const c = rectCtx.value;
851
+ wx = c.mx + dragX.value;
852
+ wy = c.my + dragY.value;
853
+ }
854
+ else if (loupeCrossMode === 'shape-corner') {
855
+ // The grabbed corner under the live (clamped, possibly uniform)
856
+ // scale — matches shapeResizeScale, so ellipses mark the true corner.
857
+ const c = shapeResizeCtx.value;
858
+ const s = shapeResizeScale.value;
859
+ wx = c.fx + (c.mx - c.fx) * s.sx;
860
+ wy = c.fy + (c.my - c.fy) * s.sy;
861
+ }
862
+ else {
863
+ // WORKLET TWIN of resizeTransform's clamped scale above: the corner
864
+ // handle under scale-about-pivot.
865
+ const c = resizeCtx.value;
866
+ const bx = c.hx - c.px;
867
+ const by = c.hy - c.py;
868
+ const baseLen = Math.sqrt(bx * bx + by * by);
869
+ let s = 1;
870
+ if (baseLen > 0) {
871
+ const nx = bx + dragX.value;
872
+ const ny = by + dragY.value;
873
+ s = Math.sqrt(nx * nx + ny * ny) / baseLen;
874
+ }
875
+ s = Math.min(c.maxS, Math.max(c.minS, s));
876
+ wx = c.px + bx * s;
877
+ wy = c.py + by * s;
878
+ }
879
+ const sx = (wx - panX.value) * zoom.value;
880
+ const sy = (wy - panY.value) * zoom.value;
881
+ x = loupeCx + LOUPE_MAGNIFICATION * (sx - magTouchX.value);
882
+ y = loupeCy + LOUPE_MAGNIFICATION * (sy - magTouchY.value);
883
+ }
884
+ path.moveTo(x - LOUPE_CROSS_ARM_PX, y);
885
+ path.lineTo(x + LOUPE_CROSS_ARM_PX, y);
886
+ path.moveTo(x, y - LOUPE_CROSS_ARM_PX);
887
+ path.lineTo(x, y + LOUPE_CROSS_ARM_PX);
888
+ return path;
889
+ }, [loupeCx, loupeCy, loupeCrossMode]);
836
890
  // Per-gesture refs so we always emit a matching down/move/up sequence.
837
891
  const pointerIdRef = useRef(1);
838
892
  const inFlightRef = useRef(null);
@@ -2007,16 +2061,17 @@ export const AnnotationCanvasInner = (props) => {
2007
2061
  ...skiaProps,
2008
2062
  worldTransform: loupeTransform,
2009
2063
  wrapContent: loupeWrap,
2010
- // Chrome stays screen-constant inside the lens: handles at their
2011
- // normal pixel size, the plane outline at its normal apparent
2012
- // thickness — only the CONTENT magnifies. At 2× the fat
2013
- // calibration chrome covered the exact point being placed.
2014
- handleRadius: loupeHandleRadius,
2015
- handleRingWidth: loupeHandleRingWidth,
2064
+ // Inside the lens only the CONTENT magnifies; chrome doesn't.
2065
+ // Handle discs don't render at all (handleRadius undefined gates
2066
+ // every Handle/plane-disc draw) — the black crosshair drawn in
2067
+ // the lens chrome (loupeCrossPath) marks the dragged point, and
2068
+ // at 2× a disc just covered the pixel being placed. Plane
2069
+ // outlines and measurement lines drop to their normal apparent
2070
+ // thickness instead of doubling.
2071
+ handleRadius: undefined,
2072
+ handleRingWidth: undefined,
2016
2073
  planeOutlineScale: 1 / LOUPE_MAGNIFICATION,
2017
- // The dragged handle renders as the black crosshair drawn in the
2018
- // lens chrome (loupeCrossPath) instead of its disc.
2019
- planeDragHandleHidden: true,
2074
+ measurementLineScale: 1 / LOUPE_MAGNIFICATION,
2020
2075
  }) }))] }));
2021
2076
  };
2022
2077
  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, }) => {
@@ -100,10 +100,10 @@ export interface AnnotationCanvasSkiaProps {
100
100
  };
101
101
  planeDragHandle?: AnimatedPoint;
102
102
  planeOutlineScale?: number;
103
- planeDragHandleHidden?: boolean;
103
+ measurementLineScale?: number;
104
104
  customPreview?: ReactNode;
105
105
  wrapContent?: (content: ReactNode) => ReactNode;
106
106
  canvasRef?: RefObject<CanvasRef | null>;
107
107
  }
108
- export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, 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, planeDragHandleHidden, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
108
+ export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, 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;
109
109
  export {};
@@ -80,7 +80,7 @@ const SelectionBox = ({ bounds, isDragging, transform, }) => (_jsx(DraggableElem
80
80
  // since the function-call pattern works identically on native we use it
81
81
  // in both Inners for consistency. Don't add hooks here; this is a plain
82
82
  // JSX-returning helper, not a component.
83
- export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, 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 = 1, planeDragHandleHidden, customPreview, wrapContent, canvasRef, }) => {
83
+ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, 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 = 1, measurementLineScale = 1, customPreview, wrapContent, canvasRef, }) => {
84
84
  // Doc rect a SCALE-mode plane grid covers: the first background layer (the
85
85
  // photo being calibrated), else the document rect. Pinned to layer 0 like
86
86
  // the tile-sizing reference — adding a second image must not re-span the
@@ -100,9 +100,7 @@ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTran
100
100
  ? plane.corners
101
101
  : [plane.refLine.a, plane.refLine.b];
102
102
  return (_jsxs(Group, { children: [planeDragGridPath && (_jsx(Path, { path: planeDragGridPath, color: PLANE_GRID_COLOR, style: "stroke", strokeWidth: PLANE_GRID_WIDTH, opacity: PLANE_GRID_OPACITY })), _jsx(Path, { path: planeDragPath, color: PLANE_CHROME_COLOR, style: "stroke", strokeWidth: PLANE_OUTLINE_WIDTH * planeOutlineScale, strokeJoin: "round" }), handleRadius != null &&
103
- statics.map((p, i) => i === planeDragIndex ? null : (_jsxs(Group, { children: [_jsx(Circle, { c: p, r: handleRadius, color: PLANE_CHROME_COLOR }), _jsx(Circle, { c: p, r: handleRadius, color: "#FFFFFF", style: "stroke", strokeWidth: handleRingWidth })] }, i))), handleRadius != null &&
104
- planeDragHandle &&
105
- !planeDragHandleHidden && (_jsxs(_Fragment, { children: [_jsx(Circle, { c: planeDragHandle, r: handleRadius, color: PLANE_CHROME_COLOR }), _jsx(Circle, { c: planeDragHandle, r: handleRadius, color: "#FFFFFF", style: "stroke", strokeWidth: handleRingWidth })] }))] }, plane.id));
103
+ statics.map((p, i) => i === planeDragIndex ? null : (_jsxs(Group, { children: [_jsx(Circle, { c: p, r: handleRadius, color: PLANE_CHROME_COLOR }), _jsx(Circle, { c: p, r: handleRadius, color: "#FFFFFF", style: "stroke", strokeWidth: handleRingWidth })] }, i))), handleRadius != null && planeDragHandle && (_jsxs(_Fragment, { children: [_jsx(Circle, { c: planeDragHandle, r: handleRadius, color: PLANE_CHROME_COLOR }), _jsx(Circle, { c: planeDragHandle, r: handleRadius, color: "#FFFFFF", style: "stroke", strokeWidth: handleRingWidth })] }))] }, plane.id));
106
104
  }
107
105
  return (_jsx(PlaneElement, { plane: plane, scaleBounds: planeScaleBounds, showHandles: showPlaneHandles === true, handleRadius: showPlaneHandles ? handleRadius : undefined, handleRingWidth: showPlaneHandles ? handleRingWidth : undefined, outlineScale: planeOutlineScale }, plane.id));
108
106
  }), effectiveCanvas.strokes.map((stroke) => (_jsx(DraggableElement, { isDragging: stroke.id === draggingId, transform: dragTransform, children: _jsx(StrokeElement, { stroke: stroke }) }, stroke.id))), effectiveCanvas.shapes.map((shape) => {
@@ -183,7 +181,7 @@ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTran
183
181
  const p1 = isEndpointDrag && liveLineP1 ? liveLineP1 : placed.line.a;
184
182
  const p2 = isEndpointDrag && liveLineP2 ? liveLineP2 : placed.line.b;
185
183
  const lineColor = placed.lineColor ?? MEASUREMENT_LINE_COLOR;
186
- const lineWidth = placed.lineWidth ?? MEASUREMENT_LINE_WIDTH;
184
+ const lineWidth = (placed.lineWidth ?? MEASUREMENT_LINE_WIDTH) * measurementLineScale;
187
185
  // Solid filled-triangle arrowheads at endpoint b (the "end", lineCap)
188
186
  // and/or endpoint a (the "start", lineStartCap). Built from the static
189
187
  // endpoints, so they're skipped during a live endpoint drag (they
@@ -451,6 +451,7 @@ export interface Label extends FirestoreDoc, Timestamps {
451
451
  * the matching print-settings selector (measurement/QR/image) instead of
452
452
  * the public label library. Absent/null = ordinary library label. */
453
453
  templateType?: 'measurement' | 'qr' | 'image' | null;
454
+ starred?: boolean;
454
455
  }
455
456
  export interface LabelRow {
456
457
  id: string;
@@ -603,6 +604,11 @@ export interface UserDocument extends FirestoreDoc, Timestamps {
603
604
  * shared across orgs, so the star can't live on the template doc (that's
604
605
  * `Template.starred`, for the org's own templates). */
605
606
  starredPublicTemplateIds?: string[];
607
+ /** Public labels this user starred in the label library. Same reasoning as
608
+ * `starredPublicTemplateIds`: public labels are shared across orgs, so the
609
+ * star can't live on the label doc (that's `Label.starred`, for the org's
610
+ * own labels). */
611
+ starredPublicLabelIds?: string[];
606
612
  }
607
613
  export interface UserComment extends FirestoreDoc, Timestamps {
608
614
  comment: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.19.1",
3
+ "version": "1.19.3",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",