@reekon-tools/boldr-utils 1.13.1 → 1.14.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 +100 -34
- package/dist/annotation/canvas/Tool.d.ts +4 -3
- package/dist/annotation/canvas/measurementGeometry.d.ts +1 -1
- package/dist/annotation/canvas/measurementGeometry.js +5 -4
- package/dist/annotation/canvas/stampLayout.d.ts +2 -1
- package/dist/annotation/canvas/stampLayout.js +18 -2
- package/dist/annotation/canvas/tools/panTool.js +1 -1
- package/dist/annotation/canvas/tools/selectTool.js +14 -10
- package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +2 -0
- package/dist/annotation/canvas/useAnnotationCanvasState.js +24 -3
- package/dist/calculator/schema.d.ts +7 -0
- package/dist/exports.d.ts +1 -1
- 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 +4 -0
- package/dist/types/annotation.js +25 -8
- 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));
|
|
287
|
+
.find((m) => hitPlacedMeasurement(m, world, state.viewport.zoom, state.tileScaleFactor, state.tileViewportScale, state.headerTileScaleFactor));
|
|
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);
|
|
323
|
+
const { width, height } = stampTileDims(placed, state.tileScaleFactor, state.tileViewportScale, state.headerTileScaleFactor);
|
|
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;
|
|
@@ -121,7 +121,11 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
121
121
|
const zoom = useSharedValue(state.viewport.zoom);
|
|
122
122
|
const panX = useSharedValue(state.viewport.pan.x);
|
|
123
123
|
const panY = useSharedValue(state.viewport.pan.y);
|
|
124
|
-
|
|
124
|
+
// Set by the viewport gestures' onChange, consumed by their onFinalize: the
|
|
125
|
+
// post-gesture sync to React state happens iff a gesture actually moved the
|
|
126
|
+
// viewport, and in onFinalize rather than onEnd because on Android the
|
|
127
|
+
// viewport gestures routinely exit through fail()/cancel() (see the pan).
|
|
128
|
+
const viewportDirty = useSharedValue(false);
|
|
125
129
|
// Skia consumes this directly and re-renders the transform on the UI thread.
|
|
126
130
|
const worldTransform = useDerivedValue(() => {
|
|
127
131
|
'worklet';
|
|
@@ -748,8 +752,17 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
748
752
|
world: stateRef.current.ctx.viewport.screenToWorld(screen),
|
|
749
753
|
});
|
|
750
754
|
// Commit the final viewport to the JS snapshot once a gesture ends, so
|
|
751
|
-
// tools' screenToWorld/worldToScreen reflect the new pan/zoom.
|
|
755
|
+
// tools' screenToWorld/worldToScreen reflect the new pan/zoom. Skips the
|
|
756
|
+
// no-op commit: the pan and the pinch can finalize on the same lift, and
|
|
757
|
+
// the second sync would re-render (and re-settle any background layer)
|
|
758
|
+
// for values React already has.
|
|
752
759
|
const syncViewport = (next) => {
|
|
760
|
+
const current = stateRef.current.ctx.viewport.state;
|
|
761
|
+
if (current.zoom === next.zoom &&
|
|
762
|
+
current.pan.x === next.pan.x &&
|
|
763
|
+
current.pan.y === next.pan.y) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
753
766
|
stateRef.current.setViewport(next);
|
|
754
767
|
};
|
|
755
768
|
const beginViewportGesture = () => {
|
|
@@ -862,7 +875,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
862
875
|
return;
|
|
863
876
|
const world = st.ctx.viewport.screenToWorld(screen);
|
|
864
877
|
const zoomNow = st.ctx.viewport.state.zoom;
|
|
865
|
-
const hit = dragSelection.hitTest(st.ctx.document, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor);
|
|
878
|
+
const hit = dragSelection.hitTest(st.ctx.document, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor);
|
|
866
879
|
if (!hit || hit.kind !== 'measurement' || hit.id !== prevSelectedId) {
|
|
867
880
|
return;
|
|
868
881
|
}
|
|
@@ -873,9 +886,17 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
873
886
|
// Viewport pan — runs on the UI thread (no runOnJS), mutating the shared
|
|
874
887
|
// viewport directly. Mirrors viewport.ts `panBy`. Used for both the
|
|
875
888
|
// two-finger pan (always available) and the one-finger Hand tool.
|
|
889
|
+
//
|
|
890
|
+
// averageTouches(true) is load-bearing on Android: PanGestureHandler
|
|
891
|
+
// defaults to tracking the LAST-LANDED pointer, not the centroid (iOS
|
|
892
|
+
// always averages). Running simultaneously with the pinch, a non-averaged
|
|
893
|
+
// two-finger pan translates the canvas by finger 2's whole travel — during
|
|
894
|
+
// a spread that is half the spread distance of sideways drift, which reads
|
|
895
|
+
// as "the zoom runs away from where I aimed".
|
|
876
896
|
const buildViewportPan = (minP, maxP) => Gesture.Pan()
|
|
877
897
|
.minPointers(minP)
|
|
878
898
|
.maxPointers(maxP)
|
|
899
|
+
.averageTouches(true)
|
|
879
900
|
.onBegin(() => {
|
|
880
901
|
'worklet';
|
|
881
902
|
runOnJS(beginViewportGesture)();
|
|
@@ -884,47 +905,91 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
884
905
|
'worklet';
|
|
885
906
|
panX.value -= e.changeX / zoom.value;
|
|
886
907
|
panY.value -= e.changeY / zoom.value;
|
|
908
|
+
viewportDirty.value = true;
|
|
887
909
|
})
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
910
|
+
// onFinalize, not onEnd: on Android this pan normally exits through
|
|
911
|
+
// fail() — a (2,2) pan the moment the first finger lifts (pointer
|
|
912
|
+
// count below minPointers), and a pure two-finger translate whose
|
|
913
|
+
// pinch never activated at the final lift. onEnd never runs on those
|
|
914
|
+
// paths, and the movement would never reach React state: hit-tests go
|
|
915
|
+
// stale, a background layer never re-rasterizes for the new view, and
|
|
916
|
+
// the next JS-side viewport write snaps the canvas back.
|
|
895
917
|
.onFinalize(() => {
|
|
896
918
|
'worklet';
|
|
919
|
+
if (viewportDirty.value) {
|
|
920
|
+
viewportDirty.value = false;
|
|
921
|
+
runOnJS(syncViewport)({
|
|
922
|
+
zoom: zoom.value,
|
|
923
|
+
pan: { x: panX.value, y: panY.value },
|
|
924
|
+
});
|
|
925
|
+
}
|
|
897
926
|
runOnJS(endViewportGesture)();
|
|
898
927
|
});
|
|
899
|
-
|
|
928
|
+
// With the Hand tool the viewport pan spans one AND two fingers as a
|
|
929
|
+
// single gesture. Two separate gestures (a one-finger pan racing the
|
|
930
|
+
// two-finger pair) cannot express a staggered pinch: finger 1 panning
|
|
931
|
+
// past the slop resolves the Race before finger 2 lands, the pinch is
|
|
932
|
+
// cancelled with it, and finger 2's touch then kills the one-finger pan
|
|
933
|
+
// too (pointer count over maxPointers cancels an active Android pan) —
|
|
934
|
+
// a dead canvas until every finger lifts. One pan spanning both counts
|
|
935
|
+
// stays active across 1→2→1, and its translation is continuous across
|
|
936
|
+
// pointer transitions (the handler rebases its origin on every
|
|
937
|
+
// POINTER_DOWN/UP).
|
|
938
|
+
const viewportPan = buildViewportPan(panViewport ? 1 : 2, 2);
|
|
900
939
|
// Pinch-to-zoom about the focal point — UI thread. Mirrors viewport.ts
|
|
901
940
|
// `zoomAt`: keep the world point under the focal screen point fixed.
|
|
941
|
+
//
|
|
942
|
+
// Driven by the per-event ratio (`scaleChange`), not by cumulative
|
|
943
|
+
// `e.scale` against a zoom snapshotted in onBegin. On Android onBegin
|
|
944
|
+
// fires at the FIRST finger down — before a pinch provably exists — so a
|
|
945
|
+
// snapshot there can go stale against anything else driving the shared
|
|
946
|
+
// values (the glide animation, the simultaneous pan). The native
|
|
947
|
+
// cumulative scale also freezes rather than rebases across pointer-set
|
|
948
|
+
// changes (2→1→2, a third finger) and sticks at 0 for the rest of the
|
|
949
|
+
// gesture if the fingers ever collapse to a single point, so only ratios
|
|
950
|
+
// between consecutive events are trustworthy. The live focal needs no
|
|
951
|
+
// snapshot either: a focal that merely moves leaves this update a no-op
|
|
952
|
+
// (translation is the pan's job), which is also what makes Android's
|
|
953
|
+
// wrong-focal BEGAN event (it reports the first finger's touch-down
|
|
954
|
+
// position, not a centroid) harmless here.
|
|
902
955
|
const pinch = Gesture.Pinch()
|
|
903
956
|
.onBegin(() => {
|
|
904
957
|
'worklet';
|
|
905
|
-
pinchStartZoom.value = zoom.value;
|
|
906
958
|
runOnJS(beginViewportGesture)();
|
|
907
959
|
})
|
|
908
|
-
.
|
|
960
|
+
.onChange((e) => {
|
|
909
961
|
'worklet';
|
|
910
|
-
|
|
962
|
+
// Drop corrupt ratios instead of applying them: a native span
|
|
963
|
+
// collapse yields scale 0, and the ratio of the frame after that is
|
|
964
|
+
// 0/0. Applying either would slam the zoom to a bound (or NaN it).
|
|
965
|
+
const ratio = e.scaleChange;
|
|
966
|
+
if (!Number.isFinite(ratio) || ratio <= 0)
|
|
967
|
+
return;
|
|
968
|
+
const next = Math.min(50, Math.max(0.05, zoom.value * ratio));
|
|
969
|
+
if (next === zoom.value)
|
|
970
|
+
return; // clamped at a bound, or ratio 1
|
|
911
971
|
// World point currently under the focal screen point (using the live
|
|
912
972
|
// zoom/pan), then re-anchor pan so it stays put at the new zoom.
|
|
913
973
|
const focalWorldX = e.focalX / zoom.value + panX.value;
|
|
914
974
|
const focalWorldY = e.focalY / zoom.value + panY.value;
|
|
915
|
-
panX.value = focalWorldX - e.focalX /
|
|
916
|
-
panY.value = focalWorldY - e.focalY /
|
|
917
|
-
zoom.value =
|
|
918
|
-
|
|
919
|
-
.onEnd(() => {
|
|
920
|
-
'worklet';
|
|
921
|
-
runOnJS(syncViewport)({
|
|
922
|
-
zoom: zoom.value,
|
|
923
|
-
pan: { x: panX.value, y: panY.value },
|
|
924
|
-
});
|
|
975
|
+
panX.value = focalWorldX - e.focalX / next;
|
|
976
|
+
panY.value = focalWorldY - e.focalY / next;
|
|
977
|
+
zoom.value = next;
|
|
978
|
+
viewportDirty.value = true;
|
|
925
979
|
})
|
|
980
|
+
// onFinalize, not onEnd, for the same reason as the pan: an Android
|
|
981
|
+
// pinch that never activated fails at the final lift, and a cancelled
|
|
982
|
+
// gesture never reaches onEnd — the dirty check keeps the no-gesture
|
|
983
|
+
// paths from committing anything.
|
|
926
984
|
.onFinalize(() => {
|
|
927
985
|
'worklet';
|
|
986
|
+
if (viewportDirty.value) {
|
|
987
|
+
viewportDirty.value = false;
|
|
988
|
+
runOnJS(syncViewport)({
|
|
989
|
+
zoom: zoom.value,
|
|
990
|
+
pan: { x: panX.value, y: panY.value },
|
|
991
|
+
});
|
|
992
|
+
}
|
|
928
993
|
runOnJS(endViewportGesture)();
|
|
929
994
|
});
|
|
930
995
|
// Freehand drawing (pen/marker/highlighter) — one finger, UI thread.
|
|
@@ -1128,7 +1193,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1128
1193
|
// so a selected tile isn't stuck until you deselect it).
|
|
1129
1194
|
const selId = st.ctx.selection?.ids[0];
|
|
1130
1195
|
if (selId &&
|
|
1131
|
-
!cfg.isSelectedTileGrab?.(st.ctx.document, selId, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor)) {
|
|
1196
|
+
!cfg.isSelectedTileGrab?.(st.ctx.document, selId, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor)) {
|
|
1132
1197
|
const handle = cfg.hitTestHandle?.(st.ctx.document, selId, world, zoomNow);
|
|
1133
1198
|
if (handle) {
|
|
1134
1199
|
const m = st.ctx.document.placedMeasurements.find((x) => x.id === selId);
|
|
@@ -1228,7 +1293,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1228
1293
|
return;
|
|
1229
1294
|
}
|
|
1230
1295
|
}
|
|
1231
|
-
const hit = cfg.hitTest(st.ctx.document, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor);
|
|
1296
|
+
const hit = cfg.hitTest(st.ctx.document, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor);
|
|
1232
1297
|
if (!hit) {
|
|
1233
1298
|
// Backgrounds can cover the whole document, so they only get the
|
|
1234
1299
|
// drag once every annotation element misses, and only when ALREADY
|
|
@@ -1248,7 +1313,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1248
1313
|
st.ctx.setSelection({ ids: [hit.id] });
|
|
1249
1314
|
// Grabbing a line annotation's tile slides it; otherwise group-move.
|
|
1250
1315
|
const grab = hit.kind === 'measurement'
|
|
1251
|
-
? cfg.classifyMeasurementGrab?.(st.ctx.document, hit.id, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor)
|
|
1316
|
+
? cfg.classifyMeasurementGrab?.(st.ctx.document, hit.id, world, zoomNow, st.ctx.tileViewportScale, st.ctx.tileScaleFactor, st.ctx.headerTileScaleFactor)
|
|
1252
1317
|
: 'move';
|
|
1253
1318
|
if (grab === 'slide') {
|
|
1254
1319
|
const m = st.ctx.document.placedMeasurements.find((x) => x.id === hit.id);
|
|
@@ -1425,7 +1490,8 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1425
1490
|
// One finger, by active tool:
|
|
1426
1491
|
// - freehand (pen/marker/highlighter) → draw on the UI thread
|
|
1427
1492
|
// - shape tools (line/rect/…) → rubber-band on the UI thread
|
|
1428
|
-
// - Hand →
|
|
1493
|
+
// - Hand → no separate gesture: viewportPan above already spans one
|
|
1494
|
+
// finger (min 1), so panning and pinching stay one uncancelled stream
|
|
1429
1495
|
// - select → drag the hit element on the UI thread
|
|
1430
1496
|
// - everything else → dispatch pointer events on the JS thread
|
|
1431
1497
|
const oneFinger = freehand
|
|
@@ -1433,18 +1499,18 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1433
1499
|
: shapeDraw
|
|
1434
1500
|
? buildShapeDrawPan(shapeDraw)
|
|
1435
1501
|
: panViewport
|
|
1436
|
-
?
|
|
1502
|
+
? null
|
|
1437
1503
|
: dragSelection
|
|
1438
1504
|
? buildSelectDragPan(dragSelection)
|
|
1439
1505
|
: toolPan;
|
|
1440
1506
|
// Long-press only joins the race when the active tool acts on it, so it
|
|
1441
1507
|
// never pre-empts a one-finger drag/draw on tools that ignore holds.
|
|
1442
|
-
return Gesture.Race(tap, ...(longPressEnabled ? [longPress] : []), Gesture.Simultaneous(viewportPan, pinch), oneFinger);
|
|
1508
|
+
return Gesture.Race(tap, ...(longPressEnabled ? [longPress] : []), Gesture.Simultaneous(viewportPan, pinch), ...(oneFinger ? [oneFinger] : []));
|
|
1443
1509
|
}, [
|
|
1444
1510
|
zoom,
|
|
1445
1511
|
panX,
|
|
1446
1512
|
panY,
|
|
1447
|
-
|
|
1513
|
+
viewportDirty,
|
|
1448
1514
|
livePoints,
|
|
1449
1515
|
handoffStrokes,
|
|
1450
1516
|
removeHandoffStrokes,
|
|
@@ -1555,7 +1621,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1555
1621
|
// non-fullscreen canvas (e.g. a diagram strip) escape into surrounding UI.
|
|
1556
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
|
|
1557
1623
|
? (state.measurementsById.get(placed.measurementId) ?? null)
|
|
1558
|
-
: (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, 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, tileViewportScale: state.tileViewportScale, onStampPress: onMeasurementStampPress, onStampLongPress: onMeasurementStampLongPress, onRemove: onMeasurementStampRemove
|
|
1559
1625
|
? () => {
|
|
1560
1626
|
const defaultRemove = () => {
|
|
1561
1627
|
const { ops, keepSelection } = buildRemoveMeasurementOps(placed);
|
|
@@ -1571,10 +1637,10 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1571
1637
|
wrapContent: loupeWrap,
|
|
1572
1638
|
}) }))] }));
|
|
1573
1639
|
};
|
|
1574
|
-
const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging, sliding, endpointDragging, rectResizing, zoomSnapshot, zoom, panX, panY, dragX, dragY, slideCtx, epCtx, rectCtx, renderMeasurementStamp, tileScaleFactor, 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, tileViewportScale, onStampPress, onStampLongPress, onRemove, }) => {
|
|
1575
1641
|
// Square tile or wide group-header pill — one footprint source of truth
|
|
1576
1642
|
// shared with the hit-test (stampTileDims).
|
|
1577
|
-
const { width, height } = stampTileDims(placed, tileScaleFactor, tileViewportScale);
|
|
1643
|
+
const { width, height } = stampTileDims(placed, tileScaleFactor, tileViewportScale, headerTileScaleFactor ?? tileScaleFactor);
|
|
1578
1644
|
const halfW = width / 2;
|
|
1579
1645
|
const halfH = height / 2;
|
|
1580
1646
|
// Remove-"X" touch target, sized as a fraction of the tile and corner-pinned
|
|
@@ -24,6 +24,7 @@ export interface ToolContext {
|
|
|
24
24
|
viewport: ViewportApi;
|
|
25
25
|
tileViewportScale: number;
|
|
26
26
|
tileScaleFactor: number;
|
|
27
|
+
headerTileScaleFactor: number;
|
|
27
28
|
preview(patch: AnnotationDocumentPatch): void;
|
|
28
29
|
commit(patch: AnnotationDocumentPatch): void;
|
|
29
30
|
setSelection(selection: Selection | null): void;
|
|
@@ -54,12 +55,12 @@ export interface ShapeDrawConfig {
|
|
|
54
55
|
}
|
|
55
56
|
export type DragElementKind = 'stroke' | 'shape' | 'measurement' | 'background';
|
|
56
57
|
export interface DragSelectionConfig {
|
|
57
|
-
hitTest(doc: AnnotationCanvasState, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number): {
|
|
58
|
+
hitTest(doc: AnnotationCanvasState, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number, headerTileScaleFactor?: number): {
|
|
58
59
|
id: AnnotationElementId;
|
|
59
60
|
kind: DragElementKind;
|
|
60
61
|
} | null;
|
|
61
62
|
buildTranslatePatch(doc: AnnotationCanvasState, id: AnnotationElementId, kind: DragElementKind, delta: Vec2): AnnotationDocumentPatch | null;
|
|
62
|
-
classifyMeasurementGrab?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number): 'slide' | 'move';
|
|
63
|
+
classifyMeasurementGrab?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number, headerTileScaleFactor?: number): 'slide' | 'move';
|
|
63
64
|
buildSlidePatch?(doc: AnnotationCanvasState, id: AnnotationElementId, delta: Vec2, zoom: number): AnnotationDocumentPatch | null;
|
|
64
65
|
hitTestHandle?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number): 'a' | 'b' | null;
|
|
65
66
|
buildEndpointPatch?(doc: AnnotationCanvasState, id: AnnotationElementId, handle: 'a' | 'b', delta: Vec2): AnnotationDocumentPatch | null;
|
|
@@ -77,7 +78,7 @@ export interface DragSelectionConfig {
|
|
|
77
78
|
fixed: Vec2;
|
|
78
79
|
} | null;
|
|
79
80
|
buildShapeCornerPatch?(doc: AnnotationCanvasState, id: AnnotationElementId, corner: RectCorner, delta: Vec2): AnnotationDocumentPatch | null;
|
|
80
|
-
isSelectedTileGrab?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number): boolean;
|
|
81
|
+
isSelectedTileGrab?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number, viewportTileScale?: number, tileScaleFactor?: number, headerTileScaleFactor?: number): boolean;
|
|
81
82
|
hitTestBackground?(doc: AnnotationCanvasState, world: Vec2): {
|
|
82
83
|
id: AnnotationElementId;
|
|
83
84
|
} | 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) => boolean;
|
|
39
|
+
export declare const hitPlacedMeasurement: (m: PlacedMeasurementRef, p: Vec2, zoom?: number, tileScaleFactor?: number, viewportScale?: number, headerTileScaleFactor?: number) => boolean;
|
|
40
40
|
export interface RemoveMeasurementResult {
|
|
41
41
|
ops: AnnotationPatchOp[];
|
|
42
42
|
keepSelection: boolean;
|
|
@@ -110,14 +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) => {
|
|
113
|
+
export const hitPlacedMeasurement = (m, p, zoom = 1, tileScaleFactor = 1, viewportScale = 1, headerTileScaleFactor = 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
|
-
//
|
|
120
|
-
|
|
118
|
+
// stamps share the square footprint; group-header chips are a wide pill
|
|
119
|
+
// sized by their OWN document-wide knob, `headerTileScaleFactor` — see
|
|
120
|
+
// stampTileDims in stampLayout).
|
|
121
|
+
const dims = stampTileDims(m, tileScaleFactor, viewportScale, headerTileScaleFactor);
|
|
121
122
|
const halfW = (dims.width / 2 + STAMP_HIT_PADDING) / zoom;
|
|
122
123
|
const halfH = (dims.height / 2 + STAMP_HIT_PADDING) / zoom;
|
|
123
124
|
const dx = Math.abs(p.x - m.anchor.x);
|
|
@@ -19,6 +19,7 @@ export declare const DEFAULT_TILE_SCALE = 1;
|
|
|
19
19
|
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
|
+
export declare const resolveHeaderTileScaleFactor: (canvas: Pick<AnnotationCanvasState, "tileScaleFactor" | "tileScaleFactorMobile" | "headerTileScaleFactor" | "headerTileScaleFactorMobile">, platform: TileScalePlatform) => number;
|
|
22
23
|
export declare const clampTileScale: (v: number) => number;
|
|
23
24
|
export declare const tileDocSizeForFactor: (factor: number, zoom: number) => number;
|
|
24
25
|
export declare const tileScaleFromDocSize: (docSize: number, zoom: number) => number;
|
|
@@ -28,4 +29,4 @@ export interface StampTileDims {
|
|
|
28
29
|
width: number;
|
|
29
30
|
height: number;
|
|
30
31
|
}
|
|
31
|
-
export declare const stampTileDims: (m: Pick<PlacedMeasurementRef, "scale" | "isGroupHeader" | "collapsed">, tileScaleFactor?: number, viewportScale?: number) => StampTileDims;
|
|
32
|
+
export declare const stampTileDims: (m: Pick<PlacedMeasurementRef, "scale" | "isGroupHeader" | "collapsed">, tileScaleFactor?: number, viewportScale?: number, headerTileScaleFactor?: number) => StampTileDims;
|
|
@@ -80,6 +80,18 @@ export const TILE_SCALE_MAX = 2;
|
|
|
80
80
|
export const resolveTileScaleFactor = (canvas, platform) => (platform === 'mobile'
|
|
81
81
|
? (canvas.tileScaleFactorMobile ?? canvas.tileScaleFactor)
|
|
82
82
|
: canvas.tileScaleFactor) ?? DEFAULT_TILE_SCALE;
|
|
83
|
+
// Resolve the document-wide HEADER-scale knob for a platform — the group/form
|
|
84
|
+
// header counterpart of resolveTileScaleFactor (Asana 1217570500895543).
|
|
85
|
+
// Same platform fallback (mobile → web), then one more step: a document with
|
|
86
|
+
// no header knob at all falls back to the MEASUREMENT factor, the pre-split
|
|
87
|
+
// behavior where one knob drove every tile — so existing documents render
|
|
88
|
+
// identically until a user first moves the header slider. The ONE read path
|
|
89
|
+
// for the header fields — renderers, hit-testing, and the slider UIs must all
|
|
90
|
+
// resolve through here so they agree.
|
|
91
|
+
export const resolveHeaderTileScaleFactor = (canvas, platform) => (platform === 'mobile'
|
|
92
|
+
? (canvas.headerTileScaleFactorMobile ?? canvas.headerTileScaleFactor)
|
|
93
|
+
: canvas.headerTileScaleFactor) ??
|
|
94
|
+
resolveTileScaleFactor(canvas, platform);
|
|
83
95
|
// Clamp a tile-scale-factor candidate to the supported range. The single guard
|
|
84
96
|
// for the value before it lands in the document (slider input, restored docs).
|
|
85
97
|
export const clampTileScale = (v) => v < TILE_SCALE_MIN ? TILE_SCALE_MIN : v > TILE_SCALE_MAX ? TILE_SCALE_MAX : v;
|
|
@@ -136,11 +148,15 @@ export const isUnassociatedStamp = (m) => !m.isGroupHeader && !m.measurementId &
|
|
|
136
148
|
// canvas's content-fit reference zoom (see the note above), so tiles scale
|
|
137
149
|
// with the drawing as the user zooms.
|
|
138
150
|
export const stampTileSize = (m, tileScaleFactor = DEFAULT_TILE_SCALE, viewportScale = 1) => STAMP_TILE_SIZE * (m.scale ?? 1) * tileScaleFactor * viewportScale;
|
|
139
|
-
|
|
140
|
-
|
|
151
|
+
// `headerTileScaleFactor` is the header chips' own document-wide knob
|
|
152
|
+
// (resolveHeaderTileScaleFactor); it defaults to `tileScaleFactor` so callers
|
|
153
|
+
// that predate the header/measurement split keep their one-knob behavior.
|
|
154
|
+
export const stampTileDims = (m, tileScaleFactor = DEFAULT_TILE_SCALE, viewportScale = 1, headerTileScaleFactor = tileScaleFactor) => {
|
|
141
155
|
if (!m.isGroupHeader) {
|
|
156
|
+
const k = (m.scale ?? 1) * tileScaleFactor * viewportScale;
|
|
142
157
|
return { width: STAMP_TILE_SIZE * k, height: STAMP_TILE_SIZE * k };
|
|
143
158
|
}
|
|
159
|
+
const k = (m.scale ?? 1) * headerTileScaleFactor * viewportScale;
|
|
144
160
|
const base = groupHeaderBaseDims(m.collapsed);
|
|
145
161
|
return { width: base.width * k, height: base.height * k };
|
|
146
162
|
};
|
|
@@ -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)) {
|
|
54
|
+
if (hitPlacedMeasurement(m, event.world, zoom, ctx.tileScaleFactor, ctx.tileViewportScale, ctx.headerTileScaleFactor)) {
|
|
55
55
|
ctx.setSelection({ ids: [m.id] });
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
@@ -66,11 +66,15 @@ const segmentDistanceSq = (px, py, ax, ay, bx, by) => {
|
|
|
66
66
|
const findHit = (doc, world, zoom, viewportTileScale = 1,
|
|
67
67
|
// Platform-resolved tile-scale knob (ctx.tileScaleFactor). Defaults to the
|
|
68
68
|
// doc's web/legacy field so un-threaded callers keep the pre-split behavior.
|
|
69
|
-
tileScaleFactor = doc.tileScaleFactor
|
|
69
|
+
tileScaleFactor = doc.tileScaleFactor,
|
|
70
|
+
// Platform-resolved header-scale knob (ctx.headerTileScaleFactor). Defaults
|
|
71
|
+
// to the doc's web header field, falling back to the measurement knob the
|
|
72
|
+
// way resolveHeaderTileScaleFactor does.
|
|
73
|
+
headerTileScaleFactor = doc.headerTileScaleFactor ?? tileScaleFactor) => {
|
|
70
74
|
// Hit-test in z-order (top first): measurements > shapes > strokes.
|
|
71
75
|
for (let i = doc.placedMeasurements.length - 1; i >= 0; i--) {
|
|
72
76
|
const m = doc.placedMeasurements[i];
|
|
73
|
-
if (hitPlacedMeasurement(m, world, zoom, tileScaleFactor, viewportTileScale)) {
|
|
77
|
+
if (hitPlacedMeasurement(m, world, zoom, tileScaleFactor, viewportTileScale, headerTileScaleFactor)) {
|
|
74
78
|
return { id: m.id, kind: 'measurement' };
|
|
75
79
|
}
|
|
76
80
|
}
|
|
@@ -179,11 +183,11 @@ const translatePatch = (elementKind, id, doc, delta) => {
|
|
|
179
183
|
// is selected; without this, a handle sitting under the tile (a rectangle
|
|
180
184
|
// tile at the rect center, a line tile slid onto an endpoint) hijacks the grab
|
|
181
185
|
// and the tile becomes unmovable until deselected.
|
|
182
|
-
const isOnMeasurementTile = (doc, id, world, zoom, viewportTileScale = 1, tileScaleFactor = doc.tileScaleFactor) => {
|
|
186
|
+
const isOnMeasurementTile = (doc, id, world, zoom, viewportTileScale = 1, tileScaleFactor = doc.tileScaleFactor, headerTileScaleFactor = doc.headerTileScaleFactor ?? tileScaleFactor) => {
|
|
183
187
|
const m = doc.placedMeasurements.find((x) => x.id === id);
|
|
184
188
|
if (!m)
|
|
185
189
|
return false;
|
|
186
|
-
const dims = stampTileDims(m, tileScaleFactor, viewportTileScale);
|
|
190
|
+
const dims = stampTileDims(m, tileScaleFactor, viewportTileScale, headerTileScaleFactor);
|
|
187
191
|
const halfW = (dims.width / 2 + HIT_PADDING) / zoom;
|
|
188
192
|
const halfH = (dims.height / 2 + HIT_PADDING) / zoom;
|
|
189
193
|
return (Math.abs(world.x - m.anchor.x) <= halfW &&
|
|
@@ -193,14 +197,14 @@ const isOnMeasurementTile = (doc, id, world, zoom, viewportTileScale = 1, tileSc
|
|
|
193
197
|
// via DragSelectionConfig AND the web pointer handlers — one source of truth) ---
|
|
194
198
|
// Grabbing the tile of a line annotation slides it along the line; everything
|
|
195
199
|
// else (bare stamps, grabs on the line body) is a group move.
|
|
196
|
-
const classifyGrab = (doc, id, world, zoom, viewportTileScale = 1, tileScaleFactor = doc.tileScaleFactor) => {
|
|
200
|
+
const classifyGrab = (doc, id, world, zoom, viewportTileScale = 1, tileScaleFactor = doc.tileScaleFactor, headerTileScaleFactor = doc.headerTileScaleFactor ?? tileScaleFactor) => {
|
|
197
201
|
const m = doc.placedMeasurements.find((x) => x.id === id);
|
|
198
202
|
if (!m)
|
|
199
203
|
return 'move';
|
|
200
204
|
// Same footprint the tile is drawn at (wide pill for group headers; folds in
|
|
201
205
|
// the document-wide tile scale + viewport scale so slide-grab matches the
|
|
202
206
|
// draw).
|
|
203
|
-
const dims = stampTileDims(m, tileScaleFactor, viewportTileScale);
|
|
207
|
+
const dims = stampTileDims(m, tileScaleFactor, viewportTileScale, headerTileScaleFactor);
|
|
204
208
|
const halfW = (dims.width / 2 + HIT_PADDING) / zoom;
|
|
205
209
|
const halfH = (dims.height / 2 + HIT_PADDING) / zoom;
|
|
206
210
|
const onTile = Math.abs(world.x - m.anchor.x) <= halfW &&
|
|
@@ -600,7 +604,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
600
604
|
// reusing the same hit-test and translate logic the pointer handlers below
|
|
601
605
|
// use for web — one source of truth.
|
|
602
606
|
dragSelection: {
|
|
603
|
-
hitTest: (doc, world, zoom, viewportTileScale, tileScaleFactor) => findHit(doc, world, zoom, viewportTileScale, tileScaleFactor),
|
|
607
|
+
hitTest: (doc, world, zoom, viewportTileScale, tileScaleFactor, headerTileScaleFactor) => findHit(doc, world, zoom, viewportTileScale, tileScaleFactor, headerTileScaleFactor),
|
|
604
608
|
buildTranslatePatch: (doc, id, kind, delta) => {
|
|
605
609
|
const op = translatePatch(kind, id, doc, delta);
|
|
606
610
|
return op ? { ops: [op] } : null;
|
|
@@ -639,7 +643,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
639
643
|
// tile stays draggable (otherwise it can only be moved after deselecting).
|
|
640
644
|
const selId = ctx.selection?.ids[0];
|
|
641
645
|
if (selId &&
|
|
642
|
-
!isOnMeasurementTile(ctx.document, selId, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor)) {
|
|
646
|
+
!isOnMeasurementTile(ctx.document, selId, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor, ctx.headerTileScaleFactor)) {
|
|
643
647
|
const handle = findHandleHit(ctx.document, selId, world, zoom);
|
|
644
648
|
if (handle) {
|
|
645
649
|
ctx.setSelection({ ids: [selId] });
|
|
@@ -708,7 +712,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
708
712
|
};
|
|
709
713
|
}
|
|
710
714
|
}
|
|
711
|
-
const hit = findHit(ctx.document, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor);
|
|
715
|
+
const hit = findHit(ctx.document, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor, ctx.headerTileScaleFactor);
|
|
712
716
|
if (!hit) {
|
|
713
717
|
// Background layers only get a look-in when every annotation element
|
|
714
718
|
// misses — they can cover the whole document, so elements above always
|
|
@@ -753,7 +757,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
753
757
|
: null;
|
|
754
758
|
ctx.setSelection({ ids: [hit.id] });
|
|
755
759
|
const mode = hit.kind === 'measurement' &&
|
|
756
|
-
classifyGrab(ctx.document, hit.id, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor) === 'slide'
|
|
760
|
+
classifyGrab(ctx.document, hit.id, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor, ctx.headerTileScaleFactor) === 'slide'
|
|
757
761
|
? 'slide'
|
|
758
762
|
: 'move';
|
|
759
763
|
return {
|
|
@@ -24,6 +24,7 @@ export interface AnnotationCanvasHandle {
|
|
|
24
24
|
durationMs?: number;
|
|
25
25
|
}): void;
|
|
26
26
|
getTileScaleFactor(): number;
|
|
27
|
+
getHeaderTileScaleFactor(): number;
|
|
27
28
|
getTileDocSize(factor: number): number;
|
|
28
29
|
ensureMeasurementVisible(id: AnnotationElementId): void;
|
|
29
30
|
placeMeasurementAtCenter(ref: MeasurementRef): void;
|
|
@@ -84,6 +85,7 @@ export interface AnnotationCanvasStateApi {
|
|
|
84
85
|
ctx: ToolContext;
|
|
85
86
|
tileViewportScale: number;
|
|
86
87
|
tileScaleFactor: number;
|
|
88
|
+
headerTileScaleFactor: number;
|
|
87
89
|
penDrawingStroke: AnnotationStroke | null;
|
|
88
90
|
customPreviewState: ToolState;
|
|
89
91
|
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 { resolveTileScaleFactor, stampTileDims, STAMP_TILE_SIZE, tileDocSizeForFactor, tileScaleFromDocSize, } from './stampLayout.js';
|
|
3
|
+
import { resolveHeaderTileScaleFactor, 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';
|
|
@@ -157,13 +157,28 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
157
157
|
// commit writes tileScaleFactorMobile and takes over for good.
|
|
158
158
|
const tileScalePlatformResolved = tileScalePlatform ?? 'web';
|
|
159
159
|
let tileScaleFactor = resolveTileScaleFactor(effectiveCanvas, tileScalePlatformResolved);
|
|
160
|
+
// Group/form header chips carry their own document-wide knob (Asana
|
|
161
|
+
// 1217570500895543), resolved the same way. With no header knob set it
|
|
162
|
+
// equals the measurement factor (resolveHeaderTileScaleFactor's fallback) —
|
|
163
|
+
// the pre-split one-knob behavior.
|
|
164
|
+
let headerTileScaleFactor = resolveHeaderTileScaleFactor(effectiveCanvas, tileScalePlatformResolved);
|
|
160
165
|
if (tileScalePlatformResolved === 'mobile' &&
|
|
161
166
|
effectiveCanvas.tileScaleFactorMobile == null &&
|
|
162
167
|
effectiveCanvas.tileDocSize != null &&
|
|
163
168
|
effectiveCanvas.tileDocSize > 0 &&
|
|
164
169
|
width > 0 &&
|
|
165
170
|
height > 0) {
|
|
166
|
-
|
|
171
|
+
const calibrated = tileScaleFromDocSize(effectiveCanvas.tileDocSize, baseFitZoom);
|
|
172
|
+
// Scale the header factor by the same calibration ratio so its relation
|
|
173
|
+
// to the measurement factor survives the override: a doc with no header
|
|
174
|
+
// knob keeps headers exactly at the calibrated measurement factor, and a
|
|
175
|
+
// doc whose web user set a header knob keeps the same header-to-tile
|
|
176
|
+
// proportion the web user saw.
|
|
177
|
+
headerTileScaleFactor =
|
|
178
|
+
tileScaleFactor > 0
|
|
179
|
+
? headerTileScaleFactor * (calibrated / tileScaleFactor)
|
|
180
|
+
: calibrated;
|
|
181
|
+
tileScaleFactor = calibrated;
|
|
167
182
|
}
|
|
168
183
|
const ctx = useMemo(() => ({
|
|
169
184
|
document: canvas,
|
|
@@ -171,6 +186,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
171
186
|
viewport: viewportApi,
|
|
172
187
|
tileViewportScale,
|
|
173
188
|
tileScaleFactor,
|
|
189
|
+
headerTileScaleFactor,
|
|
174
190
|
preview(patch) {
|
|
175
191
|
setPreviewPatch(patch);
|
|
176
192
|
},
|
|
@@ -222,6 +238,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
222
238
|
viewportApi,
|
|
223
239
|
tileViewportScale,
|
|
224
240
|
tileScaleFactor,
|
|
241
|
+
headerTileScaleFactor,
|
|
225
242
|
tileScalePlatformResolved,
|
|
226
243
|
baseFitZoom,
|
|
227
244
|
onCommit,
|
|
@@ -425,6 +442,9 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
425
442
|
getTileScaleFactor() {
|
|
426
443
|
return ctxRef.current.tileScaleFactor;
|
|
427
444
|
},
|
|
445
|
+
getHeaderTileScaleFactor() {
|
|
446
|
+
return ctxRef.current.headerTileScaleFactor;
|
|
447
|
+
},
|
|
428
448
|
getTileDocSize(factor) {
|
|
429
449
|
// Tiles are doc-constant, so the doc size a factor yields derives
|
|
430
450
|
// from the content-fit reference, not the current zoom.
|
|
@@ -437,7 +457,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
437
457
|
return;
|
|
438
458
|
// The tile's CURRENT on-screen half-extents: stampTileDims with the
|
|
439
459
|
// zoom-relative tileViewportScale folded in.
|
|
440
|
-
const dims = stampTileDims(m, c.tileScaleFactor, c.tileViewportScale);
|
|
460
|
+
const dims = stampTileDims(m, c.tileScaleFactor, c.tileViewportScale, c.headerTileScaleFactor);
|
|
441
461
|
const halfW = dims.width / 2;
|
|
442
462
|
const halfH = dims.height / 2;
|
|
443
463
|
const s = c.viewport.worldToScreen(m.anchor);
|
|
@@ -734,6 +754,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
734
754
|
ctx,
|
|
735
755
|
tileViewportScale,
|
|
736
756
|
tileScaleFactor,
|
|
757
|
+
headerTileScaleFactor,
|
|
737
758
|
penDrawingStroke,
|
|
738
759
|
customPreviewState: toolState,
|
|
739
760
|
dispatchPointerDown,
|
|
@@ -194,6 +194,13 @@ export interface CalculatorDefinition {
|
|
|
194
194
|
diagramFileId?: string | null;
|
|
195
195
|
/** Public calculators are readable by everyone; rules/query key. */
|
|
196
196
|
isPublic: boolean;
|
|
197
|
+
sharedUserIds?: string[];
|
|
198
|
+
/**
|
|
199
|
+
* Orgs this calculator is shared with. Written only by the backend
|
|
200
|
+
* shareCalculatorWithOrg / unshareCalculatorWithOrg callables, which also
|
|
201
|
+
* maintain the organizations/{orgId}/sharedCalculators pointer docs clients
|
|
202
|
+
* use for discovery (rules can't authorize an array-contains query here).
|
|
203
|
+
*/
|
|
197
204
|
sharedOrgIds?: string[];
|
|
198
205
|
createdBy?: {
|
|
199
206
|
userId: string;
|
package/dist/exports.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type { GestureConfig, PanTrigger, AnnotationCanvasInnerProps, } from './a
|
|
|
29
29
|
export type { CanvasPointerEvent, RequestTextInput, ShapeDrawConfig, Tool, ToolContext, ToolState, } from './annotation/canvas/Tool.js';
|
|
30
30
|
export type { 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, 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, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, 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, 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, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, } 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';
|