@adcops/autocore-react 3.3.61 → 3.3.64

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.
Files changed (45) hide show
  1. package/dist/components/ams/AmsProvider.d.ts +45 -0
  2. package/dist/components/ams/AmsProvider.d.ts.map +1 -0
  3. package/dist/components/ams/AmsProvider.js +1 -0
  4. package/dist/components/ams/AssetDetailView.d.ts +3 -0
  5. package/dist/components/ams/AssetDetailView.d.ts.map +1 -0
  6. package/dist/components/ams/AssetDetailView.js +1 -0
  7. package/dist/components/ams/AssetRegistryTable.d.ts +3 -0
  8. package/dist/components/ams/AssetRegistryTable.d.ts.map +1 -0
  9. package/dist/components/ams/AssetRegistryTable.js +1 -0
  10. package/dist/components/ams/CalibrationEntryDialog.d.ts +10 -0
  11. package/dist/components/ams/CalibrationEntryDialog.d.ts.map +1 -0
  12. package/dist/components/ams/CalibrationEntryDialog.js +1 -0
  13. package/dist/components/ams/SubLocationPicker.d.ts +3 -0
  14. package/dist/components/ams/SubLocationPicker.d.ts.map +1 -0
  15. package/dist/components/ams/SubLocationPicker.js +1 -0
  16. package/dist/components/ams/index.d.ts +6 -0
  17. package/dist/components/ams/index.d.ts.map +1 -0
  18. package/dist/components/ams/index.js +1 -0
  19. package/dist/components/index.d.ts +7 -0
  20. package/dist/components/index.d.ts.map +1 -1
  21. package/dist/components/index.js +1 -1
  22. package/dist/components/tis/TestDataView.d.ts +9 -1
  23. package/dist/components/tis/TestDataView.d.ts.map +1 -1
  24. package/dist/components/tis/TestDataView.js +1 -1
  25. package/dist/components/tis/TisProvider.d.ts +17 -0
  26. package/dist/components/tis/TisProvider.d.ts.map +1 -1
  27. package/dist/components/tis/TisProvider.js +1 -1
  28. package/dist/core/AutoCoreTagContext.d.ts +16 -0
  29. package/dist/core/AutoCoreTagContext.d.ts.map +1 -1
  30. package/dist/core/AutoCoreTagContext.js +1 -1
  31. package/dist/themes/adc-dark/blue/theme.css +77 -37
  32. package/dist/themes/adc-dark/blue/theme.css.map +1 -1
  33. package/package.json +1 -1
  34. package/src/components/ams/AmsProvider.tsx +219 -0
  35. package/src/components/ams/AssetDetailView.tsx +101 -0
  36. package/src/components/ams/AssetRegistryTable.tsx +171 -0
  37. package/src/components/ams/CalibrationEntryDialog.tsx +197 -0
  38. package/src/components/ams/SubLocationPicker.tsx +146 -0
  39. package/src/components/ams/index.ts +12 -0
  40. package/src/components/index.ts +27 -0
  41. package/src/components/tis/TestDataView.tsx +321 -28
  42. package/src/components/tis/TisProvider.tsx +44 -0
  43. package/src/core/AutoCoreTagContext.tsx +114 -16
  44. package/src/themes/adc-dark/_extensions.scss +23 -0
  45. package/src/themes/adc-dark/blue/adc_theme.scss +56 -10
@@ -341,6 +341,33 @@ export const TisProvider: React.FC<TisProviderProps> = ({ children, defaultMetho
341
341
  }));
342
342
  }, []);
343
343
 
