@adcops/autocore-react 3.3.9 → 3.3.14

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 (223) hide show
  1. package/LICENSE +58 -58
  2. package/additional-docs/AutoCoreTagContext.md +441 -441
  3. package/additional-docs/ButtonApiSpecs.md +48 -48
  4. package/additional-docs/GlobalEventEmitter.md +243 -243
  5. package/additional-docs/general_recommendations.md +22 -22
  6. package/additional-docs/react_performance_notes.md +94 -94
  7. package/dist/assets/svg/blockly_logo.svg +82 -82
  8. package/dist/assets/svg/distance.svg +40 -40
  9. package/dist/assets/svg/python_logo.svg +246 -246
  10. package/dist/assets/svg/rotation_ccw.svg +50 -50
  11. package/dist/assets/svg/rotation_ccw_a.svg +57 -57
  12. package/dist/assets/svg/rotation_ccw_b.svg +57 -57
  13. package/dist/assets/svg/rotation_ccw_c.svg +57 -57
  14. package/dist/assets/svg/rotation_cw.svg +49 -49
  15. package/dist/assets/svg/rotation_cw_a.svg +30 -30
  16. package/dist/assets/svg/rotation_cw_b.svg +30 -30
  17. package/dist/assets/svg/rotation_cw_c.svg +30 -30
  18. package/dist/assets/svg/speed.svg +39 -39
  19. package/dist/components/BlocklyEditor.css +93 -93
  20. package/dist/components/Indicator.js +1 -1
  21. package/dist/components/IndicatorRect.d.ts.map +1 -1
  22. package/dist/components/IndicatorRect.js +1 -1
  23. package/dist/components/JogPanel.css +41 -41
  24. package/dist/components/ProgressBarWithValue.css +27 -27
  25. package/dist/components/TextInput.js +1 -1
  26. package/dist/components/ToggleGroup.js +1 -1
  27. package/dist/components/ValueIndicator.css +29 -31
  28. package/dist/components/ValueInput.js +1 -1
  29. package/dist/components/osk.css +123 -123
  30. package/dist/core/AutoCoreTagContext.d.ts.map +1 -1
  31. package/dist/core/AutoCoreTagContext.js +1 -1
  32. package/dist/hub/HubBase.d.ts +3 -3
  33. package/dist/hub/HubBase.d.ts.map +1 -1
  34. package/dist/hub/HubBase.js +1 -1
  35. package/dist/themes/adc-dark/blue/theme.css +3 -0
  36. package/dist/themes/adc-dark/blue/theme.css.map +1 -1
  37. package/package.json +104 -104
  38. package/readme.md +343 -343
  39. package/src/assets/BlocklyLogo.tsx +27 -27
  40. package/src/assets/Distance.tsx +18 -18
  41. package/src/assets/JogLong.tsx +13 -13
  42. package/src/assets/JogMedium.tsx +13 -13
  43. package/src/assets/JogShort.tsx +13 -13
  44. package/src/assets/PythonLogo.tsx +83 -83
  45. package/src/assets/Rotation3D.tsx +13 -13
  46. package/src/assets/RotationCcw.tsx +33 -33
  47. package/src/assets/RotationCcwA.tsx +45 -45
  48. package/src/assets/RotationCcwB.tsx +45 -45
  49. package/src/assets/RotationCcwC.tsx +45 -45
  50. package/src/assets/RotationCw.tsx +31 -31
  51. package/src/assets/RotationCwA.tsx +42 -42
  52. package/src/assets/RotationCwB.tsx +42 -42
  53. package/src/assets/RotationCwC.tsx +42 -42
  54. package/src/assets/Run.tsx +13 -13
  55. package/src/assets/Speed.tsx +18 -18
  56. package/src/assets/SpeedFast.tsx +13 -13
  57. package/src/assets/SpeedMedium.tsx +13 -13
  58. package/src/assets/SpeedNone.tsx +13 -13
  59. package/src/assets/SpeedSlow.tsx +13 -13
  60. package/src/assets/Walk.tsx +13 -13
  61. package/src/assets/index.ts +22 -22
  62. package/src/assets/svg/blockly_logo.svg +82 -82
  63. package/src/assets/svg/distance.svg +40 -40
  64. package/src/assets/svg/python_logo.svg +246 -246
  65. package/src/assets/svg/rotation_ccw.svg +50 -50
  66. package/src/assets/svg/rotation_ccw_a.svg +57 -57
  67. package/src/assets/svg/rotation_ccw_b.svg +57 -57
  68. package/src/assets/svg/rotation_ccw_c.svg +57 -57
  69. package/src/assets/svg/rotation_cw.svg +49 -49
  70. package/src/assets/svg/rotation_cw_a.svg +30 -30
  71. package/src/assets/svg/rotation_cw_b.svg +30 -30
  72. package/src/assets/svg/rotation_cw_c.svg +30 -30
  73. package/src/assets/svg/speed.svg +39 -39
  74. package/src/components/AutoCoreDevPanel.tsx +414 -414
  75. package/src/components/BlocklyEditor.css +93 -93
  76. package/src/components/BlocklyEditor.tsx +609 -609
  77. package/src/components/CodeEditor.tsx +155 -155
  78. package/src/components/FileList.tsx +390 -390
  79. package/src/components/FileSelect.tsx +128 -128
  80. package/src/components/FitText.tsx +35 -35
  81. package/src/components/Indicator.tsx +188 -188
  82. package/src/components/IndicatorButton.tsx +214 -214
  83. package/src/components/IndicatorRect.tsx +170 -172
  84. package/src/components/JogPanel.css +41 -41
  85. package/src/components/JogPanel.tsx +461 -461
  86. package/src/components/Lamp.tsx +243 -243
  87. package/src/components/Osk.tsx +192 -192
  88. package/src/components/OskDialog.tsx +164 -164
  89. package/src/components/ProgressBarWithValue.css +27 -27
  90. package/src/components/ProgressBarWithValue.tsx +48 -48
  91. package/src/components/TextInput.tsx +195 -195
  92. package/src/components/ToggleGroup.tsx +322 -322
  93. package/src/components/ValueDisplay.tsx +236 -236
  94. package/src/components/ValueIndicator.css +29 -31
  95. package/src/components/ValueIndicator.tsx +135 -135
  96. package/src/components/ValueInput.tsx +368 -368
  97. package/src/components/osk.css +123 -123
  98. package/src/core/ActionMode.ts +19 -19
  99. package/src/core/AutoCoreTagContext.tsx +625 -614
  100. package/src/core/AutoCoreTagTypes.ts +334 -334
  101. package/src/core/CoreStreamTypes.ts +512 -512
  102. package/src/core/EventEmitterContext.tsx +434 -434
  103. package/src/core/IndicatorButtonState.ts +34 -34
  104. package/src/core/IndicatorColor.ts +35 -35
  105. package/src/core/MaskPatterns.ts +87 -87
  106. package/src/core/NumerableTypes.ts +80 -80
  107. package/src/core/PositionContext.ts +59 -59
  108. package/src/core/UniqueId.ts +41 -41
  109. package/src/core/ValueSimulator.ts +166 -166
  110. package/src/core/hoc.tsx +65 -65
  111. package/src/hooks/adsHooks.tsx +287 -287
  112. package/src/hooks/commandHooks.tsx +300 -300
  113. package/src/hooks/index.ts +12 -12
  114. package/src/hooks/useAutoCoreTag.ts +103 -103
  115. package/src/hooks/useScaledValue.tsx +99 -99
  116. package/src/hub/CommandMessage.ts +89 -89
  117. package/src/hub/DebugPanel.ts +307 -307
  118. package/src/hub/HubBase.ts +249 -236
  119. package/src/hub/HubSimulate.ts +124 -124
  120. package/src/hub/HubTauri.ts +140 -140
  121. package/src/hub/HubWebSocket.ts +250 -250
  122. package/src/hub/debug.ts +211 -211
  123. package/src/hub/index.ts +81 -81
  124. package/src/themes/adc-dark/_extensions.scss +166 -166
  125. package/src/themes/adc-dark/_variables.scss +913 -913
  126. package/src/themes/adc-dark/blue/_fonts.scss +23 -23
  127. package/src/themes/adc-dark/blue/adc_theme.scss +31 -31
  128. package/src/themes/adc-dark/blue/theme.scss +14 -14
  129. package/src/themes/theme-base/_colors.scss +17 -17
  130. package/src/themes/theme-base/_common.scss +78 -74
  131. package/src/themes/theme-base/_components.scss +111 -111
  132. package/src/themes/theme-base/_mixins.scss +243 -243
  133. package/src/themes/theme-base/components/button/_button.scss +644 -644
  134. package/src/themes/theme-base/components/button/_speeddial.scss +91 -91
  135. package/src/themes/theme-base/components/button/_splitbutton.scss +358 -358
  136. package/src/themes/theme-base/components/data/_carousel.scss +39 -39
  137. package/src/themes/theme-base/components/data/_datascroller.scss +47 -47
  138. package/src/themes/theme-base/components/data/_datatable.scss +388 -388
  139. package/src/themes/theme-base/components/data/_dataview.scss +47 -47
  140. package/src/themes/theme-base/components/data/_filter.scss +137 -137
  141. package/src/themes/theme-base/components/data/_orderlist.scss +86 -86
  142. package/src/themes/theme-base/components/data/_organizationchart.scss +50 -50
  143. package/src/themes/theme-base/components/data/_paginator.scss +91 -91
  144. package/src/themes/theme-base/components/data/_picklist.scss +73 -73
  145. package/src/themes/theme-base/components/data/_timeline.scss +38 -38
  146. package/src/themes/theme-base/components/data/_tree.scss +184 -184
  147. package/src/themes/theme-base/components/data/_treetable.scss +431 -431
  148. package/src/themes/theme-base/components/file/_fileupload.scss +41 -41
  149. package/src/themes/theme-base/components/input/_autocomplete.scss +94 -94
  150. package/src/themes/theme-base/components/input/_calendar.scss +251 -251
  151. package/src/themes/theme-base/components/input/_cascadeselect.scss +107 -107
  152. package/src/themes/theme-base/components/input/_checkbox.scss +181 -181
  153. package/src/themes/theme-base/components/input/_chips.scss +102 -102
  154. package/src/themes/theme-base/components/input/_colorpicker.scss +17 -17
  155. package/src/themes/theme-base/components/input/_dropdown.scss +252 -252
  156. package/src/themes/theme-base/components/input/_editor.scss +122 -122
  157. package/src/themes/theme-base/components/input/_iconfield.scss +9 -9
  158. package/src/themes/theme-base/components/input/_inputgroup.scss +74 -74
  159. package/src/themes/theme-base/components/input/_inputicon.scss +14 -14
  160. package/src/themes/theme-base/components/input/_inputnumber.scss +4 -4
  161. package/src/themes/theme-base/components/input/_inputotp.scss +10 -10
  162. package/src/themes/theme-base/components/input/_inputswitch.scss +99 -99
  163. package/src/themes/theme-base/components/input/_inputtext.scss +101 -101
  164. package/src/themes/theme-base/components/input/_listbox.scss +138 -138
  165. package/src/themes/theme-base/components/input/_mention.scss +30 -30
  166. package/src/themes/theme-base/components/input/_multiselect.scss +278 -278
  167. package/src/themes/theme-base/components/input/_password.scss +32 -32
  168. package/src/themes/theme-base/components/input/_radiobutton.scss +169 -169
  169. package/src/themes/theme-base/components/input/_rating.scss +80 -80
  170. package/src/themes/theme-base/components/input/_selectbutton.scss +49 -49
  171. package/src/themes/theme-base/components/input/_slider.scss +49 -49
  172. package/src/themes/theme-base/components/input/_togglebutton.scss +99 -99
  173. package/src/themes/theme-base/components/input/_treeselect.scss +151 -151
  174. package/src/themes/theme-base/components/input/_tristatecheckbox.scss +46 -46
  175. package/src/themes/theme-base/components/menu/_breadcrumb.scss +42 -42
  176. package/src/themes/theme-base/components/menu/_contextmenu.scss +39 -39
  177. package/src/themes/theme-base/components/menu/_dock.scss +109 -109
  178. package/src/themes/theme-base/components/menu/_megamenu.scss +141 -141
  179. package/src/themes/theme-base/components/menu/_menu.scss +33 -33
  180. package/src/themes/theme-base/components/menu/_menubar.scss +216 -216
  181. package/src/themes/theme-base/components/menu/_panelmenu.scss +153 -153
  182. package/src/themes/theme-base/components/menu/_slidemenu.scss +60 -60
  183. package/src/themes/theme-base/components/menu/_steps.scss +57 -57
  184. package/src/themes/theme-base/components/menu/_tabmenu.scss +50 -50
  185. package/src/themes/theme-base/components/menu/_tieredmenu.scss +43 -43
  186. package/src/themes/theme-base/components/messages/_inlinemessage.scss +69 -69
  187. package/src/themes/theme-base/components/messages/_message.scss +107 -107
  188. package/src/themes/theme-base/components/messages/_toast.scss +100 -100
  189. package/src/themes/theme-base/components/misc/_avatar.scss +33 -33
  190. package/src/themes/theme-base/components/misc/_badge.scss +76 -76
  191. package/src/themes/theme-base/components/misc/_chip.scss +38 -38
  192. package/src/themes/theme-base/components/misc/_inplace.scss +17 -17
  193. package/src/themes/theme-base/components/misc/_metergroup.scss +80 -80
  194. package/src/themes/theme-base/components/misc/_progressbar.scss +17 -17
  195. package/src/themes/theme-base/components/misc/_scrolltop.scss +24 -24
  196. package/src/themes/theme-base/components/misc/_skeleton.scss +7 -7
  197. package/src/themes/theme-base/components/misc/_tag.scss +39 -39
  198. package/src/themes/theme-base/components/misc/_terminal.scss +12 -12
  199. package/src/themes/theme-base/components/multimedia/_galleria.scss +153 -153
  200. package/src/themes/theme-base/components/multimedia/_image.scss +53 -53
  201. package/src/themes/theme-base/components/overlay/_confirmpopup.scss +72 -72
  202. package/src/themes/theme-base/components/overlay/_dialog.scss +78 -78
  203. package/src/themes/theme-base/components/overlay/_overlaypanel.scss +64 -64
  204. package/src/themes/theme-base/components/overlay/_sidebar.scss +23 -23
  205. package/src/themes/theme-base/components/overlay/_tooltip.scss +33 -33
  206. package/src/themes/theme-base/components/panel/_accordion.scss +118 -118
  207. package/src/themes/theme-base/components/panel/_card.scss +30 -30
  208. package/src/themes/theme-base/components/panel/_divider.scss +30 -30
  209. package/src/themes/theme-base/components/panel/_fieldset.scss +47 -47
  210. package/src/themes/theme-base/components/panel/_panel.scss +47 -47
  211. package/src/themes/theme-base/components/panel/_scrollpanel.scss +10 -10
  212. package/src/themes/theme-base/components/panel/_splitter.scss +23 -23
  213. package/src/themes/theme-base/components/panel/_stepper.scss +136 -136
  214. package/src/themes/theme-base/components/panel/_tabview.scss +147 -147
  215. package/src/themes/theme-base/components/panel/_toolbar.scss +11 -11
  216. package/terser.config.cjs +25 -25
  217. package/todo.md +18 -18
  218. package/tools/build-themes.cjs +65 -65
  219. package/tools/copy-distribution-files.cjs +77 -77
  220. package/tools/minify.cjs +55 -55
  221. package/tsconfig.json +48 -48
  222. package/typedoc.json +12 -12
  223. package/.claude/settings.local.json +0 -7
