@reekon-tools/boldr-utils 1.6.4 → 1.6.5

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.
Files changed (67) hide show
  1. package/dist/canvas/AnnotationCanvas.d.ts +11 -0
  2. package/dist/canvas/AnnotationCanvas.js +10 -0
  3. package/dist/canvas/AnnotationCanvas.native.d.ts +8 -0
  4. package/dist/canvas/AnnotationCanvas.native.js +6 -0
  5. package/dist/canvas/AnnotationCanvasInner.d.ts +37 -0
  6. package/dist/canvas/AnnotationCanvasInner.js +179 -0
  7. package/dist/canvas/AnnotationCanvasInner.native.d.ts +33 -0
  8. package/dist/canvas/AnnotationCanvasInner.native.js +102 -0
  9. package/dist/canvas/AnnotationCanvasSkia.d.ts +26 -0
  10. package/dist/canvas/AnnotationCanvasSkia.js +19 -0
  11. package/dist/canvas/Tool.d.ts +38 -0
  12. package/dist/canvas/Tool.js +1 -0
  13. package/dist/canvas/elements/BackgroundImageElement.d.ts +9 -0
  14. package/dist/canvas/elements/BackgroundImageElement.js +37 -0
  15. package/dist/canvas/elements/MeasurementStampElement.d.ts +13 -0
  16. package/dist/canvas/elements/MeasurementStampElement.js +30 -0
  17. package/dist/canvas/elements/ShapeElement.d.ts +7 -0
  18. package/dist/canvas/elements/ShapeElement.js +62 -0
  19. package/dist/canvas/elements/StrokeElement.d.ts +7 -0
  20. package/dist/canvas/elements/StrokeElement.js +18 -0
  21. package/dist/canvas/measurementPicker.d.ts +10 -0
  22. package/dist/canvas/measurementPicker.js +1 -0
  23. package/dist/canvas/pointerAdapter.d.ts +3 -0
  24. package/dist/canvas/pointerAdapter.js +19 -0
  25. package/dist/canvas/stampLayout.d.ts +4 -0
  26. package/dist/canvas/stampLayout.js +8 -0
  27. package/dist/canvas/tools/measurementStampTool.d.ts +9 -0
  28. package/dist/canvas/tools/measurementStampTool.js +37 -0
  29. package/dist/canvas/tools/panTool.d.ts +5 -0
  30. package/dist/canvas/tools/panTool.js +25 -0
  31. package/dist/canvas/tools/penTool.d.ts +13 -0
  32. package/dist/canvas/tools/penTool.js +68 -0
  33. package/dist/canvas/tools/selectTool.d.ts +2 -0
  34. package/dist/canvas/tools/selectTool.js +182 -0
  35. package/dist/canvas/useAnnotationCanvasState.d.ts +53 -0
  36. package/dist/canvas/useAnnotationCanvasState.js +182 -0
  37. package/dist/canvas/viewport.d.ts +16 -0
  38. package/dist/canvas/viewport.js +54 -0
  39. package/dist/data/AnnotationDataContext.d.ts +8 -0
  40. package/dist/data/AnnotationDataContext.js +11 -0
  41. package/dist/data/AnnotationDataProvider.d.ts +65 -0
  42. package/dist/data/AnnotationDataProvider.js +4 -0
  43. package/dist/data/InMemoryAnnotationProvider.d.ts +30 -0
  44. package/dist/data/InMemoryAnnotationProvider.js +197 -0
  45. package/dist/data/hooks/useAnnotationDoc.d.ts +7 -0
  46. package/dist/data/hooks/useAnnotationDoc.js +33 -0
  47. package/dist/data/hooks/useAnnotationList.d.ts +7 -0
  48. package/dist/data/hooks/useAnnotationList.js +26 -0
  49. package/dist/data/hooks/useAnnotationMutations.d.ts +9 -0
  50. package/dist/data/hooks/useAnnotationMutations.js +11 -0
  51. package/dist/exports.d.ts +25 -0
  52. package/dist/exports.js +26 -0
  53. package/dist/index.d.ts +2 -8
  54. package/dist/index.js +6 -8
  55. package/dist/index.native.d.ts +5 -0
  56. package/dist/index.native.js +6 -0
  57. package/dist/types/annotation.d.ts +139 -0
  58. package/dist/types/annotation.js +147 -0
  59. package/dist/types/firestore.d.ts +4 -15
  60. package/dist/types/firestore.js +0 -2
  61. package/dist/utils/groups.d.ts +4 -0
  62. package/dist/utils/groups.js +3 -0
  63. package/dist/utils/micrometersToUnit.d.ts +0 -1
  64. package/dist/utils/micrometersToUnit.js +1 -24
  65. package/dist/utils/tolerance.d.ts +0 -15
  66. package/dist/utils/tolerance.js +0 -41
  67. package/package.json +33 -2
