@adcops/autocore-react 3.3.124 → 3.5.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/components/UnitSystemSelector.d.ts +17 -0
- package/dist/components/UnitSystemSelector.d.ts.map +1 -0
- package/dist/components/UnitSystemSelector.js +1 -0
- package/dist/components/UnitsEditor.d.ts +11 -0
- package/dist/components/UnitsEditor.d.ts.map +1 -0
- package/dist/components/UnitsEditor.js +1 -0
- package/dist/components/ams/AmsProvider.d.ts +14 -0
- package/dist/components/ams/AmsProvider.d.ts.map +1 -1
- package/dist/components/ams/AmsProvider.js +1 -1
- package/dist/components/ams/AssetDetailView.d.ts.map +1 -1
- package/dist/components/ams/AssetDetailView.js +1 -1
- package/dist/components/ams/AssetNameplateGrid.d.ts +13 -0
- package/dist/components/ams/AssetNameplateGrid.d.ts.map +1 -0
- package/dist/components/ams/AssetNameplateGrid.js +1 -0
- package/dist/components/ams/InstalledAssetPicker.d.ts +35 -0
- package/dist/components/ams/InstalledAssetPicker.d.ts.map +1 -0
- package/dist/components/ams/InstalledAssetPicker.js +1 -0
- package/dist/components/ams/index.d.ts +5 -0
- package/dist/components/ams/index.d.ts.map +1 -1
- package/dist/components/ams/index.js +1 -1
- package/dist/components/ams/useInstalledAsset.d.ts +26 -0
- package/dist/components/ams/useInstalledAsset.d.ts.map +1 -0
- package/dist/components/ams/useInstalledAsset.js +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
- package/dist/components/tis/ResultHistoryTable.js +1 -1
- package/dist/components/tis/ScienceTable.d.ts.map +1 -1
- package/dist/components/tis/ScienceTable.js +1 -1
- package/dist/components/tis/TestDataView.d.ts +16 -4
- package/dist/components/tis/TestDataView.d.ts.map +1 -1
- package/dist/components/tis/TestDataView.js +1 -1
- package/dist/components/tis/TestFieldRow.d.ts +88 -7
- package/dist/components/tis/TestFieldRow.d.ts.map +1 -1
- package/dist/components/tis/TestFieldRow.js +1 -1
- package/dist/components/tis/TestSetupForm.d.ts.map +1 -1
- package/dist/components/tis/TestSetupForm.js +1 -1
- package/dist/components/tis/TisProvider.d.ts.map +1 -1
- package/dist/components/tis/TisProvider.js +1 -1
- package/dist/components/tis-editor/editor/TestFieldDialog.d.ts.map +1 -1
- package/dist/components/tis-editor/editor/TestFieldDialog.js +1 -1
- package/dist/components/tis-editor/types.d.ts +6 -2
- package/dist/components/tis-editor/types.d.ts.map +1 -1
- package/dist/core/AutoCoreTagContext.d.ts.map +1 -1
- package/dist/core/AutoCoreTagContext.js +1 -1
- package/dist/core/AutoCoreTagTypes.d.ts +82 -7
- package/dist/core/AutoCoreTagTypes.d.ts.map +1 -1
- package/dist/core/formatScaled.d.ts +16 -0
- package/dist/core/formatScaled.d.ts.map +1 -0
- package/dist/core/formatScaled.js +1 -0
- package/dist/hooks/useAutoCoreTag.d.ts +11 -3
- package/dist/hooks/useAutoCoreTag.d.ts.map +1 -1
- package/dist/hooks/useAutoCoreTag.js +1 -1
- package/package.json +1 -1
- package/src/components/UnitSystemSelector.tsx +88 -0
- package/src/components/UnitsEditor.tsx +317 -0
- package/src/components/ams/AmsProvider.tsx +36 -1
- package/src/components/ams/AssetDetailView.tsx +2 -20
- package/src/components/ams/AssetNameplateGrid.tsx +62 -0
- package/src/components/ams/InstalledAssetPicker.tsx +209 -0
- package/src/components/ams/index.ts +7 -0
- package/src/components/ams/useInstalledAsset.ts +108 -0
- package/src/components/index.ts +4 -0
- package/src/components/tis/ResultHistoryTable.tsx +89 -6
- package/src/components/tis/ScienceTable.tsx +13 -0
- package/src/components/tis/TestDataView.tsx +153 -35
- package/src/components/tis/TestFieldRow.tsx +157 -19
- package/src/components/tis/TestSetupForm.tsx +35 -7
- package/src/components/tis/TisProvider.tsx +46 -3
- package/src/components/tis-editor/editor/TestFieldDialog.tsx +29 -18
- package/src/components/tis-editor/types.ts +6 -2
- package/src/core/AutoCoreTagContext.tsx +165 -16
- package/src/core/AutoCoreTagTypes.ts +91 -7
- package/src/core/formatScaled.ts +72 -0
- package/src/hooks/useAutoCoreTag.ts +51 -3
- package/todo.md +6 -0
|
@@ -147,17 +147,63 @@ export interface SubscriptionOptions {
|
|
|
147
147
|
*/
|
|
148
148
|
args?: Record<string, unknown>;
|
|
149
149
|
}
|
|
150
|
+
/** How a resolved scale's `precision` is interpreted. */
|
|
151
|
+
export type PrecisionMode = "decimals" | "significant";
|
|
150
152
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
+
* One system's display treatment of one quantity, as it appears in `units.json`.
|
|
154
|
+
* Mirrors `mechutil::units::SystemScale`.
|
|
155
|
+
*/
|
|
156
|
+
export interface UnitSystemScale {
|
|
157
|
+
/** Display label ("mm", "lbf", "°C"). */
|
|
158
|
+
label: string;
|
|
159
|
+
/** `display = backend * scalar (+ offset)`. */
|
|
160
|
+
scalar: number;
|
|
161
|
+
/** Additive term — absolute quantities (temperature) only. */
|
|
162
|
+
offset?: number;
|
|
163
|
+
/** Digits, interpreted per `precision_mode`. */
|
|
164
|
+
precision?: number;
|
|
165
|
+
precision_mode?: PrecisionMode;
|
|
166
|
+
}
|
|
167
|
+
/** One quantity row of `units.json`. Mirrors `mechutil::units::Scale`. */
|
|
168
|
+
export interface UnitScale {
|
|
169
|
+
/** The unit GM/GNV actually holds on this machine. */
|
|
170
|
+
backend: string;
|
|
171
|
+
/** `"absolute"` takes the offset; `"delta"` (default) does not. */
|
|
172
|
+
kind?: "delta" | "absolute";
|
|
173
|
+
/** system name → display treatment. Flattened in the JSON. */
|
|
174
|
+
[system: string]: unknown;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The whole `units.json` document — the single definition of units for a
|
|
178
|
+
* machine. Fetched from `system.get_units`; see `autocore-server/UNITS_PLAN.md`.
|
|
179
|
+
*/
|
|
180
|
+
export interface UnitsTable {
|
|
181
|
+
/** Named systems, in display order. Any names, not a fixed pair. */
|
|
182
|
+
systems: string[];
|
|
183
|
+
default_system: string;
|
|
184
|
+
/** quantity name → row. Quantities are DATA, not a fixed enum. */
|
|
185
|
+
scales: Record<string, UnitScale>;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* A quantity resolved against the active system — what the UI actually renders
|
|
189
|
+
* with. Produced by the provider from `UnitsTable` + active system.
|
|
190
|
+
*
|
|
191
|
+
* `scale`/`label` keep their historical names so the provider's existing
|
|
192
|
+
* rescale-from-raw machinery is unchanged; `precision` is new and is what makes
|
|
193
|
+
* "too many decimals" configurable rather than hard-coded.
|
|
153
194
|
*/
|
|
154
195
|
export interface ScaleConfig {
|
|
155
|
-
/**
|
|
196
|
+
/** Quantity name this resolves (e.g., "position", "force") */
|
|
156
197
|
name: string;
|
|
157
198
|
/** Current scale factor — incoming values ×= scale; outgoing values /= scale */
|
|
158
199
|
scale: number;
|
|
159
200
|
/** Units/label for display (e.g., "mm", "in", "lbs") */
|
|
160
201
|
label: string;
|
|
202
|
+
/** Additive term for absolute quantities (temperature). Default 0. */
|
|
203
|
+
offset?: number;
|
|
204
|
+
/** Display digits. */
|
|
205
|
+
precision?: number;
|
|
206
|
+
precisionMode?: PrecisionMode;
|
|
161
207
|
/** Optional description for debugging/documentation */
|
|
162
208
|
description?: string;
|
|
163
209
|
/**
|
|
@@ -200,10 +246,21 @@ export type TagConfig<K extends string = string, T extends PrimitiveKind = Primi
|
|
|
200
246
|
/** Optional initial **raw** value before first server update */
|
|
201
247
|
initialValue?: TagValueOf<T>;
|
|
202
248
|
/**
|
|
203
|
-
*
|
|
204
|
-
* If present, provider will:
|
|
205
|
-
* - Multiply incoming numbers by the
|
|
206
|
-
* - Divide outgoing numbers
|
|
249
|
+
* The QUANTITY this tag measures — a row name in `units.json`
|
|
250
|
+
* ("position", "force", "torque", …). If present, the provider will:
|
|
251
|
+
* - Multiply incoming numbers by the active system's `scalar` (+ offset)
|
|
252
|
+
* - Divide outgoing numbers back to backend units
|
|
253
|
+
* - Render them at the active system's `precision`
|
|
254
|
+
*
|
|
255
|
+
* Quantities are data, not an enum: a machine with a channel nobody
|
|
256
|
+
* anticipated just gets a new row in `units.json`.
|
|
257
|
+
*/
|
|
258
|
+
quantity?: string;
|
|
259
|
+
/**
|
|
260
|
+
* @deprecated Renamed to {@link quantity} in 4.0, when hand-authored
|
|
261
|
+
* `acScales` was replaced by the server's `units.json`. Still read as a
|
|
262
|
+
* fallback so a project can be migrated without its HMI going dark, but it
|
|
263
|
+
* will be removed — regenerate with `acctl codegen-tags`.
|
|
207
264
|
*/
|
|
208
265
|
scale?: string;
|
|
209
266
|
/**
|
|
@@ -258,6 +315,24 @@ export type BindingOf<Spec extends readonly TagConfig[], K extends Spec[number][
|
|
|
258
315
|
export interface BaseContextValue<VMap extends Record<string, any>> {
|
|
259
316
|
/** Current app-visible tag values (already scaled/decoded). */
|
|
260
317
|
values: Partial<VMap>;
|
|
318
|
+
/**
|
|
319
|
+
* The machine's units table (`units.json`), or `null` when the machine has
|
|
320
|
+
* none — in which case values render in backend units.
|
|
321
|
+
*/
|
|
322
|
+
units?: UnitsTable | null;
|
|
323
|
+
/** The display system currently in effect (e.g. "Metric"). */
|
|
324
|
+
activeSystem?: string | null;
|
|
325
|
+
/**
|
|
326
|
+
* Switch the machine's display system. Persisted server-side and broadcast,
|
|
327
|
+
* so every connected client switches live with no reload.
|
|
328
|
+
*/
|
|
329
|
+
setUnitSystem?: (system: string) => Promise<void>;
|
|
330
|
+
/**
|
|
331
|
+
* Format a BACKEND value for display in the active system, honouring that
|
|
332
|
+
* quantity's label precision. Rounding is a rendering step only — this never
|
|
333
|
+
* changes a stored value.
|
|
334
|
+
*/
|
|
335
|
+
formatQuantity?: (quantity: string, backendValue: number) => string;
|
|
261
336
|
/** Last raw (controller) values, as received (pre-scale, pre-codec). */
|
|
262
337
|
rawValues: Record<string, unknown>;
|
|
263
338
|
/** Whether initial reads are pending. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoCoreTagTypes.d.ts","sourceRoot":"","sources":["../../src/core/AutoCoreTagTypes.ts"],"names":[],"mappings":"AASA;;;;;;;;GAQG;AAEH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAErE,oDAAoD;AACpD,KAAK,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AACtD,MAAM,MAAM,SAAS,GACf,aAAa,GACb;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAC5B,SAAS,EAAE,CAAC;AAElB;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,IAAI,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAE1E;;;GAGG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,aAAa,IAC1C,CAAC,SAAS,SAAS,GAAG,OAAO,GAC7B,CAAC,SAAS,QAAQ,GAAG,MAAM,GAC3B,CAAC,SAAS,QAAQ,GAAG,MAAM,GAC3B,iBAAiB,CAAC;AAEtB;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC;IACjC,0CAA0C;IAC1C,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,0EAA0E;IAC1E,KAAK,EAAE,CAAC,YAAY,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,4DAA4D;IAC5D,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,gCAAgC;IAChC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,iCAAiC;IACjC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;CACtB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,WAAW,mBAAmB;IAChC;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;
|
|
1
|
+
{"version":3,"file":"AutoCoreTagTypes.d.ts","sourceRoot":"","sources":["../../src/core/AutoCoreTagTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PrecisionMode, ScaleConfig } from "./AutoCoreTagTypes";
|
|
2
|
+
/** Digits used when a scale declares no precision. */
|
|
3
|
+
export declare const DEFAULT_PRECISION = 3;
|
|
4
|
+
/**
|
|
5
|
+
* Format an already-converted (display-unit) value.
|
|
6
|
+
*
|
|
7
|
+
* Precision is a **cap, not padding**: a clean 0/1/2 axis stays "0", "1", "2"
|
|
8
|
+
* rather than becoming "0.000", which is what made fixed `toFixed` unusable for
|
|
9
|
+
* chart ticks.
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatValue(value: number, precision?: number, mode?: PrecisionMode): string;
|
|
12
|
+
/** Format a display-unit value using a resolved scale's precision settings. */
|
|
13
|
+
export declare function formatScaled(displayValue: number, scale?: ScaleConfig): string;
|
|
14
|
+
/** `"12.5 mm"` — value plus the active system's label, when there is one. */
|
|
15
|
+
export declare function formatScaledWithLabel(displayValue: number, scale?: ScaleConfig): string;
|
|
16
|
+
//# sourceMappingURL=formatScaled.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatScaled.d.ts","sourceRoot":"","sources":["../../src/core/formatScaled.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAErE,sDAAsD;AACtD,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC;;;;;;GAMG;AACH,wBAAgB,WAAW,CACvB,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,MAA0B,EACrC,IAAI,GAAE,aAA0B,GACjC,MAAM,CAwBR;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM,CAM9E;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM,CAIvF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_PRECISION=3;export function formatValue(t,e=3,a="decimals"){if(!Number.isFinite(t))return String(t);let i;if("significant"===a){const a=Math.max(1,e);if(0===t)i="0";else{const e=Math.floor(Math.log10(Math.abs(t))),r=Math.max(0,a-1-e);i=t.toFixed(Math.min(r,100))}}else i=t.toFixed(Math.max(0,Math.min(e,100)));return i.includes(".")&&(i=i.replace(/0+$/,"").replace(/\.$/,""),""!==i&&"-"!==i||(i="0")),i}export function formatScaled(t,e){return formatValue(t,e?.precision??3,e?.precisionMode??"decimals")}export function formatScaledWithLabel(t,e){const a=formatScaled(t,e),i=e?.label?.trim();return i?`${a} ${i}`:a}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaseContextValue, TagConfig, TagValueMap } from "../core/AutoCoreTagTypes";
|
|
1
|
+
import type { BaseContextValue, ScaleConfig, TagConfig, TagValueMap } from "../core/AutoCoreTagTypes";
|
|
2
2
|
export declare function makeAutoCoreTagHooks<Spec extends readonly TagConfig[]>(Context: React.Context<BaseContextValue<TagValueMap<Spec>>>, tags: Spec): {
|
|
3
3
|
readonly useAutoCoreTag: <K extends Spec[number]["tagName"]>(tagName: K) => {
|
|
4
4
|
readonly value: TagValueMap<Spec>[K];
|
|
@@ -20,9 +20,17 @@ export declare function makeAutoCoreTagHooks<Spec extends readonly TagConfig[]>(
|
|
|
20
20
|
readonly isLoading: boolean;
|
|
21
21
|
};
|
|
22
22
|
readonly useScales: () => {
|
|
23
|
-
readonly scales: Record<string,
|
|
23
|
+
readonly scales: Record<string, ScaleConfig>;
|
|
24
24
|
readonly updateScale: (scaleName: string, newScale: number, newLabel: string) => Promise<void>;
|
|
25
|
-
readonly getScale: (scaleName: string) =>
|
|
25
|
+
readonly getScale: (scaleName: string) => ScaleConfig;
|
|
26
|
+
};
|
|
27
|
+
readonly useUnits: () => {
|
|
28
|
+
readonly units: import("../core/AutoCoreTagTypes").UnitsTable | null;
|
|
29
|
+
readonly activeSystem: string | null;
|
|
30
|
+
readonly systems: string[];
|
|
31
|
+
readonly setUnitSystem: ((system: string) => Promise<void>) | undefined;
|
|
32
|
+
readonly label: (quantity: string) => string;
|
|
33
|
+
readonly format: (quantity: string, backendValue: number) => string;
|
|
26
34
|
};
|
|
27
35
|
};
|
|
28
36
|
//# sourceMappingURL=useAutoCoreTag.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAutoCoreTag.d.ts","sourceRoot":"","sources":["../../src/hooks/useAutoCoreTag.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"useAutoCoreTag.d.ts","sourceRoot":"","sources":["../../src/hooks/useAutoCoreTag.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEtG,wBAAgB,oBAAoB,CAAC,IAAI,SAAS,SAAS,SAAS,EAAE,EAClE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAC3D,IAAI,EAAE,IAAI;8BAYc,CAAC,2CAA4B,CAAC;;;;;;;;;+BA6B7B,CAAC,4CAA6B,SAAS,CAAC,EAAE;4BAO/C,CAAC;;kCAYyB,CAAC;;;iCAUpB,CAAC,YAAY,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,mBAAM,CAAC,KAAK,CAAC,SAAQ,GAAG,EAAE;;;;;;;uCAmBpE,MAAM,KAAG,WAAW;;;;;;;mCAoBrB,MAAM;oCAIN,MAAM,gBAAgB,MAAM;;EAqBlD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback,useContext,useMemo}from"react";export function makeAutoCoreTagHooks(e,
|
|
1
|
+
import{useCallback,useContext,useMemo}from"react";export function makeAutoCoreTagHooks(e,s){return{useAutoCoreTag:a=>{const{values:t,rawValues:u,isLoading:l,write:n,tap:o,press:r,release:i}=useContext(e),c=(e=>s.find(s=>s.tagName===e))(a);if(!c)throw new Error(`useAutoCoreTag: unknown tagName '${String(a)}'`);const m=useCallback(e=>n(a,e),[n,a]),C=useCallback(()=>o?.(a),[o,a]),g=useCallback(()=>r?.(a),[r,a]),b=useCallback(()=>i?.(a),[i,a]);return{value:t[a],rawValue:u[a],write:m,tap:C,press:g,release:b,isLoading:l}},useAutoCoreTags:s=>{const{values:a,rawValues:t,isLoading:u,write:l}=useContext(e);return{values:useMemo(()=>Object.fromEntries(s.map(e=>[e,a[e]])),[a,s]),rawValues:useMemo(()=>Object.fromEntries(s.map(e=>[e,t[e]])),[t,s]),write:useCallback(async(e,s)=>{await l(e,s)},[l]),isLoading:u}},useAutoCoreSelect:(s,a=[])=>{const{values:t,isLoading:u}=useContext(e);return{selected:useMemo(()=>s(t),[t,...a]),isLoading:u}},useScales:()=>{const{scales:s,updateScale:a}=useContext(e),t=useCallback(e=>s[e]??{name:e,scale:1,label:""},[s]);return{scales:s,updateScale:a,getScale:t}},useUnits:()=>{const{units:s,activeSystem:a,setUnitSystem:t,formatQuantity:u,scales:l}=useContext(e),n=useCallback(e=>l[e]?.label??"",[l]),o=useCallback((e,s)=>u?u(e,s):String(s),[u]);return{units:s??null,activeSystem:a??null,systems:s?.systems??[],setUnitSystem:t,label:n,format:o}}}}
|
package/package.json
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Automated Design Corp. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* UnitSystemSelector — the operator's Metric/Imperial (or any named system)
|
|
5
|
+
* switch.
|
|
6
|
+
*
|
|
7
|
+
* Selecting a system writes it server-side, so it is machine-wide and survives
|
|
8
|
+
* a restart, and so the server's CSV export follows it rather than disagreeing
|
|
9
|
+
* with the screen. The server broadcasts the change and every connected client
|
|
10
|
+
* re-derives its scales, which means the switch is **live**: readouts, tables,
|
|
11
|
+
* charts and the sequence view all update with no page reload and no
|
|
12
|
+
* control-program restart. Nothing stored is converted — only what is rendered.
|
|
13
|
+
*
|
|
14
|
+
* See autocore-server/UNITS_PLAN.md §6.3.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React, { useContext } from "react";
|
|
18
|
+
import { SelectButton } from "primereact/selectbutton";
|
|
19
|
+
import { Dropdown } from "primereact/dropdown";
|
|
20
|
+
import { AutoCoreTagContext } from "../core/AutoCoreTagContext";
|
|
21
|
+
|
|
22
|
+
export interface UnitSystemSelectorProps {
|
|
23
|
+
/**
|
|
24
|
+
* `"buttons"` (default) renders a segmented control — right for a
|
|
25
|
+
* touchscreen with two or three systems. `"dropdown"` is better past that.
|
|
26
|
+
*/
|
|
27
|
+
variant?: "buttons" | "dropdown";
|
|
28
|
+
/** Optional label rendered above the control. */
|
|
29
|
+
label?: string;
|
|
30
|
+
/** Disable interaction (e.g. while a test is running). */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
className?: string;
|
|
33
|
+
style?: React.CSSProperties;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const UnitSystemSelector: React.FC<UnitSystemSelectorProps> = ({
|
|
37
|
+
variant = "buttons",
|
|
38
|
+
label,
|
|
39
|
+
disabled = false,
|
|
40
|
+
className,
|
|
41
|
+
style,
|
|
42
|
+
}) => {
|
|
43
|
+
const ctx = useContext(AutoCoreTagContext);
|
|
44
|
+
const units = ctx.units;
|
|
45
|
+
const active = ctx.activeSystem ?? units?.default_system ?? null;
|
|
46
|
+
|
|
47
|
+
// A machine with no units.json renders nothing rather than an empty control
|
|
48
|
+
// the operator can't use — the HMI still works, in backend units.
|
|
49
|
+
if (!units || !Array.isArray(units.systems) || units.systems.length === 0) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const options = units.systems.map((s) => ({ label: s, value: s }));
|
|
54
|
+
|
|
55
|
+
const onChange = (next: string | null | undefined) => {
|
|
56
|
+
if (!next || next === active) return;
|
|
57
|
+
void ctx.setUnitSystem?.(next);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className={className} style={style}>
|
|
62
|
+
{label ? (
|
|
63
|
+
<div style={{ marginBottom: "0.35rem", fontWeight: 600 }}>{label}</div>
|
|
64
|
+
) : null}
|
|
65
|
+
{variant === "dropdown" ? (
|
|
66
|
+
<Dropdown
|
|
67
|
+
value={active}
|
|
68
|
+
options={options}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
onChange={(e) => onChange(e.value)}
|
|
71
|
+
style={{ minWidth: "10rem" }}
|
|
72
|
+
/>
|
|
73
|
+
) : (
|
|
74
|
+
<SelectButton
|
|
75
|
+
value={active}
|
|
76
|
+
options={options}
|
|
77
|
+
disabled={disabled}
|
|
78
|
+
// PrimeReact clears the value when the active option is
|
|
79
|
+
// re-clicked; ignore that so there is always a system.
|
|
80
|
+
onChange={(e) => onChange(e.value)}
|
|
81
|
+
allowEmpty={false}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default UnitSystemSelector;
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Automated Design Corp. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* UnitsEditor — operator-facing editor for the machine's `units.json`.
|
|
5
|
+
*
|
|
6
|
+
* This is the UI half of the units design (autocore-server/UNITS_PLAN.md). The
|
|
7
|
+
* file it edits is a SIDECAR next to project.json precisely so that this
|
|
8
|
+
* operator work is not wiped by an engineering project.json push.
|
|
9
|
+
*
|
|
10
|
+
* What the operator may change (all display-only, none of it reaches the control
|
|
11
|
+
* program): the display `label`, the `scalar` and `offset` that convert the
|
|
12
|
+
* backend value, and the display `precision` / `precision_mode`. They may also
|
|
13
|
+
* add and remove named systems.
|
|
14
|
+
*
|
|
15
|
+
* What they may NOT change here: `backend` — the unit GM/GNV actually holds. It
|
|
16
|
+
* is a commissioning declaration; changing it on a machine with recorded data is
|
|
17
|
+
* a data migration, not a settings tweak. It is shown, read-only, because you
|
|
18
|
+
* cannot sanely set a scalar without knowing what you are converting *from*.
|
|
19
|
+
*
|
|
20
|
+
* Precision is a first-class column, not an advanced setting: wrong precision is
|
|
21
|
+
* one of the most common customer complaints, and the whole point of putting this
|
|
22
|
+
* screen on the machine is that nobody has to wait for a release to fix it.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
26
|
+
import { DataTable } from "primereact/datatable";
|
|
27
|
+
import { Column } from "primereact/column";
|
|
28
|
+
import { InputText } from "primereact/inputtext";
|
|
29
|
+
import { InputNumber } from "primereact/inputnumber";
|
|
30
|
+
import { Dropdown } from "primereact/dropdown";
|
|
31
|
+
import { Button } from "primereact/button";
|
|
32
|
+
import { SelectButton } from "primereact/selectbutton";
|
|
33
|
+
import { Message } from "primereact/message";
|
|
34
|
+
import { EventEmitterContext } from "../core/EventEmitterContext";
|
|
35
|
+
import { MessageType } from "../hub/CommandMessage";
|
|
36
|
+
import type { PrecisionMode, UnitsTable } from "../core/AutoCoreTagTypes";
|
|
37
|
+
|
|
38
|
+
const PRECISION_MODES: { label: string; value: PrecisionMode }[] = [
|
|
39
|
+
{ label: "Decimals", value: "decimals" },
|
|
40
|
+
{ label: "Significant", value: "significant" },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/** One editable row: a quantity, seen through the system being edited. */
|
|
44
|
+
interface Row {
|
|
45
|
+
quantity: string;
|
|
46
|
+
backend: string;
|
|
47
|
+
kind: "delta" | "absolute";
|
|
48
|
+
label: string;
|
|
49
|
+
scalar: number;
|
|
50
|
+
offset: number;
|
|
51
|
+
precision: number;
|
|
52
|
+
precisionMode: PrecisionMode;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface UnitsEditorProps {
|
|
56
|
+
/** Heading text. Omit for none. */
|
|
57
|
+
title?: string;
|
|
58
|
+
/** Called after a successful save. */
|
|
59
|
+
onSaved?: (units: UnitsTable) => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const UnitsEditor: React.FC<UnitsEditorProps> = ({ title = "Units", onSaved }) => {
|
|
63
|
+
const { invoke } = useContext(EventEmitterContext);
|
|
64
|
+
|
|
65
|
+
const [units, setUnits] = useState<UnitsTable | null>(null);
|
|
66
|
+
const [editSystem, setEditSystem] = useState<string | null>(null);
|
|
67
|
+
const [dirty, setDirty] = useState(false);
|
|
68
|
+
const [busy, setBusy] = useState(false);
|
|
69
|
+
const [error, setError] = useState<string | null>(null);
|
|
70
|
+
const [notice, setNotice] = useState<string | null>(null);
|
|
71
|
+
|
|
72
|
+
const load = useCallback(async () => {
|
|
73
|
+
setBusy(true);
|
|
74
|
+
setError(null);
|
|
75
|
+
try {
|
|
76
|
+
const resp: any = await invoke("system.get_units" as any, MessageType.Request, {} as any);
|
|
77
|
+
const payload = resp?.data ?? resp;
|
|
78
|
+
const table = payload?.units as UnitsTable | null;
|
|
79
|
+
if (!table) {
|
|
80
|
+
setError(
|
|
81
|
+
"This machine has no units.json. Display values are shown in backend " +
|
|
82
|
+
"units until one is installed.",
|
|
83
|
+
);
|
|
84
|
+
setUnits(null);
|
|
85
|
+
} else {
|
|
86
|
+
setUnits(table);
|
|
87
|
+
setEditSystem((prev) =>
|
|
88
|
+
prev && table.systems.includes(prev) ? prev : table.default_system,
|
|
89
|
+
);
|
|
90
|
+
setDirty(false);
|
|
91
|
+
}
|
|
92
|
+
} catch (e) {
|
|
93
|
+
setError(`Could not read units: ${e instanceof Error ? e.message : String(e)}`);
|
|
94
|
+
}
|
|
95
|
+
setBusy(false);
|
|
96
|
+
}, [invoke]);
|
|
97
|
+
|
|
98
|
+
useEffect(() => { void load(); }, [load]);
|
|
99
|
+
|
|
100
|
+
/** Flatten the table into rows for the system being edited. */
|
|
101
|
+
const rows = useMemo<Row[]>(() => {
|
|
102
|
+
if (!units || !editSystem) return [];
|
|
103
|
+
return Object.entries(units.scales)
|
|
104
|
+
.map(([quantity, scale]: [string, any]) => {
|
|
105
|
+
const entry = scale?.[editSystem] ?? {};
|
|
106
|
+
return {
|
|
107
|
+
quantity,
|
|
108
|
+
backend: scale?.backend ?? "",
|
|
109
|
+
kind: (scale?.kind ?? "delta") as "delta" | "absolute",
|
|
110
|
+
label: entry.label ?? "",
|
|
111
|
+
scalar: typeof entry.scalar === "number" ? entry.scalar : 1,
|
|
112
|
+
offset: typeof entry.offset === "number" ? entry.offset : 0,
|
|
113
|
+
precision: typeof entry.precision === "number" ? entry.precision : 3,
|
|
114
|
+
precisionMode: (entry.precision_mode ?? "decimals") as PrecisionMode,
|
|
115
|
+
};
|
|
116
|
+
})
|
|
117
|
+
.sort((a, b) => a.quantity.localeCompare(b.quantity));
|
|
118
|
+
}, [units, editSystem]);
|
|
119
|
+
|
|
120
|
+
/** Write one field of one (quantity, system) entry. */
|
|
121
|
+
const patch = (quantity: string, field: keyof Row, value: any) => {
|
|
122
|
+
if (!units || !editSystem) return;
|
|
123
|
+
const next: UnitsTable = JSON.parse(JSON.stringify(units));
|
|
124
|
+
const scale: any = next.scales[quantity];
|
|
125
|
+
if (!scale) return;
|
|
126
|
+
const entry = { ...(scale[editSystem] ?? {}) };
|
|
127
|
+
switch (field) {
|
|
128
|
+
case "label": entry.label = value; break;
|
|
129
|
+
case "scalar": entry.scalar = value; break;
|
|
130
|
+
case "offset": entry.offset = value; break;
|
|
131
|
+
case "precision": entry.precision = value; break;
|
|
132
|
+
case "precisionMode": entry.precision_mode = value; break;
|
|
133
|
+
default: return;
|
|
134
|
+
}
|
|
135
|
+
scale[editSystem] = entry;
|
|
136
|
+
setUnits(next);
|
|
137
|
+
setDirty(true);
|
|
138
|
+
setNotice(null);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const save = async () => {
|
|
142
|
+
if (!units) return;
|
|
143
|
+
setBusy(true);
|
|
144
|
+
setError(null);
|
|
145
|
+
setNotice(null);
|
|
146
|
+
try {
|
|
147
|
+
// The server validates authoritatively and REFUSES a bad table —
|
|
148
|
+
// an invalid scale would break every display on the machine, so a
|
|
149
|
+
// rejected save must leave the stored file untouched.
|
|
150
|
+
const resp: any = await invoke(
|
|
151
|
+
"system.save_units" as any, MessageType.Request, { units } as any,
|
|
152
|
+
);
|
|
153
|
+
if (!resp?.success) {
|
|
154
|
+
setError(resp?.error_message ?? "Save was refused.");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
setDirty(false);
|
|
158
|
+
setNotice("Saved.");
|
|
159
|
+
onSaved?.(units);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
setError(`Save failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
162
|
+
} finally {
|
|
163
|
+
setBusy(false);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
if (!units) {
|
|
168
|
+
return (
|
|
169
|
+
<div>
|
|
170
|
+
{title ? <h3 style={{ marginTop: 0 }}>{title}</h3> : null}
|
|
171
|
+
{error ? <Message severity="warn" text={error} /> : null}
|
|
172
|
+
<div style={{ marginTop: "0.75rem" }}>
|
|
173
|
+
<Button label="Reload" icon="pi pi-refresh" onClick={() => void load()} loading={busy} />
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div>
|
|
181
|
+
{title ? <h3 style={{ marginTop: 0 }}>{title}</h3> : null}
|
|
182
|
+
|
|
183
|
+
<div style={{
|
|
184
|
+
display: "flex", alignItems: "center", gap: "1rem",
|
|
185
|
+
flexWrap: "wrap", marginBottom: "0.75rem",
|
|
186
|
+
}}>
|
|
187
|
+
<div>
|
|
188
|
+
<div style={{ fontWeight: 600, marginBottom: "0.3rem" }}>Editing system</div>
|
|
189
|
+
<SelectButton
|
|
190
|
+
value={editSystem}
|
|
191
|
+
options={units.systems.map((s) => ({ label: s, value: s }))}
|
|
192
|
+
onChange={(e) => { if (e.value) setEditSystem(e.value); }}
|
|
193
|
+
allowEmpty={false}
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
<div style={{ marginLeft: "auto", display: "flex", gap: "0.5rem" }}>
|
|
197
|
+
<Button
|
|
198
|
+
label="Revert"
|
|
199
|
+
icon="pi pi-undo"
|
|
200
|
+
className="p-button-text"
|
|
201
|
+
disabled={!dirty || busy}
|
|
202
|
+
onClick={() => void load()}
|
|
203
|
+
/>
|
|
204
|
+
<Button
|
|
205
|
+
label="Save"
|
|
206
|
+
icon="pi pi-check"
|
|
207
|
+
disabled={!dirty || busy}
|
|
208
|
+
loading={busy}
|
|
209
|
+
onClick={() => void save()}
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{error ? <Message severity="error" text={error} style={{ marginBottom: "0.5rem" }} /> : null}
|
|
215
|
+
{notice ? <Message severity="success" text={notice} style={{ marginBottom: "0.5rem" }} /> : null}
|
|
216
|
+
|
|
217
|
+
<Message
|
|
218
|
+
severity="info"
|
|
219
|
+
style={{ marginBottom: "0.75rem" }}
|
|
220
|
+
text={
|
|
221
|
+
"Display only — these settings never change what the machine stores or " +
|
|
222
|
+
"how it moves. 'Backend' is the unit the controller holds and is set at " +
|
|
223
|
+
"commissioning."
|
|
224
|
+
}
|
|
225
|
+
/>
|
|
226
|
+
|
|
227
|
+
<DataTable value={rows} size="small" stripedRows scrollable scrollHeight="26rem">
|
|
228
|
+
<Column field="quantity" header="Quantity" style={{ minWidth: "11rem" }} />
|
|
229
|
+
<Column
|
|
230
|
+
field="backend"
|
|
231
|
+
header="Backend"
|
|
232
|
+
style={{ width: "7rem" }}
|
|
233
|
+
body={(r: Row) => (
|
|
234
|
+
<span style={{ opacity: 0.7 }}>
|
|
235
|
+
{r.backend || "—"}
|
|
236
|
+
{r.kind === "absolute" ? " (abs)" : ""}
|
|
237
|
+
</span>
|
|
238
|
+
)}
|
|
239
|
+
/>
|
|
240
|
+
<Column
|
|
241
|
+
header="Label"
|
|
242
|
+
style={{ width: "8rem" }}
|
|
243
|
+
body={(r: Row) => (
|
|
244
|
+
<InputText
|
|
245
|
+
value={r.label}
|
|
246
|
+
style={{ width: "100%" }}
|
|
247
|
+
onChange={(e) => patch(r.quantity, "label", e.target.value)}
|
|
248
|
+
/>
|
|
249
|
+
)}
|
|
250
|
+
/>
|
|
251
|
+
<Column
|
|
252
|
+
header="Scalar"
|
|
253
|
+
style={{ width: "11rem" }}
|
|
254
|
+
body={(r: Row) => (
|
|
255
|
+
<InputNumber
|
|
256
|
+
value={r.scalar}
|
|
257
|
+
minFractionDigits={0}
|
|
258
|
+
maxFractionDigits={10}
|
|
259
|
+
style={{ width: "100%" }}
|
|
260
|
+
inputStyle={{ width: "100%" }}
|
|
261
|
+
onValueChange={(e) => patch(r.quantity, "scalar", e.value ?? 1)}
|
|
262
|
+
/>
|
|
263
|
+
)}
|
|
264
|
+
/>
|
|
265
|
+
<Column
|
|
266
|
+
header="Offset"
|
|
267
|
+
style={{ width: "8rem" }}
|
|
268
|
+
body={(r: Row) =>
|
|
269
|
+
// Only an absolute quantity takes an offset. A temperature
|
|
270
|
+
// DIFFERENCE must not: a 10 °C rise is 18 °F, not 50 °F.
|
|
271
|
+
r.kind === "absolute" ? (
|
|
272
|
+
<InputNumber
|
|
273
|
+
value={r.offset}
|
|
274
|
+
minFractionDigits={0}
|
|
275
|
+
maxFractionDigits={6}
|
|
276
|
+
style={{ width: "100%" }}
|
|
277
|
+
inputStyle={{ width: "100%" }}
|
|
278
|
+
onValueChange={(e) => patch(r.quantity, "offset", e.value ?? 0)}
|
|
279
|
+
/>
|
|
280
|
+
) : (
|
|
281
|
+
<span style={{ opacity: 0.4 }}>n/a</span>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
/>
|
|
285
|
+
<Column
|
|
286
|
+
header="Precision"
|
|
287
|
+
style={{ width: "7rem" }}
|
|
288
|
+
body={(r: Row) => (
|
|
289
|
+
<InputNumber
|
|
290
|
+
value={r.precision}
|
|
291
|
+
min={0}
|
|
292
|
+
max={12}
|
|
293
|
+
showButtons
|
|
294
|
+
style={{ width: "100%" }}
|
|
295
|
+
inputStyle={{ width: "100%" }}
|
|
296
|
+
onValueChange={(e) => patch(r.quantity, "precision", e.value ?? 0)}
|
|
297
|
+
/>
|
|
298
|
+
)}
|
|
299
|
+
/>
|
|
300
|
+
<Column
|
|
301
|
+
header="Mode"
|
|
302
|
+
style={{ width: "9rem" }}
|
|
303
|
+
body={(r: Row) => (
|
|
304
|
+
<Dropdown
|
|
305
|
+
value={r.precisionMode}
|
|
306
|
+
options={PRECISION_MODES}
|
|
307
|
+
style={{ width: "100%" }}
|
|
308
|
+
onChange={(e) => patch(r.quantity, "precisionMode", e.value)}
|
|
309
|
+
/>
|
|
310
|
+
)}
|
|
311
|
+
/>
|
|
312
|
+
</DataTable>
|
|
313
|
+
</div>
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
export default UnitsEditor;
|