'use client'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useDropzone } from 'react-dropzone'; import { FaUpload } from 'react-icons/fa'; import { apiClient } from '@/utils/api'; type AcceptMap = { [mime: string]: string[]; }; interface FullscreenDropOverlayProps { datasetName: string; // where to upload onComplete?: () => void; // called after successful upload accept?: AcceptMap; // optional override multiple?: boolean; // default true } export default function FullscreenDropOverlay({ datasetName, onComplete, accept, multiple = true, }: FullscreenDropOverlayProps) { const [visible, setVisible] = useState(false); const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const dragDepthRef = useRef(0); // drag-enter/leave tracking // Only show the overlay for real file drags (not text, images from page, etc) const isFileDrag = (e: DragEvent) => { const types = e?.dataTransfer?.types; return !!types && Array.from(types).includes('Files'); }; // Window-level drag listeners to toggle visibility useEffect(() => { const onDragEnter = (e: DragEvent) => { if (!isFileDrag(e)) return; dragDepthRef.current += 1; setVisible(true); e.preventDefault(); }; const onDragOver = (e: DragEvent) => { if (!isFileDrag(e)) return; // Must preventDefault to allow dropping in the browser e.preventDefault(); if (!visible) setVisible(true); }; const onDragLeave = (e: DragEvent) => { if (!isFileDrag(e)) return; dragDepthRef.current = Math.max(0, dragDepthRef.current - 1); if (dragDepthRef.current === 0 && !isUploading) { setVisible(false); } }; const onDrop = (e: DragEvent) => { if (!isFileDrag(e)) return; // Prevent browser from opening the file e.preventDefault(); dragDepthRef.current = 0; // We do NOT hide here; the dropzone onDrop will handle workflow visibility. }; window.addEventListener('dragenter', onDragEnter); window.addEventListener('dragover', onDragOver); window.addEventListener('dragleave', onDragLeave); window.addEventListener('drop', onDrop); return () => { window.removeEventListener('dragenter', onDragEnter); window.removeEventListener('dragover', onDragOver); window.removeEventListener('dragleave', onDragLeave); window.removeEventListener('drop', onDrop); }; }, [visible, isUploading]); const onDrop = useCallback( async (acceptedFiles: File[]) => { if (acceptedFiles.length === 0) { // no accepted files; hide overlay cleanly setVisible(false); return; } setIsUploading(true); setUploadProgress(0); const formData = new FormData(); acceptedFiles.forEach(file => formData.append('files', file)); formData.append('datasetName', datasetName || ''); try { await apiClient.post(`/api/datasets/upload`, formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: pe => { const percent = Math.round(((pe.loaded || 0) * 100) / (pe.total || pe.loaded || 1)); setUploadProgress(percent); }, timeout: 0, }); onComplete?.(); } catch (err) { console.error('Upload failed:', err); } finally { setIsUploading(false); setUploadProgress(0); setVisible(false); } }, [datasetName, onComplete], ); const dropAccept = useMemo( () => accept || { 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'], 'video/*': ['.mp4', '.avi', '.mov', '.mkv', '.wmv', '.m4v', '.flv'], 'text/*': ['.txt'], }, [accept], ); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: dropAccept, multiple, noClick: true, noKeyboard: true, // Prevent "folder opens" by browser if someone drags outside the overlay mid-drop: preventDropOnDocument: true, }); return (
{/* Fullscreen capture layer */} {/* Backdrop: keep it subtle so context remains visible */}
{/* Center drop target UI */}
{!isUploading ? ( <>

Drop files to upload

Destination: {datasetName || 'unknown'}

Images, videos, or .txt supported

) : ( <>

Uploading… {uploadProgress}%

)}
); }