comand-component-library 3.1.43 → 3.1.44

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,166 +1,455 @@
1
1
  <template>
2
- <fieldset ref="upload" class=" cmd-upload-form grid-container-create-columns">
3
- <legend>Upload form</legend>
4
- <h2 v-if="headline">{{ headline }}</h2>
5
- <CmdFormElement
6
- element="input"
7
- type="file"
8
- multiple="multiple"
9
- labelText="Choose file(s) with file-explorer:"
10
- @change="filesSelected"
11
- v-show="enableFileSelect"
12
- />
13
- <template v-if="enableDragAndDrop">
14
- <span v-show="enableFileSelect">or</span>
15
- <a href="#" :class="['box', {'allow-drop': allowDrop}]" @dragenter="dragEnter" @dragover="dragOver"
16
- @dragleave="dragLeave" @drop="drop($event)" @click.prevent="openFileDialog">
17
- <span>Drag & drop file(s) here</span>
18
- </a>
19
- </template>
20
- <hr/>
21
- <h2>List of files to upload</h2>
22
- <ul v-if="listOfFiles.length" class="list-of-files">
23
- <li v-for="(uploadFile, index) in listOfFiles" :key="index">
24
- <a href="#" class="icon-delete" title="Remove from list" @click.prevent="removeFile(index)"></a>
25
- <span :class="[uploadFile.allowedType ? 'allowed' : 'not-allowed']">
26
- <strong>{{ uploadFile.file.name }}</strong> ({{ uploadFile.file.type }}, {{
27
- formatSize(uploadFile.file.size)
28
- }}<template v-if="uploadFile.width && uploadFile.height">, {{
29
- uploadFile.width
30
- }} px x {{ uploadFile.height }} px</template>)
31
- <span v-if="uploadFile.allowedType" class="icon-check allowed" title="File ready to upload!"></span>
32
- <span v-else class="icon-cancel not-allowed"
33
- title="File type not allowed (file will not be uploaded)!"></span>
34
- </span>
35
- </li>
36
- </ul>
37
- <CmdSystemMessage v-if="!listOfFiles.length" status="warning" :fullWidth="true"
38
- message="No files selected for upload!">
39
- </CmdSystemMessage>
40
- <CmdSystemMessage v-else :status="messageStatusUploadSize()" :fullWidth="true">
41
- <p>Current upload size is {{ formatSize(uploadSize) }} (of max. {{ formatSize(maxUploadSize) }}).</p>
42
- </CmdSystemMessage>
43
- <CmdSystemMessage v-if="uploadSize > maxUploadSize" status="error" :fullWidth="true"
44
- message="Total file size to large!">
2
+ <fieldset :class="['my-sp-upload-form flex-container', { 'upload-initiated': uploadInitiated }]">
3
+ <h3 v-if="headline">{{ headline }}</h3>
4
+ <CmdSystemMessage
5
+ v-if="systemMessageStatus && allSystemMessages.length"
6
+ :closeIcon="{ show: false }"
7
+ :status="systemMessageStatus"
8
+ :systemMessage="
9
+ allSystemMessages.length === 1
10
+ ? allSystemMessages[0]
11
+ : getMessage('cmduploadform.system_message.the_following_errors_occurred')
12
+ "
13
+ >
14
+ <ul v-if="allSystemMessages.length > 1">
15
+ <li v-for="(systemMessage, index) in allSystemMessages" :key="index">
16
+ {{ systemMessage }}
17
+ </li>
18
+ </ul>
45
19
  </CmdSystemMessage>
