@onehat/ui 0.4.45 → 0.4.47
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
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
UI_MODE_NATIVE,
|
|
18
18
|
UI_MODE_WEB,
|
|
19
19
|
} from '../../../Constants/UiModes.js';
|
|
20
|
+
import Button from '../../Buttons/Button.js';
|
|
20
21
|
import UiGlobals from '../../../UiGlobals.js';
|
|
21
22
|
import Formatters from '@onehat/data/src/Util/Formatters.js';
|
|
22
23
|
import Parsers from '@onehat/data/src/Util/Parsers.js';
|
|
@@ -219,6 +220,9 @@ export const DateElement = forwardRef((props, ref) => {
|
|
|
219
220
|
setBothValues(value);
|
|
220
221
|
setTextInputValue(value);
|
|
221
222
|
}
|
|
223
|
+
},
|
|
224
|
+
onToday = () => {
|
|
225
|
+
onPickerChange(moment());
|
|
222
226
|
};
|
|
223
227
|
|
|
224
228
|
useEffect(() => {
|
|
@@ -460,6 +464,15 @@ export const DateElement = forwardRef((props, ref) => {
|
|
|
460
464
|
timeFormat={mode === TIME || mode === DATETIME ? 'HH:mm:ss' : false}
|
|
461
465
|
onChange={onPickerChange}
|
|
462
466
|
/>
|
|
467
|
+
<Button
|
|
468
|
+
{...testProps('todayBtn')}
|
|
469
|
+
key="todayBtn"
|
|
470
|
+
onPress={onToday}
|
|
471
|
+
className={`
|
|
472
|
+
mt-2
|
|
473
|
+
`}
|
|
474
|
+
text="Today"
|
|
475
|
+
/>
|
|
463
476
|
</PopoverBody>
|
|
464
477
|
</PopoverContent>
|
|
465
478
|
</Popover>;
|
|
@@ -465,13 +465,15 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
465
465
|
}
|
|
466
466
|
|
|
467
467
|
setIsSaving(true);
|
|
468
|
-
let success;
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
468
|
+
let success = true;
|
|
469
|
+
const tempListener = (msg, data) => {
|
|
470
|
+
success = { msg, data };
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
SecondaryRepository.on('error', tempListener); // add a temporary listener for the error event
|
|
474
|
+
await SecondaryRepository.save(null, useStaged);
|
|
475
|
+
SecondaryRepository.off('error', tempListener); // remove the temporary listener
|
|
476
|
+
|
|
475
477
|
setIsSaving(false);
|
|
476
478
|
|
|
477
479
|
if (_.isBoolean(success) && success) {
|
|
@@ -499,7 +501,10 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
499
501
|
secondaryOnSave(secondarySelection);
|
|
500
502
|
}
|
|
501
503
|
}
|
|
502
|
-
|
|
504
|
+
if (secondaryEditorType === EDITOR_TYPE__INLINE) {
|
|
505
|
+
secondarySetIsEditorShown(false);
|
|
506
|
+
}
|
|
507
|
+
|
|
503
508
|
}
|
|
504
509
|
|
|
505
510
|
return success;
|
|
@@ -500,13 +500,15 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
500
500
|
}
|
|
501
501
|
|
|
502
502
|
setIsSaving(true);
|
|
503
|
-
let success;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
503
|
+
let success = true;
|
|
504
|
+
const tempListener = (msg, data) => {
|
|
505
|
+
success = { msg, data };
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
Repository.on('error', tempListener); // add a temporary listener for the error event
|
|
509
|
+
await Repository.save(null, useStaged);
|
|
510
|
+
Repository.off('error', tempListener); // remove the temporary listener
|
|
511
|
+
|
|
510
512
|
setIsSaving(false);
|
|
511
513
|
|
|
512
514
|
if (_.isBoolean(success) && success) {
|
|
@@ -534,7 +536,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
534
536
|
onSave(selection);
|
|
535
537
|
}
|
|
536
538
|
}
|
|
537
|
-
|
|
539
|
+
if (editorType === EDITOR_TYPE__INLINE) {
|
|
540
|
+
setIsEditorShown(false);
|
|
541
|
+
}
|
|
538
542
|
}
|
|
539
543
|
|
|
540
544
|
return success;
|
|
@@ -21,12 +21,17 @@ import {
|
|
|
21
21
|
import { Avatar, Dropzone, FileMosaic, FileCard, FileInputButton, } from "@files-ui/react";
|
|
22
22
|
import inArray from '../../Functions/inArray.js';
|
|
23
23
|
import IconButton from '../../Components/Buttons/IconButton.js';
|
|
24
|
-
import Xmark from '../../Components/Icons/Xmark.js'
|
|
24
|
+
import Xmark from '../../Components/Icons/Xmark.js';
|
|
25
|
+
import Eye from '../../Components/Icons/Eye.js';
|
|
26
|
+
import ChevronLeft from '../../Components/Icons/ChevronLeft.js';
|
|
27
|
+
import ChevronRight from '../../Components/Icons/ChevronRight.js';
|
|
25
28
|
import withAlert from '../../Components/Hoc/withAlert.js';
|
|
26
29
|
import withComponent from '../../Components/Hoc/withComponent.js';
|
|
27
30
|
import withData from '../../Components/Hoc/withData.js';
|
|
31
|
+
import CenterBox from '../../Components/Layout/CenterBox.js';
|
|
28
32
|
import downloadInBackground from '../../Functions/downloadInBackground.js';
|
|
29
33
|
import downloadWithFetch from '../../Functions/downloadWithFetch.js';
|
|
34
|
+
import useForceUpdate from '../../Hooks/useForceUpdate.js';
|
|
30
35
|
import _ from 'lodash';
|
|
31
36
|
|
|
32
37
|
const
|
|
@@ -35,16 +40,18 @@ const
|
|
|
35
40
|
isPwa = !!window?.navigator?.standalone;
|
|
36
41
|
|
|
37
42
|
function FileCardCustom(props) {
|
|
38
|
-
const
|
|
39
|
-
{
|
|
43
|
+
const {
|
|
40
44
|
id,
|
|
41
45
|
name: filename,
|
|
42
46
|
type: mimetype,
|
|
43
47
|
onDelete,
|
|
48
|
+
onSee,
|
|
44
49
|
downloadUrl,
|
|
45
50
|
uploadStatus,
|
|
46
51
|
} = props,
|
|
47
|
-
isDownloading = uploadStatus && inArray(uploadStatus, ['preparing', 'uploading', 'success'])
|
|
52
|
+
isDownloading = uploadStatus && inArray(uploadStatus, ['preparing', 'uploading', 'success']),
|
|
53
|
+
isPdf = mimetype === 'application/pdf';
|
|
54
|
+
|
|
48
55
|
return <Pressable
|
|
49
56
|
onPress={() => {
|
|
50
57
|
downloadInBackground(downloadUrl);
|
|
@@ -52,6 +59,7 @@ function FileCardCustom(props) {
|
|
|
52
59
|
className="px-3 py-1 items-center flex-row rounded-[5px] border border-primary.700"
|
|
53
60
|
>
|
|
54
61
|
{isDownloading && <Spinner className="mr-2" />}
|
|
62
|
+
{onSee && isPdf && <IconButton mr={1} icon={Eye} onPress={() => onSee(null, id)} />}
|
|
55
63
|
<Text>{filename}</Text>
|
|
56
64
|
{onDelete && <IconButton ml={1} icon={Xmark} onPress={() => onDelete(id)} />}
|
|
57
65
|
</Pressable>;
|
|
@@ -81,7 +89,7 @@ function AttachmentsElement(props) {
|
|
|
81
89
|
expandedMax = EXPANDED_MAX,
|
|
82
90
|
collapsedMax = COLLAPSED_MAX,
|
|
83
91
|
autoUpload = true,
|
|
84
|
-
|
|
92
|
+
onAfterDropzoneChange, // fn, should return true if it mutated the files array
|
|
85
93
|
|
|
86
94
|
// withComponent
|
|
87
95
|
self,
|
|
@@ -94,6 +102,8 @@ function AttachmentsElement(props) {
|
|
|
94
102
|
Repository,
|
|
95
103
|
|
|
96
104
|
// withAlert
|
|
105
|
+
showModal,
|
|
106
|
+
updateModalBody,
|
|
97
107
|
alert,
|
|
98
108
|
confirm,
|
|
99
109
|
|
|
@@ -102,6 +112,7 @@ function AttachmentsElement(props) {
|
|
|
102
112
|
model = _.isArray(selectorSelected) && selectorSelected[0] ? selectorSelected[0].repository?.name : selectorSelected?.repository?.name,
|
|
103
113
|
modelidCalc = _.isArray(selectorSelected) ? _.map(selectorSelected, (entity) => entity[selectorSelectedField]) : selectorSelected?.[selectorSelectedField],
|
|
104
114
|
modelid = useRef(modelidCalc),
|
|
115
|
+
forceUpdate = useForceUpdate(),
|
|
105
116
|
[isReady, setIsReady] = useState(false),
|
|
106
117
|
[isUploading, setIsUploading] = useState(false),
|
|
107
118
|
[showAll, setShowAll] = useState(false),
|
|
@@ -135,7 +146,7 @@ function AttachmentsElement(props) {
|
|
|
135
146
|
toggleShowAll = () => {
|
|
136
147
|
setShowAll(!showAll);
|
|
137
148
|
},
|
|
138
|
-
onDropzoneChange = (files) => {
|
|
149
|
+
onDropzoneChange = async (files) => {
|
|
139
150
|
if (!files.length) {
|
|
140
151
|
alert('No files accepted. Perhaps they were too large or the wrong file type?');
|
|
141
152
|
return;
|
|
@@ -148,8 +159,11 @@ function AttachmentsElement(props) {
|
|
|
148
159
|
...extraUploadData,
|
|
149
160
|
};
|
|
150
161
|
});
|
|
151
|
-
if (
|
|
152
|
-
|
|
162
|
+
if (onAfterDropzoneChange) {
|
|
163
|
+
const isChanged = await onAfterDropzoneChange(files);
|
|
164
|
+
if (isChanged) {
|
|
165
|
+
forceUpdate();
|
|
166
|
+
}
|
|
153
167
|
}
|
|
154
168
|
},
|
|
155
169
|
onUploadStart = (files) => {
|
|
@@ -199,9 +213,120 @@ function AttachmentsElement(props) {
|
|
|
199
213
|
downloadInBackground(url);
|
|
200
214
|
}
|
|
201
215
|
},
|
|
216
|
+
buildModalBody = (url, id) => {
|
|
217
|
+
// This method was abstracted out so showModal/onPrev/onNext can all use it.
|
|
218
|
+
// url comes from FileMosaic, which passes in imageUrl,
|
|
219
|
+
// whereas FileCardCustom passes in id.
|
|
220
|
+
|
|
221
|
+
function findFile(url, id) {
|
|
222
|
+
if (id) {
|
|
223
|
+
return _.find(files, { id });
|
|
224
|
+
}
|
|
225
|
+
return _.find(files, (file) => file.imageUrl === url);
|
|
226
|
+
}
|
|
227
|
+
function findPrevFile(url, id) {
|
|
228
|
+
const
|
|
229
|
+
currentFile = findFile(url, id),
|
|
230
|
+
currentIx = _.findIndex(files, currentFile);
|
|
231
|
+
if (currentIx > 0) {
|
|
232
|
+
return files[currentIx - 1];
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
function findNextFile(url, id) {
|
|
237
|
+
const
|
|
238
|
+
currentFile = findFile(url, id),
|
|
239
|
+
currentIx = _.findIndex(files, currentFile);
|
|
240
|
+
if (currentIx < files.length - 1) {
|
|
241
|
+
return files[currentIx + 1];
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const
|
|
247
|
+
prevFile = findPrevFile(url, id),
|
|
248
|
+
isPrevDisabled = !prevFile,
|
|
249
|
+
nextFile = findNextFile(url, id),
|
|
250
|
+
isNextDisabled = !nextFile,
|
|
251
|
+
onPrev = () => {
|
|
252
|
+
const { imageUrl, id } = prevFile;
|
|
253
|
+
updateModalBody(buildModalBody(imageUrl, id));
|
|
254
|
+
},
|
|
255
|
+
onNext = () => {
|
|
256
|
+
const { imageUrl, id } = nextFile;
|
|
257
|
+
updateModalBody(buildModalBody(imageUrl, id));
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
let isPdf = false,
|
|
261
|
+
body = null;
|
|
262
|
+
|
|
263
|
+
if (id) {
|
|
264
|
+
const file = _.find(files, { id });
|
|
265
|
+
url = file.imageUrl;
|
|
266
|
+
isPdf = true;
|
|
267
|
+
} else if (url?.match(/\.pdf$/)) {
|
|
268
|
+
isPdf = true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (isPdf) {
|
|
272
|
+
body = <iframe
|
|
273
|
+
src={url}
|
|
274
|
+
className="w-full h-full"
|
|
275
|
+
/>;
|
|
276
|
+
} else {
|
|
277
|
+
body = <CenterBox className="w-full h-full">
|
|
278
|
+
<img src={url} />
|
|
279
|
+
</CenterBox>;
|
|
280
|
+
}
|
|
281
|
+
return <HStack
|
|
282
|
+
className="w-full h-full"
|
|
283
|
+
>
|
|
284
|
+
<IconButton
|
|
285
|
+
onPress={onPrev}
|
|
286
|
+
className="Lightbox-prevBtn h-full w-[50px]"
|
|
287
|
+
icon={ChevronLeft}
|
|
288
|
+
isDisabled={isPrevDisabled}
|
|
289
|
+
/>
|
|
290
|
+
{body}
|
|
291
|
+
<IconButton
|
|
292
|
+
onPress={onNext}
|
|
293
|
+
className="Lightbox-prevBtn h-full w-[50px]"
|
|
294
|
+
icon={ChevronRight}
|
|
295
|
+
isDisabled={isNextDisabled}
|
|
296
|
+
/>
|
|
297
|
+
</HStack>;
|
|
298
|
+
},
|
|
299
|
+
onViewLightbox = (url, id) => {
|
|
300
|
+
if (!url && !id) {
|
|
301
|
+
alert('Cannot view lightbox until image is uploaded.');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
showModal({
|
|
305
|
+
title: 'Lightbox',
|
|
306
|
+
body: buildModalBody(url, id),
|
|
307
|
+
canClose: true,
|
|
308
|
+
includeCancel: true,
|
|
309
|
+
w: 1920,
|
|
310
|
+
h: 1080,
|
|
311
|
+
});
|
|
312
|
+
},
|
|
202
313
|
doDelete = (id) => {
|
|
203
|
-
Repository.
|
|
204
|
-
|
|
314
|
+
const file = Repository.getById(id);
|
|
315
|
+
if (file) {
|
|
316
|
+
// if the file exists in the repository, delete it there
|
|
317
|
+
Repository.deleteById(id);
|
|
318
|
+
Repository.save();
|
|
319
|
+
|
|
320
|
+
} else {
|
|
321
|
+
// simply remove it from the files array
|
|
322
|
+
const newFiles = [];
|
|
323
|
+
_.each(files, (file) => {
|
|
324
|
+
if (file.id !== id) {
|
|
325
|
+
newFiles.push(file);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
setFiles(newFiles);
|
|
329
|
+
}
|
|
205
330
|
};
|
|
206
331
|
|
|
207
332
|
if (!_.isEqual(modelidCalc, modelid.current)) {
|
|
@@ -296,6 +421,12 @@ function AttachmentsElement(props) {
|
|
|
296
421
|
<HStack className="AttachmentsElement-HStack flex-wrap">
|
|
297
422
|
{files.length === 0 && <Text className="text-grey-600 italic">No files</Text>}
|
|
298
423
|
{files.map((file) => {
|
|
424
|
+
let seeProps = {};
|
|
425
|
+
if (file.type && (file.type.match(/^image\//) || file.type === 'application/pdf')) {
|
|
426
|
+
seeProps = {
|
|
427
|
+
onSee: onViewLightbox,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
299
430
|
return <Box
|
|
300
431
|
key={file.id}
|
|
301
432
|
className="mr-2"
|
|
@@ -306,12 +437,14 @@ function AttachmentsElement(props) {
|
|
|
306
437
|
backgroundBlurImage={false}
|
|
307
438
|
onDownload={onDownload}
|
|
308
439
|
{..._fileMosaic}
|
|
440
|
+
{...seeProps}
|
|
309
441
|
/>}
|
|
310
442
|
{!useFileMosaic &&
|
|
311
443
|
<FileCardCustom
|
|
312
444
|
{...file}
|
|
313
445
|
backgroundBlurImage={false}
|
|
314
446
|
{..._fileMosaic}
|
|
447
|
+
{...seeProps}
|
|
315
448
|
/>}
|
|
316
449
|
</Box>;
|
|
317
450
|
})}
|