Skip to content

Pu/fix filehandle support #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
143 changes: 116 additions & 27 deletions src/Html5FileSelector.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand All @@ -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);
}
});
}

Expand All @@ -99,42 +183,47 @@ 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) {
return handleFilePromises(filePromises, dataTransferFiles);
}
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
Expand All @@ -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;
Expand Down