@formio/js 5.0.0-rc.34 → 5.0.0-rc.35
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
}
|