mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
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:
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ const FileEditorThumbnail = ({
|
||||
selectedFiles,
|
||||
onToggleFile,
|
||||
onDeleteFile,
|
||||
onViewFile,
|
||||
onSetStatus,
|
||||
onReorderFiles,
|
||||
onDownloadFile,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
+2
-6
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user