20
+ <div :class="['box', { 'allow-drop': allowDrop }]" v-on="dragAndDropHandler">
21
+ <template v-if="!listOfFiles.length">
22
+ <h4 v-if="allowMultipleFileUploads">
23
+ {{ getMessage("cmduploadform.no_files_to_upload") }}
24
+ </h4>
25
+ <h4 v-else>
26
+ {{ getMessage("cmduploadform.no_file_to_upload") }}
27
+ </h4>
28
+ </template>
29
+
30
+ <!-- begin total-upload information -->
31
+ <template v-else>
32
+ <template v-if="showTotalUpload && listOfFiles.length !== 1">
33
+ <h4>{{ getMessage("cmduploadform.headline.summary_of_all_files") }}</h4>
34
+ <ul v-if="showTotalUpload && listOfFiles.length !== 1" class="list-of-files">
35
+ <li class="flex-container no-flex">
36
+ <a
37
+ href="#"
38
+ :title="getMessage('cmduploadform.labeltext.remove_all_files_from_list')"
39
+ @click.prevent="cancelUpload"
40
+ >
41
+ <span :class="deleteIconClass"></span>
42
+ </a>
43
+ <span>
44
+ <strong>{{ listOfFiles.length }}
45
+ <template v-if="!allowMultipleFileUploads">
46
+ {{ getMessage("cmduploadform.labeltext.file_uploading") }}
47
+ </template>
48
+ <template v-else>
49
+ {{ getMessage("cmduploadform.labeltext.files_uploading") }}
50
+ </template>
51
+ <span
52
+ :class="[
53
+ 'text-align-right',
54
+ { error: maxTotalUploadSize > 0 && totalSize > maxTotalUploadSize }
55
+ ]">({{ formatSize(totalSize) }})</span>
56
+ </strong>
57
+ </span>
58
+ <span class="progressbar" v-if="uploadInitiated">
59
+ <span>{{ getPercentage(totalUploadProgress) }}</span>
60
+ <progress
61
+ max="100"
62
+ :value="totalUploadProgress"
63
+ :title="totalBytesUploaded"
64
+ ></progress>
65
+ </span>
66
+ </li>
67
+ </ul>
68
+ <hr/>
69
+ </template>
70
+ <!-- end total-upload information -->
71
+
72
+ <!-- begin list of selected files -->
73
+ <h4>{{ getMessage("cmduploadform.headline.list_of_selected_files") }}</h4>
74
+ <ul class="list-of-files">
75
+ <li
76
+ v-for="(uploadFile, index) in listOfFiles"
77
+ :key="index"
78
+ class="flex-container no-flex"
79
+ >
80
+ <a
81
+ href="#"
82
+ :title="getMessage('cmduploadform.labeltext.remove_file_from_list')"
83
+ @click.prevent="removeFile(index)"
84
+ ><span :class="deleteIconClass"></span>
85
+ </a>
86
+ <span
87
+ :class="[
88
+ 'text-align-right',
89
+ uploadFile.allowedType ? 'allowed' : 'not-allowed',
90
+ { error: uploadFile.error }
91
+ ]"
92
+ >
93
+ {{ uploadFile.file.name }} ({{ formatSize(uploadFile.file.size) }})
94
+ </span>
95
+ <template v-if="uploadInitiated && !uploadFile.error">
96
+ <span class="progressbar">
97
+ <span>{{ getPercentage(uploadFile.progress) }}</span>
98
+ <!-- do not place inside progress-tag (will not be displayed then) -->
99
+ <progress
100
+ max="100"
101
+ :value="uploadFile.progress"
102
+ :title="
103
+ formatSize(uploadFile.uploadedBytes) + '/' + formatSize(uploadFile.file.size)
104
+ "
105
+ ></progress>
106
+ </span>
107
+ </template>
108
+ </li>
109
+ </ul>
110
+ <a
111
+ v-if="failedUpload"
112
+ href="#"
113
+ @click.prevent="cancel"
114
+ :title="getMessage('cmduploadform.all_files_will_be_removed')">
115
+ {{ getMessage("cmduploadform.reset_upload") }}
116
+ </a>
117
+ <hr/>
118
+ </template>
119
+ <!-- end list of selected files -->
120
+
121
+ <!-- begin upload conditions -->
122
+ <h4 v-if="allowMultipleFileUploads && listOfFiles.length">
123
+ {{ getMessage("cmduploadform.headline.select_additional_files") }}
124
+ </h4>
125
+ <h4 v-if="!allowMultipleFileUploads && listOfFiles.length">
126
+ {{ getMessage("cmduploadform.headline.select_new_file") }}
127
+ </h4>
128
+ <dl class="small">
129
+ <template v-if="maxTotalUploadSize > 0">
130
+ <dt :class="{ error: totalSize > maxTotalUploadSize }">
131
+ {{ getMessage("cmduploadform.max_total_upload_size") }}
132
+ </dt>
133
+ <dd :class="['text-align-right', { error: totalSize > maxTotalUploadSize }]">
134
+ {{ formatSize(this.maxTotalUploadSize) }}
135
+ </dd>
136
+ </template>
137
+ <dt :class="{ error: errors.fileSize }">
138
+ {{ getMessage("cmduploadform.max_file_upload_size") }}
139
+ </dt>
140
+ <dd :class="['text-align-right', { error: errors.fileSize }]">
141
+ {{ formatSize(this.maxFileUploadSize) }}
142
+ </dd>
143
+ <dt :class="{ error: errors.fileType }">
144
+ {{ getMessage("cmduploadform.allowed_file_types") }}
145
+ </dt>
146
+ <dd>
147
+ <a
148
+ :class="showListOfFileExtensions ? 'icon-not-visible' : 'icon-visible'"
149
+ href="#"
150
+ @click.prevent="showListOfFileExtensions = !showListOfFileExtensions"
151
+ :title="getMessage('cmduploadform.tooltip.toggle_list_of_allowed_file_types')"
152
+ ></a>
153
+ <transition name="fade">
154
+ <ul v-if="showListOfFileExtensions" class="list-of-file-extensions">
155
+ <li
156
+ v-for="(fileExtension, index) in allowedFileExtensions"
157
+ :key="index"
158
+ :class="{ error: errors.fileType }"
159
+ >
160
+ {{ fileExtension }}
161
+ </li>
162
+ </ul>
163
+ </transition>
164
+ </dd>
165
+ </dl>
166
+ <!-- end upload conditions -->
167
+ <button
168
+ type="button"
169
+ :class="['button upload primary', { disabled: uploadInitiated }]"
170
+ :disabled="uploadInitiated"
171
+ @click="selectFiles()"
172
+ >
173
+ <span class="icon-file-upload"></span>
174
+ <span v-if="allowMultipleFileUploads">{{
175
+ getMessage("cmduploadform.labeltext.select_files")
176
+ }}</span>
177
+ <span v-else>{{ getMessage("cmduploadform.labeltext.select_file") }}</span>
178
+ </button>
179
+ <CmdFormElement
180
+ element="input"
181
+ type="file"
182
+ :labelText="getMessage('cmduploadform.labeltext.select_files')"
183
+ :disabled="uploadInitiated"
184
+ :multiple="allowMultipleFileUploads"
185
+ @change="filesSelected"
186
+ />
187
+ <p v-if="enableDragAndDrop" :class="['text-drag-and-drop', { disabled: uploadInitiated }]">
188
+ <span>{{ getMessage("cmduploadform.or") }}</span>
189
+ <strong>
190
+ {{ getMessage("cmduploadform.drag_and_drop") }}
191
+ <template v-if="allowMultipleFileUploads && listOfFiles.length">
192
+ {{ getMessage("cmduploadform.additional") }}
193
+ </template
194
+ >
195
+ <template v-if="!allowMultipleFileUploads && listOfFiles.length">
196
+ {{ getMessage("cmduploadform.new") }}
197
+ </template
198
+ >
199
+ {{ getMessage("cmduploadform.files_to_this_area") }}
200
+ </strong>
201
+ </p>
202
+ </div>
46
203
  <CmdFormElement
