UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

453 lines (380 loc) 12 kB
import Vue from 'vue' import QBtn from '../btn/QBtn.js' import QIcon from '../icon/QIcon.js' import QSpinner from '../spinner/QSpinner.js' import QCircularProgress from '../circular-progress/QCircularProgress.js' import FileMixin from '../../mixins/file.js' import DarkMixin from '../../mixins/dark.js' import { stop } from '../../utils/event.js' import { humanStorageSize } from '../../utils/format.js' import cache from '../../utils/cache.js' export default Vue.extend({ name: 'QUploaderBase', mixins: [ DarkMixin, FileMixin ], props: { label: String, color: String, textColor: String, square: Boolean, flat: Boolean, bordered: Boolean, noThumbnails: Boolean, autoUpload: Boolean, hideUploadBtn: Boolean, disable: Boolean, readonly: Boolean }, provide () { return { __qUploaderGetInput: this.__getInputControl } }, data () { return { files: [], queuedFiles: [], uploadedFiles: [], dnd: false, expanded: false, uploadSize: 0, uploadedSize: 0 } }, watch: { isUploading (newVal, oldVal) { if (oldVal === false && newVal === true) { this.$emit('start') } else if (oldVal === true && newVal === false) { this.$emit('finish') } } }, computed: { /* * When extending: * Required : isUploading * Optional: isBusy */ canUpload () { return this.editable === true && this.isBusy !== true && this.isUploading !== true && this.queuedFiles.length > 0 }, canAddFiles () { return ( this.editable === true && this.isUploading !== true && // if single selection and no files are queued: (this.multiple === true || this.queuedFiles.length === 0) && // if max-files is set and current number of files does not exceeds it: (this.maxFiles === void 0 || this.files.length < this.maxFilesNumber) && // if max-total-size is set and current upload size does not exceeds it: (this.maxTotalSize === void 0 || this.uploadSize < this.maxTotalSizeNumber) ) }, uploadProgress () { return this.uploadSize === 0 ? 0 : this.uploadedSize / this.uploadSize }, uploadProgressLabel () { return this.__getProgressLabel(this.uploadProgress) }, uploadedSizeLabel () { return humanStorageSize(this.uploadedSize) }, uploadSizeLabel () { return humanStorageSize(this.uploadSize) }, colorClass () { const cls = [] this.color !== void 0 && cls.push(`bg-${this.color}`) this.textColor !== void 0 && cls.push(`text-${this.textColor}`) return cls.join(' ') }, editable () { return this.disable !== true && this.readonly !== true } }, methods: { reset () { if (!this.disable) { this.abort() this.uploadedSize = 0 this.uploadSize = 0 this.__revokeImgURLs() this.files = [] this.queuedFiles = [] this.uploadedFiles = [] } }, removeUploadedFiles () { this.__removeFiles([ 'uploaded' ], () => { this.uploadedFiles = [] }) }, removeQueuedFiles () { this.__removeFiles([ 'idle', 'failed' ], ({ size }) => { this.uploadSize -= size this.queuedFiles = [] }) }, __removeFiles (statusList, cb) { if (this.disable === true) { return } const removed = { files: [], size: 0 } const files = this.files.filter(f => { if (statusList.indexOf(f.__status) === -1) { return true } removed.size += f.size removed.files.push(f) f._img !== void 0 && window.URL.revokeObjectURL(f._img.src) return false }) if (removed.files.length > 0) { this.files = files cb !== void 0 && cb(removed) this.$emit('removed', removed.files) } }, removeFile (file) { if (this.disable) { return } if (file.__status === 'uploaded') { this.uploadedFiles = this.uploadedFiles.filter(f => f.name !== file.name) } else if (file.__status === 'uploading') { file.__abort() } else { this.uploadSize -= file.size } this.files = this.files.filter(f => { if (f.name !== file.name) { return true } f._img !== void 0 && window.URL.revokeObjectURL(f._img.src) return false }) this.queuedFiles = this.queuedFiles.filter(f => f.name !== file.name) this.$emit('removed', [ file ]) }, __revokeImgURLs () { this.files.forEach(f => { f._img !== void 0 && window.URL.revokeObjectURL(f._img.src) }) }, __getFileInput () { return this.$refs.input || this.$el.getElementsByClassName('q-uploader__input')[0] }, __getProgressLabel (p) { return (p * 100).toFixed(2) + '%' }, __updateFile (file, status, uploadedSize) { file.__status = status if (status === 'idle') { file.__uploaded = 0 file.__progress = 0 file.__sizeLabel = humanStorageSize(file.size) file.__progressLabel = '0.00%' return } if (status === 'failed') { this.$forceUpdate() return } file.__uploaded = status === 'uploaded' ? file.size : uploadedSize file.__progress = status === 'uploaded' ? 1 : Math.min(0.9999, file.__uploaded / file.size) file.__progressLabel = this.__getProgressLabel(file.__progress) this.$forceUpdate() }, __addFiles (e, fileList) { const processedFiles = this.__processFiles(e, fileList, this.files, true) if (processedFiles === void 0) { return } const files = processedFiles .filter(file => this.files.findIndex(f => file.name === f.name) === -1) if (files === void 0) { return } const fileInput = this.__getFileInput() if (fileInput !== void 0) { fileInput.value = '' } files.forEach(file => { this.__updateFile(file, 'idle') this.uploadSize += file.size if (this.noThumbnails !== true && file.type.toUpperCase().startsWith('IMAGE')) { const img = new Image() img.src = window.URL.createObjectURL(file) file.__img = img } }) this.files = this.files.concat(files) this.queuedFiles = this.queuedFiles.concat(files) this.$emit('added', files) this.autoUpload === true && this.upload() }, __getBtn (h, show, icon, fn) { if (show === true) { return h(QBtn, { props: { type: 'a', icon: this.$q.iconSet.uploader[icon], flat: true, dense: true }, on: icon === 'add' ? null : { click: fn } }, icon === 'add' ? this.__getInputControl(h) : null) } }, __getInputControl (h) { return [ h('input', { ref: 'input', staticClass: 'q-uploader__input overflow-hidden absolute-full', attrs: { tabindex: -1, type: 'file', title: '', // try to remove default tooltip accept: this.accept, capture: this.capture, ...(this.multiple === true ? { multiple: true } : {}) }, on: cache(this, 'input', { mousedown: stop, // need to stop refocus from QBtn change: this.__addFiles }) }) ] }, __getHeader (h) { if (this.$scopedSlots.header !== void 0) { return this.$scopedSlots.header(this) } return [ h('div', { staticClass: 'q-uploader__header-content flex flex-center no-wrap q-gutter-xs' }, [ this.__getBtn(h, this.queuedFiles.length > 0, 'removeQueue', this.removeQueuedFiles), this.__getBtn(h, this.uploadedFiles.length > 0, 'removeUploaded', this.removeUploadedFiles), this.isUploading === true ? h(QSpinner, { staticClass: 'q-uploader__spinner' }) : null, h('div', { staticClass: 'col column justify-center' }, [ this.label !== void 0 ? h('div', { staticClass: 'q-uploader__title' }, [ this.label ]) : null, h('div', { staticClass: 'q-uploader__subtitle' }, [ this.uploadSizeLabel + ' / ' + this.uploadProgressLabel ]) ]), this.__getBtn(h, this.canAddFiles, 'add', this.pickFiles), this.__getBtn(h, this.hideUploadBtn === false && this.canUpload === true, 'upload', this.upload), this.__getBtn(h, this.isUploading, 'clear', this.abort) ]) ] }, __getList (h) { if (this.$scopedSlots.list !== void 0) { return this.$scopedSlots.list(this) } return this.files.map(file => h('div', { key: file.name, staticClass: 'q-uploader__file relative-position', class: { 'q-uploader__file--img': this.noThumbnails !== true && file.__img !== void 0, 'q-uploader__file--failed': file.__status === 'failed', 'q-uploader__file--uploaded': file.__status === 'uploaded' }, style: this.noThumbnails !== true && file.__img !== void 0 ? { backgroundImage: 'url("' + file.__img.src + '")' } : null }, [ h('div', { staticClass: 'q-uploader__file-header row flex-center no-wrap' }, [ file.__status === 'failed' ? h(QIcon, { staticClass: 'q-uploader__file-status', props: { name: this.$q.iconSet.type.negative, color: 'negative' } }) : null, h('div', { staticClass: 'q-uploader__file-header-content col' }, [ h('div', { staticClass: 'q-uploader__title' }, [ file.name ]), h('div', { staticClass: 'q-uploader__subtitle row items-center no-wrap' }, [ file.__sizeLabel + ' / ' + file.__progressLabel ]) ]), file.__status === 'uploading' ? h(QCircularProgress, { props: { value: file.__progress, min: 0, max: 1, indeterminate: file.__progress === 0 } }) : h(QBtn, { props: { round: true, dense: true, flat: true, icon: this.$q.iconSet.uploader[file.__status === 'uploaded' ? 'done' : 'clear'] }, on: { click: () => { this.removeFile(file) } } }) ]) ])) } }, beforeDestroy () { this.isUploading === true && this.abort() this.files.length > 0 && this.__revokeImgURLs() }, render (h) { const children = [ h('div', { staticClass: 'q-uploader__header', class: this.colorClass }, this.__getHeader(h)), h('div', { staticClass: 'q-uploader__list scroll' }, this.__getList(h)), this.__getDnd(h, 'uploader') ] this.isBusy === true && children.push( h('div', { staticClass: 'q-uploader__overlay absolute-full flex flex-center' }, [ h(QSpinner) ]) ) return h('div', { staticClass: 'q-uploader column no-wrap', class: { 'q-uploader--dark q-dark': this.isDark, 'q-uploader--bordered': this.bordered, 'q-uploader--square no-border-radius': this.square, 'q-uploader--flat no-shadow': this.flat, 'disabled q-uploader--disable': this.disable }, on: this.canAddFiles === true ? cache(this, 'drag', { dragover: this.__onDragOver }) : null }, children) } })