Files
Stirling-PDF/frontend/src/hooks/useFileManager.ts
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

164 lines
5.0 KiB
TypeScript
Raw Normal View History

2025-07-16 17:53:50 +01:00
import { useState, useCallback } from 'react';
import { useIndexedDB } from '../contexts/IndexedDBContext';
2025-09-16 15:08:11 +01:00
import { fileStorage } from '../services/fileStorage';
import { StirlingFileStub, StirlingFile } from '../types/fileContext';
import { FileId } from '../types/fileContext';
2025-07-16 17:53:50 +01:00
export const useFileManager = () => {
const [loading, setLoading] = useState(false);
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> => {
if (!indexedDB) {
throw new Error('IndexedDB context not available');
}
// Regular file loading
2025-09-16 15:08:11 +01:00
if (fileStub.id) {
const file = await indexedDB.loadFile(fileStub.id);
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})`);
}, [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 {
if (!indexedDB) {
return [];
}
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();
// 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));
return sortedFiles;
2025-07-16 17:53:50 +01:00
} catch (error) {
console.error('Failed to load recent files:', error);
return [];
} finally {
setLoading(false);
}
}, [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];
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 {
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;
}
}, [indexedDB]);
2025-07-16 17:53:50 +01:00
const storeFile = useCallback(async (file: File, fileId: FileId) => {
if (!indexedDB) {
throw new Error('IndexedDB context not available');
}
2025-07-16 17:53:50 +01:00
try {
// 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
const arrayBuffer = await file.arrayBuffer();
2025-09-16 15:08:11 +01:00
// This method is deprecated - use FileStorage directly instead
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-07-16 17:53:50 +01:00
} catch (error) {
console.error('Failed to store file:', error);
throw error;
}
}, [indexedDB]);
2025-07-16 17:53:50 +01:00
const createFileSelectionHandlers = useCallback((
selectedFiles: FileId[],
setSelectedFiles: (files: FileId[]) => void
2025-07-16 17:53:50 +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
const selectedFileObjects = files.filter(f => selectedFiles.includes(f.id));
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-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]);
const touchFile = useCallback(async (id: FileId) => {
if (!indexedDB) {
console.warn('IndexedDB context not available for touch operation');
return;
}
2025-08-08 15:15:09 +01:00
try {
// 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);
}
}, [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
};