@reekon-tools/boldr-utils 1.7.3 → 1.7.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.
- package/dist/canvas/AnnotationCanvas.d.ts +11 -0
- package/dist/canvas/AnnotationCanvas.js +10 -0
- package/dist/canvas/AnnotationCanvas.native.d.ts +8 -0
- package/dist/canvas/AnnotationCanvas.native.js +6 -0
- package/dist/canvas/AnnotationCanvasInner.d.ts +39 -0
- package/dist/canvas/AnnotationCanvasInner.js +219 -0
- package/dist/canvas/AnnotationCanvasInner.native.d.ts +35 -0
- package/dist/canvas/AnnotationCanvasInner.native.js +138 -0
- package/dist/canvas/AnnotationCanvasSkia.d.ts +27 -0
- package/dist/canvas/AnnotationCanvasSkia.js +20 -0
- package/dist/canvas/Tool.d.ts +38 -0
- package/dist/canvas/Tool.js +1 -0
- package/dist/canvas/elements/BackgroundImageElement.d.ts +9 -0
- package/dist/canvas/elements/BackgroundImageElement.js +37 -0
- package/dist/canvas/elements/MeasurementStampElement.d.ts +13 -0
- package/dist/canvas/elements/MeasurementStampElement.js +30 -0
- package/dist/canvas/elements/ShapeElement.d.ts +7 -0
- package/dist/canvas/elements/ShapeElement.js +62 -0
- package/dist/canvas/elements/StrokeElement.d.ts +7 -0
- package/dist/canvas/elements/StrokeElement.js +18 -0
- package/dist/canvas/measurementPicker.d.ts +10 -0
- package/dist/canvas/measurementPicker.js +1 -0
- package/dist/canvas/measurementStampOverlay.d.ts +11 -0
- package/dist/canvas/measurementStampOverlay.js +1 -0
- package/dist/canvas/pointerAdapter.d.ts +3 -0
- package/dist/canvas/pointerAdapter.js +19 -0
- package/dist/canvas/stampLayout.d.ts +5 -0
- package/dist/canvas/stampLayout.js +14 -0
- package/dist/canvas/tools/measurementStampTool.d.ts +9 -0
- package/dist/canvas/tools/measurementStampTool.js +37 -0
- package/dist/canvas/tools/panTool.d.ts +5 -0
- package/dist/canvas/tools/panTool.js +25 -0
- package/dist/canvas/tools/penTool.d.ts +13 -0
- package/dist/canvas/tools/penTool.js +68 -0
- package/dist/canvas/tools/selectTool.d.ts +2 -0
- package/dist/canvas/tools/selectTool.js +182 -0
- package/dist/canvas/useAnnotationCanvasState.d.ts +54 -0
- package/dist/canvas/useAnnotationCanvasState.js +210 -0
- package/dist/canvas/viewport.d.ts +16 -0
- package/dist/canvas/viewport.js +54 -0
- package/dist/data/AnnotationDataContext.d.ts +8 -0
- package/dist/data/AnnotationDataContext.js +11 -0
- package/dist/data/AnnotationDataProvider.d.ts +65 -0
- package/dist/data/AnnotationDataProvider.js +4 -0
- package/dist/data/InMemoryAnnotationProvider.d.ts +30 -0
- package/dist/data/InMemoryAnnotationProvider.js +197 -0
- package/dist/data/canvasPersistence.d.ts +3 -0
- package/dist/data/canvasPersistence.js +26 -0
- package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +33 -0
- package/dist/data/hooks/useAnnotationCanvasDoc.js +314 -0
- package/dist/data/hooks/useAnnotationDoc.d.ts +7 -0
- package/dist/data/hooks/useAnnotationDoc.js +33 -0
- package/dist/data/hooks/useAnnotationList.d.ts +7 -0
- package/dist/data/hooks/useAnnotationList.js +26 -0
- package/dist/data/hooks/useAnnotationMutations.d.ts +9 -0
- package/dist/data/hooks/useAnnotationMutations.js +11 -0
- package/dist/export/buildJobCsv.d.ts +26 -0
- package/dist/export/buildJobCsv.js +189 -0
- package/dist/export/config.d.ts +18 -0
- package/dist/export/config.js +72 -0
- package/dist/export/desktopLinks.d.ts +11 -0
- package/dist/export/desktopLinks.js +45 -0
- package/dist/export/objectKinds.d.ts +3 -0
- package/dist/export/objectKinds.js +28 -0
- package/dist/exports.d.ts +5 -1
- package/dist/exports.js +7 -1
- package/dist/hooks/useParseMeasurement.d.ts +4 -0
- package/dist/hooks/useParseMeasurement.js +14 -0
- package/dist/utils/evaluateFormula.d.ts +20 -0
- package/dist/utils/evaluateFormula.js +31 -0
- package/dist/utils/groups.d.ts +2 -1
- package/dist/utils/groups.js +14 -0
- package/package.json +1 -1
- package/dist/annotation/canvas/tools/measurementLineTool.d.ts +0 -12
- package/dist/annotation/canvas/tools/measurementLineTool.js +0 -95
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Shared model for the configurable job CSV export (rock-pro-app +
|
|
2
|
+
// rock-desktop). The config is persisted on the user doc (`exportConfig`) so
|
|
3
|
+
// it follows the user across devices and platforms; both apps must therefore
|
|
4
|
+
// read/write the same shape and tolerate values written by the other.
|
|
5
|
+
export const ALL_EXPORT_CATEGORIES = [
|
|
6
|
+
'defaultGroup',
|
|
7
|
+
'groups',
|
|
8
|
+
'forms',
|
|
9
|
+
];
|
|
10
|
+
export const ALL_EXPORT_OBJECT_TYPES = [
|
|
11
|
+
'calculators',
|
|
12
|
+
'files',
|
|
13
|
+
'photos',
|
|
14
|
+
'cad',
|
|
15
|
+
'labels',
|
|
16
|
+
'notes',
|
|
17
|
+
];
|
|
18
|
+
export const ALL_EXPORT_MEASUREMENT_DATA_FIELDS = ['measurement', 'label', 'notes'];
|
|
19
|
+
// Objects are opt-in: nothing ticked and images excluded until the user
|
|
20
|
+
// configures otherwise, so a default export is measurements-only.
|
|
21
|
+
export const DEFAULT_EXPORT_CONFIG = {
|
|
22
|
+
categories: [...ALL_EXPORT_CATEGORIES],
|
|
23
|
+
objects: [],
|
|
24
|
+
measurements: 'all',
|
|
25
|
+
imageFormat: 'none',
|
|
26
|
+
measurementData: [...ALL_EXPORT_MEASUREMENT_DATA_FIELDS],
|
|
27
|
+
};
|
|
28
|
+
// "Export All" (everything, ignoring the saved config): every category, every
|
|
29
|
+
// object kind as a hyperlink, all measurements, all list columns.
|
|
30
|
+
export const EXPORT_EVERYTHING_CONFIG = {
|
|
31
|
+
categories: [...ALL_EXPORT_CATEGORIES],
|
|
32
|
+
objects: [...ALL_EXPORT_OBJECT_TYPES],
|
|
33
|
+
measurements: 'all',
|
|
34
|
+
imageFormat: 'hyperlink',
|
|
35
|
+
measurementData: [...ALL_EXPORT_MEASUREMENT_DATA_FIELDS],
|
|
36
|
+
};
|
|
37
|
+
const isCategory = (v) => typeof v === 'string' && ALL_EXPORT_CATEGORIES.includes(v);
|
|
38
|
+
const isObjectType = (v) => typeof v === 'string' && ALL_EXPORT_OBJECT_TYPES.includes(v);
|
|
39
|
+
const isMeasurementFilter = (v) => v === 'all' || v === 'complete' || v === 'incomplete';
|
|
40
|
+
const isImageFormat = (v) => v === 'hyperlink' || v === 'none';
|
|
41
|
+
const isMeasurementDataField = (v) => typeof v === 'string' &&
|
|
42
|
+
ALL_EXPORT_MEASUREMENT_DATA_FIELDS.includes(v);
|
|
43
|
+
// Tolerant reader for the raw user-doc field. Missing/invalid fields fall
|
|
44
|
+
// back to defaults and unknown array members are dropped. Empty `categories`
|
|
45
|
+
// or `measurementData` fall back to all — the UIs can't save those, so treat
|
|
46
|
+
// them as corrupt. `objects` defaults to none (objects are opt-in), so
|
|
47
|
+
// missing and empty read the same.
|
|
48
|
+
export function normalizeExportConfig(raw) {
|
|
49
|
+
const r = raw && typeof raw === 'object' ? raw : {};
|
|
50
|
+
const categories = Array.isArray(r.categories)
|
|
51
|
+
? r.categories.filter(isCategory)
|
|
52
|
+
: [];
|
|
53
|
+
const objects = Array.isArray(r.objects)
|
|
54
|
+
? r.objects.filter(isObjectType)
|
|
55
|
+
: [];
|
|
56
|
+
const measurementData = Array.isArray(r.measurementData)
|
|
57
|
+
? r.measurementData.filter(isMeasurementDataField)
|
|
58
|
+
: [];
|
|
59
|
+
return {
|
|
60
|
+
categories: categories.length > 0 ? categories : [...ALL_EXPORT_CATEGORIES],
|
|
61
|
+
objects,
|
|
62
|
+
measurements: isMeasurementFilter(r.measurements)
|
|
63
|
+
? r.measurements
|
|
64
|
+
: DEFAULT_EXPORT_CONFIG.measurements,
|
|
65
|
+
imageFormat: isImageFormat(r.imageFormat)
|
|
66
|
+
? r.imageFormat
|
|
67
|
+
: DEFAULT_EXPORT_CONFIG.imageFormat,
|
|
68
|
+
measurementData: measurementData.length > 0
|
|
69
|
+
? measurementData
|
|
70
|
+
: [...ALL_EXPORT_MEASUREMENT_DATA_FIELDS],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type FileUpload } from '../types/firestore.js';
|
|
2
|
+
export type DesktopLinkContext = {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
orgId: string;
|
|
5
|
+
projectId: string;
|
|
6
|
+
jobId: string;
|
|
7
|
+
groupId: string;
|
|
8
|
+
isDefaultGroup: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function desktopBaseUrlForFirebaseProject(firebaseProjectId: string | undefined): string;
|
|
11
|
+
export declare function desktopFileEditorUrl(file: FileUpload, ctx: DesktopLinkContext): string | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { FileUploadType } from '../types/firestore.js';
|
|
2
|
+
// Maps the running mobile app's Firebase project onto the desktop
|
|
3
|
+
// deployment. Dev builds target a local rock-desktop dev server (`yarn dev`,
|
|
4
|
+
// Vite's default port) for testing.
|
|
5
|
+
export function desktopBaseUrlForFirebaseProject(firebaseProjectId) {
|
|
6
|
+
switch (firebaseProjectId) {
|
|
7
|
+
case 'rock-production-pro':
|
|
8
|
+
return 'https://app.boldrpro.com';
|
|
9
|
+
case 'rock-staging-pro':
|
|
10
|
+
return 'https://staging.app.boldrpro.com';
|
|
11
|
+
default:
|
|
12
|
+
return 'http://localhost:5173';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// Returns the desktop editor URL for an exported group file, or null when
|
|
16
|
+
// desktop has no editor for it (labels render inert there) — the CSV cell
|
|
17
|
+
// then degrades to the plain object name.
|
|
18
|
+
export function desktopFileEditorUrl(file, ctx) {
|
|
19
|
+
const { baseUrl, orgId, projectId, jobId, groupId, isDefaultGroup } = ctx;
|
|
20
|
+
const jobPath = `${baseUrl}/app/organizations/${orgId}/projects/${projectId}/jobs/${jobId}`;
|
|
21
|
+
const groupQuery = `groupId=${encodeURIComponent(groupId)}`;
|
|
22
|
+
switch (file.type) {
|
|
23
|
+
case FileUploadType.LayoutGroup:
|
|
24
|
+
return `${jobPath}/layout-group/${file.id}?${groupQuery}`;
|
|
25
|
+
case FileUploadType.Note:
|
|
26
|
+
return `${jobPath}/notes/${file.id}?${groupQuery}`;
|
|
27
|
+
case FileUploadType.Canvas:
|
|
28
|
+
return `${jobPath}/canvas/${file.id}?${groupQuery}`;
|
|
29
|
+
case FileUploadType.Annotation: {
|
|
30
|
+
// Pass a top-level fileType through when the doc carries one (desktop
|
|
31
|
+
// uses it to pick the 3D viewer); omit otherwise — the viewer defaults
|
|
32
|
+
// to the PDF path.
|
|
33
|
+
const rawFileType = file.fileType;
|
|
34
|
+
const fileTypeQuery = rawFileType
|
|
35
|
+
? `&fileType=${encodeURIComponent(rawFileType)}`
|
|
36
|
+
: '';
|
|
37
|
+
const defaultQuery = isDefaultGroup ? '&isDefault=true' : '';
|
|
38
|
+
return `${jobPath}/viewer?fileId=${encodeURIComponent(file.id)}&${groupQuery}${fileTypeQuery}${defaultQuery}`;
|
|
39
|
+
}
|
|
40
|
+
default:
|
|
41
|
+
// Labels (no desktop editor) and anything classifyFileForExport should
|
|
42
|
+
// already have excluded.
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { FileUploadType } from '../types/firestore.js';
|
|
2
|
+
// Maps a group file onto the export config's object kinds. `null` means the
|
|
3
|
+
// file is never exported: Calculator is a config placeholder for now (the
|
|
4
|
+
// checkbox persists but nothing exports until calculator export ships),
|
|
5
|
+
// Conversion/Template/Background are internal utility docs, and Annotation
|
|
6
|
+
// files flagged `isLabel` are label-print artifacts hidden from group grids.
|
|
7
|
+
export function classifyFileForExport(file) {
|
|
8
|
+
switch (file.type) {
|
|
9
|
+
case FileUploadType.LayoutGroup:
|
|
10
|
+
return 'cad';
|
|
11
|
+
case FileUploadType.Note:
|
|
12
|
+
return 'notes';
|
|
13
|
+
case FileUploadType.Label:
|
|
14
|
+
return 'labels';
|
|
15
|
+
case FileUploadType.Canvas:
|
|
16
|
+
return 'photos';
|
|
17
|
+
case FileUploadType.Annotation: {
|
|
18
|
+
const fileData = file.fileData;
|
|
19
|
+
if (fileData?.isLabel)
|
|
20
|
+
return null;
|
|
21
|
+
// Legacy Nutrient sketches count as photos for filtering, but still
|
|
22
|
+
// open in the document viewer (they have no Skia canvas state).
|
|
23
|
+
return fileData?.fileType === 'sketch' ? 'photos' : 'files';
|
|
24
|
+
}
|
|
25
|
+
default:
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/exports.d.ts
CHANGED
|
@@ -7,7 +7,11 @@ 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
|
-
export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, } from './utils/groups.js';
|
|
10
|
+
export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
|
|
11
|
+
export { ALL_EXPORT_CATEGORIES, ALL_EXPORT_MEASUREMENT_DATA_FIELDS, ALL_EXPORT_OBJECT_TYPES, DEFAULT_EXPORT_CONFIG, EXPORT_EVERYTHING_CONFIG, normalizeExportConfig, type ExportCategory, type ExportConfig, type ExportImageFormat, type ExportMeasurementDataField, type ExportMeasurementFilter, type ExportObjectType, } from './export/config.js';
|
|
12
|
+
export { classifyFileForExport } from './export/objectKinds.js';
|
|
13
|
+
export { buildJobCsv, escapeCSVField, hyperlinkCell, type BuildJobCsvInput, type ExportObjectRow, type ExportScope, } from './export/buildJobCsv.js';
|
|
14
|
+
export { desktopBaseUrlForFirebaseProject, desktopFileEditorUrl, type DesktopLinkContext, } from './export/desktopLinks.js';
|
|
11
15
|
export { numberToLetterIndex, formGroupInputLabel, listGroupMeasurementLabel, } from './utils/indexLabels.js';
|
|
12
16
|
export { toSelectedValues, formatSelectedValues, toStoredValue, } from './utils/selectValues.js';
|
|
13
17
|
export * from './calculator/index.js';
|
package/dist/exports.js
CHANGED
|
@@ -11,7 +11,13 @@ 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
|
-
export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, } from './utils/groups.js';
|
|
14
|
+
export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
|
|
15
|
+
// Configurable job CSV export, shared by rock-pro-app and rock-desktop so
|
|
16
|
+
// both platforms emit identical files.
|
|
17
|
+
export { ALL_EXPORT_CATEGORIES, ALL_EXPORT_MEASUREMENT_DATA_FIELDS, ALL_EXPORT_OBJECT_TYPES, DEFAULT_EXPORT_CONFIG, EXPORT_EVERYTHING_CONFIG, normalizeExportConfig, } from './export/config.js';
|
|
18
|
+
export { classifyFileForExport } from './export/objectKinds.js';
|
|
19
|
+
export { buildJobCsv, escapeCSVField, hyperlinkCell, } from './export/buildJobCsv.js';
|
|
20
|
+
export { desktopBaseUrlForFirebaseProject, desktopFileEditorUrl, } from './export/desktopLinks.js';
|
|
15
21
|
export { numberToLetterIndex, formGroupInputLabel, listGroupMeasurementLabel, } from './utils/indexLabels.js';
|
|
16
22
|
export { toSelectedValues, formatSelectedValues, toStoredValue, } from './utils/selectValues.js';
|
|
17
23
|
// Construction Calculator shared module (schema, units, evaluator, solver,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { parseMeasurement } from '../utils/parseMeasurement.js';
|
|
3
|
+
export const useParseMeasurement = () => {
|
|
4
|
+
const [error, setError] = useState(null);
|
|
5
|
+
const parseMeasurementInput = (input, defaultUnit = 'mm') => {
|
|
6
|
+
setError(null);
|
|
7
|
+
const result = parseMeasurement(input, defaultUnit);
|
|
8
|
+
if (result === null) {
|
|
9
|
+
setError('Invalid measurement. Please provide a valid number and unit.');
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
};
|
|
13
|
+
return { parseMeasurementInput, error };
|
|
14
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type MeasurementMap = Map<string, {
|
|
2
|
+
id: string;
|
|
3
|
+
value: number;
|
|
4
|
+
}[]>;
|
|
5
|
+
interface FormulaEvaluationOptions {
|
|
6
|
+
expression: string;
|
|
7
|
+
mappings: Record<string, string>;
|
|
8
|
+
group: {
|
|
9
|
+
id: string;
|
|
10
|
+
columns: Map<string, any>;
|
|
11
|
+
};
|
|
12
|
+
columns: {
|
|
13
|
+
id: string;
|
|
14
|
+
type: string;
|
|
15
|
+
}[];
|
|
16
|
+
measurementMap: MeasurementMap;
|
|
17
|
+
defaultUnit?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function evaluateFormula({ expression, mappings, group, columns, measurementMap, defaultUnit, }: FormulaEvaluationOptions): number | string;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { create, all } from 'mathjs';
|
|
2
|
+
const math = create(all);
|
|
3
|
+
export function evaluateFormula({ expression, mappings, group, columns, measurementMap, defaultUnit = 'um', }) {
|
|
4
|
+
const scope = {};
|
|
5
|
+
for (const [variable, columnId] of Object.entries(mappings)) {
|
|
6
|
+
const rawValue = group.columns.get(columnId);
|
|
7
|
+
const columnDef = columns.find((c) => c.id === columnId);
|
|
8
|
+
if (columnDef?.type === 'Measurement') {
|
|
9
|
+
const groupMeasurements = measurementMap.get(group.id) || [];
|
|
10
|
+
const measurement = groupMeasurements.find((m) => m.id === rawValue);
|
|
11
|
+
if (measurement?.value !== undefined) {
|
|
12
|
+
scope[variable] = math.unit(measurement.value, defaultUnit);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const parsedValue = typeof rawValue === 'number' ? rawValue : parseFloat(rawValue || '');
|
|
17
|
+
scope[variable] = isNaN(parsedValue) ? 0 : parsedValue;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const compiled = math.compile(expression);
|
|
21
|
+
const result = compiled.evaluate(scope);
|
|
22
|
+
if (math.typeOf(result) === 'Unit') {
|
|
23
|
+
return result.toNumber(defaultUnit);
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error(`Formula evaluation failed:`, err);
|
|
29
|
+
return 'Error';
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/utils/groups.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Group, type Section } from '../types/firestore.js';
|
|
2
2
|
export declare const DEFAULT_GROUP_INDEX = 0;
|
|
3
3
|
export declare const isDefaultGroup: (group: Pick<Group, "groupIndex"> | null | undefined) => boolean;
|
|
4
4
|
export declare const findDefaultGroup: <T extends Pick<Group, "groupIndex">>(groups: T[] | null | undefined) => T | null;
|
|
5
|
+
export declare const isFormGroupCompleted: (group: Group, section: Section) => boolean;
|
package/dist/utils/groups.js
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import { ColumnType } from '../types/firestore.js';
|
|
1
2
|
export const DEFAULT_GROUP_INDEX = 0;
|
|
2
3
|
export const isDefaultGroup = (group) => group?.groupIndex === DEFAULT_GROUP_INDEX;
|
|
3
4
|
export const findDefaultGroup = (groups) => groups?.find((g) => g.groupIndex === DEFAULT_GROUP_INDEX) ?? null;
|
|
5
|
+
// A FORM group (one row of its section's table) counts as complete when every
|
|
6
|
+
// Measurement column holds a value. Angle columns are intentionally excluded
|
|
7
|
+
// (mirrors the apps' header-completion computations), and a section with no
|
|
8
|
+
// Measurement columns is never "complete".
|
|
9
|
+
export const isFormGroupCompleted = (group, section) => {
|
|
10
|
+
const measurementColumns = section.tableConfig.filter((column) => column.type === ColumnType.Measurement);
|
|
11
|
+
if (measurementColumns.length === 0)
|
|
12
|
+
return false;
|
|
13
|
+
return measurementColumns.every((column) => {
|
|
14
|
+
const value = group.columns?.[column.id];
|
|
15
|
+
return value !== undefined && value !== '';
|
|
16
|
+
});
|
|
17
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { PlacedMeasurementRef } from '../../../types/annotation.js';
|
|
2
|
-
import type { Tool } from '../Tool.js';
|
|
3
|
-
export interface MeasurementLineToolOptions {
|
|
4
|
-
id?: string;
|
|
5
|
-
label?: string;
|
|
6
|
-
minDragPx?: number;
|
|
7
|
-
autoSwitchToSelect?: boolean;
|
|
8
|
-
selectToolId?: string;
|
|
9
|
-
onAutoSwitch?: (toToolId: string) => void;
|
|
10
|
-
onPlaced?: (measurement: PlacedMeasurementRef) => void;
|
|
11
|
-
}
|
|
12
|
-
export declare const createMeasurementLineTool: (options?: MeasurementLineToolOptions) => Tool;
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_LAYER_ID } from '../../../types/annotation.js';
|
|
2
|
-
import { DEFAULT_LINE_POS, recomputeAnchor } from '../measurementGeometry.js';
|
|
3
|
-
let counter = 0;
|
|
4
|
-
const makeId = () => `annotation-${Date.now().toString(36)}-${(counter++).toString(36)}`;
|
|
5
|
-
const firstLayerId = (doc) => doc.layers[0]?.id ?? DEFAULT_LAYER_ID;
|
|
6
|
-
// Build the blank line-measurement annotation for a drag from `a` to `b`: a
|
|
7
|
-
// 2-point line with a value tile magnetized to its center (linePos 0.5). The
|
|
8
|
-
// payload mirrors useAnnotationCanvasState's placeAnnotationAtCenter so a drawn
|
|
9
|
-
// line and a (legacy) center-placed one are byte-identical once committed.
|
|
10
|
-
const buildLineMeasurement = (opts) => {
|
|
11
|
-
const line = { a: opts.a, b: opts.b };
|
|
12
|
-
return {
|
|
13
|
-
id: opts.id,
|
|
14
|
-
layerId: opts.layerId,
|
|
15
|
-
placement: 'line',
|
|
16
|
-
line,
|
|
17
|
-
linePos: DEFAULT_LINE_POS,
|
|
18
|
-
// Center of the line; recomputeAnchor keeps this in sync on edits.
|
|
19
|
-
anchor: recomputeAnchor(line, 'line', DEFAULT_LINE_POS, {
|
|
20
|
-
x: (opts.a.x + opts.b.x) / 2,
|
|
21
|
-
y: (opts.a.y + opts.b.y) / 2,
|
|
22
|
-
}),
|
|
23
|
-
showLabel: true,
|
|
24
|
-
showValue: true,
|
|
25
|
-
createdAt: Date.now(),
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
// Drag-to-draw measurement-line tool. Press-drag rubber-bands a measurement
|
|
29
|
-
// annotation (a line with a blank value tile at its center); release commits
|
|
30
|
-
// it. Mirrors createShapeTool's interaction so "input lines" are placed the
|
|
31
|
-
// same way as shapes — draw to add, not tap-the-icon-to-add — replacing the
|
|
32
|
-
// old center-place affordance. The annotation stays blank (no measurement
|
|
33
|
-
// associated) until the user fills it via the tile / picker. Skia-free, like
|
|
34
|
-
// every tool factory; it renders its live preview through ctx.preview, so it
|
|
35
|
-
// works on web and (via the generic tool-pan dispatch) on native.
|
|
36
|
-
export const createMeasurementLineTool = (options = {}) => {
|
|
37
|
-
const minDragPx = options.minDragPx ?? 4;
|
|
38
|
-
const autoSwitchToSelect = options.autoSwitchToSelect ?? true;
|
|
39
|
-
const selectToolId = options.selectToolId ?? 'select';
|
|
40
|
-
return {
|
|
41
|
-
id: options.id ?? 'measure-line',
|
|
42
|
-
label: options.label ?? 'Measurement line',
|
|
43
|
-
cursor: 'crosshair',
|
|
44
|
-
onPointerDown(event) {
|
|
45
|
-
return {
|
|
46
|
-
kind: 'measurement-line-drawing',
|
|
47
|
-
id: makeId(),
|
|
48
|
-
startWorld: event.world,
|
|
49
|
-
startScreen: event.screen,
|
|
50
|
-
moved: false,
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
onPointerMove(event, ctx, state) {
|
|
54
|
-
const s = state;
|
|
55
|
-
if (s?.kind !== 'measurement-line-drawing')
|
|
56
|
-
return s;
|
|
57
|
-
const measurement = buildLineMeasurement({
|
|
58
|
-
id: s.id,
|
|
59
|
-
layerId: firstLayerId(ctx.document),
|
|
60
|
-
a: s.startWorld,
|
|
61
|
-
b: event.world,
|
|
62
|
-
});
|
|
63
|
-
ctx.preview({ ops: [{ op: 'addMeasurement', measurement }] });
|
|
64
|
-
return { ...s, moved: true };
|
|
65
|
-
},
|
|
66
|
-
onPointerUp(event, ctx, state) {
|
|
67
|
-
const s = state;
|
|
68
|
-
if (s?.kind !== 'measurement-line-drawing')
|
|
69
|
-
return;
|
|
70
|
-
const dx = event.screen.x - s.startScreen.x;
|
|
71
|
-
const dy = event.screen.y - s.startScreen.y;
|
|
72
|
-
if (!s.moved || dx * dx + dy * dy < minDragPx * minDragPx) {
|
|
73
|
-
// Accidental tap — discard the rubber-band, commit nothing.
|
|
74
|
-
ctx.preview({ ops: [] });
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const measurement = buildLineMeasurement({
|
|
78
|
-
id: s.id,
|
|
79
|
-
layerId: firstLayerId(ctx.document),
|
|
80
|
-
a: s.startWorld,
|
|
81
|
-
b: event.world,
|
|
82
|
-
});
|
|
83
|
-
ctx.commit({ ops: [{ op: 'addMeasurement', measurement }] });
|
|
84
|
-
// Leave it selected (and hand back to select) so the blank line can be
|
|
85
|
-
// filled / moved straight away — the same flow as the text tool.
|
|
86
|
-
ctx.setSelection({ ids: [measurement.id] });
|
|
87
|
-
options.onPlaced?.(measurement);
|
|
88
|
-
if (autoSwitchToSelect)
|
|
89
|
-
options.onAutoSwitch?.(selectToolId);
|
|
90
|
-
},
|
|
91
|
-
onCancel(_state, ctx) {
|
|
92
|
-
ctx.preview({ ops: [] });
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
};
|