UNPKG

quasar

Version:

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

262 lines (216 loc) 6.24 kB
import { h, ref, computed, getCurrentInstance } from 'vue' import { client } from '../../plugins/platform/Platform.js' import { stop, stopAndPrevent } from '../../utils/event/event.js' function filterFiles(files, rejectedFiles, failedPropValidation, filterFn) { const acceptedFiles = [] files.forEach(file => { if (filterFn(file) === true) { acceptedFiles.push(file) } else { rejectedFiles.push({ failedPropValidation, file }) } }) return acceptedFiles } function stopAndPreventDrag(e) { if (e?.dataTransfer) { e.dataTransfer.dropEffect = 'copy' } stopAndPrevent(e) } export const useFileProps = { multiple: Boolean, accept: String, capture: String, maxFileSize: [Number, String], maxTotalSize: [Number, String], maxFiles: [Number, String], filter: Function } export const useFileEmits = ['rejected'] export default function useFile({ editable, dnd, getFileInput, addFilesToQueue }) { const { props, emit, proxy } = getCurrentInstance() const dndRef = ref(null) const extensions = computed(() => props.accept !== void 0 ? props.accept.split(',').map(ext => { ext = ext.trim() if (ext === '*') { // support "*" return '*/' } else if (ext.endsWith('/*')) { // support "image/*" or "*/*" ext = ext.slice(0, ext.length - 1) } return ext.toUpperCase() }) : null ) const maxFilesNumber = computed(() => parseInt(props.maxFiles, 10)) const maxTotalSizeNumber = computed(() => parseInt(props.maxTotalSize, 10)) function pickFiles(e) { if (editable.value) { if (e !== Object(e)) { e = { target: null } } if (e.target?.matches('input[type="file"]') === true) { // stop propagation if it's not a real pointer event if (e.clientX === 0 && e.clientY === 0) stop(e) } else { const input = getFileInput() if (input !== e.target) input?.click(e) } } } function addFiles(files) { if (editable.value && files) { addFilesToQueue(null, files) } } function processFiles(e, filesToProcess, currentFileList, append) { let files = Array.from(filesToProcess || e.target.files) const rejectedFiles = [] const done = () => { if (rejectedFiles.length !== 0) { emit('rejected', rejectedFiles) } } // filter file types if (props.accept !== void 0 && extensions.value.indexOf('*/') === -1) { files = filterFiles(files, rejectedFiles, 'accept', file => extensions.value.some( ext => file.type.toUpperCase().startsWith(ext) || file.name.toUpperCase().endsWith(ext) ) ) if (files.length === 0) { return done() } } // filter max file size if (props.maxFileSize !== void 0) { const maxFileSize = parseInt(props.maxFileSize, 10) files = filterFiles( files, rejectedFiles, 'max-file-size', file => file.size <= maxFileSize ) if (files.length === 0) { return done() } } // Cordova/iOS allows selecting multiple files even when the // multiple attribute is not specified. We also normalize drag'n'dropped // files here: if (props.multiple !== true && files.length !== 0) { files = [files[0]] } // Compute key to use for each file files.forEach(file => { file.__key = file.webkitRelativePath + file.lastModified + file.name + file.size }) if (append === true) { // Avoid duplicate files const filenameMap = currentFileList.map(entry => entry.__key) files = filterFiles( files, rejectedFiles, 'duplicate', file => filenameMap.includes(file.__key) === false ) } if (files.length === 0) { return done() } if (props.maxTotalSize !== void 0) { let size = append === true ? currentFileList.reduce((total, file) => total + file.size, 0) : 0 files = filterFiles(files, rejectedFiles, 'max-total-size', file => { size += file.size return size <= maxTotalSizeNumber.value }) if (files.length === 0) { return done() } } // do we have custom filter function? if (typeof props.filter === 'function') { const filteredFiles = props.filter(files) files = filterFiles(files, rejectedFiles, 'filter', file => filteredFiles.includes(file) ) } if (props.maxFiles !== void 0) { let filesNumber = append === true ? currentFileList.length : 0 files = filterFiles(files, rejectedFiles, 'max-files', () => { filesNumber++ return filesNumber <= maxFilesNumber.value }) if (files.length === 0) { return done() } } done() if (files.length !== 0) { return files } } function onDragover(e) { stopAndPreventDrag(e) if (dnd.value !== true) dnd.value = true } function onDragleave(e) { stopAndPrevent(e) // Safari bug: relatedTarget is null for over 10 years // https://bugs.webkit.org/show_bug.cgi?id=66547 const gone = e.relatedTarget !== null || client.is.safari !== true ? e.relatedTarget !== dndRef.value : document .elementsFromPoint(e.clientX, e.clientY) .includes(dndRef.value) === false if (gone === true) dnd.value = false } function onDrop(e) { stopAndPreventDrag(e) const files = e.dataTransfer.files if (files.length !== 0) { addFilesToQueue(null, files) } dnd.value = false } function getDndNode(type) { if (dnd.value === true) { return h('div', { ref: dndRef, class: `q-${type}__dnd absolute-full`, onDragenter: stopAndPreventDrag, onDragover: stopAndPreventDrag, onDragleave, onDrop }) } } // expose public methods Object.assign(proxy, { pickFiles, addFiles }) return { pickFiles, addFiles, onDragover, onDragleave, processFiles, getDndNode, maxFilesNumber, maxTotalSizeNumber } }