@reekon-tools/boldr-utils 1.11.0 → 1.13.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.native.js +48 -7
- package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +2 -2
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +16 -3
- package/dist/annotation/canvas/Tool.d.ts +8 -1
- package/dist/annotation/canvas/backgroundLayers.d.ts +20 -0
- package/dist/annotation/canvas/backgroundLayers.js +98 -0
- package/dist/annotation/canvas/elements/BackgroundImageElement.d.ts +10 -7
- package/dist/annotation/canvas/elements/BackgroundImageElement.js +45 -11
- package/dist/annotation/canvas/elements/BackgroundSvg.d.ts +3 -4
- package/dist/annotation/canvas/elements/BackgroundSvg.js +23 -29
- package/dist/annotation/canvas/elements/backgroundUrl.js +2 -2
- package/dist/annotation/canvas/groupHeaderLayout.d.ts +72 -0
- package/dist/annotation/canvas/groupHeaderLayout.js +115 -0
- package/dist/annotation/canvas/stampLayout.d.ts +15 -2
- package/dist/annotation/canvas/stampLayout.js +46 -12
- package/dist/annotation/canvas/tools/selectTool.js +183 -19
- package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +1 -0
- package/dist/annotation/canvas/useAnnotationCanvasState.js +40 -20
- package/dist/annotation/canvas/viewport.js +7 -6
- package/dist/annotation/data/AnnotationDataProvider.d.ts +1 -1
- package/dist/annotation/data/InMemoryAnnotationProvider.d.ts +1 -1
- package/dist/annotation/data/hooks/useAnnotationCanvasDoc.d.ts +7 -1
- package/dist/annotation/data/hooks/useAnnotationCanvasDoc.js +124 -22
- package/dist/annotation/data/hooks/useAnnotationMutations.d.ts +1 -1
- package/dist/canvas/AnnotationCanvas.d.ts +11 -0
- package/dist/canvas/AnnotationCanvas.js +10 -0
- package/dist/canvas/AnnotationCanvas.native.d.ts +8 -0
- package/dist/canvas/AnnotationCanvas.native.js +6 -0
- package/dist/canvas/AnnotationCanvasInner.d.ts +39 -0
- package/dist/canvas/AnnotationCanvasInner.js +219 -0
- package/dist/canvas/AnnotationCanvasInner.native.d.ts +35 -0
- package/dist/canvas/AnnotationCanvasInner.native.js +138 -0
- package/dist/canvas/AnnotationCanvasSkia.d.ts +27 -0
- package/dist/canvas/AnnotationCanvasSkia.js +20 -0
- package/dist/canvas/Tool.d.ts +38 -0
- package/dist/canvas/Tool.js +1 -0
- package/dist/canvas/elements/BackgroundImageElement.d.ts +9 -0
- package/dist/canvas/elements/BackgroundImageElement.js +37 -0
- package/dist/canvas/elements/MeasurementStampElement.d.ts +13 -0
- package/dist/canvas/elements/MeasurementStampElement.js +30 -0
- package/dist/canvas/elements/ShapeElement.d.ts +7 -0
- package/dist/canvas/elements/ShapeElement.js +62 -0
- package/dist/canvas/elements/StrokeElement.d.ts +7 -0
- package/dist/canvas/elements/StrokeElement.js +18 -0
- package/dist/canvas/measurementPicker.d.ts +10 -0
- package/dist/canvas/measurementPicker.js +1 -0
- package/dist/canvas/measurementStampOverlay.d.ts +11 -0
- package/dist/canvas/measurementStampOverlay.js +1 -0
- package/dist/canvas/pointerAdapter.d.ts +3 -0
- package/dist/canvas/pointerAdapter.js +19 -0
- package/dist/canvas/stampLayout.d.ts +5 -0
- package/dist/canvas/stampLayout.js +14 -0
- package/dist/canvas/tools/measurementStampTool.d.ts +9 -0
- package/dist/canvas/tools/measurementStampTool.js +37 -0
- package/dist/canvas/tools/panTool.d.ts +5 -0
- package/dist/canvas/tools/panTool.js +25 -0
- package/dist/canvas/tools/penTool.d.ts +13 -0
- package/dist/canvas/tools/penTool.js +68 -0
- package/dist/canvas/tools/selectTool.d.ts +2 -0
- package/dist/canvas/tools/selectTool.js +182 -0
- package/dist/canvas/useAnnotationCanvasState.d.ts +54 -0
- package/dist/canvas/useAnnotationCanvasState.js +210 -0
- package/dist/canvas/viewport.d.ts +16 -0
- package/dist/canvas/viewport.js +54 -0
- package/dist/data/AnnotationDataContext.d.ts +8 -0
- package/dist/data/AnnotationDataContext.js +11 -0
- package/dist/data/AnnotationDataProvider.d.ts +65 -0
- package/dist/data/AnnotationDataProvider.js +4 -0
- package/dist/data/InMemoryAnnotationProvider.d.ts +30 -0
- package/dist/data/InMemoryAnnotationProvider.js +197 -0
- package/dist/data/canvasPersistence.d.ts +3 -0
- package/dist/data/canvasPersistence.js +26 -0
- package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +33 -0
- package/dist/data/hooks/useAnnotationCanvasDoc.js +314 -0
- package/dist/data/hooks/useAnnotationDoc.d.ts +7 -0
- package/dist/data/hooks/useAnnotationDoc.js +33 -0
- package/dist/data/hooks/useAnnotationList.d.ts +7 -0
- package/dist/data/hooks/useAnnotationList.js +26 -0
- package/dist/data/hooks/useAnnotationMutations.d.ts +9 -0
- package/dist/data/hooks/useAnnotationMutations.js +11 -0
- package/dist/exports.d.ts +3 -1
- package/dist/exports.js +3 -1
- package/dist/hooks/useParseMeasurement.d.ts +4 -0
- package/dist/hooks/useParseMeasurement.js +14 -0
- package/dist/types/annotation.d.ts +28 -0
- package/dist/types/annotation.js +68 -0
- package/dist/utils/evaluateFormula.d.ts +20 -0
- package/dist/utils/evaluateFormula.js +31 -0
- package/package.json +1 -1
- package/dist/theme/index.d.ts +0 -3
- package/dist/theme/index.js +0 -3
- package/dist/theme/preset.d.ts +0 -120
- package/dist/theme/preset.js +0 -25
- package/dist/theme/semantic.d.ts +0 -136
- package/dist/theme/semantic.js +0 -116
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Presentation model for the group-header chip placed on an annotation canvas
|
|
2
|
+
// (`PlacedMeasurementRef.isGroupHeader`), shared by mobile and web.
|
|
3
|
+
//
|
|
4
|
+
// The chip itself can't be shared — this package ships DOM and React Native
|
|
5
|
+
// forks of the canvas host, and there is no react-native-web in the graph, so
|
|
6
|
+
// a single component would only render on one of them. What CAN be shared is
|
|
7
|
+
// everything that isn't a view node: which badges a header shows, what they
|
|
8
|
+
// say, what colour they are, and every measurement in the Figma frames. Both
|
|
9
|
+
// hosts render the SAME descriptor into ~40 lines of platform view code, so a
|
|
10
|
+
// change to the header's content or metrics lands in one place.
|
|
11
|
+
//
|
|
12
|
+
// Figma (BOLDR PRO / APP STRUCTURE, "HEADER SETTINGS - MVP" 490:4189):
|
|
13
|
+
// 491:5371 expanded group · 491:5654 collapsed group
|
|
14
|
+
// 491:5381 expanded form · 491:5659 collapsed form
|
|
15
|
+
// Asana 1217382797393698.
|
|
16
|
+
import { GroupType } from '../../types/firestore.js';
|
|
17
|
+
import { GeneralColors, TileColors, YellowColors } from '../../theme/colors.js';
|
|
18
|
+
import { GROUP_HEADER_DESIGN } from './stampLayout.js';
|
|
19
|
+
// Every length here is in the DESIGN space the frames were drawn in — the
|
|
20
|
+
// 200×56 / 90×65 boxes of GROUP_HEADER_DESIGN. Hosts lay the chip out at that
|
|
21
|
+
// natural size and uniformly transform-scale it into the overlay box
|
|
22
|
+
// stampTileDims hands them, so these numbers never need scaling by hand.
|
|
23
|
+
export const GROUP_HEADER_STYLE = {
|
|
24
|
+
// Outer chip.
|
|
25
|
+
background: GeneralColors.grey2,
|
|
26
|
+
radius: 8,
|
|
27
|
+
padding: 5,
|
|
28
|
+
// Selection ring. Drawn INSIDE the chip's box (the footprint is the
|
|
29
|
+
// hit-test box too, so an outset ring would lie about what is grabbable).
|
|
30
|
+
selectedBorderWidth: 3,
|
|
31
|
+
selectedBorderColor: YellowColors.mainYellow,
|
|
32
|
+
// Index badges.
|
|
33
|
+
badgeHeight: 25,
|
|
34
|
+
badgeRadius: 4,
|
|
35
|
+
badgeFontSize: 13,
|
|
36
|
+
badgeTextColor: GeneralColors.white,
|
|
37
|
+
// Gap between the two badges of a form header: horizontal when expanded
|
|
38
|
+
// (they sit side by side), vertical when collapsed (they stack).
|
|
39
|
+
badgeGapExpanded: 5,
|
|
40
|
+
badgeGapCollapsed: 5,
|
|
41
|
+
// Group name, expanded only.
|
|
42
|
+
nameGap: 3,
|
|
43
|
+
nameFontSize: 15,
|
|
44
|
+
nameColor: GeneralColors.white,
|
|
45
|
+
// Globe glyph that stands in for the index badge on the default group.
|
|
46
|
+
globeSize: 17,
|
|
47
|
+
globeColor: GeneralColors.white,
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Resolve what a header chip shows. `group` is looked up live by the host from
|
|
51
|
+
* `placed.groupId` so renames and re-indexes appear without touching the
|
|
52
|
+
* canvas document; `section` is that group's section, needed only for a FORM
|
|
53
|
+
* header's "# Table N". Both may be undefined — a group deleted after its
|
|
54
|
+
* header was placed still has to render something rather than crash.
|
|
55
|
+
*/
|
|
56
|
+
export const describeGroupHeader = ({ group, section, collapsed = false, }) => {
|
|
57
|
+
const design = collapsed
|
|
58
|
+
? GROUP_HEADER_DESIGN.collapsed
|
|
59
|
+
: GROUP_HEADER_DESIGN.expanded;
|
|
60
|
+
const base = {
|
|
61
|
+
collapsed,
|
|
62
|
+
name: collapsed ? null : (group?.name ?? 'Group'),
|
|
63
|
+
design,
|
|
64
|
+
};
|
|
65
|
+
if (!group)
|
|
66
|
+
return { ...base, badges: [], showGlobe: false };
|
|
67
|
+
// Default group (index 0): no number to show — the globe chip stands in.
|
|
68
|
+
if (group.groupIndex === 0) {
|
|
69
|
+
return {
|
|
70
|
+
...base,
|
|
71
|
+
showGlobe: true,
|
|
72
|
+
badges: [{ key: 'group', text: '', background: TileColors.darkBlue }],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const badges = [];
|
|
76
|
+
if (group.type === GroupType.FORM) {
|
|
77
|
+
// "# Table N" only when the section resolved — a form header with a
|
|
78
|
+
// missing section drops to its "# Form N" badge alone rather than
|
|
79
|
+
// rendering an empty chip.
|
|
80
|
+
if (typeof section?.index === 'number') {
|
|
81
|
+
badges.push({
|
|
82
|
+
key: 'table',
|
|
83
|
+
text: `# Table ${section.index + 1}`,
|
|
84
|
+
background: TileColors.lightBlue1,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
badges.push({
|
|
88
|
+
key: 'form',
|
|
89
|
+
text: `# Form ${group.groupIndex + 1}`,
|
|
90
|
+
background: TileColors.activeBlue,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
badges.push({
|
|
95
|
+
key: 'group',
|
|
96
|
+
text: `# Group ${group.groupIndex + 1}`,
|
|
97
|
+
background: TileColors.darkBlue,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return { ...base, badges, showGlobe: false };
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Signature of everything `describeGroupHeader` would render, for hosts that
|
|
104
|
+
* cache on tile content (mobile re-bakes its canvas thumbnail when this
|
|
105
|
+
* changes). Kept beside the resolver so the two can't drift.
|
|
106
|
+
*/
|
|
107
|
+
export const groupHeaderSignature = (input) => {
|
|
108
|
+
const d = describeGroupHeader(input);
|
|
109
|
+
return [
|
|
110
|
+
d.collapsed ? 'c' : 'e',
|
|
111
|
+
d.showGlobe ? 'globe' : '',
|
|
112
|
+
d.badges.map((b) => `${b.key}:${b.text}`).join(','),
|
|
113
|
+
d.name ?? '',
|
|
114
|
+
].join('~');
|
|
115
|
+
};
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import type { AnnotationCanvasState, PlacedMeasurementRef, TileScalePlatform } from '../../types/annotation.js';
|
|
2
2
|
export declare const STAMP_TILE_SIZE = 96;
|
|
3
3
|
export declare const GROUP_HEADER_TILE_WIDTH = 240;
|
|
4
|
-
export declare const GROUP_HEADER_TILE_HEIGHT =
|
|
4
|
+
export declare const GROUP_HEADER_TILE_HEIGHT = 67.2;
|
|
5
|
+
export declare const GROUP_HEADER_DESIGN: {
|
|
6
|
+
readonly expanded: {
|
|
7
|
+
readonly width: 200;
|
|
8
|
+
readonly height: 56;
|
|
9
|
+
};
|
|
10
|
+
readonly collapsed: {
|
|
11
|
+
readonly width: 90;
|
|
12
|
+
readonly height: 65;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare const GROUP_HEADER_COLLAPSED_WIDTH: number;
|
|
16
|
+
export declare const GROUP_HEADER_COLLAPSED_HEIGHT: number;
|
|
17
|
+
export declare const groupHeaderBaseDims: (collapsed?: boolean) => StampTileDims;
|
|
5
18
|
export declare const DEFAULT_TILE_SCALE = 1;
|
|
6
19
|
export declare const TILE_SCALE_MIN = 0.4;
|
|
7
20
|
export declare const TILE_SCALE_MAX = 2;
|
|
@@ -15,4 +28,4 @@ export interface StampTileDims {
|
|
|
15
28
|
width: number;
|
|
16
29
|
height: number;
|
|
17
30
|
}
|
|
18
|
-
export declare const stampTileDims: (m: Pick<PlacedMeasurementRef, "scale" | "isGroupHeader">, tileScaleFactor?: number, viewportScale?: number) => StampTileDims;
|
|
31
|
+
export declare const stampTileDims: (m: Pick<PlacedMeasurementRef, "scale" | "isGroupHeader" | "collapsed">, tileScaleFactor?: number, viewportScale?: number) => StampTileDims;
|
|
@@ -15,13 +15,48 @@
|
|
|
15
15
|
// tile-scale slider against blank tiles saw their tiles balloon once
|
|
16
16
|
// populated (Asana 1216233394800417).
|
|
17
17
|
export const STAMP_TILE_SIZE = 96;
|
|
18
|
-
// Base footprint of
|
|
19
|
-
// rather than the square tile
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
18
|
+
// Base footprint of an EXPANDED group-header chip (`isGroupHeader`) — a wide
|
|
19
|
+
// pill rather than the square tile. Sized exactly like STAMP_TILE_SIZE:
|
|
20
|
+
// `placed.scale` × the document-wide tile-scale factor, scaling with zoom from
|
|
21
|
+
// the content-fit reference. The width is the header's established base and
|
|
22
|
+
// does not change; the height now follows the design's 200×56 proportion
|
|
23
|
+
// (see GROUP_HEADER_DESIGN below) rather than the legacy Nutrient stamp's
|
|
24
|
+
// 150×40, a ~5% difference on existing documents.
|
|
23
25
|
export const GROUP_HEADER_TILE_WIDTH = 240;
|
|
24
|
-
export const GROUP_HEADER_TILE_HEIGHT =
|
|
26
|
+
export const GROUP_HEADER_TILE_HEIGHT = 67.2;
|
|
27
|
+
// Natural design-space size of each header state, straight off the Figma
|
|
28
|
+
// frames (491:5371 expanded / 491:5654 collapsed — the two form variants,
|
|
29
|
+
// 491:5381 / 491:5659, use the same boxes). Renderers lay the chip out at
|
|
30
|
+
// THESE dimensions and uniformly transform-scale it into the overlay box, the
|
|
31
|
+
// same natural-size-then-scale pattern the measurement tiles use, so type and
|
|
32
|
+
// badges keep their proportions under the tile-scale slider. Collapsed is one
|
|
33
|
+
// fixed box for both group and form headers — a form stacks its two badges to
|
|
34
|
+
// fill it, a group centers its single badge — so a canvas of collapsed
|
|
35
|
+
// headers reads as a tidy row of equal chips.
|
|
36
|
+
export const GROUP_HEADER_DESIGN = {
|
|
37
|
+
expanded: { width: 200, height: 56 },
|
|
38
|
+
collapsed: { width: 90, height: 65 },
|
|
39
|
+
};
|
|
40
|
+
// Design units → base units. Pinned by the expanded header keeping its
|
|
41
|
+
// established 240-unit width, and applied to BOTH states so the two share one
|
|
42
|
+
// scale: a badge is the same size whether its header is expanded or collapsed.
|
|
43
|
+
const HEADER_BASE_SCALE = GROUP_HEADER_TILE_WIDTH / GROUP_HEADER_DESIGN.expanded.width;
|
|
44
|
+
// Base footprint of a COLLAPSED group-header chip — the design's 90×65 at the
|
|
45
|
+
// same scale. Wider-than-tall no longer holds here: a collapsed header is
|
|
46
|
+
// slightly TALLER than an expanded one (it stacks badges where the expanded
|
|
47
|
+
// one puts them side by side and hangs the name underneath).
|
|
48
|
+
export const GROUP_HEADER_COLLAPSED_WIDTH = GROUP_HEADER_DESIGN.collapsed.width * HEADER_BASE_SCALE;
|
|
49
|
+
export const GROUP_HEADER_COLLAPSED_HEIGHT = GROUP_HEADER_DESIGN.collapsed.height * HEADER_BASE_SCALE;
|
|
50
|
+
// Unscaled footprint of a group header in each state — the one place the
|
|
51
|
+
// expanded/collapsed box choice is made. Callers that already hold a
|
|
52
|
+
// PlacedMeasurementRef should go through stampTileDims instead, which folds in
|
|
53
|
+
// `scale`, the document-wide factor and the zoom.
|
|
54
|
+
export const groupHeaderBaseDims = (collapsed) => collapsed
|
|
55
|
+
? {
|
|
56
|
+
width: GROUP_HEADER_COLLAPSED_WIDTH,
|
|
57
|
+
height: GROUP_HEADER_COLLAPSED_HEIGHT,
|
|
58
|
+
}
|
|
59
|
+
: { width: GROUP_HEADER_TILE_WIDTH, height: GROUP_HEADER_TILE_HEIGHT };
|
|
25
60
|
// Document-wide tile scale factor: one knob that shrinks/grows EVERY
|
|
26
61
|
// measurement tile on the canvas at once, on top of each tile's own `scale`.
|
|
27
62
|
// Lets a user pull tiles down on a dense drawing where lines crowd together,
|
|
@@ -103,10 +138,9 @@ export const isUnassociatedStamp = (m) => !m.isGroupHeader && !m.measurementId &
|
|
|
103
138
|
export const stampTileSize = (m, tileScaleFactor = DEFAULT_TILE_SCALE, viewportScale = 1) => STAMP_TILE_SIZE * (m.scale ?? 1) * tileScaleFactor * viewportScale;
|
|
104
139
|
export const stampTileDims = (m, tileScaleFactor = DEFAULT_TILE_SCALE, viewportScale = 1) => {
|
|
105
140
|
const k = (m.scale ?? 1) * tileScaleFactor * viewportScale;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
: { width: STAMP_TILE_SIZE * k, height: STAMP_TILE_SIZE * k };
|
|
141
|
+
if (!m.isGroupHeader) {
|
|
142
|
+
return { width: STAMP_TILE_SIZE * k, height: STAMP_TILE_SIZE * k };
|
|
143
|
+
}
|
|
144
|
+
const base = groupHeaderBaseDims(m.collapsed);
|
|
145
|
+
return { width: base.width * k, height: base.height * k };
|
|
112
146
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { backgroundLayerDocRect, backgroundLayersOf, } from '../backgroundLayers.js';
|
|
1
2
|
import { stampTileDims } from '../stampLayout.js';
|
|
2
3
|
import { placementOf, linePosOf, snapLinePos, lerp, recomputeAnchor, rectCenter, rectCornerPoint, oppositeRectCorner, hitPlacedMeasurement, } from '../measurementGeometry.js';
|
|
3
4
|
import { hitShapeOutline, visualShapeBounds } from '../shapeGeometry.js';
|
|
@@ -16,6 +17,12 @@ const isTextShape = (doc, id) => doc.shapes.some((s) => s.id === id && s.kind ==
|
|
|
16
17
|
// tap (shape not yet selected) leaves this null, so it only selects; the second
|
|
17
18
|
// tap edits. Cleared on drag, release, and cancel.
|
|
18
19
|
let pendingTextEditId = null;
|
|
20
|
+
// Tap-toggle for background layers, latched the same way (and for the same
|
|
21
|
+
// native synchronous down+up reason) as pendingTextEditId: a pointer-down on
|
|
22
|
+
// an ALREADY-selected background records its id here; a release with no
|
|
23
|
+
// movement then deselects it. Movement disarms it — the gesture was a body
|
|
24
|
+
// drag, not a toggle. Cleared on drag, release, and cancel.
|
|
25
|
+
let pendingBackgroundDeselectId = null;
|
|
19
26
|
// Hit-test in doc-space. Crude but fast — good enough for v1; tools can
|
|
20
27
|
// override via `hitTest` for more precision later.
|
|
21
28
|
const hitStroke = (stroke, p) => {
|
|
@@ -100,6 +107,25 @@ tileScaleFactor = doc.tileScaleFactor) => {
|
|
|
100
107
|
return null;
|
|
101
108
|
};
|
|
102
109
|
const translatePatch = (elementKind, id, doc, delta) => {
|
|
110
|
+
if (elementKind === 'background') {
|
|
111
|
+
// Looked up through the normalized view so a legacy single-image doc's
|
|
112
|
+
// synthesized layer is draggable too — the dedicated op materializes the
|
|
113
|
+
// array on apply.
|
|
114
|
+
const layer = backgroundLayerById(doc, id);
|
|
115
|
+
if (!layer)
|
|
116
|
+
return null;
|
|
117
|
+
return {
|
|
118
|
+
op: 'updateBackgroundImage',
|
|
119
|
+
id,
|
|
120
|
+
patch: {
|
|
121
|
+
transform: {
|
|
122
|
+
...layer.transform,
|
|
123
|
+
x: layer.transform.x + delta.x,
|
|
124
|
+
y: layer.transform.y + delta.y,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
103
129
|
if (elementKind === 'measurement') {
|
|
104
130
|
const m = doc.placedMeasurements.find((x) => x.id === id);
|
|
105
131
|
if (!m)
|
|
@@ -417,17 +443,83 @@ const shapeCornerPatch = (doc, id, corner, delta) => {
|
|
|
417
443
|
],
|
|
418
444
|
};
|
|
419
445
|
};
|
|
446
|
+
// --- Background layers (shared by the native UI-thread drag via
|
|
447
|
+
// DragSelectionConfig AND the web pointer handlers — one source of truth).
|
|
448
|
+
// Backgrounds can cover the whole document, so they never join the general
|
|
449
|
+
// hit-test: a tap selects one only when every stroke/shape/measurement
|
|
450
|
+
// misses, and a drag translates one only when it is ALREADY selected — the
|
|
451
|
+
// layout tool's two-step model, which keeps a full-bleed background from
|
|
452
|
+
// hijacking pan/marquee gestures. ---
|
|
453
|
+
// Doc-space clamp on a resized background's larger doc dimension. The floor
|
|
454
|
+
// keeps a corner drag from collapsing a layer below grabbable size; the
|
|
455
|
+
// ceiling keeps the doc rect (which feeds fit math and the web SVG raster
|
|
456
|
+
// planner) from exploding to numerically silly sizes.
|
|
457
|
+
const MIN_BACKGROUND_DOC_EXTENT = 16;
|
|
458
|
+
const MAX_BACKGROUND_DOC_EXTENT = 65536;
|
|
459
|
+
const backgroundLayerById = (doc, id) => backgroundLayersOf(doc.viewport).find((l) => l.id === id) ?? null;
|
|
460
|
+
const pointInBackgroundLayer = (layer, p) => {
|
|
461
|
+
const r = backgroundLayerDocRect(layer);
|
|
462
|
+
return (p.x >= r.x && p.x <= r.x + r.width && p.y >= r.y && p.y <= r.y + r.height);
|
|
463
|
+
};
|
|
464
|
+
// Topmost background layer under a world point (reverse array order — later
|
|
465
|
+
// layers draw on top). Point-in-doc-rect, no padding: a background is a big
|
|
466
|
+
// target already.
|
|
467
|
+
const findBackgroundHit = (doc, world) => {
|
|
468
|
+
const layers = backgroundLayersOf(doc.viewport);
|
|
469
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
470
|
+
if (pointInBackgroundLayer(layers[i], world)) {
|
|
471
|
+
return { id: layers[i].id };
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
};
|
|
476
|
+
// Topmost ALREADY-selected background layer under a world point. The drag
|
|
477
|
+
// paths call this only after the general hit-test misses, so annotation
|
|
478
|
+
// elements always win a grab over the background beneath them.
|
|
479
|
+
const findSelectedBackgroundHit = (doc, world, selectedIds) => {
|
|
480
|
+
const layers = backgroundLayersOf(doc.viewport);
|
|
481
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
482
|
+
const layer = layers[i];
|
|
483
|
+
if (!selectedIds.includes(layer.id))
|
|
484
|
+
continue;
|
|
485
|
+
if (pointInBackgroundLayer(layer, world)) {
|
|
486
|
+
return { id: layer.id, kind: 'background' };
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return null;
|
|
490
|
+
};
|
|
491
|
+
// Corner-scale geometry for a background layer: pivot = the doc rect's
|
|
492
|
+
// top-left — the layer's transform origin, so scaling about it multiplies
|
|
493
|
+
// scaleX/scaleY while x/y stay put — and handle = the EXACT bottom-right
|
|
494
|
+
// corner (the selection chrome draws it un-padded, unlike the padded text
|
|
495
|
+
// box; the grab tolerance lives in the hit radius, not the geometry).
|
|
496
|
+
const backgroundResizeGeometry = (layer) => {
|
|
497
|
+
const r = backgroundLayerDocRect(layer);
|
|
498
|
+
const maxDim = Math.max(r.width, r.height);
|
|
499
|
+
if (!(maxDim > 0))
|
|
500
|
+
return null;
|
|
501
|
+
return {
|
|
502
|
+
pivot: { x: r.x, y: r.y },
|
|
503
|
+
handle: { x: r.x + r.width, y: r.y + r.height },
|
|
504
|
+
minScale: MIN_BACKGROUND_DOC_EXTENT / maxDim,
|
|
505
|
+
maxScale: MAX_BACKGROUND_DOC_EXTENT / maxDim,
|
|
506
|
+
};
|
|
507
|
+
};
|
|
420
508
|
// --- Text-shape resize (corner-scale about the top-left anchor; shared by the
|
|
421
509
|
// native UI-thread drag via DragSelectionConfig AND the web pointer handlers) ---
|
|
422
|
-
// Resize geometry when the (selected) text shape's
|
|
423
|
-
// `world`, else null.
|
|
424
|
-
// bottom-right corner
|
|
425
|
-
//
|
|
510
|
+
// Resize geometry when the (selected) text shape's or background layer's
|
|
511
|
+
// corner handle is under `world`, else null. A text handle sits on the padded
|
|
512
|
+
// selection box's bottom-right corner, a background handle on its exact doc
|
|
513
|
+
// rect corner (see AnnotationCanvasSkia); grab radius matches the measurement
|
|
514
|
+
// endpoint handles either way.
|
|
426
515
|
const findResizeHandleHit = (doc, id, world, zoom) => {
|
|
427
516
|
const s = doc.shapes.find((x) => x.id === id);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
517
|
+
const layer = s ? null : backgroundLayerById(doc, id);
|
|
518
|
+
const geom = s
|
|
519
|
+
? textResizeGeometry(s)
|
|
520
|
+
: layer
|
|
521
|
+
? backgroundResizeGeometry(layer)
|
|
522
|
+
: null;
|
|
431
523
|
if (!geom)
|
|
432
524
|
return null;
|
|
433
525
|
const r2 = (HANDLE_GRAB_PX / zoom) ** 2;
|
|
@@ -435,13 +527,38 @@ const findResizeHandleHit = (doc, id, world, zoom) => {
|
|
|
435
527
|
const dy = world.y - geom.handle.y;
|
|
436
528
|
return dx * dx + dy * dy <= r2 ? geom : null;
|
|
437
529
|
};
|
|
438
|
-
// Scale the
|
|
439
|
-
//
|
|
440
|
-
// the scale
|
|
530
|
+
// Scale the element by the drag (clamped to the geometry's scale range, so it
|
|
531
|
+
// matches the native live preview exactly). The pivot is untouched: a text
|
|
532
|
+
// shape keeps its anchor and bakes the scale into fontSize; a background keeps
|
|
533
|
+
// transform.x/y — its top-left IS the scale pivot — and bakes the scale into
|
|
534
|
+
// scaleX/scaleY (both axes, so the aspect ratio is preserved even on layers
|
|
535
|
+
// synthesized from a legacy 'stretch' fit).
|
|
441
536
|
const resizePatch = (doc, id, delta) => {
|
|
442
537
|
const s = doc.shapes.find((x) => x.id === id);
|
|
443
|
-
if (!s)
|
|
444
|
-
|
|
538
|
+
if (!s) {
|
|
539
|
+
const layer = backgroundLayerById(doc, id);
|
|
540
|
+
if (!layer)
|
|
541
|
+
return null;
|
|
542
|
+
const geom = backgroundResizeGeometry(layer);
|
|
543
|
+
if (!geom)
|
|
544
|
+
return null;
|
|
545
|
+
const scale = resizeScaleFromDrag(geom, delta);
|
|
546
|
+
return {
|
|
547
|
+
ops: [
|
|
548
|
+
{
|
|
549
|
+
op: 'updateBackgroundImage',
|
|
550
|
+
id,
|
|
551
|
+
patch: {
|
|
552
|
+
transform: {
|
|
553
|
+
...layer.transform,
|
|
554
|
+
scaleX: layer.transform.scaleX * scale,
|
|
555
|
+
scaleY: layer.transform.scaleY * scale,
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
],
|
|
560
|
+
};
|
|
561
|
+
}
|
|
445
562
|
const geom = textResizeGeometry(s);
|
|
446
563
|
if (!geom)
|
|
447
564
|
return null;
|
|
@@ -497,6 +614,8 @@ export const createSelectTool = (options = {}) => ({
|
|
|
497
614
|
buildShapeEndpointPatch: shapeEndpointPatch,
|
|
498
615
|
hitTestResizeHandle: findResizeHandleHit,
|
|
499
616
|
buildResizePatch: resizePatch,
|
|
617
|
+
hitTestBackground: findBackgroundHit,
|
|
618
|
+
hitTestSelectedBackground: findSelectedBackgroundHit,
|
|
500
619
|
hitTestRectCorner: findRectCornerHit,
|
|
501
620
|
buildRectCornerPatch: rectCornerPatch,
|
|
502
621
|
hitTestShapeCorner: findShapeCornerHit,
|
|
@@ -508,10 +627,12 @@ export const createSelectTool = (options = {}) => ({
|
|
|
508
627
|
onPointerDown(event, ctx) {
|
|
509
628
|
const { world } = event;
|
|
510
629
|
const zoom = ctx.viewport.state.zoom;
|
|
511
|
-
// Reset the re-tap
|
|
512
|
-
//
|
|
513
|
-
//
|
|
630
|
+
// Reset the re-tap latches; only a body grab of an already-selected text
|
|
631
|
+
// shape / background layer (below) re-arms them. Grabbing a handle or
|
|
632
|
+
// empty canvas leaves them cleared, so neither can leak an edit or a
|
|
633
|
+
// deselect into the next release.
|
|
514
634
|
pendingTextEditId = null;
|
|
635
|
+
pendingBackgroundDeselectId = null;
|
|
515
636
|
// Endpoint/resize handles show only on the selected element — check first,
|
|
516
637
|
// UNLESS the grab is on that element's tile: the tile is the move/slide
|
|
517
638
|
// affordance and must win over a handle sitting under it, so a selected
|
|
@@ -549,7 +670,12 @@ export const createSelectTool = (options = {}) => ({
|
|
|
549
670
|
return {
|
|
550
671
|
kind: 'dragging',
|
|
551
672
|
id: selId,
|
|
552
|
-
|
|
673
|
+
// findResizeHandleHit serves both text shapes and background
|
|
674
|
+
// layers; only the state's bookkeeping cares which one this is
|
|
675
|
+
// (dragPatch keys 'resize' off the mode alone).
|
|
676
|
+
elementKind: backgroundLayerById(ctx.document, selId)
|
|
677
|
+
? 'background'
|
|
678
|
+
: 'shape',
|
|
553
679
|
mode: 'resize',
|
|
554
680
|
start: world,
|
|
555
681
|
delta: { x: 0, y: 0 },
|
|
@@ -584,6 +710,30 @@ export const createSelectTool = (options = {}) => ({
|
|
|
584
710
|
}
|
|
585
711
|
const hit = findHit(ctx.document, world, zoom, ctx.tileViewportScale, ctx.tileScaleFactor);
|
|
586
712
|
if (!hit) {
|
|
713
|
+
// Background layers only get a look-in when every annotation element
|
|
714
|
+
// misses — they can cover the whole document, so elements above always
|
|
715
|
+
// win. An already-selected background grabs as a body drag (and arms
|
|
716
|
+
// the tap-toggle: a release with no movement deselects it).
|
|
717
|
+
const selectedBg = findSelectedBackgroundHit(ctx.document, world, ctx.selection?.ids ?? []);
|
|
718
|
+
if (selectedBg) {
|
|
719
|
+
pendingBackgroundDeselectId = selectedBg.id;
|
|
720
|
+
return {
|
|
721
|
+
kind: 'dragging',
|
|
722
|
+
id: selectedBg.id,
|
|
723
|
+
elementKind: 'background',
|
|
724
|
+
mode: 'move',
|
|
725
|
+
start: world,
|
|
726
|
+
delta: { x: 0, y: 0 },
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
// An unselected background selects only — the gesture is consumed (no
|
|
730
|
+
// pan, no drag): translating it takes a second gesture once selected,
|
|
731
|
+
// the layout tool's two-step model.
|
|
732
|
+
const bg = findBackgroundHit(ctx.document, world);
|
|
733
|
+
if (bg) {
|
|
734
|
+
ctx.setSelection({ ids: [bg.id] });
|
|
735
|
+
return { kind: 'idle' };
|
|
736
|
+
}
|
|
587
737
|
ctx.setSelection(null);
|
|
588
738
|
// Nothing under the pointer: with panOnEmptyDrag the gesture pans the
|
|
589
739
|
// viewport instead of dead-ending, so bare drags navigate the canvas.
|
|
@@ -630,9 +780,12 @@ export const createSelectTool = (options = {}) => ({
|
|
|
630
780
|
x: event.world.x - s.start.x,
|
|
631
781
|
y: event.world.y - s.start.y,
|
|
632
782
|
};
|
|
633
|
-
// Any real movement turns this into a drag, not a re-tap — disarm the
|
|
634
|
-
|
|
783
|
+
// Any real movement turns this into a drag, not a re-tap — disarm the
|
|
784
|
+
// edit and the background deselect toggle.
|
|
785
|
+
if (delta.x !== 0 || delta.y !== 0) {
|
|
635
786
|
pendingTextEditId = null;
|
|
787
|
+
pendingBackgroundDeselectId = null;
|
|
788
|
+
}
|
|
636
789
|
const patch = dragPatch(s, ctx.document, delta, ctx.viewport.state.zoom);
|
|
637
790
|
if (patch)
|
|
638
791
|
ctx.preview(patch);
|
|
@@ -641,14 +794,24 @@ export const createSelectTool = (options = {}) => ({
|
|
|
641
794
|
onPointerUp(_event, ctx, state) {
|
|
642
795
|
const editId = pendingTextEditId;
|
|
643
796
|
pendingTextEditId = null;
|
|
797
|
+
const deselectId = pendingBackgroundDeselectId;
|
|
798
|
+
pendingBackgroundDeselectId = null;
|
|
644
799
|
const s = state;
|
|
645
|
-
// A moved selection commits its drag and is never a tap-to-edit.
|
|
800
|
+
// A moved selection commits its drag and is never a tap-to-edit/-toggle.
|
|
646
801
|
if (s?.kind === 'dragging' && (s.delta.x !== 0 || s.delta.y !== 0)) {
|
|
647
802
|
const patch = dragPatch(s, ctx.document, s.delta, ctx.viewport.state.zoom);
|
|
648
803
|
if (patch)
|
|
649
804
|
ctx.commit(patch);
|
|
650
805
|
return;
|
|
651
806
|
}
|
|
807
|
+
// No movement on an already-selected background: the tap toggles it back
|
|
808
|
+
// off. `deselectId` was latched on the down for the same synchronous
|
|
809
|
+
// native down+up reason as `editId` below.
|
|
810
|
+
if (deselectId) {
|
|
811
|
+
if (ctx.selection?.ids.includes(deselectId))
|
|
812
|
+
ctx.setSelection(null);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
652
815
|
// No movement: re-tapping an already-selected text shape re-opens its
|
|
653
816
|
// editor (the same edit flow as tapping it with the text tool, via the
|
|
654
817
|
// shared editTextShape). `editId` was latched on the down, so this survives
|
|
@@ -661,6 +824,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
661
824
|
},
|
|
662
825
|
onCancel(_state, ctx) {
|
|
663
826
|
pendingTextEditId = null;
|
|
827
|
+
pendingBackgroundDeselectId = null;
|
|
664
828
|
ctx.preview({ ops: [] });
|
|
665
829
|
},
|
|
666
830
|
hitTest(element, p) {
|
|
@@ -46,6 +46,7 @@ export interface AnnotationCanvasHandle {
|
|
|
46
46
|
placeGroupHeaderAtCenter(ref: {
|
|
47
47
|
groupId: string;
|
|
48
48
|
}): AnnotationElementId;
|
|
49
|
+
setGroupHeaderCollapsed(id: AnnotationElementId, collapsed: boolean): void;
|
|
49
50
|
deleteSelected(): void;
|
|
50
51
|
}
|
|
51
52
|
export interface UseAnnotationCanvasStateProps {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { applyPatch, invertPatch, DEFAULT_LAYER_ID, } from '../../types/annotation.js';
|
|
3
3
|
import { resolveTileScaleFactor, stampTileDims, STAMP_TILE_SIZE, tileDocSizeForFactor, tileScaleFromDocSize, } from './stampLayout.js';
|
|
4
|
-
import { createViewportApi, fitRectToScreen,
|
|
4
|
+
import { createViewportApi, fitRectToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, } from './viewport.js';
|
|
5
|
+
import { backgroundLayerDocRect, backgroundLayersOf, backgroundLayersUnionRect, } from './backgroundLayers.js';
|
|
5
6
|
import { buildRemoveMeasurementOps, recomputeAnchor, rectCenter, DEFAULT_LINE_POS, } from './measurementGeometry.js';
|
|
6
7
|
// The viewport that frames the document's content inside a screen rect of the
|
|
7
|
-
// canvas box. When
|
|
8
|
-
//
|
|
9
|
-
// never the native-resolution top-left crop a 1:1 viewport
|
|
10
|
-
// image); otherwise we fit the document rect. Placed tiles
|
|
8
|
+
// canvas box. When background images are present we fit the union of their
|
|
9
|
+
// rendered rects (so every image shows whole, filling the rect regardless of
|
|
10
|
+
// pixel resolution — never the native-resolution top-left crop a 1:1 viewport
|
|
11
|
+
// gives a high-res image); otherwise we fit the document rect. Placed tiles
|
|
12
|
+
// count as content
|
|
11
13
|
// too: authors routinely drop them AROUND the image (dimension labels sit
|
|
12
14
|
// outside the object they measure), and a fit framed on the image alone opens
|
|
13
15
|
// with those tiles off-screen — so the rect is widened to the union of the
|
|
@@ -22,10 +24,12 @@ const computeContentFitRect = (canvas, rect) => {
|
|
|
22
24
|
if (!(rect.width > 0) || !(rect.height > 0))
|
|
23
25
|
return DEFAULT_VIEWPORT;
|
|
24
26
|
const { viewport } = canvas;
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const baseRect = backgroundLayersUnionRect(backgroundLayersOf(viewport)) ?? {
|
|
28
|
+
x: 0,
|
|
29
|
+
y: 0,
|
|
30
|
+
width: viewport.width,
|
|
31
|
+
height: viewport.height,
|
|
32
|
+
};
|
|
29
33
|
let minX = baseRect.x;
|
|
30
34
|
let minY = baseRect.y;
|
|
31
35
|
let maxX = baseRect.x + baseRect.width;
|
|
@@ -64,20 +68,25 @@ const computeContentFitRect = (canvas, rect) => {
|
|
|
64
68
|
};
|
|
65
69
|
};
|
|
66
70
|
const computeContentFit = (canvas, width, height) => computeContentFitRect(canvas, { x: 0, y: 0, width, height });
|
|
67
|
-
// Content-fit zoom of the DOCUMENT alone — the background
|
|
68
|
-
// rect when present, the document rect otherwise, deliberately
|
|
69
|
-
// placed tiles — into the full canvas box. This is the stable
|
|
70
|
-
// for tile sizing (see tileViewportScale below): at this zoom
|
|
71
|
-
// at exactly its slider size. computeContentFitRect's
|
|
72
|
-
// would make the reference move whenever a tile is
|
|
73
|
-
// (every tile on the canvas would resize); the
|
|
71
|
+
// Content-fit zoom of the DOCUMENT alone — the FIRST background layer's
|
|
72
|
+
// rendered rect when present, the document rect otherwise, deliberately
|
|
73
|
+
// ignoring placed tiles — into the full canvas box. This is the stable
|
|
74
|
+
// reference zoom for tile sizing (see tileViewportScale below): at this zoom
|
|
75
|
+
// a tile renders at exactly its slider size. computeContentFitRect's
|
|
76
|
+
// tile-extended union would make the reference move whenever a tile is
|
|
77
|
+
// dragged outside the image (every tile on the canvas would resize); the
|
|
78
|
+
// document rect can't. The reference is pinned to the first layer — never
|
|
79
|
+
// the layer union — for the same reason: adding, dragging, or scaling a
|
|
80
|
+
// SECOND image must not silently resize (and re-hit-test) every placed tile
|
|
81
|
+
// on the canvas. For a single-image doc the first layer's rect is exactly
|
|
82
|
+
// the legacy fit rect, so existing canvases keep their tile sizes.
|
|
74
83
|
const computeBaseFitZoom = (canvas, width, height) => {
|
|
75
84
|
if (!(width > 0) || !(height > 0))
|
|
76
85
|
return 1;
|
|
77
86
|
const { viewport } = canvas;
|
|
78
|
-
const
|
|
79
|
-
const baseRect =
|
|
80
|
-
?
|
|
87
|
+
const first = backgroundLayersOf(viewport)[0];
|
|
88
|
+
const baseRect = first
|
|
89
|
+
? backgroundLayerDocRect(first)
|
|
81
90
|
: { x: 0, y: 0, width: viewport.width, height: viewport.height };
|
|
82
91
|
return fitRectToScreen(baseRect, width, height).zoom;
|
|
83
92
|
};
|
|
@@ -120,7 +129,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
120
129
|
useEffect(() => {
|
|
121
130
|
if (didInitialFitRef.current)
|
|
122
131
|
return;
|
|
123
|
-
if (
|
|
132
|
+
if (backgroundLayersOf(canvas.viewport).length === 0)
|
|
124
133
|
return;
|
|
125
134
|
if (!(width > 0) || !(height > 0))
|
|
126
135
|
return;
|
|
@@ -652,6 +661,17 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
652
661
|
c.setSelection({ ids: [placed.id] });
|
|
653
662
|
return placed.id;
|
|
654
663
|
},
|
|
664
|
+
setGroupHeaderCollapsed(id, collapsed) {
|
|
665
|
+
const c = ctxRef.current;
|
|
666
|
+
const target = c.document.placedMeasurements.find((m) => m.id === id);
|
|
667
|
+
if (!target?.isGroupHeader)
|
|
668
|
+
return;
|
|
669
|
+
if (!!target.collapsed === collapsed)
|
|
670
|
+
return;
|
|
671
|
+
c.commit({
|
|
672
|
+
ops: [{ op: 'updateMeasurement', id, patch: { collapsed } }],
|
|
673
|
+
});
|
|
674
|
+
},
|
|
655
675
|
deleteSelected() {
|
|
656
676
|
const c = ctxRef.current;
|
|
657
677
|
const ids = c.selection?.ids;
|
|
@@ -29,13 +29,14 @@ export const fitToScreen = (docWidth, docHeight, screenWidth, screenHeight, padd
|
|
|
29
29
|
pan: { x: -offsetX / zoom, y: -offsetY / zoom },
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
|
-
// The doc-space rectangle a background image occupies,
|
|
33
|
-
// intrinsic pixel size, the document dimensions, and the
|
|
34
|
-
//
|
|
35
|
-
// so "fit the image to the screen" lines up exactly with what
|
|
32
|
+
// The doc-space rectangle a legacy fit-placed background image occupies,
|
|
33
|
+
// given the image's intrinsic pixel size, the document dimensions, and the
|
|
34
|
+
// fit mode. backgroundLayersOf synthesizes the legacy slot's layer transform
|
|
35
|
+
// from this rect, so "fit the image to the screen" lines up exactly with what
|
|
36
|
+
// is drawn.
|
|
36
37
|
export const imageDocRect = (imgW, imgH, docW, docH, fit = 'contain') => {
|
|
37
|
-
// Unknown/zero image dimensions (or an explicit stretch) fill the doc
|
|
38
|
-
//
|
|
38
|
+
// Unknown/zero image dimensions (or an explicit stretch) fill the doc,
|
|
39
|
+
// avoiding NaN geometry from a division by zero.
|
|
39
40
|
if (!(imgW > 0) || !(imgH > 0) || fit === 'stretch') {
|
|
40
41
|
return { x: 0, y: 0, width: docW, height: docH };
|
|
41
42
|
}
|
|
@@ -75,7 +75,7 @@ export interface AnnotationDataProvider {
|
|
|
75
75
|
list(scope: AnnotationScope, onNext: (files: AnnotationFileSummary[]) => void, onError?: (err: Error) => void): Unsubscribe;
|
|
76
76
|
subscribeGroupMeasurements(scope: JobGroupScope, onNext: (measurements: Measurement[]) => void, onError?: (err: Error) => void): Unsubscribe;
|
|
77
77
|
subscribeJobMeasurements(scope: JobScope, onNext: (measurements: Measurement[]) => void, onError?: (err: Error) => void): Unsubscribe;
|
|
78
|
-
uploadImage(scope: AnnotationScope, fileId: string, role:
|
|
78
|
+
uploadImage(scope: AnnotationScope, fileId: string, role: string, blob: ImageBlob): Promise<UploadedImageRef>;
|
|
79
79
|
getImageUrl(scope: AnnotationScope, fileId: string, storagePath: string): Promise<string>;
|
|
80
80
|
deleteImage(scope: AnnotationScope, fileId: string, storagePath: string): Promise<void>;
|
|
81
81
|
}
|