comand-component-library 3.1.41 → 3.1.45

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.
@@ -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>