| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- import { useLayoutEffect, useRef, useState, useCallback } from 'react';
- import { useLanguage } from '../contexts/LanguageContext';
- import { GROUP_ALLOWED_EXTENSIONS } from '../constants/fileSupport';
- import { motion, AnimatePresence } from 'framer-motion';
- import { FileUp, ShieldCheck, FileText, Image as ImageIcon } from 'lucide-react';
- interface NotebookGlobalDragDropProps {
- onFilesSelected: (files: FileList) => void;
- isAdmin: boolean;
- }
- let isNotebookDragDropEnabled = true;
- let notebookForceHideCallback: (() => void) | null = null;
- export const setNotebookDragDropEnabled = (enabled: boolean) => {
- isNotebookDragDropEnabled = enabled;
- if (!enabled && notebookForceHideCallback) {
- notebookForceHideCallback();
- }
- };
- export const NotebookGlobalDragDropOverlay: React.FC<NotebookGlobalDragDropProps> = ({ onFilesSelected, isAdmin }) => {
- const { t } = useLanguage();
- const overlayRef = useRef<HTMLDivElement>(null);
- const [isVisible, setIsVisible] = useState(false);
- const dragCounterRef = useRef(0);
- const isDragActiveRef = useRef(false);
- const hasFiles = useCallback((dt: DataTransfer | null) => {
- if (!dt) return false;
- const hasFileType = dt.types && dt.types.includes('Files');
- if (!hasFileType) return false;
- if (dt.items && dt.items.length > 0) {
- for (let i = 0; i < dt.items.length; i++) {
- if (dt.items[i].kind === 'file') return true;
- }
- return false;
- }
- return hasFileType;
- }, []);
- const handleDragEnter = useCallback((e: DragEvent) => {
- if (!isNotebookDragDropEnabled || !hasFiles(e.dataTransfer)) return;
- e.preventDefault();
- e.stopPropagation();
- dragCounterRef.current++;
- if (dragCounterRef.current === 1) {
- setIsVisible(true);
- isDragActiveRef.current = true;
- }
- }, [hasFiles]);
- const handleDragOver = useCallback((e: DragEvent) => {
- if (!isNotebookDragDropEnabled || !hasFiles(e.dataTransfer)) return;
- e.preventDefault();
- e.stopPropagation();
- e.dataTransfer!.dropEffect = 'copy';
- }, [hasFiles]);
- const handleDragLeave = useCallback((e: DragEvent) => {
- if (!isNotebookDragDropEnabled || !hasFiles(e.dataTransfer)) return;
- e.preventDefault();
- e.stopPropagation();
- dragCounterRef.current = Math.max(0, dragCounterRef.current - 1);
- if (dragCounterRef.current === 0 && isDragActiveRef.current) {
- setIsVisible(false);
- isDragActiveRef.current = false;
- }
- }, [hasFiles]);
- const handleDrop = useCallback((e: DragEvent) => {
- if (!isNotebookDragDropEnabled || !hasFiles(e.dataTransfer)) return;
- e.preventDefault();
- e.stopPropagation();
- dragCounterRef.current = 0;
- setIsVisible(false);
- isDragActiveRef.current = false;
- if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
- onFilesSelected(e.dataTransfer.files);
- }
- }, [hasFiles, onFilesSelected]);
- useLayoutEffect(() => {
- if (!isAdmin) return;
- dragCounterRef.current = 0;
- isDragActiveRef.current = false;
- notebookForceHideCallback = () => {
- dragCounterRef.current = 0;
- isDragActiveRef.current = false;
- setIsVisible(false);
- };
- document.addEventListener('dragenter', handleDragEnter);
- document.addEventListener('dragover', handleDragOver);
- document.addEventListener('dragleave', handleDragLeave);
- document.addEventListener('drop', handleDrop);
- return () => {
- document.removeEventListener('dragenter', handleDragEnter);
- document.removeEventListener('dragover', handleDragOver);
- document.removeEventListener('dragleave', handleDragLeave);
- document.removeEventListener('drop', handleDrop);
- notebookForceHideCallback = null;
- dragCounterRef.current = 0;
- isDragActiveRef.current = false;
- setIsVisible(false);
- };
- }, [isAdmin, handleDragEnter, handleDragOver, handleDragLeave, handleDrop]);
- if (!isAdmin || typeof window === 'undefined') return null;
- return (
- <AnimatePresence>
- {isVisible && (
- <motion.div
- initial={{ opacity: 0 }}
- animate={{ opacity: 1 }}
- exit={{ opacity: 0 }}
- className="fixed inset-0 bg-blue-600/10 backdrop-blur-md items-center justify-center z-[9999] pointer-events-none flex p-8"
- >
- <motion.div
- initial={{ scale: 0.9, y: 20 }}
- animate={{ scale: 1, y: 0 }}
- exit={{ scale: 0.9, y: 20 }}
- className="w-full max-w-2xl bg-white rounded-[2.5rem] p-12 text-center shadow-[0_32px_64px_-12px_rgba(0,0,0,0.14)] border border-white pointer-events-auto"
- >
- <div className="flex flex-col items-center justify-center gap-8">
- <div className="w-24 h-24 bg-blue-600 text-white rounded-3xl flex items-center justify-center shadow-xl shadow-blue-200 animate-bounce">
- <FileUp size={48} />
- </div>
- <div className="space-y-3">
- <h3 className="text-3xl font-black text-slate-900 tracking-tight">
- {t('dragDropUploadTitle')}
- </h3>
- <p className="text-lg text-slate-500 font-medium">
- Release to ingest files into this Group
- </p>
- </div>
- <div className="flex flex-wrap items-center justify-center gap-6 py-8 border-y border-slate-100 w-full">
- <div className="flex items-center gap-3 px-4 py-2 bg-slate-50 rounded-2xl text-sm font-bold text-slate-600 uppercase tracking-wider">
- <ShieldCheck size={20} className="text-emerald-500" />
- <span>Group Specific</span>
- </div>
- <div className="flex items-center gap-3 px-4 py-2 bg-slate-50 rounded-2xl text-sm font-bold text-slate-600 uppercase tracking-wider">
- <FileText size={20} className="text-blue-500" />
- <span>{GROUP_ALLOWED_EXTENSIONS.slice(0, 3).join(', ').toUpperCase()}...</span>
- </div>
- </div>
- <div className="text-slate-400 font-bold text-xs uppercase tracking-[0.3em]">
- Drop anywhere to begin
- </div>
- </div>
- </motion.div>
- </motion.div>
- )}
- </AnimatePresence>
- );
- };
|