import { useState, useCallback } from 'react' export interface UseFileOperationsReturn { // File inclusion states includedFiles: string[] includedFileNames: Map isDragOver: boolean // File inclusion functions addIncludedFiles: (files: string[]) => void removeIncludedFile: (fileIdentifier: string) => void setIncludedFileNames: React.Dispatch>> getDisplayName: (fileIdentifier: string) => string // Drag and drop handlers handleDragOver: (e: React.DragEvent) => void handleDragEnter: (e: React.DragEvent) => void handleDragLeave: (e: React.DragEvent) => void handleDrop: (e: React.DragEvent) => void } export function useFileOperations(): UseFileOperationsReturn { // File inclusion states const [includedFiles, setIncludedFiles] = useState([]) const [includedFileNames, setIncludedFileNames] = useState>(new Map()) const [isDragOver, setIsDragOver] = useState(false) // Helper function to get display name for included files const getDisplayName = useCallback((fileIdentifier: string): string => { // If we have a stored name for this identifier, use it const storedName = includedFileNames.get(fileIdentifier) if (storedName) return storedName // If it looks like a UUID, show "Loading..." while we fetch the name if (fileIdentifier.match(/^[a-f0-9-]{36}$/)) { return "Loading..." } // Otherwise it's probably a file name, return as-is return fileIdentifier }, [includedFileNames]) // File inclusion functions const addIncludedFiles = useCallback((files: string[]) => { setIncludedFiles(prev => { const uniqueFiles = files.filter(file => !prev.includes(file)) return [...prev, ...uniqueFiles] }) }, []) const removeIncludedFile = useCallback((fileIdentifier: string) => { setIncludedFiles(prev => prev.filter(file => file !== fileIdentifier)) setIncludedFileNames(prev => { const newMap = new Map(prev) newMap.delete(fileIdentifier) return newMap }) }, []) // Drag and drop handlers const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault() e.stopPropagation() if (!isDragOver) { setIsDragOver(true) } }, [isDragOver]) const handleDragEnter = useCallback((e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setIsDragOver(true) }, []) const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault() e.stopPropagation() // Only set isDragOver to false if we're leaving the drop zone entirely if (!e.currentTarget.contains(e.relatedTarget as Node)) { setIsDragOver(false) } }, []) const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault() setIsDragOver(false) const files = Array.from(e.dataTransfer.files) const items = Array.from(e.dataTransfer.items) console.log('Dropped files:', files) console.log('Dropped items:', items) // Handle files if available if (files.length > 0) { const newFiles = files.map(file => { // Use the full path if available (webkitRelativePath), otherwise use name const fileName = file.webkitRelativePath || file.name console.log('Processing file:', fileName, file) return fileName }) console.log('New files to add:', newFiles) addIncludedFiles(newFiles) } else { // Handle items when files array is empty const newFiles: string[] = [] // Check if we have JSON data (prioritize over text/plain) const hasJsonData = items.some(item => item.kind === 'string' && item.type === 'application/json') items.forEach(item => { console.log('Processing item:', item) if (item.kind === 'file') { const entry = item.webkitGetAsEntry() if (entry) { console.log('Entry:', entry.name, entry.isDirectory ? 'directory' : 'file') newFiles.push(entry.name) } else { // Fallback: try to get file directly const file = item.getAsFile() if (file) { console.log('Got file from item:', file.name) newFiles.push(file.name) } } } else if (item.kind === 'string' && (item.type === 'application/json' || !hasJsonData)) { // Handle JSON data from file explorer drag (async) item.getAsString((text) => { try { const fileData = JSON.parse(text) console.log('Got file data from JSON:', fileData) if (fileData.id && fileData.type === 'file') { // Use file ID when available (dragged from internal file explorer) setIncludedFiles(prev => { if (!prev.includes(fileData.id)) { // Store the name for this ID setIncludedFileNames(prevNames => new Map(prevNames).set(fileData.id, fileData.name)) const updated = [...prev, fileData.id] console.log('Updated included files from JSON (ID):', updated) return updated } return prev }) } else if (fileData.name) { // Fallback to name if no ID setIncludedFiles(prev => { if (!prev.includes(fileData.name)) { const updated = [...prev, fileData.name] console.log('Updated included files from JSON (name):', updated) return updated } return prev }) } } catch (e) { console.error('Failed to parse JSON drag data:', e) } }) } }) // Only process synchronous files here if (newFiles.length > 0) { addIncludedFiles(newFiles) } } }, [addIncludedFiles]) return { // File inclusion states includedFiles, includedFileNames, isDragOver, // File inclusion functions addIncludedFiles, removeIncludedFile, setIncludedFileNames, getDisplayName, // Drag and drop handlers handleDragOver, handleDragEnter, handleDragLeave, handleDrop, } }