47
204
  v-if="enableComment"
48
205
  element="textarea"
49
- labelText="Comment:"
50
- placeholder="Add a comment"
51
- v-model:value="comment"
206
+ :labelText="getMessage('cmduploadform.labeltext.comment')"
207
+ v-model="comment"
208
+ :required="commentRequired"
209
+ :statusMessage="commentStatusMessage"
210
+ :placeholder="getMessage('cmduploadform.placeholder.comment')"
211
+ :status="commentStatusMessage ? 'error' : ''"
52
212
  />
53
213
  <div class="button-wrapper no-flex">
54
- <button :class="['button', 'primary', {disabled: getNumberAllowedFiles < 1}]" @click="uploadFiles">
55
- <span class="icon-upload"></span>
56
- <span v-if="getNumberAllowedFiles < 1">Nothing to upload</span>
57
- <span v-else>Upload {{ getNumberAllowedFiles }} of {{ listOfFiles.length }} files</span>
214
+ <button
215
+ :class="[
216
+ 'button primary',
217
+ {
218
+ disabled:
219
+ listOfFiles.length === 0 ||
220
+ (maxTotalUploadSize > 0 && totalSize > maxTotalUploadSize) ||
221
+ uploadInitiated
222
+ }
223
+ ]"
224
+ :disabled="
225
+ listOfFiles.length === 0 ||
226
+ (maxTotalUploadSize > 0 && totalSize > maxTotalUploadSize) ||
227
+ uploadInitiated
228
+ "
229
+ @click="uploadFiles"
230
+ >
231
+ <span class="icon-upload"></span
232
+ ><span v-if="listOfFiles.length === 1 || !allowMultipleFileUploads">{{
233
+ getMessage("cmduploadform.buttontext.upload_file")
234
+ }}</span>
235
+ <span v-else>{{ getMessage("cmduploadform.buttontext.upload_files") }}</span>
58
236
  </button>
59
- <button class="button" @click="cancelUpload">
60
- <span class="icon-cancel"></span><span>Cancel</span>
237
+ <button :class="['button', { disabled: listOfFiles.length === 0 }]" @click="cancel">
238
+ <span class="icon-cancel"></span
239
+ ><span>{{ getMessage("cmduploadform.buttontext.cancel") }}</span>
61
240
  </button>
62
241
  </div>
63
242
  </fieldset>
64
243
  </template>
65
244
 
66
245
  <script>
67
- // import components
246
+ import I18n from "../mixins/I18n"
247
+ import DefaultMessageProperties from "../mixins/CmdUploadForm/DefaultMessageProperties"
248
+ import {getFileExtension} from "../utils/GetFileExtension.js"
249
+ import axios from "axios"
250
+
68
251
  import CmdFormElement from "./CmdFormElement"
69
252
  import CmdSystemMessage from "./CmdSystemMessage"
70
253
 
