import { useCallback, useRef, useState, type DragEvent } from 'react'; import { getDndApi } from '../shared/dndApi'; const AUDIO_EXTENSIONS = new Set(['.mp3', '.wav', '.ogg', '.m4a', '.aac']); const PREVIEW_EXTENSIONS = new Set([ '.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp', '.mp4', '.webm', '.mov', ]); function fileExtension(filePath: string): string { const dot = filePath.lastIndexOf('.'); return dot >= 0 ? filePath.slice(dot).toLowerCase() : ''; } export function getDroppedFilePaths(e: DragEvent): string[] { const files = e.dataTransfer?.files; if (!files?.length) return []; const getPathForFile = getDndApi().getPathForFile; return Array.from(files) .map((file) => getPathForFile(file)) .filter((path) => path.length > 0); } export function filterAudioFilePaths(paths: string[]): string[] { return paths.filter((path) => AUDIO_EXTENSIONS.has(fileExtension(path))); } export function pickFirstPreviewFilePath(paths: string[]): string | null { for (const path of paths) { if (PREVIEW_EXTENSIONS.has(fileExtension(path))) return path; } return null; } function dragHasFiles(e: DragEvent): boolean { return Array.from(e.dataTransfer?.types ?? []).includes('Files'); } type UseFileDropZoneOptions = { disabled?: boolean; onDropPaths: (paths: string[]) => void; filterPaths?: (paths: string[]) => string[]; }; export function useFileDropZone({ disabled = false, onDropPaths, filterPaths, }: UseFileDropZoneOptions) { const [dragOver, setDragOver] = useState(false); const depthRef = useRef(0); const onDragEnter = useCallback( (e: DragEvent) => { if (disabled || !dragHasFiles(e)) return; e.preventDefault(); e.stopPropagation(); depthRef.current += 1; setDragOver(true); }, [disabled], ); const onDragLeave = useCallback( (e: DragEvent) => { if (disabled) return; e.preventDefault(); e.stopPropagation(); depthRef.current = Math.max(0, depthRef.current - 1); if (depthRef.current === 0) setDragOver(false); }, [disabled], ); const onDragOver = useCallback( (e: DragEvent) => { if (disabled || !dragHasFiles(e)) return; e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = 'copy'; }, [disabled], ); const onDrop = useCallback( (e: DragEvent) => { if (disabled || !dragHasFiles(e)) return; e.preventDefault(); e.stopPropagation(); depthRef.current = 0; setDragOver(false); const raw = getDroppedFilePaths(e); const paths = filterPaths ? filterPaths(raw) : raw; if (paths.length === 0) return; onDropPaths(paths); }, [disabled, filterPaths, onDropPaths], ); return { dragOver, onDragEnter, onDragLeave, onDragOver, onDrop }; }