@@ -0,0 +1,16 @@
1
+ import type { Vec2 } from '../types/annotation.js';
2
+ export interface ViewportState {
3
+ zoom: number;
4
+ pan: Vec2;
5
+ }
6
+ export interface ViewportApi {
7
+ state: ViewportState;
8
+ screenToWorld(screen: Vec2): Vec2;
9
+ worldToScreen(world: Vec2): Vec2;
10
+ worldDistanceToScreen(distance: number): number;
11
+ }
12
+ export declare const createViewportApi: (state: ViewportState) => ViewportApi;
13
+ export declare const DEFAULT_VIEWPORT: ViewportState;
14
+ export declare const fitToScreen: (docWidth: number, docHeight: number, screenWidth: number, screenHeight: number, padding?: number) => ViewportState;
15
+ export declare const zoomAt: (state: ViewportState, focalScreen: Vec2, nextZoom: number) => ViewportState;
16
+ export declare const panBy: (state: ViewportState, deltaScreen: Vec2) => ViewportState;
@@ -0,0 +1,54 @@
1
+ export const createViewportApi = (state) => ({
2
+ state,
3
+ screenToWorld: ({ x, y }) => ({
4
+ x: x / state.zoom + state.pan.x,
5
+ y: y / state.zoom + state.pan.y,
6
+ }),
7
+ worldToScreen: ({ x, y }) => ({
8
+ x: (x - state.pan.x) * state.zoom,
9
+ y: (y - state.pan.y) * state.zoom,
10
+ }),
11
+ worldDistanceToScreen: (d) => d * state.zoom,
12
+ });
13
+ export const DEFAULT_VIEWPORT = {
14
+ zoom: 1,
15
+ pan: { x: 0, y: 0 },
16
+ };
17
+ // Returns a viewport that fits `docWidth x docHeight` into `screenWidth x
18
+ // screenHeight` with optional padding (screen-space pixels).
19
+ export const fitToScreen = (docWidth, docHeight, screenWidth, screenHeight, padding = 16) => {
20
+ const availableW = Math.max(1, screenWidth - padding * 2);
21
+ const availableH = Math.max(1, screenHeight - padding * 2);
22
+ const zoom = Math.min(availableW / docWidth, availableH / docHeight);
23
+ const renderedW = docWidth * zoom;
24
+ const renderedH = docHeight * zoom;
25
+ const offsetX = (screenWidth - renderedW) / 2;
26
+ const offsetY = (screenHeight - renderedH) / 2;
27
+ return {
28
+ zoom,
29
+ pan: { x: -offsetX / zoom, y: -offsetY / zoom },
30
+ };
31
+ };
32
+ // Zoom toward a focal screen point so the world point under the cursor stays
33
+ // fixed. Used for wheel-zoom on web and pinch on native.
34
+ export const zoomAt = (state, focalScreen, nextZoom) => {
35
+ const clampedZoom = Math.max(0.05, Math.min(50, nextZoom));
36
+ const focalWorld = {
37
+ x: focalScreen.x / state.zoom + state.pan.x,
38
+ y: focalScreen.y / state.zoom + state.pan.y,
39
+ };
40
+ return {
41
+ zoom: clampedZoom,
42
+ pan: {
43
+ x: focalWorld.x - focalScreen.x / clampedZoom,
44
+ y: focalWorld.y - focalScreen.y / clampedZoom,
45
+ },
46
+ };
47
+ };
48
+ export const panBy = (state, deltaScreen) => ({
49
+ zoom: state.zoom,
50
+ pan: {
51
+ x: state.pan.x - deltaScreen.x / state.zoom,
52
+ y: state.pan.y - deltaScreen.y / state.zoom,
53
+ },
54
+ });
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { AnnotationDataProvider } from './AnnotationDataProvider.js';
3
+ export interface AnnotationDataProviderProps {
4
+ value: AnnotationDataProvider;
5
+ children: ReactNode;
6
+ }
7
+ export declare const AnnotationDataProviderContext: ({ value, children, }: AnnotationDataProviderProps) => import("react/jsx-runtime").JSX.Element;
8
+ export declare const useAnnotationData: () => AnnotationDataProvider;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from 'react';
3
+ const AnnotationDataContext = createContext(null);
4
+ export const AnnotationDataProviderContext = ({ value, children, }) => (_jsx(AnnotationDataContext.Provider, { value: value, children: children }));
5
+ export const useAnnotationData = () => {
6
+ const provider = useContext(AnnotationDataContext);
7
+ if (!provider) {
8
+ throw new Error('useAnnotationData must be used inside <AnnotationDataProviderContext>');
9
+ }
10
+ return provider;
11
+ };
@@ -0,0 +1,65 @@
1
+ import type { FileUpload, FileUploadType, Measurement } from '../types/firestore.js';
2
+ export interface JobScope {
3
+ orgId: string;
4
+ projectId: string;
5
+ jobId: string;
6
+ }
7
+ export interface JobGroupScope extends JobScope {
8
+ groupId: string;
9
+ }
10
+ export type Unsubscribe = () => void;
11
+ export type FieldOp = {
12
+ kind: 'serverTimestamp';
13
+ } | {
14
+ kind: 'arrayUnion';
15
+ values: unknown[];
16
+ } | {
17
+ kind: 'arrayRemove';
18
+ values: unknown[];
19
+ } | {
20
+ kind: 'increment';
21
+ by: number;
22
+ } | {
23
+ kind: 'delete';
24
+ };
25
+ export declare const isFieldOp: (v: unknown) => v is FieldOp;
26
+ export type Patch<T> = {
27
+ [K in keyof T]?: T[K] | FieldOp;
28
+ };
29
+ export interface ImageBlob {
30
+ data: Blob | ArrayBuffer | string;
31
+ contentType: string;
32
+ filename?: string;
33
+ }
34
+ export interface UploadedImageRef {
35
+ storagePath: string;
36
+ downloadUrl: string;
37
+ width?: number;
38
+ height?: number;
39
+ bytes?: number;
40
+ }
41
+ export type AnnotationFile = Extract<FileUpload, {
42
+ type: FileUploadType.Annotation;
43
+ }>;
44
+ export interface AnnotationFileSummary {
45
+ id: string;
46
+ name: string;
47
+ thumbnailUrl: string | null;
48
+ fileType: 'sketch' | 'document';
49
+ isLabel?: boolean;
50
+ createdAt?: Date;
51
+ createdBy?: AnnotationFile['createdBy'];
52
+ }
53
+ export interface AnnotationDataProvider {
54
+ create(scope: JobGroupScope, seed: Partial<AnnotationFile>): Promise<string>;
55
+ get(scope: JobGroupScope, fileId: string): Promise<AnnotationFile | null>;
56
+ update(scope: JobGroupScope, fileId: string, patch: Patch<AnnotationFile>): Promise<void>;
57
+ delete(scope: JobGroupScope, fileId: string): Promise<void>;
58
+ subscribe(scope: JobGroupScope, fileId: string, onNext: (doc: AnnotationFile | null) => void, onError?: (err: Error) => void): Unsubscribe;
59
+ list(scope: JobGroupScope, onNext: (files: AnnotationFileSummary[]) => void, onError?: (err: Error) => void): Unsubscribe;
60
+ subscribeGroupMeasurements(scope: JobGroupScope, onNext: (measurements: Measurement[]) => void, onError?: (err: Error) => void): Unsubscribe;
61
+ subscribeJobMeasurements(scope: JobScope, onNext: (measurements: Measurement[]) => void, onError?: (err: Error) => void): Unsubscribe;
62
+ uploadImage(scope: JobGroupScope, fileId: string, role: 'background' | 'thumbnail', blob: ImageBlob): Promise<UploadedImageRef>;
63
+ getImageUrl(scope: JobGroupScope, fileId: string, storagePath: string): Promise<string>;
64
+ deleteImage(scope: JobGroupScope, fileId: string, storagePath: string): Promise<void>;
65
+ }
@@ -0,0 +1,4 @@
1
+ export const isFieldOp = (v) => !!v &&
2
+ typeof v === 'object' &&
3
+ 'kind' in v &&
4
+ typeof v.kind === 'string';
@@ -0,0 +1,30 @@
1
+ import { type Measurement } from '../types/firestore.js';
2
+ import { type AnnotationDataProvider, type AnnotationFile, type AnnotationFileSummary, type ImageBlob, type JobGroupScope, type JobScope, type Patch, type Unsubscribe, type UploadedImageRef } from './AnnotationDataProvider.js';
3
+ export declare class InMemoryAnnotationProvider implements AnnotationDataProvider {
4
+ private docs;
5
+ private measurements;
6
+ private images;
7
+ private docListeners;
8
+ private listListeners;
9
+ private groupMeasurementListeners;
10
+ private jobMeasurementListeners;
11
+ private nextId;
12
+ setMeasurements(scope: JobGroupScope, measurements: Measurement[]): void;
13
+ create(scope: JobGroupScope, seed: Partial<AnnotationFile>): Promise<string>;
14
+ get(scope: JobGroupScope, fileId: string): Promise<AnnotationFile | null>;
15
+ update(scope: JobGroupScope, fileId: string, patch: Patch<AnnotationFile>): Promise<void>;
16
+ delete(scope: JobGroupScope, fileId: string): Promise<void>;
17
+ subscribe(scope: JobGroupScope, fileId: string, onNext: (doc: AnnotationFile | null) => void): Unsubscribe;
18
+ list(scope: JobGroupScope, onNext: (files: AnnotationFileSummary[]) => void): Unsubscribe;
19
+ subscribeGroupMeasurements(scope: JobGroupScope, onNext: (measurements: Measurement[]) => void): Unsubscribe;
20
+ subscribeJobMeasurements(scope: JobScope, onNext: (measurements: Measurement[]) => void): Unsubscribe;
21
+ uploadImage(scope: JobGroupScope, fileId: string, role: 'background' | 'thumbnail', blob: ImageBlob): Promise<UploadedImageRef>;
22
+ getImageUrl(_scope: JobGroupScope, fileId: string, storagePath: string): Promise<string>;
23
+ deleteImage(_scope: JobGroupScope, fileId: string, storagePath: string): Promise<void>;
24
+ private getBucket;
25
+ private notifyDoc;
26
+ private notifyList;
27
+ private notifyGroupMeasurements;
28
+ private notifyJobMeasurements;
29
+ private collectJobMeasurements;
30
+ }
@@ -0,0 +1,197 @@
1
+ import { FileUploadType, } from '../types/firestore.js';
2
+ import { isFieldOp, } from './AnnotationDataProvider.js';
3
+ const scopeKey = (s) => `${s.orgId}/${s.projectId}/${s.jobId}/${s.groupId}`;
4
+ const jobKey = (s) => `${s.orgId}/${s.projectId}/${s.jobId}`;
5
+ const summarize = (file) => ({
6
+ id: file.id,
7
+ name: file.name,
8
+ thumbnailUrl: file.thumbnailUrl,
9
+ fileType: file.fileData.fileType,
10
+ isLabel: file.fileData.isLabel,
11
+ createdAt: file.createdAt,
12
+ createdBy: file.createdBy,
13
+ });
14
+ const applyFieldOps = (patch) => {
15
+ const out = {};
16
+ for (const [key, value] of Object.entries(patch)) {
17
+ if (isFieldOp(value)) {
18
+ out[key] = resolveFieldOp(value);
19
+ }
20
+ else {
21
+ out[key] = value;
22
+ }
23
+ }
24
+ return out;
25
+ };
26
+ const resolveFieldOp = (op) => {
27
+ switch (op.kind) {
28
+ case 'serverTimestamp':
29
+ return new Date();
30
+ case 'arrayUnion':
31
+ return op.values;
32
+ case 'arrayRemove':
33
+ return [];
34
+ case 'increment':
35
+ return op.by;
36
+ case 'delete':
37
+ return undefined;
38
+ }
39
+ };
40
+ // Simple test/dev provider. Stores documents and image blobs in memory and
41
+ // fans out subscription notifications synchronously. Not designed for
42
+ // performance — designed for predictable behavior in tests and Storybook.
43
+ export class InMemoryAnnotationProvider {
44
+ constructor() {
45
+ this.docs = new Map();
46
+ this.measurements = new Map();
47
+ this.images = new Map();
48
+ this.docListeners = new Map();
49
+ this.listListeners = new Map();
50
+ this.groupMeasurementListeners = new Map();
51
+ this.jobMeasurementListeners = new Map();
52
+ this.nextId = 1;
53
+ }
54
+ // Seed helpers for tests
55
+ setMeasurements(scope, measurements) {
56
+ this.measurements.set(scopeKey(scope), measurements);
57
+ this.notifyGroupMeasurements(scope);
58
+ this.notifyJobMeasurements({
59
+ orgId: scope.orgId,
60
+ projectId: scope.projectId,
61
+ jobId: scope.jobId,
62
+ });
63
+ }
64
+ async create(scope, seed) {
65
+ const id = seed.id ?? `mem-${this.nextId++}`;
66
+ const file = {
67
+ id,
68
+ name: seed.name ?? 'Untitled annotation',
69
+ thumbnailUrl: seed.thumbnailUrl ?? null,
70
+ createdAt: seed.createdAt ?? new Date(),
71
+ createdBy: seed.createdBy,
72
+ type: FileUploadType.Annotation,
73
+ fileData: seed.fileData ?? { fileType: 'sketch' },
74
+ };
75
+ this.getBucket(scope).set(id, file);
76
+ this.notifyDoc(scope, id);
77
+ this.notifyList(scope);
78
+ return id;
79
+ }
80
+ async get(scope, fileId) {
81
+ return this.getBucket(scope).get(fileId) ?? null;
82
+ }
83
+ async update(scope, fileId, patch) {
84
+ const bucket = this.getBucket(scope);
85
+ const prev = bucket.get(fileId);
86
+ if (!prev)
87
+ throw new Error(`Annotation file ${fileId} not found`);
88
+ const resolved = applyFieldOps(patch);
89
+ const next = { ...prev, ...resolved };
90
+ bucket.set(fileId, next);
91
+ this.notifyDoc(scope, fileId);
92
+ this.notifyList(scope);
93
+ }
94
+ async delete(scope, fileId) {
95
+ this.getBucket(scope).delete(fileId);
96
+ this.notifyDoc(scope, fileId);
97
+ this.notifyList(scope);
98
+ }
99
+ subscribe(scope, fileId, onNext) {
100
+ const key = `${scopeKey(scope)}/${fileId}`;
101
+ const set = this.docListeners.get(key) ?? new Set();
102
+ set.add(onNext);
103
+ this.docListeners.set(key, set);
104
+ queueMicrotask(() => onNext(this.getBucket(scope).get(fileId) ?? null));
105
+ return () => {
106
+ set.delete(onNext);
107
+ };
108
+ }
109
+ list(scope, onNext) {
110
+ const key = scopeKey(scope);
111
+ const set = this.listListeners.get(key) ?? new Set();
112
+ set.add(onNext);
113
+ this.listListeners.set(key, set);
114
+ queueMicrotask(() => onNext(Array.from(this.getBucket(scope).values()).map(summarize)));
115
+ return () => {
116
+ set.delete(onNext);
117
+ };
118
+ }
119
+ subscribeGroupMeasurements(scope, onNext) {
120
+ const key = scopeKey(scope);
121
+ const set = this.groupMeasurementListeners.get(key) ?? new Set();
122
+ set.add(onNext);
123
+ this.groupMeasurementListeners.set(key, set);
124
+ queueMicrotask(() => onNext(this.measurements.get(key) ?? []));
125
+ return () => {
126
+ set.delete(onNext);
127
+ };
128
+ }
129
+ subscribeJobMeasurements(scope, onNext) {
130
+ const key = jobKey(scope);
131
+ const set = this.jobMeasurementListeners.get(key) ?? new Set();
132
+ set.add(onNext);
133
+ this.jobMeasurementListeners.set(key, set);
134
+ queueMicrotask(() => onNext(this.collectJobMeasurements(scope)));
135
+ return () => {
136
+ set.delete(onNext);
137
+ };
138
+ }
139
+ async uploadImage(scope, fileId, role, blob) {
140
+ const storagePath = `${scopeKey(scope)}/${fileId}/${role}`;
141
+ const ref = {
142
+ storagePath,
143
+ downloadUrl: `mem://${storagePath}`,
144
+ };
145
+ const bucket = this.images.get(fileId) ?? new Map();
146
+ bucket.set(storagePath, { blob, ref });
147
+ this.images.set(fileId, bucket);
148
+ return ref;
149
+ }
150
+ async getImageUrl(_scope, fileId, storagePath) {
151
+ const ref = this.images.get(fileId)?.get(storagePath);
152
+ if (!ref)
153
+ throw new Error(`Image ${storagePath} not found`);
154
+ return ref.ref.downloadUrl;
155
+ }
156
+ async deleteImage(_scope, fileId, storagePath) {
157
+ this.images.get(fileId)?.delete(storagePath);
158
+ }
159
+ getBucket(scope) {
160
+ const key = scopeKey(scope);
161
+ let bucket = this.docs.get(key);
162
+ if (!bucket) {
163
+ bucket = new Map();
164
+ this.docs.set(key, bucket);
165
+ }
166
+ return bucket;
167
+ }
168
+ notifyDoc(scope, fileId) {
169
+ const key = `${scopeKey(scope)}/${fileId}`;
170
+ const doc = this.getBucket(scope).get(fileId) ?? null;
171
+ this.docListeners.get(key)?.forEach((cb) => cb(doc));
172
+ }
173
+ notifyList(scope) {
174
+ const key = scopeKey(scope);
175
+ const files = Array.from(this.getBucket(scope).values()).map(summarize);
176
+ this.listListeners.get(key)?.forEach((cb) => cb(files));
177
+ }
178
+ notifyGroupMeasurements(scope) {
179
+ const key = scopeKey(scope);
180
+ const ms = this.measurements.get(key) ?? [];
181
+ this.groupMeasurementListeners.get(key)?.forEach((cb) => cb(ms));
182
+ }
183
+ notifyJobMeasurements(scope) {
184
+ const key = jobKey(scope);
185
+ const ms = this.collectJobMeasurements(scope);
186
+ this.jobMeasurementListeners.get(key)?.forEach((cb) => cb(ms));
187
+ }
188
+ collectJobMeasurements(scope) {
189
+ const prefix = `${jobKey(scope)}/`;
190
+ const out = [];
191
+ for (const [key, ms] of this.measurements) {
192
+ if (key.startsWith(prefix))
193
+ out.push(...ms);
194
+ }
195
+ return out;
196
+ }
197
+ }
@@ -0,0 +1,7 @@
1
+ import type { AnnotationFile, JobGroupScope } from '../AnnotationDataProvider.js';
2
+ export interface UseAnnotationDocResult {
3
+ data: AnnotationFile | null;
4
+ loading: boolean;
5
+ error: Error | null;
6
+ }
7
+ export declare const useAnnotationDoc: (scope: JobGroupScope | null, fileId: string | null) => UseAnnotationDocResult;
@@ -0,0 +1,33 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useAnnotationData } from '../AnnotationDataContext.js';
3
+ export const useAnnotationDoc = (scope, fileId) => {
4
+ const provider = useAnnotationData();
5
+ const [data, setData] = useState(null);
6
+ const [loading, setLoading] = useState(true);
7
+ const [error, setError] = useState(null);
8
+ useEffect(() => {
9
+ if (!scope || !fileId) {
10
+ setData(null);
11
+ setLoading(false);
12
+ return;
13
+ }
14
+ setLoading(true);
15
+ setError(null);
16
+ const unsubscribe = provider.subscribe(scope, fileId, (doc) => {
17
+ setData(doc);
18
+ setLoading(false);
19
+ }, (err) => {
20
+ setError(err);
21
+ setLoading(false);
22
+ });
23
+ return unsubscribe;
24
+ }, [
25
+ provider,
26
+ scope?.orgId,
27
+ scope?.projectId,
28
+ scope?.jobId,
29
+ scope?.groupId,
30
+ fileId,
31
+ ]);
32
+ return { data, loading, error };
33
+ };
@@ -0,0 +1,7 @@
1
+ import type { AnnotationFileSummary, JobGroupScope } from '../AnnotationDataProvider.js';
2
+ export interface UseAnnotationListResult {
3
+ files: AnnotationFileSummary[];
4
+ loading: boolean;
5
+ error: Error | null;
6
+ }
7
+ export declare const useAnnotationList: (scope: JobGroupScope | null) => UseAnnotationListResult;
@@ -0,0 +1,26 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useAnnotationData } from '../AnnotationDataContext.js';
3
+ export const useAnnotationList = (scope) => {
4
+ const provider = useAnnotationData();
5
+ const [files, setFiles] = useState([]);
6
+ const [loading, setLoading] = useState(true);
7
+ const [error, setError] = useState(null);
8
+ useEffect(() => {
9
+ if (!scope) {
10
+ setFiles([]);
11
+ setLoading(false);
12
+ return;
13
+ }
14
+ setLoading(true);
15
+ setError(null);
16
+ const unsubscribe = provider.list(scope, (next) => {
17
+ setFiles(next);
18
+ setLoading(false);
19
+ }, (err) => {
20
+ setError(err);
21
+ setLoading(false);
22
+ });
23
+ return unsubscribe;
24
+ }, [provider, scope?.orgId, scope?.projectId, scope?.jobId, scope?.groupId]);
25
+ return { files, loading, error };
26
+ };
@@ -0,0 +1,9 @@
1
+ import type { AnnotationFile, ImageBlob, JobGroupScope, Patch, UploadedImageRef } from '../AnnotationDataProvider.js';
2
+ export interface AnnotationMutations {
3
+ create(seed: Partial<AnnotationFile>): Promise<string>;
4
+ update(fileId: string, patch: Patch<AnnotationFile>): Promise<void>;
5
+ remove(fileId: string): Promise<void>;
6
+ uploadImage(fileId: string, role: 'background' | 'thumbnail', blob: ImageBlob): Promise<UploadedImageRef>;
7
+ deleteImage(fileId: string, storagePath: string): Promise<void>;
8
+ }
9
+ export declare const useAnnotationMutations: (scope: JobGroupScope) => AnnotationMutations;
@@ -0,0 +1,11 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { useAnnotationData } from '../AnnotationDataContext.js';
3
+ export const useAnnotationMutations = (scope) => {
4
+ const provider = useAnnotationData();
5
+ const create = useCallback((seed) => provider.create(scope, seed), [provider, scope.orgId, scope.projectId, scope.jobId, scope.groupId]);
6
+ const update = useCallback((fileId, patch) => provider.update(scope, fileId, patch), [provider, scope.orgId, scope.projectId, scope.jobId, scope.groupId]);
7
+ const remove = useCallback((fileId) => provider.delete(scope, fileId), [provider, scope.orgId, scope.projectId, scope.jobId, scope.groupId]);
8
+ const uploadImage = useCallback((fileId, role, blob) => provider.uploadImage(scope, fileId, role, blob), [provider, scope.orgId, scope.projectId, scope.jobId, scope.groupId]);
9
+ const deleteImage = useCallback((fileId, storagePath) => provider.deleteImage(scope, fileId, storagePath), [provider, scope.orgId, scope.projectId, scope.jobId, scope.groupId]);
10
+ return useMemo(() => ({ create, update, remove, uploadImage, deleteImage }), [create, update, remove, uploadImage, deleteImage]);
11
+ };
@@ -0,0 +1,25 @@
1
+ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, type EnhancedMapping, type FormulaDefinition, type FormulaEvaluationOptions, } from './formulas/evaluateFormula.js';
2
+ export { calculateFormula } from './formulas/calculateFormula.js';
3
+ export { convertMicrometers } from './utils/micrometersToUnit.js';
4
+ export { parseMeasurement } from './utils/parseMeasurement.js';
5
+ export { useParseMeasurement } from './hooks/useParseMeasurement.js';
6
+ export * from './types/firestore.js';
7
+ export * from './types/layout.js';
8
+ export * from './types/annotation.js';
9
+ export { getToleranceColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
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 { InMemoryAnnotationProvider } from './data/InMemoryAnnotationProvider.js';
17
+ export type { AnnotationCanvasHandle, } from './canvas/useAnnotationCanvasState.js';
18
+ export type { GestureConfig, PanTrigger, AnnotationCanvasInnerProps, } from './canvas/AnnotationCanvasInner.js';
19
+ export type { CanvasPointerEvent, Tool, ToolContext, ToolState, } from './canvas/Tool.js';
20
+ export type { MeasurementRef, PickMeasurement, } from './canvas/measurementPicker.js';
21
+ export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, type ViewportApi, type ViewportState, } from './canvas/viewport.js';
22
+ export { createPenTool, type PenToolOptions, } from './canvas/tools/penTool.js';
23
+ export { createSelectTool } from './canvas/tools/selectTool.js';
24
+ export { createMeasurementStampTool, type MeasurementStampToolOptions, } from './canvas/tools/measurementStampTool.js';
25
+ export { createPanTool, type PanToolOptions, } from './canvas/tools/panTool.js';
@@ -0,0 +1,26 @@
1
+ // Shared exports for both platform entry points. Everything that is NOT the
2
+ // platform-specific `AnnotationCanvas` wrapper lives here. The two index
3
+ // files (index.ts, index.native.ts) re-export from this and add their own
4
+ // AnnotationCanvas import.
5
+ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, } from './formulas/evaluateFormula.js';
6
+ export { calculateFormula } from './formulas/calculateFormula.js';
7
+ export { convertMicrometers } from './utils/micrometersToUnit.js';
8
+ export { parseMeasurement } from './utils/parseMeasurement.js';
9
+ export { useParseMeasurement } from './hooks/useParseMeasurement.js';
10
+ export * from './types/firestore.js';
11
+ export * from './types/layout.js';
12
+ export * from './types/annotation.js';
13
+ export { getToleranceColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, } from './utils/tolerance.js';
14
+ export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, } from './utils/groups.js';
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 { InMemoryAnnotationProvider } from './data/InMemoryAnnotationProvider.js';
22
+ export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, } from './canvas/viewport.js';
23
+ export { createPenTool, } from './canvas/tools/penTool.js';
24
+ export { createSelectTool } from './canvas/tools/selectTool.js';
25
+ export { createMeasurementStampTool, } from './canvas/tools/measurementStampTool.js';
26
+ export { createPanTool, } from './canvas/tools/panTool.js';
package/dist/index.d.ts CHANGED
@@ -1,8 +1,2 @@
1
- export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, type EnhancedMapping, type FormulaDefinition, type FormulaEvaluationOptions, } from './formulas/evaluateFormula.js';
2
- export { calculateFormula } from './formulas/calculateFormula.js';
3
- export { convertMicrometers } from './utils/micrometersToUnit.js';
4
- export { parseMeasurement } from './utils/parseMeasurement.js';
5
- export { useParseMeasurement } from './hooks/useParseMeasurement.js';
6
- export * from './types/firestore.js';
7
- export * from './types/layout.js';
8
- export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
1
+ export * from './exports.js';
2
+ export { AnnotationCanvas, type AnnotationCanvasProps, type CanvasKitOpts, } from './canvas/AnnotationCanvas.js';
package/dist/index.js CHANGED
@@ -1,8 +1,6 @@
1
- export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, } from './formulas/evaluateFormula.js';
2
- export { calculateFormula } from './formulas/calculateFormula.js';
3
- export { convertMicrometers } from './utils/micrometersToUnit.js';
4
- export { parseMeasurement } from './utils/parseMeasurement.js';
5
- export { useParseMeasurement } from './hooks/useParseMeasurement.js';
6
- export * from './types/firestore.js';
7
- export * from './types/layout.js';
8
- export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, } from './utils/tolerance.js';
1
+ // Web entry. Re-exports everything from the shared `exports.ts` plus the
2
+ // web-targeted `AnnotationCanvas` that lazy-loads CanvasKit via
3
+ // WithSkiaWeb. Metro/RN consumers resolve `index.native.ts` instead via
4
+ // the `react-native` condition in package.json `exports`.
5
+ export * from './exports.js';
6
+ export { AnnotationCanvas, } from './canvas/AnnotationCanvas.js';
@@ -0,0 +1,5 @@
1
+ export * from './exports.js';
2
+ export { AnnotationCanvas, type AnnotationCanvasProps, } from './canvas/AnnotationCanvas.native.js';
3
+ export interface CanvasKitOpts {
4
+ locateFile?: (file: string) => string;
5
+ }
@@ -0,0 +1,6 @@
1
+ // React Native entry. Resolved by Metro via the `react-native` condition in
2
+ // package.json `exports`. Pulls the native `AnnotationCanvas` (no
3
+ // WithSkiaWeb / canvaskit-wasm), avoiding the `fs` import that Metro
4
+ // chokes on.
5
+ export * from './exports.js';
6
+ export { AnnotationCanvas, } from './canvas/AnnotationCanvas.native.js';