254
+
71
255
  export default {
72
256
  name: "CmdUploadForm",
73
- emits: ["click"],
74
257
  data() {
75
258
  return {
76
259
  comment: "",
77
260
  allowDrop: false,
78
- listOfFiles: []
261
+ listOfFiles: [],
262
+ systemMessages: [],
263
+ defaultSystemMessageStatus: "",
264
+ showListOfFileExtensions: false,
265
+ resetForm: {},
266
+ uploadInitiated: false,
267
+ errors: {}
79
268
  }
80
269
  },
270
+ mixins: [I18n, DefaultMessageProperties],
81
271
  components: {
82
- CmdFormElement,
83
- CmdSystemMessage
272
+ CmdSystemMessage,
273
+ CmdFormElement
274
+ },
275
+ created() {
276
+ // Set initial data for resetForm.
277
+ this.resetForm.comment = this.presetComment
278
+ this.resetForm.allowDrop = this.allowDrop
279
+ this.resetForm.listOfFiles = JSON.parse(JSON.stringify(this.listOfFiles))
280
+ this.resetForm.systemMessages = this.systemMessages
281
+ this.resetForm.systemMessageStatus = this.systemMessageStatus
84
282
  },
85
283
  props: {
86
284
  /**
87
- * headline for form
285
+ * set icon class for delete-icons
88
286
  */
89
- headline: {
287
+ deleteIconClass: {
90
288
  type: String,
91
- required: false
289
+ default: "icon-delete"
92
290
  },
93
291
  /**
94
- * url to uplaod files
292
+ * toggle visibility of total upload (number of files, total size, total progress
95
293
  */
96
- uploadURL: {
97
- type: String,
98
- required: false
294
+ showTotalUpload: {
295
+ type: Boolean,
296
+ default: true
99
297
  },
100
298
  /**
101
- * activate if files for upload should be selected by native input (type="file")
299
+ * toggle if upload is handled by component itself or by outer component
102
300
  */
103
- enableFileSelect: {
301
+ componentHandlesUpload: {
104
302
  type: Boolean,
105
303
  default: true
106
304
  },
107
305
  /**
108
- * activate if files should be dragged and dropped from os-file-explorer
306
+ * list of allowed file extensions to upload (all can be selected)
109
307
  */
110
- enableDragAndDrop: {
308
+ allowedFileExtensions: {
309
+ type: Array,
310
+ required: true
311
+ },
312
+ commentRequired: {
111
313
  type: Boolean,
112
314
  default: true
113
315
  },
316
+ commentStatusMessage: {
317
+ type: String,
318
+ default: ""
319
+ },
320
+ /**
321
+ * set an optional headlien for the fieldset
322
+ */
323
+ headline: {
324
+ type: String,
325
+ required: false
326
+ },
114
327
  /**
115
- * enable textarea for additional comments
328
+ * enable if files can also be dragged (and dropped) into upload-area
116
329
  */
330
+ enableDragAndDrop: {
331
+ type: Boolean,
332
+ default: false
333
+ },
117
334
  enableComment: {
118
335
  type: Boolean,
119
336
  default: true
120
337
  },
121
338
  /**
122
- * set upload options
339
+ * set to 0 if no maximum for total upload size should be set
123
340
  */
124
- uploadOptions: {
125
- type: Object,
126
- required: false
341
+ maxTotalUploadSize: {
342
+ type: Number,
343
+ default: 5242880
127
344
  },
128
345
  /**
129
- * list of allowed file types to upload (all can be selected)
346
+ * max file size (in bytes) for each single file
130
347
  */
131
- allowedFileTypes: {
132
- type: Array,
133
- required: true
348
+ maxFileUploadSize: {
349
+ type: Number,
350
+ default: 10485760
351
+ },
352
+ allowMultipleFileUploads: {
353
+ type: Boolean,
354
+ default: false
355
+ },
356
+ presetComment: {
357
+ type: String,
358
+ default: ""
134
359
  },
135
360
  /**
136
- * set maximum upload size (for all files combined)
361
+ * defines upload options if component handles upload itself
362
+ * (componentHandlesUpload-property must be true)
363
+ *
364
+ * Example:
365
+ * <pre>
366
+ * url: String,
367
+ * filesParam: String,
368
+ * additionalParams: {}
369
+ * </pre>
137
370
  */
138
- maxUploadSize: {
139
- type: Number,
140
- default: 10485760
371
+ uploadOptions: {
372
+ type: Object,
373
+ required: false
141
374
  }
142
375
  },
143
376
  computed: {
144
- uploadSize() {
145
- let uploadSize = 0
377
+ failedUpload() {
378
+ return this.listOfFiles.some(file => file.error)
379
+ },
380
+ totalBytesUploaded() {
381
+ const bytes = this.listOfFiles
382
+ .map(uploadFile => [
383
+ uploadFile.file.size,
384
+ ((uploadFile.progress || 0) * uploadFile.file.size) / 100
385
+ ])
386
+ .reduce((a, b) => [a[0] + b[0], a[1] + b[1]])
387
+ return this.formatSize(bytes[1]) + "/" + this.formatSize(bytes[0])
388
+ },
389
+ totalSize() {
390
+ let totalSize = 0
146
391
  for (let i = 0; i < this.listOfFiles.length; i++) {
147
- if (this.listOfFiles[i].allowedType) {
148
- uploadSize = uploadSize + this.listOfFiles[i].file.size
149
- }
392
+ totalSize = totalSize + this.listOfFiles[i].file.size
150
393
  }
151
- return uploadSize
394
+ return totalSize
152
395
  },
153
- getNumberAllowedFiles() {
154
- let numberAllowedFiles = 0
155
- for (let i = 0; i < this.listOfFiles.length; i++) {
156
- if (this.listOfFiles[i].allowedType) {
157
- numberAllowedFiles++
396
+ allSystemMessages() {
397
+ if (this.maxTotalUploadSize > 0 && this.totalSize > this.maxTotalUploadSize) {
398
+ return [
399
+ this.getMessage("cmduploadform.system_message_total_size_of_files_too_large"),
400
+ ...this.systemMessages
401
+ ]
402
+ }
403
+ return this.systemMessages
404
+ },
405
+ systemMessageStatus() {
406
+ return this.defaultSystemMessageStatus || (this.allSystemMessages.length ? "error" : "")
407
+ },
408
+ dragAndDropHandler() {
409
+ // register handlers only if drag-and-drop is enabled
410
+ if (this.enableDragAndDrop) {
411
+ return {
412
+ dragenter: this.dragEnter,
413
+ dragover: this.dragOver,
414
+ dragleave: this.dragLeave,
415
+ drop: this.drop
158
416
  }
159
417
  }
160
- return numberAllowedFiles
418
+ return {}
419
+ },
420
+ totalUploadProgress() {
421
+ const progress = this.listOfFiles
422
+ .map(uploadFile => [
423
+ uploadFile.file.size,
424
+ ((uploadFile.progress || 0) * uploadFile.file.size) / 100
425
+ ])
426
+ .reduce((a, b) => [a[0] + b[0], a[1] + b[1]])
427
+ return (progress[1] / progress[0]) * 100
428
+ }
429
+ },
430
+ watch: {
431
+ presetComment: {
432
+ handler(newValue) {
433
+ this.comment = newValue
434
+ },
435
+ immediate: true
161
436
  }
162
437
  },
163
438
  methods: {
439
+ getPercentage(percentage) {
440
+ if (percentage) {
441
+ return percentage.toFixed(2) + "%"
442
+ }
443
+ return "0.00%"
444
+ },
445
+ // use imported function as method (to use in template)
446
+ getFileExtension(filename) {
447
+ return getFileExtension(filename)
448
+ },
449
+ selectFiles() {
450
+ let inputFile = this.$el.querySelector('input[type="file"]')
451
+ inputFile.click()
452
+ },
164
453
  dragEnter(event) {
165
454
  this.dragOver(event)
166
455
  },
@@ -187,11 +476,11 @@ export default {
187
476
  this.allowDrop = false
188
477
  },
189
478
  /*
190
- drag(event) {
191
- alert("dropped")
192
- event.dataTransfer.setData("text", event.target.id)
193
- },
194
- */
479
+ drag(event) {
480
+ alert("dropped")
481
+ event.dataTransfer.setData("text", event.target.id)
482
+ },
483
+ */
195
484
  drop(event) {
196
485
  this.allowDrop = false
197
486
  if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length > 0) {
@@ -200,144 +489,403 @@ export default {
200
489
  }
201
490
  },
202
491
  cancelUpload() {
492
+ // cancel upload for each file
493
+ this.listOfFiles.forEach(file => {
494
+ if (file.abortController) {
495
+ file.abortController.abort()
496
+ }
497
+ })
498
+
499
+ // clear list of files, remove error-highlighting and hide all system-messages afterwards
500
+ this.errors = {}
203
501
  this.listOfFiles = []
204
- this.comment = ""
205
- this.$emit('click', 'cancel')
502
+ this.hideAllSystemMessages()
503
+
504
+ // set uploadInitiated to false to enable all disabled buttons
505
+ this.uploadInitiated = false
506
+ },
507
+ cancel() {
508
+ this.cancelUpload()
509
+
510
+ // emit click event with argument "cancel" to react in outer component
511
+ this.$emit("click", "cancel")
206
512
  },
207
513
  filesSelected(event) {
208
514
  this.checkFiles(event.target.files)
209
515
  },
210
516
  checkFiles(files) {
517
+ this.defaultSystemMessageStatus = "" // hide systemMessage if already is shown
518
+ this.systemMessages = [] // hide systemMessage if already is shown
519
+ this.errors = {}
520
+
211
521
  for (let i = 0; i < files.length; i++) {
212
- let uploadFile = {
213
- "file": files[i],
214
- "allowedType": false,
215
- width: 0,
216
- height: 0
522
+ // define file-object which will be pushed in listOfFiles
523
+ const uploadFile = {
524
+ file: files[i],
525
+ progress: null,
526
+ uploadedBytes: 0
217
527
  }
218
528
 
219
- if (this.allowedFileTypes.includes(files[i].type)) {
220
- uploadFile.allowedType = true
529
+ // check size for current file
530
+ if (files[i].size > this.maxFileUploadSize) {
531
+ this.errors.fileSize = true
532
+ this.systemMessages.push(
533
+ this.getMessage(
534
+ "cmduploadform.system_message.file_size_too_large",
535
+ files[i].name,
536
+ files[i].size
537
+ )
538
+ )
539
+ continue
221
540
  }
222
541
 
223
- if (files[i].type.slice(0, 6) == "image/" && files[i].size < this.maxUploadSize) {
224
- // get dimensions if image
225
- const reader = new FileReader()
226
-
227
- reader.addEventListener('load', function () {
228
- const img = new Image()
229
- img.src = this.result
230
- img.addEventListener('load', () => {
231
- uploadFile.width = img.width
232
- uploadFile.height = img.height
233
- })
234
- })
235
- reader.readAsDataURL(files[i])
542
+ // check if current file has allowed file-type (else continue with next file)
543
+ if (!this.allowedFileExtensions.includes(getFileExtension(files[i].name))) {
544
+ this.showListOfFileExtensions = true
545
+ this.errors.fileType = true
546
+ this.systemMessages.push(
547
+ this.getMessage(
548
+ "cmduploadform.system_message.not_allowed_file_type",
549
+ files[i].name,
550
+ getFileExtension(files[i].name)
551
+ )
552
+ )
553
+ continue
554
+ }
555
+
556
+ // check (if multiple files can be selected) if current file already exists in listOfFiles
557
+ if (
558
+ this.allowMultipleFileUploads &&
559
+ this.listOfFiles.some(listOfFilesEntry =>
560
+ this.compareFiles(listOfFilesEntry.file, files[i])
561
+ )
562
+ ) {
563
+ this.systemMessages.push(
564
+ this.getMessage(
565
+ "cmduploadform.system_message.duplicate_file",
566
+ files[i].name,
567
+ getFileExtension(files[i].name)
568
+ )
569
+ )
570
+ continue
571
+ }
572
+
573
+ if (this.allowMultipleFileUploads) {
574
+ // push file-object (for each valid file) to listOfFiles-array
575
+ this.listOfFiles.push(uploadFile)
576
+ } else {
577
+ if (files.length > 1) {
578
+ this.systemMessages.push(
579
+ this.getMessage("cmduploadform.system_message.only_one_file_allowed")
580
+ )
581
+ }
582
+ // assign uploadFile-object (which contains current (and valid) file to listOfFiles-array
583
+ this.listOfFiles = [uploadFile]
584
+ break
236
585
  }
237
- this.listOfFiles.push(uploadFile)
238
586
  }
239
587
  },
588
+ compareFiles(file1, file2) {
589
+ return (
590
+ file1.name === file2.name &&
591
+ file1.lastModified === file2.lastModified &&
592
+ file1.size === file2.size
593
+ )
594
+ },
240
595
  removeFile(index) {
241
- /* remove file from list */
596
+ /* remove specific file from list */
242
597
  this.listOfFiles.splice(index, 1)
598
+ if (!this.listOfFiles.length) {
599
+ this.uploadInitiated = false
600
+ }
601
+ this.hideAllSystemMessages()
602
+ },
603
+ hideAllSystemMessages() {
604
+ // hide all system-messages if all files are removed from list
605
+ if (!this.listOfFiles.length) {
606
+ this.systemMessages = []
607
+ }
243
608
  },
244
609
  formatSize(size) {
245
610
  if (size < 1024) {
246
611
  return size
247
612
  } else if (size < 1048576) {
248
- return size = (Math.round(size / 1024 * 100) / 100) + " KB"
613
+ return (size = Math.round((size / 1024) * 100) / 100 + " KB")
249
614
  } else {
250
- return size = (Math.round(size / 1048576 * 100) / 100) + " MB"
615
+ return (size = Math.round((size / 1048576) * 100) / 100 + " MB")
251
616
  }
252
617
  },
253
618
  uploadFiles() {
254
- if (this.uploadURL) {
255
- let formData = new FormData()
256
- for (let i; i < this.listOfFiles.length; i++) {
257
- if (this.listOfFiles[i].allowedType) {
258
- formData.append("uploadedFiles", this.listOfFiles[i].file)
619
+ this.systemMessages = []
620
+ this.errors = {}
621
+ this.defaultSystemMessageStatus = ""
622
+
623
+ if (
624
+ this.enableComment &&
625
+ !this.comment &&
626
+ this.commentRequired &&
627
+ this.commentStatusMessage
628
+ ) {
629
+ this.defaultSystemMessageStatus = "error"
630
+ this.systemMessages.push(this.getMessage("cmduploadform.system_message.fill_required"))
631
+ } else {
632
+ this.uploadInitiated = true
633
+
634
+ if (this.componentHandlesUpload && this.uploadOptions && this.uploadOptions.url) {
635
+ const url = new URL(this.uploadOptions.url, location.href)
636
+ const formData = new FormData()
637
+
638
+ // // append information about files to formData-object
639
+ // formData.append(
640
+ // this.uploadOptions.filesParam ? this.uploadOptions.filesParam : "files",
641
+ // this.listOfFiles.map(uploadFile => uploadFile.file)
642
+ // )
643
+
644
+ // iterate over additionalParams-object and append to formData-object
645
+ Object.entries(this.uploadOptions.additionalParams || {}).forEach(([key, value]) =>
646
+ formData.append(key, value)
647
+ )
648
+
649
+ // append comment to formData-object
650
+ if (this.enableComment) {
651
+ formData.append("comment", this.comment)
259
652
  }
260
- }
261
653
 
262
- if (formData.has("uploadedFiles")) {
263
- fetch(this.uploadURL, {method: "POST", body: formData})
654
+ const requests = []
655
+
656
+ // iterate over list-of-files to send upload request for each file individually
657
+ this.listOfFiles.forEach(file => {
658
+ requests.push(this.uploadSingleFile(url, file, formData))
659
+ })
660
+
661
+ // check upload-status for each file individually
662
+ Promise.allSettled(requests).then(results => {
663
+ // if status equals "rejected" the upload was not successful and the file will not be deleted from list
664
+ const rejectedFiles = results.filter(result => result.status === "rejected")
665
+ this.uploadInitiated = false
666
+
667
+ if (rejectedFiles.length) {
668
+ this.defaultSystemMessageStatus = "error"
669
+ this.systemMessages.push(
670
+ this.getMessage(
671
+ "cmduploadform.system_message.some_files_are_not_uploaded_successfully"
672
+ )
673
+ )
674
+ } else {
675
+ this.defaultSystemMessageStatus = "success"
676
+ this.systemMessages.push(
677
+ this.getMessage("cmduploadform.system_message.all_files_are_uploaded_successfully")
678
+ )
679
+ }
680
+
681
+ this.$emit("upload-complete", {success: !rejectedFiles.length})
682
+ })
683
+ } else {
684
+ let uploadObj = {}
685
+ uploadObj.listOfFiles = this.listOfFiles
686
+ uploadObj.type = "upload"
687
+ uploadObj.comment = this.comment
688
+
689
+ // emit uploadObj to handle upload by outer component
690
+ this.$emit("click", uploadObj, this.showMessage)
264
691
  }
692
+ }
693
+ },
694
+ onUploadProgress(event, uploadFile) {
695
+ if (event.lengthComputable) {
696
+ uploadFile.progress = (event.loaded / event.total) * 100
697
+ uploadFile.uploadedBytes = event.loaded
265
698
  } else {
266
- this.$emit('click', 'upload')
699
+ uploadFile.progress = null
700
+ uploadFile.uploadedBytes = 0
267
701
  }
702
+ this.$forceUpdate()
703
+ },
704
+ uploadSingleFile(url, file, formData) {
705
+ file.abortController = new AbortController()
706
+
707
+ // append information about given file to formData-object
708
+ formData.set(
709
+ this.uploadOptions.filesParam ? this.uploadOptions.filesParam : "files",
710
+ file.file
711
+ )
712
+
713
+ return (
714
+ axios
715
+ .post(url, formData, {
716
+ signal: file.abortController.signal,
717
+ onUploadProgress: event => this.onUploadProgress(event, file)
718
+ })
719
+ // emit information about successful-upload + file
720
+ .then(response => {
721
+ this.$emit("upload-file-success", file)
722
+ return response
723
+ })
724
+ // delete uploaded file from list-of-files-array
725
+ .then(response => {
726
+ const positionOfFile = this.listOfFiles.indexOf(file)
727
+ if (positionOfFile !== -1) {
728
+ this.listOfFiles.splice(positionOfFile, 1)
729
+ }
730
+ return response
731
+ })
732
+ .catch(error => {
733
+ this.defaultSystemMessageStatus = "error"
734
+ this.systemMessages.push(error)
735
+ file.error = true
736
+ throw new Error()
737
+ })
738
+ )
268
739
  },
269
- messageStatusUploadSize() {
270
- if (this.uploadSize > this.maxUploadSize || this.uploadSize === 0) {
271
- return "error"
740
+ showMessage(result) {
741
+ if (result === true) {
742
+ this.defaultSystemMessageStatus = "success"
743
+ this.systemMessages.push(this.getMessage("cmduploadform.system_message.upload_success"))
744
+ } else if (result === false) {
745
+ this.defaultSystemMessageStatus = "error"
746
+ this.systemMessages.push(this.getMessage("cmduploadform.system_message.upload_failed"))
272
747
  }
273
- return "success"
274
748
  },
275
- openFileDialog() {
276
- this.$refs.upload.querySelector("input[type='file']").click()
749
+ resetFields() {
750
+ if (typeof this.resetForm === "object") {
751
+ const resetArr = Object.keys(this.resetForm)
752
+ for (let key of resetArr) {
753
+ if (typeof this.resetForm[key] === "object") {
754
+ this[key] = JSON.parse(JSON.stringify(this.resetForm[key]))
755
+ } else {
756
+ this[key] = this.resetForm[key]
757
+ }
758
+ }
759
+ }
277
760
  }
278
761
  }
279
762
  }
280
763
  </script>
281
764
 
282
765
  <style lang="scss">
283
- /* begin cmd-upload-form ---------------------------------------------------------------------------------------- */
284
- .cmd-upload-form {
766
+ /* begin my-sp-upload-form -------------------------------------------------------------------------------------------- */
767
+ .my-sp-upload-form {
285
768
  .box {
286
- padding: calc(var(--default-padding) * 3);
769
+ padding: (var(--default-padding));
287
770
  text-align: center;
288
- background: rgba(255, 255, 255, .5);
289
771
  border-style: dashed;
772
+ background: var(--pure-white-reduced-opacity);
290
773
 
291
774
  &.allow-drop {
292
775
  border-style: solid;
293
- background: rgba(255, 255, 255, 1);
776
+ background: var(--pure-white);
777
+ }
778
+
779
+ dl {
780
+ justify-content: center;
781
+ text-align: left;
782
+
783
+ .list-of-file-extensions {
784
+ display: table;
785
+
786
+ > li:only-child {
787
+ list-style-type: none;
788
+ margin: 0;
789
+ }
790
+ }
791
+ }
792
+ }
793
+
794
+ [class*="list-of-file"] {
795
+ max-height: 10rem;
796
+ overflow-x: hidden;
797
+ overflow-y: auto;
798
+ border: var(--default-border);
799
+ padding: calc(var(--default-padding) / 2);
800
+ margin: 0;
801
+
802
+ > li {
803
+ flex-wrap: nowrap;
804
+ margin-right: var(--default-margin); /* avoids text to be placed below scrollbar */
805
+
806
+ .progressbar {
807
+ display: table;
808
+ align-self: center;
809
+
810
+ progress {
811
+ &[value] {
812
+ background: var(--pure-white);
813
+
814
+ &::-moz-progress-bar {
815
+ border-top-left-radius: var(--border-radius);
816
+ border-bottom-left-radius: var(--border-radius);
817
+ background: var(--primary-color);
818
+ }
819
+ }
820
+ }
821
+
822
+ & > span {
823
+ position: absolute;
824
+ left: 50%;
825
+ transform: translateX(-50%);
826
+ z-index: 1;
827
+ font-size: 1.2rem;
828
+ display: table;
829
+ top: 0.2rem;
830
+ padding: 0.1rem 0.2rem;
831
+ line-height: 100%;
832
+ background: var(--pure-white);
833
+ }
834
+ }
294
835
  }
295
836
  }
296
837
 
297
838
  .list-of-files {
298
- margin-bottom: 0;
839
+ display: inline-flex;
840
+ flex-direction: column;
841
+ gap: calc(var(--default-gap) / 2);
842
+
843
+ &:last-of-type {
844
+ margin-bottom: var(--default-margin);
845
+ }
299
846
 
300
847
  li {
301
848
  list-style-type: none;
302
849
  margin-left: 0;
850
+ gap: calc(var(--default-gap) / 2);
851
+ }
303
852
 
304
- > span {
305
- span[class*="icon"]:last-child {
306
- display: inline-flex;
307
- align-items: center;
308
- justify-content: center;
309
- padding: calc(var(--default-padding) / 2);
310
- }
853
+ & + a {
854
+ display: table;
855
+ margin: 0 auto;
856
+ }
857
+ }
311
858
 
312
- &.allowed {
313
- color: var(--success-color);
859
+ p {
860
+ &.text-drag-and-drop {
861
+ &.disabled {
862
+ background: none !important; /* overwrite default styling for .disabled */
863
+ }
864
+ }
865
+ }
314
866
 
315
- span[class*="icon"]:last-child {
316
- border: var(--success-border);
317
- background-color: var(--success-color);
318
- }
319
- }
867
+ textarea {
868
+ min-height: 0;
869
+ }
320
870
 
321
- &.not-allowed {
322
- color: var(--error-color);
871
+ .button.upload {
872
+ align-self: center;
323
873
 
324
- span[class*="icon"]:last-child {
325
- border: var(--error-border);
326
- background-color: var(--error-color);
327
- }
328
- }
874
+ & + .cmd-form-element {
875
+ display: none;
876
+ }
329
877
 
330
- span[class*="icon"]:last-child {
331
- border: var(--default-border);
332
- border-radius: var(--full-circle);
333
- font-size: 1rem;
334
- font-weight: bold;
335
- color: var(--pure-white);
336
- }
878
+ & ~ p {
879
+ & > * {
880
+ display: block;
337
881
  }
338
882
  }
339
883
  }
884
+
885
+ .error {
886
+ color: var(--error-color);
887
+ }
340
888
  }
341
889
 
342
- /* end cmd-upload-form ------------------------------------------------------------------------------------------ */
890
+ /* end my-sp-upload-form ---------------------------------------------------------------------------------------------- */
343
891
  </style>