2025-07-16 17:53:50 +01:00
|
|
|
import { useState, useCallback } from 'react';
|
|
|
|
import { fileStorage } from '../services/fileStorage';
|
2025-08-14 18:07:18 +01:00
|
|
|
import { FileWithUrl, FileMetadata } from '../types/file';
|
2025-08-08 15:15:09 +01:00
|
|
|
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
|
2025-07-16 17:53:50 +01:00
|
|
|
|
|
|
|
export const useFileManager = () => {
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
2025-08-14 18:07:18 +01:00
|
|
|
const convertToFile = useCallback(async (fileMetadata: FileMetadata): Promise<File> => {
|
|
|
|
// Always use ID - no fallback to names to prevent identity drift
|
|
|
|
if (!fileMetadata.id) {
|
|
|
|
throw new Error('File ID is required - cannot convert file without stable ID');
|
2025-07-16 17:53:50 +01:00
|
|
|
}
|
2025-08-14 18:07:18 +01:00
|
|
|
const storedFile = await fileStorage.getFile(fileMetadata.id);
|
2025-07-16 17:53:50 +01:00
|
|
|
if (storedFile) {
|
|
|
|
const file = new File([storedFile.data], storedFile.name, {
|
|
|
|
type: storedFile.type,
|
|
|
|
lastModified: storedFile.lastModified
|
|
|
|
});
|
2025-08-14 18:07:18 +01:00
|
|
|
// NO FILE MUTATION - Return clean File, let FileContext manage ID
|
2025-07-16 17:53:50 +01:00
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error('File not found in storage');
|
|
|
|
}, []);
|
|
|
|
|
2025-08-14 18:07:18 +01:00
|
|
|
const loadRecentFiles = useCallback(async (): Promise<FileMetadata[]> => {
|
2025-07-16 17:53:50 +01:00
|
|
|
setLoading(true);
|
|
|
|
try {
|
2025-08-14 18:07:18 +01:00
|
|
|
// Get metadata only (no file data) for performance
|
|
|
|
const storedFileMetadata = await fileStorage.getAllFileMetadata();
|
|
|
|
const sortedFiles = storedFileMetadata.sort((a, b) => (b.lastModified || 0) - (a.lastModified || 0));
|
|
|
|
|
|
|
|
// Convert StoredFile metadata to FileMetadata format
|
|
|
|
return sortedFiles.map(stored => ({
|
|
|
|
id: stored.id, // UUID from FileContext
|
|
|
|
name: stored.name,
|
|
|
|
type: stored.type,
|
|
|
|
size: stored.size,
|
|
|
|
lastModified: stored.lastModified,
|
|
|
|
thumbnail: stored.thumbnail,
|
|
|
|
storedInIndexedDB: true
|
|
|
|
}));
|
2025-07-16 17:53:50 +01:00
|
|
|
} catch (error) {
|
|
|
|
console.error('Failed to load recent files:', error);
|
|
|
|
return [];
|
|
|
|
} finally {
|
|
|
|
setLoading(false);
|
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
|
2025-08-14 18:07:18 +01:00
|
|
|
const handleRemoveFile = useCallback(async (index: number, files: FileMetadata[], setFiles: (files: FileMetadata[]) => void) => {
|
2025-07-16 17:53:50 +01:00
|
|
|
const file = files[index];
|
2025-08-14 18:07:18 +01:00
|
|
|
if (!file.id) {
|
|
|
|
throw new Error('File ID is required for removal');
|
|
|
|
}
|
2025-07-16 17:53:50 +01:00
|
|
|
try {
|
2025-08-14 18:07:18 +01:00
|
|
|
await fileStorage.deleteFile(file.id);
|
2025-07-16 17:53:50 +01:00
|
|
|
setFiles(files.filter((_, i) => i !== index));
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Failed to remove file:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
|
2025-08-14 18:07:18 +01:00
|
|
|
const storeFile = useCallback(async (file: File, fileId: string) => {
|
2025-07-16 17:53:50 +01:00
|
|
|
try {
|
2025-08-08 15:15:09 +01:00
|
|
|
// Generate thumbnail for the file
|
|
|
|
const thumbnail = await generateThumbnailForFile(file);
|
2025-08-11 16:40:38 +01:00
|
|
|
|
2025-08-14 18:07:18 +01:00
|
|
|
// Store file with provided UUID from FileContext
|
|
|
|
const storedFile = await fileStorage.storeFile(file, fileId, thumbnail);
|
2025-08-11 16:40:38 +01:00
|
|
|
|
2025-08-14 18:07:18 +01:00
|
|
|
// NO FILE MUTATION - Return StoredFile, FileContext manages mapping
|
2025-07-16 17:53:50 +01:00
|
|
|
return storedFile;
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Failed to store file:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const createFileSelectionHandlers = useCallback((
|
|
|
|
selectedFiles: string[],
|
|
|
|
setSelectedFiles: (files: string[]) => void
|
|
|
|
) => {
|
|
|
|
const toggleSelection = (fileId: string) => {
|
|
|
|
setSelectedFiles(
|
|
|
|
selectedFiles.includes(fileId)
|
|
|
|
? selectedFiles.filter(id => id !== fileId)
|
|
|
|
: [...selectedFiles, fileId]
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const clearSelection = () => {
|
|
|
|
setSelectedFiles([]);
|
|
|
|
};
|
|
|
|
|
2025-08-14 18:07:18 +01:00
|
|
|
const selectMultipleFiles = async (files: FileMetadata[], onFilesSelect: (files: File[]) => void) => {
|
2025-07-16 17:53:50 +01:00
|
|
|
if (selectedFiles.length === 0) return;
|
|
|
|
|
|
|
|
try {
|
2025-08-14 18:07:18 +01:00
|
|
|
// Filter by UUID and convert to File objects
|
|
|
|
const selectedFileObjects = files.filter(f => selectedFiles.includes(f.id));
|
2025-07-16 17:53:50 +01:00
|
|
|
const filePromises = selectedFileObjects.map(convertToFile);
|
|
|
|
const convertedFiles = await Promise.all(filePromises);
|
2025-08-14 18:07:18 +01:00
|
|
|
onFilesSelect(convertedFiles); // FileContext will assign new UUIDs
|
2025-07-16 17:53:50 +01:00
|
|
|
clearSelection();
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Failed to load selected files:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
toggleSelection,
|
|
|
|
clearSelection,
|
|
|
|
selectMultipleFiles
|
|
|
|
};
|
|
|
|
}, [convertToFile]);
|
|
|
|
|
2025-08-08 15:15:09 +01:00
|
|
|
const touchFile = useCallback(async (id: string) => {
|
|
|
|
try {
|
|
|
|
await fileStorage.touchFile(id);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Failed to touch file:', error);
|
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
|
2025-07-16 17:53:50 +01:00
|
|
|
return {
|
|
|
|
loading,
|
|
|
|
convertToFile,
|
|
|
|
loadRecentFiles,
|
|
|
|
handleRemoveFile,
|
|
|
|
storeFile,
|
2025-08-08 15:15:09 +01:00
|
|
|
touchFile,
|
2025-07-16 17:53:50 +01:00
|
|
|
createFileSelectionHandlers
|
|
|
|
};
|
2025-08-11 16:40:38 +01:00
|
|
|
};
|