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

Sign up to get free protection for your applications and to get access to all the features.
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
  }