/* eslint-disable no-undef */ import { useEffect, useRef, useState } from 'react'; const DRAGGING_ROOT_ID = 'dragging-root'; export const getContainer = () => document.querySelector(`#${DRAGGING_ROOT_ID}`); const handleDragOver = (e: DragEvent) => { if (!e.dataTransfer?.items || e.dataTransfer.items.length === 0) return; const isFile = e.dataTransfer.types.includes('Files'); if (isFile) { e.preventDefault(); } }; const processEntry = async (entry: FileSystemEntry): Promise => { return new Promise((resolve) => { if (entry.isFile) { (entry as FileSystemFileEntry).file((file) => { resolve([file]); }); } else if (entry.isDirectory) { const dirReader = (entry as FileSystemDirectoryEntry).createReader(); dirReader.readEntries(async (entries) => { const filesPromises = entries.map((element) => processEntry(element)); const fileArrays = await Promise.all(filesPromises); resolve(fileArrays.flat()); }); } else { resolve([]); } }); }; const getFileListFromDataTransferItems = async (items: DataTransferItem[]) => { // get filesList const filePromises: Promise[] = []; for (const item of items) { if (item.kind === 'file') { const entry = item.webkitGetAsEntry(); if (entry) { filePromises.push(processEntry(entry)); } else { const file = item.getAsFile(); if (file) filePromises.push( new Promise((resolve) => { resolve([file]); }), ); } } } const fileArrays = await Promise.all(filePromises); return fileArrays.flat(); }; export const useDragUpload = (onUploadFiles: (files: File[]) => Promise) => { const [isDragging, setIsDragging] = useState(false); // When a file is dragged to a different area, the 'dragleave' event may be triggered, // causing isDragging to be mistakenly set to false. // to fix this issue, use a counter to ensure the status change only when drag event left the browser window . const dragCounter = useRef(0); const handleDragEnter = (e: DragEvent) => { if (!e.dataTransfer?.items || e.dataTransfer.items.length === 0) return; const isFile = e.dataTransfer.types.includes('Files'); if (isFile) { dragCounter.current += 1; e.preventDefault(); setIsDragging(true); } }; const handleDragLeave = (e: DragEvent) => { if (!e.dataTransfer?.items || e.dataTransfer.items.length === 0) return; const isFile = e.dataTransfer.types.includes('Files'); if (isFile) { e.preventDefault(); // reset counter dragCounter.current -= 1; if (dragCounter.current === 0) { setIsDragging(false); } } }; const handleDrop = async (e: DragEvent) => { if (!e.dataTransfer?.items || e.dataTransfer.items.length === 0) return; const isFile = e.dataTransfer.types.includes('Files'); if (!isFile) return; e.preventDefault(); // reset counter dragCounter.current = 0; setIsDragging(false); const items = Array.from(e.dataTransfer?.items); const files = await getFileListFromDataTransferItems(items); if (files.length === 0) return; // upload files onUploadFiles(files); }; const handlePaste = async (event: ClipboardEvent) => { // get files from clipboard if (!event.clipboardData) return; const items = Array.from(event.clipboardData?.items); const files = await getFileListFromDataTransferItems(items); if (files.length === 0) return; onUploadFiles(files); }; useEffect(() => { if (getContainer()) return; const root = document.createElement('div'); root.id = DRAGGING_ROOT_ID; document.body.append(root); return () => { root.remove(); }; }, []); useEffect(() => { window.addEventListener('dragenter', handleDragEnter); window.addEventListener('dragover', handleDragOver); window.addEventListener('dragleave', handleDragLeave); window.addEventListener('drop', handleDrop); window.addEventListener('paste', handlePaste); return () => { window.removeEventListener('dragenter', handleDragEnter); window.removeEventListener('dragover', handleDragOver); window.removeEventListener('dragleave', handleDragLeave); window.removeEventListener('drop', handleDrop); window.removeEventListener('paste', handlePaste); }; }, [handleDragEnter, handleDragOver, handleDragLeave, handleDrop, handlePaste]); return isDragging; };