@adcops/autocore-react 3.0.40 → 3.3.2

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 (296) hide show
  1. package/additional-docs/AutoCoreTagContext.md +441 -0
  2. package/additional-docs/react_performance_notes.md +94 -0
  3. package/dist/assets/BlocklyLogo.d.ts +1 -0
  4. package/dist/assets/BlocklyLogo.d.ts.map +1 -0
  5. package/dist/assets/Distance.d.ts +1 -0
  6. package/dist/assets/Distance.d.ts.map +1 -0
  7. package/dist/assets/JogLong.d.ts +1 -0
  8. package/dist/assets/JogLong.d.ts.map +1 -0
  9. package/dist/assets/JogMedium.d.ts +1 -0
  10. package/dist/assets/JogMedium.d.ts.map +1 -0
  11. package/dist/assets/JogShort.d.ts +1 -0
  12. package/dist/assets/JogShort.d.ts.map +1 -0
  13. package/dist/assets/PythonLogo.d.ts +1 -0
  14. package/dist/assets/PythonLogo.d.ts.map +1 -0
  15. package/dist/assets/Rotation3D.d.ts +1 -0
  16. package/dist/assets/Rotation3D.d.ts.map +1 -0
  17. package/dist/assets/RotationCcw.d.ts +1 -0
  18. package/dist/assets/RotationCcw.d.ts.map +1 -0
  19. package/dist/assets/RotationCcwA.d.ts +1 -0
  20. package/dist/assets/RotationCcwA.d.ts.map +1 -0
  21. package/dist/assets/RotationCcwB.d.ts +1 -0
  22. package/dist/assets/RotationCcwB.d.ts.map +1 -0
  23. package/dist/assets/RotationCcwC.d.ts +1 -0
  24. package/dist/assets/RotationCcwC.d.ts.map +1 -0
  25. package/dist/assets/RotationCw.d.ts +1 -0
  26. package/dist/assets/RotationCw.d.ts.map +1 -0
  27. package/dist/assets/RotationCwA.d.ts +1 -0
  28. package/dist/assets/RotationCwA.d.ts.map +1 -0
  29. package/dist/assets/RotationCwB.d.ts +1 -0
  30. package/dist/assets/RotationCwB.d.ts.map +1 -0
  31. package/dist/assets/RotationCwC.d.ts +1 -0
  32. package/dist/assets/RotationCwC.d.ts.map +1 -0
  33. package/dist/assets/Run.d.ts +1 -0
  34. package/dist/assets/Run.d.ts.map +1 -0
  35. package/dist/assets/Speed.d.ts +1 -0
  36. package/dist/assets/Speed.d.ts.map +1 -0
  37. package/dist/assets/SpeedFast.d.ts +1 -0
  38. package/dist/assets/SpeedFast.d.ts.map +1 -0
  39. package/dist/assets/SpeedMedium.d.ts +1 -0
  40. package/dist/assets/SpeedMedium.d.ts.map +1 -0
  41. package/dist/assets/SpeedNone.d.ts +1 -0
  42. package/dist/assets/SpeedNone.d.ts.map +1 -0
  43. package/dist/assets/SpeedSlow.d.ts +1 -0
  44. package/dist/assets/SpeedSlow.d.ts.map +1 -0
  45. package/dist/assets/Walk.d.ts +1 -0
  46. package/dist/assets/Walk.d.ts.map +1 -0
  47. package/dist/assets/index.d.ts +1 -0
  48. package/dist/assets/index.d.ts.map +1 -0
  49. package/dist/components/AutoCoreDevPanel.d.ts +144 -0
  50. package/dist/components/AutoCoreDevPanel.d.ts.map +1 -0
  51. package/dist/components/AutoCoreDevPanel.js +1 -0
  52. package/dist/components/BlocklyEditor.d.ts +1 -0
  53. package/dist/components/BlocklyEditor.d.ts.map +1 -0
  54. package/dist/components/BlocklyEditor.js +1 -1
  55. package/dist/components/CodeEditor.d.ts +2 -1
  56. package/dist/components/CodeEditor.d.ts.map +1 -0
  57. package/dist/components/CodeEditor.js +1 -1
  58. package/dist/components/FileList.d.ts +1 -0
  59. package/dist/components/FileList.d.ts.map +1 -0
  60. package/dist/components/FileList.js +1 -1
  61. package/dist/components/FileSelect.d.ts +1 -0
  62. package/dist/components/FileSelect.d.ts.map +1 -0
  63. package/dist/components/FileSelect.js +1 -1
  64. package/dist/components/FitText.d.ts +1 -0
  65. package/dist/components/FitText.d.ts.map +1 -0
  66. package/dist/components/FitText.js +1 -1
  67. package/dist/components/Indicator.d.ts +2 -1
  68. package/dist/components/Indicator.d.ts.map +1 -0
  69. package/dist/components/Indicator.js +1 -1
  70. package/dist/components/IndicatorButton.d.ts +2 -1
  71. package/dist/components/IndicatorButton.d.ts.map +1 -0
  72. package/dist/components/IndicatorButton.js +1 -1
  73. package/dist/components/IndicatorRect.d.ts +2 -1
  74. package/dist/components/IndicatorRect.d.ts.map +1 -0
  75. package/dist/components/JogPanel.d.ts +1 -0
  76. package/dist/components/JogPanel.d.ts.map +1 -0
  77. package/dist/components/Lamp.d.ts +2 -1
  78. package/dist/components/Lamp.d.ts.map +1 -0
  79. package/dist/components/Lamp.js +1 -1
  80. package/dist/components/Osk.d.ts +1 -0
  81. package/dist/components/Osk.d.ts.map +1 -0
  82. package/dist/components/Osk.js +1 -1
  83. package/dist/components/OskDialog.d.ts +1 -0
  84. package/dist/components/OskDialog.d.ts.map +1 -0
  85. package/dist/components/ProgressBarWithValue.d.ts +1 -0
  86. package/dist/components/ProgressBarWithValue.d.ts.map +1 -0
  87. package/dist/components/ProgressBarWithValue.js +1 -1
  88. package/dist/components/TextInput.d.ts +62 -103
  89. package/dist/components/TextInput.d.ts.map +1 -0
  90. package/dist/components/TextInput.js +1 -1
  91. package/dist/components/ToggleGroup.d.ts +2 -1
  92. package/dist/components/ToggleGroup.d.ts.map +1 -0
  93. package/dist/components/ToggleGroup.js +1 -1
  94. package/dist/components/ValueDisplay.d.ts +3 -2
  95. package/dist/components/ValueDisplay.d.ts.map +1 -0
  96. package/dist/components/ValueDisplay.js +1 -1
  97. package/dist/components/ValueIndicator.d.ts +2 -1
  98. package/dist/components/ValueIndicator.d.ts.map +1 -0
  99. package/dist/components/ValueIndicator.js +1 -1
  100. package/dist/components/ValueInput.d.ts +2 -1
  101. package/dist/components/ValueInput.d.ts.map +1 -0
  102. package/dist/components/ValueInput.js +1 -1
  103. package/dist/core/ActionMode.d.ts +1 -0
  104. package/dist/core/ActionMode.d.ts.map +1 -0
  105. package/dist/core/AutoCoreTagContext.d.ts +98 -0
  106. package/dist/core/AutoCoreTagContext.d.ts.map +1 -0
  107. package/dist/core/AutoCoreTagContext.js +1 -0
  108. package/dist/core/AutoCoreTagTypes.d.ts +283 -0
  109. package/dist/core/AutoCoreTagTypes.d.ts.map +1 -0
  110. package/dist/core/AutoCoreTagTypes.js +1 -0
  111. package/dist/core/CoreStreamTypes.d.ts +345 -0
  112. package/dist/core/CoreStreamTypes.d.ts.map +1 -0
  113. package/dist/core/CoreStreamTypes.js +1 -0
  114. package/dist/core/EventEmitterContext.d.ts +113 -202
  115. package/dist/core/EventEmitterContext.d.ts.map +1 -0
  116. package/dist/core/EventEmitterContext.js +1 -1
  117. package/dist/core/IndicatorButtonState.d.ts +1 -0
  118. package/dist/core/IndicatorButtonState.d.ts.map +1 -0
  119. package/dist/core/IndicatorColor.d.ts +1 -0
  120. package/dist/core/IndicatorColor.d.ts.map +1 -0
  121. package/dist/core/MaskPatterns.d.ts +1 -0
  122. package/dist/core/MaskPatterns.d.ts.map +1 -0
  123. package/dist/core/NumerableTypes.d.ts +1 -0
  124. package/dist/core/NumerableTypes.d.ts.map +1 -0
  125. package/dist/core/NumerableTypes.js +1 -1
  126. package/dist/core/PositionContext.d.ts +1 -1
  127. package/dist/core/PositionContext.d.ts.map +1 -0
  128. package/dist/core/UniqueId.d.ts +1 -0
  129. package/dist/core/UniqueId.d.ts.map +1 -0
  130. package/dist/core/ValueSimulator.d.ts +2 -1
  131. package/dist/core/ValueSimulator.d.ts.map +1 -0
  132. package/dist/core/ValueSimulator.js +1 -1
  133. package/dist/core/hoc.d.ts +1 -0
  134. package/dist/core/hoc.d.ts.map +1 -0
  135. package/dist/core/hoc.js +1 -1
  136. package/dist/hooks/adsHooks.d.ts +1 -0
  137. package/dist/hooks/adsHooks.d.ts.map +1 -0
  138. package/dist/hooks/adsHooks.js +1 -1
  139. package/dist/hooks/commandHooks.d.ts +4 -3
  140. package/dist/hooks/commandHooks.d.ts.map +1 -0
  141. package/dist/hooks/commandHooks.js +1 -1
  142. package/dist/hooks/index.d.ts +1 -0
  143. package/dist/hooks/index.d.ts.map +1 -0
  144. package/dist/hooks/useAutoCoreTag.d.ts +26 -0
  145. package/dist/hooks/useAutoCoreTag.d.ts.map +1 -0
  146. package/dist/hooks/useAutoCoreTag.js +1 -0
  147. package/dist/hooks/useScaledValue.d.ts +1 -0
  148. package/dist/hooks/useScaledValue.d.ts.map +1 -0
  149. package/dist/hooks/useScaledValue.js +1 -1
  150. package/dist/hub/CommandMessage.d.ts +19 -9
  151. package/dist/hub/CommandMessage.d.ts.map +1 -0
  152. package/dist/hub/CommandMessage.js +1 -1
  153. package/dist/hub/DebugPanel.d.ts +31 -0
  154. package/dist/hub/DebugPanel.d.ts.map +1 -0
  155. package/dist/hub/DebugPanel.js +1 -0
  156. package/dist/hub/HubBase.d.ts +85 -130
  157. package/dist/hub/HubBase.d.ts.map +1 -0
  158. package/dist/hub/HubBase.js +1 -1
  159. package/dist/hub/HubSimulate.d.ts +42 -8
  160. package/dist/hub/HubSimulate.d.ts.map +1 -0
  161. package/dist/hub/HubSimulate.js +1 -1
  162. package/dist/hub/HubTauri.d.ts +25 -60
  163. package/dist/hub/HubTauri.d.ts.map +1 -0
  164. package/dist/hub/HubTauri.js +1 -1
  165. package/dist/hub/HubWebSocket.d.ts +34 -17
  166. package/dist/hub/HubWebSocket.d.ts.map +1 -0
  167. package/dist/hub/HubWebSocket.js +1 -1
  168. package/dist/hub/debug.d.ts +23 -0
  169. package/dist/hub/debug.d.ts.map +1 -0
  170. package/dist/hub/debug.js +1 -0
  171. package/dist/hub/index.d.ts +20 -4
  172. package/dist/hub/index.d.ts.map +1 -0
  173. package/dist/hub/index.js +1 -1
  174. package/package.json +32 -27
  175. package/readme.md +193 -22
  176. package/src/components/AutoCoreDevPanel.tsx +414 -0
  177. package/src/components/CodeEditor.tsx +2 -2
  178. package/src/components/FileList.tsx +7 -6
  179. package/src/components/FileSelect.tsx +2 -1
  180. package/src/components/Indicator.tsx +2 -2
  181. package/src/components/IndicatorButton.tsx +2 -2
  182. package/src/components/IndicatorRect.tsx +2 -2
  183. package/src/components/Lamp.tsx +3 -3
  184. package/src/components/TextInput.tsx +159 -240
  185. package/src/components/ToggleGroup.tsx +3 -3
  186. package/src/components/ValueDisplay.tsx +4 -4
  187. package/src/components/ValueIndicator.tsx +2 -2
  188. package/src/components/ValueInput.tsx +2 -2
  189. package/src/core/ActionMode.ts +1 -1
  190. package/src/core/AutoCoreTagContext.tsx +615 -0
  191. package/src/core/AutoCoreTagTypes.ts +334 -0
  192. package/src/core/CoreStreamTypes.ts +512 -0
  193. package/src/core/EventEmitterContext.tsx +257 -281
  194. package/src/core/IndicatorButtonState.ts +1 -1
  195. package/src/core/ValueSimulator.ts +2 -2
  196. package/src/core/hoc.tsx +1 -1
  197. package/src/hooks/adsHooks.tsx +21 -22
  198. package/src/hooks/commandHooks.tsx +23 -19
  199. package/src/hooks/index.ts +1 -1
  200. package/src/hooks/useAutoCoreTag.ts +103 -0
  201. package/src/hooks/useScaledValue.tsx +1 -1
  202. package/src/hub/CommandMessage.ts +71 -19
  203. package/src/hub/DebugPanel.ts +280 -0
  204. package/src/hub/HubBase.ts +147 -223
  205. package/src/hub/HubSimulate.ts +93 -24
  206. package/src/hub/HubTauri.ts +87 -96
  207. package/src/hub/HubWebSocket.ts +133 -158
  208. package/src/hub/debug.ts +211 -0
  209. package/src/hub/index.ts +49 -39
  210. package/tsconfig.json +43 -28
  211. package/docs/classes/components_BlocklyEditor.BlocklyEditor.html +0 -124
  212. package/docs/classes/components_CodeEditor.CodeEditor.html +0 -128
  213. package/docs/classes/components_JogPanel.JogPanel.html +0 -138
  214. package/docs/classes/components_Lamp.Lamp.html +0 -105
  215. package/docs/classes/components_TextInput.TextInput.html +0 -115
  216. package/docs/classes/components_ValueIndicator.ValueIndicator.html +0 -119
  217. package/docs/classes/components_ValueInput.ValueInput.html +0 -113
  218. package/docs/classes/hub_HubWebSocket.HubWebSocket.html +0 -106
  219. package/docs/enums/components_JogPanel.JogDistanceAction.html +0 -5
  220. package/docs/enums/components_JogPanel.JogPanelAction.html +0 -18
  221. package/docs/enums/components_JogPanel.JogSpeedAction.html +0 -5
  222. package/docs/enums/core_ActionMode.ActionMode.html +0 -6
  223. package/docs/enums/core_IndicatorColor.IndicatorColor.html +0 -23
  224. package/docs/functions/assets_BlocklyLogo.default.html +0 -1
  225. package/docs/functions/assets_Distance.default.html +0 -1
  226. package/docs/functions/assets_JogLong.default.html +0 -1
  227. package/docs/functions/assets_JogMedium.default.html +0 -1
  228. package/docs/functions/assets_JogShort.default.html +0 -1
  229. package/docs/functions/assets_PythonLogo.default.html +0 -1
  230. package/docs/functions/assets_Rotation3D.default.html +0 -1
  231. package/docs/functions/assets_RotationCcw.default.html +0 -1
  232. package/docs/functions/assets_RotationCcwA.default.html +0 -1
  233. package/docs/functions/assets_RotationCcwB.default.html +0 -1
  234. package/docs/functions/assets_RotationCcwC.default.html +0 -1
  235. package/docs/functions/assets_RotationCw.default.html +0 -1
  236. package/docs/functions/assets_RotationCwA.default.html +0 -1
  237. package/docs/functions/assets_RotationCwB.default.html +0 -1
  238. package/docs/functions/assets_RotationCwC.default.html +0 -1
  239. package/docs/functions/assets_Run.default.html +0 -1
  240. package/docs/functions/assets_Speed.default.html +0 -1
  241. package/docs/functions/assets_SpeedFast.default.html +0 -1
  242. package/docs/functions/assets_SpeedMedium.default.html +0 -1
  243. package/docs/functions/assets_SpeedNone.default.html +0 -1
  244. package/docs/functions/assets_SpeedSlow.default.html +0 -1
  245. package/docs/functions/assets_Walk.default.html +0 -1
  246. package/docs/functions/components_BlocklyEditor.createCustomToolbox.html +0 -6
  247. package/docs/functions/components_FileList.FileList.html +0 -21
  248. package/docs/functions/components_FitText.FitText.html +0 -8
  249. package/docs/functions/components_ToggleGroup.ToggleGroup.html +0 -5
  250. package/docs/interfaces/components_JogPanel.JogPanelButtonDefinition.html +0 -5
  251. package/docs/interfaces/components_ToggleGroup.ToggleGroupProps.html +0 -618
  252. package/docs/interfaces/core_IndicatorButtonState.IndicatorButtonState.html +0 -10
  253. package/docs/interfaces/hub_CommandMessage.CommandMessage.html +0 -6
  254. package/docs/interfaces/hub_CommandMessage.CommandMessageResult.html +0 -4
  255. package/docs/modules/assets.html +0 -23
  256. package/docs/modules/assets_BlocklyLogo.html +0 -2
  257. package/docs/modules/assets_Distance.html +0 -2
  258. package/docs/modules/assets_JogLong.html +0 -2
  259. package/docs/modules/assets_JogMedium.html +0 -2
  260. package/docs/modules/assets_JogShort.html +0 -2
  261. package/docs/modules/assets_PythonLogo.html +0 -2
  262. package/docs/modules/assets_Rotation3D.html +0 -2
  263. package/docs/modules/assets_RotationCcw.html +0 -2
  264. package/docs/modules/assets_RotationCcwA.html +0 -2
  265. package/docs/modules/assets_RotationCcwB.html +0 -2
  266. package/docs/modules/assets_RotationCcwC.html +0 -2
  267. package/docs/modules/assets_RotationCw.html +0 -2
  268. package/docs/modules/assets_RotationCwA.html +0 -2
  269. package/docs/modules/assets_RotationCwB.html +0 -2
  270. package/docs/modules/assets_RotationCwC.html +0 -2
  271. package/docs/modules/assets_Run.html +0 -2
  272. package/docs/modules/assets_Speed.html +0 -2
  273. package/docs/modules/assets_SpeedFast.html +0 -2
  274. package/docs/modules/assets_SpeedMedium.html +0 -2
  275. package/docs/modules/assets_SpeedNone.html +0 -2
  276. package/docs/modules/assets_SpeedSlow.html +0 -2
  277. package/docs/modules/assets_Walk.html +0 -2
  278. package/docs/modules/components_BlocklyEditor.html +0 -5
  279. package/docs/modules/components_CodeEditor.html +0 -3
  280. package/docs/modules/components_FileList.html +0 -3
  281. package/docs/modules/components_FitText.html +0 -3
  282. package/docs/modules/components_JogPanel.html +0 -9
  283. package/docs/modules/components_Lamp.html +0 -4
  284. package/docs/modules/components_TextInput.html +0 -2
  285. package/docs/modules/components_ToggleGroup.html +0 -6
  286. package/docs/modules/components_ValueIndicator.html +0 -4
  287. package/docs/modules/components_ValueInput.html +0 -2
  288. package/docs/modules/core_ActionMode.html +0 -2
  289. package/docs/modules/core_IndicatorButtonState.html +0 -2
  290. package/docs/modules/core_IndicatorColor.html +0 -2
  291. package/docs/modules/hub_CommandMessage.html +0 -3
  292. package/docs/modules/hub_HubWebSocket.html +0 -2
  293. package/docs/types/components_IndicatorButton.IndicatorButtonOptionsType.html +0 -1
  294. package/docs/variables/components_BlocklyEditor.StandardToolbox.html +0 -1
  295. package/docs/variables/components_JogPanel.DefaultLinearJogButtons.html +0 -2
  296. package/docs/variables/components_JogPanel.DefaultRotationJogButtons.html +0 -2
