@adcops/autocore-react 3.3.111 → 3.3.113
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/assets/JogYDownLeft.d.ts +4 -0
- package/dist/assets/JogYDownLeft.d.ts.map +1 -0
- package/dist/assets/JogYDownLeft.js +1 -0
- package/dist/assets/JogYUpRight.d.ts +4 -0
- package/dist/assets/JogYUpRight.d.ts.map +1 -0
- package/dist/assets/JogYUpRight.js +1 -0
- package/dist/assets/index.d.ts +2 -2
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +1 -1
- package/dist/components/JogPanel.js +1 -1
- package/dist/components/ams/AmsProvider.d.ts.map +1 -1
- package/dist/components/ams/AmsProvider.js +1 -1
- package/dist/components/ams-editor/AssetFieldArray.d.ts +21 -0
- package/dist/components/ams-editor/AssetFieldArray.d.ts.map +1 -0
- package/dist/components/ams-editor/AssetFieldArray.js +1 -0
- package/dist/components/ams-editor/AssetTypeEditor.css +122 -0
- package/dist/components/ams-editor/AssetTypeEditor.d.ts +26 -0
- package/dist/components/ams-editor/AssetTypeEditor.d.ts.map +1 -0
- package/dist/components/ams-editor/AssetTypeEditor.js +1 -0
- package/dist/components/ams-editor/AssetTypeFormEditor.d.ts +21 -0
- package/dist/components/ams-editor/AssetTypeFormEditor.d.ts.map +1 -0
- package/dist/components/ams-editor/AssetTypeFormEditor.js +1 -0
- package/dist/components/ams-editor/SubLocationsEditor.d.ts +17 -0
- package/dist/components/ams-editor/SubLocationsEditor.d.ts.map +1 -0
- package/dist/components/ams-editor/SubLocationsEditor.js +1 -0
- package/dist/components/ams-editor/index.d.ts +7 -0
- package/dist/components/ams-editor/index.d.ts.map +1 -0
- package/dist/components/ams-editor/index.js +1 -0
- package/dist/components/ams-editor/types.d.ts +34 -0
- package/dist/components/ams-editor/types.d.ts.map +1 -0
- package/dist/components/ams-editor/types.js +1 -0
- package/dist/components/tis-editor/TisConfigEditor.d.ts +1 -1
- package/dist/components/tis-editor/editor/SaveDiffDialog.d.ts.map +1 -1
- package/dist/components/tis-editor/editor/SaveDiffDialog.js +1 -1
- package/dist/hooks/useAmsSchemaConfig.d.ts +39 -0
- package/dist/hooks/useAmsSchemaConfig.d.ts.map +1 -0
- package/dist/hooks/useAmsSchemaConfig.js +1 -0
- package/dist/hooks/useTisConfig.d.ts +1 -1
- package/package.json +1 -1
- package/src/assets/{JogYPos.tsx → JogYDownLeft.tsx} +2 -2
- package/src/assets/{JogYNeg.tsx → JogYUpRight.tsx} +3 -2
- package/src/assets/index.ts +2 -2
- package/src/components/JogPanel.tsx +7 -7
- package/src/components/ams/AmsProvider.tsx +20 -15
- package/src/components/ams-editor/AssetFieldArray.tsx +103 -0
- package/src/components/ams-editor/AssetTypeEditor.css +122 -0
- package/src/components/ams-editor/AssetTypeEditor.tsx +295 -0
- package/src/components/ams-editor/AssetTypeFormEditor.tsx +178 -0
- package/src/components/ams-editor/SubLocationsEditor.tsx +151 -0
- package/src/components/ams-editor/index.ts +16 -0
- package/src/components/ams-editor/types.ts +40 -0
- package/src/components/tis-editor/TisConfigEditor.tsx +1 -1
- package/src/components/tis-editor/editor/SaveDiffDialog.tsx +4 -3
- package/src/hooks/useAmsSchemaConfig.ts +142 -0
- package/src/hooks/useTisConfig.ts +1 -1
- package/dist/assets/JogYNeg.d.ts +0 -4
- package/dist/assets/JogYNeg.d.ts.map +0 -1
- package/dist/assets/JogYNeg.js +0 -1
- package/dist/assets/JogYPos.d.ts +0 -4
- package/dist/assets/JogYPos.d.ts.map +0 -1
- package/dist/assets/JogYPos.js +0 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AssetTypeFormEditor — tabbed shell for a single custom asset type,
|
|
3
|
+
* mirroring the TIS MethodFormEditor. Tabs: Identity, Fields, Calibration
|
|
4
|
+
* Fields, Sub-locations, and an always-available JSON escape hatch.
|
|
5
|
+
*
|
|
6
|
+
* Owns the local working copy; hands the changed copy back via onApply —
|
|
7
|
+
* the parent decides when to call ams.put_asset_type.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
11
|
+
import { TabView, TabPanel } from 'primereact/tabview';
|
|
12
|
+
import { Button } from 'primereact/button';
|
|
13
|
+
import { InputText } from 'primereact/inputtext';
|
|
14
|
+
import { InputTextarea } from 'primereact/inputtextarea';
|
|
15
|
+
import { Dropdown } from 'primereact/dropdown';
|
|
16
|
+
import { CodeEditor } from '../CodeEditor';
|
|
17
|
+
import { FormSection } from '../forms/FormSection';
|
|
18
|
+
import { FormRow } from '../forms/FormRow';
|
|
19
|
+
import { AssetFieldArray } from './AssetFieldArray';
|
|
20
|
+
import { SubLocationsEditor } from './SubLocationsEditor';
|
|
21
|
+
import type { AssetTypeDef, TestField } from './types';
|
|
22
|
+
|
|
23
|
+
export interface AssetTypeFormEditorProps {
|
|
24
|
+
typeId: string;
|
|
25
|
+
def: AssetTypeDef;
|
|
26
|
+
/** Called on Apply with the working copy — parent forwards to put_asset_type. */
|
|
27
|
+
onApply: (next: AssetTypeDef) => Promise<void> | void;
|
|
28
|
+
/** Built-in type names, for the `extends` dropdown. */
|
|
29
|
+
builtinTypes?: string[];
|
|
30
|
+
busy?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const AssetTypeFormEditor: React.FC<AssetTypeFormEditorProps> = ({
|
|
34
|
+
typeId, def, onApply, builtinTypes = [], busy,
|
|
35
|
+
}) => {
|
|
36
|
+
const [draft, setDraft] = useState<AssetTypeDef>(def);
|
|
37
|
+
const [jsonText, setJsonText] = useState<string>('');
|
|
38
|
+
const [jsonError, setJsonError] = useState<string | null>(null);
|
|
39
|
+
const [activeTab, setActiveTab] = useState<number>(0);
|
|
40
|
+
|
|
41
|
+
// Reset the working copy whenever the incoming type changes (selection
|
|
42
|
+
// change, or a fresh version after save). Unapplied edits are discarded.
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
setDraft(def);
|
|
45
|
+
setJsonText(JSON.stringify(def, null, 2));
|
|
46
|
+
setJsonError(null);
|
|
47
|
+
}, [def, typeId]);
|
|
48
|
+
|
|
49
|
+
const dirty = useMemo(() => {
|
|
50
|
+
try { return JSON.stringify(draft) !== JSON.stringify(def); }
|
|
51
|
+
catch { return true; }
|
|
52
|
+
}, [draft, def]);
|
|
53
|
+
|
|
54
|
+
const handleFormChange = (next: AssetTypeDef) => {
|
|
55
|
+
setDraft(next);
|
|
56
|
+
setJsonText(JSON.stringify(next, null, 2));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleJsonChange = (v: string | undefined) => {
|
|
60
|
+
const text = v ?? '';
|
|
61
|
+
setJsonText(text);
|
|
62
|
+
try {
|
|
63
|
+
setDraft(JSON.parse(text));
|
|
64
|
+
setJsonError(null);
|
|
65
|
+
} catch (e: any) {
|
|
66
|
+
setJsonError(String(e?.message ?? e));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleApply = async () => {
|
|
71
|
+
if (jsonError) return;
|
|
72
|
+
await onApply(draft);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const extendsOptions = [
|
|
76
|
+
{ label: '— none —', value: '' },
|
|
77
|
+
...builtinTypes.map(t => ({ label: `builtin: ${t}`, value: t })),
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>
|
|
82
|
+
<div
|
|
83
|
+
style={{
|
|
84
|
+
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
85
|
+
padding: '0.5rem 1rem',
|
|
86
|
+
borderBottom: '1px solid var(--surface-d, #e2e8f0)',
|
|
87
|
+
background: 'var(--surface-b, #f8fafc)',
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
<strong>
|
|
91
|
+
{typeId}
|
|
92
|
+
{dirty && <span style={{ marginLeft: '0.5rem', color: '#ea580c' }}>•</span>}
|
|
93
|
+
</strong>
|
|
94
|
+
<Button
|
|
95
|
+
label="Apply"
|
|
96
|
+
icon="pi pi-check"
|
|
97
|
+
disabled={busy || !dirty || !!jsonError}
|
|
98
|
+
onClick={handleApply}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div style={{ flex: 1, minHeight: 0, overflow: 'auto' }}>
|
|
103
|
+
<TabView activeIndex={activeTab} onTabChange={(e) => setActiveTab(e.index)}>
|
|
104
|
+
<TabPanel header="Identity">
|
|
105
|
+
<FormSection title="Identity" description="How this type appears in the Add-Asset picker and how its asset IDs are prefixed.">
|
|
106
|
+
<FormRow label="Label" hint="Pretty name in the asset picker. Falls back to the type id.">
|
|
107
|
+
<InputText
|
|
108
|
+
value={draft.label ?? ''}
|
|
109
|
+
onChange={(e) => handleFormChange({ ...draft, label: e.target.value || undefined })}
|
|
110
|
+
/>
|
|
111
|
+
</FormRow>
|
|
112
|
+
<FormRow label="Description">
|
|
113
|
+
<InputTextarea
|
|
114
|
+
rows={2}
|
|
115
|
+
value={draft.description ?? ''}
|
|
116
|
+
onChange={(e) => handleFormChange({ ...draft, description: e.target.value || undefined })}
|
|
117
|
+
/>
|
|
118
|
+
</FormRow>
|
|
119
|
+
<FormRow label="ID prefix" hint="Asset IDs are minted as <prefix>001, 002, … Defaults to A- when blank.">
|
|
120
|
+
<InputText
|
|
121
|
+
value={draft.id_prefix ?? ''}
|
|
122
|
+
placeholder="e.g. TSDR-"
|
|
123
|
+
onChange={(e) => handleFormChange({ ...draft, id_prefix: e.target.value || undefined })}
|
|
124
|
+
/>
|
|
125
|
+
</FormRow>
|
|
126
|
+
<FormRow label="Extends" hint="Optionally build on a built-in type's schema (merges its fields under yours).">
|
|
127
|
+
<Dropdown
|
|
128
|
+
value={draft.extends ?? ''}
|
|
129
|
+
options={extendsOptions}
|
|
130
|
+
onChange={(e) => handleFormChange({ ...draft, extends: e.value || undefined })}
|
|
131
|
+
editable
|
|
132
|
+
/>
|
|
133
|
+
</FormRow>
|
|
134
|
+
</FormSection>
|
|
135
|
+
</TabPanel>
|
|
136
|
+
<TabPanel header="Fields">
|
|
137
|
+
<AssetFieldArray
|
|
138
|
+
title="Nameplate Fields"
|
|
139
|
+
description="Per-asset fields captured when the asset is registered (e.g. capacity, material)."
|
|
140
|
+
fields={(draft.fields ?? []) as TestField[]}
|
|
141
|
+
onChange={(next) => handleFormChange({ ...draft, fields: next })}
|
|
142
|
+
/>
|
|
143
|
+
</TabPanel>
|
|
144
|
+
<TabPanel header="Calibration Fields">
|
|
145
|
+
<AssetFieldArray
|
|
146
|
+
title="Calibration Fields"
|
|
147
|
+
description="The calibration-record schema (e.g. scale, offset). Leave empty for types that don't calibrate."
|
|
148
|
+
fields={(draft.calibration_fields ?? []) as TestField[]}
|
|
149
|
+
onChange={(next) => handleFormChange({ ...draft, calibration_fields: next })}
|
|
150
|
+
/>
|
|
151
|
+
</TabPanel>
|
|
152
|
+
<TabPanel header="Sub-locations">
|
|
153
|
+
<SubLocationsEditor def={draft} onChange={handleFormChange} />
|
|
154
|
+
</TabPanel>
|
|
155
|
+
<TabPanel header="JSON">
|
|
156
|
+
{jsonError && (
|
|
157
|
+
<div style={{ color: '#dc2626', marginBottom: '0.5rem', fontFamily: 'monospace' }}>
|
|
158
|
+
{jsonError}
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
<div style={{ height: '60vh' }}>
|
|
162
|
+
<CodeEditor
|
|
163
|
+
code={jsonText}
|
|
164
|
+
language="json"
|
|
165
|
+
theme="vs-dark"
|
|
166
|
+
readOnly={false}
|
|
167
|
+
cursorStyle="line"
|
|
168
|
+
onCodeChanged={handleJsonChange}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
</TabPanel>
|
|
172
|
+
</TabView>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export default AssetTypeFormEditor;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SubLocationsEditor — edits the keyed-fields (multi-axis) `sub_locations`
|
|
3
|
+
* shape of an asset type: a fixed set of keys (e.g. x/y/z axes) plus
|
|
4
|
+
* per-key nameplate and calibration fields.
|
|
5
|
+
*
|
|
6
|
+
* The positional (surface-lanes) shape — `count` + `per_location_state` —
|
|
7
|
+
* is not editable here; it's left to the form's JSON tab. Assets without
|
|
8
|
+
* any sub_locations can add a keyed schema with one click.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Button } from 'primereact/button';
|
|
12
|
+
import { InputText } from 'primereact/inputtext';
|
|
13
|
+
import { FormSection } from '../forms/FormSection';
|
|
14
|
+
import { FormRow } from '../forms/FormRow';
|
|
15
|
+
import { AssetFieldArray } from './AssetFieldArray';
|
|
16
|
+
import { isKeyedSubLocations, type AssetTypeDef, type KeyedSubLocations, type TestField } from './types';
|
|
17
|
+
|
|
18
|
+
export interface SubLocationsEditorProps {
|
|
19
|
+
def: AssetTypeDef;
|
|
20
|
+
onChange: (next: AssetTypeDef) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const EMPTY_KEYED: KeyedSubLocations = { keys: [], fields: [], calibration_fields: [] };
|
|
24
|
+
|
|
25
|
+
export const SubLocationsEditor: React.FC<SubLocationsEditorProps> = ({ def, onChange }) => {
|
|
26
|
+
const sl = def.sub_locations;
|
|
27
|
+
|
|
28
|
+
// No sub_locations yet — offer to add a keyed schema.
|
|
29
|
+
if (sl == null) {
|
|
30
|
+
return (
|
|
31
|
+
<FormSection
|
|
32
|
+
title="Sub-locations"
|
|
33
|
+
description="Optional. For multi-axis assets (e.g. a triaxial transducer), declare a fixed set of keys with per-key fields and calibration."
|
|
34
|
+
>
|
|
35
|
+
<Button
|
|
36
|
+
label="Add multi-axis sub-locations"
|
|
37
|
+
icon="pi pi-plus"
|
|
38
|
+
size="small"
|
|
39
|
+
onClick={() => onChange({ ...def, sub_locations: { ...EMPTY_KEYED } })}
|
|
40
|
+
/>
|
|
41
|
+
</FormSection>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Present but positional (surface-lanes) — not editable in the form.
|
|
46
|
+
if (!isKeyedSubLocations(sl)) {
|
|
47
|
+
return (
|
|
48
|
+
<FormSection title="Sub-locations">
|
|
49
|
+
<p style={{ color: 'var(--text-color-secondary, #64748b)' }}>
|
|
50
|
+
This type uses the positional (surface-lanes) sub-locations shape,
|
|
51
|
+
which isn't editable in the form. Use the <strong>JSON</strong> tab
|
|
52
|
+
to edit it.
|
|
53
|
+
</p>
|
|
54
|
+
</FormSection>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const keyed = sl as KeyedSubLocations;
|
|
59
|
+
const setKeyed = (next: KeyedSubLocations) => onChange({ ...def, sub_locations: next });
|
|
60
|
+
|
|
61
|
+
const setKey = (idx: number, value: string) => {
|
|
62
|
+
const keys = [...keyed.keys];
|
|
63
|
+
keys[idx] = value;
|
|
64
|
+
setKeyed({ ...keyed, keys });
|
|
65
|
+
};
|
|
66
|
+
const addKey = () => setKeyed({ ...keyed, keys: [...keyed.keys, ''] });
|
|
67
|
+
const removeKey = (idx: number) =>
|
|
68
|
+
setKeyed({ ...keyed, keys: keyed.keys.filter((_, i) => i !== idx) });
|
|
69
|
+
const moveKey = (idx: number, dir: -1 | 1) => {
|
|
70
|
+
const j = idx + dir;
|
|
71
|
+
if (j < 0 || j >= keyed.keys.length) return;
|
|
72
|
+
const keys = [...keyed.keys];
|
|
73
|
+
[keys[idx], keys[j]] = [keys[j], keys[idx]];
|
|
74
|
+
setKeyed({ ...keyed, keys });
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<>
|
|
79
|
+
<FormSection
|
|
80
|
+
title="Sub-locations (multi-axis)"
|
|
81
|
+
description="A fixed, ordered set of keys. Each asset carries per-key values; calibration records carry a per-key sub-object."
|
|
82
|
+
actions={
|
|
83
|
+
<Button
|
|
84
|
+
label="Remove sub-locations"
|
|
85
|
+
icon="pi pi-trash"
|
|
86
|
+
className="p-button-text p-button-danger"
|
|
87
|
+
size="small"
|
|
88
|
+
onClick={() => {
|
|
89
|
+
if (window.confirm('Remove the sub-locations schema from this type?')) {
|
|
90
|
+
const next = { ...def };
|
|
91
|
+
delete next.sub_locations;
|
|
92
|
+
onChange(next);
|
|
93
|
+
}
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
}
|
|
97
|
+
>
|
|
98
|
+
<FormRow label="Group label" hint="Pretty label for the set (e.g. Axes).">
|
|
99
|
+
<InputText
|
|
100
|
+
value={keyed.label ?? ''}
|
|
101
|
+
onChange={(e) => setKeyed({ ...keyed, label: e.target.value || undefined })}
|
|
102
|
+
/>
|
|
103
|
+
</FormRow>
|
|
104
|
+
<FormRow label="Key label" hint="Pretty label for one key (e.g. Axis).">
|
|
105
|
+
<InputText
|
|
106
|
+
value={keyed.key_label ?? ''}
|
|
107
|
+
onChange={(e) => setKeyed({ ...keyed, key_label: e.target.value || undefined })}
|
|
108
|
+
/>
|
|
109
|
+
</FormRow>
|
|
110
|
+
<FormRow label="Keys" hint="Ordered. Each becomes a per-key column on assets & calibrations.">
|
|
111
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.35rem' }}>
|
|
112
|
+
{keyed.keys.map((k, i) => (
|
|
113
|
+
<div key={i} style={{ display: 'flex', gap: '0.25rem', alignItems: 'center' }}>
|
|
114
|
+
<InputText
|
|
115
|
+
value={k}
|
|
116
|
+
placeholder="e.g. x"
|
|
117
|
+
onChange={(e) => setKey(i, e.target.value)}
|
|
118
|
+
style={{ flex: 1 }}
|
|
119
|
+
/>
|
|
120
|
+
<Button icon="pi pi-arrow-up" className="p-button-text p-button-sm"
|
|
121
|
+
disabled={i === 0} onClick={() => moveKey(i, -1)} aria-label="Move up" />
|
|
122
|
+
<Button icon="pi pi-arrow-down" className="p-button-text p-button-sm"
|
|
123
|
+
disabled={i === keyed.keys.length - 1} onClick={() => moveKey(i, 1)} aria-label="Move down" />
|
|
124
|
+
<Button icon="pi pi-trash" className="p-button-text p-button-danger p-button-sm"
|
|
125
|
+
onClick={() => removeKey(i)} aria-label="Remove key" />
|
|
126
|
+
</div>
|
|
127
|
+
))}
|
|
128
|
+
<div>
|
|
129
|
+
<Button label="Add key" icon="pi pi-plus" size="small" className="p-button-secondary" onClick={addKey} />
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</FormRow>
|
|
133
|
+
</FormSection>
|
|
134
|
+
|
|
135
|
+
<AssetFieldArray
|
|
136
|
+
title="Per-key fields"
|
|
137
|
+
description="Nameplate fields recorded once per key on each asset."
|
|
138
|
+
fields={(keyed.fields ?? []) as TestField[]}
|
|
139
|
+
onChange={(next) => setKeyed({ ...keyed, fields: next })}
|
|
140
|
+
/>
|
|
141
|
+
<AssetFieldArray
|
|
142
|
+
title="Per-key calibration fields"
|
|
143
|
+
description="Calibration values captured per key. When present, each calibration record must carry a per-key sub-object."
|
|
144
|
+
fields={(keyed.calibration_fields ?? []) as TestField[]}
|
|
145
|
+
onChange={(next) => setKeyed({ ...keyed, calibration_fields: next })}
|
|
146
|
+
/>
|
|
147
|
+
</>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export default SubLocationsEditor;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* AMS asset-TYPE editor. Operator-facing HMI screen for authoring the
|
|
3
|
+
* custom asset types that AssetRegistryTable / AssetEditDialog then create
|
|
4
|
+
* instances of. Mount inside a Settings screen (e.g. a PrimeReact
|
|
5
|
+
* AccordionTab), like the TIS TisConfigEditor.
|
|
6
|
+
*
|
|
7
|
+
* Deep-imported by consumer apps:
|
|
8
|
+
* import { AssetTypeEditor } from '@adcops/autocore-react/components/ams-editor/AssetTypeEditor';
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { AssetTypeEditor } from './AssetTypeEditor';
|
|
12
|
+
export type { AssetTypeEditorProps } from './AssetTypeEditor';
|
|
13
|
+
export { AssetTypeFormEditor } from './AssetTypeFormEditor';
|
|
14
|
+
export { AssetFieldArray } from './AssetFieldArray';
|
|
15
|
+
export { SubLocationsEditor } from './SubLocationsEditor';
|
|
16
|
+
export type { AssetTypeDef, KeyedSubLocations, TestField } from './types';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side mirror of the autocore-ams `AssetTypeConfig` (see
|
|
3
|
+
* autocore-ams/src/config.rs). Kept loose so unknown server-side
|
|
4
|
+
* extensions don't trip the editor at parse time. Reuses the shared
|
|
5
|
+
* `TestField` shape from the TIS editor — the server reuses the same
|
|
6
|
+
* Rust `TestField` for both test methods and asset/calibration fields.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { TestField } from '../tis-editor/types';
|
|
10
|
+
|
|
11
|
+
export type { TestField };
|
|
12
|
+
|
|
13
|
+
/** One custom asset type. `sub_locations` is deliberately untyped: two
|
|
14
|
+
* shapes exist (keyed-fields multi-axis, discriminated by a `keys` array;
|
|
15
|
+
* and positional surface-lanes). The form edits the keyed shape; the
|
|
16
|
+
* positional shape is left to the JSON tab. */
|
|
17
|
+
export interface AssetTypeDef {
|
|
18
|
+
extends?: string;
|
|
19
|
+
id_prefix?: string;
|
|
20
|
+
label?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
fields?: TestField[];
|
|
23
|
+
calibration_fields?: TestField[];
|
|
24
|
+
sub_locations?: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The keyed-fields (multi-axis) `sub_locations` shape. */
|
|
28
|
+
export interface KeyedSubLocations {
|
|
29
|
+
label?: string;
|
|
30
|
+
key_label?: string;
|
|
31
|
+
keys: string[];
|
|
32
|
+
fields?: TestField[];
|
|
33
|
+
calibration_fields?: TestField[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** True when a `sub_locations` value is the keyed-fields (multi-axis)
|
|
37
|
+
* shape the form can edit — discriminated by a `keys` array. */
|
|
38
|
+
export function isKeyedSubLocations(v: unknown): v is KeyedSubLocations {
|
|
39
|
+
return !!v && typeof v === 'object' && Array.isArray((v as any).keys);
|
|
40
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Action bar: New / Duplicate / Delete / Apply / Save / Revert.
|
|
8
8
|
*
|
|
9
9
|
* "Apply" pushes the local Monaco buffer to the server-side stage
|
|
10
|
-
* (`tis.put_method`). "Save" persists the entire stage to
|
|
10
|
+
* (`tis.put_method`). "Save" persists the entire stage to test_methods.json
|
|
11
11
|
* (`tis.save_config`). "Revert" drops the stage (`tis.discard_config_changes`).
|
|
12
12
|
*
|
|
13
13
|
* Save is disabled (with a tooltip) when active tests are open against
|
|
@@ -79,9 +79,10 @@ export const SaveDiffDialog: React.FC<SaveDiffDialogProps> = ({
|
|
|
79
79
|
{diff && (
|
|
80
80
|
<div>
|
|
81
81
|
<p>
|
|
82
|
-
About to
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
About to save the test methods to{' '}
|
|
83
|
+
<code>test_methods.json</code>. A backup of the
|
|
84
|
+
previous file will be written to{' '}
|
|
85
|
+
<code>test_methods.json.bak</code>.
|
|
85
86
|
</p>
|
|
86
87
|
{diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0 && (
|
|
87
88
|
<p><em>No changes detected.</em></p>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAmsSchemaConfig — load and edit the AMS custom `asset_types` catalog.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around the `ams.show_schema_config` / `ams.put_asset_type` /
|
|
5
|
+
* `ams.remove_asset_type` / `ams.save_schema_config` /
|
|
6
|
+
* `ams.discard_schema_changes` IPC surface. Mirrors the server's
|
|
7
|
+
* staged-then-saved model (and the TIS `useTisConfig` hook exactly): edits
|
|
8
|
+
* land in the AMS module's in-memory buffer (dirty = true) until `save()`
|
|
9
|
+
* persists them to the `asset_management.json` sidecar next to project.json.
|
|
10
|
+
*
|
|
11
|
+
* Only the project's CUSTOM asset types are editable here. The three
|
|
12
|
+
* built-in types (load_cell/linear_encoder/spring) are code-defined and
|
|
13
|
+
* surfaced read-only via `ams.list_schemas` (see `useAmsAssetTypes`).
|
|
14
|
+
*
|
|
15
|
+
* Pass an `invoker` override to mock the IPC layer (playground / tests).
|
|
16
|
+
* When omitted, the hook reads `EventEmitterContext`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
20
|
+
import { EventEmitterContext } from '../core/EventEmitterContext';
|
|
21
|
+
import { MessageType } from '../hub/CommandMessage';
|
|
22
|
+
import type { TisIpcInvoker } from './useTisConfig';
|
|
23
|
+
import type { AssetTypeDef } from '../components/ams-editor/types';
|
|
24
|
+
|
|
25
|
+
export interface AmsSchemaConfig {
|
|
26
|
+
assetTypes: Record<string, AssetTypeDef>;
|
|
27
|
+
dirty: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface UseAmsSchemaConfigResult {
|
|
31
|
+
config: AmsSchemaConfig | null;
|
|
32
|
+
loading: boolean;
|
|
33
|
+
/** Last server-reported error from a mutation. Cleared on next success. */
|
|
34
|
+
error: string | null;
|
|
35
|
+
refresh: () => Promise<void>;
|
|
36
|
+
putType: (typeId: string, def: AssetTypeDef) => Promise<void>;
|
|
37
|
+
removeType: (typeId: string) => Promise<void>;
|
|
38
|
+
save: () => Promise<void>;
|
|
39
|
+
revert: () => Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface UseAmsSchemaConfigOptions {
|
|
43
|
+
invoker?: TisIpcInvoker;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useAmsSchemaConfig(
|
|
47
|
+
options?: UseAmsSchemaConfigOptions,
|
|
48
|
+
): UseAmsSchemaConfigResult {
|
|
49
|
+
const ctx = useContext(EventEmitterContext);
|
|
50
|
+
|
|
51
|
+
const invoker: TisIpcInvoker = options?.invoker
|
|
52
|
+
?? (async (topic, payload) => {
|
|
53
|
+
return await ctx.invoke(topic as any, MessageType.Request, payload as any);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Hold the invoker in a ref so the memoized callbacks keep a STABLE
|
|
57
|
+
// identity even when the caller passes a freshly-built invoker each
|
|
58
|
+
// render — same refetch-loop hazard the TIS hook documents (an unstable
|
|
59
|
+
// `refresh` re-ran the mount effect, wiping in-progress edits).
|
|
60
|
+
const invokerRef = useRef(invoker);
|
|
61
|
+
invokerRef.current = invoker;
|
|
62
|
+
|
|
63
|
+
const [config, setConfig] = useState<AmsSchemaConfig | null>(null);
|
|
64
|
+
const [loading, setLoading] = useState<boolean>(true);
|
|
65
|
+
const [error, setError] = useState<string | null>(null);
|
|
66
|
+
|
|
67
|
+
const refresh = useCallback(async () => {
|
|
68
|
+
setLoading(true);
|
|
69
|
+
try {
|
|
70
|
+
const resp = await invokerRef.current('ams.show_schema_config', {});
|
|
71
|
+
if (!resp.success) {
|
|
72
|
+
setError(resp.error_message ?? 'ams.show_schema_config failed');
|
|
73
|
+
setConfig(null);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const d = resp.data ?? {};
|
|
77
|
+
setConfig({
|
|
78
|
+
assetTypes: (d.asset_types ?? {}) as Record<string, AssetTypeDef>,
|
|
79
|
+
dirty: !!d.dirty,
|
|
80
|
+
});
|
|
81
|
+
setError(null);
|
|
82
|
+
} catch (e: any) {
|
|
83
|
+
setError(String(e?.message ?? e));
|
|
84
|
+
setConfig(null);
|
|
85
|
+
} finally {
|
|
86
|
+
setLoading(false);
|
|
87
|
+
}
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
void refresh();
|
|
92
|
+
}, [refresh]);
|
|
93
|
+
|
|
94
|
+
const putType = useCallback(async (typeId: string, def: AssetTypeDef) => {
|
|
95
|
+
const resp = await invokerRef.current('ams.put_asset_type', {
|
|
96
|
+
type_id: typeId,
|
|
97
|
+
definition: def,
|
|
98
|
+
});
|
|
99
|
+
if (!resp.success) {
|
|
100
|
+
const m = resp.error_message ?? 'ams.put_asset_type failed';
|
|
101
|
+
setError(m);
|
|
102
|
+
throw new Error(m);
|
|
103
|
+
}
|
|
104
|
+
setError(null);
|
|
105
|
+
await refresh();
|
|
106
|
+
}, [refresh]);
|
|
107
|
+
|
|
108
|
+
const removeType = useCallback(async (typeId: string) => {
|
|
109
|
+
const resp = await invokerRef.current('ams.remove_asset_type', { type_id: typeId });
|
|
110
|
+
if (!resp.success) {
|
|
111
|
+
const m = resp.error_message ?? 'ams.remove_asset_type failed';
|
|
112
|
+
setError(m);
|
|
113
|
+
throw new Error(m);
|
|
114
|
+
}
|
|
115
|
+
setError(null);
|
|
116
|
+
await refresh();
|
|
117
|
+
}, [refresh]);
|
|
118
|
+
|
|
119
|
+
const save = useCallback(async () => {
|
|
120
|
+
const resp = await invokerRef.current('ams.save_schema_config', {});
|
|
121
|
+
if (!resp.success) {
|
|
122
|
+
const m = resp.error_message ?? 'ams.save_schema_config failed';
|
|
123
|
+
setError(m);
|
|
124
|
+
throw new Error(m);
|
|
125
|
+
}
|
|
126
|
+
setError(null);
|
|
127
|
+
await refresh();
|
|
128
|
+
}, [refresh]);
|
|
129
|
+
|
|
130
|
+
const revert = useCallback(async () => {
|
|
131
|
+
const resp = await invokerRef.current('ams.discard_schema_changes', {});
|
|
132
|
+
if (!resp.success) {
|
|
133
|
+
const m = resp.error_message ?? 'ams.discard_schema_changes failed';
|
|
134
|
+
setError(m);
|
|
135
|
+
throw new Error(m);
|
|
136
|
+
}
|
|
137
|
+
setError(null);
|
|
138
|
+
await refresh();
|
|
139
|
+
}, [refresh]);
|
|
140
|
+
|
|
141
|
+
return { config, loading, error, refresh, putType, removeType, save, revert };
|
|
142
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* `tis.remove_method` / `tis.save_config` / `tis.discard_config_changes`
|
|
6
6
|
* IPC surface. Mirrors the server's staged-then-saved model: edits land in
|
|
7
7
|
* the server's in-memory copy (dirty = true) until `save()` persists them
|
|
8
|
-
* to project.json.
|
|
8
|
+
* to the `test_methods.json` sidecar next to project.json.
|
|
9
9
|
*
|
|
10
10
|
* Today the server hosts exactly one project (the one configured at
|
|
11
11
|
* startup), so `projectId` is informational; the hook still threads it
|
package/dist/assets/JogYNeg.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"JogYNeg.d.ts","sourceRoot":"","sources":["../../src/assets/JogYNeg.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC,eAAO,MAAM,UAAU,GAAI,OAAO,QAAQ,CAAC,aAAa,CAAC,4CAkBxD,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
package/dist/assets/JogYNeg.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";export const SvgJogYPos=o=>_jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...o,children:[_jsx("path",{d:"M21 3 21 11 13 3Z",fill:"currentColor"}),_jsx("path",{d:"M4 12l3 4 3-4M7 16v5"})]});export default SvgJogYPos;
|
package/dist/assets/JogYPos.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"JogYPos.d.ts","sourceRoot":"","sources":["../../src/assets/JogYPos.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC,eAAO,MAAM,UAAU,GAAI,OAAO,QAAQ,CAAC,aAAa,CAAC,4CAkBxD,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
package/dist/assets/JogYPos.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";export const SvgJogYNeg=o=>_jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",...o,children:[_jsx("path",{d:"M20 3l-3 4-3-4M17 7v5"}),_jsx("path",{d:"M3 21 3 13 11 21Z",fill:"currentColor"})]});export default SvgJogYNeg;
|