@onehat/ui 0.3.183 → 0.3.187
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 +3 -2
- package/src/Components/Form/Form.js +9 -1
- package/src/Components/Hoc/withFilters.js +4 -0
- package/src/Components/Hoc/withPresetButtons.js +52 -14
- package/src/Components/Panel/Panel.js +2 -1
- package/src/Components/Viewer/Viewer.js +9 -1
- package/src/Components/Window/UploadsDownloadsWindow.js +189 -0
- package/src/Functions/downloadWithFetch.js +6 -3
- package/src/PlatformImports/Web/File.js +93 -213
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onehat/ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.187",
|
|
4
4
|
"description": "Base UI for OneHat apps",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
"react-draggable": "^4.4.5",
|
|
50
50
|
"react-native": "*",
|
|
51
51
|
"react-native-draggable": "^3.3.0",
|
|
52
|
-
"react-native-svg": "*"
|
|
52
|
+
"react-native-svg": "*",
|
|
53
|
+
"use-file-picker": "^2.1.1"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@babel/core": "^7.22.1",
|
|
@@ -287,7 +287,15 @@ function Form(props) {
|
|
|
287
287
|
return <Row>{elements}</Row>;
|
|
288
288
|
},
|
|
289
289
|
buildFromItems = () => {
|
|
290
|
-
|
|
290
|
+
const builtItems = [];
|
|
291
|
+
_.each(items, (item, ix) => {
|
|
292
|
+
if (!item) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const builtItem = buildFromItem(item, ix, columnDefaults);
|
|
296
|
+
builtItems.push(builtItem);
|
|
297
|
+
});
|
|
298
|
+
return builtItems;
|
|
291
299
|
},
|
|
292
300
|
buildFromItem = (item, ix, defaults) => {
|
|
293
301
|
if (React.isValidElement(item)) {
|
|
@@ -297,6 +297,10 @@ export default function withFilters(WrappedComponent) {
|
|
|
297
297
|
elementProps.autoSubmit = true;
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
|
+
if (!Element) {
|
|
301
|
+
debugger;
|
|
302
|
+
return; // to protect against errors
|
|
303
|
+
}
|
|
300
304
|
if (field === 'q') {
|
|
301
305
|
elementProps.flex = 1;
|
|
302
306
|
elementProps.minWidth = 100;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect, } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
|
+
} from 'native-base';
|
|
2
5
|
import Clipboard from '../Icons/Clipboard.js';
|
|
3
6
|
import Duplicate from '../Icons/Duplicate.js';
|
|
4
7
|
import Edit from '../Icons/Edit.js';
|
|
@@ -6,7 +9,9 @@ import Eye from '../Icons/Eye.js';
|
|
|
6
9
|
import Trash from '../Icons/Trash.js';
|
|
7
10
|
import Plus from '../Icons/Plus.js';
|
|
8
11
|
import Print from '../Icons/Print.js';
|
|
12
|
+
import UploadDownload from '../Icons/UploadDownload.js';
|
|
9
13
|
import inArray from '../../Functions/inArray.js';
|
|
14
|
+
import UploadsDownloadsWindow from '../Window/UploadsDownloadsWindow.js';
|
|
10
15
|
import _ from 'lodash';
|
|
11
16
|
|
|
12
17
|
// Note: A 'present button' will create both a context menu item
|
|
@@ -20,6 +25,7 @@ const presetButtons = [
|
|
|
20
25
|
'copy',
|
|
21
26
|
'duplicate',
|
|
22
27
|
// 'print',
|
|
28
|
+
'uploadDownload',
|
|
23
29
|
];
|
|
24
30
|
|
|
25
31
|
export default function withPresetButtons(WrappedComponent, isGrid = false) {
|
|
@@ -34,6 +40,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
|
|
|
34
40
|
// extract and pass
|
|
35
41
|
contextMenuItems = [],
|
|
36
42
|
additionalToolbarButtons = [],
|
|
43
|
+
useUploadDownload = false,
|
|
37
44
|
onChangeColumnsConfig,
|
|
38
45
|
verifyCanEdit,
|
|
39
46
|
verifyCanDelete,
|
|
@@ -60,6 +67,9 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
|
|
|
60
67
|
// withComponent
|
|
61
68
|
self,
|
|
62
69
|
|
|
70
|
+
// withData
|
|
71
|
+
Repository,
|
|
72
|
+
|
|
63
73
|
// withEditor
|
|
64
74
|
userCanEdit = true,
|
|
65
75
|
userCanView = true,
|
|
@@ -77,6 +87,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
|
|
|
77
87
|
selectorSelected,
|
|
78
88
|
} = props,
|
|
79
89
|
[isReady, setIsReady] = useState(false),
|
|
90
|
+
[isModalShown, setIsModalShown] = useState(false),
|
|
80
91
|
[localContextMenuItems, setLocalContextMenuItems] = useState([]),
|
|
81
92
|
[localAdditionalToolbarButtons, setLocalAdditionalToolbarButtons] = useState([]),
|
|
82
93
|
[localColumnsConfig, setLocalColumnsConfig] = useState([]),
|
|
@@ -124,6 +135,11 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
|
|
|
124
135
|
isDisabled = true;
|
|
125
136
|
}
|
|
126
137
|
break;
|
|
138
|
+
case 'uploadDownload':
|
|
139
|
+
if (!useUploadDownload) {
|
|
140
|
+
isDisabled = true;
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
127
143
|
default:
|
|
128
144
|
}
|
|
129
145
|
return isDisabled;
|
|
@@ -224,6 +240,12 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
|
|
|
224
240
|
// handler = onPrint;
|
|
225
241
|
// icon = <Print />;
|
|
226
242
|
// break;
|
|
243
|
+
case 'uploadDownload':
|
|
244
|
+
key = 'uploadDownloadBtn';
|
|
245
|
+
text = 'Upload/Download';
|
|
246
|
+
handler = onUploadDownload;
|
|
247
|
+
icon = <UploadDownload />;
|
|
248
|
+
break;
|
|
227
249
|
default:
|
|
228
250
|
}
|
|
229
251
|
return {
|
|
@@ -282,7 +304,9 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
|
|
|
282
304
|
if (showInfo) {
|
|
283
305
|
showInfo('Copied to clipboard!');
|
|
284
306
|
}
|
|
285
|
-
}
|
|
307
|
+
},
|
|
308
|
+
onUploadDownload = () => setIsModalShown(true),
|
|
309
|
+
onModalClose = () => setIsModalShown(false);
|
|
286
310
|
// onPrint = () => {
|
|
287
311
|
// debugger;
|
|
288
312
|
// };
|
|
@@ -298,18 +322,32 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
|
|
|
298
322
|
return null;
|
|
299
323
|
}
|
|
300
324
|
|
|
301
|
-
return
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
325
|
+
return <>
|
|
326
|
+
<WrappedComponent
|
|
327
|
+
{...propsToPass}
|
|
328
|
+
disablePresetButtons={false}
|
|
329
|
+
contextMenuItems={[
|
|
330
|
+
...contextMenuItems,
|
|
331
|
+
...localContextMenuItems,
|
|
332
|
+
]}
|
|
333
|
+
additionalToolbarButtons={[
|
|
334
|
+
...additionalToolbarButtons,
|
|
335
|
+
...localAdditionalToolbarButtons,
|
|
336
|
+
]}
|
|
337
|
+
onChangeColumnsConfig={onChangeColumnsConfigDecorator}
|
|
338
|
+
/>
|
|
339
|
+
{isModalShown &&
|
|
340
|
+
<Modal
|
|
341
|
+
isOpen={true}
|
|
342
|
+
onClose={onModalClose}
|
|
343
|
+
>
|
|
344
|
+
<UploadsDownloadsWindow
|
|
345
|
+
reference="uploadsDownloads"
|
|
346
|
+
onClose={onModalClose}
|
|
347
|
+
Repository={Repository}
|
|
348
|
+
columnsConfig={props.columnsConfig}
|
|
349
|
+
/>
|
|
350
|
+
</Modal>}
|
|
351
|
+
</>;
|
|
314
352
|
};
|
|
315
353
|
}
|
|
@@ -13,6 +13,7 @@ import Inflector from 'inflector-js';
|
|
|
13
13
|
import Header from './Header.js';
|
|
14
14
|
import Mask from './Mask.js';
|
|
15
15
|
import withCollapsible from '../Hoc/withCollapsible.js';
|
|
16
|
+
import withComponent from '../Hoc/withComponent.js';
|
|
16
17
|
import emptyFn from '../../Functions/emptyFn.js';
|
|
17
18
|
import UiGlobals from '../../UiGlobals.js';
|
|
18
19
|
import _ from 'lodash';
|
|
@@ -169,4 +170,4 @@ function Panel(props) {
|
|
|
169
170
|
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
export default withCollapsible(Panel);
|
|
173
|
+
export default withComponent(withCollapsible(Panel));
|
|
@@ -57,7 +57,15 @@ function Viewer(props) {
|
|
|
57
57
|
styles = UiGlobals.styles,
|
|
58
58
|
flex = props.flex || 1,
|
|
59
59
|
buildFromItems = () => {
|
|
60
|
-
|
|
60
|
+
const builtItems = [];
|
|
61
|
+
_.each(items, (item, ix) => {
|
|
62
|
+
if (!item) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const builtItem = buildFromItem(item, ix, columnDefaults);
|
|
66
|
+
builtItems.push(builtItem);
|
|
67
|
+
});
|
|
68
|
+
return builtItems;
|
|
61
69
|
},
|
|
62
70
|
buildFromItem = (item, ix, defaults) => {
|
|
63
71
|
let {
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COPYRIGHT NOTICE
|
|
3
|
+
* This file is categorized as "Custom Source Code"
|
|
4
|
+
* and is subject to the terms and conditions defined in the
|
|
5
|
+
* "LICENSE.txt" file, which is part of this source code package.
|
|
6
|
+
*/
|
|
7
|
+
import { useState, } from 'react';
|
|
8
|
+
import {
|
|
9
|
+
Icon,
|
|
10
|
+
} from 'native-base';
|
|
11
|
+
import Excel from '../Icons/Excel';
|
|
12
|
+
import Panel from '../Panel/Panel.js';
|
|
13
|
+
import Form from '../Form/Form.js';
|
|
14
|
+
import useAdjustedWindowSize from '../../Hooks/useAdjustedWindowSize.js';
|
|
15
|
+
import downloadWithFetch from '../../Functions/downloadWithFetch.js';
|
|
16
|
+
import withAlert from '../Hoc/withAlert.js';
|
|
17
|
+
import withComponent from '../Hoc/withComponent.js';
|
|
18
|
+
import Cookies from 'js-cookie';
|
|
19
|
+
import _ from 'lodash';
|
|
20
|
+
|
|
21
|
+
function UploadsDownloadsWindow(props) {
|
|
22
|
+
const
|
|
23
|
+
{
|
|
24
|
+
Repository,
|
|
25
|
+
columnsConfig = [],
|
|
26
|
+
|
|
27
|
+
// withComponent
|
|
28
|
+
self,
|
|
29
|
+
|
|
30
|
+
// withAlert
|
|
31
|
+
alert,
|
|
32
|
+
showInfo,
|
|
33
|
+
} = props,
|
|
34
|
+
[importFile, setImportFile] = useState(null),
|
|
35
|
+
[width, height] = useAdjustedWindowSize(400, 450),
|
|
36
|
+
onDownload = (isTemplate = false) => {
|
|
37
|
+
const
|
|
38
|
+
baseURL = Repository.api.baseURL,
|
|
39
|
+
filters = Repository.filters.reduce((result, current) => {
|
|
40
|
+
result[current.name] = current.value;
|
|
41
|
+
return result;
|
|
42
|
+
}, {}),
|
|
43
|
+
columns = columnsConfig.map((column) => {
|
|
44
|
+
return column.fieldName;
|
|
45
|
+
}),
|
|
46
|
+
order = Repository.getSortField() + ' ' + Repository.getSortDirection(),
|
|
47
|
+
model = Repository.name,
|
|
48
|
+
url = baseURL + 'Reports/getReport',
|
|
49
|
+
download_token = 'dl' + (new Date()).getTime(),
|
|
50
|
+
options = {
|
|
51
|
+
// method: 'GET',
|
|
52
|
+
method: 'POST',
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
download_token,
|
|
55
|
+
report_id: 1,
|
|
56
|
+
filters,
|
|
57
|
+
columns,
|
|
58
|
+
order,
|
|
59
|
+
model,
|
|
60
|
+
isTemplate,
|
|
61
|
+
}),
|
|
62
|
+
headers: _.merge({ 'Content-Type': 'application/json' }, Repository.headers),
|
|
63
|
+
},
|
|
64
|
+
fetchWindow = downloadWithFetch(url, options),
|
|
65
|
+
interval = setInterval(function() {
|
|
66
|
+
const cookie = Cookies.get(download_token);
|
|
67
|
+
if (fetchWindow.window && cookie) {
|
|
68
|
+
clearInterval(interval);
|
|
69
|
+
Cookies.remove(download_token);
|
|
70
|
+
fetchWindow.window.close();
|
|
71
|
+
}
|
|
72
|
+
}, 1000);
|
|
73
|
+
},
|
|
74
|
+
onDownloadTemplate = () => {
|
|
75
|
+
onDownload(true);
|
|
76
|
+
},
|
|
77
|
+
onUpload = async () => {
|
|
78
|
+
const
|
|
79
|
+
url = Repository.api.baseURL + Repository.name + '/uploadBatch',
|
|
80
|
+
result = await Repository._send('POST', url, { importFile })
|
|
81
|
+
.catch(error => {
|
|
82
|
+
if (Repository.debugMode) {
|
|
83
|
+
console.log(url + ' error', error);
|
|
84
|
+
console.log('response:', error.response);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
if (Repository.debugMode) {
|
|
88
|
+
console.log('Result ' + url, result);
|
|
89
|
+
}
|
|
90
|
+
const
|
|
91
|
+
parsed = JSON.parse(result.data),
|
|
92
|
+
{
|
|
93
|
+
data,
|
|
94
|
+
success,
|
|
95
|
+
message,
|
|
96
|
+
} = parsed;
|
|
97
|
+
if (!success) {
|
|
98
|
+
const msgElements = ['Could not upload.'];
|
|
99
|
+
if (message === 'Errors') {
|
|
100
|
+
// assemble the errors from the upload
|
|
101
|
+
_.each(data, (obj) => {
|
|
102
|
+
// {
|
|
103
|
+
// "2": "ID does not exist."
|
|
104
|
+
// }
|
|
105
|
+
const line = Object.entries(obj)
|
|
106
|
+
.map(([key, value]) => `Line ${key}: ${value}`)
|
|
107
|
+
.join("\n");
|
|
108
|
+
msgElements.push(line);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
alert(msgElements.join("\n"));
|
|
112
|
+
} else {
|
|
113
|
+
setImportFile(null);
|
|
114
|
+
self.formSetValue('file', null);
|
|
115
|
+
showInfo("Upload successful.\n");
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return <Panel
|
|
120
|
+
{...props}
|
|
121
|
+
parent={self}
|
|
122
|
+
reference="UploadsDownloadsWindow"
|
|
123
|
+
isCollapsible={false}
|
|
124
|
+
title="Uploads & Downloads"
|
|
125
|
+
bg="#fff"
|
|
126
|
+
w={width}
|
|
127
|
+
h={height}
|
|
128
|
+
flex={null}
|
|
129
|
+
>
|
|
130
|
+
<Form
|
|
131
|
+
{...props}
|
|
132
|
+
parent={self}
|
|
133
|
+
reference="form"
|
|
134
|
+
items={[
|
|
135
|
+
{
|
|
136
|
+
"type": "Column",
|
|
137
|
+
"flex": 1,
|
|
138
|
+
"defaults": {},
|
|
139
|
+
"items": [
|
|
140
|
+
{
|
|
141
|
+
type: 'DisplayField',
|
|
142
|
+
text: 'Download an Excel file of the current grid contents.',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'Button',
|
|
146
|
+
text: 'Download',
|
|
147
|
+
isEditable: false,
|
|
148
|
+
leftIcon: <Icon as={Excel} />,
|
|
149
|
+
onPress: () => onDownload(),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: 'DisplayField',
|
|
153
|
+
text: 'Upload an Excel file to the current grid.',
|
|
154
|
+
mt: 10,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
type: 'File',
|
|
158
|
+
name: 'file',
|
|
159
|
+
onChangeValue: setImportFile,
|
|
160
|
+
accept: '.xlsx',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
type: 'Button',
|
|
164
|
+
text: 'Upload',
|
|
165
|
+
isEditable: false,
|
|
166
|
+
leftIcon: <Icon as={Excel} />,
|
|
167
|
+
isDisabled: !importFile,
|
|
168
|
+
onPress: onUpload,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
type: 'Button',
|
|
172
|
+
text: 'Get Template',
|
|
173
|
+
isEditable: false,
|
|
174
|
+
onPress: onDownloadTemplate,
|
|
175
|
+
variant: 'ghost',
|
|
176
|
+
},
|
|
177
|
+
]
|
|
178
|
+
},
|
|
179
|
+
]}
|
|
180
|
+
// record={selection}
|
|
181
|
+
// onCancel={onCancel}
|
|
182
|
+
// onSave={onSave}
|
|
183
|
+
// onClose={onClose}
|
|
184
|
+
// onDelete={onDelete}
|
|
185
|
+
/>
|
|
186
|
+
</Panel>;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default withComponent(withAlert(UploadsDownloadsWindow));
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
const downloadWithFetch = (url, options = {}) => {
|
|
2
|
+
let obj = {};
|
|
2
3
|
fetch(url, options)
|
|
3
4
|
.then( res => res.blob() )
|
|
4
5
|
.then( blob => {
|
|
5
6
|
const
|
|
6
7
|
winName = 'ReportWindow',
|
|
7
8
|
opts = 'resizable=yes,height=600,width=800,location=0,menubar=0,scrollbars=1',
|
|
8
|
-
|
|
9
|
-
file =
|
|
10
|
-
|
|
9
|
+
win = window.open('', winName, opts),
|
|
10
|
+
file = win.URL.createObjectURL(blob);
|
|
11
|
+
obj.window = win;
|
|
12
|
+
win.location.assign(file);
|
|
11
13
|
});
|
|
14
|
+
return obj;
|
|
12
15
|
};
|
|
13
16
|
export default downloadWithFetch;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, } from 'react';
|
|
2
2
|
import {
|
|
3
|
-
Box,
|
|
4
3
|
Button,
|
|
5
4
|
Column,
|
|
6
|
-
Pressable,
|
|
7
5
|
Row,
|
|
8
|
-
Spinner,
|
|
9
6
|
Text,
|
|
10
7
|
} from 'native-base';
|
|
11
8
|
import {
|
|
@@ -14,52 +11,22 @@ import {
|
|
|
14
11
|
UI_MODE_REACT_NATIVE,
|
|
15
12
|
} from '../../Constants/UiModes.js';
|
|
16
13
|
import UiGlobals from '../../UiGlobals.js';
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
// import {
|
|
15
|
+
// FileAmountLimitValidator,
|
|
16
|
+
// FileTypeValidator,
|
|
17
|
+
// FileSizeValidator,
|
|
18
|
+
// ImageDimensionsValidator,
|
|
19
|
+
// } from 'use-file-picker/validators';
|
|
20
|
+
import { useFilePicker, useImperativeFilePicker } from 'use-file-picker'; // https://www.npmjs.com/package/use-file-picker
|
|
23
21
|
import IconButton from '../../Components/Buttons/IconButton.js';
|
|
24
22
|
import Xmark from '../../Components/Icons/Xmark.js'
|
|
25
23
|
import withAlert from '../../Components/Hoc/withAlert.js';
|
|
26
24
|
import withValue from '../../Components/Hoc/withValue.js';
|
|
27
|
-
import
|
|
25
|
+
import Loading from '../../Components/Messages/Loading.js';
|
|
28
26
|
import _ from 'lodash';
|
|
29
27
|
|
|
30
|
-
const
|
|
31
|
-
EXPANDED_MAX = 100,
|
|
32
|
-
COLLAPSED_MAX = 2;
|
|
33
|
-
|
|
34
|
-
function FileCardCustom(props) {
|
|
35
|
-
const
|
|
36
|
-
{
|
|
37
|
-
id,
|
|
38
|
-
name: filename,
|
|
39
|
-
type: mimetype,
|
|
40
|
-
onDelete,
|
|
41
|
-
downloadUrl,
|
|
42
|
-
uploadStatus,
|
|
43
|
-
} = props,
|
|
44
|
-
isDownloading = uploadStatus && inArray(uploadStatus, ['preparing', 'uploading', 'success']);
|
|
45
|
-
return <Pressable
|
|
46
|
-
px={3}
|
|
47
|
-
py={1}
|
|
48
|
-
alignItems="center"
|
|
49
|
-
flexDirection="row"
|
|
50
|
-
borderRadius={5}
|
|
51
|
-
borderWidth={1}
|
|
52
|
-
borderColor="primary.700"
|
|
53
|
-
onPress={() => {
|
|
54
|
-
downloadWithFetch(downloadUrl);
|
|
55
|
-
}}
|
|
56
|
-
>
|
|
57
|
-
{isDownloading && <Spinner mr={2} />}
|
|
58
|
-
<Text>{filename}</Text>
|
|
59
|
-
{onDelete && <IconButton ml={1} icon={Xmark} onPress={() => onDelete(id)} />}
|
|
60
|
-
</Pressable>;
|
|
61
|
-
}
|
|
62
28
|
|
|
29
|
+
// This component is used to present a single file upload button
|
|
63
30
|
|
|
64
31
|
function FileComponent(props) {
|
|
65
32
|
|
|
@@ -68,186 +35,99 @@ function FileComponent(props) {
|
|
|
68
35
|
}
|
|
69
36
|
|
|
70
37
|
const {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
selectorSelected,
|
|
38
|
+
encodeAsBase64 = true,
|
|
39
|
+
readAs = 'BinaryString', // 'DataURL', 'Text', 'BinaryString', 'ArrayBuffer'
|
|
40
|
+
accept, // ['.png', '.txt'], 'image/*', '.txt'
|
|
41
|
+
multiple = false,
|
|
42
|
+
readFilesContent = true, // Ignores files content and omits reading process if set to false
|
|
43
|
+
validators, // [ new FileAmountLimitValidator({ max: 1 }), new FileTypeValidator(['jpg', 'png']), new FileSizeValidator({ maxFileSize: 50 * 1024 * 1024 /* 50 MB */ }), new ImageDimensionsValidator({maxHeight: 900,maxWidth: 1600,minHeight: 600,minWidth: 768,}),]
|
|
44
|
+
onFilesSelected, // always called, even if there are errors
|
|
45
|
+
onFilesRejected, // called when there were validation errors
|
|
46
|
+
onFilesSuccessfullySelected, // called when there were no validation errors
|
|
47
|
+
onFileRemoved, // called when a file is removed from the list of selected files
|
|
48
|
+
onClear, // called when the selection is cleared
|
|
83
49
|
|
|
84
50
|
// withValue
|
|
85
51
|
value,
|
|
86
52
|
setValue,
|
|
87
|
-
|
|
88
|
-
// withAlert
|
|
89
|
-
alert,
|
|
90
|
-
confirm,
|
|
91
|
-
|
|
92
53
|
} = props,
|
|
93
54
|
styles = UiGlobals.styles,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// imageUrl: entity.attachments__uri, // string A string representation or web url of the image that will be set to the "src" prop of an <img/> tag. If given, the component will use this image source instead of reading the image file.
|
|
114
|
-
// downloadUrl: entity.attachments__uri, // string The url to be used to perform a GET request in order to download the file. If defined, the download icon will be shown.
|
|
115
|
-
// // progress: null, // number The current percentage of upload progress. This value will have a higher priority over the upload progress value calculated inside the component.
|
|
116
|
-
// // extraUploadData: null, // Record<string, any> The additional data that will be sent to the server when files are uploaded individually
|
|
117
|
-
// // extraData: null, // Object Any kind of extra data that could be needed.
|
|
118
|
-
// // serverResponse: null, // ServerResponse The upload response from server.
|
|
119
|
-
// // xhr: null, // XMLHttpRequest A reference to the XHR object that allows the upload, progress and abort events.
|
|
120
|
-
// };
|
|
121
|
-
// });
|
|
122
|
-
// setFiles(files);
|
|
123
|
-
// },
|
|
124
|
-
clearFiles = () => {
|
|
125
|
-
setFiles([]);
|
|
126
|
-
},
|
|
127
|
-
toggleShowAll = () => {
|
|
128
|
-
setShowAll(!showAll);
|
|
129
|
-
},
|
|
130
|
-
onDropzoneChange = (files) => {
|
|
131
|
-
if (!files.length) {
|
|
132
|
-
alert('No files accepted. Perhaps they were too large or the wrong file type?');
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
setFiles(files);
|
|
136
|
-
},
|
|
137
|
-
onUploadStart = (files) => {
|
|
138
|
-
setIsUploading(true);
|
|
139
|
-
},
|
|
140
|
-
onUploadFinish = (files) => {
|
|
141
|
-
let isDoneUploading = true,
|
|
142
|
-
isError = false;
|
|
143
|
-
|
|
144
|
-
_.each(files, (file) => {
|
|
145
|
-
if (!file.xhr || file.xhr.status !== 200) {
|
|
146
|
-
isDoneUploading = false;
|
|
147
|
-
return false; // break
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
if (isDoneUploading) {
|
|
152
|
-
_.each(files, (file) => {
|
|
153
|
-
if (file.uploadStatus === 'error') {
|
|
154
|
-
isError = true;
|
|
155
|
-
const msg = file.serverResponse?.payload || 'An error occurred';
|
|
156
|
-
alert(msg);
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
if (!isError) {
|
|
161
|
-
setIsUploading(false);
|
|
55
|
+
{
|
|
56
|
+
openFilePicker,
|
|
57
|
+
filesContent,
|
|
58
|
+
loading,
|
|
59
|
+
errors,
|
|
60
|
+
plainFiles,
|
|
61
|
+
clear,
|
|
62
|
+
} = useFilePicker({
|
|
63
|
+
readAs,
|
|
64
|
+
accept,
|
|
65
|
+
multiple,
|
|
66
|
+
readFilesContent,
|
|
67
|
+
validators,
|
|
68
|
+
onFilesSelected,
|
|
69
|
+
onFilesRejected,
|
|
70
|
+
onFilesSuccessfullySelected: ({ filesContent, plainFiles }) => {
|
|
71
|
+
let value = filesContent[0].content;
|
|
72
|
+
if (readAs === 'BinaryString' && encodeAsBase64) {
|
|
73
|
+
value = btoa(value); // convert to base64 encoded string
|
|
162
74
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
75
|
+
setValue(value);
|
|
76
|
+
},
|
|
77
|
+
onFileRemoved,
|
|
78
|
+
onClear: () => setValue(null),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (errors.length) {
|
|
83
|
+
const
|
|
84
|
+
errorStack = errors.map(err => err.name + ': ' + err.reason),
|
|
85
|
+
msg = errorStack.join("\n");
|
|
86
|
+
alert(msg);
|
|
87
|
+
}
|
|
88
|
+
}, [errors.length]);
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!value && filesContent.length) {
|
|
92
|
+
clear();
|
|
93
|
+
}
|
|
94
|
+
}, [value, filesContent.length]);
|
|
95
|
+
|
|
96
|
+
if (loading) {
|
|
97
|
+
return <Loading />;
|
|
179
98
|
}
|
|
180
|
-
let content = <Column
|
|
181
|
-
w="100%"
|
|
182
|
-
p={2}
|
|
183
|
-
background={styles.ATTACHMENTS_BG}
|
|
184
|
-
>
|
|
185
|
-
<Row flexWrap="wrap">
|
|
186
|
-
{files.map((file) => {
|
|
187
|
-
return <Box
|
|
188
|
-
key={file.id}
|
|
189
|
-
marginRight={4}
|
|
190
|
-
>
|
|
191
|
-
{useFileMosaic &&
|
|
192
|
-
<FileMosaic
|
|
193
|
-
{...file}
|
|
194
|
-
backgroundBlurImage={false}
|
|
195
|
-
{..._fileMosaic}
|
|
196
|
-
/>}
|
|
197
|
-
{!useFileMosaic &&
|
|
198
|
-
<FileCardCustom
|
|
199
|
-
{...file}
|
|
200
|
-
backgroundBlurImage={false}
|
|
201
|
-
{..._fileMosaic}
|
|
202
|
-
/>}
|
|
203
|
-
</Box>;
|
|
204
|
-
})}
|
|
205
|
-
</Row>
|
|
206
|
-
{Repository.total <= COLLAPSED_MAX ? null :
|
|
207
|
-
<Button
|
|
208
|
-
onPress={toggleShowAll}
|
|
209
|
-
mt={4}
|
|
210
|
-
_text={{
|
|
211
|
-
color: 'trueGray.600',
|
|
212
|
-
fontStyle: 'italic',
|
|
213
|
-
textAlign: 'left',
|
|
214
|
-
width: '100%',
|
|
215
|
-
}}
|
|
216
|
-
variant="ghost"
|
|
217
|
-
>{'Show ' + (showAll ? ' Less' : ' All ' + Repository.total)}</Button>}
|
|
218
|
-
</Column>;
|
|
219
|
-
|
|
220
|
-
if (canCrud) {
|
|
221
|
-
content = <Dropzone
|
|
222
|
-
value={files}
|
|
223
|
-
onChange={onDropzoneChange}
|
|
224
|
-
accept={accept}
|
|
225
|
-
maxFiles={maxFiles}
|
|
226
|
-
maxFileSize={styles.ATTACHMENTS_MAX_FILESIZE}
|
|
227
|
-
autoClean={true}
|
|
228
|
-
uploadConfig={{
|
|
229
|
-
url: Repository.api.baseURL + Repository.name + '/uploadAttachment',
|
|
230
|
-
method: 'POST',
|
|
231
|
-
headers: Repository.headers,
|
|
232
|
-
autoUpload: true,
|
|
233
|
-
}}
|
|
234
|
-
headerConfig={{
|
|
235
|
-
deleteFiles: false,
|
|
236
|
-
}}
|
|
237
|
-
onUploadStart={onUploadStart}
|
|
238
|
-
onUploadFinish={onUploadFinish}
|
|
239
|
-
background={styles.ATTACHMENTS_BG}
|
|
240
|
-
color={styles.ATTACHMENTS_COLOR}
|
|
241
|
-
minHeight={150}
|
|
242
|
-
footer={false}
|
|
243
|
-
clickable={clickable}
|
|
244
|
-
{..._dropZone}
|
|
245
|
-
>
|
|
246
|
-
{content}
|
|
247
|
-
</Dropzone>;
|
|
248
99
|
|
|
100
|
+
let assembledComponents = null;
|
|
101
|
+
if (_.isEmpty(filesContent)) {
|
|
102
|
+
assembledComponents = <Button onPress={() => openFilePicker()}>Select File</Button>;
|
|
103
|
+
} else {
|
|
104
|
+
assembledComponents = <Row
|
|
105
|
+
px={3}
|
|
106
|
+
py={1}
|
|
107
|
+
alignItems="center"
|
|
108
|
+
borderRadius={5}
|
|
109
|
+
borderWidth={1}
|
|
110
|
+
borderColor="primary.700"
|
|
111
|
+
>
|
|
112
|
+
<IconButton
|
|
113
|
+
_icon={{
|
|
114
|
+
as: Xmark,
|
|
115
|
+
color: 'trueGray.600',
|
|
116
|
+
size: 'sm',
|
|
117
|
+
}}
|
|
118
|
+
onPress={() => clear()}
|
|
119
|
+
h="100%"
|
|
120
|
+
bg={styles.FORM_COMBO_TRIGGER_BG}
|
|
121
|
+
_hover={{
|
|
122
|
+
bg: styles.FORM_COMBO_TRIGGER_HOVER_BG,
|
|
123
|
+
}}
|
|
124
|
+
mr={1}
|
|
125
|
+
/>
|
|
126
|
+
<Text>{plainFiles[0].name}</Text>
|
|
127
|
+
</Row>;
|
|
249
128
|
}
|
|
250
|
-
|
|
129
|
+
|
|
130
|
+
return assembledComponents;
|
|
251
131
|
}
|
|
252
132
|
|
|
253
133
|
export default withAlert(withValue(FileComponent));
|