@adcops/autocore-react 3.3.9 → 3.3.10

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 (215) 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/JogPanel.css +41 -41
  21. package/dist/components/ProgressBarWithValue.css +27 -27
  22. package/dist/components/ValueIndicator.css +31 -31
  23. package/dist/components/osk.css +123 -123
  24. package/dist/core/AutoCoreTagContext.d.ts.map +1 -1
  25. package/dist/core/AutoCoreTagContext.js +1 -1
  26. package/dist/hub/HubBase.d.ts +3 -3
  27. package/dist/hub/HubBase.d.ts.map +1 -1
  28. package/dist/hub/HubBase.js +1 -1
  29. package/package.json +104 -104
  30. package/readme.md +343 -343
  31. package/src/assets/BlocklyLogo.tsx +27 -27
  32. package/src/assets/Distance.tsx +18 -18
  33. package/src/assets/JogLong.tsx +13 -13
  34. package/src/assets/JogMedium.tsx +13 -13
  35. package/src/assets/JogShort.tsx +13 -13
  36. package/src/assets/PythonLogo.tsx +83 -83
  37. package/src/assets/Rotation3D.tsx +13 -13
  38. package/src/assets/RotationCcw.tsx +33 -33
  39. package/src/assets/RotationCcwA.tsx +45 -45
  40. package/src/assets/RotationCcwB.tsx +45 -45
  41. package/src/assets/RotationCcwC.tsx +45 -45
  42. package/src/assets/RotationCw.tsx +31 -31
  43. package/src/assets/RotationCwA.tsx +42 -42
  44. package/src/assets/RotationCwB.tsx +42 -42
  45. package/src/assets/RotationCwC.tsx +42 -42
  46. package/src/assets/Run.tsx +13 -13
  47. package/src/assets/Speed.tsx +18 -18
  48. package/src/assets/SpeedFast.tsx +13 -13
  49. package/src/assets/SpeedMedium.tsx +13 -13
  50. package/src/assets/SpeedNone.tsx +13 -13
  51. package/src/assets/SpeedSlow.tsx +13 -13
  52. package/src/assets/Walk.tsx +13 -13
  53. package/src/assets/index.ts +22 -22
  54. package/src/assets/svg/blockly_logo.svg +82 -82
  55. package/src/assets/svg/distance.svg +40 -40
  56. package/src/assets/svg/python_logo.svg +246 -246
  57. package/src/assets/svg/rotation_ccw.svg +50 -50
  58. package/src/assets/svg/rotation_ccw_a.svg +57 -57
  59. package/src/assets/svg/rotation_ccw_b.svg +57 -57
  60. package/src/assets/svg/rotation_ccw_c.svg +57 -57
  61. package/src/assets/svg/rotation_cw.svg +49 -49
  62. package/src/assets/svg/rotation_cw_a.svg +30 -30
  63. package/src/assets/svg/rotation_cw_b.svg +30 -30
  64. package/src/assets/svg/rotation_cw_c.svg +30 -30
  65. package/src/assets/svg/speed.svg +39 -39
  66. package/src/components/AutoCoreDevPanel.tsx +414 -414
  67. package/src/components/BlocklyEditor.css +93 -93
  68. package/src/components/BlocklyEditor.tsx +609 -609
  69. package/src/components/CodeEditor.tsx +155 -155
  70. package/src/components/FileList.tsx +390 -390
  71. package/src/components/FileSelect.tsx +128 -128
  72. package/src/components/FitText.tsx +35 -35
  73. package/src/components/Indicator.tsx +188 -188
  74. package/src/components/IndicatorButton.tsx +214 -214
  75. package/src/components/IndicatorRect.tsx +172 -172
  76. package/src/components/JogPanel.css +41 -41
  77. package/src/components/JogPanel.tsx +461 -461
  78. package/src/components/Lamp.tsx +243 -243
  79. package/src/components/Osk.tsx +192 -192
  80. package/src/components/OskDialog.tsx +164 -164
  81. package/src/components/ProgressBarWithValue.css +27 -27
  82. package/src/components/ProgressBarWithValue.tsx +48 -48
  83. package/src/components/TextInput.tsx +195 -195
  84. package/src/components/ToggleGroup.tsx +322 -322
  85. package/src/components/ValueDisplay.tsx +236 -236
  86. package/src/components/ValueIndicator.css +31 -31
  87. package/src/components/ValueIndicator.tsx +135 -135
  88. package/src/components/ValueInput.tsx +368 -368
  89. package/src/components/osk.css +123 -123
  90. package/src/core/ActionMode.ts +19 -19
  91. package/src/core/AutoCoreTagContext.tsx +625 -614
  92. package/src/core/AutoCoreTagTypes.ts +334 -334
  93. package/src/core/CoreStreamTypes.ts +512 -512
  94. package/src/core/EventEmitterContext.tsx +434 -434
  95. package/src/core/IndicatorButtonState.ts +34 -34
  96. package/src/core/IndicatorColor.ts +35 -35
  97. package/src/core/MaskPatterns.ts +87 -87
  98. package/src/core/NumerableTypes.ts +80 -80
  99. package/src/core/PositionContext.ts +59 -59
  100. package/src/core/UniqueId.ts +41 -41
  101. package/src/core/ValueSimulator.ts +166 -166
  102. package/src/core/hoc.tsx +65 -65
  103. package/src/hooks/adsHooks.tsx +287 -287
  104. package/src/hooks/commandHooks.tsx +300 -300
  105. package/src/hooks/index.ts +12 -12
  106. package/src/hooks/useAutoCoreTag.ts +103 -103
  107. package/src/hooks/useScaledValue.tsx +99 -99
  108. package/src/hub/CommandMessage.ts +89 -89
  109. package/src/hub/DebugPanel.ts +307 -307
  110. package/src/hub/HubBase.ts +249 -236
  111. package/src/hub/HubSimulate.ts +124 -124
  112. package/src/hub/HubTauri.ts +140 -140
  113. package/src/hub/HubWebSocket.ts +250 -250
  114. package/src/hub/debug.ts +211 -211
  115. package/src/hub/index.ts +81 -81
  116. package/src/themes/adc-dark/_extensions.scss +166 -166
  117. package/src/themes/adc-dark/_variables.scss +913 -913
  118. package/src/themes/adc-dark/blue/_fonts.scss +23 -23
  119. package/src/themes/adc-dark/blue/adc_theme.scss +31 -31
  120. package/src/themes/adc-dark/blue/theme.scss +14 -14
  121. package/src/themes/theme-base/_colors.scss +17 -17
  122. package/src/themes/theme-base/_common.scss +74 -74
  123. package/src/themes/theme-base/_components.scss +111 -111
  124. package/src/themes/theme-base/_mixins.scss +243 -243
  125. package/src/themes/theme-base/components/button/_button.scss +644 -644
  126. package/src/themes/theme-base/components/button/_speeddial.scss +91 -91
  127. package/src/themes/theme-base/components/button/_splitbutton.scss +358 -358
  128. package/src/themes/theme-base/components/data/_carousel.scss +39 -39
  129. package/src/themes/theme-base/components/data/_datascroller.scss +47 -47
  130. package/src/themes/theme-base/components/data/_datatable.scss +388 -388
  131. package/src/themes/theme-base/components/data/_dataview.scss +47 -47
  132. package/src/themes/theme-base/components/data/_filter.scss +137 -137
  133. package/src/themes/theme-base/components/data/_orderlist.scss +86 -86
  134. package/src/themes/theme-base/components/data/_organizationchart.scss +50 -50
  135. package/src/themes/theme-base/components/data/_paginator.scss +91 -91
  136. package/src/themes/theme-base/components/data/_picklist.scss +73 -73
  137. package/src/themes/theme-base/components/data/_timeline.scss +38 -38
  138. package/src/themes/theme-base/components/data/_tree.scss +184 -184
  139. package/src/themes/theme-base/components/data/_treetable.scss +431 -431
  140. package/src/themes/theme-base/components/file/_fileupload.scss +41 -41
  141. package/src/themes/theme-base/components/input/_autocomplete.scss +94 -94
  142. package/src/themes/theme-base/components/input/_calendar.scss +251 -251
  143. package/src/themes/theme-base/components/input/_cascadeselect.scss +107 -107
  144. package/src/themes/theme-base/components/input/_checkbox.scss +181 -181
  145. package/src/themes/theme-base/components/input/_chips.scss +102 -102
  146. package/src/themes/theme-base/components/input/_colorpicker.scss +17 -17
  147. package/src/themes/theme-base/components/input/_dropdown.scss +252 -252
  148. package/src/themes/theme-base/components/input/_editor.scss +122 -122
  149. package/src/themes/theme-base/components/input/_iconfield.scss +9 -9
  150. package/src/themes/theme-base/components/input/_inputgroup.scss +74 -74
  151. package/src/themes/theme-base/components/input/_inputicon.scss +14 -14
  152. package/src/themes/theme-base/components/input/_inputnumber.scss +4 -4
  153. package/src/themes/theme-base/components/input/_inputotp.scss +10 -10
  154. package/src/themes/theme-base/components/input/_inputswitch.scss +99 -99
  155. package/src/themes/theme-base/components/input/_inputtext.scss +101 -101
  156. package/src/themes/theme-base/components/input/_listbox.scss +138 -138
  157. package/src/themes/theme-base/components/input/_mention.scss +30 -30
  158. package/src/themes/theme-base/components/input/_multiselect.scss +278 -278
  159. package/src/themes/theme-base/components/input/_password.scss +32 -32
  160. package/src/themes/theme-base/components/input/_radiobutton.scss +169 -169
  161. package/src/themes/theme-base/components/input/_rating.scss +80 -80
  162. package/src/themes/theme-base/components/input/_selectbutton.scss +49 -49
  163. package/src/themes/theme-base/components/input/_slider.scss +49 -49
  164. package/src/themes/theme-base/components/input/_togglebutton.scss +99 -99
  165. package/src/themes/theme-base/components/input/_treeselect.scss +151 -151
  166. package/src/themes/theme-base/components/input/_tristatecheckbox.scss +46 -46
  167. package/src/themes/theme-base/components/menu/_breadcrumb.scss +42 -42
  168. package/src/themes/theme-base/components/menu/_contextmenu.scss +39 -39
  169. package/src/themes/theme-base/components/menu/_dock.scss +109 -109
  170. package/src/themes/theme-base/components/menu/_megamenu.scss +141 -141
  171. package/src/themes/theme-base/components/menu/_menu.scss +33 -33
  172. package/src/themes/theme-base/components/menu/_menubar.scss +216 -216
  173. package/src/themes/theme-base/components/menu/_panelmenu.scss +153 -153
  174. package/src/themes/theme-base/components/menu/_slidemenu.scss +60 -60
  175. package/src/themes/theme-base/components/menu/_steps.scss +57 -57
  176. package/src/themes/theme-base/components/menu/_tabmenu.scss +50 -50
  177. package/src/themes/theme-base/components/menu/_tieredmenu.scss +43 -43
  178. package/src/themes/theme-base/components/messages/_inlinemessage.scss +69 -69
  179. package/src/themes/theme-base/components/messages/_message.scss +107 -107
  180. package/src/themes/theme-base/components/messages/_toast.scss +100 -100
  181. package/src/themes/theme-base/components/misc/_avatar.scss +33 -33
  182. package/src/themes/theme-base/components/misc/_badge.scss +76 -76
  183. package/src/themes/theme-base/components/misc/_chip.scss +38 -38
  184. package/src/themes/theme-base/components/misc/_inplace.scss +17 -17
  185. package/src/themes/theme-base/components/misc/_metergroup.scss +80 -80
  186. package/src/themes/theme-base/components/misc/_progressbar.scss +17 -17
  187. package/src/themes/theme-base/components/misc/_scrolltop.scss +24 -24
  188. package/src/themes/theme-base/components/misc/_skeleton.scss +7 -7
  189. package/src/themes/theme-base/components/misc/_tag.scss +39 -39
  190. package/src/themes/theme-base/components/misc/_terminal.scss +12 -12
  191. package/src/themes/theme-base/components/multimedia/_galleria.scss +153 -153
  192. package/src/themes/theme-base/components/multimedia/_image.scss +53 -53
  193. package/src/themes/theme-base/components/overlay/_confirmpopup.scss +72 -72
  194. package/src/themes/theme-base/components/overlay/_dialog.scss +78 -78
  195. package/src/themes/theme-base/components/overlay/_overlaypanel.scss +64 -64
  196. package/src/themes/theme-base/components/overlay/_sidebar.scss +23 -23
  197. package/src/themes/theme-base/components/overlay/_tooltip.scss +33 -33
  198. package/src/themes/theme-base/components/panel/_accordion.scss +118 -118
  199. package/src/themes/theme-base/components/panel/_card.scss +30 -30
  200. package/src/themes/theme-base/components/panel/_divider.scss +30 -30
  201. package/src/themes/theme-base/components/panel/_fieldset.scss +47 -47
  202. package/src/themes/theme-base/components/panel/_panel.scss +47 -47
  203. package/src/themes/theme-base/components/panel/_scrollpanel.scss +10 -10
  204. package/src/themes/theme-base/components/panel/_splitter.scss +23 -23
  205. package/src/themes/theme-base/components/panel/_stepper.scss +136 -136
  206. package/src/themes/theme-base/components/panel/_tabview.scss +147 -147
  207. package/src/themes/theme-base/components/panel/_toolbar.scss +11 -11
  208. package/terser.config.cjs +25 -25
  209. package/todo.md +18 -18
  210. package/tools/build-themes.cjs +65 -65
  211. package/tools/copy-distribution-files.cjs +77 -77
  212. package/tools/minify.cjs +55 -55
  213. package/tsconfig.json +48 -48
  214. package/typedoc.json +12 -12
  215. 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;