@onehat/ui 0.4.77 → 0.4.78

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 (58) hide show
  1. package/package.json +1 -1
  2. package/src/Components/Editor/AttachmentDirectoriesEditor.js +51 -0
  3. package/src/Components/Editor/AttachmentsEditor.js +81 -0
  4. package/src/Components/Form/Field/Combo/AttachmentDirectoriesCombo.js +20 -0
  5. package/src/Components/Form/Field/Combo/AttachmentDirectoriesComboEditor.js +22 -0
  6. package/src/Components/Form/Field/Combo/AttachmentsCombo.js +20 -0
  7. package/src/Components/Form/Field/Combo/AttachmentsComboEditor.js +22 -0
  8. package/src/Components/Form/Field/Tag/AttachmentDirectoriesTag.js +22 -0
  9. package/src/Components/Form/Field/Tag/AttachmentDirectoriesTagEditor.js +22 -0
  10. package/src/Components/Form/Field/Tag/AttachmentsTag.js +22 -0
  11. package/src/Components/Form/Field/Tag/AttachmentsTagEditor.js +22 -0
  12. package/src/Components/Form/Form.js +17 -17
  13. package/src/Components/Grid/AttachmentDirectoriesFilteredGrid.js +17 -0
  14. package/src/Components/Grid/AttachmentDirectoriesFilteredGridEditor.js +17 -0
  15. package/src/Components/Grid/AttachmentDirectoriesFilteredInlineGridEditor.js +17 -0
  16. package/src/Components/Grid/AttachmentDirectoriesFilteredSideGridEditor.js +17 -0
  17. package/src/Components/Grid/AttachmentDirectoriesGrid.js +20 -0
  18. package/src/Components/Grid/AttachmentDirectoriesGridEditor.js +27 -0
  19. package/src/Components/Grid/AttachmentDirectoriesInlineGridEditor.js +25 -0
  20. package/src/Components/Grid/AttachmentDirectoriesSideGridEditor.js +24 -0
  21. package/src/Components/Grid/AttachmentsFilteredGrid.js +17 -0
  22. package/src/Components/Grid/AttachmentsFilteredGridEditor.js +17 -0
  23. package/src/Components/Grid/AttachmentsFilteredInlineGridEditor.js +17 -0
  24. package/src/Components/Grid/AttachmentsFilteredSideGridEditor.js +17 -0
  25. package/src/Components/Grid/AttachmentsGrid.js +20 -0
  26. package/src/Components/Grid/AttachmentsGridEditor.js +27 -0
  27. package/src/Components/Grid/AttachmentsInlineGridEditor.js +25 -0
  28. package/src/Components/Grid/AttachmentsSideGridEditor.js +24 -0
  29. package/src/Components/Grid/Columns/AttachmentDirectoriesGridColumns.js +32 -0
  30. package/src/Components/Grid/Columns/AttachmentsGridColumns.js +133 -0
  31. package/src/Components/Grid/Grid.js +193 -20
  32. package/src/Components/Grid/GridHeaderRow.js +10 -17
  33. package/src/Components/Grid/GridRow.js +49 -22
  34. package/src/Components/Grid/RowHandle.js +8 -6
  35. package/src/Components/Hoc/withEditor.js +1 -1
  36. package/src/Components/Hoc/withPdfButtons.js +1 -1
  37. package/src/Components/Hoc/withSelection.js +26 -4
  38. package/src/Components/Layout/AsyncOperation.js +299 -195
  39. package/src/Components/Messages/GlobalModals.js +1 -2
  40. package/src/Components/Panel/Panel.js +14 -2
  41. package/src/Components/Panel/TabPanel.js +1 -1
  42. package/src/Components/Panel/TreePanel.js +1 -1
  43. package/src/Components/Report/Report.js +106 -17
  44. package/src/Components/Toolbar/PaginationToolbar.js +4 -3
  45. package/src/Components/Toolbar/Toolbar.js +6 -3
  46. package/src/Components/Tree/Tree.js +218 -147
  47. package/src/Components/Tree/TreeNode.js +20 -13
  48. package/src/Components/Window/AttachmentDirectoriesEditorWindow.js +34 -0
  49. package/src/Components/Window/AttachmentsEditorWindow.js +34 -0
  50. package/src/Components/index.js +92 -1
  51. package/src/Constants/Attachments.js +2 -0
  52. package/src/Constants/Dates.js +2 -2
  53. package/src/Constants/Progress.js +5 -1
  54. package/src/Models/Schemas/AttachmentDirectories.js +66 -0
  55. package/src/Models/Schemas/Attachments.js +88 -0
  56. package/src/Models/Slices/SystemSlice.js +220 -0
  57. package/src/PlatformImports/Web/Attachments.js +783 -145
  58. package/src/Styles/Global.css +7 -2
