import { useLayoutEffect, useRef, useState, useCallback } from 'react'; import { useLanguage } from '../contexts/LanguageContext'; import { GROUP_ALLOWED_EXTENSIONS } from '../constants/fileSupport'; 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 = ({ onFilesSelected, isAdmin }) => { const { t } = useLanguage(); const overlayRef = useRef(null); const [isVisible, setIsVisible] = useState(false); const dragCounterRef = useRef(0); const isDragActiveRef = useRef(false); const hasFiles = useCallback((dt: DataTransfer | null) => { if (!dt) return false; // より厳格なチェック: typesにFilesが含まれることを確認 const hasFileType = dt.types && dt.types.includes('Files'); if (!hasFileType) return false; // itemsが存在する場合、実際にファイルがあるかチェック if (dt.items && dt.items.length > 0) { // 少なくとも1つのファイルアイテムがあることを確認 for (let i = 0; i < dt.items.length; i++) { if (dt.items[i].kind === 'file') { return true; } } return false; } // itemsがない場合はtypesのみで判断(後方互換性) return hasFileType; }, []); const handleDragEnter = useCallback((e: DragEvent) => { // ドラッグドロップ機能が無効な場合はイベントを無視 if (!isNotebookDragDropEnabled) return; // データ転送にファイルが含まれている場合のみ処理 if (!hasFiles(e.dataTransfer)) return; e.preventDefault(); e.stopPropagation(); dragCounterRef.current++; if (dragCounterRef.current === 1) { // 直ちにオーバーレイを表示し、誤作動は強制非表示メカニズムに依存 setIsVisible(true); if (overlayRef.current) { overlayRef.current.style.opacity = '1'; overlayRef.current.style.visibility = 'visible'; } isDragActiveRef.current = true; } }, [hasFiles]); const handleDragOver = useCallback((e: DragEvent) => { // ドラッグドロップ機能が無効な場合はイベントを無視 if (!isNotebookDragDropEnabled) return; // データ転送にファイルが含まれている場合のみ処理 if (!hasFiles(e.dataTransfer)) return; e.preventDefault(); e.stopPropagation(); e.dataTransfer!.dropEffect = 'copy'; }, [hasFiles]); const handleDragLeave = useCallback((e: DragEvent) => { // ドラッグドロップ機能が無効な場合はイベントを無視 if (!isNotebookDragDropEnabled) return; // データ転送にファイルが含まれている場合のみ処理 if (!hasFiles(e.dataTransfer)) return; e.preventDefault(); e.stopPropagation(); dragCounterRef.current = Math.max(0, dragCounterRef.current - 1); if (dragCounterRef.current === 0 && isDragActiveRef.current) { // 全域ドラッグアップロードオーバーレイを非表示にする if (overlayRef.current) { overlayRef.current.style.opacity = '0'; overlayRef.current.style.visibility = 'hidden'; } setIsVisible(false); // ステートを介して非表示を制御 isDragActiveRef.current = false; } }, [hasFiles]); const handleDrop = useCallback((e: DragEvent) => { // ドラッグドロップ機能が無効な場合はイベントを無視 if (!isNotebookDragDropEnabled) return; // データ転送にファイルが含まれている場合のみ処理 if (!hasFiles(e.dataTransfer)) return; e.preventDefault(); e.stopPropagation(); dragCounterRef.current = 0; // 全域ドラッグアップロードオーバーレイを非表示にする if (overlayRef.current) { overlayRef.current.style.opacity = '0'; overlayRef.current.style.visibility = 'hidden'; } setIsVisible(false); isDragActiveRef.current = false; // ファイルを処理 if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) { onFilesSelected(e.dataTransfer.files); } }, [hasFiles, onFilesSelected]); useLayoutEffect(() => { if (!isAdmin) return; // 初期化時にdragCounterとisDragActiveが初期値であることを確認 dragCounterRef.current = 0; isDragActiveRef.current = false; // 強制非表示コールバックを登録 notebookForceHideCallback = () => { dragCounterRef.current = 0; isDragActiveRef.current = false; setIsVisible(false); if (overlayRef.current) { overlayRef.current.style.opacity = '0'; overlayRef.current.style.visibility = 'hidden'; } }; // 全域イベントリスナーを追加 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; if (overlayRef.current) { overlayRef.current.style.opacity = '0'; overlayRef.current.style.visibility = 'hidden'; } setIsVisible(false); }; }, [isAdmin, handleDragEnter, handleDragOver, handleDragLeave, handleDrop]); // ランタイムチェックを追加し、適切な場合のみレンダリングすることを保証 if (!isAdmin || typeof window === 'undefined') { return null; } // isVisible が true の場合のみコンポーネントの内容をレンダリング if (!isVisible) { return null; } return (

{t('dragDropUploadTitle')}

{t('dragDropUploadDesc')}

{t('supportedFormats')}
{GROUP_ALLOWED_EXTENSIONS.slice(0, 10).join(', ').toUpperCase()}...
); };