@reekon-tools/boldr-utils 1.14.2 → 1.15.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.js +2 -2
- package/dist/annotation/canvas/AnnotationCanvasInner.native.js +40 -17
- package/dist/annotation/canvas/Tool.d.ts +4 -3
- package/dist/annotation/canvas/measurementGeometry.d.ts +1 -1
- package/dist/annotation/canvas/measurementGeometry.js +4 -4
- package/dist/annotation/canvas/measurementPicker.d.ts +5 -0
- package/dist/annotation/canvas/stampLayout.d.ts +4 -2
- package/dist/annotation/canvas/stampLayout.js +29 -8
- package/dist/annotation/canvas/tools/panTool.js +1 -1
- package/dist/annotation/canvas/tools/selectTool.js +10 -10
- package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +4 -1
- package/dist/annotation/canvas/useAnnotationCanvasState.js +73 -2
- package/dist/exports.d.ts +2 -2
- package/dist/exports.js +1 -1
- package/dist/theme/index.d.ts +3 -0
- package/dist/theme/index.js +3 -0
- package/dist/theme/preset.d.ts +120 -0
- package/dist/theme/preset.js +25 -0
- package/dist/theme/semantic.d.ts +136 -0
- package/dist/theme/semantic.js +116 -0
- package/dist/types/annotation.d.ts +5 -1
- package/dist/types/annotation.js +11 -1
- package/dist/types/firestore.d.ts +0 -2
- 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
|
@@ -284,7 +284,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
284
284
|
const world = state.ctx.viewport.screenToWorld(screen);
|
|
285
285
|
const placed = [...state.effectiveCanvas.placedMeasurements]
|
|
286
286
|
.reverse()
|
|
287
|
-
.find((m) => hitPlacedMeasurement(m, world, state.viewport.zoom, state.tileScaleFactor, state.tileViewportScale, state.headerTileScaleFactor));
|
|
287
|
+
.find((m) => hitPlacedMeasurement(m, world, state.viewport.zoom, state.tileScaleFactor, state.tileViewportScale, state.headerTileScaleFactor, state.fileTileScaleFactor));
|
|
288
288
|
if (placed)
|
|
289
289
|
onMeasurementStampDoubleTap(placed);
|
|
290
290
|
}, [onMeasurementStampDoubleTap, state]);
|
|
@@ -320,7 +320,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
320
320
|
}, children: state.effectiveCanvas.placedMeasurements.map((placed) => {
|
|
321
321
|
// Square tile or wide group-header pill — one footprint source of
|
|
322
322
|
// truth shared with the hit-test (stampTileDims).
|
|
323
|
-
const { width, height } = stampTileDims(placed, state.tileScaleFactor, state.tileViewportScale, state.headerTileScaleFactor);
|
|
323
|
+
const { width, height } = stampTileDims(placed, state.tileScaleFactor, state.tileViewportScale, state.headerTileScaleFactor, state.fileTileScaleFactor);
|
|
324
324
|
const cx = (placed.anchor.x - state.viewport.pan.x) * state.viewport.zoom;
|
|
325
325
|
const cy = (placed.anchor.y - state.viewport.pan.y) * state.viewport.zoom;
|
|
326
326
|
const isSelected = selection?.ids.includes(placed.id) ?? false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Group, Line, Rect, RoundedRect, Skia, rect, rrect, useFont, } from '@shopify/react-native-skia';
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
4
4
|
import { StyleSheet, TouchableOpacity, View, } from 'react-native';
|
|
5
5
|
import { Gesture, GestureDetector, GestureHandlerRootView, } from 'react-native-gesture-handler';
|
|
6
6
|
import Animated, { runOnJS, runOnUI, useAnimatedStyle, useDerivedValue, useSharedValue, } from 'react-native-reanimated';
|
|
@@ -875,7 +875,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
875
875
|
return;
|
|
876
876
|
const world = st.ctx.viewport.screenToWorld(screen);
|
|
877
877
|
const zoomNow = st.ctx.viewport.state.zoom;
|
|
878
|
-
const hit = dragSelection.hitTest(st.ctx.document, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor);
|
|
878
|
+
const hit = dragSelection.hitTest(st.ctx.document, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor, st.ctx.fileTileScaleFactor);
|
|
879
879
|
if (!hit || hit.kind !== 'measurement' || hit.id !== prevSelectedId) {
|
|
880
880
|
return;
|
|
881
881
|
}
|
|
@@ -1193,7 +1193,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1193
1193
|
// so a selected tile isn't stuck until you deselect it).
|
|
1194
1194
|
const selId = st.ctx.selection?.ids[0];
|
|
1195
1195
|
if (selId &&
|
|
1196
|
-
!cfg.isSelectedTileGrab?.(st.ctx.document, selId, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor)) {
|
|
1196
|
+
!cfg.isSelectedTileGrab?.(st.ctx.document, selId, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor, st.ctx.fileTileScaleFactor)) {
|
|
1197
1197
|
const handle = cfg.hitTestHandle?.(st.ctx.document, selId, world, zoomNow);
|
|
1198
1198
|
if (handle) {
|
|
1199
1199
|
const m = st.ctx.document.placedMeasurements.find((x) => x.id === selId);
|
|
@@ -1293,7 +1293,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1293
1293
|
return;
|
|
1294
1294
|
}
|
|
1295
1295
|
}
|
|
1296
|
-
const hit = cfg.hitTest(st.ctx.document, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor);
|
|
1296
|
+
const hit = cfg.hitTest(st.ctx.document, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor, st.ctx.fileTileScaleFactor);
|
|
1297
1297
|
if (!hit) {
|
|
1298
1298
|
// Backgrounds can cover the whole document, so they only get the
|
|
1299
1299
|
// drag once every annotation element misses, and only when ALREADY
|
|
@@ -1313,7 +1313,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1313
1313
|
st.ctx.setSelection({ ids: [hit.id] });
|
|
1314
1314
|
// Grabbing a line annotation's tile slides it; otherwise group-move.
|
|
1315
1315
|
const grab = hit.kind === 'measurement'
|
|
1316
|
-
? cfg.classifyMeasurementGrab?.(st.ctx.document, hit.id, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor)
|
|
1316
|
+
? cfg.classifyMeasurementGrab?.(st.ctx.document, hit.id, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor, st.ctx.fileTileScaleFactor)
|
|
1317
1317
|
: 'move';
|
|
1318
1318
|
if (grab === 'slide') {
|
|
1319
1319
|
const m = st.ctx.document.placedMeasurements.find((x) => x.id === hit.id);
|
|
@@ -1621,7 +1621,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1621
1621
|
// non-fullscreen canvas (e.g. a diagram strip) escape into surrounding UI.
|
|
1622
1622
|
_jsxs(GestureHandlerRootView, { style: [{ width, height, overflow: 'hidden' }, style], children: [backgroundLayers.map((layer) => (_jsx(BackgroundSkImageLoader, { layer: layer, resolveUrl: resolveImageUrl, 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
|
|
1623
1623
|
? (state.measurementsById.get(placed.measurementId) ?? null)
|
|
1624
|
-
: (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, zoomSnapshot: state.viewport.zoom, zoom: zoom, panX: panX, panY: panY, dragX: dragX, dragY: dragY, slideCtx: slideCtx, epCtx: epCtx, rectCtx: rectCtx, renderMeasurementStamp: renderMeasurementStamp, tileScaleFactor: state.tileScaleFactor, headerTileScaleFactor: state.headerTileScaleFactor, tileViewportScale: state.tileViewportScale, onStampPress: onMeasurementStampPress, onStampLongPress: onMeasurementStampLongPress, onRemove: onMeasurementStampRemove
|
|
1624
|
+
: (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, zoomSnapshot: state.viewport.zoom, zoom: zoom, panX: panX, panY: panY, dragX: dragX, dragY: dragY, slideCtx: slideCtx, epCtx: epCtx, rectCtx: rectCtx, renderMeasurementStamp: renderMeasurementStamp, tileScaleFactor: state.tileScaleFactor, headerTileScaleFactor: state.headerTileScaleFactor, fileTileScaleFactor: state.fileTileScaleFactor, tileViewportScale: state.tileViewportScale, onStampPress: onMeasurementStampPress, onStampLongPress: onMeasurementStampLongPress, onRemove: onMeasurementStampRemove
|
|
1625
1625
|
? () => {
|
|
1626
1626
|
const defaultRemove = () => {
|
|
1627
1627
|
const { ops, keepSelection } = buildRemoveMeasurementOps(placed);
|
|
@@ -1637,10 +1637,10 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1637
1637
|
wrapContent: loupeWrap,
|
|
1638
1638
|
}) }))] }));
|
|
1639
1639
|
};
|
|
1640
|
-
const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging, sliding, endpointDragging, rectResizing, zoomSnapshot, zoom, panX, panY, dragX, dragY, slideCtx, epCtx, rectCtx, renderMeasurementStamp, tileScaleFactor, headerTileScaleFactor, tileViewportScale, onStampPress, onStampLongPress, onRemove, }) => {
|
|
1640
|
+
const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging, sliding, endpointDragging, rectResizing, zoomSnapshot, zoom, panX, panY, dragX, dragY, slideCtx, epCtx, rectCtx, renderMeasurementStamp, tileScaleFactor, headerTileScaleFactor, fileTileScaleFactor, tileViewportScale, onStampPress, onStampLongPress, onRemove, }) => {
|
|
1641
1641
|
// Square tile or wide group-header pill — one footprint source of truth
|
|
1642
1642
|
// shared with the hit-test (stampTileDims).
|
|
1643
|
-
const { width, height } = stampTileDims(placed, tileScaleFactor, tileViewportScale, headerTileScaleFactor ?? tileScaleFactor);
|
|
1643
|
+
const { width, height } = stampTileDims(placed, tileScaleFactor, tileViewportScale, headerTileScaleFactor ?? tileScaleFactor, fileTileScaleFactor ?? tileScaleFactor);
|
|
1644
1644
|
const halfW = width / 2;
|
|
1645
1645
|
const halfH = height / 2;
|
|
1646
1646
|
// Remove-"X" touch target, sized as a fraction of the tile and corner-pinned
|
|
@@ -1659,13 +1659,36 @@ const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging,
|
|
|
1659
1659
|
// in the same render the new anchor arrives — no flicker.
|
|
1660
1660
|
//
|
|
1661
1661
|
// Size: the LAID-OUT box is the tile's screen size at `zoomSnapshot` (the
|
|
1662
|
-
// JS viewport this render was built from)
|
|
1663
|
-
//
|
|
1664
|
-
//
|
|
1665
|
-
//
|
|
1666
|
-
// the
|
|
1667
|
-
//
|
|
1662
|
+
// JS viewport this render was built from) — a real re-layout, which is what
|
|
1663
|
+
// keeps the tile crisp: iOS rasterizes a view at its laid-out size, so a
|
|
1664
|
+
// box magnified by a transform alone blurs at 3× (tried; illegible). Tiles
|
|
1665
|
+
// scale with zoom, so while a pinch/glide has the live zoom ahead of that
|
|
1666
|
+
// snapshot the worklet bridges the gap with a scale transform (about the
|
|
1667
|
+
// centered anchor); when the gesture ends and the JS snapshot syncs, the
|
|
1668
|
+
// item re-renders at the final size and the scale settles back to 1.
|
|
1669
|
+
//
|
|
1670
|
+
// That settle MUST land in the same frame as the re-layout, and captured
|
|
1671
|
+
// props can't do it: reanimated re-evaluates a useAnimatedStyle whose
|
|
1672
|
+
// captured values changed from a PASSIVE effect — a later task, after
|
|
1673
|
+
// React's commit has already mounted the new layout — so every pinch
|
|
1674
|
+
// release drew one frame of the new, bigger box under the old, still-
|
|
1675
|
+
// magnifying transform and the whole canvas flashed (Asana
|
|
1676
|
+
// 1217570500895545 QA). The layout-dependent inputs therefore travel as
|
|
1677
|
+
// SHARED VALUES written from useLayoutEffect: that runs inside the commit
|
|
1678
|
+
// task, right behind the mount dispatch, and mapper runs on the UI runtime
|
|
1679
|
+
// are microtasks, so the transform update lands in the same main-queue
|
|
1680
|
+
// drain as the new layout — before Core Animation commits the frame.
|
|
1681
|
+
// (`dragging`/`sliding` stay captured: the anchor they gate changes in the
|
|
1682
|
+
// same render and doesn't touch layout, so both lag together, harmlessly.)
|
|
1668
1683
|
const zoomBase = zoomSnapshot > 0 ? zoomSnapshot : 1;
|
|
1684
|
+
const zoomBaseSv = useSharedValue(zoomBase);
|
|
1685
|
+
const halfWSv = useSharedValue(halfW);
|
|
1686
|
+
const halfHSv = useSharedValue(halfH);
|
|
1687
|
+
useLayoutEffect(() => {
|
|
1688
|
+
zoomBaseSv.value = zoomBase;
|
|
1689
|
+
halfWSv.value = halfW;
|
|
1690
|
+
halfHSv.value = halfH;
|
|
1691
|
+
}, [zoomBase, halfW, halfH, zoomBaseSv, halfWSv, halfHSv]);
|
|
1669
1692
|
const animatedStyle = useAnimatedStyle(() => {
|
|
1670
1693
|
'worklet';
|
|
1671
1694
|
let worldX = anchorX;
|
|
@@ -1714,11 +1737,11 @@ const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging,
|
|
|
1714
1737
|
const cy = (worldY - panY.value) * zoom.value;
|
|
1715
1738
|
return {
|
|
1716
1739
|
transform: [
|
|
1717
|
-
{ translateX: cx -
|
|
1718
|
-
{ translateY: cy -
|
|
1740
|
+
{ translateX: cx - halfWSv.value },
|
|
1741
|
+
{ translateY: cy - halfHSv.value },
|
|
1719
1742
|
// Scales about the box center, which the translate just pinned to
|
|
1720
1743
|
// the anchor — so the tile grows/shrinks in place.
|
|
1721
|
-
{ scale: zoom.value /
|
|
1744
|
+
{ scale: zoom.value / zoomBaseSv.value },
|
|
1722
1745
|
],
|
|
1723
1746
|
};
|
|
1724
1747
|
});
|
|
@@ -25,6 +25,7 @@ export interface ToolContext {
|
|
|
25
25
|
tileViewportScale: number;
|
|
26
26
|
tileScaleFactor: number;
|
|
27
27
|
headerTileScaleFactor: number;
|
|
28
|
+
fileTileScaleFactor: number;
|
|
28
29
|
preview(patch: AnnotationDocumentPatch): void;
|
|
29
30
|
commit(patch: AnnotationDocumentPatch): void;
|
|
30
31
|
setSelection(selection: Selection | null): void;
|
|
@@ -55,12 +56,12 @@ export interface ShapeDrawConfig {
|
|
|
55
56
|
}
|
|
56
57
|
export type DragElementKind = 'stroke' | 'shape' | 'measurement' | 'background';
|
|
57
58
|
export interface DragSelectionConfig {
|
|
58
|
-
hitTest(doc: AnnotationCanvasState, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number, headerTileScaleFactor?: number): {
|
|
59
|
+
hitTest(doc: AnnotationCanvasState, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number, headerTileScaleFactor?: number, fileTileScaleFactor?: number): {
|
|
59
60
|
id: AnnotationElementId;
|
|
60
61
|
kind: DragElementKind;
|
|
61
62
|
} | null;
|
|
62
63
|
buildTranslatePatch(doc: AnnotationCanvasState, id: AnnotationElementId, kind: DragElementKind, delta: Vec2): AnnotationDocumentPatch | null;
|
|
63
|
-
classifyMeasurementGrab?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number, headerTileScaleFactor?: number): 'slide' | 'move';
|
|
64
|
+
classifyMeasurementGrab?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number, headerTileScaleFactor?: number, fileTileScaleFactor?: number): 'slide' | 'move';
|
|
64
65
|
buildSlidePatch?(doc: AnnotationCanvasState, id: AnnotationElementId, delta: Vec2, zoom: number): AnnotationDocumentPatch | null;
|
|
65
66
|
hitTestHandle?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number): 'a' | 'b' | null;
|
|
66
67
|
buildEndpointPatch?(doc: AnnotationCanvasState, id: AnnotationElementId, handle: 'a' | 'b', delta: Vec2): AnnotationDocumentPatch | null;
|
|
@@ -78,7 +79,7 @@ export interface DragSelectionConfig {
|
|
|
78
79
|
fixed: Vec2;
|
|
79
80
|
} | null;
|
|
80
81
|
buildShapeCornerPatch?(doc: AnnotationCanvasState, id: AnnotationElementId, corner: RectCorner, delta: Vec2): AnnotationDocumentPatch | null;
|
|
81
|
-
isSelectedTileGrab?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number, headerTileScaleFactor?: number): boolean;
|
|
82
|
+
isSelectedTileGrab?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number, headerTileScaleFactor?: number, fileTileScaleFactor?: number): boolean;
|
|
82
83
|
hitTestBackground?(doc: AnnotationCanvasState, world: Vec2): {
|
|
83
84
|
id: AnnotationElementId;
|
|
84
85
|
} | null;
|
|
@@ -36,7 +36,7 @@ export declare const rectCornerPoint: (rect: {
|
|
|
36
36
|
b: Vec2;
|
|
37
37
|
}, corner: RectCorner) => Vec2;
|
|
38
38
|
export declare const oppositeRectCorner: (corner: RectCorner) => RectCorner;
|
|
39
|
-
export declare const hitPlacedMeasurement: (m: PlacedMeasurementRef, p: Vec2, zoom?: number, tileScaleFactor?: number, viewportScale?: number, headerTileScaleFactor?: number) => boolean;
|
|
39
|
+
export declare const hitPlacedMeasurement: (m: PlacedMeasurementRef, p: Vec2, zoom?: number, tileScaleFactor?: number, viewportScale?: number, headerTileScaleFactor?: number, fileTileScaleFactor?: number) => boolean;
|
|
40
40
|
export interface RemoveMeasurementResult {
|
|
41
41
|
ops: AnnotationPatchOp[];
|
|
42
42
|
keepSelection: boolean;
|
|
@@ -110,15 +110,15 @@ const segmentDistSq = (p, a, b) => {
|
|
|
110
110
|
// around the anchor, the line body of a line annotation, or the
|
|
111
111
|
// border ring of a rectangle annotation (interiors stay transparent to hits so
|
|
112
112
|
// elements inside remain reachable).
|
|
113
|
-
export const hitPlacedMeasurement = (m, p, zoom = 1, tileScaleFactor = 1, viewportScale = 1, headerTileScaleFactor = tileScaleFactor) => {
|
|
113
|
+
export const hitPlacedMeasurement = (m, p, zoom = 1, tileScaleFactor = 1, viewportScale = 1, headerTileScaleFactor = tileScaleFactor, fileTileScaleFactor = tileScaleFactor) => {
|
|
114
114
|
// stampTileDims yields the tile's CURRENT screen-space box (the
|
|
115
115
|
// zoom-relative multiplier arrives via `viewportScale`); converting the
|
|
116
116
|
// half-extents (+ padding) back to doc space via the zoom keeps the hit box
|
|
117
117
|
// matching what's drawn — a doc-constant footprint (blank and populated
|
|
118
|
-
// stamps share the square footprint; group-header chips are a wide pill
|
|
119
|
-
// sized by
|
|
118
|
+
// stamps share the square footprint; group-header chips are a wide pill and
|
|
119
|
+
// object tiles a square, each sized by its OWN document-wide knob — see
|
|
120
120
|
// stampTileDims in stampLayout).
|
|
121
|
-
const dims = stampTileDims(m, tileScaleFactor, viewportScale, headerTileScaleFactor);
|
|
121
|
+
const dims = stampTileDims(m, tileScaleFactor, viewportScale, headerTileScaleFactor, fileTileScaleFactor);
|
|
122
122
|
const halfW = (dims.width / 2 + STAMP_HIT_PADDING) / zoom;
|
|
123
123
|
const halfH = (dims.height / 2 + STAMP_HIT_PADDING) / zoom;
|
|
124
124
|
const dx = Math.abs(p.x - m.anchor.x);
|
|
@@ -20,13 +20,15 @@ export declare const TILE_SCALE_MIN = 0.4;
|
|
|
20
20
|
export declare const TILE_SCALE_MAX = 2;
|
|
21
21
|
export declare const resolveTileScaleFactor: (canvas: Pick<AnnotationCanvasState, "tileScaleFactor" | "tileScaleFactorMobile">, platform: TileScalePlatform) => number;
|
|
22
22
|
export declare const resolveHeaderTileScaleFactor: (canvas: Pick<AnnotationCanvasState, "tileScaleFactor" | "tileScaleFactorMobile" | "headerTileScaleFactor" | "headerTileScaleFactorMobile">, platform: TileScalePlatform) => number;
|
|
23
|
+
export declare const resolveFileTileScaleFactor: (canvas: Pick<AnnotationCanvasState, "tileScaleFactor" | "tileScaleFactorMobile" | "fileTileScaleFactor" | "fileTileScaleFactorMobile">, platform: TileScalePlatform) => number;
|
|
23
24
|
export declare const clampTileScale: (v: number) => number;
|
|
24
25
|
export declare const tileDocSizeForFactor: (factor: number, zoom: number) => number;
|
|
25
26
|
export declare const tileScaleFromDocSize: (docSize: number, zoom: number) => number;
|
|
26
|
-
export declare const
|
|
27
|
+
export declare const isFileStamp: (m: Pick<PlacedMeasurementRef, "fileId">) => boolean;
|
|
28
|
+
export declare const isUnassociatedStamp: (m: Pick<PlacedMeasurementRef, "measurementId" | "measurementPath" | "columnId" | "isGroupHeader" | "fileId">) => boolean;
|
|
27
29
|
export declare const stampTileSize: (m: Pick<PlacedMeasurementRef, "scale">, tileScaleFactor?: number, viewportScale?: number) => number;
|
|
28
30
|
export interface StampTileDims {
|
|
29
31
|
width: number;
|
|
30
32
|
height: number;
|
|
31
33
|
}
|
|
32
|
-
export declare const stampTileDims: (m: Pick<PlacedMeasurementRef, "scale" | "isGroupHeader" | "collapsed">, tileScaleFactor?: number, viewportScale?: number, headerTileScaleFactor?: number) => StampTileDims;
|
|
34
|
+
export declare const stampTileDims: (m: Pick<PlacedMeasurementRef, "scale" | "isGroupHeader" | "collapsed" | "fileId">, tileScaleFactor?: number, viewportScale?: number, headerTileScaleFactor?: number, fileTileScaleFactor?: number) => StampTileDims;
|
|
@@ -90,8 +90,15 @@ export const resolveTileScaleFactor = (canvas, platform) => (platform === 'mobil
|
|
|
90
90
|
// resolve through here so they agree.
|
|
91
91
|
export const resolveHeaderTileScaleFactor = (canvas, platform) => (platform === 'mobile'
|
|
92
92
|
? (canvas.headerTileScaleFactorMobile ?? canvas.headerTileScaleFactor)
|
|
93
|
-
: canvas.headerTileScaleFactor) ??
|
|
94
|
-
|
|
93
|
+
: canvas.headerTileScaleFactor) ?? resolveTileScaleFactor(canvas, platform);
|
|
94
|
+
// Resolve the document-wide OBJECT-tile-scale knob for a platform — the third
|
|
95
|
+
// knob (Asana 1217570500895545), resolved exactly like the header knob: own
|
|
96
|
+
// platform value → web value → the MEASUREMENT factor, so an object tile
|
|
97
|
+
// placed before its knob was ever moved matches the measurement tiles beside
|
|
98
|
+
// it. The ONE read path for the file fields.
|
|
99
|
+
export const resolveFileTileScaleFactor = (canvas, platform) => (platform === 'mobile'
|
|
100
|
+
? (canvas.fileTileScaleFactorMobile ?? canvas.fileTileScaleFactor)
|
|
101
|
+
: canvas.fileTileScaleFactor) ?? resolveTileScaleFactor(canvas, platform);
|
|
95
102
|
// Clamp a tile-scale-factor candidate to the supported range. The single guard
|
|
96
103
|
// for the value before it lands in the document (slider input, restored docs).
|
|
97
104
|
export const clampTileScale = (v) => v < TILE_SCALE_MIN ? TILE_SCALE_MIN : v > TILE_SCALE_MAX ? TILE_SCALE_MAX : v;
|
|
@@ -130,12 +137,21 @@ export const tileScaleFromDocSize = (docSize, zoom) => (docSize * zoom) / STAMP_
|
|
|
130
137
|
// (An even earlier per-canvas auto-multiplier — tile size derived from the
|
|
131
138
|
// canvas's pixel size — was retired for the user-driven "Tile size" slider,
|
|
132
139
|
// which remains: the slider and the zoom multiplier compose.)
|
|
140
|
+
// A placed element is an OBJECT tile (a group file placed from the picker's
|
|
141
|
+
// object grid) when it carries a `fileId` — the one field measurement, column
|
|
142
|
+
// and header tiles never set. The single discriminant every host and engine
|
|
143
|
+
// path should branch on, so the check can't drift between them.
|
|
144
|
+
export const isFileStamp = (m) => !!m.fileId;
|
|
133
145
|
// A placed measurement is an unassociated input until a measurement reference
|
|
134
146
|
// is attached (id or path) OR it is bound to a form column (`columnId`). Such
|
|
135
147
|
// stamps render as the blank "+" placeholder (same footprint as a populated
|
|
136
|
-
// tile — see STAMP_TILE_SIZE). Group-header chips
|
|
137
|
-
// NOT inputs — never treat them as blank "+" placeholders.
|
|
138
|
-
export const isUnassociatedStamp = (m) => !m.isGroupHeader &&
|
|
148
|
+
// tile — see STAMP_TILE_SIZE). Group-header chips and object tiles carry
|
|
149
|
+
// neither ref but are NOT inputs — never treat them as blank "+" placeholders.
|
|
150
|
+
export const isUnassociatedStamp = (m) => !m.isGroupHeader &&
|
|
151
|
+
!isFileStamp(m) &&
|
|
152
|
+
!m.measurementId &&
|
|
153
|
+
!m.measurementPath &&
|
|
154
|
+
!m.columnId;
|
|
139
155
|
// Screen-space edge length for a placed stamp: the base size scaled by the
|
|
140
156
|
// per-stamp `scale`, the document-wide `tileScaleFactor`, and the
|
|
141
157
|
// zoom-relative `viewportScale`. Blank and populated stamps share the
|
|
@@ -149,9 +165,14 @@ export const isUnassociatedStamp = (m) => !m.isGroupHeader && !m.measurementId &
|
|
|
149
165
|
// with the drawing as the user zooms.
|
|
150
166
|
export const stampTileSize = (m, tileScaleFactor = DEFAULT_TILE_SCALE, viewportScale = 1) => STAMP_TILE_SIZE * (m.scale ?? 1) * tileScaleFactor * viewportScale;
|
|
151
167
|
// `headerTileScaleFactor` is the header chips' own document-wide knob
|
|
152
|
-
// (resolveHeaderTileScaleFactor)
|
|
153
|
-
//
|
|
154
|
-
|
|
168
|
+
// (resolveHeaderTileScaleFactor) and `fileTileScaleFactor` the object tiles'
|
|
169
|
+
// (resolveFileTileScaleFactor); both default to `tileScaleFactor` so callers
|
|
170
|
+
// that predate the per-kind split keep their one-knob behavior.
|
|
171
|
+
export const stampTileDims = (m, tileScaleFactor = DEFAULT_TILE_SCALE, viewportScale = 1, headerTileScaleFactor = tileScaleFactor, fileTileScaleFactor = tileScaleFactor) => {
|
|
172
|
+
if (isFileStamp(m)) {
|
|
173
|
+
const k = (m.scale ?? 1) * fileTileScaleFactor * viewportScale;
|
|
174
|
+
return { width: STAMP_TILE_SIZE * k, height: STAMP_TILE_SIZE * k };
|
|
175
|
+
}
|
|
155
176
|
if (!m.isGroupHeader) {
|
|
156
177
|
const k = (m.scale ?? 1) * tileScaleFactor * viewportScale;
|
|
157
178
|
return { width: STAMP_TILE_SIZE * k, height: STAMP_TILE_SIZE * k };
|
|
@@ -51,7 +51,7 @@ export const createPanTool = (options = {}) => ({
|
|
|
51
51
|
const measurements = ctx.document.placedMeasurements;
|
|
52
52
|
for (let i = measurements.length - 1; i >= 0; i--) {
|
|
53
53
|
const m = measurements[i];
|
|
54
|
-
if (hitPlacedMeasurement(m, event.world, zoom, ctx.tileScaleFactor, ctx.tileViewportScale, ctx.headerTileScaleFactor)) {
|
|
54
|
+
if (hitPlacedMeasurement(m, event.world, zoom, ctx.tileScaleFactor, ctx.tileViewportScale, ctx.headerTileScaleFactor, ctx.fileTileScaleFactor)) {
|
|
55
55
|
ctx.setSelection({ ids: [m.id] });
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
@@ -70,11 +70,11 @@ tileScaleFactor = doc.tileScaleFactor,
|
|
|
70
70
|
// Platform-resolved header-scale knob (ctx.headerTileScaleFactor). Defaults
|
|
71
71
|
// to the doc's web header field, falling back to the measurement knob the
|
|
72
72
|
// way resolveHeaderTileScaleFactor does.
|
|
73
|
-
headerTileScaleFactor = doc.headerTileScaleFactor ?? tileScaleFactor) => {
|
|
73
|
+
headerTileScaleFactor = doc.headerTileScaleFactor ?? tileScaleFactor, fileTileScaleFactor = doc.fileTileScaleFactor ?? tileScaleFactor) => {
|
|
74
74
|
// Hit-test in z-order (top first): measurements > shapes > strokes.
|
|
75
75
|
for (let i = doc.placedMeasurements.length - 1; i >= 0; i--) {
|
|
76
76
|
const m = doc.placedMeasurements[i];
|
|
77
|
-
if (hitPlacedMeasurement(m, world, zoom, tileScaleFactor, viewportTileScale, headerTileScaleFactor)) {
|
|
77
|
+
if (hitPlacedMeasurement(m, world, zoom, tileScaleFactor, viewportTileScale, headerTileScaleFactor, fileTileScaleFactor)) {
|
|
78
78
|
return { id: m.id, kind: 'measurement' };
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -183,11 +183,11 @@ const translatePatch = (elementKind, id, doc, delta) => {
|
|
|
183
183
|
// is selected; without this, a handle sitting under the tile (a rectangle
|
|
184
184
|
// tile at the rect center, a line tile slid onto an endpoint) hijacks the grab
|
|
185
185
|
// and the tile becomes unmovable until deselected.
|
|
186
|
-
const isOnMeasurementTile = (doc, id, world, zoom, viewportTileScale = 1, tileScaleFactor = doc.tileScaleFactor, headerTileScaleFactor = doc.headerTileScaleFactor ?? tileScaleFactor) => {
|
|
186
|
+
const isOnMeasurementTile = (doc, id, world, zoom, viewportTileScale = 1, tileScaleFactor = doc.tileScaleFactor, headerTileScaleFactor = doc.headerTileScaleFactor ?? tileScaleFactor, fileTileScaleFactor = doc.fileTileScaleFactor ?? tileScaleFactor) => {
|
|
187
187
|
const m = doc.placedMeasurements.find((x) => x.id === id);
|
|
188
188
|
if (!m)
|
|
189
189
|
return false;
|
|
190
|
-
const dims = stampTileDims(m, tileScaleFactor, viewportTileScale, headerTileScaleFactor);
|
|
190
|
+
const dims = stampTileDims(m, tileScaleFactor, viewportTileScale, headerTileScaleFactor, fileTileScaleFactor);
|
|
191
191
|
const halfW = (dims.width / 2 + HIT_PADDING) / zoom;
|
|
192
192
|
const halfH = (dims.height / 2 + HIT_PADDING) / zoom;
|
|
193
193
|
return (Math.abs(world.x - m.anchor.x) <= halfW &&
|
|
@@ -197,14 +197,14 @@ const isOnMeasurementTile = (doc, id, world, zoom, viewportTileScale = 1, tileSc
|
|
|
197
197
|
// via DragSelectionConfig AND the web pointer handlers — one source of truth) ---
|
|
198
198
|
// Grabbing the tile of a line annotation slides it along the line; everything
|
|
199
199
|
// else (bare stamps, grabs on the line body) is a group move.
|
|
200
|
-
const classifyGrab = (doc, id, world, zoom, viewportTileScale = 1, tileScaleFactor = doc.tileScaleFactor, headerTileScaleFactor = doc.headerTileScaleFactor ?? tileScaleFactor) => {
|
|
200
|
+
const classifyGrab = (doc, id, world, zoom, viewportTileScale = 1, tileScaleFactor = doc.tileScaleFactor, headerTileScaleFactor = doc.headerTileScaleFactor ?? tileScaleFactor, fileTileScaleFactor = doc.fileTileScaleFactor ?? tileScaleFactor) => {
|
|
201
201
|
const m = doc.placedMeasurements.find((x) => x.id === id);
|
|
202
202
|
if (!m)
|
|
203
203
|
return 'move';
|
|
204
204
|
// Same footprint the tile is drawn at (wide pill for group headers; folds in
|
|
205
205
|
// the document-wide tile scale + viewport scale so slide-grab matches the
|
|
206
206
|
// draw).
|
|
207
|
-
const dims = stampTileDims(m, tileScaleFactor, viewportTileScale, headerTileScaleFactor);
|
|
207
|
+
const dims = stampTileDims(m, tileScaleFactor, viewportTileScale, headerTileScaleFactor, fileTileScaleFactor);
|
|
208
208
|
const halfW = (dims.width / 2 + HIT_PADDING) / zoom;
|
|
209
209
|
const halfH = (dims.height / 2 + HIT_PADDING) / zoom;
|
|
210
210
|
const onTile = Math.abs(world.x - m.anchor.x) <= halfW &&
|
|
@@ -604,7 +604,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
604
604
|
// reusing the same hit-test and translate logic the pointer handlers below
|
|
605
605
|
// use for web — one source of truth.
|
|
606
606
|
dragSelection: {
|
|
607
|
-
hitTest: (doc, world, zoom, viewportTileScale, tileScaleFactor, headerTileScaleFactor) => findHit(doc, world, zoom, viewportTileScale, tileScaleFactor, headerTileScaleFactor),
|
|
607
|
+
hitTest: (doc, world, zoom, viewportTileScale, tileScaleFactor, headerTileScaleFactor, fileTileScaleFactor) => findHit(doc, world, zoom, viewportTileScale, tileScaleFactor, headerTileScaleFactor, fileTileScaleFactor),
|
|
608
608
|
buildTranslatePatch: (doc, id, kind, delta) => {
|
|
609
609
|
const op = translatePatch(kind, id, doc, delta);
|
|
610
610
|
return op ? { ops: [op] } : null;
|
|
@@ -643,7 +643,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
643
643
|
// tile stays draggable (otherwise it can only be moved after deselecting).
|
|
644
644
|
const selId = ctx.selection?.ids[0];
|
|
645
645
|
if (selId &&
|
|
646
|
-
!isOnMeasurementTile(ctx.document, selId, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor, ctx.headerTileScaleFactor)) {
|
|
646
|
+
!isOnMeasurementTile(ctx.document, selId, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor, ctx.headerTileScaleFactor, ctx.fileTileScaleFactor)) {
|
|
647
647
|
const handle = findHandleHit(ctx.document, selId, world, zoom);
|
|
648
648
|
if (handle) {
|
|
649
649
|
ctx.setSelection({ ids: [selId] });
|
|
@@ -712,7 +712,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
712
712
|
};
|
|
713
713
|
}
|
|
714
714
|
}
|
|
715
|
-
const hit = findHit(ctx.document, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor, ctx.headerTileScaleFactor);
|
|
715
|
+
const hit = findHit(ctx.document, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor, ctx.headerTileScaleFactor, ctx.fileTileScaleFactor);
|
|
716
716
|
if (!hit) {
|
|
717
717
|
// Background layers only get a look-in when every annotation element
|
|
718
718
|
// misses — they can cover the whole document, so elements above always
|
|
@@ -757,7 +757,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
757
757
|
: null;
|
|
758
758
|
ctx.setSelection({ ids: [hit.id] });
|
|
759
759
|
const mode = hit.kind === 'measurement' &&
|
|
760
|
-
classifyGrab(ctx.document, hit.id, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor, ctx.headerTileScaleFactor) === 'slide'
|
|
760
|
+
classifyGrab(ctx.document, hit.id, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor, ctx.headerTileScaleFactor, ctx.fileTileScaleFactor) === 'slide'
|
|
761
761
|
? 'slide'
|
|
762
762
|
: 'move';
|
|
763
763
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Measurement } from '../../types/firestore.js';
|
|
2
2
|
import { type AnnotationCanvasState, type AnnotationDocumentPatch, type AnnotationElementId, type AnnotationStroke, type MeasurementPlacement, type Selection, type TileScalePlatform, type Vec2 } from '../../types/annotation.js';
|
|
3
|
-
import type { MeasurementRef } from './measurementPicker.js';
|
|
3
|
+
import type { FileRef, MeasurementRef } from './measurementPicker.js';
|
|
4
4
|
import type { CanvasPointerEvent, RequestTextInput, Tool, ToolContext, ToolState } from './Tool.js';
|
|
5
5
|
import { type ViewportState } from './viewport.js';
|
|
6
6
|
export interface CanvasScreenRect {
|
|
@@ -25,6 +25,7 @@ export interface AnnotationCanvasHandle {
|
|
|
25
25
|
}): void;
|
|
26
26
|
getTileScaleFactor(): number;
|
|
27
27
|
getHeaderTileScaleFactor(): number;
|
|
28
|
+
getFileTileScaleFactor(): number;
|
|
28
29
|
getTileDocSize(factor: number): number;
|
|
29
30
|
ensureMeasurementVisible(id: AnnotationElementId): void;
|
|
30
31
|
placeMeasurementAtCenter(ref: MeasurementRef): void;
|
|
@@ -47,6 +48,7 @@ export interface AnnotationCanvasHandle {
|
|
|
47
48
|
placeGroupHeaderAtCenter(ref: {
|
|
48
49
|
groupId: string;
|
|
49
50
|
}): AnnotationElementId;
|
|
51
|
+
placeFileAtCenter(ref: FileRef): AnnotationElementId;
|
|
50
52
|
setGroupHeaderCollapsed(id: AnnotationElementId, collapsed: boolean): void;
|
|
51
53
|
deleteSelected(): void;
|
|
52
54
|
}
|
|
@@ -86,6 +88,7 @@ export interface AnnotationCanvasStateApi {
|
|
|
86
88
|
tileViewportScale: number;
|
|
87
89
|
tileScaleFactor: number;
|
|
88
90
|
headerTileScaleFactor: number;
|
|
91
|
+
fileTileScaleFactor: number;
|
|
89
92
|
penDrawingStroke: AnnotationStroke | null;
|
|
90
93
|
customPreviewState: ToolState;
|
|
91
94
|
dispatchPointerDown(event: CanvasPointerEvent): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { applyPatch, invertPatch, DEFAULT_LAYER_ID, } from '../../types/annotation.js';
|
|
3
|
-
import { resolveHeaderTileScaleFactor, resolveTileScaleFactor, stampTileDims, STAMP_TILE_SIZE, tileDocSizeForFactor, tileScaleFromDocSize, } from './stampLayout.js';
|
|
3
|
+
import { resolveHeaderTileScaleFactor, resolveFileTileScaleFactor, resolveTileScaleFactor, stampTileDims, STAMP_TILE_SIZE, tileDocSizeForFactor, tileScaleFromDocSize, } from './stampLayout.js';
|
|
4
4
|
import { createViewportApi, fitRectToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, } from './viewport.js';
|
|
5
5
|
import { backgroundLayerDocRect, backgroundLayersOf, backgroundLayersUnionRect, } from './backgroundLayers.js';
|
|
6
6
|
import { buildRemoveMeasurementOps, recomputeAnchor, rectCenter, DEFAULT_LINE_POS, } from './measurementGeometry.js';
|
|
@@ -162,6 +162,8 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
162
162
|
// equals the measurement factor (resolveHeaderTileScaleFactor's fallback) —
|
|
163
163
|
// the pre-split one-knob behavior.
|
|
164
164
|
let headerTileScaleFactor = resolveHeaderTileScaleFactor(effectiveCanvas, tileScalePlatformResolved);
|
|
165
|
+
// Object tiles: the third knob, same fallback (Asana 1217570500895545).
|
|
166
|
+
let fileTileScaleFactor = resolveFileTileScaleFactor(effectiveCanvas, tileScalePlatformResolved);
|
|
165
167
|
if (tileScalePlatformResolved === 'mobile' &&
|
|
166
168
|
effectiveCanvas.tileScaleFactorMobile == null &&
|
|
167
169
|
effectiveCanvas.tileDocSize != null &&
|
|
@@ -178,6 +180,10 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
178
180
|
tileScaleFactor > 0
|
|
179
181
|
? headerTileScaleFactor * (calibrated / tileScaleFactor)
|
|
180
182
|
: calibrated;
|
|
183
|
+
fileTileScaleFactor =
|
|
184
|
+
tileScaleFactor > 0
|
|
185
|
+
? fileTileScaleFactor * (calibrated / tileScaleFactor)
|
|
186
|
+
: calibrated;
|
|
181
187
|
tileScaleFactor = calibrated;
|
|
182
188
|
}
|
|
183
189
|
const ctx = useMemo(() => ({
|
|
@@ -187,6 +193,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
187
193
|
tileViewportScale,
|
|
188
194
|
tileScaleFactor,
|
|
189
195
|
headerTileScaleFactor,
|
|
196
|
+
fileTileScaleFactor,
|
|
190
197
|
preview(patch) {
|
|
191
198
|
setPreviewPatch(patch);
|
|
192
199
|
},
|
|
@@ -209,6 +216,47 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
209
216
|
};
|
|
210
217
|
}
|
|
211
218
|
}
|
|
219
|
+
// The three tile-kind knobs are independent (Asana 1217570500895545):
|
|
220
|
+
// moving the MEASUREMENT slider must not move header chips or object
|
|
221
|
+
// tiles. Their knobs fall back to the measurement factor while unset
|
|
222
|
+
// (back-compat for documents from the one-knob era), so the first
|
|
223
|
+
// measurement-slider commit on this platform latches each unset knob
|
|
224
|
+
// at the value it is drawn at RIGHT NOW — the resolved (and, on
|
|
225
|
+
// mobile, calibration-corrected) factor — and it stops following. Same
|
|
226
|
+
// undo step as the slider commit.
|
|
227
|
+
const measurementKnobOp = effective.ops.find((o) => o.op === 'setTileScaleFactor' &&
|
|
228
|
+
(o.target ?? 'measurement') === 'measurement' &&
|
|
229
|
+
(o.platform ?? 'web') === tileScalePlatformResolved);
|
|
230
|
+
if (measurementKnobOp) {
|
|
231
|
+
const isMobile = tileScalePlatformResolved === 'mobile';
|
|
232
|
+
const headerUnset = isMobile
|
|
233
|
+
? canvas.headerTileScaleFactorMobile == null
|
|
234
|
+
: canvas.headerTileScaleFactor == null;
|
|
235
|
+
const fileUnset = isMobile
|
|
236
|
+
? canvas.fileTileScaleFactorMobile == null
|
|
237
|
+
: canvas.fileTileScaleFactor == null;
|
|
238
|
+
const latch = [];
|
|
239
|
+
if (headerUnset) {
|
|
240
|
+
latch.push({
|
|
241
|
+
op: 'setTileScaleFactor',
|
|
242
|
+
value: headerTileScaleFactor,
|
|
243
|
+
platform: tileScalePlatformResolved,
|
|
244
|
+
target: 'header',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (fileUnset) {
|
|
248
|
+
latch.push({
|
|
249
|
+
op: 'setTileScaleFactor',
|
|
250
|
+
value: fileTileScaleFactor,
|
|
251
|
+
platform: tileScalePlatformResolved,
|
|
252
|
+
target: 'file',
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
// Latches go FIRST so they read the pre-move state; the slider op
|
|
256
|
+
// then only moves the measurement knob.
|
|
257
|
+
if (latch.length)
|
|
258
|
+
effective = { ops: [...latch, ...effective.ops] };
|
|
259
|
+
}
|
|
212
260
|
const inverse = invertPatch(canvas, effective);
|
|
213
261
|
undoStackRef.current.push({ forward: effective, inverse });
|
|
214
262
|
redoStackRef.current = [];
|
|
@@ -239,6 +287,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
239
287
|
tileViewportScale,
|
|
240
288
|
tileScaleFactor,
|
|
241
289
|
headerTileScaleFactor,
|
|
290
|
+
fileTileScaleFactor,
|
|
242
291
|
tileScalePlatformResolved,
|
|
243
292
|
baseFitZoom,
|
|
244
293
|
onCommit,
|
|
@@ -445,6 +494,9 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
445
494
|
getHeaderTileScaleFactor() {
|
|
446
495
|
return ctxRef.current.headerTileScaleFactor;
|
|
447
496
|
},
|
|
497
|
+
getFileTileScaleFactor() {
|
|
498
|
+
return ctxRef.current.fileTileScaleFactor;
|
|
499
|
+
},
|
|
448
500
|
getTileDocSize(factor) {
|
|
449
501
|
// Tiles are doc-constant, so the doc size a factor yields derives
|
|
450
502
|
// from the content-fit reference, not the current zoom.
|
|
@@ -457,7 +509,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
457
509
|
return;
|
|
458
510
|
// The tile's CURRENT on-screen half-extents: stampTileDims with the
|
|
459
511
|
// zoom-relative tileViewportScale folded in.
|
|
460
|
-
const dims = stampTileDims(m, c.tileScaleFactor, c.tileViewportScale, c.headerTileScaleFactor);
|
|
512
|
+
const dims = stampTileDims(m, c.tileScaleFactor, c.tileViewportScale, c.headerTileScaleFactor, c.fileTileScaleFactor);
|
|
461
513
|
const halfW = dims.width / 2;
|
|
462
514
|
const halfH = dims.height / 2;
|
|
463
515
|
const s = c.viewport.worldToScreen(m.anchor);
|
|
@@ -681,6 +733,24 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
681
733
|
c.setSelection({ ids: [placed.id] });
|
|
682
734
|
return placed.id;
|
|
683
735
|
},
|
|
736
|
+
placeFileAtCenter(ref) {
|
|
737
|
+
const c = ctxRef.current;
|
|
738
|
+
const anchor = cascadeCenterWorld();
|
|
739
|
+
// No showLabel/showValue/placement — an object tile is not an input;
|
|
740
|
+
// it renders the file's preview, resolved live by the consumer.
|
|
741
|
+
const placed = {
|
|
742
|
+
id: `file-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`,
|
|
743
|
+
layerId: c.document.layers[0]?.id ?? DEFAULT_LAYER_ID,
|
|
744
|
+
fileId: ref.fileId,
|
|
745
|
+
filePath: ref.filePath,
|
|
746
|
+
groupId: ref.groupId,
|
|
747
|
+
anchor,
|
|
748
|
+
createdAt: Date.now(),
|
|
749
|
+
};
|
|
750
|
+
c.commit({ ops: [{ op: 'addMeasurement', measurement: placed }] });
|
|
751
|
+
c.setSelection({ ids: [placed.id] });
|
|
752
|
+
return placed.id;
|
|
753
|
+
},
|
|
684
754
|
setGroupHeaderCollapsed(id, collapsed) {
|
|
685
755
|
const c = ctxRef.current;
|
|
686
756
|
const target = c.document.placedMeasurements.find((m) => m.id === id);
|
|
@@ -755,6 +825,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
755
825
|
tileViewportScale,
|
|
756
826
|
tileScaleFactor,
|
|
757
827
|
headerTileScaleFactor,
|
|
828
|
+
fileTileScaleFactor,
|
|
758
829
|
penDrawingStroke,
|
|
759
830
|
customPreviewState: toolState,
|
|
760
831
|
dispatchPointerDown,
|
package/dist/exports.d.ts
CHANGED
|
@@ -27,9 +27,9 @@ export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotation
|
|
|
27
27
|
export type { AnnotationCanvasHandle } from './annotation/canvas/useAnnotationCanvasState.js';
|
|
28
28
|
export type { GestureConfig, PanTrigger, AnnotationCanvasInnerProps, } from './annotation/canvas/AnnotationCanvasInner.js';
|
|
29
29
|
export type { CanvasPointerEvent, RequestTextInput, ShapeDrawConfig, Tool, ToolContext, ToolState, } from './annotation/canvas/Tool.js';
|
|
30
|
-
export type { MeasurementRef, PickMeasurement, } from './annotation/canvas/measurementPicker.js';
|
|
30
|
+
export type { FileRef, MeasurementRef, PickMeasurement, } from './annotation/canvas/measurementPicker.js';
|
|
31
31
|
export type { MeasurementStampRenderArgs, RenderMeasurementStamp, } from './annotation/canvas/measurementStampOverlay.js';
|
|
32
|
-
export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GROUP_HEADER_COLLAPSED_WIDTH, GROUP_HEADER_COLLAPSED_HEIGHT, GROUP_HEADER_DESIGN, groupHeaderBaseDims, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, resolveTileScaleFactor, resolveHeaderTileScaleFactor, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, type StampTileDims, } from './annotation/canvas/stampLayout.js';
|
|
32
|
+
export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GROUP_HEADER_COLLAPSED_WIDTH, GROUP_HEADER_COLLAPSED_HEIGHT, GROUP_HEADER_DESIGN, groupHeaderBaseDims, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, resolveTileScaleFactor, resolveHeaderTileScaleFactor, resolveFileTileScaleFactor, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, isFileStamp, type StampTileDims, } from './annotation/canvas/stampLayout.js';
|
|
33
33
|
export { GROUP_HEADER_STYLE, describeGroupHeader, groupHeaderSignature, type GroupHeaderBadge, type GroupHeaderDescriptor, } from './annotation/canvas/groupHeaderLayout.js';
|
|
34
34
|
export { createViewportApi, fitToScreen, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, type ViewportApi, type ViewportState, } from './annotation/canvas/viewport.js';
|
|
35
35
|
export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
|
package/dist/exports.js
CHANGED
|
@@ -34,7 +34,7 @@ export { useAnnotationMutations, } from './annotation/data/hooks/useAnnotationMu
|
|
|
34
34
|
export { useAnnotationCanvasDoc, } from './annotation/data/hooks/useAnnotationCanvasDoc.js';
|
|
35
35
|
export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
|
|
36
36
|
export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
|
|
37
|
-
export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GROUP_HEADER_COLLAPSED_WIDTH, GROUP_HEADER_COLLAPSED_HEIGHT, GROUP_HEADER_DESIGN, groupHeaderBaseDims, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, resolveTileScaleFactor, resolveHeaderTileScaleFactor, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, } from './annotation/canvas/stampLayout.js';
|
|
37
|
+
export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GROUP_HEADER_COLLAPSED_WIDTH, GROUP_HEADER_COLLAPSED_HEIGHT, GROUP_HEADER_DESIGN, groupHeaderBaseDims, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, resolveTileScaleFactor, resolveHeaderTileScaleFactor, resolveFileTileScaleFactor, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, isFileStamp, } from './annotation/canvas/stampLayout.js';
|
|
38
38
|
export { GROUP_HEADER_STYLE, describeGroupHeader, groupHeaderSignature, } from './annotation/canvas/groupHeaderLayout.js';
|
|
39
39
|
export { createViewportApi, fitToScreen, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, } from './annotation/canvas/viewport.js';
|
|
40
40
|
export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
|