Commit 9201965e authored by tom's avatar tom

drag-and-drop folders

parent b1f8e17a
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es6",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
......
...@@ -41,7 +41,7 @@ const ContractVerificationFieldSources = ({ accept, multiple, title, className, ...@@ -41,7 +41,7 @@ const ContractVerificationFieldSources = ({ accept, multiple, title, className,
<Box display="grid" gridTemplateColumns={{ base: '1fr', lg: '1fr 1fr' }} columnGap={ 3 } rowGap={ 3 }> <Box display="grid" gridTemplateColumns={{ base: '1fr', lg: '1fr 1fr' }} columnGap={ 3 } rowGap={ 3 }>
{ files.map((file, index) => ( { files.map((file, index) => (
<FileSnippet <FileSnippet
key={ file.name + file.lastModified } key={ file.name + file.lastModified + index }
file={ file } file={ file }
maxW="initial" maxW="initial"
onRemove={ handleFileRemove } onRemove={ handleFileRemove }
......
...@@ -2,6 +2,8 @@ import { Box } from '@chakra-ui/react'; ...@@ -2,6 +2,8 @@ import { Box } from '@chakra-ui/react';
import type { DragEvent } from 'react'; import type { DragEvent } from 'react';
import React from 'react'; import React from 'react';
import { getAllFileEntries, convertFileEntryToFile } from './utils/files';
interface Props { interface Props {
onDrop: (files: Array<File>) => void; onDrop: (files: Array<File>) => void;
} }
...@@ -11,7 +13,9 @@ const DragAndDropArea = ({ onDrop }: Props) => { ...@@ -11,7 +13,9 @@ const DragAndDropArea = ({ onDrop }: Props) => {
const handleDrop = React.useCallback(async(event: DragEvent<HTMLDivElement>) => { const handleDrop = React.useCallback(async(event: DragEvent<HTMLDivElement>) => {
event.preventDefault(); event.preventDefault();
const files = Array.from(event.dataTransfer.files);
const fileEntries = await getAllFileEntries(event.dataTransfer.items);
const files = await Promise.all(fileEntries.map(convertFileEntryToFile));
onDrop(files); onDrop(files);
setIsDragOver(false); setIsDragOver(false);
......
// Function to get all files in drop directory
export async function getAllFileEntries(dataTransferItemList: DataTransferItemList): Promise<Array<FileSystemFileEntry>> {
const fileEntries: Array<FileSystemFileEntry> = [];
// Use BFS to traverse entire directory/file structure
const queue: Array<FileSystemEntry | FileSystemDirectoryEntry | null> = [];
// Unfortunately dataTransferItemList is not iterable i.e. no forEach
for (let i = 0; i < dataTransferItemList.length; i++) {
// Note webkitGetAsEntry a non-standard feature and may change
// Usage is necessary for handling directories
// + typescript types are kinda wrong - https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
const item = dataTransferItemList[i].webkitGetAsEntry();
queue.push(item);
}
while (queue.length > 0) {
const entry = queue.shift();
if (entry?.isFile) {
fileEntries.push(entry as FileSystemFileEntry);
} else if (entry?.isDirectory && 'createReader' in entry) {
queue.push(...await readAllDirectoryEntries(entry.createReader()));
}
}
return fileEntries;
}
// Get all the entries (files or sub-directories) in a directory
// by calling readEntries until it returns empty array
async function readAllDirectoryEntries(directoryReader: DirectoryReader) {
const entries: Array<FileSystemEntry> = [];
let readEntries = await readEntriesPromise(directoryReader);
while (readEntries && readEntries.length > 0) {
entries.push(...readEntries);
readEntries = await readEntriesPromise(directoryReader);
}
return entries;
}
// Wrap readEntries in a promise to make working with readEntries easier
// readEntries will return only some of the entries in a directory
// e.g. Chrome returns at most 100 entries at a time
async function readEntriesPromise(directoryReader: DirectoryReader): Promise<Array<FileSystemEntry> | undefined> {
try {
return await new Promise((resolve, reject) => {
directoryReader.readEntries(resolve, reject);
});
} catch (err) {}
}
export function convertFileEntryToFile(entry: FileSystemFileEntry): Promise<File> {
return new Promise((resolve) => {
entry.file(async(file: File) => {
// const newFile = new File([ file ], entry.fullPath, { lastModified: file.lastModified, type: file.type });
resolve(file);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment