@reekon-tools/boldr-utils 1.6.31 → 1.6.32
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PlacedMeasurementRef } from '../../../types/annotation.js';
|
|
1
|
+
import type { PlacedMeasurementRef, StrokeCap } from '../../../types/annotation.js';
|
|
2
2
|
import type { Tool } from '../Tool.js';
|
|
3
3
|
export type MeasurementToolPlacement = 'line' | 'rectangle' | 'none';
|
|
4
4
|
export interface MeasurementToolOptions {
|
|
@@ -11,5 +11,10 @@ export interface MeasurementToolOptions {
|
|
|
11
11
|
selectToolId?: string;
|
|
12
12
|
onAutoSwitch?: (toToolId: string) => void;
|
|
13
13
|
onPlaced?: (measurement: PlacedMeasurementRef) => void;
|
|
14
|
+
color?: string;
|
|
15
|
+
width?: number;
|
|
16
|
+
cap?: StrokeCap;
|
|
17
|
+
startCap?: StrokeCap;
|
|
18
|
+
dash?: boolean;
|
|
14
19
|
}
|
|
15
20
|
export declare const createMeasurementTool: (options?: MeasurementToolOptions) => Tool;
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { DEFAULT_LAYER_ID } from '../../../types/annotation.js';
|
|
2
2
|
import { DEFAULT_LINE_POS, recomputeAnchor, rectCenter, } from '../measurementGeometry.js';
|
|
3
|
+
// The `line*` style fields for a placed measurement, built from the tool's
|
|
4
|
+
// style options. Undefined fields are dropped (never emitted) so the render
|
|
5
|
+
// defaults apply and Firestore never sees an `undefined` value.
|
|
6
|
+
const lineStyleFields = (placement, style) => {
|
|
7
|
+
if (placement === 'none')
|
|
8
|
+
return {};
|
|
9
|
+
const out = {};
|
|
10
|
+
if (style.color !== undefined)
|
|
11
|
+
out.lineColor = style.color;
|
|
12
|
+
if (style.width !== undefined)
|
|
13
|
+
out.lineWidth = style.width;
|
|
14
|
+
if (style.dash !== undefined)
|
|
15
|
+
out.lineDash = style.dash;
|
|
16
|
+
// End/start caps are only meaningful on the open `line` path.
|
|
17
|
+
if (placement === 'line') {
|
|
18
|
+
if (style.cap !== undefined)
|
|
19
|
+
out.lineCap = style.cap;
|
|
20
|
+
if (style.startCap !== undefined)
|
|
21
|
+
out.lineStartCap = style.startCap;
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
};
|
|
3
25
|
let counter = 0;
|
|
4
26
|
const makeId = () => `annotation-${Date.now().toString(36)}-${(counter++).toString(36)}`;
|
|
5
27
|
const firstLayerId = (doc) => doc.layers[0]?.id ?? DEFAULT_LAYER_ID;
|
|
@@ -8,13 +30,14 @@ const firstLayerId = (doc) => doc.layers[0]?.id ?? DEFAULT_LAYER_ID;
|
|
|
8
30
|
// placeAnnotationAtCenter so a drawn annotation is identical to a (legacy)
|
|
9
31
|
// center-placed one once committed.
|
|
10
32
|
const buildMeasurement = (opts) => {
|
|
11
|
-
const { id, layerId, placement, linePos, a, b } = opts;
|
|
33
|
+
const { id, layerId, placement, linePos, a, b, style } = opts;
|
|
12
34
|
const base = {
|
|
13
35
|
id,
|
|
14
36
|
layerId,
|
|
15
37
|
showLabel: true,
|
|
16
38
|
showValue: true,
|
|
17
39
|
createdAt: Date.now(),
|
|
40
|
+
...lineStyleFields(placement, style),
|
|
18
41
|
};
|
|
19
42
|
if (placement === 'rectangle') {
|
|
20
43
|
const rect = { a, b };
|
|
@@ -51,6 +74,13 @@ export const createMeasurementTool = (options = {}) => {
|
|
|
51
74
|
const minDragPx = options.minDragPx ?? 4;
|
|
52
75
|
const autoSwitchToSelect = options.autoSwitchToSelect ?? true;
|
|
53
76
|
const selectToolId = options.selectToolId ?? 'select';
|
|
77
|
+
const style = {
|
|
78
|
+
color: options.color,
|
|
79
|
+
width: options.width,
|
|
80
|
+
cap: options.cap,
|
|
81
|
+
startCap: options.startCap,
|
|
82
|
+
dash: options.dash,
|
|
83
|
+
};
|
|
54
84
|
const place = (ctx, measurement) => {
|
|
55
85
|
ctx.commit({ ops: [{ op: 'addMeasurement', measurement }] });
|
|
56
86
|
options.onPlaced?.(measurement);
|
|
@@ -78,6 +108,7 @@ export const createMeasurementTool = (options = {}) => {
|
|
|
78
108
|
linePos,
|
|
79
109
|
a: event.world,
|
|
80
110
|
b: event.world,
|
|
111
|
+
style,
|
|
81
112
|
}));
|
|
82
113
|
},
|
|
83
114
|
};
|
|
@@ -108,6 +139,7 @@ export const createMeasurementTool = (options = {}) => {
|
|
|
108
139
|
linePos,
|
|
109
140
|
a: s.startWorld,
|
|
110
141
|
b: event.world,
|
|
142
|
+
style,
|
|
111
143
|
});
|
|
112
144
|
ctx.preview({ ops: [{ op: 'addMeasurement', measurement }] });
|
|
113
145
|
return { ...s, moved: true };
|
|
@@ -130,6 +162,7 @@ export const createMeasurementTool = (options = {}) => {
|
|
|
130
162
|
linePos,
|
|
131
163
|
a: s.startWorld,
|
|
132
164
|
b: event.world,
|
|
165
|
+
style,
|
|
133
166
|
}));
|
|
134
167
|
},
|
|
135
168
|
onCancel(_state, ctx) {
|
|
@@ -16,6 +16,7 @@ export interface AnnotationCanvasHandle {
|
|
|
16
16
|
}): void;
|
|
17
17
|
setAnnotationType(id: AnnotationElementId, type: MeasurementPlacement): void;
|
|
18
18
|
centerOnLine(id: AnnotationElementId): void;
|
|
19
|
+
setLinePos(id: AnnotationElementId, linePos: number): void;
|
|
19
20
|
removeMeasurementValue(id: AnnotationElementId): void;
|
|
20
21
|
associateMeasurement(id: AnnotationElementId, ref: MeasurementRef): void;
|
|
21
22
|
bindColumn(id: AnnotationElementId, binding: {
|
|
@@ -195,6 +195,37 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
195
195
|
useEffect(() => {
|
|
196
196
|
if (!imperativeRef)
|
|
197
197
|
return;
|
|
198
|
+
// Shared body for centerOnLine / setLinePos: ensure the annotation is a
|
|
199
|
+
// 'line' (synthesizing a default horizontal line around the anchor if it
|
|
200
|
+
// has none) and pin its tile at `linePos` along that line.
|
|
201
|
+
const applyLinePos = (id, linePos) => {
|
|
202
|
+
const c = ctxRef.current;
|
|
203
|
+
const m = c.document.placedMeasurements.find((x) => x.id === id);
|
|
204
|
+
if (!m)
|
|
205
|
+
return;
|
|
206
|
+
let line = m.line;
|
|
207
|
+
if (!line) {
|
|
208
|
+
const len = Math.min(400, Math.max(120, canvas.viewport.width * 0.25));
|
|
209
|
+
line = {
|
|
210
|
+
a: { x: m.anchor.x - len / 2, y: m.anchor.y },
|
|
211
|
+
b: { x: m.anchor.x + len / 2, y: m.anchor.y },
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
c.commit({
|
|
215
|
+
ops: [
|
|
216
|
+
{
|
|
217
|
+
op: 'updateMeasurement',
|
|
218
|
+
id,
|
|
219
|
+
patch: {
|
|
220
|
+
placement: 'line',
|
|
221
|
+
line,
|
|
222
|
+
linePos,
|
|
223
|
+
anchor: recomputeAnchor(line, 'line', linePos, m.anchor),
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
};
|
|
198
229
|
imperativeRef.current = {
|
|
199
230
|
undo() {
|
|
200
231
|
const entry = undoStackRef.current.pop();
|
|
@@ -340,34 +371,10 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
340
371
|
});
|
|
341
372
|
},
|
|
342
373
|
centerOnLine(id) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// Same default-line synthesis as setAnnotationType('line') so centering
|
|
348
|
-
// a floating tile also gives it a line to sit on.
|
|
349
|
-
let line = m.line;
|
|
350
|
-
if (!line) {
|
|
351
|
-
const len = Math.min(400, Math.max(120, canvas.viewport.width * 0.25));
|
|
352
|
-
line = {
|
|
353
|
-
a: { x: m.anchor.x - len / 2, y: m.anchor.y },
|
|
354
|
-
b: { x: m.anchor.x + len / 2, y: m.anchor.y },
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
c.commit({
|
|
358
|
-
ops: [
|
|
359
|
-
{
|
|
360
|
-
op: 'updateMeasurement',
|
|
361
|
-
id,
|
|
362
|
-
patch: {
|
|
363
|
-
placement: 'line',
|
|
364
|
-
line,
|
|
365
|
-
linePos: 0.5,
|
|
366
|
-
anchor: recomputeAnchor(line, 'line', 0.5, m.anchor),
|
|
367
|
-
},
|
|
368
|
-
},
|
|
369
|
-
],
|
|
370
|
-
});
|
|
374
|
+
applyLinePos(id, 0.5);
|
|
375
|
+
},
|
|
376
|
+
setLinePos(id, linePos) {
|
|
377
|
+
applyLinePos(id, linePos);
|
|
371
378
|
},
|
|
372
379
|
removeMeasurementValue(id) {
|
|
373
380
|
const c = ctxRef.current;
|