@adcops/autocore-react 3.1.1 → 3.3.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/additional-docs/react_performance_notes.md +94 -0
- package/dist/components/AutoCoreDevPanel.d.ts.map +1 -1
- package/dist/components/AutoCoreDevPanel.js +1 -1
- package/dist/components/FileList.d.ts.map +1 -1
- package/dist/components/FileList.js +1 -1
- package/dist/components/FileSelect.d.ts.map +1 -1
- package/dist/components/FileSelect.js +1 -1
- package/dist/core/AutoCoreTagContext.d.ts +59 -185
- package/dist/core/AutoCoreTagContext.d.ts.map +1 -1
- package/dist/core/AutoCoreTagContext.js +1 -1
- package/dist/core/AutoCoreTagTypes.d.ts +127 -6
- package/dist/core/AutoCoreTagTypes.d.ts.map +1 -1
- package/dist/core/CoreStreamTypes.d.ts +345 -0
- package/dist/core/CoreStreamTypes.d.ts.map +1 -0
- package/dist/core/CoreStreamTypes.js +1 -0
- package/dist/core/EventEmitterContext.d.ts +91 -473
- package/dist/core/EventEmitterContext.d.ts.map +1 -1
- package/dist/core/EventEmitterContext.js +1 -1
- package/dist/hooks/adsHooks.d.ts.map +1 -1
- package/dist/hooks/adsHooks.js +1 -1
- package/dist/hooks/commandHooks.d.ts +3 -3
- package/dist/hooks/commandHooks.d.ts.map +1 -1
- package/dist/hooks/commandHooks.js +1 -1
- package/dist/hooks/useAutoCoreTag.js +1 -1
- package/dist/hub/CommandMessage.d.ts +18 -9
- package/dist/hub/CommandMessage.d.ts.map +1 -1
- package/dist/hub/CommandMessage.js +1 -1
- package/dist/hub/DebugPanel.d.ts +31 -0
- package/dist/hub/DebugPanel.d.ts.map +1 -0
- package/dist/hub/DebugPanel.js +1 -0
- package/dist/hub/HubBase.d.ts +83 -129
- package/dist/hub/HubBase.d.ts.map +1 -1
- package/dist/hub/HubBase.js +1 -1
- package/dist/hub/HubSimulate.d.ts +41 -8
- package/dist/hub/HubSimulate.d.ts.map +1 -1
- package/dist/hub/HubSimulate.js +1 -1
- package/dist/hub/HubTauri.d.ts +24 -60
- package/dist/hub/HubTauri.d.ts.map +1 -1
- package/dist/hub/HubTauri.js +1 -1
- package/dist/hub/HubWebSocket.d.ts +33 -17
- package/dist/hub/HubWebSocket.d.ts.map +1 -1
- package/dist/hub/HubWebSocket.js +1 -1
- package/dist/hub/debug.d.ts +23 -0
- package/dist/hub/debug.d.ts.map +1 -0
- package/dist/hub/debug.js +1 -0
- package/dist/hub/index.d.ts +19 -4
- package/dist/hub/index.d.ts.map +1 -1
- package/dist/hub/index.js +1 -1
- package/package.json +8 -4
- package/src/components/AutoCoreDevPanel.tsx +14 -11
- package/src/components/FileList.tsx +5 -4
- package/src/components/FileSelect.tsx +2 -1
- package/src/core/ActionMode.ts +1 -1
- package/src/core/AutoCoreTagContext.tsx +247 -330
- package/src/core/AutoCoreTagTypes.ts +236 -104
- package/src/core/CoreStreamTypes.ts +512 -0
- package/src/core/EventEmitterContext.tsx +182 -520
- package/src/core/IndicatorButtonState.ts +1 -1
- package/src/core/hoc.tsx +1 -1
- package/src/hooks/adsHooks.tsx +21 -22
- package/src/hooks/commandHooks.tsx +23 -19
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useAutoCoreTag.ts +2 -2
- package/src/hooks/useScaledValue.tsx +1 -1
- package/src/hub/CommandMessage.ts +71 -19
- package/src/hub/DebugPanel.ts +280 -0
- package/src/hub/HubBase.ts +147 -223
- package/src/hub/HubSimulate.ts +93 -24
- package/src/hub/HubTauri.ts +87 -96
- package/src/hub/HubWebSocket.ts +118 -140
- package/src/hub/debug.ts +211 -0
- package/src/hub/index.ts +49 -39
- package/docs/.nojekyll +0 -1
- package/docs/assets/hierarchy.js +0 -1
- package/docs/assets/highlight.css +0 -134
- package/docs/assets/icons.js +0 -18
- package/docs/assets/icons.svg +0 -1
- package/docs/assets/main.js +0 -60
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1633
- package/docs/classes/components_CodeEditor.CodeEditor.html +0 -135
- package/docs/classes/components_Indicator.Indicator.html +0 -122
- package/docs/classes/components_IndicatorRect.IndicatorRect.html +0 -121
- package/docs/classes/components_JogPanel.JogPanel.html +0 -136
- package/docs/classes/components_Lamp.Lamp.html +0 -122
- package/docs/classes/components_OskDialog.OskDialog.html +0 -125
- package/docs/classes/components_TextInput.TextInput.html +0 -125
- package/docs/classes/components_ValueDisplay.ValueDisplay.html +0 -148
- package/docs/classes/components_ValueIndicator.ValueIndicator.html +0 -126
- package/docs/classes/core_ValueSimulator.ValueSimulator.html +0 -51
- package/docs/classes/hub_HubBase.HubBase.html +0 -106
- package/docs/classes/hub_HubSimulate.HubSimulate.html +0 -75
- package/docs/classes/hub_HubTauri.HubTauri.html +0 -93
- package/docs/classes/hub_HubWebSocket.HubWebSocket.html +0 -112
- package/docs/documents/core_AutoCoreTagContext.AutoCoreTagContext.html +0 -148
- package/docs/enums/components_JogPanel.JogDistanceAction.html +0 -5
- package/docs/enums/components_JogPanel.JogPanelAction.html +0 -18
- package/docs/enums/components_JogPanel.JogSpeedAction.html +0 -5
- package/docs/enums/core_ActionMode.ActionMode.html +0 -6
- package/docs/enums/core_IndicatorColor.IndicatorColor.html +0 -23
- package/docs/functions/assets.BlocklyLogo.html +0 -1
- package/docs/functions/assets.Distance.html +0 -1
- package/docs/functions/assets.JogLong.html +0 -1
- package/docs/functions/assets.JogMedium.html +0 -1
- package/docs/functions/assets.JogShort.html +0 -1
- package/docs/functions/assets.PythonLogo.html +0 -1
- package/docs/functions/assets.Rotation3D.html +0 -1
- package/docs/functions/assets.RotationCcw.html +0 -1
- package/docs/functions/assets.RotationCcwA.html +0 -1
- package/docs/functions/assets.RotationCcwB.html +0 -1
- package/docs/functions/assets.RotationCcwC.html +0 -1
- package/docs/functions/assets.RotationCw.html +0 -1
- package/docs/functions/assets.RotationCwA.html +0 -1
- package/docs/functions/assets.RotationCwB.html +0 -1
- package/docs/functions/assets.RotationCwC.html +0 -1
- package/docs/functions/assets.Run.html +0 -1
- package/docs/functions/assets.Speed.html +0 -1
- package/docs/functions/assets.SpeedFast.html +0 -1
- package/docs/functions/assets.SpeedMedium.html +0 -1
- package/docs/functions/assets.SpeedNone.html +0 -1
- package/docs/functions/assets.SpeedSlow.html +0 -1
- package/docs/functions/assets.Walk.html +0 -1
- package/docs/functions/components_BlocklyEditor.createCustomToolbox.html +0 -6
- package/docs/functions/core_UniqueId.UniqueId.html +0 -9
- package/docs/functions/core_hoc.hocAddSubscription.html +0 -6
- package/docs/functions/hooks_adsHooks.useAdsRegisterSymbols.html +0 -16
- package/docs/functions/hooks_adsHooks.useAdsTapValue.html +0 -8
- package/docs/functions/hooks_adsHooks.useAdsWriteScaledValue.html +0 -18
- package/docs/functions/hooks_adsHooks.useAdsWriteValue.html +0 -9
- package/docs/functions/hooks_commandHooks.useRegisterSymbols.html +0 -16
- package/docs/functions/hooks_commandHooks.useTapValue.html +0 -10
- package/docs/functions/hooks_commandHooks.useWriteScaledValue.html +0 -18
- package/docs/functions/hooks_commandHooks.useWriteValue.html +0 -11
- package/docs/functions/hooks_useAutoCoreTag.ts.makeAutoCoreTagHooks.html +0 -12
- package/docs/functions/hooks_useScaledValue.useScaledValue.html +0 -18
- package/docs/functions/hub.createHub.html +0 -3
- package/docs/hierarchy.html +0 -1
- package/docs/index.html +0 -148
- package/docs/interfaces/components_IndicatorButton.IndicatorButtonProps.html +0 -654
- package/docs/interfaces/components_IndicatorRect.IndicatorRectProps.html +0 -37
- package/docs/interfaces/components_JogPanel.JogPanelButtonDefinition.html +0 -5
- package/docs/interfaces/components_ToggleGroup.ToggleGroupProps.html +0 -644
- package/docs/interfaces/core_AutoCoreTagTypes.BaseContextValue.html +0 -12
- package/docs/interfaces/core_AutoCoreTagTypes.ScaleConfig.html +0 -13
- package/docs/interfaces/core_EventEmitterContext.Action.html +0 -8
- package/docs/interfaces/core_EventEmitterContext.EventEmitterContextType.html +0 -33
- package/docs/interfaces/core_EventEmitterContext.State.html +0 -8
- package/docs/interfaces/core_EventEmitterContext.Subscription.html +0 -6
- package/docs/interfaces/core_IndicatorButtonState.IndicatorButtonState.html +0 -10
- package/docs/interfaces/core_PositionContext.IPositionContext.html +0 -17
- package/docs/interfaces/hub_CommandMessage.CommandMessage.html +0 -6
- package/docs/interfaces/hub_CommandMessage.CommandMessageResult.html +0 -4
- package/docs/modules/assets.html +0 -1
- package/docs/modules/assets_BlocklyLogo.html +0 -1
- package/docs/modules/assets_Distance.html +0 -1
- package/docs/modules/assets_JogLong.html +0 -1
- package/docs/modules/assets_JogMedium.html +0 -1
- package/docs/modules/assets_JogShort.html +0 -1
- package/docs/modules/assets_PythonLogo.html +0 -1
- package/docs/modules/assets_Rotation3D.html +0 -1
- package/docs/modules/assets_RotationCcw.html +0 -1
- package/docs/modules/assets_RotationCcwA.html +0 -1
- package/docs/modules/assets_RotationCcwB.html +0 -1
- package/docs/modules/assets_RotationCcwC.html +0 -1
- package/docs/modules/assets_RotationCw.html +0 -1
- package/docs/modules/assets_RotationCwA.html +0 -1
- package/docs/modules/assets_RotationCwB.html +0 -1
- package/docs/modules/assets_RotationCwC.html +0 -1
- package/docs/modules/assets_Run.html +0 -1
- package/docs/modules/assets_Speed.html +0 -1
- package/docs/modules/assets_SpeedFast.html +0 -1
- package/docs/modules/assets_SpeedMedium.html +0 -1
- package/docs/modules/assets_SpeedNone.html +0 -1
- package/docs/modules/assets_SpeedSlow.html +0 -1
- package/docs/modules/assets_Walk.html +0 -1
- package/docs/modules/components_AutoCoreDevPanel.html +0 -20
- package/docs/modules/components_BlocklyEditor.html +0 -1
- package/docs/modules/components_CodeEditor.html +0 -1
- package/docs/modules/components_FileList.html +0 -1
- package/docs/modules/components_FileSelect.html +0 -1
- package/docs/modules/components_FitText.html +0 -1
- package/docs/modules/components_Indicator.html +0 -1
- package/docs/modules/components_IndicatorButton.html +0 -1
- package/docs/modules/components_IndicatorRect.html +0 -1
- package/docs/modules/components_JogPanel.html +0 -1
- package/docs/modules/components_Lamp.html +0 -1
- package/docs/modules/components_Osk.html +0 -1
- package/docs/modules/components_OskDialog.html +0 -1
- package/docs/modules/components_ProgressBarWithValue.html +0 -1
- package/docs/modules/components_TextInput.html +0 -1
- package/docs/modules/components_ToggleGroup.html +0 -1
- package/docs/modules/components_ValueDisplay.html +0 -1
- package/docs/modules/components_ValueIndicator.html +0 -1
- package/docs/modules/components_ValueInput.html +0 -1
- package/docs/modules/core_ActionMode.html +0 -1
- package/docs/modules/core_AutoCoreTagContext.html +0 -11
- package/docs/modules/core_AutoCoreTagTypes.html +0 -1
- package/docs/modules/core_EventEmitterContext.html +0 -53
- package/docs/modules/core_IndicatorButtonState.html +0 -1
- package/docs/modules/core_IndicatorColor.html +0 -1
- package/docs/modules/core_MaskPatterns.html +0 -1
- package/docs/modules/core_NumerableTypes.html +0 -1
- package/docs/modules/core_PositionContext.html +0 -1
- package/docs/modules/core_UniqueId.html +0 -1
- package/docs/modules/core_ValueSimulator.html +0 -1
- package/docs/modules/core_hoc.html +0 -1
- package/docs/modules/hooks.html +0 -1
- package/docs/modules/hooks_adsHooks.html +0 -1
- package/docs/modules/hooks_commandHooks.html +0 -1
- package/docs/modules/hooks_useAutoCoreTag.ts.html +0 -52
- package/docs/modules/hooks_useScaledValue.html +0 -1
- package/docs/modules/hub.html +0 -1
- package/docs/modules/hub_CommandMessage.html +0 -1
- package/docs/modules/hub_HubBase.html +0 -1
- package/docs/modules/hub_HubSimulate.html +0 -1
- package/docs/modules/hub_HubTauri.html +0 -1
- package/docs/modules/hub_HubWebSocket.html +0 -1
- package/docs/modules.html +0 -23
- package/docs/types/components_IndicatorButton.IndicatorButtonOptionsType.html +0 -1
- package/docs/types/core_AutoCoreTagTypes.ExtractByTag.html +0 -2
- package/docs/types/core_AutoCoreTagTypes.PrimitiveKind.html +0 -1
- package/docs/types/core_AutoCoreTagTypes.TagConfig.html +0 -16
- package/docs/types/core_AutoCoreTagTypes.TagValueMap.html +0 -1
- package/docs/types/core_AutoCoreTagTypes.TagValueOf.html +0 -1
- package/docs/types/core_EventEmitterContext.EmitterDispatchFunction.html +0 -3
- package/docs/types/core_EventEmitterContext.EmitterSubscribeFunction.html +0 -3
- package/docs/types/core_EventEmitterContext.EmitterUnsubscribeFunction.html +0 -3
- package/docs/types/core_NumerableTypes.NumerableFormatOptions.html +0 -4
- package/docs/types/core_hoc.HocAddSubscriptionProps.html +0 -6
- package/docs/variables/components_AutoCoreDevPanel.AutoCoreDevPanel.html +0 -43
- package/docs/variables/components_BlocklyEditor.BlocklyEditor.html +0 -13
- package/docs/variables/components_BlocklyEditor.StandardToolbox.html +0 -1
- package/docs/variables/components_FileList.FileList.html +0 -23
- package/docs/variables/components_FileSelect.FileSelect.html +0 -1
- package/docs/variables/components_FitText.FitText.html +0 -4
- package/docs/variables/components_IndicatorButton.IndicatorButton.html +0 -1
- package/docs/variables/components_JogPanel.DefaultLinearJogButtons.html +0 -2
- package/docs/variables/components_JogPanel.DefaultRotationJogButtons.html +0 -2
- package/docs/variables/components_Osk.Osk.html +0 -1
- package/docs/variables/components_ProgressBarWithValue.ProgressBarWithValue.html +0 -1
- package/docs/variables/components_ToggleGroup.ToggleGroup.html +0 -1
- package/docs/variables/components_ValueInput.ValueInput.html +0 -4
- package/docs/variables/core_AutoCoreTagContext.AutoCoreTagContext.html +0 -1
- package/docs/variables/core_AutoCoreTagContext.AutoCoreTagProvider.html +0 -7
- package/docs/variables/core_EventEmitterContext.EventEmitterContext.html +0 -64
- package/docs/variables/core_EventEmitterContext.EventEmitterProvider.html +0 -10
- package/docs/variables/core_MaskPatterns.PrimeReactMaskPatterns.html +0 -14
- package/docs/variables/core_MaskPatterns.RegExMaskPatterns.html +0 -15
- package/docs/variables/core_PositionContext.DimensionsContext.html +0 -6
- package/docs/variables/hooks_useScaledValue.kMillimeters2Inches.html +0 -2
- package/docs/variables/hooks_useScaledValue.kNewtons2Pounds.html +0 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# AutoCore React Performance & Architecture Notes
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document details the architectural decisions and performance optimizations implemented in the `autocore-react` library, specifically regarding the `EventEmitterContext` and `AutoCoreTagContext`. These changes were made to solve issues with infinite re-render loops and performance bottlenecks when handling high-frequency sensor data (e.g., 50Hz+ updates).
|
|
6
|
+
|
|
7
|
+
## Key Performance Rules
|
|
8
|
+
|
|
9
|
+
### 1. Avoid Global Context State Updates for High-Frequency Data
|
|
10
|
+
|
|
11
|
+
**The Problem:**
|
|
12
|
+
Originally, `EventEmitterContext` stored the latest event payload in its state (`state.eventData`).
|
|
13
|
+
Every time a WebSocket message arrived (e.g., a sensor value update), `setState` was called.
|
|
14
|
+
Since `EventEmitterContext` wraps the entire application, **every single component in the app re-rendered** on every sensor update.
|
|
15
|
+
|
|
16
|
+
**The Fix:**
|
|
17
|
+
We removed the `setState` call from the `dispatch` function.
|
|
18
|
+
- **Old way:** `dispatch` -> `setState` -> `Context Value Change` -> `App Re-render`.
|
|
19
|
+
- **New way:** `dispatch` -> Iterate Subscribers -> `callback(payload)`.
|
|
20
|
+
|
|
21
|
+
**Rule:**
|
|
22
|
+
> Do NOT store high-frequency data (like sensor readings) in a global Context's `value` or `state` property if that Context is consumed by the root component. Use a subscription model (observers/listeners) instead.
|
|
23
|
+
|
|
24
|
+
### 2. Stable Object References (Memoization)
|
|
25
|
+
|
|
26
|
+
**The Problem:**
|
|
27
|
+
In `AutoCoreTagContext`, the `scales` prop was defaulted using a destructuring assignment: `scales = {}`.
|
|
28
|
+
This created a **new object reference** on every render.
|
|
29
|
+
This unstable reference was used in `useEffect` dependency arrays, causing effects to re-run infinitely, leading to subscription storms and infinite loops.
|
|
30
|
+
|
|
31
|
+
**The Fix:**
|
|
32
|
+
We memoized the default empty object:
|
|
33
|
+
```typescript
|
|
34
|
+
const defaultScales = useMemo(() => ({}), []);
|
|
35
|
+
const actualScales = scales ?? defaultScales;
|
|
36
|
+
```
|
|
37
|
+
And used `actualScales` everywhere.
|
|
38
|
+
|
|
39
|
+
**Rule:**
|
|
40
|
+
> Always ensure objects passed to Context Providers or used in `useEffect` dependencies are referentially stable. Use `useMemo` for derived objects and constants.
|
|
41
|
+
|
|
42
|
+
### 3. Side-Effects in Render Phase
|
|
43
|
+
|
|
44
|
+
**The Problem:**
|
|
45
|
+
`EventEmitterProvider` was calling `hub.setContext(contextValue)` directly in the component body (during the render phase).
|
|
46
|
+
React's Concurrent Mode or Strict Mode can call the render function multiple times without committing, leading to unpredictable state in the `Hub` singleton.
|
|
47
|
+
|
|
48
|
+
**The Fix:**
|
|
49
|
+
Moved the side-effect to `useEffect`:
|
|
50
|
+
```typescript
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
hub.setContext(contextValue);
|
|
53
|
+
}, [hub, contextValue]);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Rule:**
|
|
57
|
+
> Never mutate external Singletons or perform side-effects in the main body of a React Functional Component. Always use `useEffect`.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Data Flow Architecture
|
|
62
|
+
|
|
63
|
+
### 1. Tag Name vs. FQDN
|
|
64
|
+
|
|
65
|
+
To decouple the UI from the backend implementation:
|
|
66
|
+
- **FQDN (Fully Qualified Domain Name)**: The backend address (e.g., `ADS.Main.bStart`). Used only for wire communication.
|
|
67
|
+
- **TagName**: The local UI alias (e.g., `startCmd`). Used by React components.
|
|
68
|
+
|
|
69
|
+
**The Flow:**
|
|
70
|
+
1. **Component**: Calls `useAutoCoreTag("startCmd")`.
|
|
71
|
+
2. **Context**: Looks up config, finds FQDN `ADS.Main.bStart`.
|
|
72
|
+
3. **Context**: Calls `hub.subscribe("startCmd", "ADS.Main.bStart")`.
|
|
73
|
+
4. **Hub**: Maps `ADS.Main.bStart` -> `startCmd`.
|
|
74
|
+
5. **Backend**: Sends update for `ADS.Main.bStart`.
|
|
75
|
+
6. **Hub**: Receives update, translates topic to `startCmd`, dispatches event.
|
|
76
|
+
7. **Context**: Listens for `startCmd`, updates state.
|
|
77
|
+
8. **Component**: Re-renders with new value.
|
|
78
|
+
|
|
79
|
+
### 2. Dual-State Storage
|
|
80
|
+
|
|
81
|
+
`AutoCoreTagContext` stores two versions of every value:
|
|
82
|
+
1. **`rawValues`**: The exact JSON received from the server. **(Source of Truth)**
|
|
83
|
+
2. **`values`**: The scaled/decoded value for display.
|
|
84
|
+
|
|
85
|
+
When a scale changes (e.g., mm to inches), we recompute `values` from `rawValues`. We never rescale a previously scaled value to avoid floating-point drift.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Best Practices for Team
|
|
90
|
+
|
|
91
|
+
1. **Use Hooks**: Always use `useAutoCoreTag` or `useAutoCoreTags`. Do not consume the Context directly unless necessary.
|
|
92
|
+
2. **Key by TagName**: When adding features, always prefer `tagName` over `fqdn` for local logic.
|
|
93
|
+
3. **Console Logs**: Avoid leaving `console.log` in render paths (components). It floods the console and slows down the browser.
|
|
94
|
+
4. **Strict Mode**: Be aware that React Strict Mode mounts components twice. Your `useEffect` cleanup functions must be robust.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoCoreDevPanel.d.ts","sourceRoot":"","sources":["../../src/components/AutoCoreDevPanel.tsx"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AAEH,OAAO,KAAqD,MAAM,OAAO,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"AutoCoreDevPanel.d.ts","sourceRoot":"","sources":["../../src/components/AutoCoreDevPanel.tsx"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AAEH,OAAO,KAAqD,MAAM,OAAO,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAM1D;;GAEG;AACH,UAAU,qBAAqB;IAC7B;;;OAGG;IACH,IAAI,EAAE,SAAS,SAAS,EAAE,CAAC;IAE3B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAsK5D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useContext,useMemo,useState,useCallback}from"react";import{AutoCoreTagContext}from"../core/AutoCoreTagContext";export const AutoCoreDevPanel=({tags:e,title:
|
|
1
|
+
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useContext,useMemo,useState,useCallback}from"react";import{AutoCoreTagContext}from"../core/AutoCoreTagContext";const getDomain=e=>e.split(".")[0];export const AutoCoreDevPanel=({tags:e,title:n="AutoCore Dev Panel",className:t=""})=>{const{values:o,rawValues:a,isLoading:s,write:l,tap:r,scales:i}=useContext(AutoCoreTagContext),[d,c]=useState({}),[u,h]=useState({}),x=useMemo(()=>e.map(e=>{const n=o[e.fqdn],t=a[e.fqdn],s=e.scale??"",l=s?i[s]:void 0;return{tag:e,display:n,raw:t,scaleName:s,factor:l?.scale,label:l?.label}}),[e,o,a,i]),m=useCallback((e,n)=>{c(t=>({...t,[e]:n})),h(n=>({...n,[e]:""}))},[]),p=useCallback((e,n)=>{h(t=>({...t,[e]:n}))},[]),y=useCallback(async e=>{const n=e.fqdn,t=d[n];try{if("boolean"===e.valueType){const e="true"===t||"false"!==t&&!o[n];return await l(n,e),void m(n,"")}if("number"===e.valueType){if(null==t||""===t.trim())return void p(n,"Enter a number.");const e=Number(t);return Number.isNaN(e)?void p(n,"Invalid number."):(await l(n,e),void m(n,""))}if("string"===e.valueType)return await l(n,t??""),void m(n,"");try{const e=t?.trim()?JSON.parse(t):null;await l(n,e),m(n,"")}catch(e){p(n,"Invalid JSON.")}}catch(e){p(n,e?.message??String(e))}},[d,m,p,l,o]);return _jsxs("div",{className:`rounded-md border border-gray-200 bg-white text-sm ${t}`,style:{minWidth:680},children:[_jsx("div",{className:"flex items-center justify-between px-3 py-2",style:{borderBottom:"1px solid #eee"},children:_jsxs("div",{className:"flex items-center gap-2",children:[_jsx("strong",{children:n}),_jsx("span",{title:s?"Loading":"Live",style:{width:8,height:8,borderRadius:999,background:s?"#aaa":"#22c55e",display:"inline-block"}})]})}),_jsx("div",{style:{overflowX:"auto"},children:_jsxs("table",{style:{borderCollapse:"collapse",width:"100%"},children:[_jsx("thead",{children:_jsxs("tr",{children:[_jsx(Th,{children:"Tag"}),_jsx(Th,{children:"Kind"}),_jsx(Th,{children:"Domain"}),_jsx(Th,{children:"Symbol"}),_jsx(Th,{children:"Display"}),_jsx(Th,{children:"Raw"}),_jsx(Th,{children:"Scale"}),_jsx(Th,{children:"Units"}),_jsx(Th,{children:"Input"}),_jsx(Th,{children:"Actions"})]})}),_jsx("tbody",{children:x.map(({tag:e,display:n,raw:t,scaleName:o,factor:a,label:s})=>{const l=d[e.fqdn]??"",i=u[e.fqdn];return _jsxs("tr",{children:[_jsx(Td,{mono:!0,children:e.tagName}),_jsx(Td,{mono:!0,children:e.valueType}),_jsx(Td,{mono:!0,children:getDomain(e.fqdn)}),_jsx(Td,{mono:!0,children:e.fqdn}),_jsx(Td,{mono:!0,children:fmt(n)}),_jsx(Td,{mono:!0,children:fmt(t)}),_jsx(Td,{mono:!0,children:o?`${o} ×${a??1}`:"—"}),_jsx(Td,{mono:!0,children:o?s??"":"—"}),_jsxs(Td,{children:[_jsx(InputForTag,{tag:e,buf:l,setBuf:n=>m(e.fqdn,n),onKeyDown:n=>((e,n)=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),y(n))})(n,e)}),i&&_jsx("div",{style:{color:"#dc2626",marginTop:4},children:i})]}),_jsx(Td,{children:_jsxs("div",{style:{display:"flex",gap:6},children:[_jsx("button",{onClick:()=>y(e),children:"Write"}),"boolean"===e.valueType&&_jsx("button",{onClick:()=>r(e.fqdn),children:"TAP"})]})})]},e.fqdn)})})]})})]})};const Th=({children:e})=>_jsx("th",{style:{textAlign:"left",borderBottom:"1px solid #ddd",padding:"6px 8px",fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace"},children:e}),Td=({children:e,mono:n})=>_jsx("td",{style:{borderBottom:"1px solid #eee",padding:"6px 8px",whiteSpace:"nowrap",fontFamily:n?"ui-monospace, SFMono-Regular, Menlo, monospace":"inherit"},children:e}),InputForTag=({tag:e,buf:n,setBuf:t,onKeyDown:o})=>"boolean"===e.valueType?_jsxs("label",{style:{display:"inline-flex",alignItems:"center",gap:6},children:[_jsx("input",{type:"checkbox",checked:n?"true"===n:Boolean,onChange:e=>t(e.target.checked?"true":"false"),onKeyDown:o}),_jsx("span",{children:"set"})]}):"number"===e.valueType?_jsx("input",{type:"number",step:"any",value:n,onChange:e=>t(e.target.value),onKeyDown:o,style:{width:140},placeholder:"number…"}):"string"===e.valueType?_jsx("input",{type:"text",value:n,onChange:e=>t(e.target.value),onKeyDown:o,style:{width:180},placeholder:"text…"}):_jsx("textarea",{value:n,onChange:e=>t(e.target.value),onKeyDown:o,rows:2,style:{width:260,fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace"},placeholder:'{"foo": 42}'});function fmt(e){if(null==e)return"—";if("number"==typeof e)return Number.isFinite(e)?e.toString():String(e);if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileList.d.ts","sourceRoot":"","sources":["../../src/components/FileList.tsx"],"names":[],"mappings":"AASA;;;;;;GAMG;AAEH,OAAO,KAA0C,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"FileList.d.ts","sourceRoot":"","sources":["../../src/components/FileList.tsx"],"names":[],"mappings":"AASA;;;;;;GAMG;AAEH,OAAO,KAA0C,MAAM,OAAO,CAAC;AAY/D;;GAEG;AACH;;GAEG;AACH,KAAK,aAAa,GAAG;IAGjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,YAAY,CAAC,EAAE,OAAO,CAAC;IAIvB,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,SAAS,CAAC,EAAG,CAAC,OAAO,EAAC,MAAM,KAAK,IAAI,CAAC;IAEtC;;OAEG;IACH,OAAO,CAAC,EAAG,CAAC,OAAO,EAAC,MAAM,KAAK,IAAI,CAAC;CACvC,CAAA;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CA8Q5C,CAAC;AAEF,eAAe,QAAQ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useState,useContext,useEffect}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Toolbar}from"primereact/toolbar";import{Button}from"primereact/button";import{confirmPopup}from"primereact/confirmpopup";import{FileUpload}from"primereact/fileupload";import{EventEmitterContext}from"../core/EventEmitterContext";export const FileList=({domain:e="DATASTORE",enableUpload:t=!1,subdir:
|
|
1
|
+
import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";import React,{useState,useContext,useEffect}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Toolbar}from"primereact/toolbar";import{Button}from"primereact/button";import{confirmPopup}from"primereact/confirmpopup";import{FileUpload}from"primereact/fileupload";import{EventEmitterContext}from"../core/EventEmitterContext";import{MessageType}from"../hub/CommandMessage";export const FileList=({domain:e="DATASTORE",enableUpload:t=!1,subdir:a,filter:o=".json",onSuccess:n,onError:i})=>{const[r,s]=useState(0),{invoke:l}=useContext(EventEmitterContext),[c,m]=useState(),d=e=>null!==e?void 0!==a?`${a}/${e}`:e:"",p=async()=>{try{const t=void 0!==a?{subdir:a}:{};let o=await l(`${e}.list_files`,MessageType.Request,t),n=[];for(let e=0;e<o.data.length;++e){const t=o.data[e];n.push({id:e+1,name:t})}m(n)}catch(e){i&&i(`Failed to upload file list: ${e}`)}};const u=()=>{s(e=>e+1)},f=`File Listing [/${void 0!==a?a:""}]`,b=_jsx(React.Fragment,{children:_jsx("span",{style:{fontWeight:600},children:f})}),x=_jsxs(React.Fragment,{children:[t&&_jsx(FileUpload,{customUpload:!0,auto:!0,uploadHandler:async t=>{const a=t.files[0];let o=d(a.name);const r=new FileReader;r.onload=async t=>{const r=t.target?.result,s=function c(e){let t="",a=new Uint8Array(e),o=a.byteLength;for(let e=0;e<o;e++)t+=String.fromCharCode(a[e]);return window.btoa(t)}(r);try{await l(`${e}.write_file`,MessageType.Request,{file_name:o,value:s,options:{base64:!0}}),n&&n(`Uploaded file ${a.name}`),p()}catch(e){i&&i(`Failed to upload file: ${e}`)}u()},r.onerror=e=>{i&&i(`Error reading file: ${e}`)},r.readAsArrayBuffer(a)},accept:o,maxFileSize:25e3,mode:"basic",chooseLabel:"",chooseOptions:{icon:"pi pi-upload",className:"p-button-icon-only p-button-text p-button-rounded p-mr-2"}},r),_jsx(Button,{icon:"pi pi-refresh",onClick:()=>{p()},className:"p-button-rounded p-mr-2","aria-label":"Refresh",size:"small",rounded:!0,text:!0})]}),g=(t,a)=>{confirmPopup({target:a.currentTarget,message:`Are you want to delete file ${t.name}?\nWARNING: This cannot be undone.`,icon:"pi pi-info-circle",defaultFocus:"reject",acceptClassName:"p-button-danger",accept:()=>(async t=>{let a=d(t);try{await l(`${e}.delete_file`,MessageType.Request,{file_name:a}),n&&n(`Deleted file: ${t}`),p()}catch(e){i&&i(`Failed to delete file: ${e}`)}p()})(t.name)})};return useEffect(()=>(p(),()=>{}),[e,t]),_jsxs("div",{children:[_jsx(Toolbar,{start:b,end:x,style:{padding:"1mm"}}),_jsxs(DataTable,{value:c,children:[_jsx(Column,{field:"name",header:"Name"}),_jsx(Column,{body:t=>_jsxs(_Fragment,{children:[_jsx(Button,{icon:"pi pi-download",onClick:()=>(async t=>{let a=d(t.name);try{await l(`${e}.download_file`,MessageType.Request,{file_name:a}),n&&n(`Downloaded file: ${t.name}`)}catch(e){i&&i(`Failed downloading file: ${e}`)}})(t),className:"p-button-rounded p-button-success p-mr-2",style:{marginRight:"2mm"},size:"small"}),_jsx(Button,{icon:"pi pi-trash",onClick:e=>g(t,e),className:"p-button-rounded p-button-danger",size:"small"})]}),header:"Actions"})]})]})};export default FileList;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileSelect.d.ts","sourceRoot":"","sources":["../../src/components/FileSelect.tsx"],"names":[],"mappings":"AAUA,OAAO,KAA0C,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"FileSelect.d.ts","sourceRoot":"","sources":["../../src/components/FileSelect.tsx"],"names":[],"mappings":"AAUA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAQ/D,KAAK,eAAe,GAAG;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB,CAAA;AAOD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA0FhD,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useContext,useEffect}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{EventEmitterContext}from"../core/EventEmitterContext";export const FileSelect=({domain:e="DATASTORE",subdir:t,filter:n,onFileSelected:l,onAccept:
|
|
1
|
+
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useContext,useEffect}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{EventEmitterContext}from"../core/EventEmitterContext";import{MessageType}from"../hub/CommandMessage";export const FileSelect=({domain:e="DATASTORE",subdir:t,filter:n,onFileSelected:l,onAccept:a,onCancel:i})=>{const{invoke:o}=useContext(EventEmitterContext),[s,c]=useState([]),[r,m]=useState(null),u=e=>{m(e),l&&l(e.name)};return useEffect(()=>{(async()=>{try{let l;l=null!=n?t?{subdir:t,options:{filter:n}}:{options:{filter:n}}:t?{subdir:t}:{};const a=(await o(`${e}.list_files`,MessageType.Request,l)).data.map((e,t)=>({id:t+1,name:e}));c(a)}catch(e){}})()},[e,t]),_jsxs("div",{children:[_jsxs(DataTable,{value:s,selectionMode:"single",selection:r,onSelectionChange:e=>u(e.value),children:[_jsx(Column,{field:"name",header:"File Name"}),_jsx(Column,{body:e=>_jsx(Button,{label:r?.name===e.name?"Selected":"Select",icon:r?.name===e.name?"pi pi-check-circle":"pi pi-circle-off",onClick:()=>u(e),className:"p-button-rounded "+(r?.name===e.name?"p-button-success":"p-button-outlined"),"aria-label":"Select",size:"small"}),header:"Select"})]}),_jsxs("div",{style:{display:"flex",justifyContent:"flex-end",marginTop:"10px"},children:[_jsx(Button,{label:"Cancel",icon:"pi pi-times",className:"p-button-text",onClick:i}),_jsx(Button,{label:"Accept",icon:"pi pi-check",className:"p-button",onClick:()=>{void 0!==a&&null!==r&&a(null!==r?void 0!==t?`${t}/${r.name}`:r.name:"")},disabled:!r})]})]})};export default FileSelect;
|
|
@@ -4,101 +4,77 @@
|
|
|
4
4
|
* @document ../../additional-docs/AutCoreTagContext.md
|
|
5
5
|
*
|
|
6
6
|
* @summary
|
|
7
|
-
* A React Context + Provider that
|
|
8
|
-
*
|
|
9
|
-
* - Derives **display** values from raw using current `scales` and optional tag `codec`
|
|
10
|
-
* - Recomputes display values **from raw** whenever a scale changes (no lossy re-scaling)
|
|
11
|
-
* - Inverse-scales on `write()` so the server always receives controller units
|
|
12
|
-
* - Supports ADS one-shot refresh, GNV-style scale objects, and resilient eager reads
|
|
7
|
+
* A React Context + Provider that manages the state and synchronization of "Tags" (data points)
|
|
8
|
+
* between the React frontend and the AutoCore backend.
|
|
13
9
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
19
|
-
* ❖ **Scaling**
|
|
20
|
-
* - Provide `scales` keyed by scale group (e.g., `"position"`, `"load"`).
|
|
21
|
-
* - Each scale may point at a server tag (`serverTag`) that publishes an object
|
|
22
|
-
* `{ name, scale, label }` (GNV-style). When received, the Provider recomputes
|
|
23
|
-
* *only* affected display values **from raw**.
|
|
24
|
-
* - Manual `updateScale(name, scale, label)` writes the object back to the server
|
|
25
|
-
* (if `serverTag` is configured) and recomputes from raw locally.
|
|
10
|
+
* It acts as the central data store for real-time values, handling:
|
|
11
|
+
* - **Buffering**: Stores raw controller values exactly as received.
|
|
12
|
+
* - **Scaling**: Converts raw values to display values (e.g., mm -> inches) based on configuration.
|
|
13
|
+
* - **Synchronization**: Subscribes to live updates from the server.
|
|
14
|
+
* - **Writing**: Converts display values back to raw controller units before sending.
|
|
26
15
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* ❖ **Dual-State Representation**
|
|
18
|
+
* To prevent rounding errors and race conditions, we maintain two parallel states:
|
|
19
|
+
* 1. `rawValues[tagName]`: The exact value received from the controller (Source of Truth).
|
|
20
|
+
* 2. `values[tagName]`: The derived value shown in the UI (calculated from raw + scale).
|
|
32
21
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* - `tap(tagName)` pulses booleans `true → (300ms) → false` with proper domain envelopes
|
|
22
|
+
* When a scale changes (e.g., user switches units), we recompute `values` from `rawValues`.
|
|
23
|
+
* We *never* rescale a previously scaled value.
|
|
36
24
|
*
|
|
37
|
-
* ❖ **
|
|
38
|
-
* -
|
|
39
|
-
* -
|
|
25
|
+
* ❖ **Key Features**
|
|
26
|
+
* - **Resilient Initialization**: Eagerly fetches initial values (via ADS refresh or read fallback).
|
|
27
|
+
* - **Server-Stored Scales**: Can load unit preferences from the server (GNV domain).
|
|
28
|
+
* - **Optimized Updates**: Uses stable references and memoization to minimize React re-renders.
|
|
29
|
+
* - **Type Safety**: Strongly typed context via generic `VMapRuntime`.
|
|
40
30
|
*
|
|
41
31
|
* @example Minimal wiring
|
|
42
32
|
* ```tsx
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* const scales: Record<string, ScaleConfig> = {
|
|
53
|
-
* position: {
|
|
54
|
-
* name: "position",
|
|
55
|
-
* scale: 1.0,
|
|
56
|
-
* label: "mm",
|
|
57
|
-
* serverTag: { domain: "GNV", symbolName: "position_units" }, // expects {name, scale, label}
|
|
58
|
-
* },
|
|
59
|
-
* load: {
|
|
60
|
-
* name: "load",
|
|
61
|
-
* scale: 1.0,
|
|
62
|
-
* label: "N",
|
|
63
|
-
* serverTag: { domain: "GNV", symbolName: "load_units" }, // expects {name, scale, label}
|
|
64
|
-
* },
|
|
33
|
+
* // 1. Define your tags
|
|
34
|
+
* const tags = [
|
|
35
|
+
* { tagName: "position", fqdn: "ADS.Axis.Pos", valueType: "number", scale: "dist" },
|
|
36
|
+
* { tagName: "speed", fqdn: "ADS.Axis.Vel", valueType: "number", scale: "dist" }
|
|
37
|
+
* ];
|
|
38
|
+
*
|
|
39
|
+
* // 2. Define scales
|
|
40
|
+
* const scales = {
|
|
41
|
+
* dist: { name: "dist", scale: 1.0, label: "mm" }
|
|
65
42
|
* };
|
|
66
43
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* </AutoCoreTagProvider>
|
|
72
|
-
* );
|
|
73
|
-
* }
|
|
74
|
-
* ```
|
|
75
|
-
*
|
|
76
|
-
* @example Consuming values with hooks
|
|
77
|
-
* ```tsx
|
|
78
|
-
* import { makeAutoCoreTagHooks } from "@adcops/autocore-react/hooks/useAutoCoreTag";
|
|
79
|
-
* import { AutoCoreTagContext } from "@adcops/autocore-react/core/AutoCoreTagContext";
|
|
80
|
-
*
|
|
81
|
-
* export const AutoCoreHooks = makeAutoCoreTagHooks(AutoCoreTagContext, acTagSpec);
|
|
82
|
-
*
|
|
83
|
-
* function Dashboard() {
|
|
84
|
-
* const { value: pos } = AutoCoreHooks.useAutoCoreTag("pressPosition"); // display units
|
|
85
|
-
* const { value: load } = AutoCoreHooks.useAutoCoreTag("pressLoad");
|
|
86
|
-
* const { scales, updateScale } = AutoCoreHooks.useScales();
|
|
87
|
-
*
|
|
88
|
-
* return (
|
|
89
|
-
* <>
|
|
90
|
-
* <div>Position: {pos} {scales.position.label}</div>
|
|
91
|
-
* <div>Load: {load} {scales.load.label}</div>
|
|
92
|
-
* <button onClick={() => updateScale("position", 1/25.4, "in")}>inches</button>
|
|
93
|
-
* </>
|
|
94
|
-
* );
|
|
95
|
-
* }
|
|
44
|
+
* // 3. Wrap application
|
|
45
|
+
* <AutoCoreTagProvider tags={tags} scales={scales}>
|
|
46
|
+
* <App />
|
|
47
|
+
* </AutoCoreTagProvider>
|
|
96
48
|
* ```
|
|
97
49
|
*/
|
|
98
50
|
import React, { type ReactNode } from "react";
|
|
99
51
|
import type { BaseContextValue, TagConfig, ScaleConfig } from "./AutoCoreTagTypes";
|
|
52
|
+
/**
|
|
53
|
+
* Runtime type for the values map - allows any tag name to map to any value type.
|
|
54
|
+
* This is the generic type used when the specific tag configuration is not known at compile time.
|
|
55
|
+
*/
|
|
100
56
|
type VMapRuntime = Record<string, unknown>;
|
|
57
|
+
/**
|
|
58
|
+
* The React Context that holds all AutoCore tag state and operations.
|
|
59
|
+
*
|
|
60
|
+
* This context provides:
|
|
61
|
+
* - `values`: Display values (with scaling/codecs applied)
|
|
62
|
+
* - `rawValues`: Raw controller values as received from server
|
|
63
|
+
* - `isLoading`: Whether initial data loading is complete
|
|
64
|
+
* - `write`: Function to write a value to a tag
|
|
65
|
+
* - `tap`: Function to pulse a boolean tag (true → delay → false)
|
|
66
|
+
* - `scales`: Current scale configurations
|
|
67
|
+
* - `updateScale`: Function to change a scale factor/label
|
|
68
|
+
*
|
|
69
|
+
* @see AutoCoreTagProvider for the provider implementation
|
|
70
|
+
* @see makeAutoCoreTagHooks for creating typed hooks from this context
|
|
71
|
+
*/
|
|
101
72
|
export declare const AutoCoreTagContext: React.Context<BaseContextValue<VMapRuntime>>;
|
|
73
|
+
/**
|
|
74
|
+
* Extracts the domain (first segment) from a Fully-Qualified Domain Name.
|
|
75
|
+
* @param fqdn - The full topic path (e.g., "ads.plc1.gio.bControlPowerOk")
|
|
76
|
+
* @returns The domain segment (e.g., "ads")
|
|
77
|
+
*/
|
|
102
78
|
/**
|
|
103
79
|
* AutoCoreTagProvider
|
|
104
80
|
*
|
|
@@ -108,111 +84,9 @@ export declare const AutoCoreTagContext: React.Context<BaseContextValue<VMapRunt
|
|
|
108
84
|
* writes/taps with proper domain envelopes. Scaling is **always** applied to display
|
|
109
85
|
* values only; raw values remain untouched and are the single source of truth.
|
|
110
86
|
*
|
|
111
|
-
* @props
|
|
112
|
-
* -
|
|
113
|
-
*
|
|
114
|
-
* `valueType` ("boolean"|"number"|"string"|"json"), optional `scale` name, and
|
|
115
|
-
* optional `codec` for JSON.
|
|
116
|
-
*
|
|
117
|
-
* - `scales?: Record<string, ScaleConfig>`
|
|
118
|
-
* Map of scale groups (`position`, `load`, …). Each scale may include an optional
|
|
119
|
-
* `serverTag` `{ domain, symbolName }`. When present, the provider reads that key
|
|
120
|
-
* first and subscribes to updates. The server must publish a GNV-style object
|
|
121
|
-
* `{ name, scale, label }` (stringified or object). When a new scale arrives,
|
|
122
|
-
* the provider recomputes affected display values **from raw**.
|
|
123
|
-
*
|
|
124
|
-
* - `eagerRead?: boolean = true`
|
|
125
|
-
* If true, the provider performs a non-ADS eager fetch pass (refresh if available,
|
|
126
|
-
* otherwise read_value with backoff) and then performs a single ADS `refresh`.
|
|
127
|
-
* Missing keys are common on clean systems—these are logged and skipped.
|
|
128
|
-
*
|
|
129
|
-
* - `children: ReactNode`
|
|
130
|
-
* Your app subtree that consumes `AutoCoreTagContext`.
|
|
131
|
-
*
|
|
132
|
-
* @context value
|
|
133
|
-
* - `values`: Partial<Record<tagName, unknown>> — app-visible (scaled/decoded)
|
|
134
|
-
* - `rawValues`: Record<tagName, unknown> — last controller values as received
|
|
135
|
-
* - `isLoading`: boolean — true until initial wiring completes
|
|
136
|
-
* - `write(tagName, displayValue)`: Promise<void>
|
|
137
|
-
* Serializes/encodes JSON if needed and inverse-scales numbers back to raw units.
|
|
138
|
-
* Dispatches `write_value` with appropriate domain envelope:
|
|
139
|
-
* - ADS: `{ symbol_name, value }`
|
|
140
|
-
* - GNV: `{ group:"ux", key, value }`
|
|
141
|
-
* - Other: `{ key, value }`
|
|
142
|
-
* - `tap(tagName)`: Promise<void>
|
|
143
|
-
* For boolean tags only. Pulses true → 300ms → false with the proper envelope.
|
|
144
|
-
* - `scales`: Record<string, ScaleConfig> — current factors/labels
|
|
145
|
-
* - `updateScale(name, scale, label)`: Promise<void>
|
|
146
|
-
* Writes `{ name, scale, label }` to the configured `serverTag` (if present) and
|
|
147
|
-
* recomputes display values **from raw** locally.
|
|
148
|
-
*
|
|
149
|
-
* @example With server-driven scales (GNV) and ADS data
|
|
150
|
-
* ```tsx
|
|
151
|
-
* const tags: readonly TagConfig[] = [
|
|
152
|
-
* { tagName: "pressPosition", domain: "ADS", valueType: "number", symbolName: "AX.Press.Pos", scale: "position" },
|
|
153
|
-
* { tagName: "pressLoad", domain: "ADS", valueType: "number", symbolName: "AX.Press.Load", scale: "load" },
|
|
154
|
-
* { tagName: "isMotorsOn", domain: "ADS", valueType: "boolean", symbolName: "GIO.isMotorsOn" },
|
|
155
|
-
* // JSON with codec example
|
|
156
|
-
* { tagName: "jobMeta", domain: "GNV", valueType: "json", symbolName: "job_meta",
|
|
157
|
-
* codec: {
|
|
158
|
-
* fromServer: (raw) => (typeof raw === "string" ? JSON.parse(raw) : raw),
|
|
159
|
-
* toServer: (val) => JSON.stringify(val),
|
|
160
|
-
* }
|
|
161
|
-
* },
|
|
162
|
-
* ] as const;
|
|
163
|
-
*
|
|
164
|
-
* const scales: Record<string, ScaleConfig> = {
|
|
165
|
-
* position: {
|
|
166
|
-
* name: "position",
|
|
167
|
-
* scale: 1, label: "mm",
|
|
168
|
-
* serverTag: { domain: "GNV", symbolName: "position_units" } // expects {name, scale, label}
|
|
169
|
-
* },
|
|
170
|
-
* load: {
|
|
171
|
-
* name: "load",
|
|
172
|
-
* scale: 1, label: "N",
|
|
173
|
-
* serverTag: { domain: "GNV", symbolName: "load_units" } // expects {name, scale, label}
|
|
174
|
-
* }
|
|
175
|
-
* };
|
|
176
|
-
*
|
|
177
|
-
* <AutoCoreTagProvider tags={tags} scales={scales} eagerRead>
|
|
178
|
-
* <YourApp/>
|
|
179
|
-
* </AutoCoreTagProvider>
|
|
180
|
-
* ```
|
|
181
|
-
*
|
|
182
|
-
* @example Reading values, raw vs display, and writing
|
|
183
|
-
* ```tsx
|
|
184
|
-
* import { makeAutoCoreTagHooks } from "../hooks/useAutoCoreTag";
|
|
185
|
-
* import { AutoCoreTagContext } from "../core/AutoCoreTagContext";
|
|
186
|
-
*
|
|
187
|
-
* const Hooks = makeAutoCoreTagHooks(AutoCoreTagContext, tags);
|
|
188
|
-
*
|
|
189
|
-
* function Status() {
|
|
190
|
-
* const { value: pos, isLoading } = Hooks.useAutoCoreTag("pressPosition"); // scaled
|
|
191
|
-
* const { value: rawPos } = (() => {
|
|
192
|
-
* // access raw via context when needed for debugging
|
|
193
|
-
* const ctx = React.useContext(AutoCoreTagContext);
|
|
194
|
-
* return { value: ctx.rawValues["pressPosition"] };
|
|
195
|
-
* })();
|
|
196
|
-
*
|
|
197
|
-
* const { scales, updateScale } = Hooks.useScales();
|
|
198
|
-
*
|
|
199
|
-
* return (
|
|
200
|
-
* <>
|
|
201
|
-
* <div>Pos: {pos} {scales.position.label} (raw: {String(rawPos)})</div>
|
|
202
|
-
* <button onClick={() => updateScale("position", 1/25.4, "in")}>inches</button>
|
|
203
|
-
* </>
|
|
204
|
-
* );
|
|
205
|
-
* }
|
|
206
|
-
* ```
|
|
207
|
-
*
|
|
208
|
-
* @example Tapping booleans and writing numbers in display units
|
|
209
|
-
* ```tsx
|
|
210
|
-
* const { tap } = Hooks.useAutoCoreTag("isMotorsOn");
|
|
211
|
-
* const { write } = Hooks.useAutoCoreTag("pressPosition");
|
|
212
|
-
*
|
|
213
|
-
* <button onClick={() => tap()}>Motors TAP</button>
|
|
214
|
-
* <button onClick={() => write(10.0)}>Jog to 10 (display units)</button>
|
|
215
|
-
* ```
|
|
87
|
+
* @param props.tags - List of tag configurations (tagName, fqdn, etc.)
|
|
88
|
+
* @param props.scales - Map of scale definitions (name, factor, label)
|
|
89
|
+
* @param props.eagerRead - If true, automatically fetches initial values on mount
|
|
216
90
|
*/
|
|
217
91
|
export declare const AutoCoreTagProvider: React.FC<{
|
|
218
92
|
children: ReactNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoCoreTagContext.d.ts","sourceRoot":"","sources":["../../src/core/AutoCoreTagContext.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AutoCoreTagContext.d.ts","sourceRoot":"","sources":["../../src/core/AutoCoreTagContext.tsx"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,KAAK,EAAE,EAQV,KAAK,SAAS,EACjB,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EACR,gBAAgB,EAChB,SAAS,EACT,WAAW,EACd,MAAM,oBAAoB,CAAC;AAI5B;;;GAGG;AACH,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,8CAQ7B,CAAC;AAsBH;;;;GAIG;AAIH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC;IACvC,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,EAAE,SAAS,SAAS,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB,CA0cA,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as _jsx}from"react/jsx-runtime";import React,{useRef,createContext,useCallback,useContext,useEffect,useMemo,useState}from"react";import{EventEmitterContext}from"./EventEmitterContext";export const AutoCoreTagContext=createContext({values:{},rawValues:{},isLoading:!0,write:async()=>{},tap:async()=>{},scales:{},updateScale:async()=>{}});const sleep=e=>new Promise(t=>setTimeout(t,e));export const AutoCoreTagProvider=({children:e,tags:t,scales:a
|
|
1
|
+
import{jsx as _jsx}from"react/jsx-runtime";import React,{useRef,createContext,useCallback,useContext,useEffect,useMemo,useState}from"react";import{EventEmitterContext}from"./EventEmitterContext";import{MessageType}from"../hub/CommandMessage";export const AutoCoreTagContext=createContext({values:{},rawValues:{},isLoading:!0,write:async()=>{},tap:async()=>{},scales:{},updateScale:async()=>{}});const sleep=e=>new Promise(t=>setTimeout(t,e));export const AutoCoreTagProvider=({children:e,tags:t,scales:a,eagerRead:s=!0})=>{const n=useRef(!1),r=useMemo(()=>({}),[]),c=a??r,{invoke:o,read:l,write:u,serverSubscribe:i,isConnected:f,subscribe:m,unsubscribe:b}=useContext(EventEmitterContext),[y,g]=useState(()=>{const e={};for(const a of t)void 0!==a.initialValue&&(e[a.tagName]=a.initialValue);return e}),[d,p]=useState({}),[v,C]=useState(c),N=useRef(v),h=useRef(y);useEffect(()=>{N.current=v},[v]),useEffect(()=>{h.current=y},[y]);const[w,T]=useState(!0),x=useCallback((e,t)=>{const{valueType:a,scale:s,codec:n}=e;if("number"===a&&"number"==typeof t&&s){const e=N.current[s];return t*(e?.scale??1)}if("json"===a&&n?.fromServer)try{return n.fromServer(t)}catch{}return t},[]),E=useCallback((e,t)=>{const{valueType:a,scale:s,codec:n}=e;if("json"===a&&n?.toServer)try{t=n.toServer(t)}catch{}if("number"===a&&"number"==typeof t&&s){const e=N.current[s];return t/(e?.scale??1)}return t},[]),S=useCallback(e=>{const a=t.filter(t=>t.scale===e);a.length&&p(e=>{const t={...e};for(const e of a){const a=h.current[e.tagName];if("number"!=typeof a)continue;const s=x(e,a);t[e.tagName]!==s&&(t[e.tagName]=s)}return t})},[t,x]),k=useCallback((e,t)=>{g(a=>a[e.tagName]===t?a:{...a,[e.tagName]:t});const a=x(e,t);p(t=>t[e.tagName]===a?t:{...t,[e.tagName]:a})},[x]),M=useCallback(async(e,t,a)=>{const s=a?.concurrency??4,n=a?.minDelayMs??20,r=a?.jitterMs??40;let c=0;const u=Array.from({length:s},()=>(async()=>{for(;;){const a=c++;if(a>=t.length)return;const s=t[a];try{let t=!1;try{await o(s.fqdn,MessageType.Request,{action:"refresh"}),t=!0}catch{}if(!t){const t="GNV"===e.toUpperCase()?{group:"ux"}:{};try{const e=await l(s.fqdn,t);e?.success&&k(s,e.data)}catch(e){}}}catch(e){}finally{const e=Math.floor(Math.random()*r);await sleep(n+e)}}})());await Promise.all(u)},[o,l,k]),j=useCallback(async()=>{for(const[e,t]of Object.entries(c)){if(!t.serverTag)continue;const{domain:a,symbolName:s}=t.serverTag;try{const t=await l(`${a}.${s}`,{group:"ux"});if(t.data&&(t.success??1)&&(t.valid??1)){const a=JSON.parse(t.data);if(a&&"number"==typeof a.scale){const{scale:t,label:s}=a;C(a=>({...a,[e]:{...a[e],scale:t,label:s??a[e]?.label??"---"}})),S(e)}}}catch{}}},[l,c,S]);useEffect(()=>{let e=!0;const a=[],s=async()=>{if(e&&!n.current){n.current=!0;try{await(async()=>{try{await j();for(const s of t){await i(s.tagName,s.fqdn,s.subscriptionOptions);const t=m(s.tagName,t=>{e&&k(s,t)});a.push(t)}}finally{e&&setTimeout(()=>e&&T(!1),100)}})()}finally{e&&setTimeout(()=>e&&T(!1),100)}}};if(f())s();else{const e=m("HUB/connected",()=>{b(e),s()});a.push(e)}return()=>{e=!1,a.forEach(b),n.current=!1}},[m,b,f,o,i,s,t,j,M,k]);const q=useMemo(()=>Object.entries(c).filter(([,e])=>e.serverTag).map(([e,t])=>({scaleName:e,domain:t.serverTag.domain,symbolName:t.serverTag.symbolName})),[c]);useEffect(()=>{let e=!0;const t=[];for(const{scaleName:a,domain:s,symbolName:n}of q){const r=m(`${s}.${n}`,t=>{if(!e)return;const s=t?.value;if(s&&"object"==typeof s&&"number"==typeof s.scale){const{scale:e,label:t}=s;C(s=>({...s,[a]:{...s[a],scale:e,label:t??s[a]?.label??"---"}})),S(a)}});t.push(r)}return()=>{e=!1,t.forEach(b)}},[m,b,q,S]);const R=useCallback(async(e,a)=>{const s=t.find(t=>t.tagName===e);if(!s)return;const n=E(s,a);await u(s.fqdn,n)},[t,u,E]),$=useCallback(async e=>{const a=t.find(t=>t.tagName===e);a&&"boolean"===a.valueType&&(await u(a.fqdn,!0),await sleep(300),await u(a.fqdn,!1))},[t,u]),V=useCallback(async(e,t,a)=>{const s=v[e];if(s){if(s.serverTag){const n=`${s.serverTag.domain}.${s.serverTag.symbolName}`;await u(n,{name:e,scale:t,label:a})}C(s=>({...s,[e]:{...s[e],scale:t,label:a}})),S(e)}},[v,u,S]);useEffect(()=>{p(e=>{const a={...e};for(const e of t){const t=h.current[e.tagName];if(void 0===t)continue;const s=x(e,t);a[e.tagName]!==s&&(a[e.tagName]=s)}return a})},[t,x,v]);const A=useMemo(()=>({values:d,rawValues:y,isLoading:w,write:R,tap:$,scales:v,updateScale:V}),[d,y,w,R,$,v,V]);return _jsx(AutoCoreTagContext.Provider,{value:A,children:e})};
|