diff --git a/package-lock.json b/package-lock.json index 6a0cb53..28a29e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "html5-file-selector", - "version": "2.1.0", + "version": "2.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 53420cd..aa045b7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": false, "name": "html5-file-selector", - "version": "2.1.0", + "version": "2.1.1", "description": "A wrapper library for more easily handling html5 drag/drop and file input file and folder selections", "homepage": "https://www.github.com/quarklemotion/html5-file-selector", "repository": "quarklemotion/html5-file-selector", diff --git a/src/Html5FileSelector.js b/src/Html5FileSelector.js index db5b5b9..2bf8a0e 100644 --- a/src/Html5FileSelector.js +++ b/src/Html5FileSelector.js @@ -1,6 +1,34 @@ const DEFAULT_FILES_TO_IGNORE = [ '.DS_Store', // OSX indexing file - 'Thumbs.db' // Windows indexing file + '.ds_store', + 'Thumbs.db', // Windows indexing file + '.*~', + '~$*', + '.~lock.*', + '~*.tmp', + '*.~*', + '._*', + '.*.sw?', + '.*.*sw?', + '.TemporaryItems', + '.Trashes', + '.DocumentRevisions-V100', + '.Trash-*', + '.fseventd', + '.apdisk', + '.directory', + '*.part', + '*.filepart', + '*.crdownload', + '*.kate-swp', + '*.gnucash.tmp-*', + '.synkron.*', + '.sync.ffs_db', + '.symform', + '.symform-store', + '.fuse_hidden*', + '*.unison', + '.nfs*', ]; // map of common (mostly media types) mime types to use when the browser does not supply the mime type @@ -19,7 +47,8 @@ const EXTENSION_TO_MIME_TYPE_MAP = { }; function shouldIgnoreFile(file) { - return DEFAULT_FILES_TO_IGNORE.indexOf(file.name) >= 0; + const micromatch = require('micromatch'); + return micromatch.isMatch(file.name, DEFAULT_FILES_TO_IGNORE); } function copyString(aString) { @@ -58,6 +87,32 @@ function traverseDirectory(entry) { }); } +function traverseDirectoryHandle(listItem) { + return new Promise(async (resolveDirectory, reject) => { + const iterationAttempts = []; + try { + const directoryHandle = await listItem.getAsFileSystemHandle(); + + async function* getFileHandlesRecursively(handle) { + if (handle.kind === 'file') { + yield handle; + } else if (handle.kind === 'directory') { + for await (const folderHandle of handle.values()) { + yield* getFileHandlesRecursively(folderHandle); + } + } + }; + + for await (const fileHandle of getFileHandlesRecursively(directoryHandle)) { + iterationAttempts.push(packageFileHandle(fileHandle, directoryHandle)); + } + resolveDirectory(iterationAttempts); + } catch (e) { + reject(e); + } + }); +} + // package the file in an object that includes the fullPath from the file entry // that would otherwise be lost function packageFile(file, entry) { @@ -70,7 +125,7 @@ function packageFile(file, entry) { } return { fileObject: file, // provide access to the raw File object (required for uploading) - fullPath: entry ? copyString(entry.fullPath) : file.name, + fullPath: entry ? copyString(entry.fullPath) : (file.webkitRelativePath !== '' ? file.webkitRelativePath : `/${file.name}`), lastModified: file.lastModified, lastModifiedDate: file.lastModifiedDate, name: file.name, @@ -80,11 +135,40 @@ function packageFile(file, entry) { }; } +function packageFileHandle(fileHandle, folderHandle) { + return new Promise(async (resolve) => { + const file = await fileHandle.getFile(); + let fileData = packageFile(file); + fileData.fileObject = fileHandle; + + if (folderHandle) { + let pathArray = await folderHandle.resolve(fileHandle); + const path = pathArray.join('/'); + fileData.fullPath = `/${folderHandle.name}/${path}`; + } + resolve(fileData); + }); +} + function getFile(entry) { - return new Promise((resolve) => { - entry.file((file) => { - resolve(packageFile(file, entry)); - }); + return new Promise(async (resolve, reject) => { + try { + if (typeof entry.getAsFileSystemHandle === 'function') { + const fileHandle = await entry.getAsFileSystemHandle(); + resolve(packageFileHandle(fileHandle)); + } + if (typeof entry.getFile === 'function') { + const file = entry.getFile(); + resolve(packageFileHandle(file)); + } + if (typeof entry.file === 'function') { + entry.file((file) => { + resolve(packageFile(file, entry)); + }); + } + } catch (e) { + reject(e); + } }); } @@ -99,34 +183,40 @@ function handleFilePromises(promises, fileList) { }); } -export function getDataTransferFiles(dataTransfer) { +export function getDataTransferFiles(dataTransfer, fileHandle) { const dataTransferFiles = []; const folderPromises = []; const filePromises = []; - [].slice.call(dataTransfer.items).forEach((listItem) => { + [].slice.call(dataTransfer.items).forEach(async (listItem) => { if (typeof listItem.webkitGetAsEntry === 'function') { const entry = listItem.webkitGetAsEntry(); - if (entry) { - if (entry.isDirectory) { - folderPromises.push(traverseDirectory(entry)); - } else { - filePromises.push(getFile(entry)); - } + if (!entry) return; + fileHandle = fileHandle && typeof listItem.getAsFileSystemHandle === 'function'; + + if (entry.isDirectory) { + const promise = fileHandle ? traverseDirectoryHandle(listItem) : traverseDirectory(entry); + folderPromises.push(promise); + } else { + const fileEntry = fileHandle ? listItem : entry; + filePromises.push(getFile(fileEntry)); } } else { dataTransferFiles.push(listItem); } }); if (folderPromises.length) { - const flatten = (array) => array.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []); - return Promise.all(folderPromises).then((fileEntries) => { - const flattenedEntries = flatten(fileEntries); - // collect async promises to convert each fileEntry into a File object - flattenedEntries.forEach((fileEntry) => { - filePromises.push(getFile(fileEntry)); - }); + return Promise.all(folderPromises).then((promises) => { + if (fileHandle) { + promises[0].forEach((promise) => {filePromises.push(promise);}); + } else { + const flatten = (array) => array.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []); + const flattenedEntries = flatten(promises); + // collect async promises to convert each fileEntry into a File object + flattenedEntries.forEach((fileEntry) => {filePromises.push(getFile(fileEntry));}); + } + return handleFilePromises(filePromises, dataTransferFiles); }); } else if (filePromises.length) { @@ -134,7 +224,6 @@ export function getDataTransferFiles(dataTransfer) { } return Promise.resolve(dataTransferFiles); } - /** * This function should be called from both the onDrop event from your drag/drop * dropzone as well as from the HTML5 file selector input field onChange event @@ -144,12 +233,12 @@ export function getDataTransferFiles(dataTransfer) { * Returns: an array of File objects, that includes all files within folders * and subfolders of the dropped/selected items. */ -export function getDroppedOrSelectedFiles(event) { +export function getDroppedOrSelectedFiles(event, fileHandle = true) { const dataTransfer = event.dataTransfer; if (dataTransfer && dataTransfer.items) { - return getDataTransferFiles(dataTransfer).then((fileList) => { - return Promise.resolve(fileList); - }); + return getDataTransferFiles(dataTransfer, fileHandle).then((fileList) => { + return Promise.resolve(fileList); + }); } const files = []; const dragDropFileList = dataTransfer && dataTransfer.files;