@reekon-tools/boldr-utils 1.6.12 → 1.6.14
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/AnnotationCanvas.native.d.ts +2 -2
- package/dist/annotation/canvas/AnnotationCanvasInner.d.ts +5 -2
- package/dist/annotation/canvas/AnnotationCanvasInner.js +58 -6
- package/dist/annotation/canvas/AnnotationCanvasInner.native.d.ts +5 -2
- package/dist/annotation/canvas/AnnotationCanvasInner.native.js +514 -59
- package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +31 -1
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +38 -9
- package/dist/annotation/canvas/Tool.d.ts +27 -0
- package/dist/annotation/canvas/elements/BackgroundImageElement.js +4 -1
- package/dist/annotation/canvas/elements/ShapeElement.js +68 -9
- package/dist/annotation/canvas/elements/StrokeElement.js +8 -3
- package/dist/annotation/canvas/measurementGeometry.d.ts +21 -0
- package/dist/annotation/canvas/measurementGeometry.js +98 -3
- package/dist/annotation/canvas/shapeGeometry.d.ts +5 -0
- package/dist/annotation/canvas/shapeGeometry.js +116 -0
- package/dist/annotation/canvas/strokeGeometry.d.ts +1 -0
- package/dist/annotation/canvas/strokeGeometry.js +8 -0
- package/dist/annotation/canvas/textGeometry.d.ts +24 -0
- package/dist/annotation/canvas/textGeometry.js +110 -0
- package/dist/annotation/canvas/tools/panTool.d.ts +1 -0
- package/dist/annotation/canvas/tools/panTool.js +38 -5
- package/dist/annotation/canvas/tools/penTool.d.ts +1 -0
- package/dist/annotation/canvas/tools/penTool.js +8 -2
- package/dist/annotation/canvas/tools/polygonTool.d.ts +11 -0
- package/dist/annotation/canvas/tools/polygonTool.js +162 -0
- package/dist/annotation/canvas/tools/selectTool.js +148 -51
- package/dist/annotation/canvas/tools/shapeTool.d.ts +25 -0
- package/dist/annotation/canvas/tools/shapeTool.js +111 -0
- package/dist/annotation/canvas/tools/textTool.d.ts +12 -0
- package/dist/annotation/canvas/tools/textTool.js +78 -0
- package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +2 -1
- package/dist/annotation/canvas/useAnnotationCanvasState.js +56 -6
- package/dist/annotation/data/coalescedRunner.d.ts +1 -0
- package/dist/annotation/data/coalescedRunner.js +48 -0
- package/dist/annotation/data/hooks/useAnnotationCanvasDoc.js +118 -38
- package/dist/exports.d.ts +9 -4
- package/dist/exports.js +8 -3
- package/dist/formulas/calculateFormula.js +1 -3
- package/dist/types/annotation.d.ts +9 -0
- package/dist/types/firestore.d.ts +4 -0
- package/package.json +1 -1
|
@@ -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
|
+
};
|
|
@@ -3,6 +3,7 @@ import { applyPatch, createEmptyCanvasState, } from '../../../types/annotation.j
|
|
|
3
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
|
|
@@ -15,11 +16,16 @@ const EMPTY_SCOPE = {
|
|
|
15
16
|
};
|
|
16
17
|
// Build the persisted fileData, omitting `isLabel` when undefined so the write
|
|
17
18
|
// contains no undefined values (Firestore rejects them on RN).
|
|
18
|
-
const buildFileData = (fileType, isLabel, canvas) => ({
|
|
19
|
+
const buildFileData = (fileType, isLabel, canvas, canvasRev) => ({
|
|
19
20
|
fileType,
|
|
20
21
|
...(isLabel !== undefined ? { isLabel } : {}),
|
|
21
22
|
canvas,
|
|
23
|
+
canvasRev,
|
|
22
24
|
});
|
|
25
|
+
// Random id for this editing session, used to stamp writes (see canvasRev in
|
|
26
|
+
// AnnotationFileData). Uniqueness only needs to hold across the handful of
|
|
27
|
+
// clients that ever touch one annotation doc.
|
|
28
|
+
const makeClientId = () => `${Date.now().toString(36)}-${Math.floor(Math.random() * 0x100000000).toString(36)}`;
|
|
23
29
|
// Orchestrates load + auto-save for the annotation canvas. Hydrates the working
|
|
24
30
|
// state from the persisted doc, applies commits optimistically, and persists
|
|
25
31
|
// (debounced) through the data provider — creating the file on first save when
|
|
@@ -51,12 +57,30 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
51
57
|
const uploadImageRef = useRef(uploadImage);
|
|
52
58
|
// Guards against overlapping thumbnail captures (each save fires one).
|
|
53
59
|
const thumbnailSavingRef = useRef(false);
|
|
60
|
+
// Set by the public `save()` so the next successful flush refreshes the
|
|
61
|
+
// thumbnail; debounced autosaves leave it false (capture is too costly to
|
|
62
|
+
// run mid-session — see captureThumbnail). Consumed (cleared) by each flush
|
|
63
|
+
// pass that attempts a write, so a failed explicit save drops the request
|
|
64
|
+
// instead of leaking the capture into some later autosave.
|
|
65
|
+
const thumbnailRequestedRef = useRef(false);
|
|
66
|
+
// In-flight capture+upload from the latest flush, so `save()` can await it —
|
|
67
|
+
// callers that navigate away right after saving must not unmount the view
|
|
68
|
+
// mid-capture.
|
|
69
|
+
const thumbnailPromiseRef = useRef(undefined);
|
|
54
70
|
const debugRef = useRef(debugLogging);
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
|
|
71
|
+
// This session's identity + write counter, stamped into `fileData.canvasRev`
|
|
72
|
+
// on every flush. An incoming snapshot carrying our clientId is the echo of
|
|
73
|
+
// our own write — state we already hold — and must not be re-applied (doing
|
|
74
|
+
// so replaces every object identity in the working canvas, busting all
|
|
75
|
+
// memoization downstream).
|
|
76
|
+
const clientIdRef = useRef(null);
|
|
77
|
+
if (clientIdRef.current === null)
|
|
78
|
+
clientIdRef.current = makeClientId();
|
|
79
|
+
const writeSeqRef = useRef(0);
|
|
80
|
+
// Monotonic local-edit counter: bumped on every commit. A flush snapshots it
|
|
81
|
+
// before writing and compares after, so "did edits land mid-flight?" is an
|
|
82
|
+
// integer comparison instead of re-serializing the whole document.
|
|
83
|
+
const editSeqRef = useRef(0);
|
|
60
84
|
workingRef.current = working;
|
|
61
85
|
dataRef.current = data;
|
|
62
86
|
statusRef.current = saveStatus;
|
|
@@ -107,7 +131,6 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
107
131
|
timerRef.current = null;
|
|
108
132
|
}
|
|
109
133
|
createdIdRef.current = null;
|
|
110
|
-
lastSavedJsonRef.current = null;
|
|
111
134
|
setWorking(null);
|
|
112
135
|
setStatus('idle');
|
|
113
136
|
}, [fileId, setStatus]);
|
|
@@ -116,39 +139,48 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
116
139
|
// First load — always hydrate.
|
|
117
140
|
if (workingRef.current === null) {
|
|
118
141
|
setWorking(hydrateCanvasState(data, fallbackViewport));
|
|
119
|
-
lastSavedJsonRef.current = data?.fileData.canvas
|
|
120
|
-
? JSON.stringify(data.fileData.canvas)
|
|
121
|
-
: null;
|
|
122
142
|
return;
|
|
123
143
|
}
|
|
124
144
|
// A write of ours is queued or in flight — ignore the snapshot; it is
|
|
125
145
|
// either the echo of our write or about to be superseded by it.
|
|
126
146
|
if (statusRef.current === 'saving' || timerRef.current !== null)
|
|
127
147
|
return;
|
|
128
|
-
// Clean locally: accept a genuine remote change, but ignore the echo of
|
|
129
|
-
// our own last write (same content).
|
|
130
148
|
const incoming = data?.fileData.canvas;
|
|
131
149
|
if (!incoming)
|
|
132
150
|
return;
|
|
133
|
-
|
|
134
|
-
|
|
151
|
+
// Ignore the echo of this session's own writes: the doc carries the
|
|
152
|
+
// canvasRev we stamped, so its content is (at most as new as) what we
|
|
153
|
+
// already hold. Only a doc written by ANOTHER client is a genuine remote
|
|
154
|
+
// change worth re-hydrating — which replaces all element identities, so
|
|
155
|
+
// it must never happen on the routine save → echo round-trip.
|
|
156
|
+
if (data?.fileData.canvasRev?.clientId === clientIdRef.current)
|
|
135
157
|
return;
|
|
136
|
-
lastSavedJsonRef.current = incomingJson;
|
|
137
158
|
setWorking(hydrateCanvasState(data, fallbackViewport));
|
|
138
159
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
139
160
|
}, [data]);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
161
|
+
// One pass of the persist logic: snapshots the latest working state and
|
|
162
|
+
// creates-or-updates the doc. NOT called directly — it runs through the
|
|
163
|
+
// single-flight `flush` below, which guarantees two passes never overlap (so
|
|
164
|
+
// a create-on-first-save can't race a concurrent flush) and re-invokes this
|
|
165
|
+
// to drain any work that arrived mid-pass. Because it re-reads `workingRef`
|
|
166
|
+
// each call, a trailing pass always persists the newest canvas.
|
|
167
|
+
const runFlush = useCallback(async () => {
|
|
145
168
|
const canvas = workingRef.current;
|
|
146
169
|
// Nothing to persist, or no real target scope yet.
|
|
147
170
|
if (!canvas || !scope)
|
|
148
171
|
return;
|
|
149
|
-
if (creatingRef.current)
|
|
150
|
-
return;
|
|
151
172
|
const json = JSON.stringify(canvas);
|
|
173
|
+
// Edits up to this point are covered by this write; anything committed
|
|
174
|
+
// while the write is in flight bumps editSeqRef past this snapshot.
|
|
175
|
+
const editSeqAtFlush = editSeqRef.current;
|
|
176
|
+
// Claim the thumbnail request for THIS pass: honored on success, dropped
|
|
177
|
+
// on failure (the next explicit save re-requests it).
|
|
178
|
+
const wantThumbnail = thumbnailRequestedRef.current;
|
|
179
|
+
thumbnailRequestedRef.current = false;
|
|
180
|
+
const canvasRev = {
|
|
181
|
+
clientId: clientIdRef.current,
|
|
182
|
+
seq: ++writeSeqRef.current,
|
|
183
|
+
};
|
|
152
184
|
const id = fileIdRef.current ?? createdIdRef.current;
|
|
153
185
|
const mode = id ? 'update' : 'create';
|
|
154
186
|
const debug = debugRef.current;
|
|
@@ -168,18 +200,18 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
168
200
|
// rejects undefined field values unless ignoreUndefinedProperties is set.
|
|
169
201
|
const canvasPayload = JSON.parse(json);
|
|
170
202
|
setStatus('saving');
|
|
203
|
+
let createdThisPass = false;
|
|
171
204
|
try {
|
|
172
205
|
if (!id) {
|
|
173
206
|
// First save with no file — create the doc seeded with the canvas.
|
|
174
|
-
creatingRef.current = true;
|
|
175
207
|
const seed = createSeedRef.current;
|
|
176
208
|
const newId = await createRef.current({
|
|
177
209
|
type: FileUploadType.Canvas,
|
|
178
210
|
...(seed?.name !== undefined ? { name: seed.name } : {}),
|
|
179
|
-
fileData: buildFileData(seed?.fileType ?? 'sketch', seed?.isLabel, canvasPayload),
|
|
211
|
+
fileData: buildFileData(seed?.fileType ?? 'sketch', seed?.isLabel, canvasPayload, canvasRev),
|
|
180
212
|
});
|
|
181
|
-
creatingRef.current = false;
|
|
182
213
|
createdIdRef.current = newId;
|
|
214
|
+
createdThisPass = true;
|
|
183
215
|
if (debug) {
|
|
184
216
|
console.log('[useAnnotationCanvasDoc] created file', newId);
|
|
185
217
|
}
|
|
@@ -190,22 +222,27 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
190
222
|
await updateRef.current(id, {
|
|
191
223
|
fileData: buildFileData(doc?.fileData.fileType ??
|
|
192
224
|
createSeedRef.current?.fileType ??
|
|
193
|
-
'sketch', doc?.fileData.isLabel ?? createSeedRef.current?.isLabel, canvasPayload),
|
|
225
|
+
'sketch', doc?.fileData.isLabel ?? createSeedRef.current?.isLabel, canvasPayload, canvasRev),
|
|
194
226
|
});
|
|
195
227
|
if (debug) {
|
|
196
228
|
console.log('[useAnnotationCanvasDoc] updated file', id);
|
|
197
229
|
}
|
|
198
230
|
}
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
//
|
|
231
|
+
// Refresh the thumbnail when an explicit save() asked for it, or when
|
|
232
|
+
// this pass CREATED the file — every file gets at least one thumbnail
|
|
233
|
+
// so the grid never shows a blank tile for a canvas the user drew and
|
|
234
|
+
// then backed out of without an explicit save. Routine debounced
|
|
235
|
+
// autosaves skip it: capture means a full view snapshot + encode on the
|
|
236
|
+
// JS thread, which would jank the very drawing session that triggered
|
|
237
|
+
// the save. Fire-and-forget for the flush itself; save() awaits the
|
|
238
|
+
// stashed promise so explicit savers can navigate safely after.
|
|
202
239
|
const savedId = fileIdRef.current ?? createdIdRef.current;
|
|
203
|
-
if (savedId)
|
|
204
|
-
|
|
240
|
+
if (savedId && (wantThumbnail || createdThisPass)) {
|
|
241
|
+
thumbnailPromiseRef.current = saveThumbnail(savedId);
|
|
242
|
+
}
|
|
205
243
|
// If new edits landed mid-flight, stay dirty and let the next debounce
|
|
206
244
|
// (or unmount) flush them.
|
|
207
|
-
|
|
208
|
-
if (latest && JSON.stringify(latest) !== json) {
|
|
245
|
+
if (editSeqRef.current !== editSeqAtFlush) {
|
|
209
246
|
setStatus('dirty');
|
|
210
247
|
}
|
|
211
248
|
else {
|
|
@@ -213,16 +250,47 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
213
250
|
}
|
|
214
251
|
}
|
|
215
252
|
catch (e) {
|
|
216
|
-
creatingRef.current = false;
|
|
217
253
|
// Always log save failures with context — these are otherwise invisible
|
|
218
254
|
// (the canvas keeps working from local state).
|
|
219
255
|
console.error(`[useAnnotationCanvasDoc] ${mode} failed`, { fileId: id, scope, bytes: json.length }, e);
|
|
220
256
|
onSaveErrorRef.current?.(e);
|
|
221
257
|
setStatus('error');
|
|
258
|
+
// Swallowed (not re-thrown): autosave/unmount call this fire-and-forget,
|
|
259
|
+
// and the canvas keeps working from local state. Callers that need to know
|
|
260
|
+
// a create succeeded (ensureFileId) detect it via the absence of an id
|
|
261
|
+
// after the flush drains, not via a rejection.
|
|
222
262
|
}
|
|
223
263
|
}, [scope, setStatus, saveThumbnail]);
|
|
264
|
+
// Stable indirection so the coalesced runner (created once) always calls the
|
|
265
|
+
// latest `runFlush` without being re-created when its deps change.
|
|
266
|
+
const runFlushRef = useRef(runFlush);
|
|
267
|
+
runFlushRef.current = runFlush;
|
|
268
|
+
// Single-flight runner: concurrent callers join the in-flight pass; work that
|
|
269
|
+
// arrives mid-pass triggers exactly one trailing pass. Created once.
|
|
270
|
+
const flushRunner = useMemo(() => createCoalescedRunner(() => runFlushRef.current()), []);
|
|
271
|
+
// Public flush: cancel any pending debounce, then run (or join) a persist
|
|
272
|
+
// pass. Resolves once the chain is fully drained, so `await flush()` is safe
|
|
273
|
+
// for create-before-upload and explicit Save.
|
|
274
|
+
const flush = useCallback(() => {
|
|
275
|
+
if (timerRef.current) {
|
|
276
|
+
clearTimeout(timerRef.current);
|
|
277
|
+
timerRef.current = null;
|
|
278
|
+
}
|
|
279
|
+
return flushRunner();
|
|
280
|
+
}, [flushRunner]);
|
|
224
281
|
const onCommit = useCallback((patch) => {
|
|
225
|
-
|
|
282
|
+
editSeqRef.current += 1;
|
|
283
|
+
setWorking((prev) => {
|
|
284
|
+
if (!prev)
|
|
285
|
+
return prev;
|
|
286
|
+
const next = applyPatch(prev, patch);
|
|
287
|
+
// Keep the flush snapshot in lockstep with editSeqRef: a flush that
|
|
288
|
+
// starts before React re-renders must not pair this commit's seq bump
|
|
289
|
+
// with the pre-commit canvas (it would mark the edit 'saved' without
|
|
290
|
+
// ever writing it).
|
|
291
|
+
workingRef.current = next;
|
|
292
|
+
return next;
|
|
293
|
+
});
|
|
226
294
|
setStatus('dirty');
|
|
227
295
|
if (timerRef.current)
|
|
228
296
|
clearTimeout(timerRef.current);
|
|
@@ -231,12 +299,24 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
231
299
|
void flush();
|
|
232
300
|
}, debounceMs);
|
|
233
301
|
}, [debounceMs, flush, setStatus]);
|
|
302
|
+
// Explicit save (Save button, "done" actions): also refreshes the file's
|
|
303
|
+
// thumbnail, which autosaves deliberately skip. Resolves only after the
|
|
304
|
+
// capture+upload too, so a caller may unmount the view right after.
|
|
305
|
+
const save = useCallback(async () => {
|
|
306
|
+
thumbnailRequestedRef.current = true;
|
|
307
|
+
await flush();
|
|
308
|
+
await thumbnailPromiseRef.current;
|
|
309
|
+
}, [flush]);
|
|
234
310
|
const ensureFileId = useCallback(async () => {
|
|
235
311
|
const existing = fileIdRef.current ?? createdIdRef.current;
|
|
236
312
|
if (existing)
|
|
237
313
|
return existing;
|
|
238
|
-
// No file yet — seed an empty canvas if nothing has hydrated, then flush
|
|
239
|
-
//
|
|
314
|
+
// No file yet — seed an empty canvas if nothing has hydrated, then flush so
|
|
315
|
+
// the create branch mints the doc. `flush` is single-flight: if the
|
|
316
|
+
// debounced autosave is already creating, this call JOINS that create and
|
|
317
|
+
// waits for it to drain (rather than racing it and dropping the upload, the
|
|
318
|
+
// old bug). After the drain, the id is present unless the create truly
|
|
319
|
+
// failed — so the throw below is now a genuine error, not a race.
|
|
240
320
|
if (!workingRef.current) {
|
|
241
321
|
const seeded = createEmptyCanvasState(fallbackViewport);
|
|
242
322
|
workingRef.current = seeded;
|
|
@@ -306,7 +386,7 @@ export const useAnnotationCanvasDoc = (options) => {
|
|
|
306
386
|
loading,
|
|
307
387
|
error,
|
|
308
388
|
saveStatus,
|
|
309
|
-
save
|
|
389
|
+
save,
|
|
310
390
|
ensureFileId,
|
|
311
391
|
setBackgroundImage,
|
|
312
392
|
clearBackgroundImage,
|
package/dist/exports.d.ts
CHANGED
|
@@ -18,13 +18,18 @@ export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
|
|
|
18
18
|
export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
|
|
19
19
|
export type { AnnotationCanvasHandle } from './annotation/canvas/useAnnotationCanvasState.js';
|
|
20
20
|
export type { GestureConfig, PanTrigger, AnnotationCanvasInnerProps, } from './annotation/canvas/AnnotationCanvasInner.js';
|
|
21
|
-
export type { CanvasPointerEvent, Tool, ToolContext, ToolState, } from './annotation/canvas/Tool.js';
|
|
21
|
+
export type { CanvasPointerEvent, RequestTextInput, ShapeDrawConfig, Tool, ToolContext, ToolState, } from './annotation/canvas/Tool.js';
|
|
22
22
|
export type { MeasurementRef, PickMeasurement, } from './annotation/canvas/measurementPicker.js';
|
|
23
23
|
export type { MeasurementStampRenderArgs, RenderMeasurementStamp, } from './annotation/canvas/measurementStampOverlay.js';
|
|
24
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';
|
|
25
|
+
export { createPenTool, type PenToolOptions, } from './annotation/canvas/tools/penTool.js';
|
|
26
26
|
export { createSelectTool } from './annotation/canvas/tools/selectTool.js';
|
|
27
27
|
export { createMeasurementStampTool, type MeasurementStampToolOptions, } from './annotation/canvas/tools/measurementStampTool.js';
|
|
28
|
-
export { createPanTool, type PanToolOptions } from './annotation/canvas/tools/panTool.js';
|
|
29
|
-
export {
|
|
28
|
+
export { createPanTool, type PanToolOptions, } from './annotation/canvas/tools/panTool.js';
|
|
29
|
+
export { createTextTool, type TextToolOptions, } from './annotation/canvas/tools/textTool.js';
|
|
30
|
+
export { createShapeTool, buildShapeFromDrag, type ShapeToolOptions, } from './annotation/canvas/tools/shapeTool.js';
|
|
31
|
+
export { createPolygonTool, type PolygonToolOptions, } from './annotation/canvas/tools/polygonTool.js';
|
|
32
|
+
export { annotationKindFor, hitShapeOutline, shapePointsFromDrag, type ShapeToolKind, } from './annotation/canvas/shapeGeometry.js';
|
|
33
|
+
export { DEFAULT_TEXT_FONT_SIZE, MIN_TEXT_FONT_SIZE, MAX_TEXT_FONT_SIZE, textShapeBounds, textResizeGeometry, type ResizeGeometry, type TextBounds, } from './annotation/canvas/textGeometry.js';
|
|
34
|
+
export { recomputeAnchor, projectToLinePos, snapLinePos, lerp, lineLength, placementOf, linePosOf, normalizeRect, rectCenter, rectCornerPoint, oppositeRectCorner, hitPlacedMeasurement, DEFAULT_LINE_POS, type NormalizedRect, type RectCorner, } from './annotation/canvas/measurementGeometry.js';
|
|
30
35
|
export { toSkiaStrokeCap, arrowheadTriangle, arrowheadLength, } from './annotation/canvas/strokeGeometry.js';
|
package/dist/exports.js
CHANGED
|
@@ -22,9 +22,14 @@ export { useAnnotationCanvasDoc, } from './annotation/data/hooks/useAnnotationCa
|
|
|
22
22
|
export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
|
|
23
23
|
export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
|
|
24
24
|
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, } from './annotation/canvas/viewport.js';
|
|
25
|
-
export { createPenTool } from './annotation/canvas/tools/penTool.js';
|
|
25
|
+
export { createPenTool, } from './annotation/canvas/tools/penTool.js';
|
|
26
26
|
export { createSelectTool } from './annotation/canvas/tools/selectTool.js';
|
|
27
27
|
export { createMeasurementStampTool, } from './annotation/canvas/tools/measurementStampTool.js';
|
|
28
|
-
export { createPanTool } from './annotation/canvas/tools/panTool.js';
|
|
29
|
-
export {
|
|
28
|
+
export { createPanTool, } from './annotation/canvas/tools/panTool.js';
|
|
29
|
+
export { createTextTool, } from './annotation/canvas/tools/textTool.js';
|
|
30
|
+
export { createShapeTool, buildShapeFromDrag, } from './annotation/canvas/tools/shapeTool.js';
|
|
31
|
+
export { createPolygonTool, } from './annotation/canvas/tools/polygonTool.js';
|
|
32
|
+
export { annotationKindFor, hitShapeOutline, shapePointsFromDrag, } from './annotation/canvas/shapeGeometry.js';
|
|
33
|
+
export { DEFAULT_TEXT_FONT_SIZE, MIN_TEXT_FONT_SIZE, MAX_TEXT_FONT_SIZE, textShapeBounds, textResizeGeometry, } from './annotation/canvas/textGeometry.js';
|
|
34
|
+
export { recomputeAnchor, projectToLinePos, snapLinePos, lerp, lineLength, placementOf, linePosOf, normalizeRect, rectCenter, rectCornerPoint, oppositeRectCorner, hitPlacedMeasurement, DEFAULT_LINE_POS, } from './annotation/canvas/measurementGeometry.js';
|
|
30
35
|
export { toSkiaStrokeCap, arrowheadTriangle, arrowheadLength, } from './annotation/canvas/strokeGeometry.js';
|
|
@@ -105,9 +105,7 @@ export const calculateFormula = (formula, formulas, columns, tableConfig, measur
|
|
|
105
105
|
// Validate that all required inputs are filled out
|
|
106
106
|
const missingInputs = [];
|
|
107
107
|
for (const [variable, mapping] of Object.entries(currentMappings)) {
|
|
108
|
-
const mappingObj = typeof mapping === 'string'
|
|
109
|
-
? { id: mapping, type: 'column' }
|
|
110
|
-
: mapping;
|
|
108
|
+
const mappingObj = typeof mapping === 'string' ? { id: mapping, type: 'column' } : mapping;
|
|
111
109
|
// Only check column references (formula references are handled separately)
|
|
112
110
|
if (mappingObj.type === 'column' || !mappingObj.type) {
|
|
113
111
|
const columnId = mappingObj.id;
|
|
@@ -13,6 +13,7 @@ export interface AnnotationStroke {
|
|
|
13
13
|
color: string;
|
|
14
14
|
width: number;
|
|
15
15
|
cap?: StrokeCap;
|
|
16
|
+
dash?: boolean;
|
|
16
17
|
points: number[];
|
|
17
18
|
pressure?: number[];
|
|
18
19
|
createdAt: number;
|
|
@@ -24,6 +25,8 @@ export interface AnnotationShapeStyle {
|
|
|
24
25
|
strokeWidth?: number;
|
|
25
26
|
fontSize?: number;
|
|
26
27
|
fontFamily?: string;
|
|
28
|
+
dash?: boolean;
|
|
29
|
+
cap?: StrokeCap;
|
|
27
30
|
}
|
|
28
31
|
export interface AnnotationShape {
|
|
29
32
|
id: AnnotationElementId;
|
|
@@ -32,6 +35,7 @@ export interface AnnotationShape {
|
|
|
32
35
|
geometry: {
|
|
33
36
|
points: Vec2[];
|
|
34
37
|
rotation?: number;
|
|
38
|
+
closed?: boolean;
|
|
35
39
|
};
|
|
36
40
|
style: AnnotationShapeStyle;
|
|
37
41
|
text?: string;
|
|
@@ -51,9 +55,14 @@ export interface PlacedMeasurementRef {
|
|
|
51
55
|
b: Vec2;
|
|
52
56
|
};
|
|
53
57
|
linePos?: number;
|
|
58
|
+
rect?: {
|
|
59
|
+
a: Vec2;
|
|
60
|
+
b: Vec2;
|
|
61
|
+
};
|
|
54
62
|
lineColor?: string;
|
|
55
63
|
lineWidth?: number;
|
|
56
64
|
lineCap?: StrokeCap;
|
|
65
|
+
lineDash?: boolean;
|
|
57
66
|
leader?: {
|
|
58
67
|
from: Vec2;
|
|
59
68
|
to: Vec2;
|
|
@@ -113,6 +113,10 @@ export interface AnnotationFileData {
|
|
|
113
113
|
fileType: 'sketch' | 'document';
|
|
114
114
|
isLabel?: boolean;
|
|
115
115
|
canvas?: AnnotationCanvasState;
|
|
116
|
+
canvasRev?: {
|
|
117
|
+
clientId: string;
|
|
118
|
+
seq: number;
|
|
119
|
+
};
|
|
116
120
|
}
|
|
117
121
|
export interface CalculatorFileData {
|
|
118
122
|
templateId: string;
|