@adcops/autocore-react 3.6.7 → 3.6.10
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/ams/AssetEditDialog.d.ts.map +1 -1
- package/dist/components/ams/AssetEditDialog.js +1 -1
- package/dist/components/ams/AssetRegistryTable.d.ts.map +1 -1
- package/dist/components/ams/AssetRegistryTable.js +1 -1
- package/dist/components/ams/index.d.ts +2 -0
- package/dist/components/ams/index.d.ts.map +1 -1
- package/dist/components/ams/index.js +1 -1
- package/dist/components/ams/schemaField.d.ts +95 -0
- package/dist/components/ams/schemaField.d.ts.map +1 -0
- package/dist/components/ams/schemaField.js +1 -0
- package/dist/components/tis/ResultHistoryTable.css +22 -9
- package/dist/components/tis/ResultHistoryTable.d.ts +6 -0
- package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
- package/dist/components/tis/ResultHistoryTable.js +1 -1
- package/package.json +1 -1
- package/src/components/ams/AssetEditDialog.tsx +22 -77
- package/src/components/ams/AssetRegistryTable.tsx +21 -70
- package/src/components/ams/index.ts +5 -0
- package/src/components/ams/schemaField.tsx +166 -0
- package/src/components/tis/ResultHistoryTable.css +22 -9
- package/src/components/tis/ResultHistoryTable.tsx +244 -58
|
@@ -15,6 +15,9 @@ import { Dialog } from 'primereact/dialog';
|
|
|
15
15
|
import { EventEmitterContext } from '../../core/EventEmitterContext';
|
|
16
16
|
import { MessageType } from '../../hub/CommandMessage';
|
|
17
17
|
import { useAms, type AmsAssetEntry, type AmsRole } from './AmsProvider';
|
|
18
|
+
import {
|
|
19
|
+
type SchemaField, SchemaFieldInput, coerceField, fieldLabel,
|
|
20
|
+
} from './schemaField';
|
|
18
21
|
import './AssetRegistryTable.css';
|
|
19
22
|
|
|
20
23
|
// Sentinel value for the "Other..." dropdown option, which lets the
|
|
@@ -76,14 +79,6 @@ function roleUsageSummary(r: AmsRole): string {
|
|
|
76
79
|
/** Render-ready descriptor for one schema-declared field. Schemas come
|
|
77
80
|
* back from `ams.list_schemas` as untyped JSON; this narrows the
|
|
78
81
|
* fields we actually care about for the Add dialog. */
|
|
79
|
-
interface SchemaField {
|
|
80
|
-
name: string;
|
|
81
|
-
type: string;
|
|
82
|
-
units?: string;
|
|
83
|
-
label?: string;
|
|
84
|
-
description?: string;
|
|
85
|
-
required?: boolean;
|
|
86
|
-
}
|
|
87
82
|
|
|
88
83
|
/** The keyed-fields sub_locations schema declared on an asset_type
|
|
89
84
|
* (multi-axis transducers etc.). `keys` fixes the row set; `fields`
|
|
@@ -138,24 +133,6 @@ function seedCustomFromRoleDefaults(
|
|
|
138
133
|
return out;
|
|
139
134
|
}
|
|
140
135
|
|
|
141
|
-
/** Coerce a per-cell string from the matrix input back to its declared
|
|
142
|
-
* type. Numeric strings → JSON numbers; booleans → bool; everything
|
|
143
|
-
* else stays as a string. Matches the top-level fields coercion. */
|
|
144
|
-
function coerceField(field: SchemaField, raw: string): any {
|
|
145
|
-
if (raw === undefined || raw === '') return undefined;
|
|
146
|
-
switch (field.type) {
|
|
147
|
-
case 'f32': case 'f64':
|
|
148
|
-
case 'i8': case 'i16': case 'i32': case 'i64':
|
|
149
|
-
case 'u8': case 'u16': case 'u32': case 'u64': {
|
|
150
|
-
const n = Number(raw);
|
|
151
|
-
return Number.isFinite(n) ? n : undefined;
|
|
152
|
-
}
|
|
153
|
-
case 'bool':
|
|
154
|
-
return raw === 'true' || raw === '1';
|
|
155
|
-
default:
|
|
156
|
-
return raw;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
136
|
|
|
160
137
|
export const AssetRegistryTable: React.FC = () => {
|
|
161
138
|
const { schemas, roles, assets, refreshAssets, setSelection, selection } = useAms();
|
|
@@ -559,44 +536,21 @@ export const AssetRegistryTable: React.FC = () => {
|
|
|
559
536
|
are what the placeholder resolver reads to seed
|
|
560
537
|
NI bridge channels and EL3356 SDOs. Required
|
|
561
538
|
fields gate Create. */}
|
|
562
|
-
{fieldsForType.map(field =>
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
{ label: 'true', value: 'true' },
|
|
578
|
-
{ label: 'false', value: 'false' },
|
|
579
|
-
]}
|
|
580
|
-
onChange={(e) => setAddState(s => ({
|
|
581
|
-
...s,
|
|
582
|
-
customFields: { ...s.customFields, [field.name]: e.value },
|
|
583
|
-
}))}
|
|
584
|
-
placeholder="—"
|
|
585
|
-
/>
|
|
586
|
-
) : (
|
|
587
|
-
<InputText
|
|
588
|
-
value={addState.customFields[field.name] ?? ''}
|
|
589
|
-
keyfilter={isNum ? 'num' : undefined}
|
|
590
|
-
placeholder={field.description ?? undefined}
|
|
591
|
-
onChange={(e) => setAddState(s => ({
|
|
592
|
-
...s,
|
|
593
|
-
customFields: { ...s.customFields, [field.name]: e.target.value },
|
|
594
|
-
}))}
|
|
595
|
-
/>
|
|
596
|
-
)}
|
|
597
|
-
</React.Fragment>
|
|
598
|
-
);
|
|
599
|
-
})}
|
|
539
|
+
{fieldsForType.map(field => (
|
|
540
|
+
<React.Fragment key={field.name}>
|
|
541
|
+
<label title={field.description ?? undefined}>
|
|
542
|
+
{fieldLabel(field)}
|
|
543
|
+
</label>
|
|
544
|
+
<SchemaFieldInput
|
|
545
|
+
field={field}
|
|
546
|
+
value={addState.customFields[field.name] ?? ''}
|
|
547
|
+
onChange={(v) => setAddState(s => ({
|
|
548
|
+
...s,
|
|
549
|
+
customFields: { ...s.customFields, [field.name]: v },
|
|
550
|
+
}))}
|
|
551
|
+
/>
|
|
552
|
+
</React.Fragment>
|
|
553
|
+
))}
|
|
600
554
|
</div>
|
|
601
555
|
|
|
602
556
|
{/* Per-axis matrix for asset types with a keyed-fields
|
|
@@ -643,16 +597,13 @@ export const AssetRegistryTable: React.FC = () => {
|
|
|
643
597
|
{subLocationsSchema.fields.map(field => {
|
|
644
598
|
const cellValue =
|
|
645
599
|
addState.subLocationFields[key]?.[field.name] ?? '';
|
|
646
|
-
const isNum =
|
|
647
|
-
field.type !== 'string' && field.type !== 'bool';
|
|
648
600
|
return (
|
|
649
601
|
<td key={field.name}
|
|
650
602
|
style={{ padding: '0.25rem 0.5rem' }}>
|
|
651
|
-
<
|
|
603
|
+
<SchemaFieldInput
|
|
604
|
+
field={field}
|
|
652
605
|
value={cellValue}
|
|
653
|
-
|
|
654
|
-
onChange={(e) => {
|
|
655
|
-
const v = e.target.value;
|
|
606
|
+
onChange={(v) => {
|
|
656
607
|
setAddState(s => ({
|
|
657
608
|
...s,
|
|
658
609
|
subLocationFields: {
|
|
@@ -19,5 +19,10 @@ export { MissingAssetsBanner } from './MissingAssetsBanner';
|
|
|
19
19
|
export { InstalledAssetPicker } from './InstalledAssetPicker';
|
|
20
20
|
export type { InstalledAssetPickerProps } from './InstalledAssetPicker';
|
|
21
21
|
export { AssetNameplateGrid } from './AssetNameplateGrid';
|
|
22
|
+
// One schema field: its shape, how a form value is coerced back to its
|
|
23
|
+
// declared type, and the input control that edits it. Shared so a field can
|
|
24
|
+
// gain a behaviour (a dropdown, say) in one place rather than in each form.
|
|
25
|
+
export { SchemaFieldInput, coerceField, fieldChoices, fieldLabel, isNumericField } from './schemaField';
|
|
26
|
+
export type { SchemaField, FieldOption, Choice, SchemaFieldInputProps } from './schemaField';
|
|
22
27
|
export { useInstalledAsset } from './useInstalledAsset';
|
|
23
28
|
export type { InstalledAssetState } from './useInstalledAsset';
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* One asset-schema field: its shape, how a typed value is coerced out of the
|
|
4
|
+
* string the form holds, and the input control that edits it.
|
|
5
|
+
*
|
|
6
|
+
* Every AMS form renders the same thing in seven places — the asset create
|
|
7
|
+
* form, the asset edit form, the per-axis sub-location matrix in each of those,
|
|
8
|
+
* the per-axis calibration matrix, the flat calibration values, and the
|
|
9
|
+
* standalone calibration dialog. They had drifted into seven copies of the same
|
|
10
|
+
* `label ?? name` / `[units]` / `*` / bool-Dropdown-else-InputText block, plus
|
|
11
|
+
* three copies of `SchemaField` and two of `coerceField`.
|
|
12
|
+
*
|
|
13
|
+
* That duplication is why a field could not gain a behaviour: adding one meant
|
|
14
|
+
* finding all seven. `capacity_units` is the case that forced this — it is a
|
|
15
|
+
* free-text string today, so an operator typed "Newtons" where the consuming
|
|
16
|
+
* machine matched on "N", and the fix is a dropdown in every one of those
|
|
17
|
+
* places or it is not a fix.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import React from 'react';
|
|
21
|
+
import { Dropdown } from 'primereact/dropdown';
|
|
22
|
+
import { InputText } from 'primereact/inputtext';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* One choice in a field's dropdown, as authored in the asset-type schema.
|
|
26
|
+
*
|
|
27
|
+
* Mirrors `FieldOption` in autocore-ams's `config.rs`: either a bare scalar,
|
|
28
|
+
* where the stored value and the operator-facing label are the same, or an
|
|
29
|
+
* explicit pair when they should differ.
|
|
30
|
+
*
|
|
31
|
+
* ```jsonc
|
|
32
|
+
* "options": ["N", "kN", "lbf"]
|
|
33
|
+
* "options": [{ "label": "Newtons (N)", "value": "N" }]
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export type FieldOption =
|
|
37
|
+
| string
|
|
38
|
+
| number
|
|
39
|
+
| boolean
|
|
40
|
+
| { label: string; value: unknown };
|
|
41
|
+
|
|
42
|
+
/** One field of an asset type's `fields` / `calibration_fields` schema. */
|
|
43
|
+
export interface SchemaField {
|
|
44
|
+
name: string;
|
|
45
|
+
type: string;
|
|
46
|
+
units?: string;
|
|
47
|
+
label?: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
required?: boolean;
|
|
50
|
+
/** Fixed set of choices. When present the field is a dropdown, not a
|
|
51
|
+
* free-form input — see the file header. */
|
|
52
|
+
options?: FieldOption[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A choice normalised for PrimeReact's `Dropdown`. */
|
|
56
|
+
export interface Choice {
|
|
57
|
+
label: string;
|
|
58
|
+
value: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The choices this field offers, or `null` if it is free-form.
|
|
63
|
+
*
|
|
64
|
+
* Booleans are a fixed two-choice dropdown, as they always were. An explicit
|
|
65
|
+
* `options` list wins over the type's own rendering, which is what lets a
|
|
66
|
+
* `string` field be constrained without inventing a new type.
|
|
67
|
+
*
|
|
68
|
+
* Values are carried as STRINGS because that is what the form state holds;
|
|
69
|
+
* `coerceField` converts back on submit.
|
|
70
|
+
*/
|
|
71
|
+
export function fieldChoices(field: SchemaField): Choice[] | null {
|
|
72
|
+
if (field.options && field.options.length > 0) {
|
|
73
|
+
return field.options.map((opt) =>
|
|
74
|
+
opt !== null && typeof opt === 'object' && 'value' in opt
|
|
75
|
+
? { label: String(opt.label), value: String(opt.value) }
|
|
76
|
+
: { label: String(opt), value: String(opt) });
|
|
77
|
+
}
|
|
78
|
+
if (field.type === 'bool') {
|
|
79
|
+
return [
|
|
80
|
+
{ label: 'true', value: 'true' },
|
|
81
|
+
{ label: 'false', value: 'false' },
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** True when the field takes digits only, so the input can keyfilter. A field
|
|
88
|
+
* with `options` is never free-typed, so it never keyfilters. */
|
|
89
|
+
export function isNumericField(field: SchemaField): boolean {
|
|
90
|
+
if (fieldChoices(field)) return false;
|
|
91
|
+
return field.type !== 'string' && field.type !== 'bool';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Coerce the form's string back to the field's declared type.
|
|
96
|
+
*
|
|
97
|
+
* Numeric strings become JSON numbers and booleans become bools, because the
|
|
98
|
+
* placeholder resolver and the SDO writers downstream expect native types — a
|
|
99
|
+
* string `"5000"` does not satisfy a `${ams.*}` reference asking for a numeric
|
|
100
|
+
* `max_val`. Empty stays `undefined` so an untouched field is omitted from the
|
|
101
|
+
* record rather than stored blank.
|
|
102
|
+
*/
|
|
103
|
+
export function coerceField(field: SchemaField, raw: string | undefined): any {
|
|
104
|
+
if (raw === undefined || raw === '') return undefined;
|
|
105
|
+
switch (field.type) {
|
|
106
|
+
case 'f32': case 'f64':
|
|
107
|
+
case 'i8': case 'i16': case 'i32': case 'i64':
|
|
108
|
+
case 'u8': case 'u16': case 'u32': case 'u64': {
|
|
109
|
+
const n = Number(raw);
|
|
110
|
+
return Number.isFinite(n) ? n : undefined;
|
|
111
|
+
}
|
|
112
|
+
case 'bool':
|
|
113
|
+
return raw === 'true' || raw === '1';
|
|
114
|
+
default:
|
|
115
|
+
return raw;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The label a form shows for this field: pretty name, units, required mark. */
|
|
120
|
+
export function fieldLabel(field: SchemaField): string {
|
|
121
|
+
const base = field.label ?? field.name;
|
|
122
|
+
const req = field.required ? ' *' : '';
|
|
123
|
+
return field.units ? `${base} [${field.units}]${req}` : `${base}${req}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface SchemaFieldInputProps {
|
|
127
|
+
field: SchemaField;
|
|
128
|
+
/** Current value, as the string the form state holds. */
|
|
129
|
+
value: string;
|
|
130
|
+
/** Called with the new string. Coercion happens on submit, not here. */
|
|
131
|
+
onChange: (next: string) => void;
|
|
132
|
+
style?: React.CSSProperties;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The input control for one schema field: a dropdown when the field has a
|
|
137
|
+
* fixed set of choices, a text box otherwise.
|
|
138
|
+
*
|
|
139
|
+
* Deliberately uncontrolled about layout — callers place it in a grid or a
|
|
140
|
+
* table cell — so this owns only the decision that must be the same everywhere.
|
|
141
|
+
*/
|
|
142
|
+
export const SchemaFieldInput: React.FC<SchemaFieldInputProps> = ({
|
|
143
|
+
field, value, onChange, style,
|
|
144
|
+
}) => {
|
|
145
|
+
const choices = fieldChoices(field);
|
|
146
|
+
if (choices) {
|
|
147
|
+
return (
|
|
148
|
+
<Dropdown
|
|
149
|
+
value={value ?? ''}
|
|
150
|
+
options={choices}
|
|
151
|
+
onChange={(e) => onChange(e.value)}
|
|
152
|
+
placeholder="—"
|
|
153
|
+
style={style}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return (
|
|
158
|
+
<InputText
|
|
159
|
+
value={value ?? ''}
|
|
160
|
+
keyfilter={isNumericField(field) ? 'num' : undefined}
|
|
161
|
+
placeholder={field.description ?? undefined}
|
|
162
|
+
onChange={(e) => onChange(e.target.value)}
|
|
163
|
+
style={style}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
@@ -1,12 +1,25 @@
|
|
|
1
|
-
/* Runs excluded from cross-test aggregation are kept visible but dimmed
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/* Runs excluded from cross-test aggregation are kept visible but dimmed, so an
|
|
2
|
+
operator can see at a glance which rows don't feed the avg/min/max/stddev/
|
|
3
|
+
count results for their sample group. The row's Status column says the word
|
|
4
|
+
"Excluded" outright, and its Details dialog says what that means — the dim
|
|
5
|
+
is a scanning aid, never the only signal.
|
|
6
|
+
|
|
7
|
+
NO line-through. It used to be here and it read as "deleted": an excluded
|
|
8
|
+
run is intact, still downloadable, and one click from being counted again.
|
|
9
|
+
|
|
10
|
+
Nor is there an exception rule for the action buttons. There was one —
|
|
11
|
+
`.tis-row-excluded > td .p-button { opacity: 1; text-decoration: none }` —
|
|
12
|
+
and neither half of it could ever have worked, because both properties come
|
|
13
|
+
from the ancestor `td`:
|
|
14
|
+
|
|
15
|
+
• `opacity` on the td composites its whole subtree as one group. A
|
|
16
|
+
descendant's `opacity: 1` cannot escape an ancestor's 0.55.
|
|
17
|
+
• `text-decoration` propagates from an ancestor to its in-flow
|
|
18
|
+
descendants and cannot be cancelled by a descendant at all.
|
|
19
|
+
|
|
20
|
+
So anything that must be exempt has to be exempt at the td: put the effect
|
|
21
|
+
on the text cells, not on every cell. Kept as one rule here because 0.55
|
|
22
|
+
leaves the buttons legible and they are what the operator reaches for. */
|
|
4
23
|
.tis-row-excluded > td {
|
|
5
24
|
opacity: 0.55;
|
|
6
|
-
text-decoration: line-through;
|
|
7
|
-
}
|
|
8
|
-
/* Keep the action buttons (Include / Download) legible and clickable. */
|
|
9
|
-
.tis-row-excluded > td .p-button {
|
|
10
|
-
opacity: 1;
|
|
11
|
-
text-decoration: none;
|
|
12
25
|
}
|