@@ -0,0 +1,615 @@
1
+ /*
2
+ * Copyright (C) 2025 Automated Design Corp.. All Rights Reserved.
3
+ * Created Date: 2025-09-05 07:35:46
4
+ * -----
5
+ * Last Modified: 2026-01-29 09:31:42
6
+ * Modified By: ADC
7
+ * -----
8
+ *
9
+ */
10
+
11
+ /**
12
+ * @module core/AutoCoreTagContext
13
+ *
14
+ * @document ../../additional-docs/AutCoreTagContext.md
15
+ *
16
+ * @summary
17
+ * A React Context + Provider that manages the state and synchronization of "Tags" (data points)
18
+ * between the React frontend and the AutoCore backend.
19
+ *
20
+ * It acts as the central data store for real-time values, handling:
21
+ * - **Buffering**: Stores raw controller values exactly as received.
22
+ * - **Scaling**: Converts raw values to display values (e.g., mm -> inches) based on configuration.
23
+ * - **Synchronization**: Subscribes to live updates from the server.
24
+ * - **Writing**: Converts display values back to raw controller units before sending.
25
+ *
26
+ * @remarks
27
+ * ❖ **Dual-State Representation**
28
+ * To prevent rounding errors and race conditions, we maintain two parallel states:
29
+ * 1. `rawValues[tagName]`: The exact value received from the controller (Source of Truth).
30
+ * 2. `values[tagName]`: The derived value shown in the UI (calculated from raw + scale).
31
+ *
32
+ * When a scale changes (e.g., user switches units), we recompute `values` from `rawValues`.
33
+ * We *never* rescale a previously scaled value.
34
+ *
35
+ * ❖ **Key Features**
36
+ * - **Resilient Initialization**: Eagerly fetches initial values (via ADS refresh or read fallback).
37
+ * - **Server-Stored Scales**: Can load unit preferences from the server (GNV domain).
38
+ * - **Optimized Updates**: Uses stable references and memoization to minimize React re-renders.
39
+ * - **Type Safety**: Strongly typed context via generic `VMapRuntime`.
40
+ *
41
+ * @example Minimal wiring
42
+ * ```tsx
43
+ * // 1. Define your tags
44
+ * const tags = [
45
+ * { tagName: "position", fqdn: "ADS.Axis.Pos", valueType: "number", scale: "dist" },
46
+ * { tagName: "speed", fqdn: "ADS.Axis.Vel", valueType: "number", scale: "dist" }
47
+ * ];
48
+ *
49
+ * // 2. Define scales
50
+ * const scales = {
51
+ * dist: { name: "dist", scale: 1.0, label: "mm" }
52
+ * };
53
+ *
54
+ * // 3. Wrap application
55
+ * <AutoCoreTagProvider tags={tags} scales={scales}>
56
+ * <App />
57
+ * </AutoCoreTagProvider>
58
+ * ```
59
+ */
60
+
61
+ import React, {
62
+ useRef,
63
+ createContext,
64
+ useCallback,
65
+ useContext,
66
+ useEffect,
67
+ useMemo,
68
+ useState,
69
+ type ReactNode,
70
+ } from "react";
71
+ import { EventEmitterContext } from "./EventEmitterContext";
72
+ import type {
73
+ BaseContextValue,
74
+ TagConfig,
75
+ ScaleConfig
76
+ } from "./AutoCoreTagTypes";
77
+
78
+ import { MessageType } from "../hub/CommandMessage";
79
+
80
+ /**
81
+ * Runtime type for the values map - allows any tag name to map to any value type.
82
+ * This is the generic type used when the specific tag configuration is not known at compile time.
83
+ */
84
+ type VMapRuntime = Record<string, unknown>;
85
+
86
+ /**
87
+ * The React Context that holds all AutoCore tag state and operations.
88
+ *
89
+ * This context provides:
90
+ * - `values`: Display values (with scaling/codecs applied)
91
+ * - `rawValues`: Raw controller values as received from server
92
+ * - `isLoading`: Whether initial data loading is complete
93
+ * - `write`: Function to write a value to a tag
94
+ * - `tap`: Function to pulse a boolean tag (true → delay → false)
95
+ * - `scales`: Current scale configurations
96
+ * - `updateScale`: Function to change a scale factor/label
97
+ *
98
+ * @see AutoCoreTagProvider for the provider implementation
99
+ * @see makeAutoCoreTagHooks for creating typed hooks from this context
100
+ */
101
+ export const AutoCoreTagContext = createContext<BaseContextValue<VMapRuntime>>({
102
+ values: {},
103
+ rawValues: {},
104
+ isLoading: true,
105
+ write: async () => { },
106
+ tap: async () => { },
107
+ scales: {},
108
+ updateScale: async () => { },
109
+ });
110
+
111
+ /**
112
+ * Standard response envelope from the AutoCore server.
113
+ * All server responses follow this structure.
114
+ *
115
+ * @template T - The type of data contained in the response
116
+ * @property success - Whether the operation succeeded
117
+ * @property valid - Whether the response data is valid
118
+ * @property data - The actual response payload
119
+ */
120
+ type HubEnvelope<T> = { success?: boolean; valid?: boolean; data?: T };
121
+
122
+ /**
123
+ * Utility function to create a promise that resolves after a specified delay.
124
+ * Used for tap() pulse timing and retry delays.
125
+ *
126
+ * @param ms - Number of milliseconds to sleep
127
+ * @returns Promise that resolves after the delay
128
+ */
129
+ const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
130
+
131
+ /**
132
+ * Extracts the domain (first segment) from a Fully-Qualified Domain Name.
133
+ * @param fqdn - The full topic path (e.g., "ads.plc1.gio.bControlPowerOk")
134
+ * @returns The domain segment (e.g., "ads")
135
+ */
136
+
137
+
138
+
139
+ /**
140
+ * AutoCoreTagProvider
141
+ *
142
+ * @description
143
+ * React Provider that wires up AutoCore tags to live server updates, buffers **raw**
144
+ * controller values, exposes **display** values (scales/codec applied), and handles
145
+ * writes/taps with proper domain envelopes. Scaling is **always** applied to display
146
+ * values only; raw values remain untouched and are the single source of truth.
147
+ *
148
+ * @param props.tags - List of tag configurations (tagName, fqdn, etc.)
149
+ * @param props.scales - Map of scale definitions (name, factor, label)
150
+ * @param props.eagerRead - If true, automatically fetches initial values on mount
151
+ */
152
+ export const AutoCoreTagProvider: React.FC<{
153
+ children: ReactNode;
154
+ tags: readonly TagConfig[];
155
+ scales?: Record<string, ScaleConfig>;
156
+ eagerRead?: boolean;
157
+ }> = ({ children, tags, scales, eagerRead = true }) => {
158
+ const startedRef = useRef(false);
159
+
160
+ // PERFORMANCE: Memoize default scales to ensure reference stability.
161
+ // If 'scales' prop is undefined, we use this stable empty object.
162
+ // This prevents infinite re-render loops caused by creating a new object on every render.
163
+ const defaultScales = useMemo<Record<string, ScaleConfig>>(() => ({}), []);
164
+ const actualScales: Record<string, ScaleConfig> = scales ?? defaultScales;
165
+
166
+ const { invoke, read, write: hubWrite, serverSubscribe, isConnected, subscribe, unsubscribe } =
167
+ useContext(EventEmitterContext);
168
+
169
+ /**
170
+ * Raw (controller) values as received from the server (pre-scale, pre-codec).
171
+ * This is the single source of truth for value data.
172
+ * Seeded with `initialValue` from tag config if available.
173
+ * Keyed by `tagName`.
174
+ */
175
+ const [rawValues, setRawValues] = useState<Record<string, unknown>>(() => {
176
+ const seed: Record<string, unknown> = {};
177
+ for (const t of tags) {
178
+ if (t.initialValue !== undefined) {
179
+ // Interpret initialValue as **raw**.
180
+ seed[t.tagName] = t.initialValue;
181
+ }
182
+ }
183
+ return seed;
184
+ });
185
+
186
+ /**
187
+ * App-visible display values, computed from raw using scales/codecs.
188
+ * This is what the UI consumes.
189
+ * Keyed by `tagName`.
190
+ */
191
+ const [values, setValues] = useState<Record<string, unknown>>({});
192
+
193
+ /**
194
+ * Current state of scales (factor/label).
195
+ * Can be updated by the server or user interaction.
196
+ */
197
+ const [scaleValues, setScaleValues] =
198
+ useState<Record<string, ScaleConfig>>(actualScales);
199
+
200
+ /**
201
+ * Refs to access latest state inside callbacks without adding them to dependency arrays.
202
+ * This helps prevent re-subscribing when state changes.
203
+ */
204
+ const scaleRef = useRef(scaleValues);
205
+ const rawRef = useRef(rawValues);
206
+ useEffect(() => { scaleRef.current = scaleValues; }, [scaleValues]);
207
+ useEffect(() => { rawRef.current = rawValues; }, [rawValues]);
208
+
209
+ const [isLoading, setIsLoading] = useState(true);
210
+
211
+ /**
212
+ * Converts a raw controller value to a display value.
213
+ *
214
+ * Pipeline: Raw -> [Codec Decode] -> [Scale Multiply] -> Display
215
+ */
216
+ const toDisplay = useCallback((tag: TagConfig, raw: unknown): unknown => {
217
+ const { valueType, scale, codec } = tag;
218
+
219
+ // 1) numeric scaling: multiply raw by scale factor
220
+ if (valueType === "number" && typeof raw === "number" && scale) {
221
+ const s = scaleRef.current[scale];
222
+ const factor = s?.scale ?? 1;
223
+ return raw * factor;
224
+ }
225
+
226
+ // 2) codec for json (optional): decode server representation
227
+ if (valueType === "json" && codec?.fromServer) {
228
+ try { return codec.fromServer(raw); } catch { /* fall through */ }
229
+ }
230
+
231
+ // 3) pass-through: no transformation needed
232
+ return raw;
233
+ }, []);
234
+
235
+ /**
236
+ * Converts a display value back to a raw controller value.
237
+ *
238
+ * Pipeline: Display -> [Scale Divide] -> [Codec Encode] -> Raw
239
+ */
240
+ const toServer = useCallback((tag: TagConfig, display: unknown): unknown => {
241
+ const { valueType, scale, codec } = tag;
242
+
243
+ // 1) invert codec first (json): encode for server
244
+ if (valueType === "json" && codec?.toServer) {
245
+ try { display = codec.toServer(display as any); } catch { /* fall through */ }
246
+ }
247
+
248
+ // 2) inverse numeric scaling: divide by scale factor
249
+ if (valueType === "number" && typeof display === "number" && scale) {
250
+ const s = scaleRef.current[scale];
251
+ const factor = s?.scale ?? 1;
252
+ return display / factor;
253
+ }
254
+
255
+ return display;
256
+ }, []);
257
+
258
+ /**
259
+ * Recomputes display values for all tags in a specific scale group.
260
+ * Called when a scale factor changes.
261
+ *
262
+ * CRITICAL: Computes from `rawValues` (Source of Truth), not existing `values`.
263
+ * This prevents floating point error accumulation from repeated scaling.
264
+ */
265
+ const rescaleFromRaw = useCallback((scaleName: string) => {
266
+ const affected = tags.filter(t => t.scale === scaleName);
267
+ if (!affected.length) return;
268
+
269
+ setValues(prev => {
270
+ const next = { ...prev };
271
+ for (const tag of affected) {
272
+ const raw = rawRef.current[tag.tagName];
273
+ if (typeof raw !== "number") continue;
274
+ const disp = toDisplay(tag, raw);
275
+ if (next[tag.tagName] !== disp) next[tag.tagName] = disp;
276
+ }
277
+ return next;
278
+ });
279
+ }, [tags, toDisplay]);
280
+
281
+ /**
282
+ * Handles incoming value updates from the server.
283
+ *
284
+ * 1. Updates `rawValues` (Source of Truth).
285
+ * 2. Computes and updates `values` (Display).
286
+ * 3. Uses functional state updates to ensure atomicity.
287
+ * 4. Keys by `tagName` for consistent access by hooks.
288
+ */
289
+ const handleTagUpdate = useCallback((tag: TagConfig, raw: unknown) => {
290
+ // Store the raw controller value (source of truth for scaling)
291
+ setRawValues(prev =>
292
+ prev[tag.tagName] === raw ? prev : { ...prev, [tag.tagName]: raw }
293
+ );
294
+ // Compute and store the display value (with scaling/codecs applied)
295
+ const display = toDisplay(tag, raw);
296
+ setValues(prev =>
297
+ prev[tag.tagName] === display ? prev : { ...prev, [tag.tagName]: display }
298
+ );
299
+ }, [toDisplay]);
300
+
301
+ /**
302
+ * Eagerly fetches initial values for non-ADS domains (e.g., MODBUS).
303
+ * Uses a worker pool to fetch in parallel while respecting rate limits.
304
+ */
305
+ const eagerPullNonADS = useCallback(
306
+ async (
307
+ domain: string,
308
+ tagList: readonly TagConfig[],
309
+ opts?: { concurrency?: number; minDelayMs?: number; jitterMs?: number }
310
+ ) => {
311
+ const concurrency = opts?.concurrency ?? 4;
312
+ const minDelayMs = opts?.minDelayMs ?? 20;
313
+ const jitterMs = opts?.jitterMs ?? 40;
314
+
315
+ let index = 0;
316
+
317
+ const workers = Array.from({ length: concurrency }, () => (async () => {
318
+ while (true) {
319
+ const i = index++;
320
+ if (i >= tagList.length) return;
321
+ const tag = tagList[i];
322
+
323
+ try {
324
+ // Try 'refresh' first (server broadcast), else 'read_value'
325
+ let usedPublishPath = false;
326
+ try {
327
+ await invoke(tag.fqdn, MessageType.Request, { action: "refresh" });
328
+ usedPublishPath = true;
329
+ } catch { /* fall through */ }
330
+
331
+ if (!usedPublishPath) {
332
+ const payload = domain.toUpperCase() === "GNV" ? { group: "ux" } : {};
333
+ try {
334
+ const resp: any = await read(tag.fqdn, payload);
335
+ if (resp?.success) {
336
+ handleTagUpdate(tag, resp.data);
337
+ }
338
+ } catch (err) { /* ignore missing keys on init */ }
339
+ }
340
+ } catch (outerErr) { /* ignore */ }
341
+ finally {
342
+ const extra = Math.floor(Math.random() * jitterMs);
343
+ await sleep(minDelayMs + extra);
344
+ }
345
+ }
346
+ })());
347
+
348
+ await Promise.all(workers);
349
+ },
350
+ [invoke, read, handleTagUpdate]
351
+ );
352
+
353
+ /**
354
+ * Reads scale configurations from the server (if configured).
355
+ * Ensures display units match the server's persisted configuration.
356
+ */
357
+ const pullServerScales = useCallback(async () => {
358
+ // Use actualScales to ensure we iterate over a stable object
359
+ for (const [scaleName, cfg] of Object.entries(actualScales)) {
360
+ if (!cfg.serverTag) continue;
361
+ const { domain, symbolName } = cfg.serverTag;
362
+
363
+ try {
364
+ const respUnknown = await read(`${domain}.${symbolName}`, { group: "ux" });
365
+ const env = respUnknown as HubEnvelope<string>;
366
+
367
+ if (env.data && (env.success ?? true) && (env.valid ?? true)) {
368
+ const env_data = JSON.parse(env.data) as ScaleConfig;
369
+ if (env_data && typeof env_data.scale === "number" ) {
370
+ const { scale, label } = env_data;
371
+ setScaleValues(prev => ({
372
+ ...prev,
373
+ [scaleName]: {
374
+ ...prev[scaleName],
375
+ scale,
376
+ label: label ?? prev[scaleName]?.label ?? "---",
377
+ },
378
+ }));
379
+ rescaleFromRaw(scaleName);
380
+ }
381
+ }
382
+ } catch { /* Scale not present, use default */ }
383
+ }
384
+ }, [read, actualScales, rescaleFromRaw]);
385
+
386
+ /**
387
+ * ==========================================================================
388
+ * MAIN SUBSCRIPTION WIRING EFFECT
389
+ * ==========================================================================
390
+ * Orchestrates the initialization sequence:
391
+ * 1. Load server scales.
392
+ * 2. Subscribe to tags (Server & Local).
393
+ * 3. Perform one-shot reads (ADS refresh).
394
+ */
395
+ useEffect(() => {
396
+ let mounted = true;
397
+ const subscriptions: number[] = [];
398
+
399
+ const registerAndSubscribe = async () => {
400
+ try {
401
+ // 1. Load scales first so initial values are correct
402
+ await pullServerScales();
403
+
404
+ // 2. Subscribe to all tags
405
+ for (const tag of tags) {
406
+ console.log(`Subscribe on [${tag.tagName}] ${tag.fqdn} ...`);
407
+
408
+ // A. Server Subscription: Tell backend to start sending updates.
409
+ // We pass tagName so the Hub can map the FQDN back to the local name.
410
+ await serverSubscribe(tag.tagName, tag.fqdn, tag.subscriptionOptions);
411
+
412
+ // B. Local Subscription: Listen for updates on the Event Bus.
413
+ // The Hub will receive FQDN messages, map them to tagName, and dispatch.
414
+ const id = subscribe(tag.tagName, (data) => {
415
+ if (!mounted) return;
416
+ // data is the raw value payload directly
417
+ handleTagUpdate(tag, data);
418
+ });
419
+ subscriptions.push(id);
420
+ }
421
+
422
+ // // 3. ADS Refresh: Ask ADS to broadcast current values immediately.
423
+ // if (eagerRead) {
424
+ // await invoke("ADS.refresh", MessageType.Request, {});
425
+ // }
426
+
427
+ } finally {
428
+ if (mounted) setTimeout(() => mounted && setIsLoading(false), 100);
429
+ }
430
+ };
431
+
432
+ // Prevents double-execution in React Strict Mode
433
+ const safeRegister = async () => {
434
+ if (!mounted || startedRef.current) return;
435
+ startedRef.current = true;
436
+ try { await registerAndSubscribe(); }
437
+ finally { if (mounted) setTimeout(() => mounted && setIsLoading(false), 100); }
438
+ };
439
+
440
+ // Wait for connection before registering
441
+ if (!isConnected()) {
442
+ const id = subscribe("HUB/connected", () => {
443
+ unsubscribe(id);
444
+ void safeRegister();
445
+ });
446
+ subscriptions.push(id);
447
+ } else {
448
+ void safeRegister();
449
+ }
450
+
451
+ return () => {
452
+ mounted = false;
453
+ subscriptions.forEach(unsubscribe);
454
+ startedRef.current = false;
455
+ };
456
+ }, [subscribe, unsubscribe, isConnected, invoke, serverSubscribe, eagerRead, tags, pullServerScales, eagerPullNonADS, handleTagUpdate]);
457
+
458
+ /**
459
+ * Memoized list of scales that need server subscriptions.
460
+ * Used for the separate effect that listens for live scale changes.
461
+ */
462
+ const scaleServerSubs = useMemo(
463
+ () =>
464
+ Object.entries(actualScales)
465
+ .filter(([, cfg]) => cfg.serverTag)
466
+ .map(([scaleName, cfg]) => ({
467
+ scaleName,
468
+ domain: cfg.serverTag!.domain,
469
+ symbolName: cfg.serverTag!.symbolName,
470
+ })),
471
+ [actualScales]
472
+ );
473
+
474
+ /**
475
+ * Effect to listen for live scale changes from the server.
476
+ * If another user changes units, this updates our local state immediately.
477
+ */
478
+ useEffect(() => {
479
+ let mounted = true;
480
+ const subs: number[] = [];
481
+
482
+ for (const { scaleName, domain, symbolName } of scaleServerSubs) {
483
+ const id = subscribe(`${domain}.${symbolName}`, (data) => {
484
+ if (!mounted) return;
485
+ const v = data?.value; // Scales come as { value: { scale, label } } usually
486
+ if (v && typeof v === "object" && typeof (v as any).scale === "number") {
487
+ const { scale, label } = v as { scale: number; label?: string };
488
+ setScaleValues(prev => ({
489
+ ...prev,
490
+ [scaleName]: {
491
+ ...prev[scaleName],
492
+ scale,
493
+ label: label ?? prev[scaleName]?.label ?? "---",
494
+ },
495
+ }));
496
+ rescaleFromRaw(scaleName);
497
+ }
498
+ });
499
+ subs.push(id);
500
+ }
501
+
502
+ return () => {
503
+ mounted = false;
504
+ subs.forEach(unsubscribe);
505
+ };
506
+ }, [subscribe, unsubscribe, scaleServerSubs, rescaleFromRaw]);
507
+
508
+ /**
509
+ * Writes a value to the server.
510
+ * 1. Finds tag config by `tagName`.
511
+ * 2. Inverse-scales the value (Display -> Raw).
512
+ * 3. Sends update to server using `fqdn`.
513
+ */
514
+ const write = useCallback(
515
+ async (tagName: string, displayValue: unknown) => {
516
+ const cfg = tags.find((t) => t.tagName === tagName);
517
+ if (!cfg) {
518
+ console.error(`write(): unknown tag '${tagName}'`);
519
+ return;
520
+ }
521
+
522
+ const serverValue = toServer(cfg, displayValue);
523
+ await hubWrite(cfg.fqdn, serverValue);
524
+ },
525
+ [tags, hubWrite, toServer]
526
+ );
527
+
528
+ /**
529
+ * Momentary pulse for boolean tags (True -> Wait 300ms -> False).
530
+ */
531
+ const tap = useCallback(
532
+ async (tagName: string) => {
533
+ const cfg = tags.find((t) => t.tagName === tagName);
534
+ if (!cfg) {
535
+ console.error(`tap(): unknown tag '${tagName}'`);
536
+ return;
537
+ }
538
+ if (cfg.valueType !== "boolean") {
539
+ console.warn(`tap(): tag '${tagName}' is not a boolean type`);
540
+ return;
541
+ }
542
+
543
+ await hubWrite(cfg.fqdn, true);
544
+ await sleep(300);
545
+ await hubWrite(cfg.fqdn, false);
546
+ },
547
+ [tags, hubWrite]
548
+ );
549
+
550
+ /**
551
+ * Updates a scale factor locally and optionally persists to server.
552
+ */
553
+ const updateScale = useCallback(
554
+ async (scaleName: string, newScale: number, newLabel: string) => {
555
+ const cfg = scaleValues[scaleName];
556
+ if (!cfg) {
557
+ console.error(`Scale '${scaleName}' not found`);
558
+ return;
559
+ }
560
+
561
+ if (cfg.serverTag) {
562
+ const topic = `${cfg.serverTag.domain}.${cfg.serverTag.symbolName}`;
563
+ await hubWrite(topic, { name: scaleName, scale: newScale, label: newLabel });
564
+ }
565
+
566
+ setScaleValues(prev => ({
567
+ ...prev,
568
+ [scaleName]: { ...prev[scaleName], scale: newScale, label: newLabel },
569
+ }));
570
+
571
+ rescaleFromRaw(scaleName);
572
+ },
573
+ [scaleValues, hubWrite, rescaleFromRaw]
574
+ );
575
+
576
+ /**
577
+ * Safety net: Recomputes all display values whenever scales or tags change.
578
+ * Ensures UI consistency even if individual updates were missed.
579
+ */
580
+ useEffect(() => {
581
+ setValues(prev => {
582
+ const next = { ...prev };
583
+ for (const tag of tags) {
584
+ const raw = rawRef.current[tag.tagName];
585
+ if (raw === undefined) continue;
586
+ const disp = toDisplay(tag, raw);
587
+ if (next[tag.tagName] !== disp) next[tag.tagName] = disp;
588
+ }
589
+ return next;
590
+ });
591
+ }, [tags, toDisplay, scaleValues]);
592
+
593
+ /**
594
+ * Construct context value. Memoized to prevent consumers from re-rendering
595
+ * unless actual data changes.
596
+ */
597
+ const ctxValue = useMemo<BaseContextValue<VMapRuntime>>(
598
+ () => ({
599
+ values, // Display values (scaled)
600
+ rawValues, // Raw values (unscaled)
601
+ isLoading,
602
+ write,
603
+ tap,
604
+ scales: scaleValues,
605
+ updateScale,
606
+ }),
607
+ [values, rawValues, isLoading, write, tap, scaleValues, updateScale]
608
+ );
609
+
610
+ return (
611
+ <AutoCoreTagContext.Provider value={ctxValue}>
612
+ {children}
613
+ </AutoCoreTagContext.Provider>
614
+ );
615
+ };