@onehat/ui 0.4.58 → 0.4.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -502,7 +502,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
502
502
|
setIsSaving(true);
|
|
503
503
|
let success = true;
|
|
504
504
|
const tempListener = (msg, data) => {
|
|
505
|
-
success =
|
|
505
|
+
success = false;
|
|
506
506
|
};
|
|
507
507
|
|
|
508
508
|
Repository.on('error', tempListener); // add a temporary listener for the error event
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { useState, } from 'react';
|
|
1
|
+
import { useState, useRef, useEffect, } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Box,
|
|
4
|
+
ScrollView,
|
|
4
5
|
Text,
|
|
5
6
|
VStack,
|
|
6
7
|
} from '@project-components/Gluestack';
|
|
8
|
+
import { useSelector, useDispatch, } from 'react-redux';
|
|
9
|
+
import { PROGRESS_COMPLETED } from '../../Constants/Progress.js';
|
|
10
|
+
import useForceUpdate from '../../Hooks/useForceUpdate.js';
|
|
7
11
|
import isJson from '../../Functions/isJson.js';
|
|
8
12
|
import Form from '../Form/Form.js';
|
|
9
13
|
import Button from '../Buttons/Button.js';
|
|
@@ -12,19 +16,31 @@ import withAlert from '../Hoc/withAlert.js';
|
|
|
12
16
|
import ChevronLeft from '../Icons/ChevronLeft.js';
|
|
13
17
|
import ChevronRight from '../Icons/ChevronRight.js';
|
|
14
18
|
import Play from '../Icons/Play.js';
|
|
19
|
+
import EllipsisHorizontal from '../Icons/EllipsisHorizontal.js';
|
|
15
20
|
import Stop from '../Icons/Stop.js';
|
|
16
21
|
import TabBar from '../Tab/TabBar.js';
|
|
17
22
|
import Panel from '../Panel/Panel.js';
|
|
18
23
|
import Toolbar from '../Toolbar/Toolbar.js';
|
|
19
24
|
import _ from 'lodash';
|
|
20
25
|
|
|
26
|
+
// NOTE: This component assumes you have an AppSlice, that has
|
|
27
|
+
// an 'operationsInProgress' state var and a 'setOperationsInProgress' action.
|
|
21
28
|
|
|
22
29
|
function AsyncOperation(props) {
|
|
30
|
+
|
|
31
|
+
if (!props.Repository || !props.action) {
|
|
32
|
+
throw Error('AsyncOperation: Repository and action are required!');
|
|
33
|
+
}
|
|
34
|
+
|
|
23
35
|
const {
|
|
24
36
|
action,
|
|
25
37
|
Repository,
|
|
26
38
|
formItems = [],
|
|
27
39
|
formStartingValues = {},
|
|
40
|
+
getProgressUpdates = false,
|
|
41
|
+
parseProgress, // optional fn, accepts 'response' as arg and returns progress string
|
|
42
|
+
progressStuckThreshold = null, // e.g. 3, if left blank, doesn't check for stuck state
|
|
43
|
+
updateInterval = 10000, // ms
|
|
28
44
|
|
|
29
45
|
// withComponent
|
|
30
46
|
self,
|
|
@@ -32,25 +48,24 @@ function AsyncOperation(props) {
|
|
|
32
48
|
// withAlert
|
|
33
49
|
alert,
|
|
34
50
|
} = props,
|
|
51
|
+
dispatch = useDispatch(),
|
|
35
52
|
initiate = async () => {
|
|
36
53
|
|
|
37
|
-
|
|
38
|
-
alert('AsyncOperation: Repository and action are required!');
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
54
|
+
clearProgress();
|
|
42
55
|
setFooter(getFooter('processing'));
|
|
56
|
+
setIsInProgress(true);
|
|
43
57
|
|
|
44
58
|
const
|
|
45
59
|
method = Repository.methods.edit,
|
|
46
60
|
uri = Repository.getModel() + '/' + action,
|
|
47
|
-
formValues = self
|
|
61
|
+
formValues = self?.children?.form?.formGetValues() || {},
|
|
48
62
|
result = await Repository._send(method, uri, formValues);
|
|
49
|
-
|
|
63
|
+
|
|
64
|
+
setFormValues(formValues);
|
|
65
|
+
|
|
50
66
|
const response = Repository._processServerResponse(result);
|
|
51
67
|
if (!response.success) {
|
|
52
|
-
|
|
53
|
-
reset();
|
|
68
|
+
resetToInitialState();
|
|
54
69
|
return;
|
|
55
70
|
}
|
|
56
71
|
|
|
@@ -75,7 +90,7 @@ function AsyncOperation(props) {
|
|
|
75
90
|
case 'initiate':
|
|
76
91
|
return <Toolbar>
|
|
77
92
|
<Button
|
|
78
|
-
text="
|
|
93
|
+
text="Start"
|
|
79
94
|
rightIcon={ChevronRight}
|
|
80
95
|
onPress={() => initiate()}
|
|
81
96
|
/>
|
|
@@ -93,29 +108,144 @@ function AsyncOperation(props) {
|
|
|
93
108
|
<Button
|
|
94
109
|
text="Reset"
|
|
95
110
|
icon={ChevronLeft}
|
|
96
|
-
onPress={() =>
|
|
111
|
+
onPress={() => resetToInitialState()}
|
|
97
112
|
/>
|
|
98
113
|
</Toolbar>;
|
|
99
114
|
}
|
|
100
115
|
},
|
|
116
|
+
operationsInProgress = useSelector((state) => state.app.operationsInProgress),
|
|
117
|
+
isInProgress = operationsInProgress.includes(action),
|
|
118
|
+
forceUpdate = useForceUpdate(),
|
|
101
119
|
[footer, setFooter] = useState(getFooter()),
|
|
102
|
-
[results, setResults] = useState(''),
|
|
103
|
-
[
|
|
120
|
+
[results, setResults] = useState(isInProgress ? 'Checking progress...' : null),
|
|
121
|
+
[progress, setProgress] = useState(null),
|
|
122
|
+
[isStuck, setIsStuck] = useState(false),
|
|
123
|
+
[currentTabIx, setCurrentTab] = useState(isInProgress ? 1 : 0),
|
|
124
|
+
previousProgressRef = useRef(null),
|
|
125
|
+
unchangedProgressCountRef = useRef(0),
|
|
126
|
+
intervalRef = useRef(null),
|
|
127
|
+
formValuesRef = useRef(null),
|
|
128
|
+
getPreviousProgress = () => {
|
|
129
|
+
return previousProgressRef.current;
|
|
130
|
+
},
|
|
131
|
+
setPreviousProgress = (progress) => {
|
|
132
|
+
previousProgressRef.current = progress;
|
|
133
|
+
},
|
|
134
|
+
getUnchangedProgressCount = () => {
|
|
135
|
+
return unchangedProgressCountRef.current;
|
|
136
|
+
},
|
|
137
|
+
setUnchangedProgressCount = (count) => {
|
|
138
|
+
unchangedProgressCountRef.current = count;
|
|
139
|
+
forceUpdate();
|
|
140
|
+
},
|
|
141
|
+
getInterval = () => {
|
|
142
|
+
return intervalRef.current;
|
|
143
|
+
},
|
|
144
|
+
setIntervalRef = (interval) => { // 'setInterval' is a reserved name
|
|
145
|
+
intervalRef.current = interval;
|
|
146
|
+
},
|
|
147
|
+
getFormValues = () => {
|
|
148
|
+
return formValuesRef.current;
|
|
149
|
+
},
|
|
150
|
+
setFormValues = (values) => {
|
|
151
|
+
formValuesRef.current = values;
|
|
152
|
+
},
|
|
104
153
|
showResults = (results) => {
|
|
105
154
|
setCurrentTab(1);
|
|
106
155
|
setFooter(getFooter('results'));
|
|
107
156
|
setResults(results);
|
|
157
|
+
getProgress();
|
|
108
158
|
},
|
|
109
|
-
|
|
159
|
+
getProgress = (immediately = false) => {
|
|
160
|
+
if (getProgressUpdates) {
|
|
161
|
+
|
|
162
|
+
async function fetchProgress() {
|
|
163
|
+
const
|
|
164
|
+
method = Repository.methods.edit,
|
|
165
|
+
progressAction = 'get' + action.charAt(0).toUpperCase() + action.slice(1) + 'Progress',
|
|
166
|
+
uri = Repository.getModel() + '/' + progressAction,
|
|
167
|
+
result = await Repository._send(method, uri, getFormValues());
|
|
168
|
+
|
|
169
|
+
const response = Repository._processServerResponse(result);
|
|
170
|
+
if (!response.success) {
|
|
171
|
+
alert(result.message);
|
|
172
|
+
clearProgress();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const progress = parseProgress ? parseProgress(response) : response.message
|
|
177
|
+
if (progress === PROGRESS_COMPLETED) {
|
|
178
|
+
clearProgress();
|
|
179
|
+
setProgress(progress);
|
|
180
|
+
} else {
|
|
181
|
+
// in process
|
|
182
|
+
let newUnchangedProgressCount = getUnchangedProgressCount();
|
|
183
|
+
if (progress === getPreviousProgress()) {
|
|
184
|
+
newUnchangedProgressCount++;
|
|
185
|
+
setUnchangedProgressCount(newUnchangedProgressCount);
|
|
186
|
+
if (progressStuckThreshold !== null && newUnchangedProgressCount >= progressStuckThreshold) {
|
|
187
|
+
clearProgress();
|
|
188
|
+
setProgress('The operation appears to be stuck.');
|
|
189
|
+
setIsStuck(true);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
setPreviousProgress(progress);
|
|
193
|
+
setProgress(progress);
|
|
194
|
+
setUnchangedProgressCount(0);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
if (immediately) {
|
|
200
|
+
fetchProgress();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const interval = setInterval(fetchProgress, updateInterval);
|
|
204
|
+
setIntervalRef(interval);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
resetToInitialState = () => {
|
|
110
208
|
setCurrentTab(0);
|
|
111
209
|
setFooter(getFooter());
|
|
210
|
+
clearProgress();
|
|
211
|
+
},
|
|
212
|
+
clearProgress = () => {
|
|
213
|
+
setIsInProgress(false);
|
|
214
|
+
setIsStuck(false);
|
|
215
|
+
setProgress(null);
|
|
216
|
+
setPreviousProgress(null);
|
|
217
|
+
setUnchangedProgressCount(0);
|
|
218
|
+
clearInterval(getInterval());
|
|
219
|
+
setIntervalRef(null);
|
|
220
|
+
},
|
|
221
|
+
setIsInProgress = (isInProgress) => {
|
|
222
|
+
dispatch({
|
|
223
|
+
type: 'app/setOperationsInProgress',
|
|
224
|
+
payload: {
|
|
225
|
+
operation: action,
|
|
226
|
+
isInProgress,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
unchangedProgressCount = getUnchangedProgressCount();
|
|
231
|
+
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
|
|
234
|
+
if (isInProgress) {
|
|
235
|
+
getProgress(true); // true to fetch immediately
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return () => {
|
|
239
|
+
// clear the interval when the component unmounts
|
|
240
|
+
clearInterval(getInterval());
|
|
112
241
|
};
|
|
242
|
+
}, []);
|
|
113
243
|
|
|
114
244
|
return <Panel {...props} footer={footer}>
|
|
115
245
|
<TabBar
|
|
116
246
|
tabs={[
|
|
117
247
|
{
|
|
118
|
-
title: '
|
|
248
|
+
title: 'Start',
|
|
119
249
|
icon: Play,
|
|
120
250
|
isDisabled: currentTabIx !== 0,
|
|
121
251
|
content: <Form
|
|
@@ -129,9 +259,15 @@ function AsyncOperation(props) {
|
|
|
129
259
|
},
|
|
130
260
|
{
|
|
131
261
|
title: 'Results',
|
|
132
|
-
icon: Stop,
|
|
262
|
+
icon: isInProgress ? EllipsisHorizontal : Stop,
|
|
133
263
|
isDisabled: currentTabIx !== 1,
|
|
134
|
-
content: <
|
|
264
|
+
content: <ScrollView className="ScrollView h-full w-full">
|
|
265
|
+
<Box className={`p-4 ${isStuck ? 'text-red-400 font-bold' : ''}`}>
|
|
266
|
+
{progress ?
|
|
267
|
+
progress + (unchangedProgressCount > 0 ? ' (unchanged x' + unchangedProgressCount + ')' : '') :
|
|
268
|
+
results}
|
|
269
|
+
</Box>
|
|
270
|
+
</ScrollView>,
|
|
135
271
|
},
|
|
136
272
|
]}
|
|
137
273
|
currentTabIx={currentTabIx}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PROGRESS_COMPLETED = 'Completed';
|