@reekon-tools/boldr-utils 1.6.6 → 1.6.9
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/data/AnnotationDataProvider.d.ts +1 -1
- package/dist/data/InMemoryAnnotationProvider.js +2 -2
- package/dist/data/canvasPersistence.d.ts +3 -0
- package/dist/data/canvasPersistence.js +26 -0
- package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +26 -0
- package/dist/data/hooks/useAnnotationCanvasDoc.js +220 -0
- package/dist/exports.d.ts +5 -3
- package/dist/exports.js +4 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/types/firestore.d.ts +20 -2
- package/dist/types/firestore.js +5 -0
- package/dist/utils/micrometersToUnit.d.ts +1 -0
- package/dist/utils/micrometersToUnit.js +24 -1
- package/dist/utils/tolerance.d.ts +15 -0
- package/dist/utils/tolerance.js +41 -0
- package/package.json +2 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FileUploadType
|
|
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}`;
|
|
@@ -69,7 +69,7 @@ export class InMemoryAnnotationProvider {
|
|
|
69
69
|
thumbnailUrl: seed.thumbnailUrl ?? null,
|
|
70
70
|
createdAt: seed.createdAt ?? new Date(),
|
|
71
71
|
createdBy: seed.createdBy,
|
|
72
|
-
type: FileUploadType.
|
|
72
|
+
type: FileUploadType.Canvas,
|
|
73
73
|
fileData: seed.fileData ?? { fileType: 'sketch' },
|
|
74
74
|
};
|
|
75
75
|
this.getBucket(scope).set(id, file);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type AnnotationCanvasState, type AnnotationViewport } from '../types/annotation.js';
|
|
2
|
+
import type { AnnotationFile } from './AnnotationDataProvider.js';
|
|
3
|
+
export declare const hydrateCanvasState: (file: AnnotationFile | null, fallbackViewport?: Partial<AnnotationViewport>) => AnnotationCanvasState;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createEmptyCanvasState, } from '../types/annotation.js';
|
|
2
|
+
// Bring a persisted canvas forward to the current schema. Keyed on
|
|
3
|
+
// `schemaVersion` so each client migrates identically and the provider can
|
|
4
|
+
// stay a dumb transport. Identity for v1 today; add a `case 1: return
|
|
5
|
+
// upgradeV1toV2(raw)` arm when the schema bumps.
|
|
6
|
+
const migrateCanvasState = (raw) => {
|
|
7
|
+
switch (raw.schemaVersion) {
|
|
8
|
+
case 1:
|
|
9
|
+
return raw;
|
|
10
|
+
default:
|
|
11
|
+
// Forward-compat: a doc written by a newer client. Trust it as-is
|
|
12
|
+
// rather than risk a lossy downgrade-write.
|
|
13
|
+
return raw;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
// Turn a loaded annotation file into a working canvas state ready to feed the
|
|
17
|
+
// canvas. Missing/legacy docs (no `fileData.canvas`) start from an empty
|
|
18
|
+
// canvas sized by `fallbackViewport`. This is the single entry point for going
|
|
19
|
+
// from `useAnnotationDoc` data to an `AnnotationCanvasState` — consumers should
|
|
20
|
+
// not call `createEmptyCanvasState` directly.
|
|
21
|
+
export const hydrateCanvasState = (file, fallbackViewport) => {
|
|
22
|
+
const persisted = file?.fileData.canvas;
|
|
23
|
+
if (!persisted)
|
|
24
|
+
return createEmptyCanvasState(fallbackViewport);
|
|
25
|
+
return migrateCanvasState(persisted);
|
|
26
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type AnnotationCanvasState, type AnnotationDocumentPatch, type AnnotationViewport } from '../../types/annotation.js';
|
|
2
|
+
import type { JobGroupScope } from '../AnnotationDataProvider.js';
|
|
3
|
+
export type SaveStatus = 'idle' | 'dirty' | 'saving' | 'saved' | 'error';
|
|
4
|
+
export interface UseAnnotationCanvasDocOptions {
|
|
5
|
+
scope: JobGroupScope | null;
|
|
6
|
+
fileId: string | null;
|
|
7
|
+
fallbackViewport?: Partial<AnnotationViewport>;
|
|
8
|
+
debounceMs?: number;
|
|
9
|
+
createSeed?: {
|
|
10
|
+
name?: string;
|
|
11
|
+
fileType?: 'sketch' | 'document';
|
|
12
|
+
isLabel?: boolean;
|
|
13
|
+
};
|
|
14
|
+
onFileCreated?: (fileId: string) => void;
|
|
15
|
+
onSaveError?: (error: unknown) => void;
|
|
16
|
+
debugLogging?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface UseAnnotationCanvasDocResult {
|
|
19
|
+
canvas: AnnotationCanvasState;
|
|
20
|
+
onCommit: (patch: AnnotationDocumentPatch) => void;
|
|
21
|
+
loading: boolean;
|
|
22
|
+
error: Error | null;
|
|
23
|
+
saveStatus: SaveStatus;
|
|
24
|
+
save: () => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare const useAnnotationCanvasDoc: (options: UseAnnotationCanvasDocOptions) => UseAnnotationCanvasDocResult;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { applyPatch, createEmptyCanvasState, } from '../../types/annotation.js';
|
|
3
|
+
import { FileUploadType } from '../../types/firestore.js';
|
|
4
|
+
import { useAnnotationDoc } from './useAnnotationDoc.js';
|
|
5
|
+
import { useAnnotationMutations } from './useAnnotationMutations.js';
|
|
6
|
+
import { hydrateCanvasState } from '../canvasPersistence.js';
|
|
7
|
+
// Stable placeholder so useAnnotationMutations (which requires a non-null
|
|
8
|
+
// scope) can be called unconditionally. Never used to write — flushes are
|
|
9
|
+
// guarded on a real scope.
|
|
10
|
+
const EMPTY_SCOPE = {
|
|
11
|
+
orgId: '',
|
|
12
|
+
projectId: '',
|
|
13
|
+
jobId: '',
|
|
14
|
+
groupId: '',
|
|
15
|
+
};
|
|
16
|
+
// Build the persisted fileData, omitting `isLabel` when undefined so the write
|
|
17
|
+
// contains no undefined values (Firestore rejects them on RN).
|
|
18
|
+
const buildFileData = (fileType, isLabel, canvas) => ({
|
|
19
|
+
fileType,
|
|
20
|
+
...(isLabel !== undefined ? { isLabel } : {}),
|
|
21
|
+
canvas,
|
|
22
|
+
});
|
|
23
|
+
// Orchestrates load + auto-save for the annotation canvas. Hydrates the working
|
|
24
|
+
// state from the persisted doc, applies commits optimistically, and persists
|
|
25
|
+
// (debounced) through the data provider — creating the file on first save when
|
|
26
|
+
// no `fileId` was supplied. Composes the existing low-level hooks so it stays
|
|
27
|
+
// decoupled from any specific Firebase SDK.
|
|
28
|
+
export const useAnnotationCanvasDoc = (options) => {
|
|
29
|
+
const { scope, fileId, fallbackViewport, debounceMs = 800, createSeed, onFileCreated, onSaveError, debugLogging = false, } = options;
|
|
30
|
+
const { data, loading, error } = useAnnotationDoc(scope, fileId);
|
|
31
|
+
const { create, update } = useAnnotationMutations(scope ?? EMPTY_SCOPE);
|
|
32
|
+
const [working, setWorking] = useState(null);
|
|
33
|
+
const [saveStatus, setSaveStatus] = useState('idle');
|
|
34
|
+
// Refs let the debounced/unmount flush read the latest values without
|
|
35
|
+
// re-creating timers on every keystroke.
|
|
36
|
+
const workingRef = useRef(null);
|
|
37
|
+
const dataRef = useRef(data);
|
|
38
|
+
const statusRef = useRef('idle');
|
|
39
|
+
const timerRef = useRef(null);
|
|
40
|
+
const createRef = useRef(create);
|
|
41
|
+
const updateRef = useRef(update);
|
|
42
|
+
const fileIdRef = useRef(fileId);
|
|
43
|
+
// Id of a file this hook created on first save (when opened with no fileId).
|
|
44
|
+
// Used for subsequent updates and to recognize the caller echoing it back
|
|
45
|
+
// into `fileId`.
|
|
46
|
+
const createdIdRef = useRef(null);
|
|
47
|
+
const createSeedRef = useRef(createSeed);
|
|
48
|
+
const onFileCreatedRef = useRef(onFileCreated);
|
|
49
|
+
const onSaveErrorRef = useRef(onSaveError);
|
|
50
|
+
const debugRef = useRef(debugLogging);
|
|
51
|
+
// JSON of the canvas we last wrote, to recognize (and ignore) the snapshot
|
|
52
|
+
// echo of our own write when reconciling incoming remote changes.
|
|
53
|
+
const lastSavedJsonRef = useRef(null);
|
|
54
|
+
// Guards against two creates racing if flushes overlap.
|
|
55
|
+
const creatingRef = useRef(false);
|
|
56
|
+
workingRef.current = working;
|
|
57
|
+
dataRef.current = data;
|
|
58
|
+
statusRef.current = saveStatus;
|
|
59
|
+
createRef.current = create;
|
|
60
|
+
updateRef.current = update;
|
|
61
|
+
createSeedRef.current = createSeed;
|
|
62
|
+
onFileCreatedRef.current = onFileCreated;
|
|
63
|
+
onSaveErrorRef.current = onSaveError;
|
|
64
|
+
debugRef.current = debugLogging;
|
|
65
|
+
const setStatus = useCallback((next) => {
|
|
66
|
+
statusRef.current = next;
|
|
67
|
+
setSaveStatus(next);
|
|
68
|
+
}, []);
|
|
69
|
+
// Reset working state when the target document changes so the next snapshot
|
|
70
|
+
// hydrates the new file rather than leaking the previous one. Skip the reset
|
|
71
|
+
// when the new fileId is one we just created (the caller echoing it back) —
|
|
72
|
+
// our working state is already correct and must not be wiped.
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (fileIdRef.current === fileId)
|
|
75
|
+
return;
|
|
76
|
+
fileIdRef.current = fileId;
|
|
77
|
+
if (fileId !== null && fileId === createdIdRef.current)
|
|
78
|
+
return;
|
|
79
|
+
if (timerRef.current) {
|
|
80
|
+
clearTimeout(timerRef.current);
|
|
81
|
+
timerRef.current = null;
|
|
82
|
+
}
|
|
83
|
+
createdIdRef.current = null;
|
|
84
|
+
lastSavedJsonRef.current = null;
|
|
85
|
+
setWorking(null);
|
|
86
|
+
setStatus('idle');
|
|
87
|
+
}, [fileId, setStatus]);
|
|
88
|
+
// Hydrate / reconcile from the persisted doc.
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
// First load — always hydrate.
|
|
91
|
+
if (workingRef.current === null) {
|
|
92
|
+
setWorking(hydrateCanvasState(data, fallbackViewport));
|
|
93
|
+
lastSavedJsonRef.current = data?.fileData.canvas
|
|
94
|
+
? JSON.stringify(data.fileData.canvas)
|
|
95
|
+
: null;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// A write of ours is queued or in flight — ignore the snapshot; it is
|
|
99
|
+
// either the echo of our write or about to be superseded by it.
|
|
100
|
+
if (statusRef.current === 'saving' || timerRef.current !== null)
|
|
101
|
+
return;
|
|
102
|
+
// Clean locally: accept a genuine remote change, but ignore the echo of
|
|
103
|
+
// our own last write (same content).
|
|
104
|
+
const incoming = data?.fileData.canvas;
|
|
105
|
+
if (!incoming)
|
|
106
|
+
return;
|
|
107
|
+
const incomingJson = JSON.stringify(incoming);
|
|
108
|
+
if (incomingJson === lastSavedJsonRef.current)
|
|
109
|
+
return;
|
|
110
|
+
lastSavedJsonRef.current = incomingJson;
|
|
111
|
+
setWorking(hydrateCanvasState(data, fallbackViewport));
|
|
112
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
113
|
+
}, [data]);
|
|
114
|
+
const flush = useCallback(async () => {
|
|
115
|
+
if (timerRef.current) {
|
|
116
|
+
clearTimeout(timerRef.current);
|
|
117
|
+
timerRef.current = null;
|
|
118
|
+
}
|
|
119
|
+
const canvas = workingRef.current;
|
|
120
|
+
// Nothing to persist, or no real target scope yet.
|
|
121
|
+
if (!canvas || !scope)
|
|
122
|
+
return;
|
|
123
|
+
if (creatingRef.current)
|
|
124
|
+
return;
|
|
125
|
+
const json = JSON.stringify(canvas);
|
|
126
|
+
const id = fileIdRef.current ?? createdIdRef.current;
|
|
127
|
+
const mode = id ? 'update' : 'create';
|
|
128
|
+
const debug = debugRef.current;
|
|
129
|
+
if (debug) {
|
|
130
|
+
console.log('[useAnnotationCanvasDoc] save attempt', {
|
|
131
|
+
mode,
|
|
132
|
+
fileId: id,
|
|
133
|
+
scope,
|
|
134
|
+
bytes: json.length,
|
|
135
|
+
strokes: canvas.strokes.length,
|
|
136
|
+
shapes: canvas.shapes.length,
|
|
137
|
+
measurements: canvas.placedMeasurements.length,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// Round-trip through JSON to drop `undefined` keys (e.g. an empty canvas's
|
|
141
|
+
// viewport.backgroundImage/backgroundFit). Firestore — RN in particular —
|
|
142
|
+
// rejects undefined field values unless ignoreUndefinedProperties is set.
|
|
143
|
+
const canvasPayload = JSON.parse(json);
|
|
144
|
+
setStatus('saving');
|
|
145
|
+
try {
|
|
146
|
+
if (!id) {
|
|
147
|
+
// First save with no file — create the doc seeded with the canvas.
|
|
148
|
+
creatingRef.current = true;
|
|
149
|
+
const seed = createSeedRef.current;
|
|
150
|
+
const newId = await createRef.current({
|
|
151
|
+
type: FileUploadType.Canvas,
|
|
152
|
+
...(seed?.name !== undefined ? { name: seed.name } : {}),
|
|
153
|
+
fileData: buildFileData(seed?.fileType ?? 'sketch', seed?.isLabel, canvasPayload),
|
|
154
|
+
});
|
|
155
|
+
creatingRef.current = false;
|
|
156
|
+
createdIdRef.current = newId;
|
|
157
|
+
if (debug) {
|
|
158
|
+
console.log('[useAnnotationCanvasDoc] created file', newId);
|
|
159
|
+
}
|
|
160
|
+
onFileCreatedRef.current?.(newId);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
const doc = dataRef.current;
|
|
164
|
+
await updateRef.current(id, {
|
|
165
|
+
fileData: buildFileData(doc?.fileData.fileType ??
|
|
166
|
+
createSeedRef.current?.fileType ??
|
|
167
|
+
'sketch', doc?.fileData.isLabel ?? createSeedRef.current?.isLabel, canvasPayload),
|
|
168
|
+
});
|
|
169
|
+
if (debug) {
|
|
170
|
+
console.log('[useAnnotationCanvasDoc] updated file', id);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
lastSavedJsonRef.current = json;
|
|
174
|
+
// If new edits landed mid-flight, stay dirty and let the next debounce
|
|
175
|
+
// (or unmount) flush them.
|
|
176
|
+
const latest = workingRef.current;
|
|
177
|
+
if (latest && JSON.stringify(latest) !== json) {
|
|
178
|
+
setStatus('dirty');
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
setStatus('saved');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
creatingRef.current = false;
|
|
186
|
+
// Always log save failures with context — these are otherwise invisible
|
|
187
|
+
// (the canvas keeps working from local state).
|
|
188
|
+
console.error(`[useAnnotationCanvasDoc] ${mode} failed`, { fileId: id, scope, bytes: json.length }, e);
|
|
189
|
+
onSaveErrorRef.current?.(e);
|
|
190
|
+
setStatus('error');
|
|
191
|
+
}
|
|
192
|
+
}, [scope, setStatus]);
|
|
193
|
+
const onCommit = useCallback((patch) => {
|
|
194
|
+
setWorking((prev) => (prev ? applyPatch(prev, patch) : prev));
|
|
195
|
+
setStatus('dirty');
|
|
196
|
+
if (timerRef.current)
|
|
197
|
+
clearTimeout(timerRef.current);
|
|
198
|
+
timerRef.current = setTimeout(() => {
|
|
199
|
+
timerRef.current = null;
|
|
200
|
+
void flush();
|
|
201
|
+
}, debounceMs);
|
|
202
|
+
}, [debounceMs, flush, setStatus]);
|
|
203
|
+
// Flush any pending edits on unmount.
|
|
204
|
+
useEffect(() => () => {
|
|
205
|
+
if (timerRef.current) {
|
|
206
|
+
clearTimeout(timerRef.current);
|
|
207
|
+
timerRef.current = null;
|
|
208
|
+
void flush();
|
|
209
|
+
}
|
|
210
|
+
}, [flush]);
|
|
211
|
+
const canvas = useMemo(() => working ?? createEmptyCanvasState(fallbackViewport), [working, fallbackViewport]);
|
|
212
|
+
return {
|
|
213
|
+
canvas,
|
|
214
|
+
onCommit,
|
|
215
|
+
loading,
|
|
216
|
+
error,
|
|
217
|
+
saveStatus,
|
|
218
|
+
save: flush,
|
|
219
|
+
};
|
|
220
|
+
};
|
package/dist/exports.d.ts
CHANGED
|
@@ -13,13 +13,15 @@ export { AnnotationDataProviderContext, useAnnotationData, type AnnotationDataPr
|
|
|
13
13
|
export { useAnnotationDoc, type UseAnnotationDocResult, } from './data/hooks/useAnnotationDoc.js';
|
|
14
14
|
export { useAnnotationList, type UseAnnotationListResult, } from './data/hooks/useAnnotationList.js';
|
|
15
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';
|
|
16
18
|
export { InMemoryAnnotationProvider } from './data/InMemoryAnnotationProvider.js';
|
|
17
|
-
export type { AnnotationCanvasHandle
|
|
19
|
+
export type { AnnotationCanvasHandle } from './canvas/useAnnotationCanvasState.js';
|
|
18
20
|
export type { GestureConfig, PanTrigger, AnnotationCanvasInnerProps, } from './canvas/AnnotationCanvasInner.js';
|
|
19
21
|
export type { CanvasPointerEvent, Tool, ToolContext, ToolState, } from './canvas/Tool.js';
|
|
20
22
|
export type { MeasurementRef, PickMeasurement, } from './canvas/measurementPicker.js';
|
|
21
23
|
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, type ViewportApi, type ViewportState, } from './canvas/viewport.js';
|
|
22
|
-
export { createPenTool, type PenToolOptions
|
|
24
|
+
export { createPenTool, type PenToolOptions } from './canvas/tools/penTool.js';
|
|
23
25
|
export { createSelectTool } from './canvas/tools/selectTool.js';
|
|
24
26
|
export { createMeasurementStampTool, type MeasurementStampToolOptions, } from './canvas/tools/measurementStampTool.js';
|
|
25
|
-
export { createPanTool, type PanToolOptions
|
|
27
|
+
export { createPanTool, type PanToolOptions } from './canvas/tools/panTool.js';
|
package/dist/exports.js
CHANGED
|
@@ -18,9 +18,11 @@ export { AnnotationDataProviderContext, useAnnotationData, } from './data/Annota
|
|
|
18
18
|
export { useAnnotationDoc, } from './data/hooks/useAnnotationDoc.js';
|
|
19
19
|
export { useAnnotationList, } from './data/hooks/useAnnotationList.js';
|
|
20
20
|
export { useAnnotationMutations, } from './data/hooks/useAnnotationMutations.js';
|
|
21
|
+
export { useAnnotationCanvasDoc, } from './data/hooks/useAnnotationCanvasDoc.js';
|
|
22
|
+
export { hydrateCanvasState } from './data/canvasPersistence.js';
|
|
21
23
|
export { InMemoryAnnotationProvider } from './data/InMemoryAnnotationProvider.js';
|
|
22
24
|
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, } from './canvas/viewport.js';
|
|
23
|
-
export { createPenTool
|
|
25
|
+
export { createPenTool } from './canvas/tools/penTool.js';
|
|
24
26
|
export { createSelectTool } from './canvas/tools/selectTool.js';
|
|
25
27
|
export { createMeasurementStampTool, } from './canvas/tools/measurementStampTool.js';
|
|
26
|
-
export { createPanTool
|
|
28
|
+
export { createPanTool } from './canvas/tools/panTool.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
export * from './exports.js';
|
|
2
|
+
export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, type EnhancedMapping, type FormulaDefinition, type FormulaEvaluationOptions, } from './formulas/evaluateFormula.js';
|
|
3
|
+
export { calculateFormula } from './formulas/calculateFormula.js';
|
|
4
|
+
export { convertMicrometers } from './utils/micrometersToUnit.js';
|
|
5
|
+
export { parseMeasurement } from './utils/parseMeasurement.js';
|
|
6
|
+
export { useParseMeasurement } from './hooks/useParseMeasurement.js';
|
|
7
|
+
export * from './types/firestore.js';
|
|
8
|
+
export * from './types/layout.js';
|
|
9
|
+
export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
|
|
2
10
|
export { AnnotationCanvas, type AnnotationCanvasProps, type CanvasKitOpts, } from './canvas/AnnotationCanvas.js';
|
package/dist/index.js
CHANGED
|
@@ -3,4 +3,12 @@
|
|
|
3
3
|
// WithSkiaWeb. Metro/RN consumers resolve `index.native.ts` instead via
|
|
4
4
|
// the `react-native` condition in package.json `exports`.
|
|
5
5
|
export * from './exports.js';
|
|
6
|
+
export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, } from './formulas/evaluateFormula.js';
|
|
7
|
+
export { calculateFormula } from './formulas/calculateFormula.js';
|
|
8
|
+
export { convertMicrometers } from './utils/micrometersToUnit.js';
|
|
9
|
+
export { parseMeasurement } from './utils/parseMeasurement.js';
|
|
10
|
+
export { useParseMeasurement } from './hooks/useParseMeasurement.js';
|
|
11
|
+
export * from './types/firestore.js';
|
|
12
|
+
export * from './types/layout.js';
|
|
13
|
+
export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, } from './utils/tolerance.js';
|
|
6
14
|
export { AnnotationCanvas, } from './canvas/AnnotationCanvas.js';
|
|
@@ -100,12 +100,14 @@ export interface SelectedTemplate extends FirestoreDoc {
|
|
|
100
100
|
}
|
|
101
101
|
export declare enum FileUploadType {
|
|
102
102
|
Annotation = "annotation",
|
|
103
|
+
Canvas = "canvas",
|
|
103
104
|
Template = "template",
|
|
104
105
|
Background = "background",
|
|
105
106
|
LayoutGroup = "layoutGroup",
|
|
106
107
|
Calculator = "calculator",
|
|
107
108
|
Conversion = "conversion",
|
|
108
|
-
Note = "note"
|
|
109
|
+
Note = "note",
|
|
110
|
+
Label = "label"
|
|
109
111
|
}
|
|
110
112
|
export interface AnnotationFileData {
|
|
111
113
|
fileType: 'sketch' | 'document';
|
|
@@ -147,6 +149,7 @@ interface FileUploadBase {
|
|
|
147
149
|
id: string;
|
|
148
150
|
name: string;
|
|
149
151
|
thumbnailUrl: string | null;
|
|
152
|
+
folderPath?: string;
|
|
150
153
|
createdBy?: {
|
|
151
154
|
userId: string;
|
|
152
155
|
firstName?: string;
|
|
@@ -157,6 +160,9 @@ interface FileUploadBase {
|
|
|
157
160
|
export type FileUpload = FileUploadBase & ({
|
|
158
161
|
type: FileUploadType.Annotation;
|
|
159
162
|
fileData: AnnotationFileData;
|
|
163
|
+
} | {
|
|
164
|
+
type: FileUploadType.Canvas;
|
|
165
|
+
fileData: AnnotationFileData;
|
|
160
166
|
} | {
|
|
161
167
|
type: FileUploadType.Calculator;
|
|
162
168
|
fileData: CalculatorFileData;
|
|
@@ -175,6 +181,9 @@ export type FileUpload = FileUploadBase & ({
|
|
|
175
181
|
} | {
|
|
176
182
|
type: FileUploadType.Template;
|
|
177
183
|
fileData?: undefined;
|
|
184
|
+
} | {
|
|
185
|
+
type: FileUploadType.Label;
|
|
186
|
+
fileData: Label;
|
|
178
187
|
});
|
|
179
188
|
export interface Folder extends FirestoreDoc, Timestamps {
|
|
180
189
|
name: string;
|
|
@@ -247,7 +256,8 @@ export declare enum ColumnType {
|
|
|
247
256
|
SingleSelect = "singleSelect",
|
|
248
257
|
MultiSelect = "multiSelect",
|
|
249
258
|
Angle = "angle",
|
|
250
|
-
ConversionTable = "conversionTable"
|
|
259
|
+
ConversionTable = "conversionTable",
|
|
260
|
+
Instructions = "instructions"
|
|
251
261
|
}
|
|
252
262
|
export interface Formula extends FirestoreDoc, Timestamps {
|
|
253
263
|
expression: string;
|
|
@@ -269,6 +279,11 @@ export interface SelectColumnData {
|
|
|
269
279
|
export interface ConversionTableColumnData {
|
|
270
280
|
conversions: Conversion[];
|
|
271
281
|
}
|
|
282
|
+
export interface InstructionsColumnData {
|
|
283
|
+
text: string;
|
|
284
|
+
textColor: string;
|
|
285
|
+
backgroundColor: string;
|
|
286
|
+
}
|
|
272
287
|
interface ColumnConfigBase {
|
|
273
288
|
id: string;
|
|
274
289
|
name: string;
|
|
@@ -300,6 +315,9 @@ export type ColumnConfig = ColumnConfigBase & ({
|
|
|
300
315
|
} | {
|
|
301
316
|
type: ColumnType.Angle;
|
|
302
317
|
columnData?: undefined;
|
|
318
|
+
} | {
|
|
319
|
+
type: ColumnType.Instructions;
|
|
320
|
+
columnData: InstructionsColumnData;
|
|
303
321
|
});
|
|
304
322
|
export interface Row {
|
|
305
323
|
[key: string]: any;
|
package/dist/types/firestore.js
CHANGED
|
@@ -18,12 +18,16 @@ export var TemplateType;
|
|
|
18
18
|
export var FileUploadType;
|
|
19
19
|
(function (FileUploadType) {
|
|
20
20
|
FileUploadType["Annotation"] = "annotation";
|
|
21
|
+
// v2 Skia photo-annotation canvas. Distinct from the legacy PSPDFKit
|
|
22
|
+
// `Annotation` docs so file-open routing sends it to the canvas editor.
|
|
23
|
+
FileUploadType["Canvas"] = "canvas";
|
|
21
24
|
FileUploadType["Template"] = "template";
|
|
22
25
|
FileUploadType["Background"] = "background";
|
|
23
26
|
FileUploadType["LayoutGroup"] = "layoutGroup";
|
|
24
27
|
FileUploadType["Calculator"] = "calculator";
|
|
25
28
|
FileUploadType["Conversion"] = "conversion";
|
|
26
29
|
FileUploadType["Note"] = "note";
|
|
30
|
+
FileUploadType["Label"] = "label";
|
|
27
31
|
})(FileUploadType || (FileUploadType = {}));
|
|
28
32
|
export var GroupType;
|
|
29
33
|
(function (GroupType) {
|
|
@@ -41,6 +45,7 @@ export var ColumnType;
|
|
|
41
45
|
ColumnType["MultiSelect"] = "multiSelect";
|
|
42
46
|
ColumnType["Angle"] = "angle";
|
|
43
47
|
ColumnType["ConversionTable"] = "conversionTable";
|
|
48
|
+
ColumnType["Instructions"] = "instructions";
|
|
44
49
|
})(ColumnType || (ColumnType = {}));
|
|
45
50
|
export var MeasurementType;
|
|
46
51
|
(function (MeasurementType) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DecimalTolerance, FractionalTolerance, Units } from '../types/firestore.js';
|
|
2
|
+
export declare const roundToT1Values: (value: number, u: Units) => number;
|
|
2
3
|
export declare const convertMicrometers: (micrometers: number | null | undefined, unit: Units, fractionalTolerance: FractionalTolerance, decimalTolerance: DecimalTolerance) => {
|
|
3
4
|
value: string;
|
|
4
5
|
unit: string;
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
import { Units, convertUnitsToReadable, } from '../types/firestore.js';
|
|
2
2
|
import { create, all } from 'mathjs';
|
|
3
3
|
const math = create(all);
|
|
4
|
+
// The T1 rounds differently depending on the selected measurement unit.
|
|
5
|
+
// This function mirrors the rounding behavior implemented on the firmware.
|
|
6
|
+
// The value being rounded is in microns.
|
|
7
|
+
export const roundToT1Values = (value, u) => {
|
|
8
|
+
switch (u) {
|
|
9
|
+
case Units.Millimeters:
|
|
10
|
+
case Units.Centimeters:
|
|
11
|
+
return Math.round(value / 500) * 500;
|
|
12
|
+
case Units.Meters:
|
|
13
|
+
return Math.round(value / 1000) * 1000;
|
|
14
|
+
case Units.Inches:
|
|
15
|
+
return Math.round(value / 508) * 508;
|
|
16
|
+
case Units.Feet:
|
|
17
|
+
return Math.round(value / 304.8) * 304.8;
|
|
18
|
+
case Units.FeetInchesDecimal:
|
|
19
|
+
return Math.round(value / 508) * 508;
|
|
20
|
+
case Units.FractionalInches:
|
|
21
|
+
case Units.FeetInchesFractional:
|
|
22
|
+
return value;
|
|
23
|
+
default:
|
|
24
|
+
return Math.round(value / 500) * 500;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
4
27
|
export const convertMicrometers = (micrometers, unit, fractionalTolerance, decimalTolerance) => {
|
|
5
28
|
if (micrometers == null || isNaN(micrometers)) {
|
|
6
29
|
return { value: 'NaN', unit: '' };
|
|
7
30
|
}
|
|
8
31
|
try {
|
|
9
|
-
const converted = math.unit(micrometers, 'um');
|
|
32
|
+
const converted = math.unit(roundToT1Values(micrometers, unit), 'um');
|
|
10
33
|
let value = 0;
|
|
11
34
|
let displayUnit = '';
|
|
12
35
|
switch (unit) {
|
|
@@ -16,6 +16,10 @@ export declare const DEFAULT_TOLERANCE_COLORS: {
|
|
|
16
16
|
readonly IN_TOLERANCE: "#22c55e";
|
|
17
17
|
readonly OUT_OF_TOLERANCE: "#ef4444";
|
|
18
18
|
};
|
|
19
|
+
export declare const DEFAULT_TOLERANCE_SECONDARY_COLORS: {
|
|
20
|
+
readonly IN_TOLERANCE: "#268F0A";
|
|
21
|
+
readonly OUT_OF_TOLERANCE: "#A91507";
|
|
22
|
+
};
|
|
19
23
|
/**
|
|
20
24
|
* Calculates the appropriate color for a measurement based on tolerance thresholds
|
|
21
25
|
*
|
|
@@ -27,6 +31,17 @@ export declare const DEFAULT_TOLERANCE_COLORS: {
|
|
|
27
31
|
* @returns The color hex string to use for the measurement
|
|
28
32
|
*/
|
|
29
33
|
export declare function getToleranceColor(actualValue: number, target: number, min: number, max: number, toleranceConfig?: ToleranceConfig | null): string;
|
|
34
|
+
/**
|
|
35
|
+
* Calculates the appropriate color for a measurement based on tolerance thresholds
|
|
36
|
+
*
|
|
37
|
+
* @param actualValue - The measured value
|
|
38
|
+
* @param target - The target/nominal value
|
|
39
|
+
* @param min - The minimum acceptable value (used as negative tolerance from target)
|
|
40
|
+
* @param max - The maximum acceptable value (used as positive tolerance from target)
|
|
41
|
+
* @param toleranceConfig - Optional tolerance configuration with custom thresholds
|
|
42
|
+
* @returns The color hex string to use for the measurement
|
|
43
|
+
*/
|
|
44
|
+
export declare function getToleranceSecondaryColor(actualValue: number, target: number, min: number, max: number, toleranceConfig?: ToleranceConfig | null): string;
|
|
30
45
|
/**
|
|
31
46
|
* Calculates the deviation percentage of a measurement from its target
|
|
32
47
|
*
|
package/dist/utils/tolerance.js
CHANGED
|
@@ -5,6 +5,10 @@ export const DEFAULT_TOLERANCE_COLORS = {
|
|
|
5
5
|
IN_TOLERANCE: '#22c55e', // Green
|
|
6
6
|
OUT_OF_TOLERANCE: '#ef4444', // Red
|
|
7
7
|
};
|
|
8
|
+
export const DEFAULT_TOLERANCE_SECONDARY_COLORS = {
|
|
9
|
+
IN_TOLERANCE: '#268F0A', // Green
|
|
10
|
+
OUT_OF_TOLERANCE: '#A91507', // Red
|
|
11
|
+
};
|
|
8
12
|
/**
|
|
9
13
|
* Calculates the appropriate color for a measurement based on tolerance thresholds
|
|
10
14
|
*
|
|
@@ -42,6 +46,43 @@ export function getToleranceColor(actualValue, target, min, max, toleranceConfig
|
|
|
42
46
|
}
|
|
43
47
|
return applicableColor;
|
|
44
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Calculates the appropriate color for a measurement based on tolerance thresholds
|
|
51
|
+
*
|
|
52
|
+
* @param actualValue - The measured value
|
|
53
|
+
* @param target - The target/nominal value
|
|
54
|
+
* @param min - The minimum acceptable value (used as negative tolerance from target)
|
|
55
|
+
* @param max - The maximum acceptable value (used as positive tolerance from target)
|
|
56
|
+
* @param toleranceConfig - Optional tolerance configuration with custom thresholds
|
|
57
|
+
* @returns The color hex string to use for the measurement
|
|
58
|
+
*/
|
|
59
|
+
export function getToleranceSecondaryColor(actualValue, target, min, max, toleranceConfig) {
|
|
60
|
+
// If no tolerance config provided, use simple green/red logic
|
|
61
|
+
if (!toleranceConfig?.thresholds || toleranceConfig.thresholds.length === 0) {
|
|
62
|
+
const isWithinTolerance = actualValue >= target - min && actualValue <= target + max;
|
|
63
|
+
return isWithinTolerance
|
|
64
|
+
? DEFAULT_TOLERANCE_SECONDARY_COLORS.IN_TOLERANCE
|
|
65
|
+
: DEFAULT_TOLERANCE_SECONDARY_COLORS.OUT_OF_TOLERANCE;
|
|
66
|
+
}
|
|
67
|
+
// Calculate deviation percentage
|
|
68
|
+
const deviation = Math.abs(actualValue - target);
|
|
69
|
+
const toleranceRange = max + min; // Fixed: total tolerance range
|
|
70
|
+
const deviationPercentage = toleranceRange > 0 ? (deviation / toleranceRange) * 100 : 0;
|
|
71
|
+
// Sort thresholds by percentage to ensure correct evaluation order
|
|
72
|
+
const sortedThresholds = toleranceConfig.thresholds.sort((a, b) => a.percentage - b.percentage);
|
|
73
|
+
// If deviation is below the first threshold, it's in tolerance (green)
|
|
74
|
+
if (deviationPercentage < sortedThresholds[0].percentage) {
|
|
75
|
+
return DEFAULT_TOLERANCE_SECONDARY_COLORS.IN_TOLERANCE;
|
|
76
|
+
}
|
|
77
|
+
// Find the highest threshold that the deviation exceeds
|
|
78
|
+
let applicableColor = sortedThresholds[0].color;
|
|
79
|
+
for (const threshold of sortedThresholds) {
|
|
80
|
+
if (deviationPercentage >= threshold.percentage) {
|
|
81
|
+
applicableColor = threshold.color;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return applicableColor;
|
|
85
|
+
}
|
|
45
86
|
/**
|
|
46
87
|
* Calculates the deviation percentage of a measurement from its target
|
|
47
88
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reekon-tools/boldr-utils",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.9",
|
|
4
4
|
"description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
|
|
5
5
|
"author": "REEKON Tools",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc",
|
|
26
|
+
"prepack": "yarn run build",
|
|
26
27
|
"test": "vitest",
|
|
27
28
|
"coverage": "vitest run --coverage",
|
|
28
29
|
"format": "prettier --write .",
|