Enable ESLint no-unused-vars rule (#4367)

# Description of Changes
Enable ESLint [no-unused-vars
rule](https://typescript-eslint.io/rules/no-unused-vars/)
This commit is contained in:
James Brunton 2025-09-05 12:16:17 +01:00 committed by GitHub
parent 87c63efcec
commit bd13f6bf57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
102 changed files with 303 additions and 825 deletions

View File

@ -25,7 +25,18 @@ export default defineConfig(
],
"@typescript-eslint/no-explicit-any": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-require-imports": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-unused-vars": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-unused-vars": [
"error",
{
"args": "all", // All function args must be used (or explicitly ignored)
"argsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
"caughtErrors": "all", // Caught errors must be used (or explicitly ignored)
"caughtErrorsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
"destructuredArrayIgnorePattern": "^_", // Allow unused variables beginning with an underscore
"varsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
"ignoreRestSiblings": true, // Allow unused variables when removing attributes from objects (otherwise this requires explicit renaming like `({ x: _x, ...y }) => y`, which is clunky)
},
],
},
}
);

View File

@ -107,7 +107,7 @@ async function main() {
needsRegeneration = false;
info(`✅ Icon set already up-to-date (${usedIcons.length} icons, ${Math.round(fs.statSync(outputPath).size / 1024)}KB)`);
}
} catch (error) {
} catch {
// If we can't parse existing file, regenerate
needsRegeneration = true;
}

View File

