Spaces:
Running
on
A100
Running
on
A100
| import { useCallback, useState, DragEvent, useRef } from 'react'; | |
| interface UseDragAndDropOptions { | |
| onFileDropped: (file: File) => void; | |
| acceptedTypes?: string[]; | |
| } | |
| interface UseDragAndDropReturn { | |
| isDragActive: boolean; | |
| dragProps: { | |
| onDrop: (event: DragEvent<HTMLDivElement>) => void; | |
| onDragOver: (event: DragEvent<HTMLDivElement>) => void; | |
| onDragEnter: (event: DragEvent<HTMLDivElement>) => void; | |
| onDragLeave: (event: DragEvent<HTMLDivElement>) => void; | |
| }; | |
| } | |
| const isValidFileType = (file: File, acceptedTypes?: string[]): boolean => { | |
| if (!acceptedTypes || acceptedTypes.length === 0) return true; | |
| return acceptedTypes.some(type => { | |
| if (type.endsWith('/*')) { | |
| // Handle MIME type categories like 'video/*' or 'audio/*' | |
| const category = type.slice(0, -2); | |
| return file.type.startsWith(category + '/'); | |
| } else { | |
| // Handle exact MIME types | |
| return file.type === type; | |
| } | |
| }); | |
| }; | |
| export const useDragAndDrop = ({ | |
| onFileDropped, | |
| acceptedTypes = ['video/*', 'audio/*'] | |
| }: UseDragAndDropOptions): UseDragAndDropReturn => { | |
| const [isDragActive, setIsDragActive] = useState(false); | |
| const dragCounter = useRef(0); | |
| const dragLeaveTimeout = useRef<number | null>(null); | |
| const handleDragEnter = useCallback((event: DragEvent<HTMLDivElement>) => { | |
| event.preventDefault(); | |
| event.stopPropagation(); | |
| // Clear any pending drag leave timeout | |
| if (dragLeaveTimeout.current) { | |
| clearTimeout(dragLeaveTimeout.current); | |
| dragLeaveTimeout.current = null; | |
| } | |
| dragCounter.current += 1; | |
| if (event.dataTransfer?.items && event.dataTransfer.items.length > 0) { | |
| setIsDragActive(true); | |
| } | |
| }, []); | |
| const handleDragLeave = useCallback((event: DragEvent<HTMLDivElement>) => { | |
| event.preventDefault(); | |
| event.stopPropagation(); | |
| dragCounter.current -= 1; | |
| // Use a small timeout to prevent flickering when moving between child elements | |
| dragLeaveTimeout.current = window.setTimeout(() => { | |
| if (dragCounter.current === 0) { | |
| setIsDragActive(false); | |
| } | |
| }, 10); | |
| }, []); | |
| const handleDragOver = useCallback((event: DragEvent<HTMLDivElement>) => { | |
| event.preventDefault(); | |
| event.stopPropagation(); | |
| // Set the dropEffect to indicate this is a copy operation | |
| if (event.dataTransfer) { | |
| event.dataTransfer.dropEffect = 'copy'; | |
| } | |
| }, []); | |
| const handleDrop = useCallback((event: DragEvent<HTMLDivElement>) => { | |
| event.preventDefault(); | |
| event.stopPropagation(); | |
| // Clear timeout and reset state | |
| if (dragLeaveTimeout.current) { | |
| clearTimeout(dragLeaveTimeout.current); | |
| dragLeaveTimeout.current = null; | |
| } | |
| setIsDragActive(false); | |
| dragCounter.current = 0; | |
| const files = Array.from(event.dataTransfer?.files || []); | |
| if (files.length > 0) { | |
| const validFile = files.find(file => isValidFileType(file, acceptedTypes)); | |
| if (validFile) { | |
| onFileDropped(validFile); | |
| } else { | |
| console.warn('Dropped file is not a supported type:', files[0]?.type); | |
| // You could also call an onError callback here if needed | |
| } | |
| } | |
| }, [onFileDropped, acceptedTypes]); | |
| return { | |
| isDragActive, | |
| dragProps: { | |
| onDrop: handleDrop, | |
| onDragOver: handleDragOver, | |
| onDragEnter: handleDragEnter, | |
| onDragLeave: handleDragLeave, | |
| }, | |
| }; | |
| }; | |