mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-08-11 13:48:37 +02:00
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>
This commit is contained in:
parent
507ad1dc61
commit
af5a9d1ae1
@ -9,7 +9,8 @@
|
||||
"Bash(find:*)",
|
||||
"Bash(npm test)",
|
||||
"Bash(npm test:*)",
|
||||
"Bash(ls:*)"
|
||||
"Bash(ls:*)",
|
||||
"Bash(npx tsc:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
16
frontend/package-lock.json
generated
16
frontend/package-lock.json
generated
@ -39,6 +39,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@types/node": "^24.2.0",
|
||||
"@types/react": "^19.1.4",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@vitejs/plugin-react": "^4.5.0",
|
||||
@ -2384,6 +2385,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
|
||||
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||
@ -7404,6 +7414,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
|
@ -34,8 +34,8 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"dev": "npx tsc --noEmit && vite",
|
||||
"build": "npx tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"generate-licenses": "node scripts/generate-licenses.js",
|
||||
"test": "vitest",
|
||||
@ -65,6 +65,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@types/node": "^24.2.0",
|
||||
"@types/react": "^19.1.4",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@vitejs/plugin-react": "^4.5.0",
|
||||
|
@ -185,7 +185,7 @@ const FileEditor = ({
|
||||
id: `file-${Date.now()}-${Math.random()}`,
|
||||
name: file.name,
|
||||
pageCount: processedFile?.totalPages || Math.floor(Math.random() * 20) + 1,
|
||||
thumbnail,
|
||||
thumbnail: thumbnail || '',
|
||||
size: file.size,
|
||||
file,
|
||||
};
|
||||
@ -605,11 +605,8 @@ const FileEditor = ({
|
||||
removeFiles([fileId], false);
|
||||
|
||||
// Remove from context selections
|
||||
setContextSelectedFiles(prev => {
|
||||
const safePrev = Array.isArray(prev) ? prev : [];
|
||||
return safePrev.filter(id => id !== fileId);
|
||||
});
|
||||
|
||||
const newSelection = contextSelectedIds.filter(id => id !== fileId);
|
||||
setContextSelectedFiles(newSelection);
|
||||
// Mark operation as applied
|
||||
markOperationApplied(fileName, operationId);
|
||||
} else {
|
||||
@ -767,21 +764,21 @@ const FileEditor = ({
|
||||
) : (
|
||||
<DragDropGrid
|
||||
items={files}
|
||||
selectedItems={localSelectedIds}
|
||||
selectedItems={localSelectedIds as any /* FIX ME */}
|
||||
selectionMode={selectionMode}
|
||||
isAnimating={isAnimating}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
onEndZoneDragEnter={handleEndZoneDragEnter}
|
||||
draggedItem={draggedFile}
|
||||
dropTarget={dropTarget}
|
||||
multiItemDrag={multiFileDrag}
|
||||
dragPosition={dragPosition}
|
||||
renderItem={(file, index, refs) => (
|
||||
onDragStart={handleDragStart as any /* FIX ME */}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={handleDragEnter as any /* FIX ME */}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop as any /* FIX ME */}
|
||||
onEndZoneDragEnter={handleEndZoneDragEnter}
|
||||
draggedItem={draggedFile as any /* FIX ME */}
|
||||
dropTarget={dropTarget as any /* FIX ME */}
|
||||
multiItemDrag={multiFileDrag as any /* FIX ME */}
|
||||
dragPosition={dragPosition}
|
||||
renderItem={(file, index, refs) => (
|
||||
<FileThumbnail
|
||||
file={file}
|
||||
index={index}
|
||||
@ -801,8 +798,6 @@ const FileEditor = ({
|
||||
onToggleFile={toggleFile}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
onViewFile={handleViewFile}
|
||||
onMergeFromHere={handleMergeFromHere}
|
||||
onSplitFile={handleSplitFile}
|
||||
onSetStatus={setStatus}
|
||||
toolMode={toolMode}
|
||||
isSupported={isFileSupported(file.name)}
|
||||
@ -831,7 +826,6 @@ const FileEditor = ({
|
||||
onClose={() => setShowFilePickerModal(false)}
|
||||
storedFiles={[]} // FileEditor doesn't have access to stored files, needs to be passed from parent
|
||||
onSelectFiles={handleLoadFromStorage}
|
||||
allowMultiple={true}
|
||||
/>
|
||||
|
||||
{status && (
|
||||
|
@ -29,7 +29,8 @@ const FileOperationHistory: React.FC<FileOperationHistoryProps> = ({
|
||||
const { getFileHistory, getAppliedOperations } = useFileContext();
|
||||
|
||||
const history = getFileHistory(fileId);
|
||||
const operations = showOnlyApplied ? getAppliedOperations(fileId) : history?.operations || [];
|
||||
const allOperations = showOnlyApplied ? getAppliedOperations(fileId) : history?.operations || [];
|
||||
const operations = allOperations.filter(op => 'fileIds' in op) as FileOperation[];
|
||||
|
||||
const formatTimestamp = (timestamp: number) => {
|
||||
return new Date(timestamp).toLocaleString();
|
||||
@ -62,7 +63,7 @@ const FileOperationHistory: React.FC<FileOperationHistoryProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const renderOperationDetails = (operation: FileOperation | PageOperation) => {
|
||||
const renderOperationDetails = (operation: FileOperation) => {
|
||||
if ('metadata' in operation && operation.metadata) {
|
||||
const { metadata } = operation;
|
||||
return (
|
||||
|
@ -142,7 +142,7 @@ export default function Workbench() {
|
||||
{/* Top Controls */}
|
||||
<TopControls
|
||||
currentView={currentView}
|
||||
setCurrentView={setCurrentView}
|
||||
setCurrentView={setCurrentView as any /* FIX ME */}
|
||||
selectedToolKey={selectedToolKey}
|
||||
/>
|
||||
|
||||
|
@ -22,7 +22,7 @@ interface DragDropGridProps<T extends DragDropItem> {
|
||||
renderItem: (item: T, index: number, refs: React.MutableRefObject<Map<string, HTMLDivElement>>) => React.ReactNode;
|
||||
renderSplitMarker?: (item: T, index: number) => React.ReactNode;
|
||||
draggedItem: number | null;
|
||||
dropTarget: number | null;
|
||||
dropTarget: number | 'end' | null;
|
||||
multiItemDrag: {pageNumbers: number[], count: number} | null;
|
||||
dragPosition: {x: number, y: number} | null;
|
||||
}
|
||||
|
@ -345,7 +345,7 @@ const FileThumbnail = ({
|
||||
onClose={() => setShowHistory(false)}
|
||||
title={`Operation History - ${file.name}`}
|
||||
size="lg"
|
||||
scrollAreaComponent="div"
|
||||
scrollAreaComponent={'div' as any}
|
||||
>
|
||||
<FileOperationHistory
|
||||
fileId={file.name}
|
||||
|
@ -43,7 +43,7 @@ export interface PageEditorProps {
|
||||
onExportAll: () => void;
|
||||
exportLoading: boolean;
|
||||
selectionMode: boolean;
|
||||
selectedPages: string[];
|
||||
selectedPages: number[];
|
||||
closePdf: () => void;
|
||||
}) => void;
|
||||
}
|
||||
@ -149,7 +149,7 @@ const PageEditor = ({
|
||||
|
||||
// Drag and drop state
|
||||
const [draggedPage, setDraggedPage] = useState<number | null>(null);
|
||||
const [dropTarget, setDropTarget] = useState<number | null>(null);
|
||||
const [dropTarget, setDropTarget] = useState<number | 'end' | null>(null);
|
||||
const [multiPageDrag, setMultiPageDrag] = useState<{pageNumbers: number[], count: number} | null>(null);
|
||||
const [dragPosition, setDragPosition] = useState<{x: number, y: number} | null>(null);
|
||||
|
||||
@ -890,7 +890,7 @@ const PageEditor = ({
|
||||
|
||||
const errors = pdfExportService.validateExport(mergedPdfDocument, exportPageIds, selectedOnly);
|
||||
if (errors.length > 0) {
|
||||
setError(errors.join(', '));
|
||||
setStatus(errors.join(', '));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -921,7 +921,7 @@ const PageEditor = ({
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Export failed';
|
||||
setError(errorMessage);
|
||||
setStatus(errorMessage);
|
||||
} finally {
|
||||
setExportLoading(false);
|
||||
}
|
||||
@ -1259,7 +1259,7 @@ const PageEditor = ({
|
||||
selectedPages={selectedPageNumbers}
|
||||
selectionMode={selectionMode}
|
||||
draggedPage={draggedPage}
|
||||
dropTarget={dropTarget}
|
||||
dropTarget={dropTarget === 'end' ? null : dropTarget}
|
||||
movingPage={movingPage}
|
||||
isAnimating={isAnimating}
|
||||
pageRefs={refs}
|
||||
|
@ -35,7 +35,7 @@ interface PageEditorControlsProps {
|
||||
|
||||
// Selection state
|
||||
selectionMode: boolean;
|
||||
selectedPages: string[];
|
||||
selectedPages: number[];
|
||||
}
|
||||
|
||||
const PageEditorControls = ({
|
||||
|
@ -7,9 +7,9 @@ import RotateRightIcon from '@mui/icons-material/RotateRight';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import ContentCutIcon from '@mui/icons-material/ContentCut';
|
||||
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
||||
import { PDFPage, PDFDocument } from '../../../types/pageEditor';
|
||||
import { RotatePagesCommand, DeletePagesCommand, ToggleSplitCommand } from '../../../commands/pageCommands';
|
||||
import { Command } from '../../../hooks/useUndoRedo';
|
||||
import { PDFPage, PDFDocument } from '../../types/pageEditor';
|
||||
import { RotatePagesCommand, DeletePagesCommand, ToggleSplitCommand } from '../../commands/pageCommands';
|
||||
import { Command } from '../../hooks/useUndoRedo';
|
||||
import styles from './PageEditor.module.css';
|
||||
import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist';
|
||||
|
||||
@ -29,7 +29,7 @@ interface PageThumbnailProps {
|
||||
selectedPages: number[];
|
||||
selectionMode: boolean;
|
||||
draggedPage: number | null;
|
||||
dropTarget: number | null;
|
||||
dropTarget: number | 'end' | null;
|
||||
movingPage: number | null;
|
||||
isAnimating: boolean;
|
||||
pageRefs: React.MutableRefObject<Map<string, HTMLDivElement>>;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Card, Stack, Text, Group, Badge, Button, Box, Image, ThemeIcon, ActionIcon, Tooltip } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
|
||||
@ -9,7 +9,6 @@ import EditIcon from "@mui/icons-material/Edit";
|
||||
import { FileWithUrl } from "../../types/file";
|
||||
import { getFileSize, getFileDate } from "../../utils/fileUtils";
|
||||
import { useIndexedDBThumbnail } from "../../hooks/useIndexedDBThumbnail";
|
||||
import { fileStorage } from "../../services/fileStorage";
|
||||
|
||||
interface FileCardProps {
|
||||
file: FileWithUrl;
|
||||
|
@ -80,7 +80,7 @@ const FileGrid = ({
|
||||
{showSearch && (
|
||||
<TextInput
|
||||
placeholder={t("fileManager.searchFiles", "Search files...")}
|
||||
leftSection={<SearchIcon size={16} />}
|
||||
leftSection={<SearchIcon fontSize="small" />}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.currentTarget.value)}
|
||||
style={{ flexGrow: 1, maxWidth: 300, minWidth: 200 }}
|
||||
@ -96,7 +96,7 @@ const FileGrid = ({
|
||||
]}
|
||||
value={sortBy}
|
||||
onChange={(value) => setSortBy(value as SortOption)}
|
||||
leftSection={<SortIcon size={16} />}
|
||||
leftSection={<SortIcon fontSize="small" />}
|
||||
style={{ minWidth: 150 }}
|
||||
/>
|
||||
)}
|
||||
@ -130,7 +130,7 @@ const FileGrid = ({
|
||||
<FileCard
|
||||
key={fileId + idx}
|
||||
file={file}
|
||||
onRemove={onRemove ? () => onRemove(originalIdx) : undefined}
|
||||
onRemove={onRemove ? () => onRemove(originalIdx) : () => {}}
|
||||
onDoubleClick={onDoubleClick && supported ? () => onDoubleClick(file) : undefined}
|
||||
onView={onView && supported ? () => onView(file) : undefined}
|
||||
onEdit={onEdit && supported ? () => onEdit(file) : undefined}
|
||||
|
@ -77,7 +77,7 @@ export default function ToolPanel() {
|
||||
{/* Tool content */}
|
||||
<div className="flex-1 min-h-0">
|
||||
<ToolRenderer
|
||||
selectedToolKey={selectedToolKey}
|
||||
selectedToolKey={selectedToolKey || ''}
|
||||
onPreviewFile={setPreviewFile}
|
||||
/>
|
||||
</div>
|
||||
|
@ -30,7 +30,7 @@ const ConvertFromImageSettings = ({
|
||||
})}
|
||||
data={[
|
||||
{ value: COLOR_TYPES.COLOR, label: t("convert.color", "Color") },
|
||||
{ value: COLOR_TYPES.GREYSCALE, label: t("convert.greyscale", "Greyscale") },
|
||||
{ value: COLOR_TYPES.GRAYSCALE, label: t("convert.grayscale", "Grayscale") },
|
||||
{ value: COLOR_TYPES.BLACK_WHITE, label: t("convert.blackwhite", "Black & White") },
|
||||
]}
|
||||
disabled={disabled}
|
||||
|
@ -31,7 +31,6 @@ const ConvertFromWebSettings = ({
|
||||
min={0.1}
|
||||
max={3.0}
|
||||
step={0.1}
|
||||
precision={1}
|
||||
disabled={disabled}
|
||||
data-testid="zoom-level-input"
|
||||
/>
|
||||
|
@ -31,7 +31,7 @@ const ConvertToImageSettings = ({
|
||||
})}
|
||||
data={[
|
||||
{ value: COLOR_TYPES.COLOR, label: t("convert.color", "Color") },
|
||||
{ value: COLOR_TYPES.GREYSCALE, label: t("convert.greyscale", "Greyscale") },
|
||||
{ value: COLOR_TYPES.GRAYSCALE, label: t("convert.grayscale", "Grayscale") },
|
||||
{ value: COLOR_TYPES.BLACK_WHITE, label: t("convert.blackwhite", "Black & White") },
|
||||
]}
|
||||
disabled={disabled}
|
||||
|
@ -30,7 +30,7 @@ const ConvertToPdfaSettings = ({
|
||||
<Text size="sm" fw={500}>{t("convert.pdfaOptions", "PDF/A Options")}:</Text>
|
||||
|
||||
{hasDigitalSignatures && (
|
||||
<Alert color="yellow" size="sm">
|
||||
<Alert color="yellow">
|
||||
<Text size="sm">
|
||||
{t("convert.pdfaDigitalSignatureWarning", "The PDF contains a digital signature. This will be removed in the next step.")}
|
||||
</Text>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Stack, TextInput, Select, Checkbox } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SPLIT_MODES, SPLIT_TYPES, type SplitMode, type SplitType } from '../../../constants/splitConstants';
|
||||
import { isSplitMode, SPLIT_MODES, SPLIT_TYPES, type SplitMode, type SplitType } from '../../../constants/splitConstants';
|
||||
|
||||
export interface SplitParameters {
|
||||
mode: SplitMode | '';
|
||||
@ -123,7 +123,7 @@ const SplitSettings = ({
|
||||
label="Choose split method"
|
||||
placeholder="Select how to split the PDF"
|
||||
value={parameters.mode}
|
||||
onChange={(v) => v && onParameterChange('mode', v)}
|
||||
onChange={(v) => isSplitMode(v) && onParameterChange('mode', v)}
|
||||
disabled={disabled}
|
||||
data={[
|
||||
{ value: SPLIT_MODES.BY_PAGES, label: t("split.header", "Split by Pages") + " (e.g. 1,3,5-10)" },
|
||||
|
@ -137,7 +137,7 @@ export interface ViewerProps {
|
||||
sidebarsVisible: boolean;
|
||||
setSidebarsVisible: (v: boolean) => void;
|
||||
onClose?: () => void;
|
||||
previewFile?: File; // For preview mode - bypasses context
|
||||
previewFile: File | null; // For preview mode - bypasses context
|
||||
}
|
||||
|
||||
const Viewer = ({
|
||||
@ -151,11 +151,6 @@ const Viewer = ({
|
||||
|
||||
// Get current file from FileContext
|
||||
const { getCurrentFile, getCurrentProcessedFile, clearAllFiles, addFiles, activeFiles } = useFileContext();
|
||||
const currentFile = getCurrentFile();
|
||||
const processedFile = getCurrentProcessedFile();
|
||||
|
||||
// Convert File to FileWithUrl format for viewer
|
||||
const pdfFile = useFileWithUrl(currentFile);
|
||||
|
||||
// Tab management for multiple files
|
||||
const [activeTab, setActiveTab] = useState<string>("0");
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
export const COLOR_TYPES = {
|
||||
COLOR: 'color',
|
||||
GREYSCALE: 'greyscale',
|
||||
GRAYSCALE: 'grayscale',
|
||||
BLACK_WHITE: 'blackwhite'
|
||||
} as const;
|
||||
|
||||
|
@ -20,3 +20,11 @@ export const ENDPOINTS = {
|
||||
|
||||
export type SplitMode = typeof SPLIT_MODES[keyof typeof SPLIT_MODES];
|
||||
export type SplitType = typeof SPLIT_TYPES[keyof typeof SPLIT_TYPES];
|
||||
|
||||
export const isSplitMode = (value: string | null): value is SplitMode => {
|
||||
return Object.values(SPLIT_MODES).includes(value as SplitMode);
|
||||
}
|
||||
|
||||
export const isSplitType = (value: string | null): value is SplitType => {
|
||||
return Object.values(SPLIT_TYPES).includes(value as SplitType);
|
||||
}
|
||||
|
@ -461,7 +461,8 @@ export function FileContextProvider({
|
||||
|
||||
// Force garbage collection hint
|
||||
if (typeof window !== 'undefined' && window.gc) {
|
||||
setTimeout(() => window.gc(), 100);
|
||||
let gc = window.gc
|
||||
setTimeout(() => gc(), 100);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@ -504,7 +505,7 @@ export function FileContextProvider({
|
||||
// File doesn't have explicit ID, store it with thumbnail
|
||||
try {
|
||||
// Generate thumbnail for better recent files experience
|
||||
const thumbnail = await thumbnailGenerationService.generateThumbnail(file);
|
||||
const thumbnail = await (thumbnailGenerationService as any /* FIX ME */).generateThumbnail(file);
|
||||
const storedFile = await fileStorage.storeFile(file, thumbnail);
|
||||
// Add the ID to the file object
|
||||
Object.defineProperty(file, 'id', { value: storedFile.id, writable: false });
|
||||
@ -597,8 +598,9 @@ export function FileContextProvider({
|
||||
|
||||
if (state.currentMode !== mode && state.activeFiles.length > 0) {
|
||||
if (window.requestIdleCallback && typeof window !== 'undefined' && window.gc) {
|
||||
let gc = window.gc;
|
||||
window.requestIdleCallback(() => {
|
||||
window.gc();
|
||||
gc();
|
||||
}, { timeout: 5000 });
|
||||
}
|
||||
}
|
||||
@ -611,8 +613,9 @@ export function FileContextProvider({
|
||||
|
||||
if (state.currentView !== view && state.activeFiles.length > 0) {
|
||||
if (window.requestIdleCallback && typeof window !== 'undefined' && window.gc) {
|
||||
let gc = window.gc;
|
||||
window.requestIdleCallback(() => {
|
||||
window.gc();
|
||||
gc();
|
||||
}, { timeout: 5000 });
|
||||
}
|
||||
}
|
||||
@ -724,7 +727,7 @@ export function FileContextProvider({
|
||||
currentView: state.currentView,
|
||||
currentTool: state.currentTool,
|
||||
selectedFileIds: state.selectedFileIds,
|
||||
selectedPageIds: state.selectedPageIds,
|
||||
selectedPageNumbers: state.selectedPageNumbers,
|
||||
viewerConfig: state.viewerConfig,
|
||||
lastExportConfig: state.lastExportConfig,
|
||||
timestamp: Date.now()
|
||||
@ -854,7 +857,7 @@ export function useCurrentFile() {
|
||||
export function useFileSelection() {
|
||||
const {
|
||||
selectedFileIds,
|
||||
selectedPageIds,
|
||||
selectedPageNumbers,
|
||||
setSelectedFiles,
|
||||
setSelectedPages,
|
||||
clearSelections
|
||||
@ -862,7 +865,7 @@ export function useFileSelection() {
|
||||
|
||||
return {
|
||||
selectedFileIds,
|
||||
selectedPageIds,
|
||||
selectedPageNumbers,
|
||||
setSelectedFiles,
|
||||
setSelectedPages,
|
||||
clearSelections
|
||||
|
@ -10,7 +10,7 @@ interface FileManagerContextValue {
|
||||
searchTerm: string;
|
||||
selectedFiles: FileWithUrl[];
|
||||
filteredFiles: FileWithUrl[];
|
||||
fileInputRef: React.RefObject<HTMLInputElement>;
|
||||
fileInputRef: React.RefObject<HTMLInputElement | null>;
|
||||
|
||||
// Handlers
|
||||
onSourceChange: (source: 'recent' | 'local' | 'drive') => void;
|
||||
@ -85,10 +85,14 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
||||
|
||||
const handleFileSelect = useCallback((file: FileWithUrl) => {
|
||||
setSelectedFileIds(prev => {
|
||||
if (prev.includes(file.id)) {
|
||||
return prev.filter(id => id !== file.id);
|
||||
if (file.id) {
|
||||
if (prev.includes(file.id)) {
|
||||
return prev.filter(id => id !== file.id);
|
||||
} else {
|
||||
return [...prev, file.id];
|
||||
}
|
||||
} else {
|
||||
return [...prev, file.id];
|
||||
return prev;
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
@ -138,7 +142,7 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
|
||||
};
|
||||
});
|
||||
|
||||
onFilesSelected(fileWithUrls);
|
||||
onFilesSelected(fileWithUrls as any /* FIX ME */);
|
||||
await refreshRecentFiles();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
|
@ -7,7 +7,7 @@ interface FilesModalContextType {
|
||||
closeFilesModal: () => void;
|
||||
onFileSelect: (file: File) => void;
|
||||
onFilesSelect: (files: File[]) => void;
|
||||
onModalClose: () => void;
|
||||
onModalClose?: () => void;
|
||||
setOnModalClose: (callback: () => void) => void;
|
||||
}
|
||||
|
||||
|
@ -345,7 +345,7 @@ describe('useConvertParameters - Auto Detection & Smart Conversion', () => {
|
||||
test('should handle malformed file objects', () => {
|
||||
const { result } = renderHook(() => useConvertParameters());
|
||||
|
||||
const malformedFiles = [
|
||||
const malformedFiles: Array<{name: string}> = [
|
||||
{ name: 'valid.pdf' },
|
||||
// @ts-ignore - Testing runtime resilience
|
||||
{ name: null },
|
||||
|
@ -99,7 +99,7 @@ export const useOCROperation = () => {
|
||||
const ocrConfig: ToolOperationConfig<OCRParameters> = {
|
||||
operationType: 'ocr',
|
||||
endpoint: '/api/v1/misc/ocr-pdf',
|
||||
buildFormData,
|
||||
buildFormData: buildFormData as any /* FIX ME */,
|
||||
filePrefix: 'ocr_',
|
||||
multiFileEndpoint: false, // Process files individually
|
||||
responseHandler, // use shared flow
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import axios, { CancelTokenSource } from 'axios';
|
||||
import { processResponse } from '../../../utils/toolResponseProcessor';
|
||||
import type { ResponseHandler, ProcessingProgress } from './useToolState';
|
||||
import { processResponse, ResponseHandler } from '../../../utils/toolResponseProcessor';
|
||||
import type { ProcessingProgress } from './useToolState';
|
||||
|
||||
export interface ApiCallsConfig<TParams = void> {
|
||||
endpoint: string | ((params: TParams) => string);
|
||||
|
@ -7,7 +7,7 @@ import { useToolApiCalls, type ApiCallsConfig } from './useToolApiCalls';
|
||||
import { useToolResources } from './useToolResources';
|
||||
import { extractErrorMessage } from '../../../utils/toolErrorHandler';
|
||||
import { createOperation } from '../../../utils/toolOperationTracker';
|
||||
import { type ResponseHandler, processResponse } from '../../../utils/toolResponseProcessor';
|
||||
import { ResponseHandler } from '../../../utils/toolResponseProcessor';
|
||||
|
||||
export interface ValidationResult {
|
||||
valid: boolean;
|
||||
@ -186,7 +186,7 @@ export const useToolOperation = <TParams = void>(
|
||||
// Individual file processing - separate API call per file
|
||||
const apiCallsConfig: ApiCallsConfig<TParams> = {
|
||||
endpoint: config.endpoint,
|
||||
buildFormData: (file: File, params: TParams) => (config.buildFormData as (file: File, params: TParams) => FormData)(file, params),
|
||||
buildFormData: (file: File, params: TParams) => (config.buildFormData as any /* FIX ME */)(file, params),
|
||||
filePrefix: config.filePrefix,
|
||||
responseHandler: config.responseHandler
|
||||
};
|
||||
|
@ -40,7 +40,9 @@ export const useToolResources = () => {
|
||||
for (const file of files) {
|
||||
try {
|
||||
const thumbnail = await generateThumbnailForFile(file);
|
||||
thumbnails.push(thumbnail);
|
||||
if (thumbnail) {
|
||||
thumbnails.push(thumbnail);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to generate thumbnail for ${file.name}:`, error);
|
||||
thumbnails.push('');
|
||||
|
@ -1,6 +1,7 @@
|
||||
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 = () => {
|
||||
@ -42,7 +43,7 @@ export const useFileManager = () => {
|
||||
try {
|
||||
const files = await fileStorage.getAllFiles();
|
||||
const sortedFiles = files.sort((a, b) => (b.lastModified || 0) - (a.lastModified || 0));
|
||||
return sortedFiles;
|
||||
return sortedFiles.map(file => createEnhancedFileFromStored(file));
|
||||
} catch (error) {
|
||||
console.error('Failed to load recent files:', error);
|
||||
return [];
|
||||
|
@ -4,7 +4,7 @@ import { useMemo } from 'react';
|
||||
* Hook to convert a File object to { file: File; url: string } format
|
||||
* Creates blob URL on-demand and handles cleanup
|
||||
*/
|
||||
export function useFileWithUrl(file: File | null): { file: File; url: string } | null {
|
||||
export function useFileWithUrl(file: File | Blob | null): { file: File | Blob; url: string } | null {
|
||||
return useMemo(() => {
|
||||
if (!file) return null;
|
||||
|
||||
|
@ -61,9 +61,9 @@ export function useIndexedDBThumbnail(file: FileWithUrl | undefined | null): {
|
||||
type: storedFile.type,
|
||||
lastModified: storedFile.lastModified
|
||||
});
|
||||
} else if (file.file) {
|
||||
} else if ((file as any /* Fix me */).file) {
|
||||
// For FileWithUrl objects that have a File object
|
||||
fileObject = file.file;
|
||||
fileObject = (file as any /* Fix me */).file;
|
||||
} else if (file.id) {
|
||||
// Fallback: try to get from IndexedDB even if storedInIndexedDB flag is missing
|
||||
const storedFile = await fileStorage.getFile(file.id);
|
||||
|
@ -44,7 +44,7 @@ function HomePageContent() {
|
||||
ref={quickAccessRef} />
|
||||
<ToolPanel />
|
||||
<Workbench />
|
||||
<FileManager selectedTool={selectedTool} />
|
||||
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@ -53,7 +53,7 @@ export default function HomePage() {
|
||||
const { setCurrentView } = useFileContext();
|
||||
return (
|
||||
<FileSelectionProvider>
|
||||
<ToolWorkflowProvider onViewChange={setCurrentView}>
|
||||
<ToolWorkflowProvider onViewChange={setCurrentView as any /* FIX ME */}>
|
||||
<SidebarProvider>
|
||||
<HomePageContent />
|
||||
</SidebarProvider>
|
||||
|
@ -520,7 +520,8 @@ export class EnhancedPDFProcessingService {
|
||||
|
||||
// Force memory cleanup hint
|
||||
if (typeof window !== 'undefined' && window.gc) {
|
||||
setTimeout(() => window.gc(), 100);
|
||||
let gc = window.gc;
|
||||
setTimeout(() => gc(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ export class FileAnalyzer {
|
||||
}).promise;
|
||||
|
||||
const pageCount = pdf.numPages;
|
||||
const isEncrypted = pdf.isEncrypted;
|
||||
const isEncrypted = (pdf as any).isEncrypted;
|
||||
|
||||
// Clean up
|
||||
pdf.destroy();
|
||||
|
@ -389,7 +389,9 @@ class FileStorageService {
|
||||
|
||||
db.close();
|
||||
} catch (error) {
|
||||
console.log(`Version ${version} not accessible:`, error.message);
|
||||
if (error instanceof Error) {
|
||||
console.log(`Version ${version} not accessible:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,8 @@ export class PDFProcessingService {
|
||||
fileName: file.name,
|
||||
status: 'processing',
|
||||
progress: 0,
|
||||
startedAt: Date.now()
|
||||
startedAt: Date.now(),
|
||||
strategy: 'immediate_full'
|
||||
};
|
||||
|
||||
this.processing.set(fileKey, state);
|
||||
@ -79,7 +80,7 @@ export class PDFProcessingService {
|
||||
} catch (error) {
|
||||
console.error('Processing failed for', file.name, ':', error);
|
||||
state.status = 'error';
|
||||
state.error = error instanceof Error ? error.message : 'Unknown error';
|
||||
state.error = (error instanceof Error ? error.message : 'Unknown error') as any;
|
||||
this.notifyListeners();
|
||||
|
||||
// Remove failed processing after delay
|
||||
|
@ -1,4 +1,17 @@
|
||||
import JSZip from 'jszip';
|
||||
import JSZip, { JSZipObject } from 'jszip';
|
||||
|
||||
// Undocumented interface in JSZip for JSZipObject._data
|
||||
interface CompressedObject {
|
||||
compressedSize: number;
|
||||
uncompressedSize: number;
|
||||
crc32: number;
|
||||
compression: object;
|
||||
compressedContent: string|ArrayBuffer|Uint8Array|Buffer;
|
||||
}
|
||||
|
||||
const getData = (zipEntry: JSZipObject): CompressedObject | undefined => {
|
||||
return (zipEntry as any)._data as CompressedObject;
|
||||
}
|
||||
|
||||
export interface ZipExtractionResult {
|
||||
success: boolean;
|
||||
@ -68,7 +81,7 @@ export class ZipFileService {
|
||||
}
|
||||
|
||||
fileCount++;
|
||||
const uncompressedSize = zipEntry._data?.uncompressedSize || 0;
|
||||
const uncompressedSize = getData(zipEntry)?.uncompressedSize || 0;
|
||||
totalSize += uncompressedSize;
|
||||
|
||||
// Check if file is a PDF
|
||||
@ -187,7 +200,7 @@ export class ZipFileService {
|
||||
const content = await zipEntry.async('uint8array');
|
||||
|
||||
// Create File object
|
||||
const extractedFile = new File([content], this.sanitizeFilename(filename), {
|
||||
const extractedFile = new File([content as any], this.sanitizeFilename(filename), {
|
||||
type: 'application/pdf',
|
||||
lastModified: zipEntry.date?.getTime() || Date.now()
|
||||
});
|
||||
@ -312,7 +325,7 @@ export class ZipFileService {
|
||||
|
||||
// Check if any files are encrypted
|
||||
for (const [filename, zipEntry] of Object.entries(zip.files)) {
|
||||
if (zipEntry.options?.compression === 'STORE' && zipEntry._data?.compressedSize === 0) {
|
||||
if (zipEntry.options?.compression === 'STORE' && getData(zipEntry)?.compressedSize === 0) {
|
||||
// This might indicate encryption, but JSZip doesn't provide direct encryption detection
|
||||
// We'll handle this in the extraction phase
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ Object.defineProperty(globalThis, 'crypto', {
|
||||
}
|
||||
return array;
|
||||
}),
|
||||
} as Crypto,
|
||||
} as unknown as Crypto,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
@ -135,7 +135,7 @@ async function uploadFileViaModal(page: Page, filePath: string) {
|
||||
await page.click('[data-testid="files-button"]');
|
||||
|
||||
// Wait for the modal to open
|
||||
await page.waitForSelector('.mantine-Modal-overlay', { state: 'visible' }, { timeout: 5000 });
|
||||
await page.waitForSelector('.mantine-Modal-overlay', { state: 'visible', timeout: 5000 });
|
||||
//await page.waitForSelector('[data-testid="file-upload-modal"]', { timeout: 5000 });
|
||||
|
||||
// Upload the file through the modal's file input
|
||||
@ -318,7 +318,13 @@ test.describe('Convert Tool E2E Tests', () => {
|
||||
// Generate a test for each potentially available conversion
|
||||
// We'll discover all possible conversions and then skip unavailable ones at runtime
|
||||
test('PDF to PNG conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/img', fromFormat: 'pdf', toFormat: 'png' };
|
||||
const conversion: ConversionEndpoint = {
|
||||
endpoint: '/api/v1/convert/pdf/img',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'png',
|
||||
description: '',
|
||||
apiPath: ''
|
||||
};
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
@ -329,7 +335,13 @@ test.describe('Convert Tool E2E Tests', () => {
|
||||
});
|
||||
|
||||
test('PDF to DOCX conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/word', fromFormat: 'pdf', toFormat: 'docx' };
|
||||
const conversion: ConversionEndpoint = {
|
||||
endpoint: '/api/v1/convert/pdf/word',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'docx',
|
||||
description: '',
|
||||
apiPath: ''
|
||||
};
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
@ -340,7 +352,13 @@ test.describe('Convert Tool E2E Tests', () => {
|
||||
});
|
||||
|
||||
test('DOCX to PDF conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/file/pdf', fromFormat: 'docx', toFormat: 'pdf' };
|
||||
const conversion: ConversionEndpoint = {
|
||||
endpoint: '/api/v1/convert/file/pdf',
|
||||
fromFormat: 'docx',
|
||||
toFormat: 'pdf',
|
||||
description: '',
|
||||
apiPath: ''
|
||||
};
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
@ -351,7 +369,13 @@ test.describe('Convert Tool E2E Tests', () => {
|
||||
});
|
||||
|
||||
test('Image to PDF conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/img/pdf', fromFormat: 'png', toFormat: 'pdf' };
|
||||
const conversion: ConversionEndpoint = {
|
||||
endpoint: '/api/v1/convert/img/pdf',
|
||||
fromFormat: 'png',
|
||||
toFormat: 'pdf',
|
||||
description: '',
|
||||
apiPath: ''
|
||||
};
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
@ -362,7 +386,13 @@ test.describe('Convert Tool E2E Tests', () => {
|
||||
});
|
||||
|
||||
test('PDF to TXT conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/text', fromFormat: 'pdf', toFormat: 'txt' };
|
||||
const conversion: ConversionEndpoint = {
|
||||
endpoint: '/api/v1/convert/pdf/text',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'txt',
|
||||
description: '',
|
||||
apiPath: ''
|
||||
};
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
@ -373,7 +403,13 @@ test.describe('Convert Tool E2E Tests', () => {
|
||||
});
|
||||
|
||||
test('PDF to HTML conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/html', fromFormat: 'pdf', toFormat: 'html' };
|
||||
const conversion: ConversionEndpoint = {
|
||||
endpoint: '/api/v1/convert/pdf/html',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'html',
|
||||
description: '',
|
||||
apiPath: ''
|
||||
};
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
@ -384,7 +420,13 @@ test.describe('Convert Tool E2E Tests', () => {
|
||||
});
|
||||
|
||||
test('PDF to XML conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/xml', fromFormat: 'pdf', toFormat: 'xml' };
|
||||
const conversion: ConversionEndpoint = {
|
||||
endpoint: '/api/v1/convert/pdf/xml',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'xml',
|
||||
description: '',
|
||||
apiPath: ''
|
||||
};
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
@ -395,7 +437,13 @@ test.describe('Convert Tool E2E Tests', () => {
|
||||
});
|
||||
|
||||
test('PDF to CSV conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/csv', fromFormat: 'pdf', toFormat: 'csv' };
|
||||
const conversion: ConversionEndpoint = {
|
||||
endpoint: '/api/v1/convert/pdf/csv',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'csv',
|
||||
description: '',
|
||||
apiPath: ''
|
||||
};
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
@ -406,7 +454,13 @@ test.describe('Convert Tool E2E Tests', () => {
|
||||
});
|
||||
|
||||
test('PDF to PDFA conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/pdfa', fromFormat: 'pdf', toFormat: 'pdfa' };
|
||||
const conversion: ConversionEndpoint = {
|
||||
endpoint: '/api/v1/convert/pdf/pdfa',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'pdfa',
|
||||
description: '',
|
||||
apiPath: ''
|
||||
};
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { describe, test, expect, vi, beforeEach, afterEach, Mock } from 'vitest';
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useConvertOperation } from '../../hooks/tools/convert/useConvertOperation';
|
||||
import { ConvertParameters } from '../../hooks/tools/convert/useConvertParameters';
|
||||
@ -85,7 +85,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
|
||||
test('should make correct API call for PDF to PNG conversion', async () => {
|
||||
const mockBlob = new Blob(['fake-image-data'], { type: 'image/png' });
|
||||
mockedAxios.post.mockResolvedValueOnce({
|
||||
(mockedAxios.post as Mock).mockResolvedValueOnce({
|
||||
data: mockBlob,
|
||||
status: 200,
|
||||
statusText: 'OK'
|
||||
@ -108,7 +108,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -123,7 +135,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
);
|
||||
|
||||
// Verify FormData contains correct parameters
|
||||
const formDataCall = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
const formDataCall = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
||||
expect(formDataCall.get('imageFormat')).toBe('png');
|
||||
expect(formDataCall.get('colorType')).toBe('color');
|
||||
expect(formDataCall.get('dpi')).toBe('300');
|
||||
@ -138,7 +150,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
|
||||
test('should handle API error responses correctly', async () => {
|
||||
const errorMessage = 'Invalid file format';
|
||||
mockedAxios.post.mockRejectedValueOnce({
|
||||
(mockedAxios.post as Mock).mockRejectedValueOnce({
|
||||
response: {
|
||||
status: 400,
|
||||
data: errorMessage
|
||||
@ -163,7 +175,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -177,7 +201,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
});
|
||||
|
||||
test('should handle network errors gracefully', async () => {
|
||||
mockedAxios.post.mockRejectedValueOnce(new Error('Network error'));
|
||||
(mockedAxios.post as Mock).mockRejectedValueOnce(new Error('Network error'));
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
@ -196,7 +220,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -212,7 +248,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
|
||||
test('should correctly map image conversion parameters to API call', async () => {
|
||||
const mockBlob = new Blob(['fake-data'], { type: 'image/jpeg' });
|
||||
mockedAxios.post.mockResolvedValueOnce({
|
||||
(mockedAxios.post as Mock).mockResolvedValueOnce({
|
||||
data: mockBlob,
|
||||
status: 200,
|
||||
headers: {
|
||||
@ -229,7 +265,6 @@ describe('Convert Tool Integration Tests', () => {
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'jpg',
|
||||
pageNumbers: 'all',
|
||||
imageOptions: {
|
||||
colorType: 'grayscale',
|
||||
dpi: 150,
|
||||
@ -239,7 +274,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -247,7 +294,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
});
|
||||
|
||||
// Verify integration: hook parameters → FormData → axios call → hook state
|
||||
const formDataCall = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
const formDataCall = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
||||
expect(formDataCall.get('imageFormat')).toBe('jpg');
|
||||
expect(formDataCall.get('colorType')).toBe('grayscale');
|
||||
expect(formDataCall.get('dpi')).toBe('150');
|
||||
@ -262,7 +309,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
|
||||
test('should make correct API call for PDF to CSV conversion with simplified workflow', async () => {
|
||||
const mockBlob = new Blob(['fake-csv-data'], { type: 'text/csv' });
|
||||
mockedAxios.post.mockResolvedValueOnce({
|
||||
(mockedAxios.post as Mock).mockResolvedValueOnce({
|
||||
data: mockBlob,
|
||||
status: 200,
|
||||
statusText: 'OK'
|
||||
@ -285,7 +332,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -300,7 +359,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
);
|
||||
|
||||
// Verify FormData contains correct parameters for simplified CSV conversion
|
||||
const formDataCall = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
const formDataCall = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
||||
expect(formDataCall.get('pageNumbers')).toBe('all'); // Always "all" for simplified workflow
|
||||
expect(formDataCall.get('fileInput')).toBe(testFile);
|
||||
|
||||
@ -329,7 +388,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -348,7 +419,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
|
||||
test('should handle multiple file uploads correctly', async () => {
|
||||
const mockBlob = new Blob(['zip-content'], { type: 'application/zip' });
|
||||
mockedAxios.post.mockResolvedValueOnce({ data: mockBlob });
|
||||
(mockedAxios.post as Mock).mockResolvedValueOnce({ data: mockBlob });
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
@ -369,7 +440,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -377,14 +460,14 @@ describe('Convert Tool Integration Tests', () => {
|
||||
});
|
||||
|
||||
// Verify both files were uploaded
|
||||
const calls = mockedAxios.post.mock.calls;
|
||||
const calls = (mockedAxios.post as Mock).mock.calls;
|
||||
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
const formData = calls[i][1] as FormData;
|
||||
const fileInputs = formData.getAll('fileInput');
|
||||
expect(fileInputs).toHaveLength(1);
|
||||
expect(fileInputs[0]).toBeInstanceOf(File);
|
||||
expect(fileInputs[0].name).toBe(files[i].name);
|
||||
expect((fileInputs[0] as File).name).toBe(files[i].name);
|
||||
}
|
||||
|
||||
});
|
||||
@ -406,7 +489,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -421,7 +516,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
describe('Error Boundary Integration', () => {
|
||||
|
||||
test('should handle corrupted file gracefully', async () => {
|
||||
mockedAxios.post.mockRejectedValueOnce({
|
||||
(mockedAxios.post as Mock).mockRejectedValueOnce({
|
||||
response: {
|
||||
status: 422,
|
||||
data: 'Processing failed'
|
||||
@ -445,7 +540,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -457,7 +564,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
});
|
||||
|
||||
test('should handle backend service unavailable', async () => {
|
||||
mockedAxios.post.mockRejectedValueOnce({
|
||||
(mockedAxios.post as Mock).mockRejectedValueOnce({
|
||||
response: {
|
||||
status: 503,
|
||||
data: 'Service unavailable'
|
||||
@ -481,7 +588,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -497,7 +616,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
|
||||
test('should record operation in FileContext', async () => {
|
||||
const mockBlob = new Blob(['fake-data'], { type: 'image/png' });
|
||||
mockedAxios.post.mockResolvedValueOnce({
|
||||
(mockedAxios.post as Mock).mockResolvedValueOnce({
|
||||
data: mockBlob,
|
||||
status: 200,
|
||||
headers: {
|
||||
@ -523,7 +642,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
@ -538,7 +669,7 @@ describe('Convert Tool Integration Tests', () => {
|
||||
|
||||
test('should clean up blob URLs on reset', async () => {
|
||||
const mockBlob = new Blob(['fake-data'], { type: 'image/png' });
|
||||
mockedAxios.post.mockResolvedValueOnce({
|
||||
(mockedAxios.post as Mock).mockResolvedValueOnce({
|
||||
data: mockBlob,
|
||||
status: 200,
|
||||
headers: {
|
||||
@ -564,7 +695,19 @@ describe('Convert Tool Integration Tests', () => {
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
smartDetectionType: 'none',
|
||||
htmlOptions: {
|
||||
zoomLevel: 0
|
||||
},
|
||||
emailOptions: {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 0,
|
||||
downloadHtml: false,
|
||||
includeAllRecipients: false
|
||||
},
|
||||
pdfaOptions: {
|
||||
outputFormat: ''
|
||||
}
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { describe, test, expect, vi, beforeEach, afterEach, Mock } from 'vitest';
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useConvertOperation } from '../../hooks/tools/convert/useConvertOperation';
|
||||
import { useConvertParameters } from '../../hooks/tools/convert/useConvertParameters';
|
||||
@ -59,7 +59,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Mock successful API response
|
||||
mockedAxios.post.mockResolvedValue({
|
||||
(mockedAxios.post as Mock).mockResolvedValue({
|
||||
data: new Blob(['fake converted content'], { type: 'application/pdf' })
|
||||
});
|
||||
});
|
||||
@ -186,7 +186,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
|
||||
});
|
||||
|
||||
// Should send all files in single request
|
||||
const formData = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
||||
const files = formData.getAll('fileInput');
|
||||
expect(files).toHaveLength(3);
|
||||
});
|
||||
@ -304,7 +304,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
const formData = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
||||
expect(formData.get('zoom')).toBe('1.5');
|
||||
});
|
||||
|
||||
@ -338,7 +338,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
const formData = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
||||
expect(formData.get('includeAttachments')).toBe('false');
|
||||
expect(formData.get('maxAttachmentSizeMB')).toBe('20');
|
||||
expect(formData.get('downloadHtml')).toBe('true');
|
||||
@ -372,7 +372,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
const formData = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
||||
expect(formData.get('outputFormat')).toBe('pdfa');
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/pdf/pdfa', expect.any(FormData), {
|
||||
responseType: 'blob'
|
||||
@ -416,7 +416,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
const formData = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
||||
expect(formData.get('fitOption')).toBe('fitToPage');
|
||||
expect(formData.get('colorType')).toBe('grayscale');
|
||||
expect(formData.get('autoRotate')).toBe('false');
|
||||
@ -470,7 +470,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
|
||||
});
|
||||
|
||||
// Mock one success, one failure
|
||||
mockedAxios.post
|
||||
(mockedAxios.post as Mock)
|
||||
.mockResolvedValueOnce({
|
||||
data: new Blob(['converted1'], { type: 'application/pdf' })
|
||||
})
|
||||
|
@ -64,14 +64,6 @@ export const mantineTheme = createTheme({
|
||||
xl: 'var(--shadow-xl)',
|
||||
},
|
||||
|
||||
// Font weights
|
||||
fontWeights: {
|
||||
normal: 'var(--font-weight-normal)',
|
||||
medium: 'var(--font-weight-medium)',
|
||||
semibold: 'var(--font-weight-semibold)',
|
||||
bold: 'var(--font-weight-bold)',
|
||||
},
|
||||
|
||||
// Component customizations
|
||||
components: {
|
||||
Button: {
|
||||
@ -83,7 +75,7 @@ export const mantineTheme = createTheme({
|
||||
},
|
||||
variants: {
|
||||
// Custom button variant for PDF tools
|
||||
pdfTool: (theme) => ({
|
||||
pdfTool: (theme: any) => ({
|
||||
root: {
|
||||
backgroundColor: 'var(--bg-surface)',
|
||||
border: '1px solid var(--border-default)',
|
||||
@ -95,7 +87,7 @@ export const mantineTheme = createTheme({
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
|
||||
Paper: {
|
||||
styles: {
|
||||
@ -287,28 +279,4 @@ export const mantineTheme = createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Global styles
|
||||
globalStyles: () => ({
|
||||
// Ensure smooth color transitions
|
||||
'*': {
|
||||
transition: 'background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease',
|
||||
},
|
||||
|
||||
// Custom scrollbar styling
|
||||
'*::-webkit-scrollbar': {
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
},
|
||||
'*::-webkit-scrollbar-track': {
|
||||
backgroundColor: 'var(--bg-muted)',
|
||||
},
|
||||
'*::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: 'var(--border-strong)',
|
||||
borderRadius: 'var(--radius-md)',
|
||||
},
|
||||
'*::-webkit-scrollbar-thumb:hover': {
|
||||
backgroundColor: 'var(--color-primary-500)',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
@ -5,7 +5,11 @@
|
||||
import { ProcessedFile } from './processing';
|
||||
import { PDFDocument, PDFPage, PageOperation } from './pageEditor';
|
||||
|
||||
export type ModeType = 'viewer' | 'pageEditor' | 'fileEditor' | 'merge' | 'split' | 'compress' | 'ocr';
|
||||
export type ModeType = 'viewer' | 'pageEditor' | 'fileEditor' | 'merge' | 'split' | 'compress' | 'ocr' | 'convert' | 'sanitize';
|
||||
|
||||
export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor';
|
||||
|
||||
export type ToolType = 'merge' | 'split' | 'compress' | 'ocr' | 'convert' | 'sanitize';
|
||||
|
||||
export type OperationType = 'merge' | 'split' | 'compress' | 'add' | 'remove' | 'replace' | 'convert' | 'upload' | 'ocr';
|
||||
|
||||
@ -54,6 +58,8 @@ export interface FileContextState {
|
||||
|
||||
// Current navigation state
|
||||
currentMode: ModeType;
|
||||
currentView: ViewType;
|
||||
currentTool: ToolType | null;
|
||||
|
||||
// Edit history and state
|
||||
fileEditHistory: Map<string, FileEditHistory>;
|
||||
@ -85,13 +91,15 @@ export interface FileContextState {
|
||||
|
||||
export interface FileContextActions {
|
||||
// File management
|
||||
addFiles: (files: File[]) => Promise<void>;
|
||||
addFiles: (files: File[]) => Promise<File[]>;
|
||||
removeFiles: (fileIds: string[], deleteFromStorage?: boolean) => void;
|
||||
replaceFile: (oldFileId: string, newFile: File) => Promise<void>;
|
||||
clearAllFiles: () => void;
|
||||
|
||||
// Navigation
|
||||
setCurrentMode: (mode: ModeType) => void;
|
||||
setCurrentView: (view: ViewType) => void;
|
||||
setCurrentTool: (tool: ToolType) => void;
|
||||
// Selection management
|
||||
setSelectedFiles: (fileIds: string[]) => void;
|
||||
setSelectedPages: (pageNumbers: number[]) => void;
|
||||
|
@ -13,6 +13,7 @@ export interface PDFDocument {
|
||||
file: File;
|
||||
pages: PDFPage[];
|
||||
totalPages: number;
|
||||
destroy?: () => void;
|
||||
}
|
||||
|
||||
export interface PageOperation {
|
||||
@ -43,7 +44,7 @@ export interface PageEditorFunctions {
|
||||
handleRedo: () => void;
|
||||
canUndo: boolean;
|
||||
canRedo: boolean;
|
||||
handleRotate: () => void;
|
||||
handleRotate: (direction: 'left' | 'right') => void;
|
||||
handleDelete: () => void;
|
||||
handleSplit: () => void;
|
||||
onExportSelected: () => void;
|
||||
|
@ -35,6 +35,11 @@ export interface ToolResult {
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface ToolConfiguration {
|
||||
maxFiles: number;
|
||||
supportedFormats?: string[];
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
id: string;
|
||||
name: string;
|
||||
|
@ -49,12 +49,16 @@ export function createEnhancedFileFromStored(storedFile: StoredFile, thumbnail?:
|
||||
size: storedFile.size,
|
||||
type: storedFile.type,
|
||||
lastModified: storedFile.lastModified,
|
||||
webkitRelativePath: '',
|
||||
// Lazy-loading File interface methods
|
||||
arrayBuffer: async () => {
|
||||
const data = await fileStorage.getFileData(storedFile.id);
|
||||
if (!data) throw new Error(`File ${storedFile.name} not found in IndexedDB - may have been purged`);
|
||||
return data;
|
||||
},
|
||||
bytes: async () => {
|
||||
return new Uint8Array();
|
||||
},
|
||||
slice: (start?: number, end?: number, contentType?: string) => {
|
||||
// Return a promise-based slice that loads from IndexedDB
|
||||
return new Blob([], { type: contentType || storedFile.type });
|
||||
@ -66,7 +70,7 @@ export function createEnhancedFileFromStored(storedFile: StoredFile, thumbnail?:
|
||||
const data = await fileStorage.getFileData(storedFile.id);
|
||||
if (!data) throw new Error(`File ${storedFile.name} not found in IndexedDB - may have been purged`);
|
||||
return new TextDecoder().decode(data);
|
||||
}
|
||||
},
|
||||
} as FileWithUrl;
|
||||
|
||||
return enhancedFile;
|
||||
@ -93,7 +97,7 @@ export async function loadFilesFromIndexedDB(): Promise<FileWithUrl[]> {
|
||||
})
|
||||
.map(storedFile => {
|
||||
try {
|
||||
return createEnhancedFileFromStored(storedFile);
|
||||
return createEnhancedFileFromStored(storedFile as any);
|
||||
} catch (error) {
|
||||
console.error('Failed to restore file:', storedFile?.name || 'unknown', error);
|
||||
return null;
|
||||
|
@ -183,13 +183,11 @@ export async function generateThumbnailForFile(file: File): Promise<string | und
|
||||
return generatePlaceholderThumbnail(file);
|
||||
}
|
||||
|
||||
// Calculate quality scale based on file size
|
||||
console.log('Generating thumbnail for', file.name);
|
||||
const scale = calculateScaleFromFileSize(file.size);
|
||||
console.log(`Using scale ${scale} for ${file.name} (${(file.size / 1024 / 1024).toFixed(1)}MB)`);
|
||||
try {
|
||||
console.log('Generating thumbnail for', file.name);
|
||||
|
||||
// Calculate quality scale based on file size
|
||||
const scale = calculateScaleFromFileSize(file.size);
|
||||
console.log(`Using scale ${scale} for ${file.name} (${(file.size / 1024 / 1024).toFixed(1)}MB)`);
|
||||
|
||||
// Only read first 2MB for thumbnail generation to save memory
|
||||
const chunkSize = 2 * 1024 * 1024; // 2MB
|
||||
const chunk = file.slice(0, Math.min(chunkSize, file.size));
|
||||
|
@ -22,7 +22,7 @@ export const createOperation = <TParams = void>(
|
||||
parameters: params,
|
||||
fileSize: selectedFiles.reduce((sum, f) => sum + f.size, 0)
|
||||
}
|
||||
};
|
||||
} as any /* FIX ME*/;
|
||||
|
||||
return { operation, operationId, fileId };
|
||||
};
|
Loading…
Reference in New Issue
Block a user