@formio/js 5.0.0-rc.34 → 5.0.0-rc.35
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/dist/fonts/bootstrap-icons.woff +0 -0
- package/dist/fonts/bootstrap-icons.woff2 +0 -0
- package/dist/formio.builder.css +19 -15
- package/dist/formio.builder.min.css +1 -1
- package/dist/formio.embed.js +1 -1
- package/dist/formio.embed.min.js +1 -1
- package/dist/formio.form.css +19 -15
- package/dist/formio.form.js +19 -19
- package/dist/formio.form.min.css +1 -1
- package/dist/formio.form.min.js +1 -1
- package/dist/formio.form.min.js.LICENSE.txt +1 -1
- package/dist/formio.full.css +363 -19
- package/dist/formio.full.js +20 -20
- package/dist/formio.full.min.css +3 -3
- package/dist/formio.full.min.js +1 -1
- package/dist/formio.full.min.js.LICENSE.txt +1 -1
- package/dist/formio.js +8 -8
- package/dist/formio.min.js +1 -1
- package/lib/cjs/CDN.js +12 -6
- package/lib/cjs/Webform.js +4 -1
- package/lib/cjs/Wizard.js +6 -9
- package/lib/cjs/components/_classes/component/Component.js +6 -1
- package/lib/cjs/components/_classes/nested/NestedComponent.js +1 -1
- package/lib/cjs/components/container/fixtures/comp4.js +45 -0
- package/lib/cjs/components/container/fixtures/index.js +3 -1
- package/lib/cjs/components/datetime/DateTime.js +6 -0
- package/lib/cjs/components/file/File.js +465 -215
- package/lib/cjs/components/file/editForm/File.edit.display.js +17 -0
- package/lib/cjs/components/textarea/TextArea.js +2 -2
- package/lib/cjs/components/textfield/TextField.js +3 -1
- package/lib/cjs/components/time/Time.js +3 -0
- package/lib/cjs/providers/storage/azure.js +6 -1
- package/lib/cjs/providers/storage/base64.js +1 -1
- package/lib/cjs/providers/storage/googleDrive.js +5 -1
- package/lib/cjs/providers/storage/indexeddb.js +1 -1
- package/lib/cjs/providers/storage/s3.js +5 -1
- package/lib/cjs/providers/storage/xhr.js +10 -0
- package/lib/mjs/CDN.js +12 -6
- package/lib/mjs/Webform.js +4 -1
- package/lib/mjs/Wizard.js +6 -9
- package/lib/mjs/components/_classes/component/Component.js +6 -1
- package/lib/mjs/components/_classes/nested/NestedComponent.js +1 -1
- package/lib/mjs/components/container/fixtures/comp4.js +43 -0
- package/lib/mjs/components/container/fixtures/index.js +2 -1
- package/lib/mjs/components/datetime/DateTime.js +6 -0
- package/lib/mjs/components/file/File.js +463 -224
- package/lib/mjs/components/file/editForm/File.edit.display.js +17 -0
- package/lib/mjs/components/textarea/TextArea.js +2 -2
- package/lib/mjs/components/textfield/TextField.js +6 -0
- package/lib/mjs/components/time/Time.js +3 -0
- package/lib/mjs/providers/storage/azure.js +6 -1
- package/lib/mjs/providers/storage/base64.js +1 -1
- package/lib/mjs/providers/storage/googleDrive.js +5 -1
- package/lib/mjs/providers/storage/indexeddb.js +1 -1
- package/lib/mjs/providers/storage/s3.js +5 -1
- package/lib/mjs/providers/storage/xhr.js +10 -0
- package/package.json +2 -2
@@ -28,6 +28,7 @@ if (htmlCanvasElement && !htmlCanvasElement.prototype.toBlob) {
|
|
28
28
|
}
|
29
29
|
});
|
30
30
|
}
|
31
|
+
const createRandomString = () => Math.random().toString(36).substring(2, 15);
|
31
32
|
export default class FileComponent extends Field {
|
32
33
|
static schema(...extend) {
|
33
34
|
return Field.schema({
|
@@ -79,8 +80,13 @@ export default class FileComponent extends Field {
|
|
79
80
|
progress: progressSupported,
|
80
81
|
};
|
81
82
|
this.cameraMode = false;
|
82
|
-
this.statuses = [];
|
83
83
|
this.fileDropHidden = false;
|
84
|
+
this.filesToSync = {
|
85
|
+
filesToUpload: [],
|
86
|
+
filesToDelete: [],
|
87
|
+
};
|
88
|
+
this.isSyncing = false;
|
89
|
+
this.abortUploads = [];
|
84
90
|
}
|
85
91
|
get dataReady() {
|
86
92
|
return this.filesReady || Promise.resolve();
|
@@ -125,14 +131,37 @@ export default class FileComponent extends Field {
|
|
125
131
|
}
|
126
132
|
this._fileBrowseHidden = value;
|
127
133
|
}
|
134
|
+
get shouldSyncFiles() {
|
135
|
+
return Boolean(this.filesToSync.filesToDelete.length || this.filesToSync.filesToUpload.length);
|
136
|
+
}
|
137
|
+
get autoSync() {
|
138
|
+
return _.get(this, 'component.autoSync', false);
|
139
|
+
}
|
140
|
+
get columnsSize() {
|
141
|
+
const actionsColumn = this.disabled ? 0 : this.autoSync ? 2 : 1;
|
142
|
+
const typeColumn = this.hasTypes ? 2 : 0;
|
143
|
+
const sizeColumn = 2;
|
144
|
+
const nameColumn = 12 - actionsColumn - typeColumn - sizeColumn;
|
145
|
+
return {
|
146
|
+
name: nameColumn,
|
147
|
+
size: sizeColumn,
|
148
|
+
type: typeColumn,
|
149
|
+
actions: actionsColumn,
|
150
|
+
};
|
151
|
+
}
|
128
152
|
render() {
|
153
|
+
const { filesToDelete, filesToUpload } = this.filesToSync;
|
129
154
|
return super.render(this.renderTemplate('file', {
|
130
155
|
fileSize: this.fileSize,
|
131
156
|
files: this.dataValue || [],
|
132
|
-
|
157
|
+
filesToDelete,
|
158
|
+
filesToUpload,
|
133
159
|
disabled: this.disabled,
|
134
160
|
support: this.support,
|
135
|
-
fileDropHidden: this.fileDropHidden
|
161
|
+
fileDropHidden: this.fileDropHidden,
|
162
|
+
showSyncButton: this.autoSync && (filesToDelete.length || filesToUpload.length),
|
163
|
+
isSyncing: this.isSyncing,
|
164
|
+
columns: this.columnsSize,
|
136
165
|
}));
|
137
166
|
}
|
138
167
|
getVideoStream(constraints) {
|
@@ -198,7 +227,7 @@ export default class FileComponent extends Field {
|
|
198
227
|
this.getFrame(videoPlayer)
|
199
228
|
.then((frame) => {
|
200
229
|
frame.name = `photo-${Date.now()}.png`;
|
201
|
-
this.
|
230
|
+
this.handleFilesToUpload([frame]);
|
202
231
|
this.cameraMode = false;
|
203
232
|
this.redraw();
|
204
233
|
});
|
@@ -280,20 +309,10 @@ export default class FileComponent extends Field {
|
|
280
309
|
}
|
281
310
|
return options;
|
282
311
|
}
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
if (fileService && typeof fileService.deleteFile === 'function') {
|
288
|
-
fileService.deleteFile(fileInfo, options);
|
289
|
-
}
|
290
|
-
else {
|
291
|
-
const formio = this.options.formio || (this.root && this.root.formio);
|
292
|
-
if (formio) {
|
293
|
-
formio.makeRequest('', fileInfo.url, 'delete');
|
294
|
-
}
|
295
|
-
}
|
296
|
-
}
|
312
|
+
get actions() {
|
313
|
+
return {
|
314
|
+
abort: this.abortRequest.bind(this),
|
315
|
+
};
|
297
316
|
}
|
298
317
|
attach(element) {
|
299
318
|
this.loadRefs(element, {
|
@@ -306,19 +325,22 @@ export default class FileComponent extends Field {
|
|
306
325
|
videoPlayer: 'single',
|
307
326
|
fileLink: 'multiple',
|
308
327
|
removeLink: 'multiple',
|
309
|
-
|
328
|
+
fileToSyncRemove: 'multiple',
|
310
329
|
fileImage: 'multiple',
|
311
330
|
fileType: 'multiple',
|
312
331
|
fileProcessingLoader: 'single',
|
332
|
+
syncNow: 'single',
|
333
|
+
restoreFile: 'multiple',
|
334
|
+
progress: 'multiple',
|
313
335
|
});
|
314
336
|
// Ensure we have an empty input refs. We need this for the setValue method to redraw the control when it is set.
|
315
337
|
this.refs.input = [];
|
316
338
|
const superAttach = super.attach(element);
|
317
339
|
if (this.refs.fileDrop) {
|
318
|
-
if (!this.statuses.length) {
|
319
|
-
|
320
|
-
}
|
321
|
-
const
|
340
|
+
// if (!this.statuses.length) {
|
341
|
+
// this.refs.fileDrop.removeAttribute('hidden');
|
342
|
+
// }
|
343
|
+
const _this = this;
|
322
344
|
this.addEventListener(this.refs.fileDrop, 'dragover', function (event) {
|
323
345
|
this.className = 'fileSelector fileDragOver';
|
324
346
|
event.preventDefault();
|
@@ -330,15 +352,18 @@ export default class FileComponent extends Field {
|
|
330
352
|
this.addEventListener(this.refs.fileDrop, 'drop', function (event) {
|
331
353
|
this.className = 'fileSelector';
|
332
354
|
event.preventDefault();
|
333
|
-
|
355
|
+
_this.handleFilesToUpload(event.dataTransfer.files);
|
334
356
|
});
|
335
357
|
}
|
358
|
+
this.addEventListener(element, 'click', (event) => {
|
359
|
+
this.handleAction(event);
|
360
|
+
});
|
336
361
|
if (this.refs.fileBrowse) {
|
337
362
|
this.addEventListener(this.refs.fileBrowse, 'click', (event) => {
|
338
363
|
event.preventDefault();
|
339
364
|
this.browseFiles(this.browseOptions)
|
340
365
|
.then((files) => {
|
341
|
-
this.
|
366
|
+
this.handleFilesToUpload(files);
|
342
367
|
});
|
343
368
|
});
|
344
369
|
}
|
@@ -350,22 +375,27 @@ export default class FileComponent extends Field {
|
|
350
375
|
});
|
351
376
|
this.refs.removeLink.forEach((removeLink, index) => {
|
352
377
|
this.addEventListener(removeLink, 'click', (event) => {
|
378
|
+
event.preventDefault();
|
353
379
|
const fileInfo = this.dataValue[index];
|
354
|
-
this.
|
380
|
+
this.handleFileToRemove(fileInfo);
|
381
|
+
});
|
382
|
+
});
|
383
|
+
this.refs.fileToSyncRemove.forEach((fileToSyncRemove, index) => {
|
384
|
+
this.addEventListener(fileToSyncRemove, 'click', (event) => {
|
355
385
|
event.preventDefault();
|
356
|
-
this.splice(index);
|
386
|
+
this.filesToSync.filesToUpload.splice(index, 1);
|
357
387
|
this.redraw();
|
358
388
|
});
|
359
389
|
});
|
360
|
-
this.refs.
|
361
|
-
this.addEventListener(
|
390
|
+
this.refs.restoreFile.forEach((fileToRestore, index) => {
|
391
|
+
this.addEventListener(fileToRestore, 'click', (event) => {
|
362
392
|
event.preventDefault();
|
363
|
-
const
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
this.
|
393
|
+
const fileInfo = this.filesToSync.filesToDelete[index];
|
394
|
+
delete fileInfo.status;
|
395
|
+
delete fileInfo.message;
|
396
|
+
this.filesToSync.filesToDelete.splice(index, 1);
|
397
|
+
this.dataValue.push(fileInfo);
|
398
|
+
this.triggerChange();
|
369
399
|
this.redraw();
|
370
400
|
});
|
371
401
|
});
|
@@ -379,7 +409,7 @@ export default class FileComponent extends Field {
|
|
379
409
|
reader.onloadend = (evt) => {
|
380
410
|
const blob = new Blob([new Uint8Array(evt.target.result)], { type: file.type });
|
381
411
|
blob.name = file.name;
|
382
|
-
this.
|
412
|
+
this.handleFilesToUpload([blob]);
|
383
413
|
};
|
384
414
|
reader.readAsArrayBuffer(file);
|
385
415
|
});
|
@@ -401,7 +431,7 @@ export default class FileComponent extends Field {
|
|
401
431
|
reader.onloadend = (evt) => {
|
402
432
|
const blob = new Blob([new Uint8Array(evt.target.result)], { type: file.type });
|
403
433
|
blob.name = file.name;
|
404
|
-
this.
|
434
|
+
this.handleFilesToUpload([blob]);
|
405
435
|
};
|
406
436
|
reader.readAsArrayBuffer(file);
|
407
437
|
});
|
@@ -431,6 +461,9 @@ export default class FileComponent extends Field {
|
|
431
461
|
});
|
432
462
|
}
|
433
463
|
this.refs.fileType.forEach((fileType, index) => {
|
464
|
+
if (!this.dataValue[index]) {
|
465
|
+
return;
|
466
|
+
}
|
434
467
|
this.dataValue[index].fileType = this.dataValue[index].fileType || this.component.fileTypes[0].label;
|
435
468
|
this.addEventListener(fileType, 'change', (event) => {
|
436
469
|
event.preventDefault();
|
@@ -438,6 +471,10 @@ export default class FileComponent extends Field {
|
|
438
471
|
this.dataValue[index].fileType = fileType.label;
|
439
472
|
});
|
440
473
|
});
|
474
|
+
this.addEventListener(this.refs.syncNow, 'click', (event) => {
|
475
|
+
event.preventDefault();
|
476
|
+
this.syncFiles();
|
477
|
+
});
|
441
478
|
const fileService = this.fileService;
|
442
479
|
if (fileService) {
|
443
480
|
const loadingImages = [];
|
@@ -553,201 +590,389 @@ export default class FileComponent extends Field {
|
|
553
590
|
validateMaxSize(file, val) {
|
554
591
|
return file.size - 0.1 <= this.translateScalars(val);
|
555
592
|
}
|
556
|
-
|
593
|
+
abortRequest(id) {
|
594
|
+
const abortUpload = this.abortUploads.find(abortUpload => abortUpload.id === id);
|
595
|
+
if (abortUpload) {
|
596
|
+
abortUpload.abort();
|
597
|
+
}
|
598
|
+
}
|
599
|
+
handleAction(event) {
|
600
|
+
const target = event.target;
|
601
|
+
if (!target.id) {
|
602
|
+
return;
|
603
|
+
}
|
604
|
+
const [action, id] = target.id.split('-');
|
605
|
+
if (!action || !id || !this.actions[action]) {
|
606
|
+
return;
|
607
|
+
}
|
608
|
+
this.actions[action](id);
|
609
|
+
}
|
610
|
+
getFileName(file) {
|
611
|
+
return uniqueName(file.name, this.component.fileNameTemplate, this.evalContext());
|
612
|
+
}
|
613
|
+
getInitFileToSync(file) {
|
614
|
+
const escapedFileName = file.name ? file.name.replaceAll('<', '<').replaceAll('>', '>') : file.name;
|
615
|
+
return {
|
616
|
+
id: createRandomString(),
|
617
|
+
// Get a unique name for this file to keep file collisions from occurring.
|
618
|
+
dir: this.interpolate(this.component.dir || ''),
|
619
|
+
name: this.getFileName(file),
|
620
|
+
originalName: escapedFileName,
|
621
|
+
fileKey: this.component.fileKey || 'file',
|
622
|
+
storage: this.component.storage,
|
623
|
+
options: this.component.options,
|
624
|
+
file,
|
625
|
+
size: file.size,
|
626
|
+
status: 'info',
|
627
|
+
message: this.t('Processing file. Please wait...'),
|
628
|
+
hash: '',
|
629
|
+
};
|
630
|
+
}
|
631
|
+
async handleSubmissionRevisions(file) {
|
632
|
+
if (this.root.form.submissionRevisions !== 'true') {
|
633
|
+
return '';
|
634
|
+
}
|
635
|
+
const bmf = new BMF();
|
636
|
+
const hash = await new Promise((resolve, reject) => {
|
637
|
+
this.emit('fileUploadingStart');
|
638
|
+
bmf.md5(file, (err, md5) => {
|
639
|
+
if (err) {
|
640
|
+
return reject(err);
|
641
|
+
}
|
642
|
+
return resolve(md5);
|
643
|
+
});
|
644
|
+
});
|
645
|
+
this.emit('fileUploadingEnd');
|
646
|
+
return hash;
|
647
|
+
}
|
648
|
+
validateFileName(file) {
|
649
|
+
// Check if file with the same name is being uploaded
|
650
|
+
const fileWithSameNameUploading = this.filesToSync.filesToUpload
|
651
|
+
.some(fileToSync => fileToSync.file?.name === file.name);
|
652
|
+
const fileWithSameNameUploaded = this.dataValue
|
653
|
+
.some(fileStatus => fileStatus.originalName === file.name);
|
654
|
+
return fileWithSameNameUploaded || fileWithSameNameUploading
|
655
|
+
? {
|
656
|
+
status: 'error',
|
657
|
+
message: this.t(`File with the same name is already ${fileWithSameNameUploading ? 'being ' : ''}uploaded`),
|
658
|
+
}
|
659
|
+
: {};
|
660
|
+
}
|
661
|
+
validateFileSettings(file) {
|
662
|
+
// Check file pattern
|
663
|
+
if (this.component.filePattern && !this.validatePattern(file, this.component.filePattern)) {
|
664
|
+
return {
|
665
|
+
status: 'error',
|
666
|
+
message: this.t('File is the wrong type; it must be {{ pattern }}', {
|
667
|
+
pattern: this.component.filePattern,
|
668
|
+
}),
|
669
|
+
};
|
670
|
+
}
|
671
|
+
// Check file minimum size
|
672
|
+
if (this.component.fileMinSize && !this.validateMinSize(file, this.component.fileMinSize)) {
|
673
|
+
return {
|
674
|
+
status: 'error',
|
675
|
+
message: this.t('File is too small; it must be at least {{ size }}', {
|
676
|
+
size: this.component.fileMinSize,
|
677
|
+
}),
|
678
|
+
};
|
679
|
+
}
|
680
|
+
// Check file maximum size
|
681
|
+
if (this.component.fileMaxSize && !this.validateMaxSize(file, this.component.fileMaxSize)) {
|
682
|
+
return {
|
683
|
+
status: 'error',
|
684
|
+
message: this.t('File is too big; it must be at most {{ size }}', {
|
685
|
+
size: this.component.fileMaxSize,
|
686
|
+
}),
|
687
|
+
};
|
688
|
+
}
|
689
|
+
return {};
|
690
|
+
}
|
691
|
+
validateFileService() {
|
692
|
+
const { fileService } = this;
|
693
|
+
return !fileService
|
694
|
+
? {
|
695
|
+
status: 'error',
|
696
|
+
message: this.t('File Service not provided.'),
|
697
|
+
}
|
698
|
+
: {};
|
699
|
+
}
|
700
|
+
validateFile(file) {
|
701
|
+
const fileServiceValidation = this.validateFileService();
|
702
|
+
if (fileServiceValidation.status === 'error') {
|
703
|
+
return fileServiceValidation;
|
704
|
+
}
|
705
|
+
const fileNameValidation = this.validateFileName(file);
|
706
|
+
if (fileNameValidation.status === 'error') {
|
707
|
+
return fileNameValidation;
|
708
|
+
}
|
709
|
+
return this.validateFileSettings(file);
|
710
|
+
}
|
711
|
+
getGroupPermissions() {
|
712
|
+
let groupKey = null;
|
713
|
+
let groupPermissions = null;
|
714
|
+
//Iterate through form components to find group resource if one exists
|
715
|
+
this.root.everyComponent((element) => {
|
716
|
+
if (element.component?.submissionAccess || element.component?.defaultPermission) {
|
717
|
+
groupPermissions = !element.component.submissionAccess ? [
|
718
|
+
{
|
719
|
+
type: element.component.defaultPermission,
|
720
|
+
roles: [],
|
721
|
+
},
|
722
|
+
] : element.component.submissionAccess;
|
723
|
+
groupPermissions.forEach((permission) => {
|
724
|
+
groupKey = ['admin', 'write', 'create'].includes(permission.type) ? element.component.key : null;
|
725
|
+
});
|
726
|
+
}
|
727
|
+
});
|
728
|
+
return { groupKey, groupPermissions };
|
729
|
+
}
|
730
|
+
async triggerFileProcessor(file) {
|
731
|
+
let processedFile = null;
|
732
|
+
if (this.root.options.fileProcessor) {
|
733
|
+
try {
|
734
|
+
if (this.refs.fileProcessingLoader) {
|
735
|
+
this.refs.fileProcessingLoader.style.display = 'block';
|
736
|
+
}
|
737
|
+
const fileProcessorHandler = fileProcessor(this.fileService, this.root.options.fileProcessor);
|
738
|
+
processedFile = await fileProcessorHandler(file, this.component.properties);
|
739
|
+
}
|
740
|
+
catch (err) {
|
741
|
+
this.fileDropHidden = false;
|
742
|
+
return {
|
743
|
+
status: 'error',
|
744
|
+
message: this.t('File processing has been failed.'),
|
745
|
+
};
|
746
|
+
}
|
747
|
+
finally {
|
748
|
+
if (this.refs.fileProcessingLoader) {
|
749
|
+
this.refs.fileProcessingLoader.style.display = 'none';
|
750
|
+
}
|
751
|
+
}
|
752
|
+
}
|
753
|
+
return {
|
754
|
+
file: processedFile,
|
755
|
+
};
|
756
|
+
}
|
757
|
+
async prepareFileToUpload(file) {
|
758
|
+
const fileToSync = this.getInitFileToSync(file);
|
759
|
+
fileToSync.hash = await this.handleSubmissionRevisions(file);
|
760
|
+
const { status, message } = this.validateFile(file);
|
761
|
+
if (status === 'error') {
|
762
|
+
fileToSync.isValidationError = true;
|
763
|
+
fileToSync.status = status;
|
764
|
+
fileToSync.message = message;
|
765
|
+
return this.filesToSync.filesToUpload.push(fileToSync);
|
766
|
+
}
|
767
|
+
if (this.component.privateDownload) {
|
768
|
+
file.private = true;
|
769
|
+
}
|
770
|
+
const { groupKey, groupPermissions } = this.getGroupPermissions();
|
771
|
+
const processedFile = await this.triggerFileProcessor(file);
|
772
|
+
if (processedFile.status === 'error') {
|
773
|
+
fileToSync.status === 'error';
|
774
|
+
fileToSync.message = processedFile.message;
|
775
|
+
return this.filesToSync.filesToUpload.push(fileToSync);
|
776
|
+
}
|
777
|
+
if (this.autoSync) {
|
778
|
+
fileToSync.message = this.t('Ready to be uploaded into storage');
|
779
|
+
}
|
780
|
+
this.filesToSync.filesToUpload.push({
|
781
|
+
...fileToSync,
|
782
|
+
message: fileToSync.message,
|
783
|
+
file: processedFile.file || file,
|
784
|
+
url: this.interpolate(this.component.url, { file: fileToSync }),
|
785
|
+
groupPermissions,
|
786
|
+
groupResourceId: groupKey ? this.currentForm.submission.data[groupKey]._id : null,
|
787
|
+
});
|
788
|
+
}
|
789
|
+
async prepareFilesToUpload(files) {
|
557
790
|
// Only allow one upload if not multiple.
|
558
791
|
if (!this.component.multiple) {
|
559
|
-
if (this.statuses.length) {
|
560
|
-
this.statuses = [];
|
561
|
-
}
|
562
792
|
files = Array.prototype.slice.call(files, 0, 1);
|
563
793
|
}
|
564
794
|
if (this.component.storage && files && files.length) {
|
565
795
|
this.fileDropHidden = true;
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
this.statuses.splice(fileWithSameNameUploadedWithError, 1);
|
611
|
-
this.redraw();
|
612
|
-
}
|
613
|
-
// Check file pattern
|
614
|
-
if (this.component.filePattern && !this.validatePattern(file, this.component.filePattern)) {
|
615
|
-
fileUpload.status = 'error';
|
616
|
-
fileUpload.message = this.t('File is the wrong type; it must be {{ pattern }}', {
|
617
|
-
pattern: this.component.filePattern,
|
618
|
-
});
|
619
|
-
}
|
620
|
-
// Check file minimum size
|
621
|
-
if (this.component.fileMinSize && !this.validateMinSize(file, this.component.fileMinSize)) {
|
622
|
-
fileUpload.status = 'error';
|
623
|
-
fileUpload.message = this.t('File is too small; it must be at least {{ size }}', {
|
624
|
-
size: this.component.fileMinSize,
|
625
|
-
});
|
626
|
-
}
|
627
|
-
// Check file maximum size
|
628
|
-
if (this.component.fileMaxSize && !this.validateMaxSize(file, this.component.fileMaxSize)) {
|
629
|
-
fileUpload.status = 'error';
|
630
|
-
fileUpload.message = this.t('File is too big; it must be at most {{ size }}', {
|
631
|
-
size: this.component.fileMaxSize,
|
632
|
-
});
|
633
|
-
}
|
634
|
-
// Get a unique name for this file to keep file collisions from occurring.
|
635
|
-
const dir = this.interpolate(this.component.dir || '');
|
636
|
-
const { fileService } = this;
|
637
|
-
if (!fileService) {
|
638
|
-
fileUpload.status = 'error';
|
639
|
-
fileUpload.message = this.t('File Service not provided.');
|
640
|
-
}
|
641
|
-
if (this.root.form.submissionRevisions !== 'true') {
|
642
|
-
this.statuses.push(fileUpload);
|
643
|
-
this.redraw();
|
796
|
+
return Promise.all([...files].map(async (file) => {
|
797
|
+
await this.prepareFileToUpload(file);
|
798
|
+
this.redraw();
|
799
|
+
}));
|
800
|
+
}
|
801
|
+
else {
|
802
|
+
return Promise.resolve();
|
803
|
+
}
|
804
|
+
}
|
805
|
+
async handleFilesToUpload(files) {
|
806
|
+
await this.prepareFilesToUpload(files);
|
807
|
+
if (!this.autoSync) {
|
808
|
+
await this.syncFiles();
|
809
|
+
}
|
810
|
+
}
|
811
|
+
prepareFileToDelete(fileInfo) {
|
812
|
+
this.filesToSync.filesToDelete.push({
|
813
|
+
...fileInfo,
|
814
|
+
status: 'info',
|
815
|
+
message: this.autoSync
|
816
|
+
? this.t('Ready to be removed from storage')
|
817
|
+
: this.t('Preparing file to remove'),
|
818
|
+
});
|
819
|
+
const index = this.dataValue.findIndex(file => file.name === fileInfo.name);
|
820
|
+
this.splice(index);
|
821
|
+
this.redraw();
|
822
|
+
}
|
823
|
+
handleFileToRemove(fileInfo) {
|
824
|
+
this.prepareFileToDelete(fileInfo);
|
825
|
+
if (!this.autoSync) {
|
826
|
+
this.syncFiles();
|
827
|
+
}
|
828
|
+
}
|
829
|
+
async deleteFile(fileInfo) {
|
830
|
+
const { options = {} } = this.component;
|
831
|
+
if (fileInfo && (['url', 'indexeddb', 's3', 'azure', 'googledrive'].includes(this.component.storage))) {
|
832
|
+
const { fileService } = this;
|
833
|
+
if (fileService && typeof fileService.deleteFile === 'function') {
|
834
|
+
return await fileService.deleteFile(fileInfo, options);
|
835
|
+
}
|
836
|
+
else {
|
837
|
+
const formio = this.options.formio || (this.root && this.root.formio);
|
838
|
+
if (formio) {
|
839
|
+
return await formio.makeRequest('', fileInfo.url, 'delete');
|
644
840
|
}
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
groupPermissions = !element.component.submissionAccess ? [
|
657
|
-
{
|
658
|
-
type: element.component.defaultPermission,
|
659
|
-
roles: [],
|
660
|
-
},
|
661
|
-
] : element.component.submissionAccess;
|
662
|
-
groupPermissions.forEach((permission) => {
|
663
|
-
groupKey = ['admin', 'write', 'create'].includes(permission.type) ? element.component.key : null;
|
664
|
-
});
|
665
|
-
}
|
666
|
-
});
|
667
|
-
const fileKey = this.component.fileKey || 'file';
|
668
|
-
const groupResourceId = groupKey ? this.currentForm.submission.data[groupKey]._id : null;
|
669
|
-
let processedFile = null;
|
670
|
-
if (this.root.options.fileProcessor) {
|
671
|
-
try {
|
672
|
-
if (this.refs.fileProcessingLoader) {
|
673
|
-
this.refs.fileProcessingLoader.style.display = 'block';
|
674
|
-
}
|
675
|
-
const fileProcessorHandler = fileProcessor(this.fileService, this.root.options.fileProcessor);
|
676
|
-
processedFile = await fileProcessorHandler(file, this.component.properties);
|
677
|
-
}
|
678
|
-
catch (err) {
|
679
|
-
fileUpload.status = 'error';
|
680
|
-
fileUpload.message = this.t('File processing has been failed.');
|
681
|
-
this.fileDropHidden = false;
|
682
|
-
this.redraw();
|
683
|
-
return;
|
684
|
-
}
|
685
|
-
finally {
|
686
|
-
if (this.refs.fileProcessingLoader) {
|
687
|
-
this.refs.fileProcessingLoader.style.display = 'none';
|
688
|
-
}
|
689
|
-
}
|
690
|
-
}
|
691
|
-
// Prep for a potential multipart upload
|
692
|
-
let count = 0;
|
693
|
-
const multipartOptions = this.component.useMultipartUpload && this.component.multipart ? {
|
694
|
-
...this.component.multipart,
|
695
|
-
progressCallback: (total) => {
|
696
|
-
count++;
|
697
|
-
fileUpload.status = 'progress';
|
698
|
-
fileUpload.progress = parseInt(100 * count / total);
|
699
|
-
delete fileUpload.message;
|
700
|
-
this.redraw();
|
701
|
-
},
|
702
|
-
changeMessage: (message) => {
|
703
|
-
fileUpload.message = message;
|
704
|
-
this.redraw();
|
705
|
-
},
|
706
|
-
} : false;
|
707
|
-
fileUpload.message = this.t('Starting upload...');
|
708
|
-
this.redraw();
|
709
|
-
const filePromise = fileService.uploadFile(storage, processedFile || file, fileName, dir,
|
710
|
-
// Progress callback
|
711
|
-
(evt) => {
|
712
|
-
fileUpload.status = 'progress';
|
713
|
-
fileUpload.progress = parseInt(100.0 * evt.loaded / evt.total);
|
714
|
-
delete fileUpload.message;
|
715
|
-
this.redraw();
|
716
|
-
}, url, options, fileKey, groupPermissions, groupResourceId,
|
717
|
-
// Upload start callback
|
718
|
-
() => {
|
719
|
-
this.emit('fileUploadingStart', filePromise);
|
720
|
-
}, (abort) => fileUpload.abort = abort, multipartOptions).then((fileInfo) => {
|
721
|
-
const index = this.statuses.indexOf(fileUpload);
|
722
|
-
if (index !== -1) {
|
723
|
-
this.statuses.splice(index, 1);
|
724
|
-
}
|
725
|
-
fileInfo.originalName = escapedFileName;
|
726
|
-
fileInfo.hash = fileUpload.hash;
|
727
|
-
if (!this.hasValue()) {
|
728
|
-
this.dataValue = [];
|
729
|
-
}
|
730
|
-
this.dataValue.push(fileInfo);
|
731
|
-
_.pull(this.filesUploading, fileInfo.originalName);
|
732
|
-
this.fileDropHidden = false;
|
733
|
-
this.redraw();
|
734
|
-
this.triggerChange();
|
735
|
-
this.emit('fileUploadingEnd', filePromise);
|
736
|
-
})
|
737
|
-
.catch((response) => {
|
738
|
-
fileUpload.status = 'error';
|
739
|
-
fileUpload.message = typeof response === 'string' ? response : response.toString();
|
740
|
-
delete fileUpload.progress;
|
741
|
-
this.fileDropHidden = false;
|
742
|
-
_.pull(this.filesUploading, file.name);
|
743
|
-
this.redraw();
|
744
|
-
this.emit('fileUploadingEnd', filePromise);
|
745
|
-
});
|
841
|
+
}
|
842
|
+
}
|
843
|
+
}
|
844
|
+
async delete() {
|
845
|
+
if (!this.filesToSync.filesToDelete.length) {
|
846
|
+
return Promise.resolve();
|
847
|
+
}
|
848
|
+
return await Promise.all(this.filesToSync.filesToDelete.map(async (fileToSync) => {
|
849
|
+
try {
|
850
|
+
if (fileToSync.isValidationError) {
|
851
|
+
return { fileToSync };
|
746
852
|
}
|
747
|
-
|
748
|
-
|
853
|
+
await this.deleteFile(fileToSync);
|
854
|
+
fileToSync.status = 'success';
|
855
|
+
fileToSync.message = this.t('Succefully removed');
|
856
|
+
}
|
857
|
+
catch (response) {
|
858
|
+
fileToSync.status = 'error';
|
859
|
+
fileToSync.message = typeof response === 'string' ? response : response.toString();
|
860
|
+
}
|
861
|
+
finally {
|
862
|
+
this.redraw();
|
863
|
+
}
|
864
|
+
return { fileToSync };
|
865
|
+
}));
|
866
|
+
}
|
867
|
+
updateProgress(fileInfo, progressEvent) {
|
868
|
+
fileInfo.progress = parseInt(100.0 * progressEvent.loaded / progressEvent.total);
|
869
|
+
if (fileInfo.status !== 'progress') {
|
870
|
+
fileInfo.status = 'progress';
|
871
|
+
delete fileInfo.message;
|
872
|
+
this.redraw();
|
873
|
+
}
|
874
|
+
else {
|
875
|
+
const progress = Array.prototype.find.call(this.refs.progress, progressElement => progressElement.id === fileInfo.id);
|
876
|
+
progress.innerHTML = `<span class="visually-hidden">${fileInfo.progress}% ${this.t('Complete')}</span>`;
|
877
|
+
progress.style.width = `${fileInfo.progress}%`;
|
878
|
+
progress.ariaValueNow = fileInfo.progress.toString();
|
879
|
+
}
|
880
|
+
}
|
881
|
+
getMultipartOptions(fileToSync) {
|
882
|
+
let count = 0;
|
883
|
+
return this.component.useMultipartUpload && this.component.multipart ? {
|
884
|
+
...this.component.multipart,
|
885
|
+
progressCallback: (total) => {
|
886
|
+
count++;
|
887
|
+
fileToSync.status = 'progress';
|
888
|
+
fileToSync.progress = parseInt(100 * count / total);
|
889
|
+
delete fileToSync.message;
|
890
|
+
this.redraw();
|
891
|
+
},
|
892
|
+
changeMessage: (message) => {
|
893
|
+
fileToSync.message = message;
|
894
|
+
this.redraw();
|
895
|
+
},
|
896
|
+
} : false;
|
897
|
+
}
|
898
|
+
async uploadFile(fileToSync) {
|
899
|
+
return await this.fileService.uploadFile(fileToSync.storage, fileToSync.file, fileToSync.name, fileToSync.dir,
|
900
|
+
// Progress callback
|
901
|
+
this.updateProgress.bind(this, fileToSync), fileToSync.url, fileToSync.options, fileToSync.fileKey, fileToSync.groupPermissions, fileToSync.groupResourceId, () => { },
|
902
|
+
// Abort upload callback
|
903
|
+
(abort) => this.abortUploads.push({
|
904
|
+
id: fileToSync.id,
|
905
|
+
abort,
|
906
|
+
}), this.getMultipartOptions(fileToSync));
|
907
|
+
}
|
908
|
+
async upload() {
|
909
|
+
if (!this.filesToSync.filesToUpload.length) {
|
910
|
+
return Promise.resolve();
|
911
|
+
}
|
912
|
+
return await Promise.all(this.filesToSync.filesToUpload.map(async (fileToSync) => {
|
913
|
+
let fileInfo = null;
|
914
|
+
try {
|
915
|
+
if (fileToSync.isValidationError) {
|
916
|
+
return {
|
917
|
+
fileToSync,
|
918
|
+
fileInfo,
|
919
|
+
};
|
749
920
|
}
|
750
|
-
|
921
|
+
fileInfo = await this.uploadFile(fileToSync);
|
922
|
+
fileToSync.status = 'success';
|
923
|
+
fileToSync.message = this.t('Succefully uploaded');
|
924
|
+
fileInfo.originalName = fileToSync.originalName;
|
925
|
+
fileInfo.hash = fileToSync.hash;
|
926
|
+
}
|
927
|
+
catch (response) {
|
928
|
+
fileToSync.status = 'error';
|
929
|
+
delete fileToSync.progress;
|
930
|
+
fileToSync.message = typeof response === 'string'
|
931
|
+
? response
|
932
|
+
: response.type === 'abort'
|
933
|
+
? this.t('Request was aborted')
|
934
|
+
: response.toString();
|
935
|
+
}
|
936
|
+
finally {
|
937
|
+
delete fileToSync.progress;
|
938
|
+
this.redraw();
|
939
|
+
}
|
940
|
+
return {
|
941
|
+
fileToSync,
|
942
|
+
fileInfo,
|
943
|
+
};
|
944
|
+
}));
|
945
|
+
}
|
946
|
+
async syncFiles() {
|
947
|
+
this.isSyncing = true;
|
948
|
+
this.fileDropHidden = true;
|
949
|
+
this.redraw();
|
950
|
+
try {
|
951
|
+
const [filesToDelete = [], filesToUpload = []] = await Promise.all([this.delete(), this.upload()]);
|
952
|
+
this.filesToSync.filesToDelete = filesToDelete
|
953
|
+
.filter(file => file.fileToSync?.status === 'error')
|
954
|
+
.map(file => file.fileToSync);
|
955
|
+
this.filesToSync.filesToUpload = filesToUpload
|
956
|
+
.filter(file => file.fileToSync?.status === 'error')
|
957
|
+
.map(file => file.fileToSync);
|
958
|
+
if (!this.hasValue()) {
|
959
|
+
this.dataValue = [];
|
960
|
+
}
|
961
|
+
const data = filesToUpload
|
962
|
+
.filter(file => file.fileToSync?.status === 'success')
|
963
|
+
.map(file => file.fileInfo);
|
964
|
+
this.dataValue.push(...data);
|
965
|
+
this.triggerChange();
|
966
|
+
return Promise.resolve();
|
967
|
+
}
|
968
|
+
catch (err) {
|
969
|
+
return Promise.reject();
|
970
|
+
}
|
971
|
+
finally {
|
972
|
+
this.isSyncing = false;
|
973
|
+
this.fileDropHidden = false;
|
974
|
+
this.abortUploads = [];
|
975
|
+
this.redraw();
|
751
976
|
}
|
752
977
|
}
|
753
978
|
getFile(fileInfo) {
|
@@ -783,7 +1008,21 @@ export default class FileComponent extends Field {
|
|
783
1008
|
this.refs.fileBrowse.focus();
|
784
1009
|
}
|
785
1010
|
}
|
786
|
-
|
1011
|
+
async beforeSubmit() {
|
1012
|
+
try {
|
1013
|
+
if (!this.autoSync) {
|
1014
|
+
return Promise.resolve();
|
1015
|
+
}
|
1016
|
+
await this.syncFiles();
|
1017
|
+
return this.shouldSyncFiles
|
1018
|
+
? Promise.reject('Synchronization is failed')
|
1019
|
+
: Promise.resolve();
|
1020
|
+
}
|
1021
|
+
catch (error) {
|
1022
|
+
return Promise.reject(error.message);
|
1023
|
+
}
|
1024
|
+
}
|
1025
|
+
destroy(all) {
|
787
1026
|
this.stopVideo();
|
788
1027
|
super.destroy(all);
|
789
1028
|
}
|