Stirling-PDF/frontend/src/hooks/useFileManager.ts
James Brunton af5a9d1ae1
Enforce type checking in CI (#4126)
# Description of Changes
Currently, the `tsconfig.json` file enforces strict type checking, but
nothing in CI checks that the code is actually correctly typed. [Vite
only transpiles TypeScript
code](https://vite.dev/guide/features.html#transpile-only) so doesn't
ensure that the TS code we're running is correct.

This PR adds running of the type checker to CI and fixes the type errors
that have already crept into the codebase.

Note that many of the changes I've made to 'fix the types' are just
using `any` to disable the type checker because the code is under too
much churn to fix anything properly at the moment. I still think
enabling the type checker now is the best course of action though
because otherwise we'll never be able to fix all of them, and it should
at least help us not break things when adding new code.

Co-authored-by: James <james@crosscourtanalytics.com>
2025-08-11 09:16:16 +01:00

139 lines
4.4 KiB
TypeScript

import { useState, useCallback } from 'react';
import { fileStorage } from '../services/fileStorage';
import { FileWithUrl } from '../types/file';
import { createEnhancedFileFromStored } from '../utils/fileUtils';
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
export const useFileManager = () => {
const [loading, setLoading] = useState(false);
const convertToFile = useCallback(async (fileWithUrl: FileWithUrl): Promise<File> => {
if (fileWithUrl.url && fileWithUrl.url.startsWith('blob:')) {
const response = await fetch(fileWithUrl.url);
const data = await response.arrayBuffer();
const file = new File([data], fileWithUrl.name, {
type: fileWithUrl.type || 'application/pdf',
lastModified: fileWithUrl.lastModified || Date.now()
});
// Preserve the ID if it exists
if (fileWithUrl.id) {
Object.defineProperty(file, 'id', { value: fileWithUrl.id, writable: false });
}
return file;
}
// Always use ID first, fallback to name only if ID doesn't exist
const lookupKey = fileWithUrl.id || fileWithUrl.name;
const storedFile = await fileStorage.getFile(lookupKey);
if (storedFile) {
const file = new File([storedFile.data], storedFile.name, {
type: storedFile.type,
lastModified: storedFile.lastModified
});
// Add the ID to the file object
Object.defineProperty(file, 'id', { value: storedFile.id, writable: false });
return file;
}
throw new Error('File not found in storage');
}, []);
const loadRecentFiles = useCallback(async (): Promise<FileWithUrl[]> => {
setLoading(true);
try {
const files = await fileStorage.getAllFiles();
const sortedFiles = files.sort((a, b) => (b.lastModified || 0) - (a.lastModified || 0));
return sortedFiles.map(file => createEnhancedFileFromStored(file));
} catch (error) {
console.error('Failed to load recent files:', error);
return [];
} finally {
setLoading(false);
}
}, []);
const handleRemoveFile = useCallback(async (index: number, files: FileWithUrl[], setFiles: (files: FileWithUrl[]) => void) => {
const file = files[index];
try {
await fileStorage.deleteFile(file.id || file.name);
setFiles(files.filter((_, i) => i !== index));
} catch (error) {
console.error('Failed to remove file:', error);
throw error;
}
}, []);
const storeFile = useCallback(async (file: File) => {
try {
// Generate thumbnail for the file
const thumbnail = await generateThumbnailForFile(file);
// Store file with thumbnail
const storedFile = await fileStorage.storeFile(file, thumbnail);
// Add the ID to the file object
Object.defineProperty(file, 'id', { value: storedFile.id, writable: false });
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([]);
};
const selectMultipleFiles = async (files: FileWithUrl[], onFilesSelect: (files: File[]) => void) => {
if (selectedFiles.length === 0) return;
try {
const selectedFileObjects = files.filter(f => selectedFiles.includes(f.id || f.name));
const filePromises = selectedFileObjects.map(convertToFile);
const convertedFiles = await Promise.all(filePromises);
onFilesSelect(convertedFiles);
clearSelection();
} catch (error) {
console.error('Failed to load selected files:', error);
throw error;
}
};
return {
toggleSelection,
clearSelection,
selectMultipleFiles
};
}, [convertToFile]);
const touchFile = useCallback(async (id: string) => {
try {
await fileStorage.touchFile(id);
} catch (error) {
console.error('Failed to touch file:', error);
}
}, []);
return {
loading,
convertToFile,
loadRecentFiles,
handleRemoveFile,
storeFile,
touchFile,
createFileSelectionHandlers
};
};