@reekon-tools/boldr-utils 1.6.24 → 1.6.26
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.d.ts +1 -0
- package/dist/annotation/canvas/AnnotationCanvasInner.native.d.ts +1 -0
- package/dist/annotation/canvas/AnnotationCanvasInner.native.js +179 -78
- package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +4 -2
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +5 -2
- package/dist/annotation/canvas/elements/BackgroundImageElement.d.ts +4 -1
- package/dist/annotation/canvas/elements/BackgroundImageElement.js +21 -19
- package/dist/annotation/canvas/useAnnotationCanvasState.js +41 -10
- package/dist/annotation/canvas/viewport.d.ts +13 -1
- package/dist/annotation/canvas/viewport.js +32 -0
- 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/exports.d.ts +2 -1
- package/dist/exports.js +2 -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/indexLabels.d.ts +3 -0
- package/dist/utils/indexLabels.js +40 -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
package/dist/exports.d.ts
CHANGED
|
@@ -8,6 +8,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
10
|
export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, } from './utils/groups.js';
|
|
11
|
+
export { numberToLetterIndex, formGroupInputLabel, listGroupMeasurementLabel, } from './utils/indexLabels.js';
|
|
11
12
|
export { isFieldOp, type AnnotationDataProvider, type AnnotationFile, type AnnotationFileSummary, type FieldOp, type ImageBlob, type JobGroupScope, type JobScope, type Patch, type Unsubscribe, type UploadedImageRef, } from './annotation/data/AnnotationDataProvider.js';
|
|
12
13
|
export { AnnotationDataProviderContext, useAnnotationData, type AnnotationDataProviderProps, } from './annotation/data/AnnotationDataContext.js';
|
|
13
14
|
export { useAnnotationDoc, type UseAnnotationDocResult, } from './annotation/data/hooks/useAnnotationDoc.js';
|
|
@@ -22,7 +23,7 @@ export type { CanvasPointerEvent, RequestTextInput, ShapeDrawConfig, Tool, ToolC
|
|
|
22
23
|
export type { MeasurementRef, PickMeasurement, } from './annotation/canvas/measurementPicker.js';
|
|
23
24
|
export type { MeasurementStampRenderArgs, RenderMeasurementStamp, } from './annotation/canvas/measurementStampOverlay.js';
|
|
24
25
|
export { STAMP_TILE_SIZE, STAMP_INPUT_TILE_SIZE, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, stampTileSize, isUnassociatedStamp, } from './annotation/canvas/stampLayout.js';
|
|
25
|
-
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, type ViewportApi, type ViewportState, } from './annotation/canvas/viewport.js';
|
|
26
|
+
export { createViewportApi, fitToScreen, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, type ViewportApi, type ViewportState, } from './annotation/canvas/viewport.js';
|
|
26
27
|
export { createPenTool, type PenToolOptions, } from './annotation/canvas/tools/penTool.js';
|
|
27
28
|
export { createSelectTool } from './annotation/canvas/tools/selectTool.js';
|
|
28
29
|
export { createMeasurementStampTool, type MeasurementStampToolOptions, } from './annotation/canvas/tools/measurementStampTool.js';
|
package/dist/exports.js
CHANGED
|
@@ -12,6 +12,7 @@ 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
14
|
export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, } from './utils/groups.js';
|
|
15
|
+
export { numberToLetterIndex, formGroupInputLabel, listGroupMeasurementLabel, } from './utils/indexLabels.js';
|
|
15
16
|
// Annotation data layer (SDK-neutral; apps provide their own provider).
|
|
16
17
|
export { isFieldOp, } from './annotation/data/AnnotationDataProvider.js';
|
|
17
18
|
export { AnnotationDataProviderContext, useAnnotationData, } from './annotation/data/AnnotationDataContext.js';
|
|
@@ -22,7 +23,7 @@ export { useAnnotationCanvasDoc, } from './annotation/data/hooks/useAnnotationCa
|
|
|
22
23
|
export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
|
|
23
24
|
export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
|
|
24
25
|
export { STAMP_TILE_SIZE, STAMP_INPUT_TILE_SIZE, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, stampTileSize, isUnassociatedStamp, } from './annotation/canvas/stampLayout.js';
|
|
25
|
-
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, } from './annotation/canvas/viewport.js';
|
|
26
|
+
export { createViewportApi, fitToScreen, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, } from './annotation/canvas/viewport.js';
|
|
26
27
|
export { createPenTool, } from './annotation/canvas/tools/penTool.js';
|
|
27
28
|
export { createSelectTool } from './annotation/canvas/tools/selectTool.js';
|
|
28
29
|
export { createMeasurementStampTool, } from './annotation/canvas/tools/measurementStampTool.js';
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const numberToLetterIndex: (index: number) => string;
|
|
2
|
+
export declare const formGroupInputLabel: (columnIndex: number, groupIndex: number) => string;
|
|
3
|
+
export declare const listGroupMeasurementLabel: (deviceMeasurementNumber: number | null | undefined, groupIndex: number) => string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Canonical measurement / input index labels, shared by rock-desktop and
|
|
2
|
+
// rock-pro-app so every surface (tables, forms, PDF tile overlays, mobile
|
|
3
|
+
// lists) renders the same identifier for the same datum.
|
|
4
|
+
//
|
|
5
|
+
// Two schemes exist:
|
|
6
|
+
//
|
|
7
|
+
// - FORM groups: inputs are identified positionally — the column's index in
|
|
8
|
+
// the section's tableConfig as a letter, plus the group's 1-based number.
|
|
9
|
+
// First column in the second group → "A2"; fifth column in the third
|
|
10
|
+
// group → "E3".
|
|
11
|
+
//
|
|
12
|
+
// - LIST groups: entries are always measurements, identified by their stable
|
|
13
|
+
// deviceMeasurementNumber (capture sequence) as the letter, plus the same
|
|
14
|
+
// group number. deviceMeasurementNumber 4 in the second group → "E2".
|
|
15
|
+
// Legacy measurements without a deviceMeasurementNumber fall back to the
|
|
16
|
+
// bare group number ("2") — positional letters would shift as the list
|
|
17
|
+
// re-sorts, so no letter is shown.
|
|
18
|
+
// 0-based index → spreadsheet-style letters: 0 → "A", 25 → "Z", 26 → "AA".
|
|
19
|
+
// Negative / non-finite input yields ''.
|
|
20
|
+
export const numberToLetterIndex = (index) => {
|
|
21
|
+
if (!Number.isFinite(index) || index < 0)
|
|
22
|
+
return '';
|
|
23
|
+
let n = Math.floor(index) + 1;
|
|
24
|
+
let result = '';
|
|
25
|
+
while (n > 0) {
|
|
26
|
+
n--;
|
|
27
|
+
result = String.fromCharCode((n % 26) + 65) + result;
|
|
28
|
+
n = Math.floor(n / 26);
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
// Label for a FORM-group input: column position (0-based) + group number.
|
|
33
|
+
// formGroupInputLabel(0, 1) → "A2"
|
|
34
|
+
export const formGroupInputLabel = (columnIndex, groupIndex) => `${numberToLetterIndex(columnIndex)}${groupIndex + 1}`;
|
|
35
|
+
// Label for a LIST-group measurement: deviceMeasurementNumber (0-based) +
|
|
36
|
+
// group number, or the bare group number when the measurement has no device
|
|
37
|
+
// number. listGroupMeasurementLabel(4, 1) → "E2"; (null, 1) → "2".
|
|
38
|
+
export const listGroupMeasurementLabel = (deviceMeasurementNumber, groupIndex) => deviceMeasurementNumber != null && deviceMeasurementNumber >= 0
|
|
39
|
+
? `${numberToLetterIndex(deviceMeasurementNumber)}${groupIndex + 1}`
|
|
40
|
+
: `${groupIndex + 1}`;
|
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
|
-
};
|