Skip to content

Instantly share code, notes, and snippets.

@amanchopra95
Last active March 7, 2024 18:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amanchopra95/7e5dd52100837d63386c6fd0bb25e1a1 to your computer and use it in GitHub Desktop.
Save amanchopra95/7e5dd52100837d63386c6fd0bb25e1a1 to your computer and use it in GitHub Desktop.
Extract Files in HTML5 and Javascript. Drop any file or folder. This gist will return the list of files by recursively traversing the directories
/**
* Traversing directory using promises
**/
const traverseDirectory = (entry) => {
const reader = entry.createReader();
return new Promise((resolveDirectory) => {
const iterationAttempts = [];
const errorHandler = () => {};
function readEntries() {
reader.readEntries((batchEntries) => {
if (!batchEntries.length) {
resolveDirectory(Promise.all(iterationAttempts))
} else {
iterationAttempts.push(Promise.all(batchEntries.map((batchEntry) => {
if (batchEntry.isDirectory) {
return traverseDirectory(batchEntry);
}
return Promise.resolve(batchEntry);
})));
readEntries();
}
}, errorHandler);
}
readEntries();
});
}
const packageFile = (file, entry) => {
object = {
fileObject: file,
fullPath: entry ? entry.fullPath : '',
lastModified: file.lastModified,
lastModifiedDate: file.lastModifiedDate,
name: file.name,
size: file.size,
type: file.type,
webkitRelativePath: file.webkitRelativePath
}
return object;
}
const getFile = (entry) => {
return new Promise((resolve) => {
entry.file((file) => {
resolve(packageFile(file, entry));
})
})
}
const handleFilePromises = (promises, fileList) => {
return Promise.all(promises).then((files) => {
files.forEach((file) => {
fileList.push(file);
});
return fileList;
})
}
const getDataTransferFiles = (dataTransfer) => {
const dataTransferFiles = [];
const folderPromises = [];
const filePromises = [];
[].slice.call(dataTransfer.items).forEach((listItem) => {
if (typeof listItem.webkitGetAsEntry === 'function') {
const entry = listItem.webkitGetAsEntry();
if (entry) {
if (entry.isDirectory) {
folderPromises.push(traverseDirectory(entry));
} else {
filePromises.push(getFile(entry));
}
} 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);
flattenedEntries.forEach((fileEntry) => {
filePromises.push(getFile(fileEntry));
});
return handleFilePromises(filePromises, dataTransferFiles);
});
} else if (filePromises.length) {
return handleFilePromises(filePromises, dataTransferFiles);
}
return Promise.resolve(dataTransferFiles);
}
// Use this function by passing the drop or change event.
const getDroppedOrSelectedFiles = (event) => {
const dataTransfer = event.dataTransfer;
if (dataTransfer && dataTransfer.items) {
return getDataTransferFiles(dataTransfer)
.then((fileList) => {
return Promise.resolve(fileList);
})
}
const files = [];
const dragDropFileList = dataTransfer && dataTransfer.files;
const inputFieldFileList = event.target && event.target.files;
const fileList = dragDropFileList || inputFieldFileList || [];
for (let i = 0; i < fileList.length; i++) {
files.push(packageFile(fileList[i]));
}
return Promise.resolve(files);
}
@av01d
Copy link

av01d commented Jan 26, 2022

Usage example:

<!doctype html>
<html>
<head>
<title>File Extractor</title>
<script src="file-extractor.js"></script>
</head>
<body>

<div id="dropZone" style="border:1px dotted gray;padding:1em">
   Drop file(s) or folder(s) here
   <br>
   or
   <br><input id="fileInput" type="file" webkitdirectory directory multiple>
</div>

<script>

const dropZone = document.getElementById('dropZone');

dropZone.addEventListener('dragover', e=>{
	e.preventDefault();
	e.dataTransfer.dropEffect = 'copy';
});
dropZone.addEventListener('drop', e=>{
	e.stopPropagation();
	e.preventDefault();
	getDroppedOrSelectedFiles(e).then(files => {
		console.log(files);
	});
});

const fileInput = document.getElementById('fileInput');

fileInput.addEventListener('change', e=>{
	getDroppedOrSelectedFiles(e).then(files => {
		console.log(files);
	});
});

</script>
</body>
</html>

@jodont
Copy link

jodont commented Oct 28, 2022

Unfortunately, when you drag and drop a directory in Firefox, the mime-types of files are lost (they show as empty strings).
When you drop the files themselves, the mime-types are fine.
Is there any work-around?

@amanchopra95
Copy link
Author

Unfortunately, when you drag and drop a directory in Firefox, the mime-types of files are lost (they show as empty strings). When you drop the files themselves, the mime-types are fine. Is there any work-around?

Simple workaround is to read the file using FileReader and read the first 4 bytes as an arraybuffer and map it to mime type sample code

`const packageFile = (file, entry, hex) => {
object = {
fileObject: file,
fullPath: entry ? entry.fullPath : '',
lastModified: file.lastModified,
lastModifiedDate: file.lastModifiedDate,
name: file.name,
size: file.size,
type: file.type === "" ? getMimetype(hex) : file.type,
webkitRelativePath: file.webkitRelativePath
}
return object;
}

    const getFile = (entry) => {
        return new Promise((resolve) => {
            entry.file((file) => {
                let reader = new FileReader();
                reader.onload = () => {
                    console.log(reader);
                    const uint = new Uint8Array(reader.result);
                    let bytes = [];
                    uint.forEach((byte) => {
                        bytes.push(byte.toString(16));
                    })
                    const hex = bytes.join('').toUpperCase();
                    resolve(packageFile(file, entry, hex));
                }
                reader.readAsArrayBuffer(file.slice(0, 4));
            })
        })
    }

    const getMimetype = (signature) => {
        switch (signature) {
            case '89504E47':
                return 'image/png'
            case '47494638':
                return 'image/gif'
            case '25504446':
                return 'application/pdf'
            case 'FFD8FFDB':
            case 'FFD8FFE0':
                return 'image/jpeg'
            case '504B0304':
                return 'application/zip'
            default:
                return 'Unknown filetype'
        }
    }

`

Note: FileReader can be an expensive operation, maybe you can only run only for Firefox

@jodont
Copy link

jodont commented Oct 30, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment