mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-27 14:49:23 +00:00

# 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>
139 lines
4.4 KiB
TypeScript
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
|
|
};
|
|
};
|