@@ -1,390 +1,390 @@
1
- /*
2
- * Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3
- * Created Date: 2024-04-24 16:01:53
4
- * -----
5
- * Last Modified: 2025-09-05 14:53:22
6
- * -----
7
- *
8
- */
9
-
10
- /** @file FileList
11
- * FileList allows a user to view the contents of a DataStore in the autocore-server.
12
- * Files can be downloaded, deleted and optionally uploaded (if enabled).
13
- *
14
- * The FileList requires the autocore-server to be the backend, as files are transferred
15
- * using specific commands via websockets.
16
- */
17
-
18
- import React, { useState, useContext, useEffect } from 'react';
19
- import { DataTable } from 'primereact/datatable';
20
- import { Column } from 'primereact/column';
21
- import { Toolbar } from 'primereact/toolbar';
22
- import { Button } from 'primereact/button';
23
- import { confirmPopup } from 'primereact/confirmpopup';
24
- import { FileUpload, type FileUploadHandlerEvent} from 'primereact/fileupload';
25
-
26
- import { EventEmitterContext } from '../core/EventEmitterContext';
27
- import { MessageType } from '../hub/CommandMessage';
28
-
29
-
30
- /**
31
- * Defines properties for the file list.
32
- */
33
- /**
34
- * Defines properties for the file list.
35
- */
36
- type FileListProps = {
37
- /// The domain name assigned to the DATASTORE servelet containing the data.
38
- /// Default: DATASTORE
39
- domain?: string,
40
- /// Enable the upload button so that files can be uploaded to the datastore.
41
- /// Default: false
42
- enableUpload?: boolean,
43
-
44
- /// The subdirectory of the datastore to list.
45
- /// If blank, the base directory is listed.
46
- subdir?: string
47
-
48
- /**
49
- * A comma-separated list of file MIME types or file extensions.
50
- * Default: .json
51
- *
52
- * ### Example: Filter by Extension:
53
- * ```
54
- * accept=".jpg,.png,.pdf" // Restricts uploads to JPG, PNG, and PDF files only
55
- * ```
56
- *
57
- * ### Example: Filter by mime type.
58
- * ```
59
- * accept="image/*,application/pdf" // Allows any image type and PDF files
60
- * ```
61
- *
62
- * ### Example: All files
63
- * ```
64
- * accept="/*"
65
- * ```
66
- *
67
- */
68
- filter?: string,
69
-
70
- /**
71
- * Callback when an operation completes successfully.
72
- * @returns void
73
- */
74
- onSuccess? : (message:string) => void;
75
-
76
- /***
77
- * Callback when an operation results in an error.
78
- */
79
- onError? : (message:string) => void;
80
- }
81
-
82
- /**
83
- * Defines the information for every row item in the file list.
84
- */
85
- type FileItem = {
86
- id: number;
87
- name: string;
88
- };
89
-
90
-
91
- /**
92
- * `FileList` is a React functional component that displays a list of files retrieved from a specified domain
93
- * in an autocore-server.
94
- * It allows users to download and delete files. The component also supports file uploads, if enabled.
95
- *
96
- * The component uses the `EventEmitterContext` to make API calls to a backend to list, download, and delete files.
97
- * It dynamically handles file operations based on the `domain` prop which determines the API endpoints for these actions.
98
- *
99
- * Requires
100
- * ```tsx
101
- * <ConfirmPopup />
102
- * ```
103
- * somewhere in a top-level page.
104
- *
105
- * Props:
106
- * - `domain` (string): The domain name assigned to the DATASTORE servelet containing the data.
107
- * Default: "DATASTORE"
108
- * - `enableUpload` (boolean): If true, enables an upload button allowing files to be uploaded to the datastore.
109
- * Default: false
110
- *
111
- * Example Usage:
112
- * ```tsx
113
- * <FileList domain="MyDomain" enableUpload={true} />
114
- * ```
115
- *
116
- * @param {FileListProps} props The properties passed to the component.
117
- */
118
- export const FileList: React.FC<FileListProps> = ({
119
- domain = "DATASTORE",
120
- enableUpload = false,
121
- subdir,
122
- filter=".json",
123
- onSuccess,
124
- onError
125
- }) => {
126
-
127
- const [uploadKey, setUploadKey] = useState(0);
128
- const { invoke } = useContext(EventEmitterContext);
129
-
130
- const [files, setFiles] = useState<FileItem[]>();
131
-
132
- const makeTargetName = (s : string) => {
133
- if (s !== null) {
134
- if (subdir !== undefined) {
135
- return `${subdir}/${s}`;
136
- }
137
- else {
138
- return s;
139
- }
140
- }
141
- else {
142
- return "";
143
- }
144
- }
145
-
146
- /**
147
- * Retrieve a list of files from an autocore-server DataStoreServelet.
148
- */
149
- const listFiles = async () => {
150
- try {
151
- const args = subdir !== undefined ? { "subdir": subdir } : {};
152
- let res = await invoke(`${domain}.list_files`, MessageType.Request, args);
153
-
154
- let items = [];
155
- for (let i = 0; i < res.data.length; ++i) {
156
- const item = res.data[i];
157
- items.push({
158
- id: i + 1,
159
- name: item
160
- });
161
- }
162
-
163
- setFiles(items);
164
- }
165
- catch (error) {
166
-
167
- if (onError)
168
- onError(`Failed to upload file list: ${error}`);
169
- }
170
-
171
-
172
- }
173
-
174
- /**
175
- * Handles when the download button is clicked on a list item.
176
- * @param file The file item selected in the DataTable
177
- */
178
- const handleDownload = async (file: FileItem) => {
179
-
180
- let target = makeTargetName(file.name);
181
-
182
- try {
183
- await invoke(`${domain}.download_file`, MessageType.Request, { file_name: target });
184
- if (onSuccess)
185
- onSuccess(`Downloaded file: ${file.name}`);
186
- } catch (error) {
187
-
188
- if (onError)
189
- onError(`Failed downloading file: ${error}`);
190
- }
191
-
192
- };
193
-
194
- /**
195
- * Sends the command to the autocore-server domain to delete the file.
196
- * @param file_name Name of the file to delete.
197
- */
198
- const handleDelete = async (file_name: string) => {
199
-
200
- let target = makeTargetName(file_name);
201
-
202
- try {
203
- await invoke(`${domain}.delete_file`, MessageType.Request, { file_name: target });
204
- if (onSuccess)
205
- onSuccess(`Deleted file: ${file_name}`);
206
-
207
- listFiles(); // Refresh the file list after successful upload
208
-
209
- } catch (error) {
210
- if (onError)
211
- onError(`Failed to delete file: ${error}`);
212
- }
213
-
214
-
215
- listFiles();
216
- };
217
-
218
- /**
219
- * Handles the upload of files selected through the PrimeReact FileUpload component.
220
- * This function is triggered when a user selects files for upload and it processes each file asynchronously.
221
- *
222
- * The function reads the selected file as an ArrayBuffer, converts it to a Base64-encoded string, and then
223
- * sends it to the server using a custom function `invoke` which interacts with the server via API calls.
224
- * Upon successful upload, a success message is dispatched to the application's notification system. If the
225
- * upload fails, an error message is similarly dispatched.
226
- *
227
- * Usage of this function requires that it be attached to a FileUpload component's event handler in the React component.
228
- *
229
- * @param {FileUploadSelectEvent} event - The event object provided by the FileUpload component, containing the files selected by the user.
230
- *
231
- * The `FileUploadSelectEvent` type should include:
232
- * - `files`: An array of `File` objects that the user has selected for upload.
233
- *
234
- * This function utilizes the `FileReader` API to read the contents of the file. It checks if the read result
235
- * is an instance of `ArrayBuffer` before proceeding to convert it to a Base64 string. Errors during file reading
236
- * or uploading are caught and appropriate actions are taken (e.g., logging the error, dispatching error notifications).
237
- *
238
- * Note:
239
- * - Ensure that the `invoke` function is properly implemented to handle the API request for file uploading.
240
- * - Adjust the maximum file size and the types of files accepted by the FileUpload component according to your application's requirements.
241
- * - This handler assumes a single file handling scenario. If multiple file uploads are needed, modifications to the iteration over `event.files` may be required.
242
- */
243
- const handleUpload = async (event: FileUploadHandlerEvent) => {
244
- const files = event.files;
245
- const file = files[0]; // Assuming single file upload
246
-
247
- let target = makeTargetName(file.name);
248
-
249
- const reader = new FileReader();
250
- reader.onload = async (e: ProgressEvent<FileReader>) => {
251
-
252
- // Convert array buffer to base64
253
- const arrayBuffer = e.target?.result as ArrayBuffer;
254
- const base64String = arrayBufferToBase64(arrayBuffer);
255
-
256
- try {
257
- await invoke(`${domain}.write_file`, MessageType.Request, { file_name: target, value: base64String, options: { "base64": true } });
258
-
259
- if (onSuccess)
260
- onSuccess(`Uploaded file ${file.name}`);
261
-
262
- // Refresh the file list after successful upload
263
- listFiles();
264
-
265
- } catch (error: any) {
266
- if (onError)
267
- onError(`Failed to upload file: ${error}`);
268
- }
269
-
270
- // Reset upload state of button so it show the file upload dialog again.
271
- resetUpload();
272
- };
273
-
274
- reader.onerror = (error) => {
275
- if (onError)
276
- onError(`Error reading file: ${error}`);
277
- };
278
-
279
-
280
- reader.readAsArrayBuffer(file);
281
- };
282
-
283
- function arrayBufferToBase64(buffer: ArrayBuffer): string {
284
- let binary = '';
285
- let bytes = new Uint8Array(buffer);
286
- let len = bytes.byteLength;
287
- for (let i = 0; i < len; i++) {
288
- binary += String.fromCharCode(bytes[i]);
289
- }
290
- return window.btoa(binary);
291
- }
292
-
293
-
294
- const resetUpload = () => {
295
- setUploadKey(prevKey => prevKey + 1); // Increment key to force re-render
296
- };
297
-
298
-
299
- const title = `File Listing [/${subdir !== undefined ? subdir : ''}]`;
300
- const toolbarStartContents = (
301
- <React.Fragment>
302
- <span style={{ fontWeight: 600 }}>{title}</span>
303
- </React.Fragment>
304
- );
305
-
306
- const toolbarEndContents = (
307
- <React.Fragment>
308
- {enableUpload && (
309
- <FileUpload
310
- key={uploadKey}
311
- customUpload={true}
312
- auto
313
- uploadHandler={handleUpload}
314
- accept={filter}
315
- maxFileSize={25000} // Set maximum file size as needed
316
- mode="basic"
317
- chooseLabel=""
318
- chooseOptions={{
319
- icon: 'pi pi-upload',
320
- className: 'p-button-icon-only p-button-text p-button-rounded p-mr-2'
321
- }}
322
- />
323
-
324
- )}
325
- <Button
326
- icon="pi pi-refresh"
327
- onClick={() => { listFiles() }}
328
- className="p-button-rounded p-mr-2"
329
- aria-label="Refresh"
330
- size="small"
331
- rounded text
332
- />
333
- </React.Fragment>
334
- );
335
-
336
-
337
- /**
338
- * Confirm that the user really wants to delete.
339
- * @param event
340
- */
341
- const confirmDelete = (file: FileItem, event: React.MouseEvent<HTMLButtonElement>) => {
342
-
343
- confirmPopup({
344
- target: event.currentTarget,
345
- message: `Are you want to delete file ${file.name}?\nWARNING: This cannot be undone.`,
346
- icon: 'pi pi-info-circle',
347
- defaultFocus: 'reject',
348
- acceptClassName: 'p-button-danger',
349
- accept: () => handleDelete(file.name)
350
- });
351
- };
352
-
353
- useEffect(() => {
354
-
355
- listFiles();
356
-
357
- return () => {
358
- }
359
- }, [domain, enableUpload]);
360
-
361
- return (
362
-
363
- <div>
364
- <Toolbar start={toolbarStartContents} end={toolbarEndContents} style={{ padding: "1mm" }} />
365
- <DataTable value={files}>
366
- <Column field="name" header="Name"></Column>
367
-
368
- <Column body={(rowData: FileItem) => (
369
- <>
370
- <Button
371
- icon="pi pi-download"
372
- onClick={() => handleDownload(rowData)}
373
- className="p-button-rounded p-button-success p-mr-2"
374
- style={{ marginRight: "2mm" }}
375
- size="small"
376
- />
377
- <Button
378
- icon="pi pi-trash"
379
- onClick={(e) => confirmDelete(rowData, e)}
380
- className="p-button-rounded p-button-danger"
381
- size="small"
382
- />
383
- </>
384
- )} header="Actions"></Column>
385
- </DataTable>
386
- </div>
387
- );
388
- };
389
-
390
- export default FileList;
1
+ /*
2
+ * Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3
+ * Created Date: 2024-04-24 16:01:53
4
+ * -----
5
+ * Last Modified: 2025-09-05 14:53:22
6
+ * -----
7
+ *
8
+ */
9
+
10
+ /** @file FileList
11
+ * FileList allows a user to view the contents of a DataStore in the autocore-server.
12
+ * Files can be downloaded, deleted and optionally uploaded (if enabled).
13
+ *
14
+ * The FileList requires the autocore-server to be the backend, as files are transferred
15
+ * using specific commands via websockets.
16
+ */
17
+
18
+ import React, { useState, useContext, useEffect } from 'react';
19
+ import { DataTable } from 'primereact/datatable';
20
+ import { Column } from 'primereact/column';
21
+ import { Toolbar } from 'primereact/toolbar';
22
+ import { Button } from 'primereact/button';
23
+ import { confirmPopup } from 'primereact/confirmpopup';
24
+ import { FileUpload, type FileUploadHandlerEvent} from 'primereact/fileupload';
25
+
26
+ import { EventEmitterContext } from '../core/EventEmitterContext';
27
+ import { MessageType } from '../hub/CommandMessage';
28
+
29
+
30
+ /**
31
+ * Defines properties for the file list.
32
+ */
33
+ /**
34
+ * Defines properties for the file list.
35
+ */
36
+ type FileListProps = {
37
+ /// The domain name assigned to the DATASTORE servelet containing the data.
38
+ /// Default: DATASTORE
39
+ domain?: string,
40
+ /// Enable the upload button so that files can be uploaded to the datastore.
41
+ /// Default: false
42
+ enableUpload?: boolean,
43
+
44
+ /// The subdirectory of the datastore to list.
45
+ /// If blank, the base directory is listed.
46
+ subdir?: string
47
+
48
+ /**
49
+ * A comma-separated list of file MIME types or file extensions.
50
+ * Default: .json
51
+ *
52
+ * ### Example: Filter by Extension:
53
+ * ```
54
+ * accept=".jpg,.png,.pdf" // Restricts uploads to JPG, PNG, and PDF files only
55
+ * ```
56
+ *
57
+ * ### Example: Filter by mime type.
58
+ * ```
59
+ * accept="image/*,application/pdf" // Allows any image type and PDF files
60
+ * ```
61
+ *
62
+ * ### Example: All files
63
+ * ```
64
+ * accept="/*"
65
+ * ```
66
+ *
67
+ */
68
+ filter?: string,
69
+
70
+ /**
71
+ * Callback when an operation completes successfully.
72
+ * @returns void
73
+ */
74
+ onSuccess? : (message:string) => void;
75
+
76
+ /***
77
+ * Callback when an operation results in an error.
78
+ */
79
+ onError? : (message:string) => void;
80
+ }
81
+
82
+ /**
83
+ * Defines the information for every row item in the file list.
84
+ */
85
+ type FileItem = {
86
+ id: number;
87
+ name: string;
88
+ };
89
+
90
+
91
+ /**
92
+ * `FileList` is a React functional component that displays a list of files retrieved from a specified domain
93
+ * in an autocore-server.
94
+ * It allows users to download and delete files. The component also supports file uploads, if enabled.
95
+ *
96
+ * The component uses the `EventEmitterContext` to make API calls to a backend to list, download, and delete files.
97
+ * It dynamically handles file operations based on the `domain` prop which determines the API endpoints for these actions.
98
+ *
99
+ * Requires
100
+ * ```tsx
101
+ * <ConfirmPopup />
102
+ * ```
103
+ * somewhere in a top-level page.
104
+ *
105
+ * Props:
106
+ * - `domain` (string): The domain name assigned to the DATASTORE servelet containing the data.
107
+ * Default: "DATASTORE"
108
+ * - `enableUpload` (boolean): If true, enables an upload button allowing files to be uploaded to the datastore.
109
+ * Default: false
110
+ *
111
+ * Example Usage:
112
+ * ```tsx
113
+ * <FileList domain="MyDomain" enableUpload={true} />
114
+ * ```
115
+ *
116
+ * @param {FileListProps} props The properties passed to the component.
117
+ */
118
+ export const FileList: React.FC<FileListProps> = ({
119
+ domain = "DATASTORE",
120
+ enableUpload = false,
121
+ subdir,
122
+ filter=".json",
123
+ onSuccess,
124
+ onError
125
+ }) => {
126
+
127
+ const [uploadKey, setUploadKey] = useState(0);
128
+ const { invoke } = useContext(EventEmitterContext);
129
+
130
+ const [files, setFiles] = useState<FileItem[]>();
131
+
132
+ const makeTargetName = (s : string) => {
133
+ if (s !== null) {
134
+ if (subdir !== undefined) {
135
+ return `${subdir}/${s}`;
136
+ }
137
+ else {
138
+ return s;
139
+ }
140
+ }
141
+ else {
142
+ return "";
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Retrieve a list of files from an autocore-server DataStoreServelet.
148
+ */
149
+ const listFiles = async () => {
150
+ try {
151
+ const args = subdir !== undefined ? { "subdir": subdir } : {};
152
+ let res = await invoke(`${domain}.list_files`, MessageType.Request, args);
153
+
154
+ let items = [];
155
+ for (let i = 0; i < res.data.length; ++i) {
156
+ const item = res.data[i];
157
+ items.push({
158
+ id: i + 1,
159
+ name: item
160
+ });
161
+ }
162
+
163
+ setFiles(items);
164
+ }
165
+ catch (error) {
166
+
167
+ if (onError)
168
+ onError(`Failed to upload file list: ${error}`);
169
+ }
170
+
171
+
172
+ }
173
+
174
+ /**
175
+ * Handles when the download button is clicked on a list item.
176
+ * @param file The file item selected in the DataTable
177
+ */
178
+ const handleDownload = async (file: FileItem) => {
179
+
180
+ let target = makeTargetName(file.name);
181
+
182
+ try {
183
+ await invoke(`${domain}.download_file`, MessageType.Request, { file_name: target });
184
+ if (onSuccess)
185
+ onSuccess(`Downloaded file: ${file.name}`);
186
+ } catch (error) {
187
+
188
+ if (onError)
189
+ onError(`Failed downloading file: ${error}`);
190
+ }
191
+
192
+ };
193
+
194
+ /**
195
+ * Sends the command to the autocore-server domain to delete the file.
196
+ * @param file_name Name of the file to delete.
197
+ */
198
+ const handleDelete = async (file_name: string) => {
199
+
200
+ let target = makeTargetName(file_name);
201
+
202
+ try {
203
+ await invoke(`${domain}.delete_file`, MessageType.Request, { file_name: target });
204
+ if (onSuccess)
205
+ onSuccess(`Deleted file: ${file_name}`);
206
+
207
+ listFiles(); // Refresh the file list after successful upload
208
+
209
+ } catch (error) {
210
+ if (onError)
211
+ onError(`Failed to delete file: ${error}`);
212
+ }
213
+
214
+
215
+ listFiles();
216
+ };
217
+
218
+ /**
219
+ * Handles the upload of files selected through the PrimeReact FileUpload component.
220
+ * This function is triggered when a user selects files for upload and it processes each file asynchronously.
221
+ *
222
+ * The function reads the selected file as an ArrayBuffer, converts it to a Base64-encoded string, and then
223
+ * sends it to the server using a custom function `invoke` which interacts with the server via API calls.
224
+ * Upon successful upload, a success message is dispatched to the application's notification system. If the
225
+ * upload fails, an error message is similarly dispatched.
226
+ *
227
+ * Usage of this function requires that it be attached to a FileUpload component's event handler in the React component.
228
+ *
229
+ * @param {FileUploadSelectEvent} event - The event object provided by the FileUpload component, containing the files selected by the user.
230
+ *
231
+ * The `FileUploadSelectEvent` type should include:
232
+ * - `files`: An array of `File` objects that the user has selected for upload.
233
+ *
234
+ * This function utilizes the `FileReader` API to read the contents of the file. It checks if the read result
235
+ * is an instance of `ArrayBuffer` before proceeding to convert it to a Base64 string. Errors during file reading
236
+ * or uploading are caught and appropriate actions are taken (e.g., logging the error, dispatching error notifications).
237
+ *
238
+ * Note:
239
+ * - Ensure that the `invoke` function is properly implemented to handle the API request for file uploading.
240
+ * - Adjust the maximum file size and the types of files accepted by the FileUpload component according to your application's requirements.
241
+ * - This handler assumes a single file handling scenario. If multiple file uploads are needed, modifications to the iteration over `event.files` may be required.
242
+ */
243
+ const handleUpload = async (event: FileUploadHandlerEvent) => {
244
+ const files = event.files;
245
+ const file = files[0]; // Assuming single file upload
246
+
247
+ let target = makeTargetName(file.name);
248
+
249
+ const reader = new FileReader();
250
+ reader.onload = async (e: ProgressEvent<FileReader>) => {
251
+
252
+ // Convert array buffer to base64
253
+ const arrayBuffer = e.target?.result as ArrayBuffer;
254
+ const base64String = arrayBufferToBase64(arrayBuffer);
255
+
256
+ try {
257
+ await invoke(`${domain}.write_file`, MessageType.Request, { file_name: target, value: base64String, options: { "base64": true } });
258
+
259
+ if (onSuccess)
260
+ onSuccess(`Uploaded file ${file.name}`);
261
+
262
+ // Refresh the file list after successful upload
263
+ listFiles();
264
+
265
+ } catch (error: any) {
266
+ if (onError)
267
+ onError(`Failed to upload file: ${error}`);
268
+ }
269
+
270
+ // Reset upload state of button so it show the file upload dialog again.
271
+ resetUpload();
272
+ };
273
+
274
+ reader.onerror = (error) => {
275
+ if (onError)
276
+ onError(`Error reading file: ${error}`);
277
+ };
278
+
279
+
280
+ reader.readAsArrayBuffer(file);
281
+ };
282
+
283
+ function arrayBufferToBase64(buffer: ArrayBuffer): string {
284
+ let binary = '';
285
+ let bytes = new Uint8Array(buffer);
286
+ let len = bytes.byteLength;
287
+ for (let i = 0; i < len; i++) {
288
+ binary += String.fromCharCode(bytes[i]);
289
+ }
290
+ return window.btoa(binary);
291
+ }
292
+
293
+
294
+ const resetUpload = () => {
295
+ setUploadKey(prevKey => prevKey + 1); // Increment key to force re-render
296
+ };
297
+
298
+
299
+ const title = `File Listing [/${subdir !== undefined ? subdir : ''}]`;
300
+ const toolbarStartContents = (
301
+ <React.Fragment>
302
+ <span style={{ fontWeight: 600 }}>{title}</span>
303
+ </React.Fragment>
304
+ );
305
+
306
+ const toolbarEndContents = (
307
+ <React.Fragment>
308
+ {enableUpload && (
309
+ <FileUpload
310
+ key={uploadKey}
311
+ customUpload={true}
312
+ auto
313
+ uploadHandler={handleUpload}
314
+ accept={filter}
315
+ maxFileSize={25000} // Set maximum file size as needed
316
+ mode="basic"
317
+ chooseLabel=""
318
+ chooseOptions={{
319
+ icon: 'pi pi-upload',
320
+ className: 'p-button-icon-only p-button-text p-button-rounded p-mr-2'
321
+ }}
322
+ />
323
+
324
+ )}
325
+ <Button
326
+ icon="pi pi-refresh"
327
+ onClick={() => { listFiles() }}
328
+ className="p-button-rounded p-mr-2"
329
+ aria-label="Refresh"
330
+ size="small"
331
+ rounded text
332
+ />
333
+ </React.Fragment>
334
+ );
335
+
336
+
337
+ /**
338
+ * Confirm that the user really wants to delete.
339
+ * @param event
340
+ */
341
+ const confirmDelete = (file: FileItem, event: React.MouseEvent<HTMLButtonElement>) => {
342
+
343
+ confirmPopup({
344
+ target: event.currentTarget,
345
+ message: `Are you want to delete file ${file.name}?\nWARNING: This cannot be undone.`,
346
+ icon: 'pi pi-info-circle',
347
+ defaultFocus: 'reject',
348
+ acceptClassName: 'p-button-danger',
349
+ accept: () => handleDelete(file.name)
350
+ });
351
+ };
352
+
353
+ useEffect(() => {
354
+
355
+ listFiles();
356
+
357
+ return () => {
358
+ }
359
+ }, [domain, enableUpload]);
360
+
361
+ return (
362
+
363
+ <div>
364
+ <Toolbar start={toolbarStartContents} end={toolbarEndContents} style={{ padding: "1mm" }} />
365
+ <DataTable value={files}>
366
+ <Column field="name" header="Name"></Column>
367
+
368
+ <Column body={(rowData: FileItem) => (
369
+ <>
370
+ <Button
371
+ icon="pi pi-download"
372
+ onClick={() => handleDownload(rowData)}
373
+ className="p-button-rounded p-button-success p-mr-2"
374
+ style={{ marginRight: "2mm" }}
375
+ size="small"
376
+ />
377
+ <Button
378
+ icon="pi pi-trash"
379
+ onClick={(e) => confirmDelete(rowData, e)}
380
+ className="p-button-rounded p-button-danger"
381
+ size="small"
382
+ />
383
+ </>
384
+ )} header="Actions"></Column>
385
+ </DataTable>
386
+ </div>
387
+ );
388
+ };
389
+
390
+ export default FileList;