@@ -6,14 +6,24 @@ import {
6
6
  VStack,
7
7
  } from '@project-components/Gluestack';
8
8
  import clsx from 'clsx';
9
- import { useSelector, useDispatch, } from 'react-redux';
10
- import { PROGRESS_COMPLETED } from '../../Constants/Progress.js';
11
- import useForceUpdate from '../../Hooks/useForceUpdate.js';
9
+ import * as Progress from 'react-native-progress';
10
+ import useForceUpdate from '@onehat/ui/src/Hooks/useForceUpdate';
11
+ import {
12
+ PROGRESS__NONE_FOUND,
13
+ PROGRESS__IN_PROCESS,
14
+ PROGRESS__COMPLETED,
15
+ PROGRESS__FAILED,
16
+ PROGRESS__STUCK,
17
+ } from '../../Constants/Progress.js';
18
+ import {
19
+ MOMENT_DATE_FORMAT_2,
20
+ } from '../../Constants/Dates.js';
12
21
  import isJson from '../../Functions/isJson.js';
13
22
  import Form from '../Form/Form.js';
14
23
  import Button from '../Buttons/Button.js';
15
24
  import withComponent from '../Hoc/withComponent.js';
16
25
  import withAlert from '../Hoc/withAlert.js';
26
+ import Loading from '../Messages/Loading.js';
17
27
  import ChevronLeft from '../Icons/ChevronLeft.js';
18
28
  import ChevronRight from '../Icons/ChevronRight.js';
19
29
  import Play from '../Icons/Play.js';
@@ -22,31 +32,35 @@ import Stop from '../Icons/Stop.js';
22
32
  import TabBar from '../Tab/TabBar.js';
23
33
  import Panel from '../Panel/Panel.js';
24
34
  import Toolbar from '../Toolbar/Toolbar.js';
35
+ import moment from 'moment';
25
36
  import _ from 'lodash';
26
37
 
27
38
  const
28
- INITIATE = 'INITIATE',
29
- PROCESSING = 'PROCESSING',
30
- RESULTS = 'RESULTS';
39
+ INIT = 'INIT', // no footer shown; used when component initially loads, to see if operation is already in progress
40
+ START = 'START', // shows the start form
41
+ PROCESSING = 'PROCESSING', // shows the loading indicator while starting the operation
42
+ RESULTS = 'RESULTS'; // shows the results of the operation, or any in-progress updates
31
43
 
32
- // NOTE: This component assumes you have an AppSlice, that has
33
- // an 'operationsInProgress' state var and a 'setOperationsInProgress' action.
44
+ // If getProgressUpdates is false, the component will show the start form initially.
45
+ // If getProgressUpdates is true, the component will initially query the server to see if
46
+ // an operation is already in progress.
47
+ // If so, it will automatically poll the server for progress updates
48
+ // If not, it will show the start form.
34
49
 
