primevue-mirror/components/lib/fileupload/FileUpload.vue

439 lines
17 KiB
Vue
Raw Normal View History

2022-09-06 12:03:37 +00:00
<template>
2024-02-11 08:10:29 +00:00
<div v-if="isAdvanced" :class="cx('root')" v-bind="ptmi('root')">
2023-04-28 16:03:29 +00:00
<input ref="fileInput" type="file" @change="onFileSelect" :multiple="multiple" :accept="accept" :disabled="chooseDisabled" v-bind="ptm('input')" />
<div :class="cx('header')" v-bind="ptm('header')">
2023-05-03 14:15:56 +00:00
<slot name="header" :files="files" :uploadedFiles="uploadedFiles" :chooseCallback="choose" :uploadCallback="upload" :clearCallback="clear">
2024-05-06 15:23:01 +00:00
<Button :label="chooseButtonLabel" :class="chooseButtonClass" :style="style" :disabled="disabled" :unstyled="unstyled" @click="choose" @keydown.enter="choose" @focus="onFocus" @blur="onBlur" :pt="ptm('pcChooseButton')">
2024-03-25 06:34:29 +00:00
<template #icon="iconProps">
<slot name="chooseicon">
2024-05-06 15:23:01 +00:00
<component :is="chooseIcon ? 'span' : 'PlusIcon'" :class="[iconProps.class, chooseIcon]" aria-hidden="true" v-bind="ptm('pcChooseButton')['icon']" />
2024-03-25 06:34:29 +00:00
</slot>
</template>
</Button>
2024-05-06 15:23:01 +00:00
<Button v-if="showUploadButton" :class="cx('pcUploadButton')" :label="uploadButtonLabel" @click="upload" :disabled="uploadDisabled" :unstyled="unstyled" :pt="ptm('pcUploadButton')">
<template #icon="iconProps">
<slot name="uploadicon">
2024-05-06 15:23:01 +00:00
<component :is="uploadIcon ? 'span' : 'UploadIcon'" :class="[iconProps.class, uploadIcon]" aria-hidden="true" v-bind="ptm('pcUploadButton')['icon']" data-pc-section="uploadbuttonicon" />
</slot>
</template>
2024-03-25 06:34:29 +00:00
</Button>
2024-05-06 15:23:01 +00:00
<Button v-if="showCancelButton" :class="cx('pcCancelButton')" :label="cancelButtonLabel" @click="clear" :disabled="cancelDisabled" :unstyled="unstyled" :pt="ptm('pcCancelButton')">
<template #icon="iconProps">
<slot name="cancelicon">
2024-05-06 15:23:01 +00:00
<component :is="cancelIcon ? 'span' : 'TimesIcon'" :class="[iconProps.class, cancelIcon]" aria-hidden="true" v-bind="ptm('pcCancelButton')['icon']" data-pc-section="cancelbuttonicon" />
</slot>
</template>
2024-03-25 06:34:29 +00:00
</Button>
2022-12-08 11:04:25 +00:00
</slot>
2022-09-06 12:03:37 +00:00
</div>
2023-05-31 08:53:28 +00:00
<div ref="content" :class="cx('content')" @dragenter="onDragEnter" @dragover="onDragOver" @dragleave="onDragLeave" @drop="onDrop" v-bind="ptm('content')" :data-p-highlight="false">
2022-12-08 11:04:25 +00:00
<slot name="content" :files="files" :uploadedFiles="uploadedFiles" :removeUploadedFileCallback="removeUploadedFile" :removeFileCallback="remove" :progress="progress" :messages="messages">
<template v-if="hasFiles">
2024-05-06 15:23:01 +00:00
<ProgressBar :value="progress" :showValue="false" :unstyled="unstyled" :pt="ptm('pcProgressbar')" />
2024-04-20 12:45:44 +00:00
<div v-if="hasFiles" :class="cx('fileList')">
<FileContent :files="files" @remove="remove" :badgeValue="pendingLabel" :previewWidth="previewWidth" :templates="$slots" :unstyled="unstyled" :pt="pt" />
</div>
</template>
2024-05-06 15:23:01 +00:00
<Message v-for="msg of messages" :key="msg" severity="error" @close="onMessageClose" :unstyled="unstyled" :pt="ptm('pcMessage')">{{ msg }}</Message>
2024-04-20 12:45:44 +00:00
<div v-if="hasUploadedFiles" :class="cx('fileList')">
<FileContent :files="uploadedFiles" @remove="removeUploadedFile" :badgeValue="completedLabel" badgeSeverity="success" :previewWidth="previewWidth" :templates="$slots" :unstyled="unstyled" :pt="pt" />
</div>
2022-12-08 11:04:25 +00:00
</slot>
<div v-if="$slots.empty && !hasFiles && !hasUploadedFiles" v-bind="ptm('empty')">
2022-09-06 12:03:37 +00:00
<slot name="empty"></slot>
</div>
</div>
</div>
2024-02-11 08:10:29 +00:00
<div v-else-if="isBasic" :class="cx('root')" v-bind="ptmi('root')">
2024-05-06 15:23:01 +00:00
<Message v-for="msg of messages" :key="msg" severity="error" @close="onMessageClose" :unstyled="unstyled" :pt="ptm('pcMessages')">{{ msg }}</Message>
<Button :label="chooseButtonLabel" :class="chooseButtonClass" :style="style" :disabled="disabled" :unstyled="unstyled" @mouseup="onBasicUploaderClick" @keydown.enter="choose" @focus="onFocus" @blur="onBlur" v-bind="ptm('pcButton')">
2024-03-25 06:34:29 +00:00
<template #icon="iconProps">
<slot v-if="!hasFiles || auto" name="uploadicon">
2024-05-06 15:23:01 +00:00
<component :is="uploadIcon ? 'span' : 'UploadIcon'" :class="[iconProps.class, uploadIcon]" aria-hidden="true" v-bind="ptm('pcButton')['icon']" />
2024-03-25 06:34:29 +00:00
</slot>
2024-05-06 15:23:01 +00:00
<slot v-else name="chooseicon">
<component :is="chooseIcon ? 'span' : 'PlusIcon'" :class="[iconProps.class, chooseIcon]" aria-hidden="true" v-bind="ptm('pcButton')['icon']" />
2024-03-25 06:34:29 +00:00
</slot>
</template>
</Button>
<slot v-if="!auto" name="filelabel" :class="cx('filelabel')">
<span :class="cx('filelabel')" :files="files">
{{ basicChooseButtonLabel }}
</span>
</slot>
2024-03-25 06:34:29 +00:00
<input v-if="!hasFiles" ref="fileInput" type="file" :accept="accept" :disabled="disabled" :multiple="multiple" @change="onFileSelect" @focus="onFocus" @blur="onBlur" v-bind="ptm('input')" />
2022-09-06 12:03:37 +00:00
</div>
</template>
<script>
import Button from 'primevue/button';
import PlusIcon from 'primevue/icons/plus';
import TimesIcon from 'primevue/icons/times';
import UploadIcon from 'primevue/icons/upload';
2022-09-06 12:03:37 +00:00
import Message from 'primevue/message';
2022-12-08 11:04:25 +00:00
import ProgressBar from 'primevue/progressbar';
2022-09-06 12:03:37 +00:00
import Ripple from 'primevue/ripple';
2023-08-18 10:43:15 +00:00
import { DomHandler } from 'primevue/utils';
2023-05-31 08:53:28 +00:00
import BaseFileUpload from './BaseFileUpload.vue';
2022-12-08 11:04:25 +00:00
import FileContent from './FileContent.vue';
2022-09-06 12:03:37 +00:00
export default {
name: 'FileUpload',
2023-05-31 08:53:28 +00:00
extends: BaseFileUpload,
2024-02-11 08:10:29 +00:00
inheritAttrs: false,
2022-12-08 11:04:25 +00:00
emits: ['select', 'uploader', 'before-upload', 'progress', 'upload', 'error', 'before-send', 'clear', 'remove', 'remove-uploaded-file'],
2022-09-06 12:03:37 +00:00
duplicateIEEvent: false,
data() {
return {
uploadedFileCount: 0,
files: [],
messages: [],
focused: false,
2022-12-08 11:04:25 +00:00
progress: null,
uploadedFiles: []
2022-09-14 11:26:01 +00:00
};
2022-09-06 12:03:37 +00:00
},
methods: {
basicUpload() {
if (this.hasFiles) this.upload();
},
onBasicUploaderClick(event) {
if (event.button === 0 && !this.hasFiles) this.$refs.fileInput.click();
},
2022-09-06 12:03:37 +00:00
onFileSelect(event) {
if (event.type !== 'drop' && this.isIE11() && this.duplicateIEEvent) {
this.duplicateIEEvent = false;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
return;
}
this.messages = [];
this.files = this.files || [];
let files = event.dataTransfer ? event.dataTransfer.files : event.target.files;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
for (let file of files) {
if (!this.isFileSelected(file)) {
if (this.validate(file)) {
if (this.isImage(file)) {
file.objectURL = window.URL.createObjectURL(file);
}
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
this.files.push(file);
}
}
}
2022-09-14 11:26:01 +00:00
this.$emit('select', { originalEvent: event, files: this.files });
2022-09-06 12:03:37 +00:00
if (this.fileLimit) {
this.checkFileLimit();
}
if (this.auto && this.hasFiles && !this.isFileLimitExceeded()) {
this.upload();
}
if (event.type !== 'drop' && this.isIE11()) {
this.clearIEInput();
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
this.clearInputElement();
}
},
choose() {
this.$refs.fileInput.click();
},
upload() {
if (this.customUpload) {
if (this.fileLimit) {
this.uploadedFileCount += this.files.length;
}
2022-09-14 11:26:01 +00:00
this.$emit('uploader', { files: this.files });
this.clear();
} else {
2022-09-06 12:03:37 +00:00
let xhr = new XMLHttpRequest();
let formData = new FormData();
this.$emit('before-upload', {
2022-09-14 11:26:01 +00:00
xhr: xhr,
formData: formData
2022-09-06 12:03:37 +00:00
});
for (let file of this.files) {
formData.append(this.name, file, file.name);
}
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
this.progress = Math.round((event.loaded * 100) / event.total);
}
this.$emit('progress', {
originalEvent: event,
progress: this.progress
});
});
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
this.progress = 0;
if (xhr.status >= 200 && xhr.status < 300) {
if (this.fileLimit) {
this.uploadedFileCount += this.files.length;
}
this.$emit('upload', {
xhr: xhr,
files: this.files
});
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
this.$emit('error', {
xhr: xhr,
files: this.files
});
}
2022-12-08 11:04:25 +00:00
this.uploadedFiles.push(...this.files);
2022-09-06 12:03:37 +00:00
this.clear();
}
};
xhr.open('POST', this.url, true);
this.$emit('before-send', {
2022-09-14 11:26:01 +00:00
xhr: xhr,
formData: formData
2022-09-06 12:03:37 +00:00
});
xhr.withCredentials = this.withCredentials;
xhr.send(formData);
}
},
clear() {
this.files = [];
this.messages = null;
this.$emit('clear');
if (this.isAdvanced) {
this.clearInputElement();
}
},
onFocus() {
this.focused = true;
},
onBlur() {
this.focused = false;
},
isFileSelected(file) {
if (this.files && this.files.length) {
for (let sFile of this.files) {
2022-09-14 11:26:01 +00:00
if (sFile.name + sFile.type + sFile.size === file.name + file.type + file.size) return true;
2022-09-06 12:03:37 +00:00
}
}
return false;
},
isIE11() {
return !!window['MSInputMethodContext'] && !!document['documentMode'];
},
validate(file) {
if (this.accept && !this.isFileTypeValid(file)) {
2022-09-14 11:26:01 +00:00
this.messages.push(this.invalidFileTypeMessage.replace('{0}', file.name).replace('{1}', this.accept));
2022-09-06 12:03:37 +00:00
return false;
}
if (this.maxFileSize && file.size > this.maxFileSize) {
this.messages.push(this.invalidFileSizeMessage.replace('{0}', file.name).replace('{1}', this.formatSize(this.maxFileSize)));
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
return false;
}
return true;
},
isFileTypeValid(file) {
2022-09-14 11:26:01 +00:00
let acceptableTypes = this.accept.split(',').map((type) => type.trim());
for (let type of acceptableTypes) {
let acceptable = this.isWildcard(type) ? this.getTypeClass(file.type) === this.getTypeClass(type) : file.type == type || this.getFileExtension(file).toLowerCase() === type.toLowerCase();
2022-09-06 12:03:37 +00:00
if (acceptable) {
return true;
}
}
return false;
},
getTypeClass(fileType) {
return fileType.substring(0, fileType.indexOf('/'));
},
2022-09-14 11:26:01 +00:00
isWildcard(fileType) {
2022-09-06 12:03:37 +00:00
return fileType.indexOf('*') !== -1;
},
getFileExtension(file) {
return '.' + file.name.split('.').pop();
},
isImage(file) {
return /^image\//.test(file.type);
},
onDragEnter(event) {
if (!this.disabled) {
event.stopPropagation();
event.preventDefault();
}
},
onDragOver(event) {
if (!this.disabled) {
!this.isUnstyled && DomHandler.addClass(this.$refs.content, 'p-fileupload-highlight');
2023-05-31 08:53:28 +00:00
this.$refs.content.setAttribute('data-p-highlight', true);
2022-09-06 12:03:37 +00:00
event.stopPropagation();
event.preventDefault();
}
},
onDragLeave() {
if (!this.disabled) {
2023-08-21 07:49:03 +00:00
!this.isUnstyled && DomHandler.removeClass(this.$refs.content, 'p-fileupload-highlight');
2023-05-31 08:53:28 +00:00
this.$refs.content.setAttribute('data-p-highlight', false);
2022-09-06 12:03:37 +00:00
}
},
onDrop(event) {
if (!this.disabled) {
2023-08-21 07:49:03 +00:00
!this.isUnstyled && DomHandler.removeClass(this.$refs.content, 'p-fileupload-highlight');
2023-05-31 08:53:28 +00:00
this.$refs.content.setAttribute('data-p-highlight', false);
2022-09-06 12:03:37 +00:00
event.stopPropagation();
event.preventDefault();
const files = event.dataTransfer ? event.dataTransfer.files : event.target.files;
const allowDrop = this.multiple || (files && files.length === 1);
if (allowDrop) {
this.onFileSelect(event);
}
}
},
remove(index) {
this.clearInputElement();
let removedFile = this.files.splice(index, 1)[0];
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
this.files = [...this.files];
this.$emit('remove', {
file: removedFile,
files: this.files
});
},
2022-12-08 11:04:25 +00:00
removeUploadedFile(index) {
let removedFile = this.uploadedFiles.splice(index, 1)[0];
this.uploadedFiles = [...this.uploadedFiles];
this.$emit('remove-uploaded-file', {
file: removedFile,
files: this.uploadedFiles
});
},
2022-09-06 12:03:37 +00:00
clearInputElement() {
this.$refs.fileInput.value = '';
},
clearIEInput() {
if (this.$refs.fileInput) {
this.duplicateIEEvent = true; //IE11 fix to prevent onFileChange trigger again
this.$refs.fileInput.value = '';
}
},
formatSize(bytes) {
const k = 1024;
const dm = 3;
const sizes = this.$primevue.config.locale?.fileSizeTypes || ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
2022-09-06 12:03:37 +00:00
if (bytes === 0) {
return `0 ${sizes[0]}`;
2022-09-06 12:03:37 +00:00
}
2022-09-14 11:26:01 +00:00
const i = Math.floor(Math.log(bytes) / Math.log(k));
const formattedSize = parseFloat((bytes / Math.pow(k, i)).toFixed(dm));
2022-09-06 12:03:37 +00:00
return `${formattedSize} ${sizes[i]}`;
2022-09-06 12:03:37 +00:00
},
isFileLimitExceeded() {
if (this.fileLimit && this.fileLimit <= this.files.length + this.uploadedFileCount && this.focused) {
this.focused = false;
}
return this.fileLimit && this.fileLimit < this.files.length + this.uploadedFileCount;
},
checkFileLimit() {
if (this.isFileLimitExceeded()) {
2022-09-14 11:26:01 +00:00
this.messages.push(this.invalidFileLimitMessage.replace('{0}', this.fileLimit.toString()));
2022-09-06 12:03:37 +00:00
}
},
onMessageClose() {
this.messages = null;
}
},
computed: {
isAdvanced() {
return this.mode === 'advanced';
},
isBasic() {
return this.mode === 'basic';
},
2023-05-31 08:53:28 +00:00
chooseButtonClass() {
2024-05-06 15:23:01 +00:00
return [this.cx('pcChooseButton'), this.class];
2022-09-06 12:03:37 +00:00
},
basicChooseButtonLabel() {
if (this.auto) return this.chooseButtonLabel;
else if (this.hasFiles) {
if (this.files && this.files.length === 1) return this.files[0].name;
return this.$primevue.config.locale?.fileChosenMessage?.replace('{0}', this.files.length);
}
return this.$primevue.config.locale?.emptyFileChosenMessage || '';
2022-09-06 12:03:37 +00:00
},
hasFiles() {
return this.files && this.files.length > 0;
},
2022-12-08 11:04:25 +00:00
hasUploadedFiles() {
return this.uploadedFiles && this.uploadedFiles.length > 0;
},
2022-09-06 12:03:37 +00:00
chooseDisabled() {
return this.disabled || (this.fileLimit && this.fileLimit <= this.files.length + this.uploadedFileCount);
},
uploadDisabled() {
return this.disabled || !this.hasFiles || (this.fileLimit && this.fileLimit < this.files.length);
},
cancelDisabled() {
return this.disabled || !this.hasFiles;
},
chooseButtonLabel() {
return this.chooseLabel || this.$primevue.config.locale.choose;
},
uploadButtonLabel() {
return this.uploadLabel || this.$primevue.config.locale.upload;
},
cancelButtonLabel() {
return this.cancelLabel || this.$primevue.config.locale.cancel;
2022-12-08 11:04:25 +00:00
},
completedLabel() {
return this.$primevue.config.locale.completed;
},
pendingLabel() {
return this.$primevue.config.locale.pending;
2022-09-06 12:03:37 +00:00
}
},
components: {
2024-03-25 06:34:29 +00:00
Button,
ProgressBar,
Message,
FileContent,
PlusIcon,
UploadIcon,
TimesIcon
2022-09-06 12:03:37 +00:00
},
directives: {
2022-09-14 11:26:01 +00:00
ripple: Ripple
2022-09-06 12:03:37 +00:00
}
2022-09-14 11:26:01 +00:00
};
2022-09-06 12:03:37 +00:00
</script>