344
+ // -----------------------------------------------------------------
345
+ // Auto-clear pins on a new active run.
346
+ //
347
+ // The server broadcasts `tis.active_run_id` immediately at
348
+ // `start_test` (before any cycle is added). When that scalar
349
+ // transitions to a new, non-empty value, any pins the operator
350
+ // set — typically by clicking a row in <ResultHistoryTable> —
351
+ // would otherwise keep <TestDataView> stuck on the old run. We
352
+ // drop all four pins here so `selection` falls through to the
353
+ // active broadcast scalars and the Data view follows the live
354
+ // test automatically.
355
+ //
356
+ // Pins set *after* the new run begins (e.g., the operator opens
357
+ // History mid-test and clicks a historical row) are honoured —
358
+ // they're set after this effect runs and they win until the
359
+ // next Start. End-of-test (active flips false) doesn't clear
360
+ // either: active_run_id keeps the just-finished run's value, so
361
+ // the operator can still read its results.
362
+ const prevActiveRunRef = useRef('');
363
+ useEffect(() => {
364
+ const newRunId = state.activeRunId;
365
+ if (newRunId && newRunId !== prevActiveRunRef.current) {
366
+ prevActiveRunRef.current = newRunId;
367
+ setPins({ projectId: null, methodId: null, sampleId: null, runId: null });
368
+ }
369
+ }, [state.activeRunId]);
370
+
344
371
  // -----------------------------------------------------------------
345
372
  // Fetchers
346
373
  // -----------------------------------------------------------------
