@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
- const c = ctxRef.current;
344
- const m = c.document.placedMeasurements.find((x) => x.id === id);
345
- if (!m)
346
- return;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.6.31",
3
+ "version": "1.6.32",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",