@formio/js 5.0.0-rc.34 → 5.0.0-rc.36

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