@ -24,7 +24,7 @@ try {
// Install license-checker if not present
try {
require.resolve('license-checker');
} catch (e) {
} catch {
console.log('📦 Installing license-checker...');
execSync('npm install --save-dev license-checker', { stdio: 'inherit' });
}

View File

@ -4,7 +4,6 @@ import { Dropzone } from '@mantine/dropzone';
import { FileMetadata } from '../types/file';
import { useFileManager } from '../hooks/useFileManager';
import { useFilesModalContext } from '../contexts/FilesModalContext';
import { createFileId } from '../types/fileContext';
import { Tool } from '../types/tool';
import MobileLayout from './fileManager/MobileLayout';
import DesktopLayout from './fileManager/DesktopLayout';
@ -21,13 +20,7 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
const [isDragging, setIsDragging] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const { loadRecentFiles, handleRemoveFile, storeFile, convertToFile } = useFileManager();
// Wrapper for storeFile that generates UUID
const storeStirlingFile = useCallback(async (file: File) => {
const fileId = createFileId(); // Generate UUID for storage
return await storeFile(file, fileId);
}, [storeFile]);
const { loadRecentFiles, handleRemoveFile, convertToFile } = useFileManager();
// File management handlers
const isFileSupported = useCallback((fileName: string) => {

View File

@ -1,42 +1,28 @@
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import React, { useState, useCallback, useRef, useMemo } from 'react';
import {
Text, Center, Box, Notification, LoadingOverlay, Stack, Group, Portal
} from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { useTranslation } from 'react-i18next';
import UploadFileIcon from '@mui/icons-material/UploadFile';
import { useFileSelection, useFileState, useFileManagement, useFileActions } from '../../contexts/FileContext';
import { useFileSelection, useFileState, useFileManagement } from '../../contexts/FileContext';
import { useNavigationActions } from '../../contexts/NavigationContext';
import { FileOperation } from '../../types/fileContext';
import { fileStorage } from '../../services/fileStorage';
import { generateThumbnailForFile } from '../../utils/thumbnailUtils';
import { zipFileService } from '../../services/zipFileService';
import { detectFileExtension } from '../../utils/fileUtils';
import styles from './FileEditor.module.css';
import FileEditorThumbnail from './FileEditorThumbnail';
import FilePickerModal from '../shared/FilePickerModal';
import SkeletonLoader from '../shared/SkeletonLoader';
import { FileId, StirlingFile } from '../../types/fileContext';
interface FileEditorProps {
onOpenPageEditor?: (file: StirlingFile) => void;
onOpenPageEditor?: () => void;
onMergeFiles?: (files: StirlingFile[]) => void;
toolMode?: boolean;
showUpload?: boolean;
showBulkActions?: boolean;
supportedExtensions?: string[];
}
const FileEditor = ({
onOpenPageEditor,
onMergeFiles,
toolMode = false,
showUpload = true,
showBulkActions = true,
supportedExtensions = ["pdf"]
}: FileEditorProps) => {
const { t } = useTranslation();
// Utility function to check if a file extension is supported
const isFileSupported = useCallback((fileName: string): boolean => {
@ -49,13 +35,10 @@ const FileEditor = ({
const { addFiles, removeFiles, reorderFiles } = useFileManagement();
// Extract needed values from state (memoized to prevent infinite loops)
const activeFiles = useMemo(() => selectors.getFiles(), [selectors.getFilesSignature()]);
const activeStirlingFileStubs = useMemo(() => selectors.getStirlingFileStubs(), [selectors.getFilesSignature()]);
const selectedFileIds = state.ui.selectedFileIds;
const isProcessing = state.ui.isProcessing;
// Get the real context actions
const { actions } = useFileActions();
// Get navigation actions
const { actions: navActions } = useNavigationActions();
// Get file selection context
@ -161,29 +144,9 @@ const FileEditor = ({
if (extractionResult.success) {
allExtractedFiles.push(...extractionResult.extractedFiles);
// Record ZIP extraction operation
const operationId = `zip-extract-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const operation: FileOperation = {
id: operationId,
type: 'convert',
timestamp: Date.now(),
fileIds: extractionResult.extractedFiles.map(f => f.name) as FileId[] /* FIX ME: This doesn't seem right */,
status: 'pending',
metadata: {
originalFileName: file.name,
outputFileNames: extractionResult.extractedFiles.map(f => f.name),
fileSize: file.size,
parameters: {
extractionType: 'zip',
extractedCount: extractionResult.extractedCount,
totalFiles: extractionResult.totalFiles
}
if (extractionResult.errors.length > 0) {
errors.push(...extractionResult.errors);
}
};
if (extractionResult.errors.length > 0) {
errors.push(...extractionResult.errors);
}
} else {
errors.push(`Failed to extract ZIP file "${file.name}": ${extractionResult.errors.join(', ')}`);
}
@ -213,25 +176,6 @@ const FileEditor = ({
// Process all extracted files
if (allExtractedFiles.length > 0) {
// Record upload operations for PDF files
for (const file of allExtractedFiles) {
const operationId = `upload-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const operation: FileOperation = {
id: operationId,
type: 'upload',
timestamp: Date.now(),
fileIds: [file.name as FileId /* This doesn't seem right */],
status: 'pending',
metadata: {
originalFileName: file.name,
fileSize: file.size,
parameters: {
uploadMethod: 'drag-drop'
}
}
};
}
// Add files to context (they will be processed automatically)
await addFiles(allExtractedFiles);
setStatus(`Added ${allExtractedFiles.length} files`);
@ -252,23 +196,6 @@ const FileEditor = ({
}
}, [addFiles]);
const selectAll = useCallback(() => {
setSelectedFiles(activeStirlingFileStubs.map(r => r.id)); // Use StirlingFileStub IDs directly
}, [activeStirlingFileStubs, setSelectedFiles]);
const deselectAll = useCallback(() => setSelectedFiles([]), [setSelectedFiles]);
const closeAllFiles = useCallback(() => {
if (activeStirlingFileStubs.length === 0) return;
// Remove all files from context but keep in storage
const allFileIds = activeStirlingFileStubs.map(record => record.id);
removeFiles(allFileIds, false); // false = keep in storage
// Clear selections
setSelectedFiles([]);
}, [activeStirlingFileStubs, removeFiles, setSelectedFiles]);
const toggleFile = useCallback((fileId: FileId) => {
const currentSelectedIds = contextSelectedIdsRef.current;
@ -304,15 +231,6 @@ const FileEditor = ({
setSelectedFiles(newSelection);
}, [setSelectedFiles, toolMode, setStatus, activeStirlingFileStubs]);
const toggleSelectionMode = useCallback(() => {
setSelectionMode(prev => {
const newMode = !prev;
if (!newMode) {
setSelectedFiles([]);
}
return newMode;
});
}, [setSelectedFiles]);
// File reordering handler for drag and drop
const handleReorderFiles = useCallback((sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => {
@ -378,27 +296,8 @@ const FileEditor = ({
const file = record ? selectors.getFile(record.id) : null;
if (record && file) {
// Record close operation
const fileName = file.name;
const contextFileId = record.id;
const operationId = `close-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const operation: FileOperation = {
id: operationId,
type: 'remove',
timestamp: Date.now(),
fileIds: [fileName as FileId /* FIX ME: This doesn't seem right */],
status: 'pending',
metadata: {
originalFileName: fileName,
fileSize: record.size,
parameters: {
action: 'close',
reason: 'user_request'
}
}
};
// Remove file from context but keep in storage (close, don't delete)
const contextFileId = record.id;
removeFiles([contextFileId], false);
// Remove from context selections
@ -416,24 +315,6 @@ const FileEditor = ({
}
}, [activeStirlingFileStubs, setSelectedFiles, navActions.setWorkbench]);
const handleMergeFromHere = useCallback((fileId: FileId) => {
const startIndex = activeStirlingFileStubs.findIndex(r => r.id === fileId);
if (startIndex === -1) return;
const recordsToMerge = activeStirlingFileStubs.slice(startIndex);
const filesToMerge = recordsToMerge.map(r => selectors.getFile(r.id)).filter(Boolean) as StirlingFile[];
if (onMergeFiles) {
onMergeFiles(filesToMerge);
}
}, [activeStirlingFileStubs, selectors, onMergeFiles]);
const handleSplitFile = useCallback((fileId: FileId) => {
const file = selectors.getFile(fileId);
if (file && onOpenPageEditor) {
onOpenPageEditor(file);
}
}, [selectors, onOpenPageEditor]);
const handleLoadFromStorage = useCallback(async (selectedFiles: File[]) => {
if (selectedFiles.length === 0) return;

View File

@ -44,7 +44,6 @@ const FileEditorThumbnail = ({
selectedFiles,
onToggleFile,
onDeleteFile,
onViewFile,
onSetStatus,
onReorderFiles,
onDownloadFile,

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import { Stack, Button, Box } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { useIndexedDBThumbnail } from '../../hooks/useIndexedDBThumbnail';
@ -11,27 +11,26 @@ interface FileDetailsProps {
compact?: boolean;
}
const FileDetails: React.FC<FileDetailsProps> = ({
const FileDetails: React.FC<FileDetailsProps> = ({
compact = false
}) => {
const { selectedFiles, onOpenFiles, modalHeight } = useFileManagerContext();
const { t } = useTranslation();
const [currentFileIndex, setCurrentFileIndex] = useState(0);
const [isAnimating, setIsAnimating] = useState(false);
// Get the currently displayed file
const currentFile = selectedFiles.length > 0 ? selectedFiles[currentFileIndex] : null;
const hasSelection = selectedFiles.length > 0;
const hasMultipleFiles = selectedFiles.length > 1;
// Use IndexedDB hook for the current file
const { thumbnail: currentThumbnail } = useIndexedDBThumbnail(currentFile);
// Get thumbnail for current file
const getCurrentThumbnail = () => {
return currentThumbnail;
};
const handlePrevious = () => {
if (isAnimating) return;
setIsAnimating(true);
@ -40,7 +39,7 @@ const FileDetails: React.FC<FileDetailsProps> = ({
setIsAnimating(false);
}, 150);
};
const handleNext = () => {
if (isAnimating) return;
setIsAnimating(true);
@ -49,14 +48,14 @@ const FileDetails: React.FC<FileDetailsProps> = ({
setIsAnimating(false);
}, 150);
};
// Reset index when selection changes
React.useEffect(() => {
if (currentFileIndex >= selectedFiles.length) {
setCurrentFileIndex(0);
}
}, [selectedFiles.length, currentFileIndex]);
if (compact) {
return (
<CompactFileDetails
@ -88,26 +87,26 @@ const FileDetails: React.FC<FileDetailsProps> = ({
onNext={handleNext}
/>
</Box>
{/* Section 2: File Details */}
<FileInfoCard
currentFile={currentFile}
modalHeight={modalHeight}
/>
<Button
size="md"
<Button
size="md"
mb="xl"
onClick={onOpenFiles}
disabled={!hasSelection}
fullWidth
style={{
flexShrink: 0,
backgroundColor: hasSelection ? 'var(--btn-open-file)' : 'var(--mantine-color-gray-4)',
color: 'white'
style={{
flexShrink: 0,
backgroundColor: hasSelection ? 'var(--btn-open-file)' : 'var(--mantine-color-gray-4)',
color: 'white'
}}
>
{selectedFiles.length > 1
{selectedFiles.length > 1
? t('fileManager.openFiles', `Open ${selectedFiles.length} Files`)
: t('fileManager.openFile', 'Open File')
}
@ -116,4 +115,4 @@ const FileDetails: React.FC<FileDetailsProps> = ({
);
};
export default FileDetails;
export default FileDetails;

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Stack, Box } from '@mantine/core';
import { Box } from '@mantine/core';
import FileSourceButtons from './FileSourceButtons';
import FileDetails from './FileDetails';
import SearchInput from './SearchInput';
@ -19,14 +19,14 @@ const MobileLayout: React.FC = () => {
const calculateFileListHeight = () => {
// Base modal height minus padding and gaps
const baseHeight = `calc(${modalHeight} - 2rem)`; // Account for Stack padding
// Estimate heights of fixed components
const fileSourceHeight = '3rem'; // FileSourceButtons height
const fileDetailsHeight = selectedFiles.length > 0 ? '10rem' : '8rem'; // FileDetails compact height
const fileActionsHeight = activeSource === 'recent' ? '3rem' : '0rem'; // FileActions height (now at bottom)
const searchHeight = activeSource === 'recent' ? '3rem' : '0rem'; // SearchInput height
const gapHeight = activeSource === 'recent' ? '3.75rem' : '2rem'; // Stack gaps
return `calc(${baseHeight} - ${fileSourceHeight} - ${fileDetailsHeight} - ${fileActionsHeight} - ${searchHeight} - ${gapHeight})`;
};
@ -36,15 +36,15 @@ const MobileLayout: React.FC = () => {
<Box style={{ flexShrink: 0 }}>
<FileSourceButtons horizontal={true} />
</Box>
<Box style={{ flexShrink: 0 }}>
<FileDetails compact={true} />
</Box>
{/* Section 3 & 4: Search Bar + File List - Unified background extending to modal edge */}
<Box style={{
<Box style={{
flex: 1,
display: 'flex',
display: 'flex',
flexDirection: 'column',
backgroundColor: 'var(--bg-file-list)',
borderRadius: '0.5rem',
@ -54,13 +54,13 @@ const MobileLayout: React.FC = () => {
}}>
{activeSource === 'recent' && (
<>
<Box style={{
<Box style={{
flexShrink: 0,
borderBottom: '1px solid var(--mantine-color-gray-2)'
}}>
<SearchInput />
</Box>
<Box style={{
<Box style={{
flexShrink: 0,
borderBottom: '1px solid var(--mantine-color-gray-2)'
}}>
@ -68,11 +68,11 @@ const MobileLayout: React.FC = () => {
</Box>
</>
)}
<Box style={{ flex: 1, minHeight: 0 }}>
<FileListArea
scrollAreaHeight={calculateFileListHeight()}
scrollAreaStyle={{
scrollAreaStyle={{
height: calculateFileListHeight(),
maxHeight: '60vh',
minHeight: '9.375rem',
@ -83,11 +83,11 @@ const MobileLayout: React.FC = () => {
/>
</Box>
</Box>
{/* Hidden file input for local file selection */}
<HiddenFileInput />
</Box>
);
};
export default MobileLayout;
export default MobileLayout;

View File

@ -1,181 +0,0 @@
import React from 'react';
import {
Stack,
Paper,
Text,
Badge,
Group,
Collapse,
Box,
ScrollArea,
Code,
Divider
} from '@mantine/core';
// FileContext no longer needed - these were stub functions anyway
import { FileOperation, FileOperationHistory as FileOperationHistoryType } from '../../types/fileContext';
import { PageOperation } from '../../types/pageEditor';
import { FileId } from '../../types/file';
interface FileOperationHistoryProps {
fileId: FileId;
showOnlyApplied?: boolean;
maxHeight?: number;
}
const FileOperationHistory: React.FC<FileOperationHistoryProps> = ({
fileId,
showOnlyApplied = false,
maxHeight = 400
}) => {
// These were stub functions in the old context - replace with empty stubs
const getFileHistory = (fileId: FileId) => ({ operations: [], createdAt: Date.now(), lastModified: Date.now() });
const getAppliedOperations = (fileId: FileId) => [];
const history = getFileHistory(fileId);
const allOperations = showOnlyApplied ? getAppliedOperations(fileId) : history?.operations || [];
const operations = allOperations.filter((op: any) => 'fileIds' in op) as FileOperation[];
const formatTimestamp = (timestamp: number) => {
return new Date(timestamp).toLocaleString();
};
const getOperationIcon = (type: string) => {
switch (type) {
case 'split': return '✂️';
case 'merge': return '🔗';
case 'compress': return '🗜️';
case 'rotate': return '🔄';
case 'delete': return '🗑️';
case 'move': return '↕️';
case 'insert': return '📄';
case 'upload': return '⬆️';
case 'add': return '';
case 'remove': return '';
case 'replace': return '🔄';
case 'convert': return '🔄';
default: return '⚙️';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'applied': return 'green';
case 'failed': return 'red';
case 'pending': return 'yellow';
default: return 'gray';
}
};
const renderOperationDetails = (operation: FileOperation) => {
if ('metadata' in operation && operation.metadata) {
const { metadata } = operation;
return (
<Box mt="xs">
{metadata.parameters && (
<Text size="xs" c="dimmed">
Parameters: <Code>{JSON.stringify(metadata.parameters, null, 2)}</Code>
</Text>
)}
{metadata.originalFileName && (
<Text size="xs" c="dimmed">
Original file: {metadata.originalFileName}
</Text>
)}
{metadata.outputFileNames && (
<Text size="xs" c="dimmed">
Output files: {metadata.outputFileNames.join(', ')}
</Text>
)}
{metadata.fileSize && (
<Text size="xs" c="dimmed">
File size: {(metadata.fileSize / 1024 / 1024).toFixed(2)} MB
</Text>
)}
{metadata.pageCount && (
<Text size="xs" c="dimmed">
Pages: {metadata.pageCount}
</Text>
)}
{metadata.error && (
<Text size="xs" c="red">
Error: {metadata.error}
</Text>
)}
</Box>
);
}
return null;
};
if (!history || operations.length === 0) {
return (
<Paper p="md" withBorder>
<Text c="dimmed" ta="center">
{showOnlyApplied ? 'No applied operations found' : 'No operation history available'}
</Text>
</Paper>
);
}
return (
<Paper p="md" withBorder>
<Group justify="space-between" mb="md">
<Text fw={500}>
{showOnlyApplied ? 'Applied Operations' : 'Operation History'}
</Text>
<Badge variant="light" color="blue">
{operations.length} operations
</Badge>
</Group>
<ScrollArea h={maxHeight}>
<Stack gap="sm">
{operations.map((operation, index) => (
<Paper key={operation.id} p="sm" withBorder radius="sm" bg="gray.0">
<Group justify="space-between" align="start">
<Group gap="xs">
<Text span size="lg">
{getOperationIcon(operation.type)}
</Text>
<Box>
<Text fw={500} size="sm">
{operation.type.charAt(0).toUpperCase() + operation.type.slice(1)}
</Text>
<Text size="xs" c="dimmed">
{formatTimestamp(operation.timestamp)}
</Text>
</Box>
</Group>
<Badge
variant="filled"
color={getStatusColor(operation.status)}
size="sm"
>
{operation.status}
</Badge>
</Group>
{renderOperationDetails(operation)}
{index < operations.length - 1 && <Divider mt="sm" />}
</Paper>
))}
</Stack>
</ScrollArea>
{history && (
<Group justify="space-between" mt="sm" pt="sm" style={{ borderTop: '1px solid var(--mantine-color-gray-3)' }}>
<Text size="xs" c="dimmed">
Created: {formatTimestamp(history.createdAt)}
</Text>
<Text size="xs" c="dimmed">
Last modified: {formatTimestamp(history.lastModified)}
</Text>
</Group>
)}
</Paper>
);
};
export default FileOperationHistory;

View File

@ -1,6 +1,4 @@
import React from 'react';
import { Box } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
import { useFileHandler } from '../../hooks/useFileHandler';
@ -19,7 +17,6 @@ import Footer from '../shared/Footer';
// No props needed - component uses contexts directly
export default function Workbench() {
const { t } = useTranslation();
const { isRainbowMode } = useRainbowThemeContext();
// Use context-based hooks to eliminate all prop drilling
@ -78,11 +75,9 @@ export default function Workbench() {
return (
<FileEditor
toolMode={!!selectedToolId}
showUpload={true}
showBulkActions={!selectedToolId}
supportedExtensions={selectedTool?.supportedFormats || ["pdf"]}
{...(!selectedToolId && {
onOpenPageEditor: (file) => {
onOpenPageEditor: () => {
setCurrentView("pageEditor");
},
onMergeFiles: (filesToMerge) => {

View File

@ -1,8 +1,6 @@
import React, { useRef, useEffect, useState, useCallback } from 'react';
import { Box } from '@mantine/core';
import { useVirtualizer } from '@tanstack/react-virtual';
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import styles from './PageEditor.module.css';
import { GRID_CONSTANTS } from './constants';
interface DragDropItem {
@ -22,65 +20,60 @@ interface DragDropGridProps<T extends DragDropItem> {
const DragDropGrid = <T extends DragDropItem>({
items,
selectedItems,
selectionMode,
isAnimating,
onReorderPages,
renderItem,
renderSplitMarker,
}: DragDropGridProps<T>) => {
const itemRefs = useRef<Map<string, HTMLDivElement>>(new Map());
const containerRef = useRef<HTMLDivElement>(null);
// Responsive grid configuration
const [itemsPerRow, setItemsPerRow] = useState(4);
const OVERSCAN = items.length > 1000 ? GRID_CONSTANTS.OVERSCAN_LARGE : GRID_CONSTANTS.OVERSCAN_SMALL;
// Calculate items per row based on container width
const calculateItemsPerRow = useCallback(() => {
if (!containerRef.current) return 4; // Default fallback
const containerWidth = containerRef.current.offsetWidth;
if (containerWidth === 0) return 4; // Container not measured yet
// Convert rem to pixels for calculation
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
const ITEM_WIDTH = parseFloat(GRID_CONSTANTS.ITEM_WIDTH) * remToPx;
const ITEM_GAP = parseFloat(GRID_CONSTANTS.ITEM_GAP) * remToPx;
// Calculate how many items fit: (width - gap) / (itemWidth + gap)
const availableWidth = containerWidth - ITEM_GAP; // Account for first gap
const itemWithGap = ITEM_WIDTH + ITEM_GAP;
const calculated = Math.floor(availableWidth / itemWithGap);
return Math.max(1, calculated); // At least 1 item per row
}, []);
// Update items per row when container resizes
useEffect(() => {
const updateLayout = () => {
const newItemsPerRow = calculateItemsPerRow();
setItemsPerRow(newItemsPerRow);
};
// Initial calculation
updateLayout();
// Listen for window resize
window.addEventListener('resize', updateLayout);
// Use ResizeObserver for container size changes
const resizeObserver = new ResizeObserver(updateLayout);
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
return () => {
window.removeEventListener('resize', updateLayout);
resizeObserver.disconnect();
};
}, [calculateItemsPerRow]);
// Virtualization with react-virtual library
const rowVirtualizer = useVirtualizer({
count: Math.ceil(items.length / itemsPerRow),
@ -92,8 +85,6 @@ const DragDropGrid = <T extends DragDropItem>({
overscan: OVERSCAN,
});
// Calculate optimal width for centering
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
const itemWidth = parseFloat(GRID_CONSTANTS.ITEM_WIDTH) * remToPx;
@ -101,9 +92,9 @@ const DragDropGrid = <T extends DragDropItem>({
const gridWidth = itemsPerRow * itemWidth + (itemsPerRow - 1) * itemGap;
return (
<Box
<Box
ref={containerRef}
style={{
style={{
// Basic container styles
width: '100%',
height: '100%',
@ -122,7 +113,7 @@ const DragDropGrid = <T extends DragDropItem>({
const startIndex = virtualRow.index * itemsPerRow;
const endIndex = Math.min(startIndex + itemsPerRow, items.length);
const rowItems = items.slice(startIndex, endIndex);
return (
<div
key={virtualRow.index}
@ -154,7 +145,7 @@ const DragDropGrid = <T extends DragDropItem>({
</React.Fragment>
);
})}
</div>
</div>
);

View File

@ -1,5 +1,5 @@
import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react';
import { Text, ActionIcon, CheckboxIndicator } from '@mantine/core';
import { ActionIcon, CheckboxIndicator } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
@ -44,7 +44,6 @@ const FileThumbnail = ({
selectedFiles,
onToggleFile,
onDeleteFile,
onViewFile,
onSetStatus,
onReorderFiles,
onDownloadFile,
@ -93,40 +92,6 @@ const FileThumbnail = ({
// ---- Selection ----
const isSelected = selectedFiles.includes(file.id);
// ---- Meta formatting ----
const prettySize = useMemo(() => {
const bytes = file.size ?? 0;
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
}, [file.size]);
const extUpper = useMemo(() => {
const m = /\.([a-z0-9]+)$/i.exec(file.name ?? '');
return (m?.[1] || '').toUpperCase();
}, [file.name]);
const pageLabel = useMemo(
() =>
file.pageCount > 0
? `${file.pageCount} ${file.pageCount === 1 ? 'Page' : 'Pages'}`
: '',
[file.pageCount]
);
const dateLabel = useMemo(() => {
const d =
file.modifiedAt != null ? new Date(file.modifiedAt) : new Date(); // fallback
if (Number.isNaN(d.getTime())) return '';
return new Intl.DateTimeFormat(undefined, {
month: 'short',
day: '2-digit',
year: 'numeric',
}).format(d);
}, [file.modifiedAt]);
// ---- Drag & drop wiring ----
const fileElementRef = useCallback((element: HTMLDivElement | null) => {
if (!element) return;

View File

@ -1,13 +1,7 @@
import React, { useState, useCallback, useRef, useEffect, useMemo } from "react";
import {
Button, Text, Center, Box,
Notification, TextInput, LoadingOverlay, Modal, Alert,
Stack, Group, Portal
} from "@mantine/core";
import { useTranslation } from "react-i18next";
import { useFileState, useFileActions, useCurrentFile, useFileSelection } from "../../contexts/FileContext";
import { PDFDocument, PDFPage, PageEditorFunctions } from "../../types/pageEditor";
import { ProcessedFile as EnhancedProcessedFile } from "../../types/processing";
import { useState, useCallback, useRef, useEffect } from "react";
import { Text, Center, Box, LoadingOverlay, Stack } from "@mantine/core";
import { useFileState, useFileActions } from "../../contexts/FileContext";
import { PDFDocument, PageEditorFunctions } from "../../types/pageEditor";
import { pdfExportService } from "../../services/pdfExportService";
import { documentManipulationService } from "../../services/documentManipulationService";
// Thumbnail generation is now handled by individual PageThumbnail components
@ -19,16 +13,11 @@ import NavigationWarningModal from '../shared/NavigationWarningModal';
import { FileId } from "../../types/file";
import {
DOMCommand,
RotatePageCommand,
DeletePagesCommand,
ReorderPagesCommand,
SplitCommand,
BulkRotateCommand,
BulkSplitCommand,
SplitAllCommand,
PageBreakCommand,
BulkPageBreakCommand,
UndoManager
} from './commands/pageCommands';
import { GRID_CONSTANTS } from './constants';
@ -49,35 +38,24 @@ const PageEditor = ({
// Prefer IDs + selectors to avoid array identity churn
const activeFileIds = state.files.ids;
const primaryFileId = activeFileIds[0] ?? null;
const selectedFiles = selectors.getSelectedFiles();
// Stable signature for effects (prevents loops)
const filesSignature = selectors.getFilesSignature();
// UI state
const globalProcessing = state.ui.isProcessing;
const processingProgress = state.ui.processingProgress;
const hasUnsavedChanges = state.ui.hasUnsavedChanges;
// Edit state management
const [editedDocument, setEditedDocument] = useState<PDFDocument | null>(null);
const [hasUnsavedDraft, setHasUnsavedDraft] = useState(false);
const [showResumeModal, setShowResumeModal] = useState(false);
const [foundDraft, setFoundDraft] = useState<any>(null);
const autoSaveTimer = useRef<number | null>(null);
// DOM-first undo manager (replaces the old React state undo system)
const undoManagerRef = useRef(new UndoManager());
// Document state management
const { document: mergedPdfDocument, isVeryLargeDocument, isLoading: documentLoading } = usePageDocument();
const { document: mergedPdfDocument } = usePageDocument();
// UI state management
const {
selectionMode, selectedPageIds, movingPage, isAnimating, splitPositions, exportLoading,
setSelectionMode, setSelectedPageIds, setMovingPage, setIsAnimating, setSplitPositions, setExportLoading,
setSelectionMode, setSelectedPageIds, setMovingPage, setSplitPositions, setExportLoading,
togglePage, toggleSelectAll, animateReorder
} = usePageEditorState();
@ -146,12 +124,6 @@ const PageEditor = ({
}).filter(id => id !== '');
}, [displayDocument]);
// Convert selectedPageIds to numbers for components that still need numbers
const selectedPageNumbers = useMemo(() =>
getPageNumbersFromIds(selectedPageIds),
[selectedPageIds, getPageNumbersFromIds]
);
// Select all pages by default when document initially loads
const hasInitializedSelection = useRef(false);
useEffect(() => {

View File

@ -1,4 +1,3 @@
import React from "react";
import {
Tooltip,
ActionIcon,
@ -9,9 +8,7 @@ import ContentCutIcon from "@mui/icons-material/ContentCut";
import RotateLeftIcon from "@mui/icons-material/RotateLeft";
import RotateRightIcon from "@mui/icons-material/RotateRight";
import DeleteIcon from "@mui/icons-material/Delete";
import CloseIcon from "@mui/icons-material/Close";
import InsertPageBreakIcon from "@mui/icons-material/InsertPageBreak";
import DownloadIcon from "@mui/icons-material/Download";
interface PageEditorControlsProps {
// Close/Reset functions
@ -46,7 +43,6 @@ interface PageEditorControlsProps {
}
const PageEditorControls = ({
onClosePdf,
onUndo,
onRedo,
canUndo,
@ -54,12 +50,7 @@ const PageEditorControls = ({
onRotate,
onDelete,
onSplit,
onSplitAll,
onPageBreak,
onPageBreakAll,
onExportAll,
exportLoading,
selectionMode,
selectedPageIds,
displayDocument,
splitPositions,

View File

@ -52,16 +52,13 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
pageRefs,
onReorderPages,
onTogglePage,
onAnimateReorder,
onExecuteCommand,
onSetStatus,
onSetMovingPage,
onDeletePage,
createRotateCommand,
createDeleteCommand,
createSplitCommand,
pdfDocument,
setPdfDocument,
splitPositions,
onInsertFiles,
}: PageThumbnailProps) => {
@ -172,7 +169,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
type: 'page',
pageNumber: page.pageNumber
}),
onDrop: ({ source }) => {}
onDrop: (_) => {}
});
(element as any).__dragCleanup = () => {

View File

@ -68,7 +68,6 @@ export function usePageDocument(): PageDocumentHook {
// Build pages by interleaving original pages with insertions
let pages: PDFPage[] = [];
let totalPageCount = 0;
// Helper function to create pages from a file
const createPagesFromFile = (fileId: FileId, startPageNumber: number): PDFPage[] => {
@ -144,8 +143,6 @@ export function usePageDocument(): PageDocumentHook {
});
}
totalPageCount = pages.length;
if (pages.length === 0) {
return null;
}

View File

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { Box, Flex, Group, Text, Button, TextInput, Select, Badge } from "@mantine/core";
import { useState } from "react";
import { Box, Flex, Group, Text, Button, TextInput, Select } from "@mantine/core";
import { useTranslation } from "react-i18next";
import SearchIcon from "@mui/icons-material/Search";
import SortIcon from "@mui/icons-material/Sort";

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Container, Text, Button, Checkbox, Group, useMantineColorScheme } from '@mantine/core';
import { Container, Button, Group, useMantineColorScheme } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import LocalIcon from './LocalIcon';
import { useTranslation } from 'react-i18next';

View File

@ -15,7 +15,6 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal
const { i18n } = useTranslation();
const [opened, setOpened] = useState(false);
const [animationTriggered, setAnimationTriggered] = useState(false);
const [isChanging, setIsChanging] = useState(false);
const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);
const [rippleEffect, setRippleEffect] = useState<{x: number, y: number, key: number} | null>(null);
@ -36,7 +35,6 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal
}
// Start transition animation
setIsChanging(true);
setPendingLanguage(value);
// Simulate processing time for smooth transition
@ -44,7 +42,6 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal
i18n.changeLanguage(value);
setTimeout(() => {
setIsChanging(false);
setPendingLanguage(null);
setOpened(false);
@ -54,7 +51,7 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal
}, 200);
};
const currentLanguage = supportedLanguages[i18n.language as keyof typeof supportedLanguages] ||
const currentLanguage = supportedLanguages[i18n.language as keyof typeof supportedLanguages] ||
supportedLanguages['en-GB'];
// Trigger animation when dropdown opens
@ -77,8 +74,8 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal
}
`}
</style>
<Menu
opened={opened}
<Menu
opened={opened}
onChange={setOpened}
width={600}
position={position}
@ -166,15 +163,15 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal
justifyContent: 'flex-start',
position: 'relative',
overflow: 'hidden',
backgroundColor: option.value === i18n.language
backgroundColor: option.value === i18n.language
? 'light-dark(var(--mantine-color-blue-1), var(--mantine-color-blue-8))'
: 'transparent',
color: option.value === i18n.language
color: option.value === i18n.language
? 'light-dark(var(--mantine-color-blue-9), var(--mantine-color-white))'
: 'light-dark(var(--mantine-color-gray-7), var(--mantine-color-white))',
transition: 'all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
'&:hover': {
backgroundColor: option.value === i18n.language
backgroundColor: option.value === i18n.language
? 'light-dark(var(--mantine-color-blue-2), var(--mantine-color-blue-7))'
: 'light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5))',
transform: 'translateY(-1px)',
@ -223,4 +220,4 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal
);
};
export default LanguageSelector;
export default LanguageSelector;

View File

@ -13,7 +13,7 @@ try {
localIconCount = Object.keys(iconSet.icons || {}).length;
console.info(`✅ Local icons loaded: ${localIconCount} icons (${Math.round(JSON.stringify(iconSet).length / 1024)}KB)`);
}
} catch (error) {
} catch {
console.info(' Local icons not available - using CDN fallback');
}
@ -31,10 +31,10 @@ interface LocalIconProps {
*/
export const LocalIcon: React.FC<LocalIconProps> = ({ icon, ...props }) => {
// Convert our icon naming convention to the local collection format
const iconName = icon.startsWith('material-symbols:')
? icon
const iconName = icon.startsWith('material-symbols:')
? icon
: `material-symbols:${icon}`;
// Development logging (only in dev mode)
if (process.env.NODE_ENV === 'development') {
const logKey = `icon-${iconName}`;
@ -44,9 +44,9 @@ export const LocalIcon: React.FC<LocalIconProps> = ({ icon, ...props }) => {
sessionStorage.setItem(logKey, 'logged');
}
}
// Always render the icon - Iconify will use local if available, CDN if not
return <Icon icon={iconName} {...props} />;
};
export default LocalIcon;
export default LocalIcon;

View File

@ -3,7 +3,6 @@ import { ActionIcon, Stack, Divider } from "@mantine/core";
import { useTranslation } from 'react-i18next';
import LocalIcon from './LocalIcon';
import { useRainbowThemeContext } from "./RainbowThemeProvider";
import AppConfigModal from './AppConfigModal';
import { useIsOverflowing } from '../../hooks/useIsOverflowing';
import { useFilesModalContext } from '../../contexts/FilesModalContext';
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';

View File

@ -29,7 +29,7 @@ export default function RightRail() {
// File state and selection
const { state, selectors } = useFileState();
const { selectedFiles, selectedFileIds, selectedPageNumbers, setSelectedFiles, setSelectedPages } = useFileSelection();
const { selectedFiles, selectedFileIds, setSelectedFiles } = useFileSelection();
const { removeFiles } = useFileManagement();
const activeFiles = selectors.getFiles();

View File

@ -1,10 +1,10 @@
/**
* ActiveToolButton - Shows the currently selected tool at the top of the Quick Access Bar
*
*
* When a user selects a tool from the All Tools list, this component displays the tool's
* icon and name at the top of the navigation bar. It provides a quick way to see which
* tool is currently active and offers a back button to return to the All Tools list.
*
*
* Features:
* - Shows tool icon and name when a tool is selected
* - Hover to reveal back arrow for returning to All Tools
@ -26,7 +26,7 @@ interface ActiveToolButtonProps {
const NAV_IDS = ['read', 'sign', 'automate'];
const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setActiveButton }) => {
const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ setActiveButton }) => {
const { selectedTool, selectedToolKey, leftPanelView, handleBackToTools } = useToolWorkflow();
// Determine if the indicator should be visible (do not require selectedTool to be resolved yet)
@ -38,7 +38,6 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setAc
const [indicatorTool, setIndicatorTool] = useState<typeof selectedTool | null>(null);
const [indicatorVisible, setIndicatorVisible] = useState<boolean>(false);
const [replayAnim, setReplayAnim] = useState<boolean>(false);
const [isAnimating, setIsAnimating] = useState<boolean>(false);
const [isBackHover, setIsBackHover] = useState<boolean>(false);
const prevKeyRef = useRef<string | null>(null);
const collapseTimeoutRef = useRef<number | null>(null);
@ -71,11 +70,9 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setAc
replayRafRef.current = requestAnimationFrame(() => {
setReplayAnim(true);
});
setIsAnimating(true);
prevKeyRef.current = (selectedToolKey as string) || null;
animTimeoutRef.current = window.setTimeout(() => {
setReplayAnim(false);
setIsAnimating(false);
animTimeoutRef.current = null;
}, 500);
}
@ -84,10 +81,8 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setAc
clearTimers();
setIndicatorTool(selectedTool);
setIndicatorVisible(true);
setIsAnimating(true);
prevKeyRef.current = (selectedToolKey as string) || null;
animTimeoutRef.current = window.setTimeout(() => {
setIsAnimating(false);
animTimeoutRef.current = null;
}, 500);
}
@ -95,11 +90,9 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setAc
const triggerCollapse = () => {
clearTimers();
setIndicatorVisible(false);
setIsAnimating(true);
collapseTimeoutRef.current = window.setTimeout(() => {
setIndicatorTool(null);
prevKeyRef.current = null;
setIsAnimating(false);
collapseTimeoutRef.current = null;
}, 500); // match CSS transition duration
}

View File

@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import { Box, Stack, Text } from '@mantine/core';
import React from 'react';
import { Box, Stack } from '@mantine/core';
import { getSubcategoryLabel, ToolRegistryEntry } from '../../data/toolsTaxonomy';
import ToolButton from './toolPicker/ToolButton';
import { useTranslation } from 'react-i18next';
@ -40,12 +40,10 @@ const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }
</Stack>
</Box>
))}
{/* global spacer to allow scrolling past last row in search mode */}
{/* Global spacer to allow scrolling past last row in search mode */}
<div aria-hidden style={{ height: 200 }} />
</Stack>
);
};
export default SearchResults;

View File

@ -1,5 +1,3 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
import ToolPicker from './ToolPicker';
@ -8,12 +6,11 @@ import ToolRenderer from './ToolRenderer';
import ToolSearch from './toolPicker/ToolSearch';
import { useSidebarContext } from "../../contexts/SidebarContext";
import rainbowStyles from '../../styles/rainbow.module.css';
import { Stack, ScrollArea } from '@mantine/core';
import { ScrollArea } from '@mantine/core';
// No props needed - component uses context
export default function ToolPanel() {
const { t } = useTranslation();
const { isRainbowMode } = useRainbowThemeContext();
const { sidebarRefs } = useSidebarContext();
const { toolPanelRef } = sidebarRefs;
@ -27,7 +24,6 @@ export default function ToolPanel() {
filteredTools,
toolRegistry,
setSearchQuery,
handleBackToTools
} = useToolWorkflow();
const { selectedToolKey, handleToolSelect } = useToolWorkflow();

View File

@ -3,7 +3,6 @@ import { render, screen, fireEvent } from '@testing-library/react';
import { MantineProvider } from '@mantine/core';
import AddPasswordSettings from './AddPasswordSettings';
import { defaultParameters } from '../../../hooks/tools/addPassword/useAddPasswordParameters';
import type { AddPasswordParameters } from '../../../hooks/tools/addPassword/useAddPasswordParameters';
// Mock useTranslation with predictable return values
const mockT = vi.fn((key: string) => `mock-${key}`);

View File

@ -1,5 +1,4 @@
import React from "react";
import { Stack, Text, PasswordInput, Select } from "@mantine/core";
import { Stack, PasswordInput, Select } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { AddPasswordParameters } from "../../../hooks/tools/addPassword/useAddPasswordParameters";

View File

@ -1,5 +1,4 @@
import React from "react";
import { Button, Stack, Text } from "@mantine/core";
import { Button, Stack } from "@mantine/core";
import { useTranslation } from "react-i18next";
interface WatermarkTypeSettingsProps {

View File

@ -1,5 +1,5 @@
import React from "react";
import { Stack, Text, TextInput } from "@mantine/core";
import { Stack, TextInput } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { AddWatermarkParameters } from "../../../hooks/tools/addWatermark/useAddWatermarkParameters";
import { removeEmojis } from "../../../utils/textUtils";

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Button,
@ -38,10 +38,8 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
automationIcon,
setAutomationIcon,
selectedTools,
addTool,
removeTool,
updateTool,
hasUnsavedChanges,
canSaveAutomation,
getToolName,
getToolDefaultParameters
@ -84,14 +82,6 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
updateTool(selectedTools.length, newTool);
};
const handleBackClick = () => {
if (hasUnsavedChanges()) {
setUnsavedWarningOpen(true);
} else {
onBack();
}
};
const handleConfirmBack = () => {
setUnsavedWarningOpen(false);
onBack();

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Group, Text, ActionIcon, Menu, Box } from '@mantine/core';
import { Group, Text, ActionIcon, Menu, Box } from '@mantine/core';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
@ -69,11 +69,11 @@ export default function AutomationEntry({
const toolChain = operations.map((op, index) => (
<React.Fragment key={`${op}-${index}`}>
<Text
component="span"
size="sm"
<Text
component="span"
size="sm"
fw={600}
style={{
style={{
color: 'var(--mantine-primary-color-filled)',
background: 'var(--mantine-primary-color-light)',
padding: '2px 6px',
@ -241,12 +241,12 @@ export default function AutomationEntry({
// Show tooltip if there's a description OR operations to display
const shouldShowTooltip = description || operations.length > 0;
return shouldShowTooltip ? (
<Tooltip
content={createTooltipContent()}
position="right"
arrow={true}
<Tooltip
content={createTooltipContent()}
position="right"
arrow={true}
delay={500}
>
{boxContent}

View File

@ -20,11 +20,11 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
const { selectedFiles } = useFileSelection();
const toolRegistry = useFlatToolRegistry();
const cleanup = useResourceCleanup();
// Progress tracking state
const [executionSteps, setExecutionSteps] = useState<ExecutionStep[]>([]);
const [currentStepIndex, setCurrentStepIndex] = useState(-1);
// Use the operation hook's loading state
const isExecuting = automateOperation?.isLoading || false;
const hasResults = automateOperation?.files.length > 0 || automateOperation?.downloadUrl !== null;
@ -74,15 +74,15 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
try {
// Use the automateOperation.executeOperation to handle file consumption properly
await automateOperation.executeOperation(
{
{
automationConfig: automation,
onStepStart: (stepIndex: number, operationName: string) => {
onStepStart: (stepIndex: number, _operationName: string) => {
setCurrentStepIndex(stepIndex);
setExecutionSteps(prev => prev.map((step, idx) =>
idx === stepIndex ? { ...step, status: EXECUTION_STATUS.RUNNING } : step
));
},
onStepComplete: (stepIndex: number, resultFiles: File[]) => {
onStepComplete: (stepIndex: number, _resultFiles: File[]) => {
setExecutionSteps(prev => prev.map((step, idx) =>
idx === stepIndex ? { ...step, status: EXECUTION_STATUS.COMPLETED } : step
));
@ -95,7 +95,7 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
},
selectedFiles
);
// Mark all as completed and reset current step
setCurrentStepIndex(-1);
console.log(`✅ Automation completed successfully`);
@ -118,20 +118,20 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
case EXECUTION_STATUS.ERROR:
return <span style={{ fontSize: 16, color: 'red' }}></span>;
case EXECUTION_STATUS.RUNNING:
return <div style={{
width: 16,
height: 16,
border: '2px solid #ccc',
borderTop: '2px solid #007bff',
return <div style={{
width: 16,
height: 16,
border: '2px solid #ccc',
borderTop: '2px solid #007bff',
borderRadius: '50%',
animation: `spin ${AUTOMATION_CONSTANTS.SPINNER_ANIMATION_DURATION} linear infinite`
animation: `spin ${AUTOMATION_CONSTANTS.SPINNER_ANIMATION_DURATION} linear infinite`
}} />;
default:
return <div style={{
width: 16,
height: 16,
border: '2px solid #ccc',
borderRadius: '50%'
return <div style={{
width: 16,
height: 16,
border: '2px solid #ccc',
borderRadius: '50%'
}} />;
}
};
@ -170,8 +170,8 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
{getStepIcon(step)}
<div style={{ flex: 1 }}>
<Text
size="sm"
<Text
size="sm"
style={{
color: step.status === EXECUTION_STATUS.RUNNING ? 'var(--mantine-color-blue-6)' : 'var(--mantine-color-text)',
fontWeight: step.status === EXECUTION_STATUS.RUNNING ? 500 : 400
@ -220,4 +220,4 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
</style>
</div>
);
}
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
Modal,
@ -32,7 +32,6 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
const { t } = useTranslation();
const [parameters, setParameters] = useState<any>({});
const [isValid, setIsValid] = useState(true);
// Get tool info from registry
const toolInfo = toolRegistry[tool.operation as keyof ToolRegistry];
@ -87,9 +86,7 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
};
const handleSave = () => {
if (isValid) {
onSave(parameters);
}
onSave(parameters);
};
return (
@ -127,7 +124,6 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
<Button
leftSection={<CheckIcon />}
onClick={handleSave}
disabled={!isValid}
>
{t('automate.config.save', 'Save Configuration')}
</Button>

View File

@ -1,4 +1,4 @@
import { Stack, Text, Checkbox } from "@mantine/core";
import { Stack, Checkbox } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ChangePermissionsParameters } from "../../../hooks/tools/changePermissions/useChangePermissionsParameters";

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Stack, Select, Text, Divider } from '@mantine/core';
import { Stack, Select, Divider } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import LanguagePicker from './LanguagePicker';
import { OCRParameters } from '../../../hooks/tools/ocr/useOCRParameters';

View File

@ -8,11 +8,7 @@ interface RemoveCertificateSignSettingsProps {
disabled?: boolean;
}
const RemoveCertificateSignSettings: React.FC<RemoveCertificateSignSettingsProps> = ({
parameters,
onParameterChange, // Unused - kept for interface consistency and future extensibility
disabled = false
}) => {
const RemoveCertificateSignSettings: React.FC<RemoveCertificateSignSettingsProps> = (_) => {
const { t } = useTranslation();
return (
@ -24,4 +20,4 @@ const RemoveCertificateSignSettings: React.FC<RemoveCertificateSignSettingsProps
);
};
export default RemoveCertificateSignSettings;
export default RemoveCertificateSignSettings;

View File

@ -1,4 +1,4 @@
import { Stack, Text, PasswordInput } from "@mantine/core";
import { Stack, PasswordInput } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { RemovePasswordParameters } from "../../../hooks/tools/removePassword/useRemovePasswordParameters";

View File

@ -8,11 +8,7 @@ interface RepairSettingsProps {
disabled?: boolean;
}
const RepairSettings: React.FC<RepairSettingsProps> = ({
parameters,
onParameterChange,
disabled = false
}) => {
const RepairSettings: React.FC<RepairSettingsProps> = (_) => {
const { t } = useTranslation();
return (
@ -24,4 +20,4 @@ const RepairSettings: React.FC<RepairSettingsProps> = ({
);
};
export default RepairSettings;
export default RepairSettings;

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import { Text, Anchor } from "@mantine/core";
import { useTranslation } from "react-i18next";
import FolderIcon from '@mui/icons-material/Folder';
@ -28,7 +28,7 @@ const FileStatusIndicator = ({
try {
const recentFiles = await loadRecentFiles();
setHasRecentFiles(recentFiles.length > 0);
} catch (error) {
} catch {
setHasRecentFiles(false);
}
};

View File

@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from "react";
import { Button, Group, Stack } from "@mantine/core";
import React, { useEffect, useRef } from "react";
import { Button, Stack } from "@mantine/core";
import { useTranslation } from "react-i18next";
import DownloadIcon from "@mui/icons-material/Download";
import UndoIcon from "@mui/icons-material/Undo";

View File

@ -1,5 +1,5 @@
import React, { createContext, useContext, useMemo, useRef } from 'react';
import { Text, Stack, Box, Flex, Divider } from '@mantine/core';
import React, { createContext, useContext, useMemo } from 'react';
import { Text, Stack, Flex, Divider } from '@mantine/core';
import LocalIcon from '../../shared/LocalIcon';
import { Tooltip } from '../../shared/Tooltip';
import { TooltipTip } from '../../../types/tips';

View File

@ -81,7 +81,7 @@ export function createToolFlow(config: ToolFlowConfig) {
})}
{/* Middle Steps */}
{config.steps.map((stepConfig, index) =>
{config.steps.map((stepConfig) =>
steps.create(stepConfig.title, {
isVisible: stepConfig.isVisible,
isCollapsed: stepConfig.isCollapsed,

View File

@ -1,5 +1,4 @@
import React from 'react';
import { Box, Stack } from '@mantine/core';
import { Box } from '@mantine/core';
import ToolButton from '../toolPicker/ToolButton';
import SubcategoryHeader from './SubcategoryHeader';

View File

@ -8,11 +8,7 @@ interface SingleLargePageSettingsProps {
disabled?: boolean;
}
const SingleLargePageSettings: React.FC<SingleLargePageSettingsProps> = ({
parameters,
onParameterChange,
disabled = false
}) => {
const SingleLargePageSettings: React.FC<SingleLargePageSettingsProps> = (_) => {
const { t } = useTranslation();
return (
@ -24,4 +20,4 @@ const SingleLargePageSettings: React.FC<SingleLargePageSettingsProps> = ({
);
};
export default SingleLargePageSettings;
export default SingleLargePageSettings;

View File

@ -8,11 +8,7 @@ interface UnlockPdfFormsSettingsProps {
disabled?: boolean;
}
const UnlockPdfFormsSettings: React.FC<UnlockPdfFormsSettingsProps> = ({
parameters,
onParameterChange, // Unused - kept for interface consistency and future extensibility
disabled = false
}) => {
const UnlockPdfFormsSettings: React.FC<UnlockPdfFormsSettingsProps> = (_) => {
const { t } = useTranslation();
return (
@ -24,4 +20,4 @@ const UnlockPdfFormsSettings: React.FC<UnlockPdfFormsSettingsProps> = ({
);
};
export default UnlockPdfFormsSettings;
export default UnlockPdfFormsSettings;

View File

@ -1,19 +1,17 @@
import React, { useEffect, useState, useRef, useCallback } from "react";
import { Paper, Stack, Text, ScrollArea, Loader, Center, Button, Group, NumberInput, useMantineTheme, ActionIcon, Box, Tabs } from "@mantine/core";
import { Paper, Stack, Text, ScrollArea, Center, Button, Group, NumberInput, useMantineTheme, ActionIcon, Box, Tabs } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { pdfWorkerManager } from "../../services/pdfWorkerManager";
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import FirstPageIcon from "@mui/icons-material/FirstPage";
import LastPageIcon from "@mui/icons-material/LastPage";
import ViewSidebarIcon from "@mui/icons-material/ViewSidebar";
import ViewWeekIcon from "@mui/icons-material/ViewWeek"; // for dual page (book)
import DescriptionIcon from "@mui/icons-material/Description"; // for single page
import CloseIcon from "@mui/icons-material/Close";
import { useLocalStorage } from "@mantine/hooks";
import { fileStorage } from "../../services/fileStorage";
import SkeletonLoader from '../shared/SkeletonLoader';
import { useFileState, useFileActions, useCurrentFile } from "../../contexts/FileContext";
import { useFileState } from "../../contexts/FileContext";
import { useFileWithUrl } from "../../hooks/useFileWithUrl";
import { isFileObject } from "../../types/fileContext";
import { FileId } from "../../types/file";
@ -142,8 +140,6 @@ export interface ViewerProps {
}
const Viewer = ({
sidebarsVisible,
setSidebarsVisible,
onClose,
previewFile,
}: ViewerProps) => {
@ -152,13 +148,7 @@ const Viewer = ({
// Get current file from FileContext
const { selectors } = useFileState();
const { actions } = useFileActions();
const currentFile = useCurrentFile();
const getCurrentFile = () => currentFile.file;
const getCurrentProcessedFile = () => currentFile.record?.processedFile || undefined;
const clearAllFiles = actions.clearAllFiles;
const addFiles = actions.addFiles;
const activeFiles = selectors.getFiles();
// Tab management for multiple files
@ -406,7 +396,7 @@ const Viewer = ({
// Start progressive preloading after a short delay
setTimeout(() => startProgressivePreload(), 100);
}
} catch (error) {
} catch {
if (!cancelled) {
setPageImages([]);
setNumPages(0);

View File

@ -39,7 +39,6 @@ const DEBUG = process.env.NODE_ENV === 'development';
// Inner provider component that has access to IndexedDB
function FileContextInner({
children,
enableUrlSync = true,
enablePersistence = true
}: FileContextProviderProps) {
const [state, dispatch] = useReducer(fileContextReducer, initialFileContextState);
@ -128,20 +127,9 @@ function FileContextInner({
}, [indexedDB]);
const undoConsumeFilesWrapper = useCallback(async (inputFiles: File[], inputStirlingFileStubs: StirlingFileStub[], outputFileIds: FileId[]): Promise<void> => {
return undoConsumeFiles(inputFiles, inputStirlingFileStubs, outputFileIds, stateRef, filesRef, dispatch, indexedDB);
return undoConsumeFiles(inputFiles, inputStirlingFileStubs, outputFileIds, filesRef, dispatch, indexedDB);
}, [indexedDB]);
// Helper to find FileId from File object
const findFileId = useCallback((file: File): FileId | undefined => {
return (Object.keys(stateRef.current.files.byId) as FileId[]).find(id => {
const storedFile = filesRef.current.get(id);
return storedFile &&
storedFile.name === file.name &&
storedFile.size === file.size &&
storedFile.lastModified === file.lastModified;
});
}, []);
// File pinning functions - use StirlingFile directly
const pinFileWrapper = useCallback((file: StirlingFile) => {
baseActions.pinFile(file.fileId);

View File

@ -1,6 +1,6 @@
import React, { createContext, useContext, useState, useRef, useCallback, useEffect, useMemo } from 'react';
import { FileMetadata } from '../types/file';
import { StoredFile, fileStorage } from '../services/fileStorage';
import { fileStorage } from '../services/fileStorage';
import { downloadFiles } from '../utils/downloadUtils';
import { FileId } from '../types/file';

View File

@ -6,7 +6,7 @@
import React, { createContext, useContext, useCallback, useRef } from 'react';
const DEBUG = process.env.NODE_ENV === 'development';
import { fileStorage, StoredFile } from '../services/fileStorage';
import { fileStorage } from '../services/fileStorage';
import { FileId } from '../types/file';
import { FileMetadata } from '../types/file';
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
@ -61,7 +61,7 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) {
const thumbnail = existingThumbnail || await generateThumbnailForFile(file);
// Store in IndexedDB
const storedFile = await fileStorage.storeFile(file, fileId, thumbnail);
await fileStorage.storeFile(file, fileId, thumbnail);
// Cache the file object for immediate reuse
fileCache.current.set(fileId, { file, lastAccessed: Date.now() });

View File

@ -103,7 +103,7 @@ const NavigationActionsContext = createContext<NavigationContextActionsValue | u
export const NavigationProvider: React.FC<{
children: React.ReactNode;
enableUrlSync?: boolean;
}> = ({ children, enableUrlSync = true }) => {
}> = ({ children }) => {
const [state, dispatch] = useReducer(navigationReducer, initialState);
const toolRegistry = useFlatToolRegistry();

View File

@ -89,6 +89,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
clearToolSelection: () => void;
// Tool Reset Actions
toolResetFunctions: Record<string, () => void>;
registerToolReset: (toolId: string, resetFunction: () => void) => void;
resetTool: (toolId: string) => void;
@ -258,6 +259,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
clearToolSelection: () => actions.setSelectedTool(null),
// Tool Reset Actions
toolResetFunctions,
registerToolReset,
resetTool,

View File

@ -13,8 +13,7 @@ import {
import { FileId, FileMetadata } from '../../types/file';
import { generateThumbnailWithMetadata } from '../../utils/thumbnailUtils';
import { FileLifecycleManager } from './lifecycle';
import { fileProcessingService } from '../../services/fileProcessingService';
import { buildQuickKeySet, buildQuickKeySetFromMetadata } from './fileSelectors';
import { buildQuickKeySet } from './fileSelectors';
const DEBUG = process.env.NODE_ENV === 'development';
@ -407,7 +406,6 @@ export async function consumeFiles(
});
if (DEBUG) console.log(`📄 consumeFiles: Successfully consumed files - removed ${inputFileIds.length} inputs, added ${outputStirlingFileStubs.length} outputs`);
// Return the output file IDs for undo tracking
return outputStirlingFileStubs.map(({ fileId }) => fileId);
}
@ -467,7 +465,6 @@ export async function undoConsumeFiles(
inputFiles: File[],
inputStirlingFileStubs: StirlingFileStub[],
outputFileIds: FileId[],
stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.MutableRefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction>,
indexedDB?: { saveFile: (file: File, fileId: FileId, existingThumbnail?: string) => Promise<any>; deleteFile: (fileId: FileId) => Promise<void> } | null
@ -507,7 +504,6 @@ export async function undoConsumeFiles(
});
if (DEBUG) console.log(`📄 undoConsumeFiles: Successfully undone consume operation - restored ${inputStirlingFileStubs.length} inputs, removed ${outputFileIds.length} outputs`);
} catch (error) {
// Rollback filesRef to previous state
if (DEBUG) console.error('📄 undoConsumeFiles: Error during undo, rolling back filesRef', error);

View File

@ -166,9 +166,9 @@ export function useFileContext() {
addFiles: actions.addFiles,
consumeFiles: actions.consumeFiles,
undoConsumeFiles: actions.undoConsumeFiles,
recordOperation: (fileId: FileId, operation: any) => {}, // Operation tracking not implemented
markOperationApplied: (fileId: FileId, operationId: string) => {}, // Operation tracking not implemented
markOperationFailed: (fileId: FileId, operationId: string, error: string) => {}, // Operation tracking not implemented
recordOperation: (_fileId: FileId, _operation: any) => {}, // Operation tracking not implemented
markOperationApplied: (_fileId: FileId, _operationId: string) => {}, // Operation tracking not implemented
markOperationFailed: (_fileId: FileId, _operationId: string, _error: string) => {}, // Operation tracking not implemented
// File ID lookup
findFileId: (file: File) => {

View File

@ -50,7 +50,7 @@ export class FileLifecycleManager {
this.blobUrls.forEach(url => {
try {
URL.revokeObjectURL(url);
} catch (error) {
} catch {
// Ignore revocation errors
}
});
@ -134,7 +134,7 @@ export class FileLifecycleManager {
if (record.thumbnailUrl && record.thumbnailUrl.startsWith('blob:')) {
try {
URL.revokeObjectURL(record.thumbnailUrl);
} catch (error) {
} catch {
// Ignore revocation errors
}
}
@ -142,18 +142,18 @@ export class FileLifecycleManager {
if (record.blobUrl && record.blobUrl.startsWith('blob:')) {
try {
URL.revokeObjectURL(record.blobUrl);
} catch (error) {
} catch {
// Ignore revocation errors
}
}
// Clean up processed file thumbnails
if (record.processedFile?.pages) {
record.processedFile.pages.forEach((page: ProcessedFilePage, index: number) => {
record.processedFile.pages.forEach((page: ProcessedFilePage) => {
if (page.thumbnail && page.thumbnail.startsWith('blob:')) {
try {
URL.revokeObjectURL(page.thumbnail);
} catch (error) {
} catch {
// Ignore revocation errors
}
}

View File

@ -1,7 +1,7 @@
import { describe, expect, test, vi, beforeEach, MockedFunction } from 'vitest';
import { describe, expect, test, vi, beforeEach } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useAddPasswordOperation } from './useAddPasswordOperation';
import type { AddPasswordFullParameters, AddPasswordParameters } from './useAddPasswordParameters';
import type { AddPasswordFullParameters } from './useAddPasswordParameters';
// Mock the useToolOperation hook
vi.mock('../shared/useToolOperation', async () => {

View File

@ -3,7 +3,6 @@ import { useCallback } from 'react';
import { executeAutomationSequence } from '../../../utils/automationExecutor';
import { useFlatToolRegistry } from '../../../data/useTranslatedToolRegistry';
import { AutomateParameters } from '../../../types/automation';
import { AUTOMATION_CONSTANTS } from '../../../constants/automation';
export function useAutomateOperation() {
const toolRegistry = useFlatToolRegistry();

View File

@ -44,9 +44,9 @@ export function useSavedAutomations() {
const copyFromSuggested = useCallback(async (suggestedAutomation: SuggestedAutomation) => {
try {
const { automationStorage } = await import('../../../services/automationStorage');
// Map suggested automation icons to MUI icon keys
const getIconKey = (suggestedIcon: {id: string}): string => {
const getIconKey = (_suggestedIcon: {id: string}): string => {
// Check the automation ID or name to determine the appropriate icon
switch (suggestedAutomation.id) {
case 'secure-pdf-ingestion':
@ -60,7 +60,7 @@ export function useSavedAutomations() {
return 'SettingsIcon'; // Default fallback
}
};
// Convert suggested automation to saved automation format
const savedAutomation = {
name: suggestedAutomation.name,
@ -68,7 +68,7 @@ export function useSavedAutomations() {
icon: getIconKey(suggestedAutomation.icon),
operations: suggestedAutomation.operations
};
await automationStorage.saveAutomation(savedAutomation);
// Refresh the list after saving
refreshAutomations();
@ -91,4 +91,4 @@ export function useSavedAutomations() {
deleteAutomation,
copyFromSuggested
};
}
}

View File

@ -6,7 +6,6 @@ import { SuggestedAutomation } from '../../../types/automation';
// Create icon components
const CompressIcon = () => React.createElement(LocalIcon, { icon: 'compress', width: '1.5rem', height: '1.5rem' });
const TextFieldsIcon = () => React.createElement(LocalIcon, { icon: 'text-fields', width: '1.5rem', height: '1.5rem' });
const SecurityIcon = () => React.createElement(LocalIcon, { icon: 'security', width: '1.5rem', height: '1.5rem' });
const StarIcon = () => React.createElement(LocalIcon, { icon: 'star', width: '1.5rem', height: '1.5rem' });

View File

@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next';
import { useToolOperation, ToolOperationConfig, ToolType } from '../shared/useToolOperation';
import { useToolOperation, ToolType } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { CompressParameters, defaultParameters } from './useCompressParameters';

View File

@ -2,9 +2,8 @@ import { useCallback } from 'react';
import axios from 'axios';
import { useTranslation } from 'react-i18next';
import { ConvertParameters, defaultParameters } from './useConvertParameters';
import { detectFileExtension } from '../../../utils/fileUtils';
import { createFileFromApiResponse } from '../../../utils/fileResponseUtils';
import { useToolOperation, ToolOperationConfig, ToolType } from '../shared/useToolOperation';
import { useToolOperation, ToolType } from '../shared/useToolOperation';
import { getEndpointUrl, isImageFormat, isWebFormat } from '../../../utils/convertUtils';
// Static function that can be used by both the hook and automation executor

View File

@ -2,7 +2,6 @@ import {
COLOR_TYPES,
OUTPUT_OPTIONS,
FIT_OPTIONS,
TO_FORMAT_OPTIONS,
CONVERSION_MATRIX,
type ColorType,
type OutputOption,
@ -127,7 +126,7 @@ export const useConvertParameters = (): ConvertParametersHook => {
endpointName: getEndpointName,
validateFn: validateParameters,
}), []);
const baseHook = useBaseParameters(config);
const getEndpoint = () => {
@ -166,7 +165,7 @@ export const useConvertParameters = (): ConvertParametersHook => {
if (prev.isSmartDetection === false && prev.smartDetectionType === 'none') {
return prev; // No change needed
}
return {
...prev,
isSmartDetection: false,
@ -290,13 +289,13 @@ export const useConvertParameters = (): ConvertParametersHook => {
// All files are images - use image-to-pdf conversion
baseHook.setParameters(prev => {
// Only update if something actually changed
if (prev.isSmartDetection === true &&
prev.smartDetectionType === 'images' &&
prev.fromExtension === 'image' &&
if (prev.isSmartDetection === true &&
prev.smartDetectionType === 'images' &&
prev.fromExtension === 'image' &&
prev.toExtension === 'pdf') {
return prev; // No change needed
}
return {
...prev,
isSmartDetection: true,
@ -309,13 +308,13 @@ export const useConvertParameters = (): ConvertParametersHook => {
// All files are web files - use html-to-pdf conversion
baseHook.setParameters(prev => {
// Only update if something actually changed
if (prev.isSmartDetection === true &&
prev.smartDetectionType === 'web' &&
prev.fromExtension === 'html' &&
if (prev.isSmartDetection === true &&
prev.smartDetectionType === 'web' &&
prev.fromExtension === 'html' &&
prev.toExtension === 'pdf') {
return prev; // No change needed
}
return {
...prev,
isSmartDetection: true,
@ -328,13 +327,13 @@ export const useConvertParameters = (): ConvertParametersHook => {
// Mixed non-image types - use file-to-pdf conversion
baseHook.setParameters(prev => {
// Only update if something actually changed
if (prev.isSmartDetection === true &&
prev.smartDetectionType === 'mixed' &&
prev.fromExtension === 'any' &&
if (prev.isSmartDetection === true &&
prev.smartDetectionType === 'mixed' &&
prev.fromExtension === 'any' &&
prev.toExtension === 'pdf') {
return prev; // No change needed
}
return {
...prev,
isSmartDetection: true,

View File

@ -4,7 +4,7 @@
*/
import { describe, test, expect } from 'vitest';
import { renderHook, act, waitFor } from '@testing-library/react';
import { renderHook, act } from '@testing-library/react';
import { useConvertParameters } from './useConvertParameters';
describe('useConvertParameters - Auto Detection & Smart Conversion', () => {

View File

@ -4,7 +4,7 @@ import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { RemoveCertificateSignParameters, defaultParameters } from './useRemoveCertificateSignParameters';
// Static function that can be used by both the hook and automation executor
export const buildRemoveCertificateSignFormData = (parameters: RemoveCertificateSignParameters, file: File): FormData => {
export const buildRemoveCertificateSignFormData = (_parameters: RemoveCertificateSignParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
return formData;

View File

@ -4,7 +4,7 @@ import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { RepairParameters, defaultParameters } from './useRepairParameters';
// Static function that can be used by both the hook and automation executor
export const buildRepairFormData = (parameters: RepairParameters, file: File): FormData => {
export const buildRepairFormData = (_parameters: RepairParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
return formData;

View File

@ -128,7 +128,7 @@ export const useToolOperation = <TParams>(
config: ToolOperationConfig<TParams>
): ToolOperationHook<TParams> => {
const { t } = useTranslation();
const { addFiles, consumeFiles, undoConsumeFiles, actions: fileActions, selectors } = useFileContext();
const { addFiles, consumeFiles, undoConsumeFiles, selectors } = useFileContext();
// Composed hooks
const { state, actions } = useToolState();
@ -243,7 +243,7 @@ export const useToolOperation = <TParams>(
// Replace input files with processed files (consumeFiles handles pinning)
const inputFileIds: FileId[] = [];
const inputStirlingFileStubs: StirlingFileStub[] = [];
// Build parallel arrays of IDs and records for undo tracking
for (const file of validFiles) {
const fileId = file.fileId;
@ -320,7 +320,7 @@ export const useToolOperation = <TParams>(
try {
// Undo the consume operation
await undoConsumeFiles(inputFiles, inputStirlingFileStubs, outputFileIds);
// Clear results and operation tracking
resetResults();

View File

@ -4,7 +4,7 @@ import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { SingleLargePageParameters, defaultParameters } from './useSingleLargePageParameters';
// Static function that can be used by both the hook and automation executor
export const buildSingleLargePageFormData = (parameters: SingleLargePageParameters, file: File): FormData => {
export const buildSingleLargePageFormData = (_parameters: SingleLargePageParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
return formData;

View File

@ -71,7 +71,7 @@ export const useSplitOperation = () => {
// Custom response handler that extracts ZIP files
// Can't add to exported config because it requires access to the zip code so must be part of the hook
const responseHandler = useCallback(async (blob: Blob, originalFiles: File[]): Promise<File[]> => {
const responseHandler = useCallback(async (blob: Blob, _originalFiles: File[]): Promise<File[]> => {
// Split operations return ZIP files with multiple PDF pages
return await extractZipFiles(blob);
}, [extractZipFiles]);

View File

@ -4,7 +4,7 @@ import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { UnlockPdfFormsParameters, defaultParameters } from './useUnlockPdfFormsParameters';
// Static function that can be used by both the hook and automation executor
export const buildUnlockPdfFormsFormData = (parameters: UnlockPdfFormsParameters, file: File): FormData => {
export const buildUnlockPdfFormsFormData = (_parameters: UnlockPdfFormsParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
return formData;

View File

@ -184,11 +184,6 @@ export const useCookieConsent = ({ analyticsEnabled = false }: CookieConsentConf
// Force show after initialization
setTimeout(() => {
window.CookieConsent.show();
// Debug: Check if modal elements exist
const ccMain = document.getElementById('cc-main');
const consentModal = document.querySelector('.cm-wrapper');
}, 200);
} catch (error) {

View File

@ -19,17 +19,17 @@ export function useEndpointEnabled(endpoint: string): {
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
const response = await fetch(`/api/v1/config/endpoint-enabled?endpoint=${encodeURIComponent(endpoint)}`);
if (!response.ok) {
throw new Error(`Failed to check endpoint: ${response.status} ${response.statusText}`);
}
const isEnabled: boolean = await response.json();
setEnabled(isEnabled);
} catch (err) {
@ -72,27 +72,27 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
// Use batch API for efficiency
const endpointsParam = endpoints.join(',');
const response = await fetch(`/api/v1/config/endpoints-enabled?endpoints=${encodeURIComponent(endpointsParam)}`);
if (!response.ok) {
throw new Error(`Failed to check endpoints: ${response.status} ${response.statusText}`);
}
const statusMap: Record<string, boolean> = await response.json();
setEndpointStatus(statusMap);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
setError(errorMessage);
console.error('Failed to check multiple endpoints:', err);
// Fallback: assume all endpoints are disabled on error
const fallbackStatus = endpoints.reduce((acc, endpoint) => {
acc[endpoint] = false;
@ -105,7 +105,6 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
};
useEffect(() => {
const endpointsKey = endpoints.join(',');
fetchAllEndpointStatuses();
}, [endpoints.join(',')]); // Re-run when endpoints array changes
@ -115,4 +114,4 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
error,
refetch: fetchAllEndpointStatuses,
};
}
}

View File

@ -49,7 +49,7 @@ export function useEnhancedProcessedFiles(
// Process files when activeFiles changes
useEffect(() => {
console.log('useEnhancedProcessedFiles: activeFiles changed', activeFiles.length, 'files');
if (activeFiles.length === 0) {
console.log('useEnhancedProcessedFiles: No active files, clearing processed cache');
setProcessedFiles(new Map());
@ -60,15 +60,15 @@ export function useEnhancedProcessedFiles(
const processFiles = async () => {
const newProcessedFiles = new Map<File, ProcessedFile>();
for (const file of activeFiles) {
// Generate hash for this file
const fileHash = await FileHasher.generateHybridHash(file);
fileHashMapRef.current.set(file, fileHash);
// First, check if we have this exact File object cached
let existing = processedFiles.get(file);
// If not found by File object, try to find by hash in case File was recreated
if (!existing) {
for (const [cachedFile, processed] of processedFiles.entries()) {
@ -79,7 +79,7 @@ export function useEnhancedProcessedFiles(
}
}
}
if (existing) {
newProcessedFiles.set(file, existing);
continue;
@ -94,11 +94,11 @@ export function useEnhancedProcessedFiles(
console.error(`Failed to start processing for ${file.name}:`, error);
}
}
// Only update if the content actually changed
const hasChanged = newProcessedFiles.size !== processedFiles.size ||
Array.from(newProcessedFiles.keys()).some(file => !processedFiles.has(file));
if (hasChanged) {
setProcessedFiles(newProcessedFiles);
}
@ -112,20 +112,20 @@ export function useEnhancedProcessedFiles(
const checkForCompletedFiles = async () => {
let hasNewFiles = false;
const updatedFiles = new Map(processedFiles);
// Generate file keys for all files first
const fileKeyPromises = activeFiles.map(async (file) => ({
file,
key: await FileHasher.generateHybridHash(file)
}));
const fileKeyPairs = await Promise.all(fileKeyPromises);
for (const { file, key } of fileKeyPairs) {
// Only check files that don't have processed results yet
if (!updatedFiles.has(file)) {
const processingState = processingStates.get(key);
// Check for both processing and recently completed files
// This ensures we catch completed files before they're cleaned up
if (processingState?.status === 'processing' || processingState?.status === 'completed') {
@ -135,13 +135,13 @@ export function useEnhancedProcessedFiles(
updatedFiles.set(file, processed);
hasNewFiles = true;
}
} catch (error) {
} catch {
// Ignore errors in completion check
}
}
}
}
if (hasNewFiles) {
setProcessedFiles(updatedFiles);
}
@ -158,11 +158,11 @@ export function useEnhancedProcessedFiles(
const currentFiles = new Set(activeFiles);
const previousFiles = Array.from(processedFiles.keys());
const removedFiles = previousFiles.filter(file => !currentFiles.has(file));
if (removedFiles.length > 0) {
// Clean up processing service cache
enhancedPDFProcessingService.cleanup(removedFiles);
// Update local state
setProcessedFiles(prev => {
const updated = new Map();
@ -179,10 +179,10 @@ export function useEnhancedProcessedFiles(
// Calculate derived state
const isProcessing = processingStates.size > 0;
const hasProcessingErrors = Array.from(processingStates.values()).some(state => state.status === 'error');
// Calculate overall progress
const processingProgress = calculateProcessingProgress(processingStates);
// Get cache stats and metrics
const cacheStats = enhancedPDFProcessingService.getCacheStats();
const metrics = enhancedPDFProcessingService.getMetrics();
@ -192,7 +192,7 @@ export function useEnhancedProcessedFiles(
cancelProcessing: (fileKey: string) => {
enhancedPDFProcessingService.cancelProcessing(fileKey);
},
retryProcessing: async (file: File) => {
try {
await enhancedPDFProcessingService.processFile(file, config);
@ -200,7 +200,7 @@ export function useEnhancedProcessedFiles(
console.error(`Failed to retry processing for ${file.name}:`, error);
}
},
clearCache: () => {
enhancedPDFProcessingService.clearAll();
}
@ -279,7 +279,7 @@ export function useEnhancedProcessedFile(
};
} {
const result = useEnhancedProcessedFiles(file ? [file] : [], config);
const processedFile = file ? result.processedFiles.get(file) || null : null;
// Note: This is async but we can't await in hook return - consider refactoring if needed
const fileKey = file ? '' : '';
@ -309,4 +309,4 @@ export function useEnhancedProcessedFile(
canRetry,
actions
};
}
}

View File

@ -1,7 +1,6 @@
import { useState, useCallback } from 'react';
import { useIndexedDB } from '../contexts/IndexedDBContext';
import { FileMetadata } from '../types/file';
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
import { FileId } from '../types/fileContext';
export const useFileManager = () => {

View File

@ -4,20 +4,6 @@ import { useIndexedDB } from "../contexts/IndexedDBContext";
import { generateThumbnailForFile } from "../utils/thumbnailUtils";
import { FileId } from "../types/fileContext";
/**
* Calculate optimal scale for thumbnail generation
* Ensures high quality while preventing oversized renders
*/
function calculateThumbnailScale(pageViewport: { width: number; height: number }): number {
const maxWidth = 400; // Max thumbnail width
const maxHeight = 600; // Max thumbnail height
const scaleX = maxWidth / pageViewport.width;
const scaleY = maxHeight / pageViewport.height;
// Don't upscale, only downscale if needed
return Math.min(scaleX, scaleY, 1.0);
}
/**
* Hook for IndexedDB-aware thumbnail loading
@ -67,7 +53,7 @@ export function useIndexedDBThumbnail(file: FileMetadata | undefined | null): {
const thumbnail = await generateThumbnailForFile(fileObject);
if (!cancelled) {
setThumb(thumbnail);
// Save thumbnail to IndexedDB for persistence
if (file.id && indexedDB && thumbnail) {
try {

View File

@ -1,5 +1,4 @@
import { useState, useEffect } from 'react';
import * as pdfjsLib from 'pdfjs-dist';
import { pdfWorkerManager } from '../services/pdfWorkerManager';
import { StirlingFile } from '../types/fileContext';
@ -26,7 +25,7 @@ export const usePdfSignatureDetection = (files: StirlingFile[]): PdfSignatureDet
for (const file of files) {
const arrayBuffer = await file.arrayBuffer();
try {
const pdf = await pdfWorkerManager.createDocument(arrayBuffer);
@ -42,7 +41,7 @@ export const usePdfSignatureDetection = (files: StirlingFile[]): PdfSignatureDet
if (foundSignature) break;
}
// Clean up PDF document using worker manager
pdfWorkerManager.destroyDocument(pdf);
} catch (error) {
@ -66,4 +65,4 @@ export const usePdfSignatureDetection = (files: StirlingFile[]): PdfSignatureDet
hasDigitalSignatures,
isChecking
};
};
};

View File

@ -1,4 +1,4 @@
import { useCallback, useRef } from 'react';
import { useCallback } from 'react';
import { thumbnailGenerationService } from '../services/thumbnailGenerationService';
import { createQuickKey } from '../types/fileContext';
import { FileId } from '../types/file';

View File

@ -1,4 +1,4 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { useState, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useFlatToolRegistry } from "../data/useTranslatedToolRegistry";
import { getAllEndpoints, type ToolRegistryEntry } from "../data/toolsTaxonomy";
@ -20,15 +20,6 @@ export const useToolManagement = (): ToolManagementResult => {
// Build endpoints list from registry entries with fallback to legacy mapping
const baseRegistry = useFlatToolRegistry();
const registryDerivedEndpoints = useMemo(() => {
const endpointsByTool: Record<string, string[]> = {};
Object.entries(baseRegistry).forEach(([key, entry]) => {
if (entry.endpoints && entry.endpoints.length > 0) {
endpointsByTool[key] = entry.endpoints;
}
});
return endpointsByTool;
}, [baseRegistry]);
const allEndpoints = useMemo(() => getAllEndpoints(baseRegistry), [baseRegistry]);
const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints);

View File

@ -10,8 +10,8 @@ type ToolParameterValues = Record<string, any>;
* Register tool parameters and get current values
*/
export function useToolParameters(
toolName: string,
parameters: Record<string, any>
_toolName: string,
_parameters: Record<string, any>
): [ToolParameterValues, (updates: Partial<ToolParameterValues>) => void] {
// Return empty values and noop updater
@ -30,9 +30,9 @@ export function useToolParameter<T = any>(
definition: any
): [T, (value: T) => void] {
const [allParams, updateParams] = useToolParameters(toolName, { [paramName]: definition });
const value = allParams[paramName] as T;
const setValue = useCallback((newValue: T) => {
updateParams({ [paramName]: newValue });
}, [paramName, updateParams]);
@ -48,4 +48,4 @@ export function useGlobalParameters() {
const updateParameters = useCallback(() => {}, []);
return [currentValues, updateParameters];
}
}

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useMemo } from 'react';
import { useState, useEffect } from 'react';
import { clamp } from '../utils/genericUtils';
import { getSidebarInfo } from '../utils/sidebarUtils';
import { SidebarRefs, SidebarState } from '../types/sidebar';
@ -65,10 +65,10 @@ export function useTooltipPosition({
sidebarRefs?: SidebarRefs;
sidebarState?: SidebarState;
}): PositionState {
const [coords, setCoords] = useState<{ top: number; left: number; arrowOffset: number | null }>({
top: 0,
left: 0,
arrowOffset: null
const [coords, setCoords] = useState<{ top: number; left: number; arrowOffset: number | null }>({
top: 0,
left: 0,
arrowOffset: null
});
const [positionReady, setPositionReady] = useState(false);
@ -174,4 +174,4 @@ export function useTooltipPosition({
}, [open, sidebarLeft, position, gap, sidebarTooltip]);
return { coords, positionReady };
}
}

View File

@ -1,4 +1,3 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
import { Group } from "@mantine/core";
@ -11,7 +10,6 @@ import Workbench from "../components/layout/Workbench";
import QuickAccessBar from "../components/shared/QuickAccessBar";
import RightRail from "../components/shared/RightRail";
import FileManager from "../components/FileManager";
import Footer from "../components/shared/Footer";
export default function HomePage() {

View File

@ -1,5 +1,4 @@
import * as pdfjsLib from 'pdfjs-dist';
import { ProcessedFile, ProcessingState, PDFPage, ProcessingStrategy, ProcessingConfig, ProcessingMetrics } from '../types/processing';
import { ProcessedFile, ProcessingState, PDFPage, ProcessingConfig, ProcessingMetrics } from '../types/processing';
import { ProcessingCache } from './processingCache';
import { FileHasher } from '../utils/fileHash';
import { FileAnalyzer } from './fileAnalyzer';
@ -355,7 +354,7 @@ export class EnhancedPDFProcessingService {
*/
private async processMetadataOnly(
file: File,
config: ProcessingConfig,
_config: ProcessingConfig,
state: ProcessingState
): Promise<ProcessedFile> {
const arrayBuffer = await file.arrayBuffer();
@ -510,7 +509,7 @@ export class EnhancedPDFProcessingService {
*/
clearAllProcessing(): void {
// Cancel all ongoing processing
this.processing.forEach((state, key) => {
this.processing.forEach((state) => {
if (state.cancellationToken) {
state.cancellationToken.abort();
}

View File

@ -128,7 +128,7 @@ export class FileAnalyzer {
* Estimate processing time based on file characteristics and strategy
*/
private static estimateProcessingTime(
fileSize: number,
_fileSize: number,
pageCount: number = 0,
strategy: ProcessingStrategy
): number {
@ -234,7 +234,7 @@ export class FileAnalyzer {
const headerString = String.fromCharCode(...headerBytes);
return headerString.startsWith('%PDF-');
} catch (error) {
} catch {
return false;
}
}

View File

@ -4,7 +4,6 @@
* Called when files are added to FileContext, before any view sees them
*/
import * as pdfjsLib from 'pdfjs-dist';
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
import { pdfWorkerManager } from './pdfWorkerManager';
import { FileId } from '../types/file';

View File

@ -496,7 +496,7 @@ class FileStorageService {
async updateThumbnail(id: FileId, thumbnail: string): Promise<boolean> {
const db = await this.getDatabase();
return new Promise((resolve, reject) => {
return new Promise((resolve, _reject) => {
try {
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);

View File

@ -31,7 +31,7 @@ export class PDFExportService {
const originalPDFBytes = await pdfDocument.file.arrayBuffer();
const sourceDoc = await PDFLibDocument.load(originalPDFBytes);
const blob = await this.createSingleDocument(sourceDoc, pagesToExport);
const exportFilename = this.generateFilename(filename || pdfDocument.name, selectedOnly, false);
const exportFilename = this.generateFilename(filename || pdfDocument.name);
return { blob, filename: exportFilename };
} catch (error) {
@ -62,7 +62,7 @@ export class PDFExportService {
}
const blob = await this.createMultiSourceDocument(sourceFiles, pagesToExport);
const exportFilename = this.generateFilename(filename || pdfDocument.name, selectedOnly, false);
const exportFilename = this.generateFilename(filename || pdfDocument.name);
return { blob, filename: exportFilename };
} catch (error) {
@ -183,7 +183,7 @@ export class PDFExportService {
/**
* Generate appropriate filename for export
*/
private generateFilename(originalName: string, selectedOnly: boolean, appendSuffix: boolean): string {
private generateFilename(originalName: string): string {
const baseName = originalName.replace(/\.pdf$/i, '');
return `${baseName}.pdf`;
}
@ -210,7 +210,7 @@ export class PDFExportService {
/**
* Download multiple files as a ZIP
*/
async downloadAsZip(blobs: Blob[], filenames: string[], zipFilename: string): Promise<void> {
async downloadAsZip(blobs: Blob[], filenames: string[]): Promise<void> {
blobs.forEach((blob, index) => {
setTimeout(() => {
this.downloadFile(blob, filenames[index]);

View File

@ -93,7 +93,7 @@ class PDFWorkerManager {
if (loadingTask) {
try {
loadingTask.destroy();
} catch (destroyError) {
} catch {
// Ignore errors
}
}
@ -110,7 +110,7 @@ class PDFWorkerManager {
pdf.destroy();
this.activeDocuments.delete(pdf);
this.workerCount = Math.max(0, this.workerCount - 1);
} catch (error) {
} catch {
// Still remove from tracking even if destroy failed
this.activeDocuments.delete(pdf);
this.workerCount = Math.max(0, this.workerCount - 1);
@ -166,7 +166,7 @@ class PDFWorkerManager {
this.activeDocuments.forEach(pdf => {
try {
pdf.destroy();
} catch (error) {
} catch {
// Ignore errors
}
});

View File

@ -277,7 +277,7 @@ export class ZipFileService {
bytes[2] === 0x44 && // D
bytes[3] === 0x46 && // F
bytes[4] === 0x2D; // -
} catch (error) {
} catch {
return false;
}
}
@ -324,7 +324,7 @@ export class ZipFileService {
await zip.loadAsync(file);
// Check if any files are encrypted
for (const [filename, zipEntry] of Object.entries(zip.files)) {
for (const [_filename, zipEntry] of Object.entries(zip.files)) {
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

View File

@ -63,7 +63,7 @@ for (let i = 0; i < 32; i++) {
Object.defineProperty(globalThis, 'crypto', {
value: {
subtle: {
digest: vi.fn().mockImplementation(async (algorithm: string, data: any) => {
digest: vi.fn().mockImplementation(async (_algorithm: string, _data: any) => {
// Always return the mock hash buffer regardless of input
return mockHashBuffer.slice();
}),

View File

@ -17,7 +17,6 @@ import * as fs from 'fs';
// Test configuration
const BASE_URL = process.env.BASE_URL || 'http://localhost:5173';
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8080';
/**
* Resolves test fixture paths dynamically based on current working directory.
@ -266,7 +265,6 @@ async function testConversion(page: Page, conversion: ConversionEndpoint) {
}
// Discover conversions at module level before tests are defined
let allConversions: ConversionEndpoint[] = [];
let availableConversions: ConversionEndpoint[] = [];
let unavailableConversions: ConversionEndpoint[] = [];
@ -275,7 +273,6 @@ let unavailableConversions: ConversionEndpoint[] = [];
try {
availableConversions = await conversionDiscovery.getAvailableConversions();
unavailableConversions = await conversionDiscovery.getUnavailableConversions();
allConversions = [...availableConversions, ...unavailableConversions];
} catch (error) {
console.error('Failed to discover conversions during module load:', error);
}

View File

@ -11,7 +11,7 @@
import React from 'react';
import { describe, test, expect, vi, beforeEach, afterEach, Mock } from 'vitest';
import { renderHook, act, waitFor } from '@testing-library/react';
import { renderHook, act } from '@testing-library/react';
import { useConvertOperation } from '../../hooks/tools/convert/useConvertOperation';
import { ConvertParameters } from '../../hooks/tools/convert/useConvertParameters';
import { FileContextProvider } from '../../contexts/FileContext';
@ -53,10 +53,6 @@ vi.mock('../../services/thumbnailGenerationService', () => ({
}));
// Create realistic test files
const createTestFile = (name: string, content: string, type: string): File => {
return new File([content], name, { type });
};
const createPDFFile = (): StirlingFile => {
const pdfContent = '%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n>>\nendobj\ntrailer\n<<\n/Size 2\n/Root 1 0 R\n>>\nstartxref\n0\n%%EOF';
return createTestStirlingFile('test.pdf', pdfContent, 'application/pdf');

View File

@ -15,7 +15,6 @@ import axios from 'axios';
import { detectFileExtension } from '../../utils/fileUtils';
import { FIT_OPTIONS } from '../../constants/convertConstants';
import { createTestStirlingFile, createTestFilesWithId } from '../utils/testFileHelpers';
import { StirlingFile } from '../../types/fileContext';
// Mock axios
vi.mock('axios');
@ -507,7 +506,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
describe('Real File Extension Detection', () => {
test('should correctly detect various file extensions', async () => {
const { result } = renderHook(() => useConvertParameters(), {
renderHook(() => useConvertParameters(), {
wrapper: TestWrapper
});

View File

@ -75,7 +75,7 @@ export const mantineTheme = createTheme({
},
variants: {
// Custom button variant for PDF tools
pdfTool: (theme: any) => ({
pdfTool: (_theme: any) => ({
root: {
backgroundColor: 'var(--bg-surface)',
border: '1px solid var(--border-default)',
@ -108,7 +108,7 @@ export const mantineTheme = createTheme({
},
},
Textarea: {
styles: (theme: any) => ({
styles: (_theme: any) => ({
input: {
backgroundColor: 'var(--bg-surface)',
borderColor: 'var(--border-default)',
@ -126,7 +126,7 @@ export const mantineTheme = createTheme({
},
TextInput: {
styles: (theme: any) => ({
styles: (_theme: any) => ({
input: {
backgroundColor: 'var(--bg-surface)',
borderColor: 'var(--border-default)',
@ -144,7 +144,7 @@ export const mantineTheme = createTheme({
},
PasswordInput: {
styles: (theme: any) => ({
styles: (_theme: any) => ({
input: {
backgroundColor: 'var(--bg-surface)',
borderColor: 'var(--border-default)',

View File

@ -1,15 +1,14 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileSelection } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
import { useAddPasswordParameters, defaultParameters } from "../hooks/tools/addPassword/useAddPasswordParameters";
import { useAddPasswordParameters } from "../hooks/tools/addPassword/useAddPasswordParameters";
import { useAddPasswordOperation } from "../hooks/tools/addPassword/useAddPasswordOperation";
import { useAddPasswordTips } from "../components/tooltips/useAddPasswordTips";
import { useAddPasswordPermissionsTips } from "../components/tooltips/useAddPasswordPermissionsTips";
@ -17,7 +16,6 @@ import { BaseToolProps, ToolComponent } from "../types/tool";
const AddPassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const [collapsedPermissions, setCollapsedPermissions] = useState(true);

View File

@ -1,8 +1,7 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileSelection } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
@ -25,7 +24,6 @@ import { BaseToolProps, ToolComponent } from "../types/tool";
const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const [collapsedType, setCollapsedType] = useState(false);

View File

@ -1,6 +1,5 @@
import React, { useState, useMemo, useEffect } from "react";
import React, { useState, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { useFileContext } from "../contexts/FileContext";
import { useFileSelection } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";

View File

@ -1,8 +1,7 @@
import React, { useEffect, useRef } from "react";
import { useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileState, useFileSelection } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
@ -15,7 +14,6 @@ import { BaseToolProps, ToolComponent } from "../types/tool";
const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation();
const { selectors } = useFileState();
const { actions } = useNavigationActions();
const activeFiles = selectors.getFiles();
const { selectedFiles } = useFileSelection();
const scrollContainerRef = useRef<HTMLDivElement>(null);

View File

@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileSelection } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";

View File

@ -1,4 +1,3 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import SplitSettings from "../components/tools/split/SplitSettings";

View File

@ -2,19 +2,19 @@
* Type safety declarations to prevent file.name/UUID confusion
*/
import { FileId, StirlingFile, OperationType, FileOperation } from './fileContext';
import { FileId, StirlingFile } from './fileContext';
declare global {
namespace FileIdSafety {
// Mark functions that should never accept file.name as parameters
type SafeFileIdFunction<T extends (...args: any[]) => any> = T extends (...args: infer P) => infer R
type SafeFileIdFunction<T extends (...args: any[]) => any> = T extends (...args: infer P) => infer _R
? P extends readonly [string, ...any[]]
? never // Reject string parameters in first position for FileId functions
: T
: T;
// Mark functions that should only accept StirlingFile, not regular File
type StirlingFileOnlyFunction<T extends (...args: any[]) => any> = T extends (...args: infer P) => infer R
type StirlingFileOnlyFunction<T extends (...args: any[]) => any> = T extends (...args: infer P) => infer _R
? P extends readonly [File, ...any[]]
? never // Reject File parameters in first position for StirlingFile functions
: T
@ -38,7 +38,7 @@ declare module '../contexts/FileContext' {
addFiles: (files: File[], options?: { insertAfterPageId?: string }) => Promise<StirlingFile[]>; // Returns StirlingFile
consumeFiles: (inputFileIds: FileId[], outputFiles: File[]) => Promise<StirlingFile[]>; // Returns StirlingFile
}
export interface StrictFileContextSelectors {
getFile: (id: FileId) => StirlingFile | undefined; // Returns StirlingFile
getFiles: (ids?: FileId[]) => StirlingFile[]; // Returns StirlingFile[]
@ -46,4 +46,4 @@ declare module '../contexts/FileContext' {
}
}
export {};
export {};

View File

@ -1,6 +1,5 @@
import axios from 'axios';
import { ToolRegistry } from '../data/toolsTaxonomy';
import { AutomationConfig, AutomationExecutionCallbacks } from '../types/automation';
import { AUTOMATION_CONSTANTS } from '../constants/automation';
import { AutomationFileProcessor } from './automationFileProcessor';
import { ResourceManager } from './resourceManager';

View File

@ -2,7 +2,7 @@
* File processing utilities specifically for automation workflows
*/
import axios, { AxiosResponse } from 'axios';
import axios from 'axios';
import { zipFileService } from '../services/zipFileService';
import { ResourceManager } from './resourceManager';
import { AUTOMATION_CONSTANTS } from '../constants/automation';

Some files were not shown because too many files have changed in this diff Show More