35
50
  function AsyncOperation(props) {
36
51
 
37
- if (!props.Repository || !props.action) {
38
- throw Error('AsyncOperation: Repository and action are required!');
52
+ if (!props.Repository || !props.process) {
53
+ throw Error('AsyncOperation: Repository and process are required!');
39
54
  }
40
55
 
41
56
  const {
42
- action,
57
+ process,
43
58
  Repository,
44
59
  formItems = [],
45
60
  formStartingValues = {},
46
61
  _form = {},
47
62
  getProgressUpdates = false,
48
- parseProgress, // optional fn, accepts 'response' as arg and returns progress string
49
- progressStuckThreshold = null, // e.g. 3, if left blank, doesn't check for stuck state
63
+ parseProgress, // optional fn, accepts 'response' as arg and returns an object like this: { status, errors, started, lastUpdated, timeElapsed, count, current, total, percentage }
50
64
  updateInterval = 10000, // ms
51
65
 
52
66
  // withComponent
@@ -55,7 +69,7 @@ function AsyncOperation(props) {
55
69
  // withAlert
56
70
  alert,
57
71
  } = props,
58
- dispatch = useDispatch(),
72
+ forceUpdate = useForceUpdate(),
59
73
  isValid = useRef(true),
60
74
  setIsValid = (valid) => {
61
75
  isValid.current = valid;
@@ -63,33 +77,105 @@ function AsyncOperation(props) {
63
77
  getIsValid = () => {
64
78
  return isValid.current;
65
79
  },
66
- mode = useRef(INITIATE),
80
+ mode = useRef(INIT),
67
81
  setMode = (newMode) => {
68
82
  mode.current = newMode;
69
83
  },
70
84
  getMode = () => {
71
85
  return mode.current;
72
86
  },
73
- initiate = async () => {
74
-
75
- clearProgress();
87
+ isInProcess = getMode() === PROCESSING,
88
+ currentTabIx = (getMode() === PROCESSING ? 1 : (getMode() === RESULTS ? 2 : 0)),
89
+ intervalRef = useRef(null),
90
+ getInterval = () => {
91
+ return intervalRef.current;
92
+ },
93
+ setIntervalRef = (interval) => { // 'setInterval' is a reserved name
94
+ intervalRef.current = interval;
95
+ },
96
+ formValuesRef = useRef(null),
97
+ getFormValues = () => {
98
+ return formValuesRef.current;
99
+ },
100
+ setFormValues = (values) => {
101
+ formValuesRef.current = values;
102
+ },
103
+ getFooter = () => {
104
+ switch(getMode()) {
105
+ case INIT:
106
+ return null;
107
+ case START:
108
+ return <Toolbar>
109
+ <Button
110
+ text="Start"
111
+ rightIcon={ChevronRight}
112
+ onPress={() => startProcess()}
113
+ isDisabled={!getIsValid()}
114
+ />
115
+ </Toolbar>;
116
+ case PROCESSING:
117
+ // TODO: Add a cancellation option to the command.
118
+ // would require a backend controller action to support it
119
+ return null;
120
+ // return <Toolbar>
121
+ // <Button
122
+ // text="Please wait"
123
+ // isLoading={true}
124
+ // variant="link"
125
+ // />
126
+ // </Toolbar>;
127
+ case RESULTS:
128
+ return <Toolbar>
129
+ <Button
130
+ text="Reset"
131
+ icon={ChevronLeft}
132
+ onPress={() => resetToInitialState()}
133
+ />
134
+ </Toolbar>;
135
+ }
136
+ },
137
+ [footer, setFooter] = useState(getFooter()),
138
+ [results, setResults] = useState(null),
139
+ [progress, setProgress] = useState(null),
140
+ [isStuck, setIsStuck] = useState(false),
141
+ [isReady, setIsReady] = useState(false),
142
+ showResults = (results) => {
143
+ setMode(RESULTS);
144
+ setFooter(getFooter());
145
+ setResults(results);
146
+ },
147
+ startProcess = async () => {
148
+ stopGettingProgress();
76
149
  setMode(PROCESSING);
77
150
  setFooter(getFooter());
78
- setIsInProgress(true);
79
151
 
80
152
  const
81
153
  method = Repository.methods.edit,
82
- uri = Repository.getModel() + '/' + action,
83
- formValues = self?.children?.form?.formGetValues() || {},
154
+ uri = Repository.getModel() + '/startProcess',
155
+ formValues = self?.children?.form?.formGetValues() || {};
156
+ formValues.process = process;
157
+ const
84
158
  result = await Repository._send(method, uri, formValues);
85
159
 
86
160
  setFormValues(formValues);
87
161
 
88
162
  const response = Repository._processServerResponse(result);
89
- if (!response.success) {
163
+ if (!response?.success) {
164
+ alert(response.message || 'Error starting process on server.');
90
165
  resetToInitialState();
91
166
  return;
92
167
  }
168
+
169
+ if (getProgressUpdates) {
170
+ setProgress(<VStack className="p-4">
171
+ <Text className="text-lg" key="status">
172
+ Process has started. Progress updates will appear here momentarily.
173
+ </Text>
174
+ <Loading />
175
+ </VStack>);
176
+ getProgress();
177
+ return;
178
+ }
93
179
 
94
180
  let results = <Text>Success</Text>;
95
181
  if (response.message) {
@@ -106,205 +192,223 @@ function AsyncOperation(props) {
106
192
  <Text>{message}</Text>;
107
193
  }
108
194
  showResults(results);
109
- },
110
- getFooter = (which = getMode()) => {
111
- switch(which) {
112
- case INITIATE:
113
- return <Toolbar>
114
- <Button
115
- text="Start"
116
- rightIcon={ChevronRight}
117
- onPress={() => initiate()}
118
- isDisabled={!getIsValid()}
119
- />
120
- </Toolbar>;
121
- case PROCESSING:
122
- return <Toolbar>
123
- <Button
124
- text="Please wait"
125
- isLoading={true}
126
- variant="link"
127
- />
128
- </Toolbar>;
129
- case RESULTS:
130
- return <Toolbar>
131
- <Button
132
- text="Reset"
133
- icon={ChevronLeft}
134
- onPress={() => resetToInitialState()}
135
- />
136
- </Toolbar>;
137
- }
138
- },
139
- operationsInProgress = useSelector((state) => state.app.operationsInProgress),
140
- isInProgress = operationsInProgress.includes(action),
141
- forceUpdate = useForceUpdate(),
142
- [footer, setFooter] = useState(getFooter()),
143
- [results, setResults] = useState(isInProgress ? 'Checking progress...' : null),
144
- [progress, setProgress] = useState(null),
145
- [isStuck, setIsStuck] = useState(false),
146
- [currentTabIx, setCurrentTab] = useState(isInProgress ? 1 : 0),
147
- previousProgressRef = useRef(null),
148
- unchangedProgressCountRef = useRef(0),
149
- intervalRef = useRef(null),
150
- formValuesRef = useRef(null),
151
- getPreviousProgress = () => {
152
- return previousProgressRef.current;
153
- },
154
- setPreviousProgress = (progress) => {
155
- previousProgressRef.current = progress;
156
- },
157
- getUnchangedProgressCount = () => {
158
- return unchangedProgressCountRef.current;
159
- },
160
- setUnchangedProgressCount = (count) => {
161
- unchangedProgressCountRef.current = count;
162
- forceUpdate();
163
- },
164
- getInterval = () => {
165
- return intervalRef.current;
166
- },
167
- setIntervalRef = (interval) => { // 'setInterval' is a reserved name
168
- intervalRef.current = interval;
169
- },
170
- getFormValues = () => {
171
- return formValuesRef.current;
172
- },
173
- setFormValues = (values) => {
174
- formValuesRef.current = values;
175
- },
176
- showResults = (results) => {
177
- setCurrentTab(1);
178
- setMode(RESULTS);
179
- setFooter(getFooter());
180
- setResults(results);
181
- getProgress();
195
+
182
196
  },
183
197
  getProgress = (immediately = false) => {
184
- if (getProgressUpdates) {
198
+ if (!getProgressUpdates) {
199
+ return;
200
+ }
201
+
202
+ async function fetchProgress(isInitial = false) {
203
+
204
+ setIsStuck(false);
205
+
206
+ const
207
+ method = Repository.methods.edit,
208
+ uri = Repository.getModel() + '/getProcessProgress',
209
+ data = {
210
+ process,
211
+ ...getFormValues(), // in case options submitted when starting the process affect the progress updates
212
+ },
213
+ result = await Repository._send(method, uri, data);
214
+
215
+ const response = Repository._processServerResponse(result);
216
+ if (!response.success) {
217
+ alert(response.message || 'Error getting progress info from server.');
218
+ stopGettingProgress();
219
+ return;
220
+ }
185
221
 
186
- async function fetchProgress() {
187
- const
188
- method = Repository.methods.edit,
189
- progressAction = 'get' + action.charAt(0).toUpperCase() + action.slice(1) + 'Progress',
190
- uri = Repository.getModel() + '/' + progressAction,
191
- result = await Repository._send(method, uri, getFormValues());
192
-
193
- const response = Repository._processServerResponse(result);
194
- if (!response.success) {
195
- alert(result.message);
196
- clearProgress();
197
- return;
222
+ const
223
+ progress = parseProgress ? parseProgress(response.root) : response.root,
224
+ {
225
+ status,
226
+ errors,
227
+ started,
228
+ lastUpdated,
229
+ timeElapsed,
230
+ count,
231
+ current,
232
+ total,
233
+ percentage,
234
+ message,
235
+ } = progress || {},
236
+ renderItems = [];
237
+ if (status === PROGRESS__NONE_FOUND) {
238
+ resetToInitialState();
239
+ setIsReady(true);
240
+ forceUpdate();
241
+ return;
242
+ }
243
+
244
+ let color = 'text-black',
245
+ statusMessage = '',
246
+ errorMessage = null;
247
+ if (status === PROGRESS__IN_PROCESS) {
248
+ setMode(PROCESSING);
249
+ color = 'text-green-600';
250
+ statusMessage = 'In process...';
251
+ } else {
252
+ setMode(RESULTS);
253
+ stopGettingProgress();
254
+ if (status === PROGRESS__COMPLETED) {
255
+ statusMessage = 'Completed';
256
+ } else if (status === PROGRESS__FAILED) {
257
+ color = 'text-red-400 font-bold';
258
+ statusMessage = 'Failed';
259
+ } else if (status === PROGRESS__STUCK) {
260
+ color = 'text-red-400 font-bold';
261
+ setIsStuck(true);
262
+ statusMessage = 'Stuck';
198
263
  }
264
+ }
199
265
 
200
- const progress = parseProgress ? parseProgress(response) : response.message
201
- if (progress === PROGRESS_COMPLETED) {
202
- clearProgress();
203
- setProgress(progress);
204
- } else {
205
- // in process
206
- let newUnchangedProgressCount = getUnchangedProgressCount();
207
- if (progress === getPreviousProgress()) {
208
- newUnchangedProgressCount++;
209
- setUnchangedProgressCount(newUnchangedProgressCount);
210
- if (progressStuckThreshold !== null && newUnchangedProgressCount >= progressStuckThreshold) {
211
- clearProgress();
212
- setProgress('The operation appears to be stuck.');
213
- setIsStuck(true);
214
- }
215
- } else {
216
- setPreviousProgress(progress);
217
- setProgress(progress);
218
- setUnchangedProgressCount(0);
219
- }
266
+ const className = 'text-lg';
267
+ renderItems.push(<Text className={className + ' ' + color} key="status">Status: {statusMessage}</Text>);
268
+ if (!_.isNil(percentage)) {
269
+ renderItems.push(<HStack className="mb-2" key="progress">
270
+ <Progress.Bar
271
+ animated={true}
272
+ progress={percentage / 100}
273
+ width={175}
274
+ height={20}
275
+ color="#666"
276
+ />
277
+ <Text className={className + ' pl-1'}>{percentage}%</Text>
278
+ </HStack>);
279
+ }
280
+ if (started) {
281
+ const startedMoment = moment(started);
282
+ if (startedMoment.isValid()) {
283
+ renderItems.push(<Text className={className} key="started">Started: {startedMoment.format(MOMENT_DATE_FORMAT_2)}</Text>);
220
284
  }
221
- };
222
-
223
- if (immediately) {
224
- fetchProgress();
225
285
  }
226
-
227
- const interval = setInterval(fetchProgress, updateInterval);
228
- setIntervalRef(interval);
286
+ if (lastUpdated) {
287
+ const updatedMoment = moment(lastUpdated);
288
+ if (updatedMoment.isValid()) {
289
+ renderItems.push(<Text className={className} key="lastUpdated">Last Updated: {updatedMoment.format(MOMENT_DATE_FORMAT_2)}</Text>);
290
+ }
291
+ }
292
+ if (timeElapsed) {
293
+ renderItems.push(<Text className={className} key="timeElapsed">Time Elapsed: {timeElapsed}</Text>);
294
+ }
295
+ if (!_.isNil(count) && count !== 0) {
296
+ renderItems.push(<Text className={className} key="count">Count: {count}</Text>);
297
+ }
298
+ if (!_.isNil(current) && !_.isNil(total) && current !== 0 && total !== 0) {
299
+ renderItems.push(<Text className={className} key="currentTotal">Current/Total: {current} / {total}</Text>);
300
+ }
301
+ if (!_.isNil(message)) {
302
+ renderItems.push(<Text className={className} key="message">{message}</Text>);
303
+ }
304
+ if (!_.isNil(errors)) {
305
+ renderItems.push(<VStack key="errors">
306
+ <Text className="text-red-400 font-bold">Errors:</Text>
307
+ {errors?.map((line, ix)=> {
308
+ return <Text key={ix}>{line}</Text>;
309
+ })}
310
+ </VStack>);
311
+ }
312
+ if (getMode() === PROCESSING) {
313
+ setProgress(renderItems);
314
+ } else {
315
+ setResults(renderItems);
316
+ }
317
+
318
+ setIsReady(true);
319
+ setFooter(getFooter());
320
+ forceUpdate();
321
+ };
322
+
323
+ let interval = getInterval();
324
+ if (interval) {
325
+ clearInterval(interval);
326
+ }
327
+ setIntervalRef(setInterval(fetchProgress, updateInterval));
328
+
329
+ if (immediately) {
330
+ fetchProgress(true); // isInitial
229
331
  }
230
332
  },
231
333
  resetToInitialState = () => {
232
- setCurrentTab(0);
233
- setMode(INITIATE);
334
+ setMode(START);
234
335
  setFooter(getFooter());
235
- clearProgress();
236
- },
237
- clearProgress = () => {
238
- setIsInProgress(false);
239
336
  setIsStuck(false);
240
- setProgress(null);
241
- setPreviousProgress(null);
242
- setUnchangedProgressCount(0);
337
+ stopGettingProgress();
338
+ },
339
+ stopGettingProgress = () => {
243
340
  clearInterval(getInterval());
244
341
  setIntervalRef(null);
245
342
  },
246
- setIsInProgress = (isInProgress) => {
247
- dispatch({
248
- type: 'app/setOperationsInProgress',
249
- payload: {
250
- operation: action,
251
- isInProgress,
252
- },
253
- });
254
- },
255
343
  onValidityChange = (isValid) => {
256
344
  setIsValid(isValid);
257
345
  setFooter(getFooter());
258
- },
259
- unchangedProgressCount = getUnchangedProgressCount();
346
+ };
260
347
 
261
348
  useEffect(() => {
262
-
263
- if (isInProgress) {
264
- getProgress(true); // true to fetch immediately
349
+ if (getProgressUpdates) {
350
+ getProgress(true);
351
+ } else {
352
+ setMode(START);
353
+ setIsReady(true);
265
354
  }
266
-
267
355
  return () => {
268
356
  // clear the interval when the component unmounts
269
- clearInterval(getInterval());
357
+ const interval = getInterval();
358
+ if (interval) {
359
+ clearInterval(interval);
360
+ }
270
361
  };
271
362
  }, []);
272
363
 
273
- return <Panel {...props} footer={footer}>
274
- <TabBar
275
- tabs={[
276
- {
277
- title: 'Start',
278
- icon: Play,
279
- isDisabled: currentTabIx !== 0,
280
- content: <Form
281
- reference="form"
282
- parent={self}
283
- className="w-full h-full flex-1"
284
- disableFooter={true}
285
- items={formItems}
286
- startingValues={formStartingValues}
287
- onValidityChange={onValidityChange}
288
- {..._form}
289
- />,
290
- },
291
- {
292
- title: 'Results',
293
- icon: isInProgress ? EllipsisHorizontal : Stop,
294
- isDisabled: currentTabIx !== 1,
295
- content: <ScrollView className="ScrollView h-full w-full">
296
- <Box className={`p-4 ${isStuck ? 'text-red-400 font-bold' : ''}`}>
297
- {progress ?
298
- progress + (unchangedProgressCount > 0 ? ' (unchanged x' + unchangedProgressCount + ')' : '') :
299
- results}
300
- </Box>
301
- </ScrollView>,
302
- },
303
- ]}
304
- currentTabIx={currentTabIx}
305
- canToggleCollapse={false}
306
- tabsAreButtons={false}
307
- />
364
+ return <Panel
365
+ {...props}
366
+ footer={footer}
367
+ >
368
+ {!isReady && <Loading />}
369
+ {isReady &&
370
+ <TabBar
371
+ tabs={[
372
+ {
373
+ title: 'Start',
374
+ icon: Play,
375
+ isDisabled: currentTabIx !== 0,
376
+ content: getMode() === INIT ?
377
+ <Loading /> :
378
+ <ScrollView className="ScrollView h-full w-full">
379
+ <Form
380
+ reference="form"
381
+ parent={self}
382
+ className="w-full h-full flex-1"
383
+ disableFooter={true}
384
+ items={formItems}
385
+ startingValues={formStartingValues}
386
+ onValidityChange={onValidityChange}
387
+ {..._form}
388
+ />
389
+ </ScrollView>,
390
+ },
391
+ {
392
+ title: 'Progress',
393
+ icon: EllipsisHorizontal,
394
+ isDisabled: currentTabIx !== 1,
395
+ content: <ScrollView className="ScrollView h-full w-full p-4">
396
+ {progress}
397
+ </ScrollView>,
398
+ },
399
+ {
400
+ title: 'Results',
401
+ icon: Stop,
402
+ isDisabled: currentTabIx !== 2,
403
+ content: <ScrollView className="ScrollView h-full w-full p-4">
404
+ {results}
405
+ </ScrollView>,
406
+ },
407
+ ]}
408
+ currentTabIx={currentTabIx}
409
+ canToggleCollapse={false}
410
+ tabsAreButtons={false}
411
+ />}
308
412
  </Panel>;
309
413
  }
310
414
 
@@ -11,13 +11,12 @@ import {
11
11
  setInfoMessage,
12
12
  selectProgressMessage,
13
13
  selectProgressPercentage,
14
- } from '@src/models/Slices/DebugSlice';
14
+ } from '../../Models/Slices/SystemSlice';
15
15
  import WaitMessage from './WaitMessage';
16
16
  import ErrorMessage from './ErrorMessage';
17
17
  import ProgressModal from './ProgressModal';
18
18
 
19
19
 
20
-
21
20
  export default function GlobalModals(props) {
22
21
  const {
23
22
  progressColor = '#666',
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useState, } from 'react';
2
2
  import {
3
+ Box,
3
4
  ScrollView,
4
5
  VStack,
5
6
  VStackNative,
@@ -110,11 +111,22 @@ function Panel(props) {
110
111
  if (maxHeight) {
111
112
  style.maxHeight = maxHeight;
112
113
  }
114
+
115
+ // Handle collapsed state and filter conflicting classes
116
+ let filteredClassName = props.className;
113
117
  if (isCollapsed) {
114
118
  if (collapseDirection === HORIZONTAL) {
119
+ if (filteredClassName) {
120
+ // Remove any width-related classes from props.className to prevent conflicts
121
+ filteredClassName = filteredClassName.replace(/\b(w-\S+|width-\S+|min-w-\S+|max-w-\S+)\b/g, '').trim();
122
+ }
115
123
  className += ' w-[33px]';
116
124
  delete style.width;
117
125
  } else {
126
+ if (filteredClassName) {
127
+ // Remove any height-related classes from props.className to prevent conflicts
128
+ filteredClassName = filteredClassName.replace(/\b(h-\S+|height-\S+|min-h-\S+|max-h-\S+)\b/g, '').trim();
129
+ }
118
130
  className += ' h-[33px]';
119
131
  delete style.height;
120
132
  }
@@ -123,8 +135,8 @@ function Panel(props) {
123
135
  // frame
124
136
  className += ' border-grey-300' + (isWindow ? ' rounded-lg shadow-lg ' : '') + (frame ? ' border-2' : ' border-none');
125
137
 
126
- if (props.className) {
127
- className += ' ' + props.className;
138
+ if (filteredClassName) {
139
+ className += ' ' + filteredClassName;
128
140
  }
129
141
 
130
142
  return <VStackNative
@@ -4,6 +4,6 @@ import Panel from './Panel.js';
4
4
 
5
5
  export default function TabPanel(props) {
6
6
  return <Panel className="w-full flex" {...props._panel}>
7
- <TabBar {...props} />
7
+ <TabBar {...props} {...props._tab} />
8
8
  </Panel>;
9
9
  }
@@ -25,7 +25,7 @@ export function TreePanel(props) {
25
25
  }
26
26
 
27
27
  return <Panel {...props._panel}>
28
- <WhichTree {...props} />
28
+ <WhichTree {...props} {...props._tree} />
29
29
  </Panel>;
30
30
  }
31
31