@@ -503,6 +530,23 @@ export const TisProvider: React.FC<TisProviderProps> = ({ children, defaultMetho
503
530
  export const useTis = () => useContext(TisContext);
504
531
  export const useTisSchemas = () => useContext(TisContext).schemas;
505
532
  export const useTisState = () => useContext(TisContext).state;
533
+ /**
534
+ * Tuple of `[selection, setSelection]`.
535
+ *
536
+ * `selection` has four fields — `projectId`, `methodId`, `sampleId`,
537
+ * `runId`. Each one falls through to the matching `tis.active_*`
538
+ * broadcast scalar when no pin is set. Pins are set by passing a
539
+ * concrete value to `setSelection`; pass `null` to clear a pin and
540
+ * resume auto-following.
541
+ *
542
+ * **Auto-clear on Start:** when a new test becomes active (the
543
+ * `tis.active_run_id` scalar transitions to a new non-empty value),
544
+ * the provider drops all four pins automatically. This is what makes
545
+ * `<TestDataView>` follow the live run after an operator has been
546
+ * browsing history — they don't have to click anything to "switch
547
+ * to the live test." Pins set *after* the new run begins (mid-test
548
+ * History click) are honoured until the next Start.
549
+ */
506
550
  export const useTisSelection = () => {
507
551
  const { selection, setSelection } = useContext(TisContext);
508
552
  return [selection, setSelection] as const;
@@ -151,13 +151,29 @@ const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
151
151
  * @param props.tags - List of tag configurations (tagName, fqdn, etc.)
152
152
  * @param props.scales - Map of scale definitions (name, factor, label)
153
153
  * @param props.eagerRead - If true, automatically fetches initial values on mount
154
+ * @param props.flushIntervalMs - Maximum rate at which broadcast updates trigger
155
+ * React state changes. Broadcasts arriving faster than this are coalesced
156
+ * per-tag (latest-value-wins) and flushed in a single batched render at most
157
+ * once per interval.
158
+ *
159
+ * Default `33` (≈30 FPS) keeps Firefox smooth even when the controller scan
160
+ * rate is 2–4 kHz. The wire still carries every change-of-value broadcast —
161
+ * this only paces the React reconciliation cost, which dominates render
162
+ * budget. To also reduce wire bandwidth (relevant on poor links / Tailscale
163
+ * over slow WiFi), see the connection-level rate cap (server-side).
164
+ *
165
+ * Set to `0` to disable throttling and apply every broadcast immediately
166
+ * (matches pre-throttle behaviour). Initial-load `eagerRead` values are
167
+ * never throttled — they bypass this path entirely so the page renders
168
+ * fully populated on mount.
154
169
  */
155
170
  export const AutoCoreTagProvider: React.FC<{
156
171
  children: ReactNode;
157
172
  tags: readonly TagConfig[];
158
173
  scales?: Record<string, ScaleConfig>;
159
174
  eagerRead?: boolean;
160
- }> = ({ children, tags, scales, eagerRead = true }) => {
175
+ flushIntervalMs?: number;
176
+ }> = ({ children, tags, scales, eagerRead = true, flushIntervalMs = 33 }) => {
161
177
  const startedRef = useRef(false);
162
178
 
163
179
  // PERFORMANCE: Memoize default scales to ensure reference stability.
@@ -281,25 +297,107 @@ export const AutoCoreTagProvider: React.FC<{
281
297
  });
282
298
  }, [tags, toDisplay]);
283
299
 
300
+ // -----------------------------------------------------------------
301
+ // Throttle: broadcast updates are coalesced per tag and flushed in
302
+ // a single batched setState at most once per `flushIntervalMs`. This
303
+ // exists because every broadcast that lands in `setRawValues` triggers
304
+ // a context-wide React reconciliation. With high-rate scan loops
305
+ // (3830 runs at 4 kHz) and ~10 fast-changing tags, that's tens of
306
+ // thousands of reconciliations per second — Firefox in particular
307
+ // stalls visibly. The batched flush keeps reconciliation at the
308
+ // `flushIntervalMs` rate (~30 FPS by default), regardless of how
309
+ // many broadcasts the wire delivers in between.
310
+ //
311
+ // Latest-value-wins: each tag keeps only its most recent raw + display
312
+ // value in the pending maps; intermediate values within a window are
313
+ // dropped. This is the right semantic for status indicators — the
314
+ // operator wants "where is X right now," not the path it took to get
315
+ // there. Event broadcasts (e.g., tis.cycle_added) don't go through
316
+ // this path; they're consumed by their own subscribers.
317
+ //
318
+ // The eagerRead path on mount writes directly to setRawValues (see
319
+ // `eagerPullNonADS` etc. above) and bypasses this throttle, so the
320
+ // initial render is fully populated.
321
+ // -----------------------------------------------------------------
322
+ const pendingRawRef = useRef<Map<string, unknown>>(new Map());
323
+ const pendingDisplayRef = useRef<Map<string, unknown>>(new Map());
324
+ const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
325
+
326
+ const flushPending = useCallback(() => {
327
+ flushTimerRef.current = null;
328
+ const r = pendingRawRef.current;
329
+ const d = pendingDisplayRef.current;
330
+ if (r.size === 0 && d.size === 0) return;
331
+ pendingRawRef.current = new Map();
332
+ pendingDisplayRef.current = new Map();
333
+ setRawValues(prev => {
334
+ let next = prev;
335
+ r.forEach((v, k) => {
336
+ if (next[k] !== v) {
337
+ if (next === prev) next = { ...prev };
338
+ next[k] = v;
339
+ }
340
+ });
341
+ return next;
342
+ });
343
+ setValues(prev => {
344
+ let next = prev;
345
+ d.forEach((v, k) => {
346
+ if (next[k] !== v) {
347
+ if (next === prev) next = { ...prev };
348
+ next[k] = v;
349
+ }
350
+ });
351
+ return next;
352
+ });
353
+ }, []);
354
+
284
355
  /**
285
- * Handles incoming value updates from the server.
286
- *
287
- * 1. Updates `rawValues` (Source of Truth).
288
- * 2. Computes and updates `values` (Display).
289
- * 3. Uses functional state updates to ensure atomicity.
290
- * 4. Keys by `tagName` for consistent access by hooks.
356
+ * Handles incoming broadcast value updates from the server.
357
+ *
358
+ * 1. Stages the raw and display values in per-tag pending maps
359
+ * (latest-value-wins; intermediate values inside a window are
360
+ * dropped).
361
+ * 2. Schedules (or piggybacks on) a flush timer that batches every
362
+ * accumulated tag into a single setRawValues + setValues pass at
363
+ * most once per `flushIntervalMs`.
364
+ * 3. When `flushIntervalMs === 0`, flushes synchronously — same as
365
+ * the pre-throttle path.
291
366
  */
292
367
  const handleTagUpdate = useCallback((tag: TagConfig, raw: unknown) => {
293
- // Store the raw controller value (source of truth for scaling)
294
- setRawValues(prev =>
295
- prev[tag.tagName] === raw ? prev : { ...prev, [tag.tagName]: raw }
296
- );
297
- // Compute and store the display value (with scaling/codecs applied)
298
368
  const display = toDisplay(tag, raw);
299
- setValues(prev =>
300
- prev[tag.tagName] === display ? prev : { ...prev, [tag.tagName]: display }
301
- );
302
- }, [toDisplay]);
369
+
370
+ if (flushIntervalMs <= 0) {
371
+ // Throttle disabled — preserve the original synchronous shape.
372
+ setRawValues(prev =>
373
+ prev[tag.tagName] === raw ? prev : { ...prev, [tag.tagName]: raw }
374
+ );
375
+ setValues(prev =>
376
+ prev[tag.tagName] === display ? prev : { ...prev, [tag.tagName]: display }
377
+ );
378
+ return;
379
+ }
380
+
381
+ pendingRawRef.current.set(tag.tagName, raw);
382
+ pendingDisplayRef.current.set(tag.tagName, display);
383
+ if (flushTimerRef.current === null) {
384
+ flushTimerRef.current = setTimeout(flushPending, flushIntervalMs);
385
+ }
386
+ }, [toDisplay, flushIntervalMs, flushPending]);
387
+
388
+ // Drain any pending updates on unmount so we don't leak the timer
389
+ // reference and so a late-arriving setState doesn't fire on a
390
+ // dead component during dev-mode StrictMode double-mounts.
391
+ useEffect(() => {
392
+ return () => {
393
+ if (flushTimerRef.current !== null) {
394
+ clearTimeout(flushTimerRef.current);
395
+ flushTimerRef.current = null;
396
+ }
397
+ pendingRawRef.current = new Map();
398
+ pendingDisplayRef.current = new Map();
399
+ };
400
+ }, []);
303
401
 
