2025-07-16 17:53:50 +01:00
|
|
|
import { useState, useCallback } from 'react';
|
2025-08-21 17:30:26 +01:00
|
|
|
import { useIndexedDB } from '../contexts/IndexedDBContext';
|
2025-09-16 15:08:11 +01:00
|
|
|
import { fileStorage } from '../services/fileStorage';
|
|
|
|
import { StirlingFileStub, StirlingFile } from '../types/fileContext';
|
2025-09-05 11:33:03 +01:00
|
|
|
import { FileId } from '../types/fileContext';
|
2025-07-16 17:53:50 +01:00
|
|
|
|
|
|
|
export const useFileManager = () => {
|
|
|
|
const [loading, setLoading] = useState(false);
|
2025-08-21 17:30:26 +01:00
|
|
|
const indexedDB = useIndexedDB();
|
2025-07-16 17:53:50 +01:00
|
|
|
|
2025-09-16 15:08:11 +01:00
|
|
|
const convertToFile = useCallback(async (fileStub: StirlingFileStub): Promise<File> => {
|
2025-08-21 17:30:26 +01:00
|
|
|
if (!indexedDB) {
|
|
|
|
throw new Error('IndexedDB context not available');
|
|
|
|
}
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
// Regular file loading
|
2025-09-16 15:08:11 +01:00
|
|
|
if (fileStub.id) {
|
|
|
|
const file = await indexedDB.loadFile(fileStub.id);
|
2025-08-21 17:30:26 +01:00
|
|
|
if (file) {
|
|
|
|
return file;
|
|
|
|
}
|
2025-07-16 17:53:50 +01:00
|
|
|
}
|
2025-09-16 15:08:11 +01:00
|
|
|
throw new Error(`File not found in storage: ${fileStub.name} (ID: ${fileStub.id})`);
|
2025-08-21 17:30:26 +01:00
|
|
|
}, [indexedDB]);
|
2025-07-16 17:53:50 +01:00
|
|
|
|
2025-09-16 15:08:11 +01:00
|
|
|
const loadRecentFiles = useCallback(async (): Promise<StirlingFileStub[]> => {
|
2025-07-16 17:53:50 +01:00
|
|
|
setLoading(true);
|
|
|
|
try {
|
2025-08-21 17:30:26 +01:00
|
|
|
if (!indexedDB) {
|
|
|
|
return [];
|
|
|
|
}
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-09-16 15:08:11 +01:00
|
|
|
// Load only leaf files metadata (processed files that haven't been used as input for other tools)
|
|
|
|
const stirlingFileStubs = await fileStorage.getLeafStirlingFileStubs();
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
// For now, only regular files - drafts will be handled separately in the future
|
2025-09-16 15:08:11 +01:00
|
|
|
const sortedFiles = stirlingFileStubs.sort((a, b) => (b.lastModified || 0) - (a.lastModified || 0));
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-08-21 17:30:26 +01:00
|
|
|
return sortedFiles;
|
2025-07-16 17:53:50 +01:00
|
|
|
} catch (error) {
|
|
|
|
console.error('Failed to load recent files:', error);
|
|
|
|
return [];
|
|
|
|
} finally {
|
|
|
|
setLoading(false);
|
|
|
|
}
|
2025-08-21 17:30:26 +01:00
|
|
|
}, [indexedDB]);
|
2025-07-16 17:53:50 +01:00
|
|
|
|
2025-09-16 15:08:11 +01:00
|
|
|
const handleRemoveFile = useCallback(async (index: number, files: StirlingFileStub[], setFiles: (files: StirlingFileStub[]) => void) => {
|
2025-07-16 17:53:50 +01:00
|
|
|
const file = files[index];
|
2025-08-21 17:30:26 +01:00
|
|
|
if (!file.id) {
|
|
|
|
throw new Error('File ID is required for removal');
|
|
|
|
}
|
|
|
|
if (!indexedDB) {
|
|
|
|
throw new Error('IndexedDB context not available');
|
|
|
|
}
|
2025-07-16 17:53:50 +01:00
|
|
|
try {
|
2025-08-21 17:30:26 +01:00
|
|
|
await indexedDB.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-21 17:30:26 +01:00
|
|
|
}, [indexedDB]);
|
2025-07-16 17:53:50 +01:00
|
|
|
|
2025-08-28 10:56:07 +01:00
|
|
|
const storeFile = useCallback(async (file: File, fileId: FileId) => {
|
2025-08-21 17:30:26 +01:00
|
|
|
if (!indexedDB) {
|
|
|
|
throw new Error('IndexedDB context not available');
|
|
|
|
}
|
2025-07-16 17:53:50 +01:00
|
|
|
try {
|
2025-08-21 17:30:26 +01:00
|
|
|
// Store file with provided UUID from FileContext (thumbnail generated internally)
|
|
|
|
const metadata = await indexedDB.saveFile(file, fileId);
|
|
|
|
|
2025-09-16 15:08:11 +01:00
|
|
|
// Convert file to ArrayBuffer for storage compatibility
|
2025-08-21 17:30:26 +01:00
|
|
|
const arrayBuffer = await file.arrayBuffer();
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-09-16 15:08:11 +01:00
|
|
|
// This method is deprecated - use FileStorage directly instead
|
2025-08-21 17:30:26 +01:00
|
|
|
return {
|
|
|
|
id: fileId,
|
|
|
|
name: file.name,
|
|
|
|
type: file.type,
|
|
|
|
size: file.size,
|
|
|
|
lastModified: file.lastModified,
|
|
|
|
data: arrayBuffer,
|
2025-09-16 15:08:11 +01:00
|
|
|
thumbnail: metadata.thumbnailUrl
|
2025-08-21 17:30:26 +01:00
|
|
|
};
|
2025-07-16 17:53:50 +01:00
|
|
|
} catch (error) {
|
|
|
|
console.error('Failed to store file:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
2025-08-21 17:30:26 +01:00
|
|
|
}, [indexedDB]);
|
2025-07-16 17:53:50 +01:00
|
|
|
|
|
|
|
const createFileSelectionHandlers = useCallback((
|
2025-08-28 10:56:07 +01:00
|
|
|
selectedFiles: FileId[],
|
|
|
|
setSelectedFiles: (files: FileId[]) => void
|
2025-07-16 17:53:50 +01:00
|
|
|
) => {
|
2025-08-28 10:56:07 +01:00
|
|
|
const toggleSelection = (fileId: FileId) => {
|
2025-07-16 17:53:50 +01:00
|
|
|
setSelectedFiles(
|
|
|
|
selectedFiles.includes(fileId)
|
|
|
|
? selectedFiles.filter(id => id !== fileId)
|
|
|
|
: [...selectedFiles, fileId]
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const clearSelection = () => {
|
|
|
|
setSelectedFiles([]);
|
|
|
|
};
|
|
|
|
|
2025-09-16 15:08:11 +01:00
|
|
|
const selectMultipleFiles = async (files: StirlingFileStub[], onStirlingFilesSelect: (stirlingFiles: StirlingFile[]) => void) => {
|
2025-07-16 17:53:50 +01:00
|
|
|
if (selectedFiles.length === 0) return;
|
|
|
|
|
|
|
|
try {
|
2025-09-16 15:08:11 +01:00
|
|
|
// Filter by UUID and load full StirlingFile objects directly
|
2025-08-21 17:30:26 +01:00
|
|
|
const selectedFileObjects = files.filter(f => selectedFiles.includes(f.id));
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-09-16 15:08:11 +01:00
|
|
|
const stirlingFiles = await Promise.all(
|
|
|
|
selectedFileObjects.map(async (stub) => {
|
|
|
|
const stirlingFile = await fileStorage.getStirlingFile(stub.id);
|
|
|
|
if (!stirlingFile) {
|
|
|
|
throw new Error(`File not found in storage: ${stub.name}`);
|
|
|
|
}
|
|
|
|
return stirlingFile;
|
|
|
|
})
|
2025-08-21 17:30:26 +01:00
|
|
|
);
|
2025-08-28 10:56:07 +01:00
|
|
|
|
2025-09-16 15:08:11 +01:00
|
|
|
onStirlingFilesSelect(stirlingFiles);
|
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-28 10:56:07 +01:00
|
|
|
const touchFile = useCallback(async (id: FileId) => {
|
2025-08-21 17:30:26 +01:00
|
|
|
if (!indexedDB) {
|
|
|
|
console.warn('IndexedDB context not available for touch operation');
|
|
|
|
return;
|
|
|
|
}
|
2025-08-08 15:15:09 +01:00
|
|
|
try {
|
2025-08-21 17:30:26 +01:00
|
|
|
// Update access time - this will be handled by the cache in IndexedDBContext
|
|
|
|
// when the file is loaded, so we can just load it briefly to "touch" it
|
|
|
|
await indexedDB.loadFile(id);
|
2025-08-08 15:15:09 +01:00
|
|
|
} catch (error) {
|
|
|
|
console.error('Failed to touch file:', error);
|
|
|
|
}
|
2025-08-21 17:30:26 +01:00
|
|
|
}, [indexedDB]);
|
2025-08-08 15:15:09 +01:00
|
|
|
|
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 09:16:16 +01:00
|
|
|
};
|