import { useState, useCallback } from 'react'
export interface UseFileOperationsReturn {
// File inclusion states
includedFiles: string[]
includedFileNames: Map<string, string>
isDragOver: boolean
// File inclusion functions
addIncludedFiles: (files: string[]) => void
removeIncludedFile: (fileIdentifier: string) => void
setIncludedFileNames: React.Dispatch<React.SetStateAction<Map<string, string>>>
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<string[]>([])
const [includedFileNames, setIncludedFileNames] = useState<Map<string, string>>(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,
}
}