@reekon-tools/boldr-utils 1.8.3 → 1.8.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/annotation/canvas/AnnotationCanvasInner.native.js +41 -10
- package/dist/annotation/canvas/measurementGeometry.js +8 -8
- package/dist/annotation/canvas/stampLayout.js +59 -54
- package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +4 -1
- package/dist/annotation/canvas/useAnnotationCanvasState.js +88 -38
- package/dist/calculator/evaluate.js +38 -4
- package/dist/calculator/schema.d.ts +12 -0
- package/dist/calculator/schema.js +11 -5
- package/dist/calculator/validate.d.ts +2 -0
- package/dist/calculator/validate.js +43 -1
- package/dist/exports.d.ts +1 -0
- package/dist/exports.js +2 -0
- package/dist/theme/colors.d.ts +101 -0
- package/dist/theme/colors.js +101 -0
- package/package.json +7 -2
- package/dist/canvas/AnnotationCanvas.d.ts +0 -11
- package/dist/canvas/AnnotationCanvas.js +0 -10
- package/dist/canvas/AnnotationCanvas.native.d.ts +0 -8
- package/dist/canvas/AnnotationCanvas.native.js +0 -6
- package/dist/canvas/AnnotationCanvasInner.d.ts +0 -39
- package/dist/canvas/AnnotationCanvasInner.js +0 -219
- package/dist/canvas/AnnotationCanvasInner.native.d.ts +0 -35
- package/dist/canvas/AnnotationCanvasInner.native.js +0 -138
- package/dist/canvas/AnnotationCanvasSkia.d.ts +0 -27
- package/dist/canvas/AnnotationCanvasSkia.js +0 -20
- package/dist/canvas/Tool.d.ts +0 -38
- package/dist/canvas/Tool.js +0 -1
- package/dist/canvas/elements/BackgroundImageElement.d.ts +0 -9
- package/dist/canvas/elements/BackgroundImageElement.js +0 -37
- package/dist/canvas/elements/MeasurementStampElement.d.ts +0 -13
- package/dist/canvas/elements/MeasurementStampElement.js +0 -30
- package/dist/canvas/elements/ShapeElement.d.ts +0 -7
- package/dist/canvas/elements/ShapeElement.js +0 -62
- package/dist/canvas/elements/StrokeElement.d.ts +0 -7
- package/dist/canvas/elements/StrokeElement.js +0 -18
- package/dist/canvas/measurementPicker.d.ts +0 -10
- package/dist/canvas/measurementPicker.js +0 -1
- package/dist/canvas/measurementStampOverlay.d.ts +0 -11
- package/dist/canvas/measurementStampOverlay.js +0 -1
- package/dist/canvas/pointerAdapter.d.ts +0 -3
- package/dist/canvas/pointerAdapter.js +0 -19
- package/dist/canvas/stampLayout.d.ts +0 -5
- package/dist/canvas/stampLayout.js +0 -14
- package/dist/canvas/tools/measurementStampTool.d.ts +0 -9
- package/dist/canvas/tools/measurementStampTool.js +0 -37
- package/dist/canvas/tools/panTool.d.ts +0 -5
- package/dist/canvas/tools/panTool.js +0 -25
- package/dist/canvas/tools/penTool.d.ts +0 -13
- package/dist/canvas/tools/penTool.js +0 -68
- package/dist/canvas/tools/selectTool.d.ts +0 -2
- package/dist/canvas/tools/selectTool.js +0 -182
- package/dist/canvas/useAnnotationCanvasState.d.ts +0 -54
- package/dist/canvas/useAnnotationCanvasState.js +0 -210
- package/dist/canvas/viewport.d.ts +0 -16
- package/dist/canvas/viewport.js +0 -54
- package/dist/data/AnnotationDataContext.d.ts +0 -8
- package/dist/data/AnnotationDataContext.js +0 -11
- package/dist/data/AnnotationDataProvider.d.ts +0 -65
- package/dist/data/AnnotationDataProvider.js +0 -4
- package/dist/data/InMemoryAnnotationProvider.d.ts +0 -30
- package/dist/data/InMemoryAnnotationProvider.js +0 -197
- package/dist/data/canvasPersistence.d.ts +0 -3
- package/dist/data/canvasPersistence.js +0 -26
- package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +0 -33
- package/dist/data/hooks/useAnnotationCanvasDoc.js +0 -314
- package/dist/data/hooks/useAnnotationDoc.d.ts +0 -7
- package/dist/data/hooks/useAnnotationDoc.js +0 -33
- package/dist/data/hooks/useAnnotationList.d.ts +0 -7
- package/dist/data/hooks/useAnnotationList.js +0 -26
- package/dist/data/hooks/useAnnotationMutations.d.ts +0 -9
- package/dist/data/hooks/useAnnotationMutations.js +0 -11
- package/dist/hooks/useParseMeasurement.d.ts +0 -4
- package/dist/hooks/useParseMeasurement.js +0 -14
- package/dist/utils/evaluateFormula.d.ts +0 -20
- package/dist/utils/evaluateFormula.js +0 -31
|
@@ -13,11 +13,17 @@ import { unitsForDimension } from './units.js';
|
|
|
13
13
|
// brackets) and every runtime field value is canonical — µm for length, µm²
|
|
14
14
|
// area, µm³ volume, degrees for angle, raw for dimensionless (see units.ts).
|
|
15
15
|
//
|
|
16
|
-
// EXPRESSION SEMANTICS: equations are math.js expressions
|
|
17
|
-
// canonical scope
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
16
|
+
// EXPRESSION SEMANTICS: equations are math.js expressions. By default they
|
|
17
|
+
// evaluate over a canonical scope, which makes numeric literals canonical too
|
|
18
|
+
// (a bare `2` multiplying a length is a plain scalar; a bare additive length
|
|
19
|
+
// constant would be micrometers). Per-equation `variableUnits` / `resultUnit`
|
|
20
|
+
// instead evaluate in author-chosen display units: each mapped variable is
|
|
21
|
+
// converted OUT of canonical into its unit before entering the scope, and the
|
|
22
|
+
// result is converted back FROM `resultUnit` into canonical. This is what
|
|
23
|
+
// makes literals meaningful ("roof area in ft² / 100 = roofing squares") —
|
|
24
|
+
// without it, a number-kind output consuming a measurement variable silently
|
|
25
|
+
// receives µm-scale values. The authoring UI always sets units; the bare
|
|
26
|
+
// canonical form survives for pre-existing definitions.
|
|
21
27
|
// ---------------------------------------------------------------------------
|
|
22
28
|
/**
|
|
23
29
|
* Version of the definition format itself. Bumped only on breaking schema
|
|
@@ -157,6 +157,8 @@ export declare const calculatorDefinitionSchema: z.ZodObject<{
|
|
|
157
157
|
targetFieldId: z.ZodString;
|
|
158
158
|
expression: z.ZodString;
|
|
159
159
|
variableToFieldId: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
160
|
+
variableUnits: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
161
|
+
resultUnit: z.ZodOptional<z.ZodString>;
|
|
160
162
|
}, z.core.$strip>>;
|
|
161
163
|
diagramFileId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
162
164
|
isPublic: z.ZodBoolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { create, all } from 'mathjs';
|
|
3
3
|
import { ColumnType, DecimalTolerance, FractionalTolerance, } from '../types/firestore.js';
|
|
4
|
-
import { CALCULATOR_SCHEMA_VERSION, equationForField, isEquationTargetKind, isNumericFieldKind, } from './schema.js';
|
|
4
|
+
import { CALCULATOR_SCHEMA_VERSION, equationForField, fieldDimension, isEquationTargetKind, isNumericFieldKind, } from './schema.js';
|
|
5
5
|
import { CALCULATOR_UNIT_INFO, unitDimension } from './units.js';
|
|
6
6
|
import { isCalculatorCategoryId } from './categories.js';
|
|
7
7
|
const math = create(all);
|
|
@@ -94,6 +94,8 @@ const calculatorEquationSchema = z.object({
|
|
|
94
94
|
targetFieldId: z.string().min(1),
|
|
95
95
|
expression: z.string().min(1),
|
|
96
96
|
variableToFieldId: z.record(z.string(), z.string()),
|
|
97
|
+
variableUnits: z.record(z.string(), calculatorUnitSchema).optional(),
|
|
98
|
+
resultUnit: calculatorUnitSchema.optional(),
|
|
97
99
|
});
|
|
98
100
|
export const calculatorDefinitionSchema = z.object({
|
|
99
101
|
id: z.string().min(1),
|
|
@@ -303,6 +305,46 @@ export const validateCalculatorDefinition = (input) => {
|
|
|
303
305
|
push('self-referencing-equation', `${path}.variableToFieldId.${variable}`, `Equation for "${eq.targetFieldId}" references its own target`);
|
|
304
306
|
}
|
|
305
307
|
}
|
|
308
|
+
// --- Unit annotations --------------------------------------------------
|
|
309
|
+
for (const [variable, unit] of Object.entries(eq.variableUnits ?? {})) {
|
|
310
|
+
const unitPath = `${path}.variableUnits.${variable}`;
|
|
311
|
+
const mappedId = eq.variableToFieldId[variable];
|
|
312
|
+
if (mappedId == null) {
|
|
313
|
+
push('unit-for-unmapped-variable', unitPath, `Variable ${variable} has a unit but no field mapping`, 'warning');
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const mapped = fieldById.get(mappedId);
|
|
317
|
+
if (!mapped)
|
|
318
|
+
continue; // unknown-mapped-field already reported above
|
|
319
|
+
const dim = fieldDimension(mapped);
|
|
320
|
+
if (dim === 'none') {
|
|
321
|
+
push('variable-unit-on-dimensionless-field', unitPath, `Variable ${variable} maps to "${mapped.name}", which has no unit dimension`);
|
|
322
|
+
}
|
|
323
|
+
else if (unitDimension(unit) !== dim) {
|
|
324
|
+
push('variable-unit-dimension-mismatch', unitPath, `Unit "${unit}" is not a ${dim} unit (variable ${variable} maps to "${mapped.name}")`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (eq.resultUnit != null && target) {
|
|
328
|
+
const dim = fieldDimension(target);
|
|
329
|
+
if (dim === 'none') {
|
|
330
|
+
push('result-unit-on-number-target', `${path}.resultUnit`, `Equation target "${target.name}" is a number field and takes the result as-is; remove the result unit`);
|
|
331
|
+
}
|
|
332
|
+
else if (unitDimension(eq.resultUnit) !== dim) {
|
|
333
|
+
push('result-unit-dimension-mismatch', `${path}.resultUnit`, `Result unit "${eq.resultUnit}" is not a ${dim} unit (target "${target.name}")`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// The canonical-leak trap: a number target consuming a unit-carrying
|
|
337
|
+
// field without a variable unit receives raw µm-scale values.
|
|
338
|
+
if (target?.kind === ColumnType.Number) {
|
|
339
|
+
for (const [variable, fieldId] of Object.entries(eq.variableToFieldId)) {
|
|
340
|
+
const mapped = fieldById.get(fieldId);
|
|
341
|
+
if (!mapped || fieldDimension(mapped) === 'none')
|
|
342
|
+
continue;
|
|
343
|
+
if (eq.variableUnits?.[variable] == null) {
|
|
344
|
+
push('canonical-value-into-number-target', `${path}.variableToFieldId.${variable}`, `"${target.name}" is a number field but variable ${variable} ("${mapped.name}") has no display unit — its canonical (µm-scale) value will be used. Pick a unit for ${variable}.`, 'warning');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
306
348
|
const symbols = expressionSymbols(eq.expression);
|
|
307
349
|
if (!symbols.ok) {
|
|
308
350
|
push('expression-invalid', `${path}.expression`, `Expression does not parse: ${symbols.error}`);
|
package/dist/exports.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ 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 { GeneralColors, SecondaryColors, FormColors, TileColors, FormulaColors, YellowColors, RedColors, GreenColors, tailwindColors, } from './theme/colors.js';
|
|
10
11
|
export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
|
|
11
12
|
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
13
|
export { classifyFileForExport } from './export/objectKinds.js';
|
package/dist/exports.js
CHANGED
|
@@ -11,6 +11,8 @@ 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
|
+
// Shared color palette (canonical source for both apps' brand colors).
|
|
15
|
+
export { GeneralColors, SecondaryColors, FormColors, TileColors, FormulaColors, YellowColors, RedColors, GreenColors, tailwindColors, } from './theme/colors.js';
|
|
14
16
|
export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
|
|
15
17
|
// Configurable job CSV export, shared by rock-pro-app and rock-desktop so
|
|
16
18
|
// both platforms emit identical files.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export declare const GeneralColors: {
|
|
2
|
+
readonly darkGrey: "#1D1F21";
|
|
3
|
+
readonly grey1: "#2B2C2E";
|
|
4
|
+
readonly grey2: "#424345";
|
|
5
|
+
readonly grey3: "#626364";
|
|
6
|
+
readonly grey4: "#9FA0A2";
|
|
7
|
+
readonly white: "#FFFFFF";
|
|
8
|
+
};
|
|
9
|
+
export declare const SecondaryColors: {
|
|
10
|
+
readonly accentBlue1: "#2D3843";
|
|
11
|
+
readonly accentBlue2: "#2E4C6A";
|
|
12
|
+
readonly accentBlue3: "#4B6074";
|
|
13
|
+
readonly accentBlue4: "#67819A";
|
|
14
|
+
};
|
|
15
|
+
export declare const FormColors: {
|
|
16
|
+
readonly lightGrey1: "#BCBEC2";
|
|
17
|
+
readonly lightGrey2: "#E8EAED";
|
|
18
|
+
};
|
|
19
|
+
export declare const TileColors: {
|
|
20
|
+
readonly darkBlue: "#02274B";
|
|
21
|
+
readonly activeBlue: "#0066FF";
|
|
22
|
+
readonly lightBlue1: "#50A6FF";
|
|
23
|
+
readonly lightBlue2: "#89C3FF";
|
|
24
|
+
};
|
|
25
|
+
export declare const FormulaColors: {
|
|
26
|
+
readonly darkPurple: "#6C155F";
|
|
27
|
+
readonly purple: "#BD30A8";
|
|
28
|
+
};
|
|
29
|
+
export declare const YellowColors: {
|
|
30
|
+
readonly mainYellow: "#FFAD00";
|
|
31
|
+
};
|
|
32
|
+
export declare const RedColors: {
|
|
33
|
+
readonly warningRed: "#DA1100";
|
|
34
|
+
readonly red: "#E83D2F";
|
|
35
|
+
readonly mutedRed: "#DF6358";
|
|
36
|
+
};
|
|
37
|
+
export declare const GreenColors: {
|
|
38
|
+
readonly warningGreen: "#00E920";
|
|
39
|
+
readonly green: "#58A942";
|
|
40
|
+
readonly mutedGreen: "#8BB380";
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The palette above shaped for Tailwind's `theme.extend.colors`. Spread it
|
|
44
|
+
* into the app's tailwind config (works with tailwindcss v3 and NativeWind):
|
|
45
|
+
*
|
|
46
|
+
* import { tailwindColors } from '@reekon-tools/boldr-utils/theme';
|
|
47
|
+
* export default {
|
|
48
|
+
* theme: { extend: { colors: { ...tailwindColors } } },
|
|
49
|
+
* };
|
|
50
|
+
*
|
|
51
|
+
* Class names follow the constant names, kebab-cased per group, e.g.
|
|
52
|
+
* `bg-general-dark-grey`, `text-secondary-accent-blue-1`, `bg-tile-active`,
|
|
53
|
+
* `border-form-light-grey-2`, `bg-formula`, `text-brand-red-muted`.
|
|
54
|
+
*
|
|
55
|
+
* The yellow/red/green groups are namespaced under `brand-*` so they don't
|
|
56
|
+
* shadow Tailwind's built-in `yellow`/`red`/`green` scales (extend replaces
|
|
57
|
+
* a color key wholesale, which would break classes like `text-red-500`).
|
|
58
|
+
*/
|
|
59
|
+
export declare const tailwindColors: {
|
|
60
|
+
readonly general: {
|
|
61
|
+
readonly 'dark-grey': "#1D1F21";
|
|
62
|
+
readonly 'grey-1': "#2B2C2E";
|
|
63
|
+
readonly 'grey-2': "#424345";
|
|
64
|
+
readonly 'grey-3': "#626364";
|
|
65
|
+
readonly 'grey-4': "#9FA0A2";
|
|
66
|
+
readonly white: "#FFFFFF";
|
|
67
|
+
};
|
|
68
|
+
readonly secondary: {
|
|
69
|
+
readonly 'accent-blue-1': "#2D3843";
|
|
70
|
+
readonly 'accent-blue-2': "#2E4C6A";
|
|
71
|
+
readonly 'accent-blue-3': "#4B6074";
|
|
72
|
+
readonly 'accent-blue-4': "#67819A";
|
|
73
|
+
};
|
|
74
|
+
readonly form: {
|
|
75
|
+
readonly 'light-grey-1': "#BCBEC2";
|
|
76
|
+
readonly 'light-grey-2': "#E8EAED";
|
|
77
|
+
};
|
|
78
|
+
readonly tile: {
|
|
79
|
+
readonly dark: "#02274B";
|
|
80
|
+
readonly active: "#0066FF";
|
|
81
|
+
readonly 'light-1': "#50A6FF";
|
|
82
|
+
readonly 'light-2': "#89C3FF";
|
|
83
|
+
};
|
|
84
|
+
readonly formula: {
|
|
85
|
+
readonly DEFAULT: "#BD30A8";
|
|
86
|
+
readonly dark: "#6C155F";
|
|
87
|
+
};
|
|
88
|
+
readonly 'brand-yellow': {
|
|
89
|
+
readonly DEFAULT: "#FFAD00";
|
|
90
|
+
};
|
|
91
|
+
readonly 'brand-red': {
|
|
92
|
+
readonly DEFAULT: "#E83D2F";
|
|
93
|
+
readonly warning: "#DA1100";
|
|
94
|
+
readonly muted: "#DF6358";
|
|
95
|
+
};
|
|
96
|
+
readonly 'brand-green': {
|
|
97
|
+
readonly DEFAULT: "#58A942";
|
|
98
|
+
readonly warning: "#00E920";
|
|
99
|
+
readonly muted: "#8BB380";
|
|
100
|
+
};
|
|
101
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export const GeneralColors = {
|
|
2
|
+
darkGrey: '#1D1F21',
|
|
3
|
+
grey1: '#2B2C2E',
|
|
4
|
+
grey2: '#424345',
|
|
5
|
+
grey3: '#626364',
|
|
6
|
+
grey4: '#9FA0A2',
|
|
7
|
+
white: '#FFFFFF',
|
|
8
|
+
};
|
|
9
|
+
export const SecondaryColors = {
|
|
10
|
+
accentBlue1: '#2D3843',
|
|
11
|
+
accentBlue2: '#2E4C6A',
|
|
12
|
+
accentBlue3: '#4B6074',
|
|
13
|
+
accentBlue4: '#67819A',
|
|
14
|
+
};
|
|
15
|
+
export const FormColors = {
|
|
16
|
+
lightGrey1: '#BCBEC2',
|
|
17
|
+
lightGrey2: '#E8EAED',
|
|
18
|
+
};
|
|
19
|
+
export const TileColors = {
|
|
20
|
+
darkBlue: '#02274B',
|
|
21
|
+
activeBlue: '#0066FF',
|
|
22
|
+
lightBlue1: '#50A6FF',
|
|
23
|
+
lightBlue2: '#89C3FF',
|
|
24
|
+
};
|
|
25
|
+
export const FormulaColors = {
|
|
26
|
+
darkPurple: '#6C155F',
|
|
27
|
+
purple: '#BD30A8',
|
|
28
|
+
};
|
|
29
|
+
export const YellowColors = {
|
|
30
|
+
mainYellow: '#FFAD00',
|
|
31
|
+
};
|
|
32
|
+
export const RedColors = {
|
|
33
|
+
warningRed: '#DA1100',
|
|
34
|
+
red: '#E83D2F',
|
|
35
|
+
mutedRed: '#DF6358',
|
|
36
|
+
};
|
|
37
|
+
export const GreenColors = {
|
|
38
|
+
warningGreen: '#00E920',
|
|
39
|
+
green: '#58A942',
|
|
40
|
+
mutedGreen: '#8BB380',
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The palette above shaped for Tailwind's `theme.extend.colors`. Spread it
|
|
44
|
+
* into the app's tailwind config (works with tailwindcss v3 and NativeWind):
|
|
45
|
+
*
|
|
46
|
+
* import { tailwindColors } from '@reekon-tools/boldr-utils/theme';
|
|
47
|
+
* export default {
|
|
48
|
+
* theme: { extend: { colors: { ...tailwindColors } } },
|
|
49
|
+
* };
|
|
50
|
+
*
|
|
51
|
+
* Class names follow the constant names, kebab-cased per group, e.g.
|
|
52
|
+
* `bg-general-dark-grey`, `text-secondary-accent-blue-1`, `bg-tile-active`,
|
|
53
|
+
* `border-form-light-grey-2`, `bg-formula`, `text-brand-red-muted`.
|
|
54
|
+
*
|
|
55
|
+
* The yellow/red/green groups are namespaced under `brand-*` so they don't
|
|
56
|
+
* shadow Tailwind's built-in `yellow`/`red`/`green` scales (extend replaces
|
|
57
|
+
* a color key wholesale, which would break classes like `text-red-500`).
|
|
58
|
+
*/
|
|
59
|
+
export const tailwindColors = {
|
|
60
|
+
general: {
|
|
61
|
+
'dark-grey': GeneralColors.darkGrey,
|
|
62
|
+
'grey-1': GeneralColors.grey1,
|
|
63
|
+
'grey-2': GeneralColors.grey2,
|
|
64
|
+
'grey-3': GeneralColors.grey3,
|
|
65
|
+
'grey-4': GeneralColors.grey4,
|
|
66
|
+
white: GeneralColors.white,
|
|
67
|
+
},
|
|
68
|
+
secondary: {
|
|
69
|
+
'accent-blue-1': SecondaryColors.accentBlue1,
|
|
70
|
+
'accent-blue-2': SecondaryColors.accentBlue2,
|
|
71
|
+
'accent-blue-3': SecondaryColors.accentBlue3,
|
|
72
|
+
'accent-blue-4': SecondaryColors.accentBlue4,
|
|
73
|
+
},
|
|
74
|
+
form: {
|
|
75
|
+
'light-grey-1': FormColors.lightGrey1,
|
|
76
|
+
'light-grey-2': FormColors.lightGrey2,
|
|
77
|
+
},
|
|
78
|
+
tile: {
|
|
79
|
+
dark: TileColors.darkBlue,
|
|
80
|
+
active: TileColors.activeBlue,
|
|
81
|
+
'light-1': TileColors.lightBlue1,
|
|
82
|
+
'light-2': TileColors.lightBlue2,
|
|
83
|
+
},
|
|
84
|
+
formula: {
|
|
85
|
+
DEFAULT: FormulaColors.purple,
|
|
86
|
+
dark: FormulaColors.darkPurple,
|
|
87
|
+
},
|
|
88
|
+
'brand-yellow': {
|
|
89
|
+
DEFAULT: YellowColors.mainYellow,
|
|
90
|
+
},
|
|
91
|
+
'brand-red': {
|
|
92
|
+
DEFAULT: RedColors.red,
|
|
93
|
+
warning: RedColors.warningRed,
|
|
94
|
+
muted: RedColors.mutedRed,
|
|
95
|
+
},
|
|
96
|
+
'brand-green': {
|
|
97
|
+
DEFAULT: GreenColors.green,
|
|
98
|
+
warning: GreenColors.warningGreen,
|
|
99
|
+
muted: GreenColors.mutedGreen,
|
|
100
|
+
},
|
|
101
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reekon-tools/boldr-utils",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.5",
|
|
4
4
|
"description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
|
|
5
5
|
"author": "REEKON Tools",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
"import": "./dist/index.js",
|
|
20
20
|
"default": "./dist/index.js"
|
|
21
21
|
},
|
|
22
|
+
"./theme": {
|
|
23
|
+
"types": "./dist/theme/colors.d.ts",
|
|
24
|
+
"import": "./dist/theme/colors.js",
|
|
25
|
+
"default": "./dist/theme/colors.js"
|
|
26
|
+
},
|
|
22
27
|
"./package.json": "./package.json"
|
|
23
28
|
},
|
|
24
29
|
"scripts": {
|
|
@@ -30,7 +35,7 @@
|
|
|
30
35
|
"coverage": "vitest run --coverage",
|
|
31
36
|
"format": "prettier --write .",
|
|
32
37
|
"check-format": "prettier --check .",
|
|
33
|
-
"check-exports": "attw --pack . --
|
|
38
|
+
"check-exports": "attw --pack . --profile esm-only",
|
|
34
39
|
"ci": "yarn run check-format && vitest run && yarn run build && yarn run check-exports"
|
|
35
40
|
},
|
|
36
41
|
"keywords": [],
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
2
|
-
import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.js';
|
|
3
|
-
export type { AnnotationCanvasHandle, GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
|
|
4
|
-
export interface CanvasKitOpts {
|
|
5
|
-
locateFile?: (file: string) => string;
|
|
6
|
-
}
|
|
7
|
-
export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
|
|
8
|
-
fallback?: ReactNode;
|
|
9
|
-
canvasKitOpts?: CanvasKitOpts;
|
|
10
|
-
};
|
|
11
|
-
export declare const AnnotationCanvas: ({ fallback, canvasKitOpts, ...rest }: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { WithSkiaWeb } from '@shopify/react-native-skia/lib/module/web/index.js';
|
|
3
|
-
// Web-only entry point: lazy-loads CanvasKit wasm via WithSkiaWeb. Imperative
|
|
4
|
-
// API (undo/redo/zoom) is exposed through the `imperativeRef` prop rather
|
|
5
|
-
// than React refs, since WithSkiaWeb can't forward refs through its lazy
|
|
6
|
-
// component boundary. The native build sits in `AnnotationCanvas.native.tsx`
|
|
7
|
-
// and skips WithSkiaWeb entirely (no wasm to load).
|
|
8
|
-
export const AnnotationCanvas = ({ fallback, canvasKitOpts, ...rest }) => (_jsx(WithSkiaWeb, { getComponent: () => import('./AnnotationCanvasInner.js').then((m) => ({
|
|
9
|
-
default: m.AnnotationCanvasInner,
|
|
10
|
-
})), fallback: fallback ?? null, componentProps: rest, opts: canvasKitOpts }));
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.native.js';
|
|
2
|
-
export type { AnnotationCanvasHandle, } from './useAnnotationCanvasState.js';
|
|
3
|
-
export type { GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
|
|
4
|
-
export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
|
|
5
|
-
fallback?: unknown;
|
|
6
|
-
canvasKitOpts?: unknown;
|
|
7
|
-
};
|
|
8
|
-
export declare const AnnotationCanvas: (props: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { AnnotationCanvasInner } from './AnnotationCanvasInner.native.js';
|
|
3
|
-
export const AnnotationCanvas = (props) => {
|
|
4
|
-
const { fallback: _f, canvasKitOpts: _o, ...rest } = props;
|
|
5
|
-
return _jsx(AnnotationCanvasInner, { ...rest });
|
|
6
|
-
};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { type CSSProperties, type MutableRefObject } from 'react';
|
|
2
|
-
import type { DecimalTolerance, FractionalTolerance, Measurement, Units } from '../types/firestore.js';
|
|
3
|
-
import type { AnnotationCanvasState, AnnotationDocumentPatch, Selection } from '../types/annotation.js';
|
|
4
|
-
import type { MeasurementRef } from './measurementPicker.js';
|
|
5
|
-
import type { Tool } from './Tool.js';
|
|
6
|
-
import { type AnnotationCanvasHandle } from './useAnnotationCanvasState.js';
|
|
7
|
-
import { type ViewportState } from './viewport.js';
|
|
8
|
-
import type { RenderMeasurementStamp } from './measurementStampOverlay.js';
|
|
9
|
-
export type { AnnotationCanvasHandle };
|
|
10
|
-
export interface AnnotationCanvasInnerProps {
|
|
11
|
-
canvas: AnnotationCanvasState;
|
|
12
|
-
onCommit(patch: AnnotationDocumentPatch): void;
|
|
13
|
-
tools: Tool[];
|
|
14
|
-
activeToolId: string;
|
|
15
|
-
selection: Selection | null;
|
|
16
|
-
onSelectionChange(selection: Selection | null): void;
|
|
17
|
-
measurements?: Measurement[];
|
|
18
|
-
fallbackUnit?: Units;
|
|
19
|
-
fractionalTolerance?: FractionalTolerance;
|
|
20
|
-
decimalTolerance?: DecimalTolerance;
|
|
21
|
-
resolveImageUrl?: (storagePath: string) => Promise<string>;
|
|
22
|
-
pickMeasurement?: () => Promise<MeasurementRef | null>;
|
|
23
|
-
renderMeasurementStamp?: RenderMeasurementStamp;
|
|
24
|
-
stampFontSource?: unknown;
|
|
25
|
-
stampValueFontSize?: number;
|
|
26
|
-
stampLabelFontSize?: number;
|
|
27
|
-
gestures?: GestureConfig;
|
|
28
|
-
width: number;
|
|
29
|
-
height: number;
|
|
30
|
-
initialViewport?: ViewportState;
|
|
31
|
-
style?: CSSProperties;
|
|
32
|
-
imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
|
|
33
|
-
}
|
|
34
|
-
export type PanTrigger = 'middleMouse' | 'rightMouse' | 'space';
|
|
35
|
-
export interface GestureConfig {
|
|
36
|
-
wheel?: 'zoom' | 'pan' | 'auto';
|
|
37
|
-
panTriggers?: PanTrigger[];
|
|
38
|
-
}
|
|
39
|
-
export declare const AnnotationCanvasInner: (props: AnnotationCanvasInnerProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useFont } from '@shopify/react-native-skia';
|
|
3
|
-
import { useCallback, useEffect, useRef, } from 'react';
|
|
4
|
-
import { AnnotationCanvasSkia } from './AnnotationCanvasSkia.js';
|
|
5
|
-
import { useAnnotationCanvasState, } from './useAnnotationCanvasState.js';
|
|
6
|
-
import { STAMP_TILE_SIZE } from './stampLayout.js';
|
|
7
|
-
const DEFAULT_PAN_TRIGGERS = ['middleMouse', 'space'];
|
|
8
|
-
export const AnnotationCanvasInner = (props) => {
|
|
9
|
-
const { fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, stampFontSource, stampValueFontSize = 14, stampLabelFontSize = 11, gestures, width, height, style, activeToolId, tools, } = props;
|
|
10
|
-
const wheelMode = gestures?.wheel ?? 'auto';
|
|
11
|
-
const panTriggers = gestures?.panTriggers ?? DEFAULT_PAN_TRIGGERS;
|
|
12
|
-
const allowSpacePan = panTriggers.includes('space');
|
|
13
|
-
const allowMiddlePan = panTriggers.includes('middleMouse');
|
|
14
|
-
const allowRightPan = panTriggers.includes('rightMouse');
|
|
15
|
-
const valueFont = useFont(stampFontSource, stampValueFontSize);
|
|
16
|
-
const labelFont = useFont(stampFontSource, stampLabelFontSize);
|
|
17
|
-
const state = useAnnotationCanvasState(props);
|
|
18
|
-
const containerRef = useRef(null);
|
|
19
|
-
const panGestureRef = useRef(null);
|
|
20
|
-
const spaceDownRef = useRef(false);
|
|
21
|
-
const activeTool = tools.find((t) => t.id === activeToolId) ?? null;
|
|
22
|
-
const toCanvasPointer = useCallback((event) => {
|
|
23
|
-
const rect = containerRef.current?.getBoundingClientRect();
|
|
24
|
-
const screen = {
|
|
25
|
-
x: event.clientX - (rect?.left ?? 0),
|
|
26
|
-
y: event.clientY - (rect?.top ?? 0),
|
|
27
|
-
};
|
|
28
|
-
return {
|
|
29
|
-
pointerId: event.pointerId,
|
|
30
|
-
screen,
|
|
31
|
-
world: state.ctx.viewport.screenToWorld(screen),
|
|
32
|
-
pressure: event.pressure || undefined,
|
|
33
|
-
shiftKey: event.shiftKey,
|
|
34
|
-
altKey: event.altKey,
|
|
35
|
-
metaKey: event.metaKey,
|
|
36
|
-
ctrlKey: event.ctrlKey,
|
|
37
|
-
};
|
|
38
|
-
}, [state.ctx.viewport]);
|
|
39
|
-
const isPanTriggerDown = useCallback((event) => {
|
|
40
|
-
if (allowMiddlePan && event.button === 1)
|
|
41
|
-
return true;
|
|
42
|
-
if (allowRightPan && event.button === 2)
|
|
43
|
-
return true;
|
|
44
|
-
if (allowSpacePan && event.button === 0 && spaceDownRef.current)
|
|
45
|
-
return true;
|
|
46
|
-
return false;
|
|
47
|
-
}, [allowMiddlePan, allowRightPan, allowSpacePan]);
|
|
48
|
-
const handlePointerDown = useCallback((event) => {
|
|
49
|
-
const rect = containerRef.current?.getBoundingClientRect();
|
|
50
|
-
const screen = {
|
|
51
|
-
x: event.clientX - (rect?.left ?? 0),
|
|
52
|
-
y: event.clientY - (rect?.top ?? 0),
|
|
53
|
-
};
|
|
54
|
-
if (isPanTriggerDown(event)) {
|
|
55
|
-
panGestureRef.current = { pointerId: event.pointerId, lastScreen: screen };
|
|
56
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
57
|
-
event.preventDefault();
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
61
|
-
state.dispatchPointerDown(toCanvasPointer(event));
|
|
62
|
-
}, [state, toCanvasPointer, isPanTriggerDown]);
|
|
63
|
-
const handlePointerMove = useCallback((event) => {
|
|
64
|
-
const pan = panGestureRef.current;
|
|
65
|
-
if (pan && event.pointerId === pan.pointerId) {
|
|
66
|
-
const rect = containerRef.current?.getBoundingClientRect();
|
|
67
|
-
const screen = {
|
|
68
|
-
x: event.clientX - (rect?.left ?? 0),
|
|
69
|
-
y: event.clientY - (rect?.top ?? 0),
|
|
70
|
-
};
|
|
71
|
-
state.pan({
|
|
72
|
-
x: screen.x - pan.lastScreen.x,
|
|
73
|
-
y: screen.y - pan.lastScreen.y,
|
|
74
|
-
});
|
|
75
|
-
panGestureRef.current = { pointerId: pan.pointerId, lastScreen: screen };
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
state.dispatchPointerMove(toCanvasPointer(event));
|
|
79
|
-
}, [state, toCanvasPointer]);
|
|
80
|
-
const handlePointerUp = useCallback((event) => {
|
|
81
|
-
const pan = panGestureRef.current;
|
|
82
|
-
if (pan && event.pointerId === pan.pointerId) {
|
|
83
|
-
panGestureRef.current = null;
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
state.dispatchPointerUp(toCanvasPointer(event));
|
|
87
|
-
}, [state, toCanvasPointer]);
|
|
88
|
-
const handlePointerCancel = useCallback(() => {
|
|
89
|
-
panGestureRef.current = null;
|
|
90
|
-
state.dispatchPointerCancel();
|
|
91
|
-
}, [state]);
|
|
92
|
-
const handleWheel = useCallback((event) => {
|
|
93
|
-
const rect = containerRef.current?.getBoundingClientRect();
|
|
94
|
-
const focal = {
|
|
95
|
-
x: event.clientX - (rect?.left ?? 0),
|
|
96
|
-
y: event.clientY - (rect?.top ?? 0),
|
|
97
|
-
};
|
|
98
|
-
if (event.ctrlKey || event.metaKey) {
|
|
99
|
-
const factor = Math.exp(-event.deltaY / 200);
|
|
100
|
-
state.zoom(focal, state.ctx.viewport.state.zoom * factor);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (wheelMode === 'zoom') {
|
|
104
|
-
const factor = Math.exp(-event.deltaY / 300);
|
|
105
|
-
state.zoom(focal, state.ctx.viewport.state.zoom * factor);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (event.shiftKey) {
|
|
109
|
-
state.pan({ x: event.deltaY, y: event.deltaX });
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
state.pan({ x: event.deltaX, y: event.deltaY });
|
|
113
|
-
}, [state, wheelMode]);
|
|
114
|
-
const handleContextMenu = useCallback((event) => {
|
|
115
|
-
if (allowRightPan)
|
|
116
|
-
event.preventDefault();
|
|
117
|
-
}, [allowRightPan]);
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
const el = containerRef.current;
|
|
120
|
-
if (!el)
|
|
121
|
-
return;
|
|
122
|
-
const stop = (e) => {
|
|
123
|
-
if (e.target === el || el.contains(e.target)) {
|
|
124
|
-
e.preventDefault();
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
el.addEventListener('wheel', stop, { passive: false });
|
|
128
|
-
return () => el.removeEventListener('wheel', stop);
|
|
129
|
-
}, []);
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (!allowSpacePan)
|
|
132
|
-
return;
|
|
133
|
-
const onKeyDown = (e) => {
|
|
134
|
-
if (e.code === 'Space' && !e.repeat) {
|
|
135
|
-
spaceDownRef.current = true;
|
|
136
|
-
if (containerRef.current)
|
|
137
|
-
containerRef.current.style.cursor = 'grab';
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
const onKeyUp = (e) => {
|
|
141
|
-
if (e.code === 'Space') {
|
|
142
|
-
spaceDownRef.current = false;
|
|
143
|
-
if (containerRef.current)
|
|
144
|
-
containerRef.current.style.cursor = '';
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
window.addEventListener('keydown', onKeyDown);
|
|
148
|
-
window.addEventListener('keyup', onKeyUp);
|
|
149
|
-
return () => {
|
|
150
|
-
window.removeEventListener('keydown', onKeyDown);
|
|
151
|
-
window.removeEventListener('keyup', onKeyUp);
|
|
152
|
-
};
|
|
153
|
-
}, [allowSpacePan]);
|
|
154
|
-
const containerStyle = {
|
|
155
|
-
position: 'relative',
|
|
156
|
-
width,
|
|
157
|
-
height,
|
|
158
|
-
overflow: 'hidden',
|
|
159
|
-
touchAction: 'none',
|
|
160
|
-
userSelect: 'none',
|
|
161
|
-
cursor: activeTool?.cursor ?? 'default',
|
|
162
|
-
...style,
|
|
163
|
-
};
|
|
164
|
-
const customPreview = activeTool?.renderPreview?.(state.customPreviewState, state.ctx);
|
|
165
|
-
const { renderMeasurementStamp, selection } = props;
|
|
166
|
-
return (_jsxs("div", { ref: containerRef, style: containerStyle, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerCancel, onWheel: handleWheel, onContextMenu: handleContextMenu, children: [AnnotationCanvasSkia({
|
|
167
|
-
width,
|
|
168
|
-
height,
|
|
169
|
-
effectiveCanvas: state.effectiveCanvas,
|
|
170
|
-
worldTransform: state.worldTransform,
|
|
171
|
-
measurementsById: state.measurementsById,
|
|
172
|
-
fallbackUnit,
|
|
173
|
-
fractionalTolerance,
|
|
174
|
-
decimalTolerance,
|
|
175
|
-
resolveImageUrl,
|
|
176
|
-
valueFont,
|
|
177
|
-
labelFont,
|
|
178
|
-
hideMeasurementStamps: !!renderMeasurementStamp,
|
|
179
|
-
penDrawingStroke: state.penDrawingStroke,
|
|
180
|
-
customPreview,
|
|
181
|
-
}), renderMeasurementStamp && (_jsx("div", { style: {
|
|
182
|
-
position: 'absolute',
|
|
183
|
-
inset: 0,
|
|
184
|
-
pointerEvents: 'none',
|
|
185
|
-
}, children: state.effectiveCanvas.placedMeasurements.map((placed) => {
|
|
186
|
-
const size = STAMP_TILE_SIZE * (placed.scale ?? 1);
|
|
187
|
-
const cx = (placed.anchor.x - state.viewport.pan.x) * state.viewport.zoom;
|
|
188
|
-
const cy = (placed.anchor.y - state.viewport.pan.y) * state.viewport.zoom;
|
|
189
|
-
const isSelected = selection?.ids.includes(placed.id) ?? false;
|
|
190
|
-
return (_jsxs("div", { style: {
|
|
191
|
-
position: 'absolute',
|
|
192
|
-
left: 0,
|
|
193
|
-
top: 0,
|
|
194
|
-
width: size,
|
|
195
|
-
height: size,
|
|
196
|
-
transform: `translate(${cx - size / 2}px, ${cy - size / 2}px)`,
|
|
197
|
-
}, children: [renderMeasurementStamp({
|
|
198
|
-
placed,
|
|
199
|
-
measurement: state.measurementsById.get(placed.measurementId) ?? null,
|
|
200
|
-
selected: isSelected,
|
|
201
|
-
size,
|
|
202
|
-
zoom: state.viewport.zoom,
|
|
203
|
-
}), isSelected && (_jsx("div", { role: "button", "aria-label": "Remove measurement", onPointerDown: (e) => {
|
|
204
|
-
e.stopPropagation();
|
|
205
|
-
state.ctx.commit({
|
|
206
|
-
ops: [{ op: 'removeMeasurement', id: placed.id }],
|
|
207
|
-
});
|
|
208
|
-
state.ctx.setSelection(null);
|
|
209
|
-
}, style: {
|
|
210
|
-
position: 'absolute',
|
|
211
|
-
top: -10,
|
|
212
|
-
right: -10,
|
|
213
|
-
width: 40,
|
|
214
|
-
height: 40,
|
|
215
|
-
cursor: 'pointer',
|
|
216
|
-
pointerEvents: 'auto',
|
|
217
|
-
} }))] }, placed.id));
|
|
218
|
-
}) }))] }));
|
|
219
|
-
};
|