@reekon-tools/boldr-utils 1.6.11 → 1.6.13
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/{canvas → annotation/canvas}/AnnotationCanvasInner.d.ts +5 -3
- package/dist/{canvas → annotation/canvas}/AnnotationCanvasInner.js +36 -17
- package/dist/{canvas → annotation/canvas}/AnnotationCanvasInner.native.d.ts +5 -3
- package/dist/annotation/canvas/AnnotationCanvasInner.native.js +810 -0
- package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +61 -0
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +158 -0
- package/dist/annotation/canvas/Tool.d.ts +77 -0
- package/dist/{canvas → annotation/canvas}/elements/BackgroundImageElement.d.ts +2 -2
- package/dist/{canvas → annotation/canvas}/elements/BackgroundImageElement.js +17 -7
- package/dist/annotation/canvas/elements/ShapeElement.d.ts +7 -0
- package/dist/{canvas → annotation/canvas}/elements/ShapeElement.js +33 -5
- package/dist/annotation/canvas/elements/StrokeElement.d.ts +7 -0
- package/dist/annotation/canvas/elements/StrokeElement.js +45 -0
- package/dist/annotation/canvas/measurementGeometry.d.ts +43 -0
- package/dist/annotation/canvas/measurementGeometry.js +111 -0
- package/dist/{canvas → annotation/canvas}/measurementPicker.d.ts +1 -1
- package/dist/{canvas → annotation/canvas}/measurementStampOverlay.d.ts +2 -2
- package/dist/annotation/canvas/stampLayout.d.ts +1 -0
- package/dist/annotation/canvas/stampLayout.js +11 -0
- package/dist/annotation/canvas/strokeGeometry.d.ts +5 -0
- package/dist/annotation/canvas/strokeGeometry.js +41 -0
- package/dist/annotation/canvas/textGeometry.d.ts +24 -0
- package/dist/annotation/canvas/textGeometry.js +110 -0
- package/dist/{canvas → annotation/canvas}/tools/measurementStampTool.d.ts +1 -1
- package/dist/{canvas → annotation/canvas}/tools/measurementStampTool.js +1 -1
- package/dist/{canvas → annotation/canvas}/tools/panTool.js +3 -0
- package/dist/{canvas → annotation/canvas}/tools/penTool.d.ts +3 -1
- package/dist/{canvas → annotation/canvas}/tools/penTool.js +34 -5
- package/dist/annotation/canvas/tools/selectTool.js +446 -0
- package/dist/annotation/canvas/tools/textTool.d.ts +12 -0
- package/dist/annotation/canvas/tools/textTool.js +78 -0
- package/dist/{canvas → annotation/canvas}/useAnnotationCanvasState.d.ts +11 -3
- package/dist/{canvas → annotation/canvas}/useAnnotationCanvasState.js +142 -2
- package/dist/{canvas → annotation/canvas}/viewport.d.ts +1 -1
- package/dist/{data → annotation/data}/AnnotationDataProvider.d.ts +1 -1
- package/dist/{data → annotation/data}/InMemoryAnnotationProvider.d.ts +1 -1
- package/dist/{data → annotation/data}/InMemoryAnnotationProvider.js +1 -1
- package/dist/{data → annotation/data}/canvasPersistence.d.ts +1 -1
- package/dist/{data → annotation/data}/canvasPersistence.js +1 -1
- package/dist/annotation/data/coalescedRunner.d.ts +1 -0
- package/dist/annotation/data/coalescedRunner.js +48 -0
- package/dist/{data → annotation/data}/hooks/useAnnotationCanvasDoc.d.ts +1 -1
- package/dist/{data → annotation/data}/hooks/useAnnotationCanvasDoc.js +37 -16
- package/dist/exports.d.ts +23 -19
- package/dist/exports.js +18 -14
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.native.d.ts +1 -1
- package/dist/index.native.js +1 -1
- package/dist/types/annotation.d.ts +22 -3
- package/dist/types/firestore.d.ts +0 -1
- package/dist/{hooks → utils}/useParseMeasurement.js +1 -1
- package/package.json +1 -1
- 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/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/StrokeElement.d.ts +0 -7
- package/dist/canvas/elements/StrokeElement.js +0 -18
- package/dist/canvas/stampLayout.d.ts +0 -5
- package/dist/canvas/stampLayout.js +0 -14
- package/dist/canvas/tools/selectTool.js +0 -182
- package/dist/utils/evaluateFormula.d.ts +0 -20
- package/dist/utils/evaluateFormula.js +0 -31
- /package/dist/{canvas → annotation/canvas}/AnnotationCanvas.d.ts +0 -0
- /package/dist/{canvas → annotation/canvas}/AnnotationCanvas.js +0 -0
- /package/dist/{canvas → annotation/canvas}/AnnotationCanvas.native.d.ts +0 -0
- /package/dist/{canvas → annotation/canvas}/AnnotationCanvas.native.js +0 -0
- /package/dist/{canvas → annotation/canvas}/Tool.js +0 -0
- /package/dist/{canvas → annotation/canvas}/measurementPicker.js +0 -0
- /package/dist/{canvas → annotation/canvas}/measurementStampOverlay.js +0 -0
- /package/dist/{canvas → annotation/canvas}/pointerAdapter.d.ts +0 -0
- /package/dist/{canvas → annotation/canvas}/pointerAdapter.js +0 -0
- /package/dist/{canvas → annotation/canvas}/tools/panTool.d.ts +0 -0
- /package/dist/{canvas → annotation/canvas}/tools/selectTool.d.ts +0 -0
- /package/dist/{canvas → annotation/canvas}/viewport.js +0 -0
- /package/dist/{data → annotation/data}/AnnotationDataContext.d.ts +0 -0
- /package/dist/{data → annotation/data}/AnnotationDataContext.js +0 -0
- /package/dist/{data → annotation/data}/AnnotationDataProvider.js +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationDoc.d.ts +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationDoc.js +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationList.d.ts +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationList.js +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationMutations.d.ts +0 -0
- /package/dist/{data → annotation/data}/hooks/useAnnotationMutations.js +0 -0
- /package/dist/{hooks → utils}/useParseMeasurement.d.ts +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import { applyPatch, invertPatch, DEFAULT_LAYER_ID, } from '
|
|
2
|
+
import { applyPatch, invertPatch, DEFAULT_LAYER_ID, } from '../../types/annotation.js';
|
|
3
3
|
import { createViewportApi, panBy, zoomAt, DEFAULT_VIEWPORT, } from './viewport.js';
|
|
4
|
+
import { recomputeAnchor, rectCenter, DEFAULT_LINE_POS, } from './measurementGeometry.js';
|
|
4
5
|
// Platform-agnostic state machine for the annotation canvas. Web and native
|
|
5
6
|
// inners share this hook; each wraps it with platform-specific event
|
|
6
7
|
// capture and JSX (div + DOM events vs. GestureDetector + RN Views).
|
|
7
8
|
export const useAnnotationCanvasState = (props) => {
|
|
8
|
-
const { canvas, onCommit, tools, activeToolId, selection, onSelectionChange, measurements, pickMeasurement, width, height, initialViewport, imperativeRef, } = props;
|
|
9
|
+
const { canvas, onCommit, tools, activeToolId, selection, onSelectionChange, measurements, pickMeasurement, requestTextInput, width, height, initialViewport, imperativeRef, } = props;
|
|
9
10
|
const [viewport, setViewport] = useState(initialViewport ?? DEFAULT_VIEWPORT);
|
|
10
11
|
const [toolState, setToolState] = useState(undefined);
|
|
11
12
|
const [previewPatch, setPreviewPatch] = useState(null);
|
|
@@ -40,6 +41,9 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
40
41
|
requestPickMeasurement() {
|
|
41
42
|
return pickMeasurement ? pickMeasurement() : Promise.resolve(null);
|
|
42
43
|
},
|
|
44
|
+
requestTextInput(options) {
|
|
45
|
+
return requestTextInput ? requestTextInput(options) : Promise.resolve(null);
|
|
46
|
+
},
|
|
43
47
|
applyPan(deltaScreen) {
|
|
44
48
|
setViewport((v) => panBy(v, deltaScreen));
|
|
45
49
|
},
|
|
@@ -53,6 +57,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
53
57
|
onCommit,
|
|
54
58
|
onSelectionChange,
|
|
55
59
|
pickMeasurement,
|
|
60
|
+
requestTextInput,
|
|
56
61
|
]);
|
|
57
62
|
// Live ctx for imperative handle methods (which are created in an effect and
|
|
58
63
|
// would otherwise capture a stale ctx/viewport).
|
|
@@ -164,6 +169,140 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
164
169
|
// responsible for switching to its move/select tool).
|
|
165
170
|
c.setSelection({ ids: [placed.id] });
|
|
166
171
|
},
|
|
172
|
+
placeAnnotationAtCenter(opts) {
|
|
173
|
+
const c = ctxRef.current;
|
|
174
|
+
const center = c.viewport.screenToWorld({
|
|
175
|
+
x: width / 2,
|
|
176
|
+
y: height / 2,
|
|
177
|
+
});
|
|
178
|
+
// Default line length: a quarter of the doc width, clamped to a sane
|
|
179
|
+
// range so it's a grabbable size at any canvas scale.
|
|
180
|
+
const len = opts?.defaultLengthDoc ??
|
|
181
|
+
Math.min(400, Math.max(120, canvas.viewport.width * 0.25));
|
|
182
|
+
const line = {
|
|
183
|
+
a: { x: center.x - len / 2, y: center.y },
|
|
184
|
+
b: { x: center.x + len / 2, y: center.y },
|
|
185
|
+
};
|
|
186
|
+
const placed = {
|
|
187
|
+
id: `annotation-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`,
|
|
188
|
+
layerId: c.document.layers[0]?.id ?? DEFAULT_LAYER_ID,
|
|
189
|
+
placement: 'line',
|
|
190
|
+
line,
|
|
191
|
+
linePos: DEFAULT_LINE_POS,
|
|
192
|
+
// Center of the line; recomputeAnchor keeps this in sync on edits.
|
|
193
|
+
anchor: recomputeAnchor(line, 'line', DEFAULT_LINE_POS, center),
|
|
194
|
+
showLabel: true,
|
|
195
|
+
showValue: true,
|
|
196
|
+
createdAt: Date.now(),
|
|
197
|
+
};
|
|
198
|
+
c.commit({ ops: [{ op: 'addMeasurement', measurement: placed }] });
|
|
199
|
+
c.setSelection({ ids: [placed.id] });
|
|
200
|
+
},
|
|
201
|
+
setAnnotationType(id, type) {
|
|
202
|
+
const c = ctxRef.current;
|
|
203
|
+
const m = c.document.placedMeasurements.find((x) => x.id === id);
|
|
204
|
+
if (!m)
|
|
205
|
+
return;
|
|
206
|
+
if (type === 'rectangle') {
|
|
207
|
+
// Reuse the existing rect if the annotation had one; otherwise
|
|
208
|
+
// synthesize a square centered on the anchor (same size clamp as
|
|
209
|
+
// the default line) so the tile doesn't jump.
|
|
210
|
+
const side = Math.min(400, Math.max(120, canvas.viewport.width * 0.25));
|
|
211
|
+
const rect = m.rect ?? {
|
|
212
|
+
a: { x: m.anchor.x - side / 2, y: m.anchor.y - side / 2 },
|
|
213
|
+
b: { x: m.anchor.x + side / 2, y: m.anchor.y + side / 2 },
|
|
214
|
+
};
|
|
215
|
+
c.commit({
|
|
216
|
+
ops: [
|
|
217
|
+
{
|
|
218
|
+
op: 'updateMeasurement',
|
|
219
|
+
id,
|
|
220
|
+
patch: {
|
|
221
|
+
placement: 'rectangle',
|
|
222
|
+
rect,
|
|
223
|
+
anchor: rectCenter(rect),
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (type === 'line') {
|
|
231
|
+
let line = m.line;
|
|
232
|
+
if (!line) {
|
|
233
|
+
const len = Math.min(400, Math.max(120, canvas.viewport.width * 0.25));
|
|
234
|
+
line = {
|
|
235
|
+
a: { x: m.anchor.x - len / 2, y: m.anchor.y },
|
|
236
|
+
b: { x: m.anchor.x + len / 2, y: m.anchor.y },
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const linePos = m.linePos ?? DEFAULT_LINE_POS;
|
|
240
|
+
c.commit({
|
|
241
|
+
ops: [
|
|
242
|
+
{
|
|
243
|
+
op: 'updateMeasurement',
|
|
244
|
+
id,
|
|
245
|
+
patch: {
|
|
246
|
+
placement: 'line',
|
|
247
|
+
line,
|
|
248
|
+
linePos,
|
|
249
|
+
anchor: recomputeAnchor(line, 'line', linePos, m.anchor),
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// 'none' — bare stamp. Keep the anchor (and the line data, which simply
|
|
257
|
+
// stops rendering); switching back to 'line' restores it.
|
|
258
|
+
c.commit({
|
|
259
|
+
ops: [{ op: 'updateMeasurement', id, patch: { placement: 'none' } }],
|
|
260
|
+
});
|
|
261
|
+
},
|
|
262
|
+
associateMeasurement(id, ref) {
|
|
263
|
+
const c = ctxRef.current;
|
|
264
|
+
c.commit({
|
|
265
|
+
ops: [
|
|
266
|
+
{
|
|
267
|
+
op: 'updateMeasurement',
|
|
268
|
+
id,
|
|
269
|
+
patch: {
|
|
270
|
+
measurementId: ref.measurementId,
|
|
271
|
+
measurementPath: ref.measurementPath,
|
|
272
|
+
groupId: ref.groupId,
|
|
273
|
+
labelOverride: ref.label,
|
|
274
|
+
unitOverride: ref.unit,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
});
|
|
279
|
+
},
|
|
280
|
+
deleteSelected() {
|
|
281
|
+
const c = ctxRef.current;
|
|
282
|
+
const ids = c.selection?.ids;
|
|
283
|
+
if (!ids || ids.length === 0)
|
|
284
|
+
return;
|
|
285
|
+
const idSet = new Set(ids);
|
|
286
|
+
const doc = c.document;
|
|
287
|
+
// Build one remove op per selected element, dispatched by which
|
|
288
|
+
// collection owns the id. A single multi-op commit makes the whole
|
|
289
|
+
// deletion one undo step (inverse re-adds each element).
|
|
290
|
+
const ops = [
|
|
291
|
+
...doc.strokes
|
|
292
|
+
.filter((s) => idSet.has(s.id))
|
|
293
|
+
.map((s) => ({ op: 'removeStroke', id: s.id })),
|
|
294
|
+
...doc.shapes
|
|
295
|
+
.filter((s) => idSet.has(s.id))
|
|
296
|
+
.map((s) => ({ op: 'removeShape', id: s.id })),
|
|
297
|
+
...doc.placedMeasurements
|
|
298
|
+
.filter((m) => idSet.has(m.id))
|
|
299
|
+
.map((m) => ({ op: 'removeMeasurement', id: m.id })),
|
|
300
|
+
];
|
|
301
|
+
if (ops.length === 0)
|
|
302
|
+
return;
|
|
303
|
+
c.commit({ ops });
|
|
304
|
+
c.setSelection(null);
|
|
305
|
+
},
|
|
167
306
|
};
|
|
168
307
|
return () => {
|
|
169
308
|
if (imperativeRef)
|
|
@@ -206,5 +345,6 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
206
345
|
dispatchPointerCancel,
|
|
207
346
|
pan,
|
|
208
347
|
zoom,
|
|
348
|
+
setViewport,
|
|
209
349
|
};
|
|
210
350
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Measurement } from '
|
|
1
|
+
import { type Measurement } from '../../types/firestore.js';
|
|
2
2
|
import { type AnnotationDataProvider, type AnnotationFile, type AnnotationFileSummary, type ImageBlob, type JobGroupScope, type JobScope, type Patch, type Unsubscribe, type UploadedImageRef } from './AnnotationDataProvider.js';
|
|
3
3
|
export declare class InMemoryAnnotationProvider implements AnnotationDataProvider {
|
|
4
4
|
private docs;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FileUploadType } from '
|
|
1
|
+
import { FileUploadType } from '../../types/firestore.js';
|
|
2
2
|
import { isFieldOp, } from './AnnotationDataProvider.js';
|
|
3
3
|
const scopeKey = (s) => `${s.orgId}/${s.projectId}/${s.jobId}/${s.groupId}`;
|
|
4
4
|
const jobKey = (s) => `${s.orgId}/${s.projectId}/${s.jobId}`;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { type AnnotationCanvasState, type AnnotationViewport } from '
|
|
1
|
+
import { type AnnotationCanvasState, type AnnotationViewport } from '../../types/annotation.js';
|
|
2
2
|
import type { AnnotationFile } from './AnnotationDataProvider.js';
|
|
3
3
|
export declare const hydrateCanvasState: (file: AnnotationFile | null, fallbackViewport?: Partial<AnnotationViewport>) => AnnotationCanvasState;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEmptyCanvasState, } from '
|
|
1
|
+
import { createEmptyCanvasState, } from '../../types/annotation.js';
|
|
2
2
|
// Bring a persisted canvas forward to the current schema. Keyed on
|
|
3
3
|
// `schemaVersion` so each client migrates identically and the provider can
|
|
4
4
|
// stay a dumb transport. Identity for v1 today; add a `case 1: return
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const createCoalescedRunner: (task: () => Promise<void>) => (() => Promise<void>);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// A single-flight async runner with trailing coalescing.
|
|
2
|
+
//
|
|
3
|
+
// Wraps an async `task` so that overlapping callers don't run it concurrently
|
|
4
|
+
// and don't drop work:
|
|
5
|
+
// • While a run is in flight, every additional call returns that same
|
|
6
|
+
// in-flight promise (single-flight) instead of starting a second run.
|
|
7
|
+
// • If any call arrives during a run, exactly ONE trailing run is scheduled
|
|
8
|
+
// after the current one finishes — so work that arrived mid-run is never
|
|
9
|
+
// lost. The trailing run re-invokes `task`, which is expected to re-read the
|
|
10
|
+
// latest state each time (it takes no arguments by design).
|
|
11
|
+
// • The returned promise resolves only once the chain is fully drained, so an
|
|
12
|
+
// `await`-ing caller can rely on all queued work having completed.
|
|
13
|
+
// • A task rejection is propagated to the awaiting callers of that run but
|
|
14
|
+
// does NOT wedge the runner: `inFlight` is always cleared, and a pending
|
|
15
|
+
// trailing run still fires, so the runner self-heals after a transient
|
|
16
|
+
// failure.
|
|
17
|
+
//
|
|
18
|
+
// This is the concurrency primitive behind the annotation autosave/flush: it
|
|
19
|
+
// guarantees a create-on-first-save can't race a concurrent flush (the old
|
|
20
|
+
// boolean-guard approach silently dropped the second writer).
|
|
21
|
+
export const createCoalescedRunner = (task) => {
|
|
22
|
+
let inFlight = null;
|
|
23
|
+
let pending = false;
|
|
24
|
+
const run = () => {
|
|
25
|
+
if (inFlight) {
|
|
26
|
+
// A run is already underway — remember that more work arrived and join
|
|
27
|
+
// the in-flight promise rather than starting a second concurrent run.
|
|
28
|
+
pending = true;
|
|
29
|
+
return inFlight;
|
|
30
|
+
}
|
|
31
|
+
inFlight = (async () => {
|
|
32
|
+
try {
|
|
33
|
+
await task();
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
inFlight = null;
|
|
37
|
+
if (pending) {
|
|
38
|
+
// Work arrived mid-run: drain it with a single trailing run, which
|
|
39
|
+
// re-reads the latest state. Recurses until quiescent.
|
|
40
|
+
pending = false;
|
|
41
|
+
await run();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
return inFlight;
|
|
46
|
+
};
|
|
47
|
+
return run;
|
|
48
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnnotationCanvasState, type AnnotationDocumentPatch, type AnnotationViewport, type BackgroundFit } from '
|
|
1
|
+
import { type AnnotationCanvasState, type AnnotationDocumentPatch, type AnnotationViewport, type BackgroundFit } from '../../../types/annotation.js';
|
|
2
2
|
import type { ImageBlob, JobGroupScope } from '../AnnotationDataProvider.js';
|
|
3
3
|
export type SaveStatus = 'idle' | 'dirty' | 'saving' | 'saved' | 'error';
|
|
4
4
|
export interface UseAnnotationCanvasDocOptions {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import { applyPatch, createEmptyCanvasState, } from '
|
|
3
|
-
import { FileUploadType } from '
|
|
2
|
+
import { applyPatch, createEmptyCanvasState, } from '../../../types/annotation.js';
|
|
3
|
+
import { FileUploadType } from '../../../types/firestore.js';
|
|
4
4
|
import { useAnnotationDoc } from './useAnnotationDoc.js';
|
|
5
5
|
import { useAnnotationMutations } from './useAnnotationMutations.js';
|
|
6
|
+
import { createCoalescedRunner } from '../coalescedRunner.js';
|
|
6
7
|
import { hydrateCanvasState } from '../canvasPersistence.js';
|
|
7
8
|
// Stable placeholder so useAnnotationMutations (which requires a non-null
|
|
8
9
|
// scope) can be called unconditionally. Never used to write — flushes are
|
|
@@ -55,8 +56,6 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
55
56
|
// JSON of the canvas we last wrote, to recognize (and ignore) the snapshot
|
|
56
57
|
// echo of our own write when reconciling incoming remote changes.
|
|
57
58
|
const lastSavedJsonRef = useRef(null);
|
|
58
|
-
// Guards against two creates racing if flushes overlap.
|
|
59
|
-
const creatingRef = useRef(false);
|
|
60
59
|
workingRef.current = working;
|
|
61
60
|
dataRef.current = data;
|
|
62
61
|
statusRef.current = saveStatus;
|
|
@@ -137,17 +136,17 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
137
136
|
setWorking(hydrateCanvasState(data, fallbackViewport));
|
|
138
137
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
139
138
|
}, [data]);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
// One pass of the persist logic: snapshots the latest working state and
|
|
140
|
+
// creates-or-updates the doc. NOT called directly — it runs through the
|
|
141
|
+
// single-flight `flush` below, which guarantees two passes never overlap (so
|
|
142
|
+
// a create-on-first-save can't race a concurrent flush) and re-invokes this
|
|
143
|
+
// to drain any work that arrived mid-pass. Because it re-reads `workingRef`
|
|
144
|
+
// each call, a trailing pass always persists the newest canvas.
|
|
145
|
+
const runFlush = useCallback(async () => {
|
|
145
146
|
const canvas = workingRef.current;
|
|
146
147
|
// Nothing to persist, or no real target scope yet.
|
|
147
148
|
if (!canvas || !scope)
|
|
148
149
|
return;
|
|
149
|
-
if (creatingRef.current)
|
|
150
|
-
return;
|
|
151
150
|
const json = JSON.stringify(canvas);
|
|
152
151
|
const id = fileIdRef.current ?? createdIdRef.current;
|
|
153
152
|
const mode = id ? 'update' : 'create';
|
|
@@ -171,14 +170,12 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
171
170
|
try {
|
|
172
171
|
if (!id) {
|
|
173
172
|
// First save with no file — create the doc seeded with the canvas.
|
|
174
|
-
creatingRef.current = true;
|
|
175
173
|
const seed = createSeedRef.current;
|
|
176
174
|
const newId = await createRef.current({
|
|
177
175
|
type: FileUploadType.Canvas,
|
|
178
176
|
...(seed?.name !== undefined ? { name: seed.name } : {}),
|
|
179
177
|
fileData: buildFileData(seed?.fileType ?? 'sketch', seed?.isLabel, canvasPayload),
|
|
180
178
|
});
|
|
181
|
-
creatingRef.current = false;
|
|
182
179
|
createdIdRef.current = newId;
|
|
183
180
|
if (debug) {
|
|
184
181
|
console.log('[useAnnotationCanvasDoc] created file', newId);
|
|
@@ -213,14 +210,34 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
213
210
|
}
|
|
214
211
|
}
|
|
215
212
|
catch (e) {
|
|
216
|
-
creatingRef.current = false;
|
|
217
213
|
// Always log save failures with context — these are otherwise invisible
|
|
218
214
|
// (the canvas keeps working from local state).
|
|
219
215
|
console.error(`[useAnnotationCanvasDoc] ${mode} failed`, { fileId: id, scope, bytes: json.length }, e);
|
|
220
216
|
onSaveErrorRef.current?.(e);
|
|
221
217
|
setStatus('error');
|
|
218
|
+
// Swallowed (not re-thrown): autosave/unmount call this fire-and-forget,
|
|
219
|
+
// and the canvas keeps working from local state. Callers that need to know
|
|
220
|
+
// a create succeeded (ensureFileId) detect it via the absence of an id
|
|
221
|
+
// after the flush drains, not via a rejection.
|
|
222
222
|
}
|
|
223
223
|
}, [scope, setStatus, saveThumbnail]);
|
|
224
|
+
// Stable indirection so the coalesced runner (created once) always calls the
|
|
225
|
+
// latest `runFlush` without being re-created when its deps change.
|
|
226
|
+
const runFlushRef = useRef(runFlush);
|
|
227
|
+
runFlushRef.current = runFlush;
|
|
228
|
+
// Single-flight runner: concurrent callers join the in-flight pass; work that
|
|
229
|
+
// arrives mid-pass triggers exactly one trailing pass. Created once.
|
|
230
|
+
const flushRunner = useMemo(() => createCoalescedRunner(() => runFlushRef.current()), []);
|
|
231
|
+
// Public flush: cancel any pending debounce, then run (or join) a persist
|
|
232
|
+
// pass. Resolves once the chain is fully drained, so `await flush()` is safe
|
|
233
|
+
// for create-before-upload and explicit Save.
|
|
234
|
+
const flush = useCallback(() => {
|
|
235
|
+
if (timerRef.current) {
|
|
236
|
+
clearTimeout(timerRef.current);
|
|
237
|
+
timerRef.current = null;
|
|
238
|
+
}
|
|
239
|
+
return flushRunner();
|
|
240
|
+
}, [flushRunner]);
|
|
224
241
|
const onCommit = useCallback((patch) => {
|
|
225
242
|
setWorking((prev) => (prev ? applyPatch(prev, patch) : prev));
|
|
226
243
|
setStatus('dirty');
|
|
@@ -235,8 +252,12 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
235
252
|
const existing = fileIdRef.current ?? createdIdRef.current;
|
|
236
253
|
if (existing)
|
|
237
254
|
return existing;
|
|
238
|
-
// No file yet — seed an empty canvas if nothing has hydrated, then flush
|
|
239
|
-
//
|
|
255
|
+
// No file yet — seed an empty canvas if nothing has hydrated, then flush so
|
|
256
|
+
// the create branch mints the doc. `flush` is single-flight: if the
|
|
257
|
+
// debounced autosave is already creating, this call JOINS that create and
|
|
258
|
+
// waits for it to drain (rather than racing it and dropping the upload, the
|
|
259
|
+
// old bug). After the drain, the id is present unless the create truly
|
|
260
|
+
// failed — so the throw below is now a genuine error, not a race.
|
|
240
261
|
if (!workingRef.current) {
|
|
241
262
|
const seeded = createEmptyCanvasState(fallbackViewport);
|
|
242
263
|
workingRef.current = seeded;
|
package/dist/exports.d.ts
CHANGED
|
@@ -2,27 +2,31 @@ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearF
|
|
|
2
2
|
export { calculateFormula } from './formulas/calculateFormula.js';
|
|
3
3
|
export { convertMicrometers } from './utils/micrometersToUnit.js';
|
|
4
4
|
export { parseMeasurement } from './utils/parseMeasurement.js';
|
|
5
|
-
export { useParseMeasurement } from './
|
|
5
|
+
export { useParseMeasurement } from './utils/useParseMeasurement.js';
|
|
6
6
|
export * from './types/firestore.js';
|
|
7
7
|
export * from './types/layout.js';
|
|
8
8
|
export * from './types/annotation.js';
|
|
9
9
|
export { getToleranceColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
|
|
10
10
|
export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, } from './utils/groups.js';
|
|
11
|
-
export { isFieldOp, type AnnotationDataProvider, type AnnotationFile, type AnnotationFileSummary, type FieldOp, type ImageBlob, type JobGroupScope, type JobScope, type Patch, type Unsubscribe, type UploadedImageRef, } from './data/AnnotationDataProvider.js';
|
|
12
|
-
export { AnnotationDataProviderContext, useAnnotationData, type AnnotationDataProviderProps, } from './data/AnnotationDataContext.js';
|
|
13
|
-
export { useAnnotationDoc, type UseAnnotationDocResult, } from './data/hooks/useAnnotationDoc.js';
|
|
14
|
-
export { useAnnotationList, type UseAnnotationListResult, } from './data/hooks/useAnnotationList.js';
|
|
15
|
-
export { useAnnotationMutations, type AnnotationMutations, } from './data/hooks/useAnnotationMutations.js';
|
|
16
|
-
export { useAnnotationCanvasDoc, type SaveStatus, type UseAnnotationCanvasDocOptions, type UseAnnotationCanvasDocResult, } from './data/hooks/useAnnotationCanvasDoc.js';
|
|
17
|
-
export { hydrateCanvasState } from './data/canvasPersistence.js';
|
|
18
|
-
export { InMemoryAnnotationProvider } from './data/InMemoryAnnotationProvider.js';
|
|
19
|
-
export type { AnnotationCanvasHandle } from './canvas/useAnnotationCanvasState.js';
|
|
20
|
-
export type { GestureConfig, PanTrigger, AnnotationCanvasInnerProps, } from './canvas/AnnotationCanvasInner.js';
|
|
21
|
-
export type { CanvasPointerEvent, Tool, ToolContext, ToolState, } from './canvas/Tool.js';
|
|
22
|
-
export type { MeasurementRef, PickMeasurement, } from './canvas/measurementPicker.js';
|
|
23
|
-
export type { MeasurementStampRenderArgs, RenderMeasurementStamp, } from './canvas/measurementStampOverlay.js';
|
|
24
|
-
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, type ViewportApi, type ViewportState, } from './canvas/viewport.js';
|
|
25
|
-
export { createPenTool, type PenToolOptions } from './canvas/tools/penTool.js';
|
|
26
|
-
export { createSelectTool } from './canvas/tools/selectTool.js';
|
|
27
|
-
export { createMeasurementStampTool, type MeasurementStampToolOptions, } from './canvas/tools/measurementStampTool.js';
|
|
28
|
-
export { createPanTool, type PanToolOptions } from './canvas/tools/panTool.js';
|
|
11
|
+
export { isFieldOp, type AnnotationDataProvider, type AnnotationFile, type AnnotationFileSummary, type FieldOp, type ImageBlob, type JobGroupScope, type JobScope, type Patch, type Unsubscribe, type UploadedImageRef, } from './annotation/data/AnnotationDataProvider.js';
|
|
12
|
+
export { AnnotationDataProviderContext, useAnnotationData, type AnnotationDataProviderProps, } from './annotation/data/AnnotationDataContext.js';
|
|
13
|
+
export { useAnnotationDoc, type UseAnnotationDocResult, } from './annotation/data/hooks/useAnnotationDoc.js';
|
|
14
|
+
export { useAnnotationList, type UseAnnotationListResult, } from './annotation/data/hooks/useAnnotationList.js';
|
|
15
|
+
export { useAnnotationMutations, type AnnotationMutations, } from './annotation/data/hooks/useAnnotationMutations.js';
|
|
16
|
+
export { useAnnotationCanvasDoc, type SaveStatus, type UseAnnotationCanvasDocOptions, type UseAnnotationCanvasDocResult, } from './annotation/data/hooks/useAnnotationCanvasDoc.js';
|
|
17
|
+
export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
|
|
18
|
+
export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
|
|
19
|
+
export type { AnnotationCanvasHandle } from './annotation/canvas/useAnnotationCanvasState.js';
|
|
20
|
+
export type { GestureConfig, PanTrigger, AnnotationCanvasInnerProps, } from './annotation/canvas/AnnotationCanvasInner.js';
|
|
21
|
+
export type { CanvasPointerEvent, RequestTextInput, Tool, ToolContext, ToolState, } from './annotation/canvas/Tool.js';
|
|
22
|
+
export type { MeasurementRef, PickMeasurement, } from './annotation/canvas/measurementPicker.js';
|
|
23
|
+
export type { MeasurementStampRenderArgs, RenderMeasurementStamp, } from './annotation/canvas/measurementStampOverlay.js';
|
|
24
|
+
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, type ViewportApi, type ViewportState, } from './annotation/canvas/viewport.js';
|
|
25
|
+
export { createPenTool, type PenToolOptions } from './annotation/canvas/tools/penTool.js';
|
|
26
|
+
export { createSelectTool } from './annotation/canvas/tools/selectTool.js';
|
|
27
|
+
export { createMeasurementStampTool, type MeasurementStampToolOptions, } from './annotation/canvas/tools/measurementStampTool.js';
|
|
28
|
+
export { createPanTool, type PanToolOptions } from './annotation/canvas/tools/panTool.js';
|
|
29
|
+
export { createTextTool, type TextToolOptions, } from './annotation/canvas/tools/textTool.js';
|
|
30
|
+
export { DEFAULT_TEXT_FONT_SIZE, MIN_TEXT_FONT_SIZE, MAX_TEXT_FONT_SIZE, textShapeBounds, textResizeGeometry, type ResizeGeometry, type TextBounds, } from './annotation/canvas/textGeometry.js';
|
|
31
|
+
export { recomputeAnchor, projectToLinePos, snapLinePos, lerp, lineLength, placementOf, linePosOf, normalizeRect, rectCenter, rectCornerPoint, oppositeRectCorner, DEFAULT_LINE_POS, type NormalizedRect, type RectCorner, } from './annotation/canvas/measurementGeometry.js';
|
|
32
|
+
export { toSkiaStrokeCap, arrowheadTriangle, arrowheadLength, } from './annotation/canvas/strokeGeometry.js';
|
package/dist/exports.js
CHANGED
|
@@ -6,23 +6,27 @@ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearF
|
|
|
6
6
|
export { calculateFormula } from './formulas/calculateFormula.js';
|
|
7
7
|
export { convertMicrometers } from './utils/micrometersToUnit.js';
|
|
8
8
|
export { parseMeasurement } from './utils/parseMeasurement.js';
|
|
9
|
-
export { useParseMeasurement } from './
|
|
9
|
+
export { useParseMeasurement } from './utils/useParseMeasurement.js';
|
|
10
10
|
export * from './types/firestore.js';
|
|
11
11
|
export * from './types/layout.js';
|
|
12
12
|
export * from './types/annotation.js';
|
|
13
13
|
export { getToleranceColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, } from './utils/tolerance.js';
|
|
14
14
|
export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, } from './utils/groups.js';
|
|
15
15
|
// Annotation data layer (SDK-neutral; apps provide their own provider).
|
|
16
|
-
export { isFieldOp, } from './data/AnnotationDataProvider.js';
|
|
17
|
-
export { AnnotationDataProviderContext, useAnnotationData, } from './data/AnnotationDataContext.js';
|
|
18
|
-
export { useAnnotationDoc, } from './data/hooks/useAnnotationDoc.js';
|
|
19
|
-
export { useAnnotationList, } from './data/hooks/useAnnotationList.js';
|
|
20
|
-
export { useAnnotationMutations, } from './data/hooks/useAnnotationMutations.js';
|
|
21
|
-
export { useAnnotationCanvasDoc, } from './data/hooks/useAnnotationCanvasDoc.js';
|
|
22
|
-
export { hydrateCanvasState } from './data/canvasPersistence.js';
|
|
23
|
-
export { InMemoryAnnotationProvider } from './data/InMemoryAnnotationProvider.js';
|
|
24
|
-
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, } from './canvas/viewport.js';
|
|
25
|
-
export { createPenTool } from './canvas/tools/penTool.js';
|
|
26
|
-
export { createSelectTool } from './canvas/tools/selectTool.js';
|
|
27
|
-
export { createMeasurementStampTool, } from './canvas/tools/measurementStampTool.js';
|
|
28
|
-
export { createPanTool } from './canvas/tools/panTool.js';
|
|
16
|
+
export { isFieldOp, } from './annotation/data/AnnotationDataProvider.js';
|
|
17
|
+
export { AnnotationDataProviderContext, useAnnotationData, } from './annotation/data/AnnotationDataContext.js';
|
|
18
|
+
export { useAnnotationDoc, } from './annotation/data/hooks/useAnnotationDoc.js';
|
|
19
|
+
export { useAnnotationList, } from './annotation/data/hooks/useAnnotationList.js';
|
|
20
|
+
export { useAnnotationMutations, } from './annotation/data/hooks/useAnnotationMutations.js';
|
|
21
|
+
export { useAnnotationCanvasDoc, } from './annotation/data/hooks/useAnnotationCanvasDoc.js';
|
|
22
|
+
export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
|
|
23
|
+
export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
|
|
24
|
+
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, } from './annotation/canvas/viewport.js';
|
|
25
|
+
export { createPenTool } from './annotation/canvas/tools/penTool.js';
|
|
26
|
+
export { createSelectTool } from './annotation/canvas/tools/selectTool.js';
|
|
27
|
+
export { createMeasurementStampTool, } from './annotation/canvas/tools/measurementStampTool.js';
|
|
28
|
+
export { createPanTool } from './annotation/canvas/tools/panTool.js';
|
|
29
|
+
export { createTextTool, } from './annotation/canvas/tools/textTool.js';
|
|
30
|
+
export { DEFAULT_TEXT_FONT_SIZE, MIN_TEXT_FONT_SIZE, MAX_TEXT_FONT_SIZE, textShapeBounds, textResizeGeometry, } from './annotation/canvas/textGeometry.js';
|
|
31
|
+
export { recomputeAnchor, projectToLinePos, snapLinePos, lerp, lineLength, placementOf, linePosOf, normalizeRect, rectCenter, rectCornerPoint, oppositeRectCorner, DEFAULT_LINE_POS, } from './annotation/canvas/measurementGeometry.js';
|
|
32
|
+
export { toSkiaStrokeCap, arrowheadTriangle, arrowheadLength, } from './annotation/canvas/strokeGeometry.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearF
|
|
|
3
3
|
export { calculateFormula } from './formulas/calculateFormula.js';
|
|
4
4
|
export { convertMicrometers } from './utils/micrometersToUnit.js';
|
|
5
5
|
export { parseMeasurement } from './utils/parseMeasurement.js';
|
|
6
|
-
export { useParseMeasurement } from './
|
|
6
|
+
export { useParseMeasurement } from './utils/useParseMeasurement.js';
|
|
7
7
|
export * from './types/firestore.js';
|
|
8
8
|
export * from './types/layout.js';
|
|
9
9
|
export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
|
|
10
|
-
export { AnnotationCanvas, type AnnotationCanvasProps, type CanvasKitOpts, } from './canvas/AnnotationCanvas.js';
|
|
10
|
+
export { AnnotationCanvas, type AnnotationCanvasProps, type CanvasKitOpts, } from './annotation/canvas/AnnotationCanvas.js';
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearF
|
|
|
7
7
|
export { calculateFormula } from './formulas/calculateFormula.js';
|
|
8
8
|
export { convertMicrometers } from './utils/micrometersToUnit.js';
|
|
9
9
|
export { parseMeasurement } from './utils/parseMeasurement.js';
|
|
10
|
-
export { useParseMeasurement } from './
|
|
10
|
+
export { useParseMeasurement } from './utils/useParseMeasurement.js';
|
|
11
11
|
export * from './types/firestore.js';
|
|
12
12
|
export * from './types/layout.js';
|
|
13
13
|
export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, } from './utils/tolerance.js';
|
|
14
|
-
export { AnnotationCanvas, } from './canvas/AnnotationCanvas.js';
|
|
14
|
+
export { AnnotationCanvas, } from './annotation/canvas/AnnotationCanvas.js';
|
package/dist/index.native.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './exports.js';
|
|
2
|
-
export { AnnotationCanvas, type AnnotationCanvasProps, } from './canvas/AnnotationCanvas.native.js';
|
|
2
|
+
export { AnnotationCanvas, type AnnotationCanvasProps, } from './annotation/canvas/AnnotationCanvas.native.js';
|
|
3
3
|
export interface CanvasKitOpts {
|
|
4
4
|
locateFile?: (file: string) => string;
|
|
5
5
|
}
|
package/dist/index.native.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
// WithSkiaWeb / canvaskit-wasm), avoiding the `fs` import that Metro
|
|
4
4
|
// chokes on.
|
|
5
5
|
export * from './exports.js';
|
|
6
|
-
export { AnnotationCanvas, } from './canvas/AnnotationCanvas.native.js';
|
|
6
|
+
export { AnnotationCanvas, } from './annotation/canvas/AnnotationCanvas.native.js';
|
|
@@ -5,12 +5,15 @@ export interface Vec2 {
|
|
|
5
5
|
x: number;
|
|
6
6
|
y: number;
|
|
7
7
|
}
|
|
8
|
+
export type StrokeCap = 'butt' | 'round' | 'square' | 'arrow';
|
|
8
9
|
export interface AnnotationStroke {
|
|
9
10
|
id: AnnotationElementId;
|
|
10
11
|
layerId: AnnotationLayerId;
|
|
11
12
|
tool: 'pen' | 'marker' | 'highlighter';
|
|
12
13
|
color: string;
|
|
13
14
|
width: number;
|
|
15
|
+
cap?: StrokeCap;
|
|
16
|
+
dash?: boolean;
|
|
14
17
|
points: number[];
|
|
15
18
|
pressure?: number[];
|
|
16
19
|
createdAt: number;
|
|
@@ -22,6 +25,7 @@ export interface AnnotationShapeStyle {
|
|
|
22
25
|
strokeWidth?: number;
|
|
23
26
|
fontSize?: number;
|
|
24
27
|
fontFamily?: string;
|
|
28
|
+
dash?: boolean;
|
|
25
29
|
}
|
|
26
30
|
export interface AnnotationShape {
|
|
27
31
|
id: AnnotationElementId;
|
|
@@ -35,13 +39,28 @@ export interface AnnotationShape {
|
|
|
35
39
|
text?: string;
|
|
36
40
|
createdAt: number;
|
|
37
41
|
}
|
|
42
|
+
export type MeasurementPlacement = 'none' | 'line' | 'rectangle';
|
|
38
43
|
export interface PlacedMeasurementRef {
|
|
39
44
|
id: AnnotationElementId;
|
|
40
45
|
layerId: AnnotationLayerId;
|
|
41
|
-
measurementPath
|
|
42
|
-
measurementId
|
|
43
|
-
groupId
|
|
46
|
+
measurementPath?: string;
|
|
47
|
+
measurementId?: string;
|
|
48
|
+
groupId?: string;
|
|
44
49
|
anchor: Vec2;
|
|
50
|
+
placement?: MeasurementPlacement;
|
|
51
|
+
line?: {
|
|
52
|
+
a: Vec2;
|
|
53
|
+
b: Vec2;
|
|
54
|
+
};
|
|
55
|
+
linePos?: number;
|
|
56
|
+
rect?: {
|
|
57
|
+
a: Vec2;
|
|
58
|
+
b: Vec2;
|
|
59
|
+
};
|
|
60
|
+
lineColor?: string;
|
|
61
|
+
lineWidth?: number;
|
|
62
|
+
lineCap?: StrokeCap;
|
|
63
|
+
lineDash?: boolean;
|
|
45
64
|
leader?: {
|
|
46
65
|
from: Vec2;
|
|
47
66
|
to: Vec2;
|
|
@@ -220,7 +220,6 @@ export interface Section extends Timestamps {
|
|
|
220
220
|
tableConfig: ColumnConfig[];
|
|
221
221
|
measurements: string[];
|
|
222
222
|
isTemplate?: boolean;
|
|
223
|
-
viewMode?: 'rows' | 'columns' | 'transposed';
|
|
224
223
|
}
|
|
225
224
|
export interface Group extends FirestoreDoc, Timestamps {
|
|
226
225
|
sectionId: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { parseMeasurement } from '
|
|
2
|
+
import { parseMeasurement } from './parseMeasurement.js';
|
|
3
3
|
export const useParseMeasurement = () => {
|
|
4
4
|
const [error, setError] = useState(null);
|
|
5
5
|
const parseMeasurementInput = (input, defaultUnit = 'mm') => {
|