import { DragEventHandler, MutableRefObject, ReactNode, useRef } from "react"; /** * TODO: TS complains that: * Type 'FileList' can only be iterated through when using the '--downlevelIteration' flag or with a '--target' of 'es2015' or higher. * So as a quick fix aI use a for loop to convert FileList to File[] * @param files * @returns */ function fileListToArray(files: FileList) { const ar = []; for (let i = 0, l = files.length; i < l; i++) { ar.push(files[i]); } return ar; } interface FileUploadInputProps { onUpload: (files: File[]) => unknown; children: ReactNode | ReactNode[]; } export function FileUploadInput({ children, onUpload }: FileUploadInputProps) { const inputRef = useRef(null); const _onUpload = () => { if (inputRef.current?.files) { onUpload(fileListToArray(inputRef.current.files)); } }; return ( ); } interface DropZoneProps { onUpload: (files: File[]) => unknown; children: ReactNode | ReactNode[]; height?: string; border?: string; borderActiveColor?: string; } //TODO implement using tailwind export function DropZone({ onUpload }: DropZoneProps) { const dropZoneProps = useDropZone({ onUpload }); return (
); } function _onDragEnter(el: any) { let cnt = el.__dragOver_cnt__ || 0; el.__dragOver_cnt__ = cnt + 1; return !cnt; // true if first drag o ver false if dragover already recorded } function _onDragLeave(el: any) { let cnt = el.__dragOver_cnt__; if (!cnt) return false; el.__dragOver_cnt__ = cnt - 1; return cnt === 1; // true if leave false if not } function _onDrop(el: any) { delete el.__dragOver_cnt__; } export interface IDropZoneOpts { onUpload: (files: File[]) => unknown; dragOverClass?: string; dropEffect?: "none" | "copy" | "link" | "move"; } export interface IDropZoneProps { onDrop: DragEventHandler; onDragOver: DragEventHandler; onDragEnter: DragEventHandler; onDragLeave: DragEventHandler; ref: MutableRefObject; } export function useDropZone({ onUpload, dragOverClass = "is-drag-over-on", dropEffect = "copy", }: IDropZoneOpts): IDropZoneProps { const ref = useRef(null); const onDrop = (ev: React.DragEvent) => { ev.preventDefault(); _onDrop(ref.current); ref.current?.classList.remove(dragOverClass); const items = ev.dataTransfer.items; if (items) { const promises: Promise[] = []; const traverseFileTree = (item: any, path: string = ""): Promise => { return new Promise((resolve) => { if (item.isFile) { item.file((file: File) => { Object.defineProperty(file, "webkitRelativePath", { value: path + file.name }); resolve([file]); }); } else if (item.isDirectory) { const dirReader = item.createReader(); const entries: Promise[] = []; const readEntries = () => { dirReader.readEntries((results: any[]) => { if (!results.length) { Promise.all(entries).then((filesArrays) => resolve(filesArrays.flat())); } else { for (const entry of results) { entries.push(traverseFileTree(entry, path + item.name + "/")); } readEntries(); } }); }; readEntries(); } }); }; for (let i = 0; i < items.length; i++) { const entry = items[i].webkitGetAsEntry(); if (entry) { promises.push(traverseFileTree(entry)); } } Promise.all(promises).then((filesArrays) => { const allFiles = filesArrays.flat(); if (allFiles.length) { onUpload(allFiles); } }); } }; const onDragOver = (ev: React.DragEvent) => { ev.preventDefault(); ev.dataTransfer.dropEffect = dropEffect; }; const onDragEnter = () => { if (_onDragEnter(ref.current)) { ref.current?.classList.add(dragOverClass); } }; const onDragLeave = () => { if (_onDragLeave(ref.current)) { ref.current?.classList.remove(dragOverClass); } }; return { onDrop, onDragOver, onDragEnter, onDragLeave, ref, }; }