304
402
  /**
305
403
  * Eagerly fetches initial values for non-ADS domains (e.g., MODBUS).
@@ -175,6 +175,29 @@
175
175
  gap: 2px;
176
176
  }
177
177
 
178
+ /* ---- ac-toolbar-tool-list: stacked text buttons in an OverlayPanel ---- */
179
+ /*
180
+ * Spacing between buttons uses `gap` on the flex parent rather than
181
+ * `margin` on the buttons. PrimeReact's `.p-button` rules carry their
182
+ * own margin reset and tie our `.ac-toolbar-tool-item` on specificity;
183
+ * because PrimeReact's CSS loads later, its margin wins. Flex `gap`
184
+ * has no PrimeReact equivalent to fight.
185
+ */
186
+ .ac-toolbar-tool-list {
187
+ display: flex;
188
+ flex-direction: column;
189
+ min-width: 16rem;
190
+ padding: 2mm;
191
+ gap: 2mm;
192
+ }
193
+
194
+ /* `gap: 0.75rem` here is the *intra-button* spacing — between the
195
+ * leading icon and the text label — not between buttons. */
196
+ .ac-toolbar-tool-item {
197
+ justify-content: flex-start;
198
+ gap: 0.75rem;
199
+ }
200
+
178
201
  /* ---- ac-form: content page form layout ---- */
179
202
 
180
203
  .ac-form {
@@ -14,8 +14,15 @@
14
14
  // General Padding & Sizing
15
15
  $panelContentPadding: 0.5rem 0.75rem !default; // Affects panels, cards, dialogs, etc.
16
16
  $inputPadding: 0.5rem 0.75rem !default; // Affects all input fields
17
- $buttonPadding: 0.5rem 0.75rem !default; // Affects buttons
18
- $buttonIconOnlyPadding: 0.5rem 0 !default;
17
+ // Buttons run one size class larger than the original lara baseline:
18
+ // the previous "medium" was too small for our 1080p kiosk targets and
19
+ // the workstation operators leaning on touchscreens. Each of small /
20
+ // medium / large was bumped up one tier (+0.125rem on font and on each
21
+ // padding axis), so the new "small" is the size the old "medium" was.
22
+ // `.p-button-sm` and `.p-button-lg` overrides below carry the matching
23
+ // numbers for the other two tiers.
24
+ $buttonPadding: 0.625rem 0.875rem !default; // Affects buttons (medium/default)
25
+ $buttonIconOnlyPadding: 0.625rem 0 !default;
19
26
  $inlineSpacing: 0.25rem !default; // Space between items like icons and text
20
27
  $borderRadius: 3px !default; // Sharper corners for a more "desktop" feel
21
28
 
@@ -24,8 +31,16 @@ $tableHeaderCellPadding: 0.5rem 0.75rem !default;
24
31
  $tableBodyCellPadding: 0.5rem 0.75rem !default;
25
32
 
26
33
  // Menu Sizing
27
- $menuitemPadding: 0.5rem 0.75rem !default;
28
- $horizontalMenuRootMenuitemPadding: 0.5rem 0.75rem !default;
34
+ //
35
+ // Bumped past the medium-button padding because menu items are
36
+ // usually selected in a single tap on a touchscreen with imprecise
37
+ // aim — slightly more vertical breathing room than a button gives a
38
+ // touch target close to the 44 px iOS-HIG / WCAG 2.5.5 minimum at
39
+ // our 1.125rem (~18 px) menu-item font-size: 18 + 2×12 = 42 px.
40
+ // `.p-menuitem-link` font-size and `.p-menuitem-icon` size below
41
+ // finish the picture (icons scale up alongside the text).
42
+ $menuitemPadding: 0.75rem 1rem !default;
43
+ $horizontalMenuRootMenuitemPadding: 0.75rem 1rem !default;
29
44
 
30
45
 
31
46
  // 2. Import the original lara-dark-blue theme
@@ -49,7 +64,7 @@ textarea {
49
64
 
50
65
  .p-button {
51
66
  padding: $buttonPadding;
52
- font-size: 1rem;
67
+ font-size: 1.125rem;
53
68
  font-family: inherit;
54
69
  border-radius: $borderRadius;
55
70
  cursor: pointer;
@@ -61,7 +76,7 @@ textarea {
61
76
 
62
77
  .p-button.p-button-icon-only {
63
78
  padding: $buttonIconOnlyPadding;
64
- width: 2.5rem;
79
+ width: 2.75rem;
65
80
  }
66
81
 
67
82
  // PrimeReact always renders a `<span class="p-button-label">&nbsp;</span>`
@@ -72,14 +87,16 @@ textarea {
72
87
  display: none;
73
88
  }
74
89
 
90
+ // `.p-button-sm` lands at what used to be the medium baseline.
75
91
  .p-button.p-button-sm {
76
- font-size: 0.875rem;
77
- padding: 0.375rem 0.625rem;
92
+ font-size: 1rem;
93
+ padding: 0.5rem 0.75rem;
78
94
  }
79
95
 
96
+ // `.p-button-lg` extends the same +0.125rem step past the new medium.
80
97
  .p-button.p-button-lg {
81
- font-size: 1.125rem;
82
- padding: 0.625rem 0.875rem;
98
+ font-size: 1.25rem;
99
+ padding: 0.75rem 1rem;
83
100
  }
84
101
 
85
102
  .p-inputtext {
@@ -133,6 +150,35 @@ textarea {
133
150
  border-bottom-right-radius: $borderRadius;
134
151
  }
135
152
 
153
+ // Menu items (TieredMenu / Menu / SplitButton dropdown / ContextMenu).
154
+ // Match the new medium-button font-size so SplitButton dropdowns don't
155
+ // look noticeably smaller than the button that opened them, and bump
156
+ // the leading icon a touch larger than the text so radio/check glyphs
157
+ // read clearly on a touchscreen.
158
+ .p-menuitem-link {
159
+ font-size: 1.125rem;
160
+ }
161
+ .p-menuitem-link .p-menuitem-icon {
162
+ font-size: 1.25rem;
163
+ width: 1.25rem;
164
+ height: 1.25rem;
165
+ }
166
+
167
+ // Input-list popup items (Dropdown / AutoComplete / MultiSelect /
168
+ // CascadeSelect / TreeSelect / picklist filters). Vertical padding
169
+ // for these is already at the touch-friendly lara default
170
+ // (`$inputListItemPadding: 0.75rem 1.25rem`); we just bump the
171
+ // font-size to the medium-button scale so options read at the same
172
+ // visual weight as the button / menu items around them. Net target
173
+ // height ≈ 18 px font + 24 px padding = 42 px, same as the menus.
174
+ .p-dropdown-item,
175
+ .p-autocomplete-item,
176
+ .p-multiselect-item,
177
+ .p-cascadeselect-item,
178
+ .p-treeselect-item {
179
+ font-size: 1.125rem;
180
+ }
181
+
136
182
  // TabView: ensure tabs are visibly tab-shaped with padding and spacing,
137
183
  // regardless of @layer cascade resolution.
138
184
  .p-tabview .p-tabview-nav {