mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-26 17:52:59 +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:
parent
87c63efcec
commit
bd13f6bf57
@ -25,7 +25,18 @@ export default defineConfig(
|
|||||||
],
|
],
|
||||||
"@typescript-eslint/no-explicit-any": "off", // Temporarily disabled until codebase conformant
|
"@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-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)
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -107,7 +107,7 @@ async function main() {
|
|||||||
needsRegeneration = false;
|
needsRegeneration = false;
|
||||||
info(`✅ Icon set already up-to-date (${usedIcons.length} icons, ${Math.round(fs.statSync(outputPath).size / 1024)}KB)`);
|
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
|
// If we can't parse existing file, regenerate
|
||||||
needsRegeneration = true;
|
needsRegeneration = true;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ try {
|
|||||||
// Install license-checker if not present
|
// Install license-checker if not present
|
||||||
try {
|
try {
|
||||||
require.resolve('license-checker');
|
require.resolve('license-checker');
|
||||||
} catch (e) {
|
} catch {
|
||||||
console.log('📦 Installing license-checker...');
|
console.log('📦 Installing license-checker...');
|
||||||
execSync('npm install --save-dev license-checker', { stdio: 'inherit' });
|
execSync('npm install --save-dev license-checker', { stdio: 'inherit' });
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import { Dropzone } from '@mantine/dropzone';
|
|||||||
import { FileMetadata } from '../types/file';
|
import { FileMetadata } from '../types/file';
|
||||||
import { useFileManager } from '../hooks/useFileManager';
|
import { useFileManager } from '../hooks/useFileManager';
|
||||||
import { useFilesModalContext } from '../contexts/FilesModalContext';
|
import { useFilesModalContext } from '../contexts/FilesModalContext';
|
||||||
import { createFileId } from '../types/fileContext';
|
|
||||||
import { Tool } from '../types/tool';
|
import { Tool } from '../types/tool';
|
||||||
import MobileLayout from './fileManager/MobileLayout';
|
import MobileLayout from './fileManager/MobileLayout';
|
||||||
import DesktopLayout from './fileManager/DesktopLayout';
|
import DesktopLayout from './fileManager/DesktopLayout';
|
||||||
@ -21,13 +20,7 @@ const FileManager: React.FC<FileManagerProps> = ({ selectedTool }) => {
|
|||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
|
|
||||||
const { loadRecentFiles, handleRemoveFile, storeFile, convertToFile } = useFileManager();
|
const { loadRecentFiles, handleRemoveFile, 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]);
|
|
||||||
|
|
||||||
// File management handlers
|
// File management handlers
|
||||||
const isFileSupported = useCallback((fileName: string) => {
|
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 {
|
import {
|
||||||
Text, Center, Box, Notification, LoadingOverlay, Stack, Group, Portal
|
Text, Center, Box, Notification, LoadingOverlay, Stack, Group, Portal
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Dropzone } from '@mantine/dropzone';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useFileSelection, useFileState, useFileManagement } from '../../contexts/FileContext';
|
||||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
|
||||||
import { useFileSelection, useFileState, useFileManagement, useFileActions } from '../../contexts/FileContext';
|
|
||||||
import { useNavigationActions } from '../../contexts/NavigationContext';
|
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 { zipFileService } from '../../services/zipFileService';
|
||||||
import { detectFileExtension } from '../../utils/fileUtils';
|
import { detectFileExtension } from '../../utils/fileUtils';
|
||||||
import styles from './FileEditor.module.css';
|
|
||||||
import FileEditorThumbnail from './FileEditorThumbnail';
|
import FileEditorThumbnail from './FileEditorThumbnail';
|
||||||
import FilePickerModal from '../shared/FilePickerModal';
|
import FilePickerModal from '../shared/FilePickerModal';
|
||||||
import SkeletonLoader from '../shared/SkeletonLoader';
|
import SkeletonLoader from '../shared/SkeletonLoader';
|
||||||
import { FileId, StirlingFile } from '../../types/fileContext';
|
import { FileId, StirlingFile } from '../../types/fileContext';
|
||||||
|
|
||||||
|
|
||||||
interface FileEditorProps {
|
interface FileEditorProps {
|
||||||
onOpenPageEditor?: (file: StirlingFile) => void;
|
onOpenPageEditor?: () => void;
|
||||||
onMergeFiles?: (files: StirlingFile[]) => void;
|
onMergeFiles?: (files: StirlingFile[]) => void;
|
||||||
toolMode?: boolean;
|
toolMode?: boolean;
|
||||||
showUpload?: boolean;
|
|
||||||
showBulkActions?: boolean;
|
|
||||||
supportedExtensions?: string[];
|
supportedExtensions?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileEditor = ({
|
const FileEditor = ({
|
||||||
onOpenPageEditor,
|
|
||||||
onMergeFiles,
|
|
||||||
toolMode = false,
|
toolMode = false,
|
||||||
showUpload = true,
|
|
||||||
showBulkActions = true,
|
|
||||||
supportedExtensions = ["pdf"]
|
supportedExtensions = ["pdf"]
|
||||||
}: FileEditorProps) => {
|
}: FileEditorProps) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
// Utility function to check if a file extension is supported
|
// Utility function to check if a file extension is supported
|
||||||
const isFileSupported = useCallback((fileName: string): boolean => {
|
const isFileSupported = useCallback((fileName: string): boolean => {
|
||||||
@ -49,13 +35,10 @@ const FileEditor = ({
|
|||||||
const { addFiles, removeFiles, reorderFiles } = useFileManagement();
|
const { addFiles, removeFiles, reorderFiles } = useFileManagement();
|
||||||
|
|
||||||
// Extract needed values from state (memoized to prevent infinite loops)
|
// 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 activeStirlingFileStubs = useMemo(() => selectors.getStirlingFileStubs(), [selectors.getFilesSignature()]);
|
||||||
const selectedFileIds = state.ui.selectedFileIds;
|
const selectedFileIds = state.ui.selectedFileIds;
|
||||||
const isProcessing = state.ui.isProcessing;
|
|
||||||
|
|
||||||
// Get the real context actions
|
// Get navigation actions
|
||||||
const { actions } = useFileActions();
|
|
||||||
const { actions: navActions } = useNavigationActions();
|
const { actions: navActions } = useNavigationActions();
|
||||||
|
|
||||||
// Get file selection context
|
// Get file selection context
|
||||||
@ -161,29 +144,9 @@ const FileEditor = ({
|
|||||||
if (extractionResult.success) {
|
if (extractionResult.success) {
|
||||||
allExtractedFiles.push(...extractionResult.extractedFiles);
|
allExtractedFiles.push(...extractionResult.extractedFiles);
|
||||||
|
|
||||||
// Record ZIP extraction operation
|
if (extractionResult.errors.length > 0) {
|
||||||
const operationId = `zip-extract-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
errors.push(...extractionResult.errors);
|
||||||
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);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
errors.push(`Failed to extract ZIP file "${file.name}": ${extractionResult.errors.join(', ')}`);
|
errors.push(`Failed to extract ZIP file "${file.name}": ${extractionResult.errors.join(', ')}`);
|
||||||
}
|
}
|
||||||
@ -213,25 +176,6 @@ const FileEditor = ({
|
|||||||
|
|
||||||
// Process all extracted files
|
// Process all extracted files
|
||||||
if (allExtractedFiles.length > 0) {
|
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)
|
// Add files to context (they will be processed automatically)
|
||||||
await addFiles(allExtractedFiles);
|
await addFiles(allExtractedFiles);
|
||||||
setStatus(`Added ${allExtractedFiles.length} files`);
|
setStatus(`Added ${allExtractedFiles.length} files`);
|
||||||
@ -252,23 +196,6 @@ const FileEditor = ({
|
|||||||
}
|
}
|
||||||
}, [addFiles]);
|
}, [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 toggleFile = useCallback((fileId: FileId) => {
|
||||||
const currentSelectedIds = contextSelectedIdsRef.current;
|
const currentSelectedIds = contextSelectedIdsRef.current;
|
||||||
|
|
||||||
@ -304,15 +231,6 @@ const FileEditor = ({
|
|||||||
setSelectedFiles(newSelection);
|
setSelectedFiles(newSelection);
|
||||||
}, [setSelectedFiles, toolMode, setStatus, activeStirlingFileStubs]);
|
}, [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
|
// File reordering handler for drag and drop
|
||||||
const handleReorderFiles = useCallback((sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => {
|
const handleReorderFiles = useCallback((sourceFileId: FileId, targetFileId: FileId, selectedFileIds: FileId[]) => {
|
||||||
@ -378,27 +296,8 @@ const FileEditor = ({
|
|||||||
const file = record ? selectors.getFile(record.id) : null;
|
const file = record ? selectors.getFile(record.id) : null;
|
||||||
|
|
||||||
if (record && file) {
|
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)
|
// Remove file from context but keep in storage (close, don't delete)
|
||||||
|
const contextFileId = record.id;
|
||||||
removeFiles([contextFileId], false);
|
removeFiles([contextFileId], false);
|
||||||
|
|
||||||
// Remove from context selections
|
// Remove from context selections
|
||||||
@ -416,24 +315,6 @@ const FileEditor = ({
|
|||||||
}
|
}
|
||||||
}, [activeStirlingFileStubs, setSelectedFiles, navActions.setWorkbench]);
|
}, [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[]) => {
|
const handleLoadFromStorage = useCallback(async (selectedFiles: File[]) => {
|
||||||
if (selectedFiles.length === 0) return;
|
if (selectedFiles.length === 0) return;
|
||||||
|
|
||||||
|
@ -44,7 +44,6 @@ const FileEditorThumbnail = ({
|
|||||||
selectedFiles,
|
selectedFiles,
|
||||||
onToggleFile,
|
onToggleFile,
|
||||||
onDeleteFile,
|
onDeleteFile,
|
||||||
onViewFile,
|
|
||||||
onSetStatus,
|
onSetStatus,
|
||||||
onReorderFiles,
|
onReorderFiles,
|
||||||
onDownloadFile,
|
onDownloadFile,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Stack, Button, Box } from '@mantine/core';
|
import { Stack, Button, Box } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useIndexedDBThumbnail } from '../../hooks/useIndexedDBThumbnail';
|
import { useIndexedDBThumbnail } from '../../hooks/useIndexedDBThumbnail';
|
||||||
@ -22,7 +22,6 @@ const FileDetails: React.FC<FileDetailsProps> = ({
|
|||||||
// Get the currently displayed file
|
// Get the currently displayed file
|
||||||
const currentFile = selectedFiles.length > 0 ? selectedFiles[currentFileIndex] : null;
|
const currentFile = selectedFiles.length > 0 ? selectedFiles[currentFileIndex] : null;
|
||||||
const hasSelection = selectedFiles.length > 0;
|
const hasSelection = selectedFiles.length > 0;
|
||||||
const hasMultipleFiles = selectedFiles.length > 1;
|
|
||||||
|
|
||||||
// Use IndexedDB hook for the current file
|
// Use IndexedDB hook for the current file
|
||||||
const { thumbnail: currentThumbnail } = useIndexedDBThumbnail(currentFile);
|
const { thumbnail: currentThumbnail } = useIndexedDBThumbnail(currentFile);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Stack, Box } from '@mantine/core';
|
import { Box } from '@mantine/core';
|
||||||
import FileSourceButtons from './FileSourceButtons';
|
import FileSourceButtons from './FileSourceButtons';
|
||||||
import FileDetails from './FileDetails';
|
import FileDetails from './FileDetails';
|
||||||
import SearchInput from './SearchInput';
|
import SearchInput from './SearchInput';
|
||||||
|
@ -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 { Box } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
|
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
|
||||||
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
||||||
import { useFileHandler } from '../../hooks/useFileHandler';
|
import { useFileHandler } from '../../hooks/useFileHandler';
|
||||||
@ -19,7 +17,6 @@ import Footer from '../shared/Footer';
|
|||||||
|
|
||||||
// No props needed - component uses contexts directly
|
// No props needed - component uses contexts directly
|
||||||
export default function Workbench() {
|
export default function Workbench() {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { isRainbowMode } = useRainbowThemeContext();
|
const { isRainbowMode } = useRainbowThemeContext();
|
||||||
|
|
||||||
// Use context-based hooks to eliminate all prop drilling
|
// Use context-based hooks to eliminate all prop drilling
|
||||||
@ -78,11 +75,9 @@ export default function Workbench() {
|
|||||||
return (
|
return (
|
||||||
<FileEditor
|
<FileEditor
|
||||||
toolMode={!!selectedToolId}
|
toolMode={!!selectedToolId}
|
||||||
showUpload={true}
|
|
||||||
showBulkActions={!selectedToolId}
|
|
||||||
supportedExtensions={selectedTool?.supportedFormats || ["pdf"]}
|
supportedExtensions={selectedTool?.supportedFormats || ["pdf"]}
|
||||||
{...(!selectedToolId && {
|
{...(!selectedToolId && {
|
||||||
onOpenPageEditor: (file) => {
|
onOpenPageEditor: () => {
|
||||||
setCurrentView("pageEditor");
|
setCurrentView("pageEditor");
|
||||||
},
|
},
|
||||||
onMergeFiles: (filesToMerge) => {
|
onMergeFiles: (filesToMerge) => {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
||||||
import { Box } from '@mantine/core';
|
import { Box } from '@mantine/core';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
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';
|
import { GRID_CONSTANTS } from './constants';
|
||||||
|
|
||||||
interface DragDropItem {
|
interface DragDropItem {
|
||||||
@ -22,12 +20,7 @@ interface DragDropGridProps<T extends DragDropItem> {
|
|||||||
|
|
||||||
const DragDropGrid = <T extends DragDropItem>({
|
const DragDropGrid = <T extends DragDropItem>({
|
||||||
items,
|
items,
|
||||||
selectedItems,
|
|
||||||
selectionMode,
|
|
||||||
isAnimating,
|
|
||||||
onReorderPages,
|
|
||||||
renderItem,
|
renderItem,
|
||||||
renderSplitMarker,
|
|
||||||
}: DragDropGridProps<T>) => {
|
}: DragDropGridProps<T>) => {
|
||||||
const itemRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
const itemRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
@ -92,8 +85,6 @@ const DragDropGrid = <T extends DragDropItem>({
|
|||||||
overscan: OVERSCAN,
|
overscan: OVERSCAN,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Calculate optimal width for centering
|
// Calculate optimal width for centering
|
||||||
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||||
const itemWidth = parseFloat(GRID_CONSTANTS.ITEM_WIDTH) * remToPx;
|
const itemWidth = parseFloat(GRID_CONSTANTS.ITEM_WIDTH) * remToPx;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
|
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
|
||||||
@ -44,7 +44,6 @@ const FileThumbnail = ({
|
|||||||
selectedFiles,
|
selectedFiles,
|
||||||
onToggleFile,
|
onToggleFile,
|
||||||
onDeleteFile,
|
onDeleteFile,
|
||||||
onViewFile,
|
|
||||||
onSetStatus,
|
onSetStatus,
|
||||||
onReorderFiles,
|
onReorderFiles,
|
||||||
onDownloadFile,
|
onDownloadFile,
|
||||||
@ -93,40 +92,6 @@ const FileThumbnail = ({
|
|||||||
// ---- Selection ----
|
// ---- Selection ----
|
||||||
const isSelected = selectedFiles.includes(file.id);
|
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 ----
|
// ---- Drag & drop wiring ----
|
||||||
const fileElementRef = useCallback((element: HTMLDivElement | null) => {
|
const fileElementRef = useCallback((element: HTMLDivElement | null) => {
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
import React, { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
import { useState, useCallback, useRef, useEffect } from "react";
|
||||||
import {
|
import { Text, Center, Box, LoadingOverlay, Stack } from "@mantine/core";
|
||||||
Button, Text, Center, Box,
|
import { useFileState, useFileActions } from "../../contexts/FileContext";
|
||||||
Notification, TextInput, LoadingOverlay, Modal, Alert,
|
import { PDFDocument, PageEditorFunctions } from "../../types/pageEditor";
|
||||||
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 { pdfExportService } from "../../services/pdfExportService";
|
import { pdfExportService } from "../../services/pdfExportService";
|
||||||
import { documentManipulationService } from "../../services/documentManipulationService";
|
import { documentManipulationService } from "../../services/documentManipulationService";
|
||||||
// Thumbnail generation is now handled by individual PageThumbnail components
|
// Thumbnail generation is now handled by individual PageThumbnail components
|
||||||
@ -19,16 +13,11 @@ import NavigationWarningModal from '../shared/NavigationWarningModal';
|
|||||||
import { FileId } from "../../types/file";
|
import { FileId } from "../../types/file";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DOMCommand,
|
|
||||||
RotatePageCommand,
|
|
||||||
DeletePagesCommand,
|
DeletePagesCommand,
|
||||||
ReorderPagesCommand,
|
ReorderPagesCommand,
|
||||||
SplitCommand,
|
SplitCommand,
|
||||||
BulkRotateCommand,
|
BulkRotateCommand,
|
||||||
BulkSplitCommand,
|
|
||||||
SplitAllCommand,
|
|
||||||
PageBreakCommand,
|
PageBreakCommand,
|
||||||
BulkPageBreakCommand,
|
|
||||||
UndoManager
|
UndoManager
|
||||||
} from './commands/pageCommands';
|
} from './commands/pageCommands';
|
||||||
import { GRID_CONSTANTS } from './constants';
|
import { GRID_CONSTANTS } from './constants';
|
||||||
@ -49,35 +38,24 @@ const PageEditor = ({
|
|||||||
|
|
||||||
// Prefer IDs + selectors to avoid array identity churn
|
// Prefer IDs + selectors to avoid array identity churn
|
||||||
const activeFileIds = state.files.ids;
|
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
|
// UI state
|
||||||
const globalProcessing = state.ui.isProcessing;
|
const globalProcessing = state.ui.isProcessing;
|
||||||
const processingProgress = state.ui.processingProgress;
|
|
||||||
const hasUnsavedChanges = state.ui.hasUnsavedChanges;
|
|
||||||
|
|
||||||
// Edit state management
|
// Edit state management
|
||||||
const [editedDocument, setEditedDocument] = useState<PDFDocument | null>(null);
|
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)
|
// DOM-first undo manager (replaces the old React state undo system)
|
||||||
const undoManagerRef = useRef(new UndoManager());
|
const undoManagerRef = useRef(new UndoManager());
|
||||||
|
|
||||||
// Document state management
|
// Document state management
|
||||||
const { document: mergedPdfDocument, isVeryLargeDocument, isLoading: documentLoading } = usePageDocument();
|
const { document: mergedPdfDocument } = usePageDocument();
|
||||||
|
|
||||||
|
|
||||||
// UI state management
|
// UI state management
|
||||||
const {
|
const {
|
||||||
selectionMode, selectedPageIds, movingPage, isAnimating, splitPositions, exportLoading,
|
selectionMode, selectedPageIds, movingPage, isAnimating, splitPositions, exportLoading,
|
||||||
setSelectionMode, setSelectedPageIds, setMovingPage, setIsAnimating, setSplitPositions, setExportLoading,
|
setSelectionMode, setSelectedPageIds, setMovingPage, setSplitPositions, setExportLoading,
|
||||||
togglePage, toggleSelectAll, animateReorder
|
togglePage, toggleSelectAll, animateReorder
|
||||||
} = usePageEditorState();
|
} = usePageEditorState();
|
||||||
|
|
||||||
@ -146,12 +124,6 @@ const PageEditor = ({
|
|||||||
}).filter(id => id !== '');
|
}).filter(id => id !== '');
|
||||||
}, [displayDocument]);
|
}, [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
|
// Select all pages by default when document initially loads
|
||||||
const hasInitializedSelection = useRef(false);
|
const hasInitializedSelection = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
@ -9,9 +8,7 @@ import ContentCutIcon from "@mui/icons-material/ContentCut";
|
|||||||
import RotateLeftIcon from "@mui/icons-material/RotateLeft";
|
import RotateLeftIcon from "@mui/icons-material/RotateLeft";
|
||||||
import RotateRightIcon from "@mui/icons-material/RotateRight";
|
import RotateRightIcon from "@mui/icons-material/RotateRight";
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
|
||||||
import InsertPageBreakIcon from "@mui/icons-material/InsertPageBreak";
|
import InsertPageBreakIcon from "@mui/icons-material/InsertPageBreak";
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
|
||||||
|
|
||||||
interface PageEditorControlsProps {
|
interface PageEditorControlsProps {
|
||||||
// Close/Reset functions
|
// Close/Reset functions
|
||||||
@ -46,7 +43,6 @@ interface PageEditorControlsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PageEditorControls = ({
|
const PageEditorControls = ({
|
||||||
onClosePdf,
|
|
||||||
onUndo,
|
onUndo,
|
||||||
onRedo,
|
onRedo,
|
||||||
canUndo,
|
canUndo,
|
||||||
@ -54,12 +50,7 @@ const PageEditorControls = ({
|
|||||||
onRotate,
|
onRotate,
|
||||||
onDelete,
|
onDelete,
|
||||||
onSplit,
|
onSplit,
|
||||||
onSplitAll,
|
|
||||||
onPageBreak,
|
onPageBreak,
|
||||||
onPageBreakAll,
|
|
||||||
onExportAll,
|
|
||||||
exportLoading,
|
|
||||||
selectionMode,
|
|
||||||
selectedPageIds,
|
selectedPageIds,
|
||||||
displayDocument,
|
displayDocument,
|
||||||
splitPositions,
|
splitPositions,
|
||||||
|
@ -52,16 +52,13 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
|||||||
pageRefs,
|
pageRefs,
|
||||||
onReorderPages,
|
onReorderPages,
|
||||||
onTogglePage,
|
onTogglePage,
|
||||||
onAnimateReorder,
|
|
||||||
onExecuteCommand,
|
onExecuteCommand,
|
||||||
onSetStatus,
|
onSetStatus,
|
||||||
onSetMovingPage,
|
onSetMovingPage,
|
||||||
onDeletePage,
|
onDeletePage,
|
||||||
createRotateCommand,
|
createRotateCommand,
|
||||||
createDeleteCommand,
|
|
||||||
createSplitCommand,
|
createSplitCommand,
|
||||||
pdfDocument,
|
pdfDocument,
|
||||||
setPdfDocument,
|
|
||||||
splitPositions,
|
splitPositions,
|
||||||
onInsertFiles,
|
onInsertFiles,
|
||||||
}: PageThumbnailProps) => {
|
}: PageThumbnailProps) => {
|
||||||
@ -172,7 +169,7 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
|||||||
type: 'page',
|
type: 'page',
|
||||||
pageNumber: page.pageNumber
|
pageNumber: page.pageNumber
|
||||||
}),
|
}),
|
||||||
onDrop: ({ source }) => {}
|
onDrop: (_) => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
(element as any).__dragCleanup = () => {
|
(element as any).__dragCleanup = () => {
|
||||||
|
@ -68,7 +68,6 @@ export function usePageDocument(): PageDocumentHook {
|
|||||||
|
|
||||||
// Build pages by interleaving original pages with insertions
|
// Build pages by interleaving original pages with insertions
|
||||||
let pages: PDFPage[] = [];
|
let pages: PDFPage[] = [];
|
||||||
let totalPageCount = 0;
|
|
||||||
|
|
||||||
// Helper function to create pages from a file
|
// Helper function to create pages from a file
|
||||||
const createPagesFromFile = (fileId: FileId, startPageNumber: number): PDFPage[] => {
|
const createPagesFromFile = (fileId: FileId, startPageNumber: number): PDFPage[] => {
|
||||||
@ -144,8 +143,6 @@ export function usePageDocument(): PageDocumentHook {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
totalPageCount = pages.length;
|
|
||||||
|
|
||||||
if (pages.length === 0) {
|
if (pages.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Box, Flex, Group, Text, Button, TextInput, Select, Badge } from "@mantine/core";
|
import { Box, Flex, Group, Text, Button, TextInput, Select } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import SearchIcon from "@mui/icons-material/Search";
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
import SortIcon from "@mui/icons-material/Sort";
|
import SortIcon from "@mui/icons-material/Sort";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
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 { Dropzone } from '@mantine/dropzone';
|
||||||
import LocalIcon from './LocalIcon';
|
import LocalIcon from './LocalIcon';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -15,7 +15,6 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal
|
|||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const [animationTriggered, setAnimationTriggered] = useState(false);
|
const [animationTriggered, setAnimationTriggered] = useState(false);
|
||||||
const [isChanging, setIsChanging] = useState(false);
|
|
||||||
const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);
|
const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);
|
||||||
const [rippleEffect, setRippleEffect] = useState<{x: number, y: number, key: number} | 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
|
// Start transition animation
|
||||||
setIsChanging(true);
|
|
||||||
setPendingLanguage(value);
|
setPendingLanguage(value);
|
||||||
|
|
||||||
// Simulate processing time for smooth transition
|
// Simulate processing time for smooth transition
|
||||||
@ -44,7 +42,6 @@ const LanguageSelector = ({ position = 'bottom-start', offset = 8, compact = fal
|
|||||||
i18n.changeLanguage(value);
|
i18n.changeLanguage(value);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsChanging(false);
|
|
||||||
setPendingLanguage(null);
|
setPendingLanguage(null);
|
||||||
setOpened(false);
|
setOpened(false);
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ try {
|
|||||||
localIconCount = Object.keys(iconSet.icons || {}).length;
|
localIconCount = Object.keys(iconSet.icons || {}).length;
|
||||||
console.info(`✅ Local icons loaded: ${localIconCount} icons (${Math.round(JSON.stringify(iconSet).length / 1024)}KB)`);
|
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');
|
console.info('ℹ️ Local icons not available - using CDN fallback');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import { ActionIcon, Stack, Divider } from "@mantine/core";
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import LocalIcon from './LocalIcon';
|
import LocalIcon from './LocalIcon';
|
||||||
import { useRainbowThemeContext } from "./RainbowThemeProvider";
|
import { useRainbowThemeContext } from "./RainbowThemeProvider";
|
||||||
import AppConfigModal from './AppConfigModal';
|
|
||||||
import { useIsOverflowing } from '../../hooks/useIsOverflowing';
|
import { useIsOverflowing } from '../../hooks/useIsOverflowing';
|
||||||
import { useFilesModalContext } from '../../contexts/FilesModalContext';
|
import { useFilesModalContext } from '../../contexts/FilesModalContext';
|
||||||
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
||||||
|
@ -29,7 +29,7 @@ export default function RightRail() {
|
|||||||
|
|
||||||
// File state and selection
|
// File state and selection
|
||||||
const { state, selectors } = useFileState();
|
const { state, selectors } = useFileState();
|
||||||
const { selectedFiles, selectedFileIds, selectedPageNumbers, setSelectedFiles, setSelectedPages } = useFileSelection();
|
const { selectedFiles, selectedFileIds, setSelectedFiles } = useFileSelection();
|
||||||
const { removeFiles } = useFileManagement();
|
const { removeFiles } = useFileManagement();
|
||||||
|
|
||||||
const activeFiles = selectors.getFiles();
|
const activeFiles = selectors.getFiles();
|
||||||
|
@ -26,7 +26,7 @@ interface ActiveToolButtonProps {
|
|||||||
|
|
||||||
const NAV_IDS = ['read', 'sign', 'automate'];
|
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();
|
const { selectedTool, selectedToolKey, leftPanelView, handleBackToTools } = useToolWorkflow();
|
||||||
|
|
||||||
// Determine if the indicator should be visible (do not require selectedTool to be resolved yet)
|
// 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 [indicatorTool, setIndicatorTool] = useState<typeof selectedTool | null>(null);
|
||||||
const [indicatorVisible, setIndicatorVisible] = useState<boolean>(false);
|
const [indicatorVisible, setIndicatorVisible] = useState<boolean>(false);
|
||||||
const [replayAnim, setReplayAnim] = useState<boolean>(false);
|
const [replayAnim, setReplayAnim] = useState<boolean>(false);
|
||||||
const [isAnimating, setIsAnimating] = useState<boolean>(false);
|
|
||||||
const [isBackHover, setIsBackHover] = useState<boolean>(false);
|
const [isBackHover, setIsBackHover] = useState<boolean>(false);
|
||||||
const prevKeyRef = useRef<string | null>(null);
|
const prevKeyRef = useRef<string | null>(null);
|
||||||
const collapseTimeoutRef = useRef<number | null>(null);
|
const collapseTimeoutRef = useRef<number | null>(null);
|
||||||
@ -71,11 +70,9 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setAc
|
|||||||
replayRafRef.current = requestAnimationFrame(() => {
|
replayRafRef.current = requestAnimationFrame(() => {
|
||||||
setReplayAnim(true);
|
setReplayAnim(true);
|
||||||
});
|
});
|
||||||
setIsAnimating(true);
|
|
||||||
prevKeyRef.current = (selectedToolKey as string) || null;
|
prevKeyRef.current = (selectedToolKey as string) || null;
|
||||||
animTimeoutRef.current = window.setTimeout(() => {
|
animTimeoutRef.current = window.setTimeout(() => {
|
||||||
setReplayAnim(false);
|
setReplayAnim(false);
|
||||||
setIsAnimating(false);
|
|
||||||
animTimeoutRef.current = null;
|
animTimeoutRef.current = null;
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
@ -84,10 +81,8 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setAc
|
|||||||
clearTimers();
|
clearTimers();
|
||||||
setIndicatorTool(selectedTool);
|
setIndicatorTool(selectedTool);
|
||||||
setIndicatorVisible(true);
|
setIndicatorVisible(true);
|
||||||
setIsAnimating(true);
|
|
||||||
prevKeyRef.current = (selectedToolKey as string) || null;
|
prevKeyRef.current = (selectedToolKey as string) || null;
|
||||||
animTimeoutRef.current = window.setTimeout(() => {
|
animTimeoutRef.current = window.setTimeout(() => {
|
||||||
setIsAnimating(false);
|
|
||||||
animTimeoutRef.current = null;
|
animTimeoutRef.current = null;
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
@ -95,11 +90,9 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setAc
|
|||||||
const triggerCollapse = () => {
|
const triggerCollapse = () => {
|
||||||
clearTimers();
|
clearTimers();
|
||||||
setIndicatorVisible(false);
|
setIndicatorVisible(false);
|
||||||
setIsAnimating(true);
|
|
||||||
collapseTimeoutRef.current = window.setTimeout(() => {
|
collapseTimeoutRef.current = window.setTimeout(() => {
|
||||||
setIndicatorTool(null);
|
setIndicatorTool(null);
|
||||||
prevKeyRef.current = null;
|
prevKeyRef.current = null;
|
||||||
setIsAnimating(false);
|
|
||||||
collapseTimeoutRef.current = null;
|
collapseTimeoutRef.current = null;
|
||||||
}, 500); // match CSS transition duration
|
}, 500); // match CSS transition duration
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import { Box, Stack, Text } from '@mantine/core';
|
import { Box, Stack } from '@mantine/core';
|
||||||
import { getSubcategoryLabel, ToolRegistryEntry } from '../../data/toolsTaxonomy';
|
import { getSubcategoryLabel, ToolRegistryEntry } from '../../data/toolsTaxonomy';
|
||||||
import ToolButton from './toolPicker/ToolButton';
|
import ToolButton from './toolPicker/ToolButton';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -40,12 +40,10 @@ const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</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 }} />
|
<div aria-hidden style={{ height: 200 }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SearchResults;
|
export default SearchResults;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
|
import { useRainbowThemeContext } from '../shared/RainbowThemeProvider';
|
||||||
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
||||||
import ToolPicker from './ToolPicker';
|
import ToolPicker from './ToolPicker';
|
||||||
@ -8,12 +6,11 @@ import ToolRenderer from './ToolRenderer';
|
|||||||
import ToolSearch from './toolPicker/ToolSearch';
|
import ToolSearch from './toolPicker/ToolSearch';
|
||||||
import { useSidebarContext } from "../../contexts/SidebarContext";
|
import { useSidebarContext } from "../../contexts/SidebarContext";
|
||||||
import rainbowStyles from '../../styles/rainbow.module.css';
|
import rainbowStyles from '../../styles/rainbow.module.css';
|
||||||
import { Stack, ScrollArea } from '@mantine/core';
|
import { ScrollArea } from '@mantine/core';
|
||||||
|
|
||||||
// No props needed - component uses context
|
// No props needed - component uses context
|
||||||
|
|
||||||
export default function ToolPanel() {
|
export default function ToolPanel() {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { isRainbowMode } = useRainbowThemeContext();
|
const { isRainbowMode } = useRainbowThemeContext();
|
||||||
const { sidebarRefs } = useSidebarContext();
|
const { sidebarRefs } = useSidebarContext();
|
||||||
const { toolPanelRef } = sidebarRefs;
|
const { toolPanelRef } = sidebarRefs;
|
||||||
@ -27,7 +24,6 @@ export default function ToolPanel() {
|
|||||||
filteredTools,
|
filteredTools,
|
||||||
toolRegistry,
|
toolRegistry,
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
handleBackToTools
|
|
||||||
} = useToolWorkflow();
|
} = useToolWorkflow();
|
||||||
|
|
||||||
const { selectedToolKey, handleToolSelect } = useToolWorkflow();
|
const { selectedToolKey, handleToolSelect } = useToolWorkflow();
|
||||||
|
@ -3,7 +3,6 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
|||||||
import { MantineProvider } from '@mantine/core';
|
import { MantineProvider } from '@mantine/core';
|
||||||
import AddPasswordSettings from './AddPasswordSettings';
|
import AddPasswordSettings from './AddPasswordSettings';
|
||||||
import { defaultParameters } from '../../../hooks/tools/addPassword/useAddPasswordParameters';
|
import { defaultParameters } from '../../../hooks/tools/addPassword/useAddPasswordParameters';
|
||||||
import type { AddPasswordParameters } from '../../../hooks/tools/addPassword/useAddPasswordParameters';
|
|
||||||
|
|
||||||
// Mock useTranslation with predictable return values
|
// Mock useTranslation with predictable return values
|
||||||
const mockT = vi.fn((key: string) => `mock-${key}`);
|
const mockT = vi.fn((key: string) => `mock-${key}`);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React from "react";
|
import { Stack, PasswordInput, Select } from "@mantine/core";
|
||||||
import { Stack, Text, PasswordInput, Select } from "@mantine/core";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { AddPasswordParameters } from "../../../hooks/tools/addPassword/useAddPasswordParameters";
|
import { AddPasswordParameters } from "../../../hooks/tools/addPassword/useAddPasswordParameters";
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React from "react";
|
import { Button, Stack } from "@mantine/core";
|
||||||
import { Button, Stack, Text } from "@mantine/core";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface WatermarkTypeSettingsProps {
|
interface WatermarkTypeSettingsProps {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Stack, Text, TextInput } from "@mantine/core";
|
import { Stack, TextInput } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { AddWatermarkParameters } from "../../../hooks/tools/addWatermark/useAddWatermarkParameters";
|
import { AddWatermarkParameters } from "../../../hooks/tools/addWatermark/useAddWatermarkParameters";
|
||||||
import { removeEmojis } from "../../../utils/textUtils";
|
import { removeEmojis } from "../../../utils/textUtils";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -38,10 +38,8 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
|||||||
automationIcon,
|
automationIcon,
|
||||||
setAutomationIcon,
|
setAutomationIcon,
|
||||||
selectedTools,
|
selectedTools,
|
||||||
addTool,
|
|
||||||
removeTool,
|
removeTool,
|
||||||
updateTool,
|
updateTool,
|
||||||
hasUnsavedChanges,
|
|
||||||
canSaveAutomation,
|
canSaveAutomation,
|
||||||
getToolName,
|
getToolName,
|
||||||
getToolDefaultParameters
|
getToolDefaultParameters
|
||||||
@ -84,14 +82,6 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
|
|||||||
updateTool(selectedTools.length, newTool);
|
updateTool(selectedTools.length, newTool);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBackClick = () => {
|
|
||||||
if (hasUnsavedChanges()) {
|
|
||||||
setUnsavedWarningOpen(true);
|
|
||||||
} else {
|
|
||||||
onBack();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirmBack = () => {
|
const handleConfirmBack = () => {
|
||||||
setUnsavedWarningOpen(false);
|
setUnsavedWarningOpen(false);
|
||||||
onBack();
|
onBack();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
@ -76,13 +76,13 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
|||||||
await automateOperation.executeOperation(
|
await automateOperation.executeOperation(
|
||||||
{
|
{
|
||||||
automationConfig: automation,
|
automationConfig: automation,
|
||||||
onStepStart: (stepIndex: number, operationName: string) => {
|
onStepStart: (stepIndex: number, _operationName: string) => {
|
||||||
setCurrentStepIndex(stepIndex);
|
setCurrentStepIndex(stepIndex);
|
||||||
setExecutionSteps(prev => prev.map((step, idx) =>
|
setExecutionSteps(prev => prev.map((step, idx) =>
|
||||||
idx === stepIndex ? { ...step, status: EXECUTION_STATUS.RUNNING } : step
|
idx === stepIndex ? { ...step, status: EXECUTION_STATUS.RUNNING } : step
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
onStepComplete: (stepIndex: number, resultFiles: File[]) => {
|
onStepComplete: (stepIndex: number, _resultFiles: File[]) => {
|
||||||
setExecutionSteps(prev => prev.map((step, idx) =>
|
setExecutionSteps(prev => prev.map((step, idx) =>
|
||||||
idx === stepIndex ? { ...step, status: EXECUTION_STATUS.COMPLETED } : step
|
idx === stepIndex ? { ...step, status: EXECUTION_STATUS.COMPLETED } : step
|
||||||
));
|
));
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Modal,
|
Modal,
|
||||||
@ -32,7 +32,6 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [parameters, setParameters] = useState<any>({});
|
const [parameters, setParameters] = useState<any>({});
|
||||||
const [isValid, setIsValid] = useState(true);
|
|
||||||
|
|
||||||
// Get tool info from registry
|
// Get tool info from registry
|
||||||
const toolInfo = toolRegistry[tool.operation as keyof ToolRegistry];
|
const toolInfo = toolRegistry[tool.operation as keyof ToolRegistry];
|
||||||
@ -87,9 +86,7 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (isValid) {
|
onSave(parameters);
|
||||||
onSave(parameters);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -127,7 +124,6 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
|
|||||||
<Button
|
<Button
|
||||||
leftSection={<CheckIcon />}
|
leftSection={<CheckIcon />}
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={!isValid}
|
|
||||||
>
|
>
|
||||||
{t('automate.config.save', 'Save Configuration')}
|
{t('automate.config.save', 'Save Configuration')}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Stack, Text, Checkbox } from "@mantine/core";
|
import { Stack, Checkbox } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ChangePermissionsParameters } from "../../../hooks/tools/changePermissions/useChangePermissionsParameters";
|
import { ChangePermissionsParameters } from "../../../hooks/tools/changePermissions/useChangePermissionsParameters";
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import LanguagePicker from './LanguagePicker';
|
import LanguagePicker from './LanguagePicker';
|
||||||
import { OCRParameters } from '../../../hooks/tools/ocr/useOCRParameters';
|
import { OCRParameters } from '../../../hooks/tools/ocr/useOCRParameters';
|
||||||
|
@ -8,11 +8,7 @@ interface RemoveCertificateSignSettingsProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RemoveCertificateSignSettings: React.FC<RemoveCertificateSignSettingsProps> = ({
|
const RemoveCertificateSignSettings: React.FC<RemoveCertificateSignSettingsProps> = (_) => {
|
||||||
parameters,
|
|
||||||
onParameterChange, // Unused - kept for interface consistency and future extensibility
|
|
||||||
disabled = false
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Stack, Text, PasswordInput } from "@mantine/core";
|
import { Stack, PasswordInput } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { RemovePasswordParameters } from "../../../hooks/tools/removePassword/useRemovePasswordParameters";
|
import { RemovePasswordParameters } from "../../../hooks/tools/removePassword/useRemovePasswordParameters";
|
||||||
|
|
||||||
|
@ -8,11 +8,7 @@ interface RepairSettingsProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RepairSettings: React.FC<RepairSettingsProps> = ({
|
const RepairSettings: React.FC<RepairSettingsProps> = (_) => {
|
||||||
parameters,
|
|
||||||
onParameterChange,
|
|
||||||
disabled = false
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Text, Anchor } from "@mantine/core";
|
import { Text, Anchor } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import FolderIcon from '@mui/icons-material/Folder';
|
import FolderIcon from '@mui/icons-material/Folder';
|
||||||
@ -28,7 +28,7 @@ const FileStatusIndicator = ({
|
|||||||
try {
|
try {
|
||||||
const recentFiles = await loadRecentFiles();
|
const recentFiles = await loadRecentFiles();
|
||||||
setHasRecentFiles(recentFiles.length > 0);
|
setHasRecentFiles(recentFiles.length > 0);
|
||||||
} catch (error) {
|
} catch {
|
||||||
setHasRecentFiles(false);
|
setHasRecentFiles(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { Button, Group, Stack } from "@mantine/core";
|
import { Button, Stack } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
import UndoIcon from "@mui/icons-material/Undo";
|
import UndoIcon from "@mui/icons-material/Undo";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
import React, { createContext, useContext, useMemo } from 'react';
|
||||||
import { Text, Stack, Box, Flex, Divider } from '@mantine/core';
|
import { Text, Stack, Flex, Divider } from '@mantine/core';
|
||||||
import LocalIcon from '../../shared/LocalIcon';
|
import LocalIcon from '../../shared/LocalIcon';
|
||||||
import { Tooltip } from '../../shared/Tooltip';
|
import { Tooltip } from '../../shared/Tooltip';
|
||||||
import { TooltipTip } from '../../../types/tips';
|
import { TooltipTip } from '../../../types/tips';
|
||||||
|
@ -81,7 +81,7 @@ export function createToolFlow(config: ToolFlowConfig) {
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Middle Steps */}
|
{/* Middle Steps */}
|
||||||
{config.steps.map((stepConfig, index) =>
|
{config.steps.map((stepConfig) =>
|
||||||
steps.create(stepConfig.title, {
|
steps.create(stepConfig.title, {
|
||||||
isVisible: stepConfig.isVisible,
|
isVisible: stepConfig.isVisible,
|
||||||
isCollapsed: stepConfig.isCollapsed,
|
isCollapsed: stepConfig.isCollapsed,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import { Box } from '@mantine/core';
|
||||||
import { Box, Stack } from '@mantine/core';
|
|
||||||
import ToolButton from '../toolPicker/ToolButton';
|
import ToolButton from '../toolPicker/ToolButton';
|
||||||
import SubcategoryHeader from './SubcategoryHeader';
|
import SubcategoryHeader from './SubcategoryHeader';
|
||||||
|
|
||||||
|
@ -8,11 +8,7 @@ interface SingleLargePageSettingsProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SingleLargePageSettings: React.FC<SingleLargePageSettingsProps> = ({
|
const SingleLargePageSettings: React.FC<SingleLargePageSettingsProps> = (_) => {
|
||||||
parameters,
|
|
||||||
onParameterChange,
|
|
||||||
disabled = false
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -8,11 +8,7 @@ interface UnlockPdfFormsSettingsProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UnlockPdfFormsSettings: React.FC<UnlockPdfFormsSettingsProps> = ({
|
const UnlockPdfFormsSettings: React.FC<UnlockPdfFormsSettingsProps> = (_) => {
|
||||||
parameters,
|
|
||||||
onParameterChange, // Unused - kept for interface consistency and future extensibility
|
|
||||||
disabled = false
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
import React, { useEffect, useState, useRef, useCallback } from "react";
|
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 { useTranslation } from "react-i18next";
|
||||||
import { pdfWorkerManager } from "../../services/pdfWorkerManager";
|
import { pdfWorkerManager } from "../../services/pdfWorkerManager";
|
||||||
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
|
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
|
||||||
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
||||||
import FirstPageIcon from "@mui/icons-material/FirstPage";
|
import FirstPageIcon from "@mui/icons-material/FirstPage";
|
||||||
import LastPageIcon from "@mui/icons-material/LastPage";
|
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 ViewWeekIcon from "@mui/icons-material/ViewWeek"; // for dual page (book)
|
||||||
import DescriptionIcon from "@mui/icons-material/Description"; // for single page
|
import DescriptionIcon from "@mui/icons-material/Description"; // for single page
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { useLocalStorage } from "@mantine/hooks";
|
|
||||||
import { fileStorage } from "../../services/fileStorage";
|
import { fileStorage } from "../../services/fileStorage";
|
||||||
import SkeletonLoader from '../shared/SkeletonLoader';
|
import SkeletonLoader from '../shared/SkeletonLoader';
|
||||||
import { useFileState, useFileActions, useCurrentFile } from "../../contexts/FileContext";
|
import { useFileState } from "../../contexts/FileContext";
|
||||||
import { useFileWithUrl } from "../../hooks/useFileWithUrl";
|
import { useFileWithUrl } from "../../hooks/useFileWithUrl";
|
||||||
import { isFileObject } from "../../types/fileContext";
|
import { isFileObject } from "../../types/fileContext";
|
||||||
import { FileId } from "../../types/file";
|
import { FileId } from "../../types/file";
|
||||||
@ -142,8 +140,6 @@ export interface ViewerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Viewer = ({
|
const Viewer = ({
|
||||||
sidebarsVisible,
|
|
||||||
setSidebarsVisible,
|
|
||||||
onClose,
|
onClose,
|
||||||
previewFile,
|
previewFile,
|
||||||
}: ViewerProps) => {
|
}: ViewerProps) => {
|
||||||
@ -152,13 +148,7 @@ const Viewer = ({
|
|||||||
|
|
||||||
// Get current file from FileContext
|
// Get current file from FileContext
|
||||||
const { selectors } = useFileState();
|
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();
|
const activeFiles = selectors.getFiles();
|
||||||
|
|
||||||
// Tab management for multiple files
|
// Tab management for multiple files
|
||||||
@ -406,7 +396,7 @@ const Viewer = ({
|
|||||||
// Start progressive preloading after a short delay
|
// Start progressive preloading after a short delay
|
||||||
setTimeout(() => startProgressivePreload(), 100);
|
setTimeout(() => startProgressivePreload(), 100);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setPageImages([]);
|
setPageImages([]);
|
||||||
setNumPages(0);
|
setNumPages(0);
|
||||||
|
@ -39,7 +39,6 @@ const DEBUG = process.env.NODE_ENV === 'development';
|
|||||||
// Inner provider component that has access to IndexedDB
|
// Inner provider component that has access to IndexedDB
|
||||||
function FileContextInner({
|
function FileContextInner({
|
||||||
children,
|
children,
|
||||||
enableUrlSync = true,
|
|
||||||
enablePersistence = true
|
enablePersistence = true
|
||||||
}: FileContextProviderProps) {
|
}: FileContextProviderProps) {
|
||||||
const [state, dispatch] = useReducer(fileContextReducer, initialFileContextState);
|
const [state, dispatch] = useReducer(fileContextReducer, initialFileContextState);
|
||||||
@ -128,20 +127,9 @@ function FileContextInner({
|
|||||||
}, [indexedDB]);
|
}, [indexedDB]);
|
||||||
|
|
||||||
const undoConsumeFilesWrapper = useCallback(async (inputFiles: File[], inputStirlingFileStubs: StirlingFileStub[], outputFileIds: FileId[]): Promise<void> => {
|
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]);
|
}, [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
|
// File pinning functions - use StirlingFile directly
|
||||||
const pinFileWrapper = useCallback((file: StirlingFile) => {
|
const pinFileWrapper = useCallback((file: StirlingFile) => {
|
||||||
baseActions.pinFile(file.fileId);
|
baseActions.pinFile(file.fileId);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { createContext, useContext, useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
import React, { createContext, useContext, useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { FileMetadata } from '../types/file';
|
import { FileMetadata } from '../types/file';
|
||||||
import { StoredFile, fileStorage } from '../services/fileStorage';
|
import { fileStorage } from '../services/fileStorage';
|
||||||
import { downloadFiles } from '../utils/downloadUtils';
|
import { downloadFiles } from '../utils/downloadUtils';
|
||||||
import { FileId } from '../types/file';
|
import { FileId } from '../types/file';
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import React, { createContext, useContext, useCallback, useRef } from 'react';
|
import React, { createContext, useContext, useCallback, useRef } from 'react';
|
||||||
|
|
||||||
const DEBUG = process.env.NODE_ENV === 'development';
|
const DEBUG = process.env.NODE_ENV === 'development';
|
||||||
import { fileStorage, StoredFile } from '../services/fileStorage';
|
import { fileStorage } from '../services/fileStorage';
|
||||||
import { FileId } from '../types/file';
|
import { FileId } from '../types/file';
|
||||||
import { FileMetadata } from '../types/file';
|
import { FileMetadata } from '../types/file';
|
||||||
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
|
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
|
||||||
@ -61,7 +61,7 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) {
|
|||||||
const thumbnail = existingThumbnail || await generateThumbnailForFile(file);
|
const thumbnail = existingThumbnail || await generateThumbnailForFile(file);
|
||||||
|
|
||||||
// Store in IndexedDB
|
// Store in IndexedDB
|
||||||
const storedFile = await fileStorage.storeFile(file, fileId, thumbnail);
|
await fileStorage.storeFile(file, fileId, thumbnail);
|
||||||
|
|
||||||
// Cache the file object for immediate reuse
|
// Cache the file object for immediate reuse
|
||||||
fileCache.current.set(fileId, { file, lastAccessed: Date.now() });
|
fileCache.current.set(fileId, { file, lastAccessed: Date.now() });
|
||||||
|
@ -103,7 +103,7 @@ const NavigationActionsContext = createContext<NavigationContextActionsValue | u
|
|||||||
export const NavigationProvider: React.FC<{
|
export const NavigationProvider: React.FC<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
enableUrlSync?: boolean;
|
enableUrlSync?: boolean;
|
||||||
}> = ({ children, enableUrlSync = true }) => {
|
}> = ({ children }) => {
|
||||||
const [state, dispatch] = useReducer(navigationReducer, initialState);
|
const [state, dispatch] = useReducer(navigationReducer, initialState);
|
||||||
const toolRegistry = useFlatToolRegistry();
|
const toolRegistry = useFlatToolRegistry();
|
||||||
|
|
||||||
|
@ -89,6 +89,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
|
|||||||
clearToolSelection: () => void;
|
clearToolSelection: () => void;
|
||||||
|
|
||||||
// Tool Reset Actions
|
// Tool Reset Actions
|
||||||
|
toolResetFunctions: Record<string, () => void>;
|
||||||
registerToolReset: (toolId: string, resetFunction: () => void) => void;
|
registerToolReset: (toolId: string, resetFunction: () => void) => void;
|
||||||
resetTool: (toolId: string) => void;
|
resetTool: (toolId: string) => void;
|
||||||
|
|
||||||
@ -258,6 +259,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
clearToolSelection: () => actions.setSelectedTool(null),
|
clearToolSelection: () => actions.setSelectedTool(null),
|
||||||
|
|
||||||
// Tool Reset Actions
|
// Tool Reset Actions
|
||||||
|
toolResetFunctions,
|
||||||
registerToolReset,
|
registerToolReset,
|
||||||
resetTool,
|
resetTool,
|
||||||
|
|
||||||
|
@ -13,8 +13,7 @@ import {
|
|||||||
import { FileId, FileMetadata } from '../../types/file';
|
import { FileId, FileMetadata } from '../../types/file';
|
||||||
import { generateThumbnailWithMetadata } from '../../utils/thumbnailUtils';
|
import { generateThumbnailWithMetadata } from '../../utils/thumbnailUtils';
|
||||||
import { FileLifecycleManager } from './lifecycle';
|
import { FileLifecycleManager } from './lifecycle';
|
||||||
import { fileProcessingService } from '../../services/fileProcessingService';
|
import { buildQuickKeySet } from './fileSelectors';
|
||||||
import { buildQuickKeySet, buildQuickKeySetFromMetadata } from './fileSelectors';
|
|
||||||
|
|
||||||
const DEBUG = process.env.NODE_ENV === 'development';
|
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`);
|
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 the output file IDs for undo tracking
|
||||||
return outputStirlingFileStubs.map(({ fileId }) => fileId);
|
return outputStirlingFileStubs.map(({ fileId }) => fileId);
|
||||||
}
|
}
|
||||||
@ -467,7 +465,6 @@ export async function undoConsumeFiles(
|
|||||||
inputFiles: File[],
|
inputFiles: File[],
|
||||||
inputStirlingFileStubs: StirlingFileStub[],
|
inputStirlingFileStubs: StirlingFileStub[],
|
||||||
outputFileIds: FileId[],
|
outputFileIds: FileId[],
|
||||||
stateRef: React.MutableRefObject<FileContextState>,
|
|
||||||
filesRef: React.MutableRefObject<Map<FileId, File>>,
|
filesRef: React.MutableRefObject<Map<FileId, File>>,
|
||||||
dispatch: React.Dispatch<FileContextAction>,
|
dispatch: React.Dispatch<FileContextAction>,
|
||||||
indexedDB?: { saveFile: (file: File, fileId: FileId, existingThumbnail?: string) => Promise<any>; deleteFile: (fileId: FileId) => Promise<void> } | null
|
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`);
|
if (DEBUG) console.log(`📄 undoConsumeFiles: Successfully undone consume operation - restored ${inputStirlingFileStubs.length} inputs, removed ${outputFileIds.length} outputs`);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Rollback filesRef to previous state
|
// Rollback filesRef to previous state
|
||||||
if (DEBUG) console.error('📄 undoConsumeFiles: Error during undo, rolling back filesRef', error);
|
if (DEBUG) console.error('📄 undoConsumeFiles: Error during undo, rolling back filesRef', error);
|
||||||
|
@ -166,9 +166,9 @@ export function useFileContext() {
|
|||||||
addFiles: actions.addFiles,
|
addFiles: actions.addFiles,
|
||||||
consumeFiles: actions.consumeFiles,
|
consumeFiles: actions.consumeFiles,
|
||||||
undoConsumeFiles: actions.undoConsumeFiles,
|
undoConsumeFiles: actions.undoConsumeFiles,
|
||||||
recordOperation: (fileId: FileId, operation: any) => {}, // Operation tracking not implemented
|
recordOperation: (_fileId: FileId, _operation: any) => {}, // Operation tracking not implemented
|
||||||
markOperationApplied: (fileId: FileId, operationId: string) => {}, // Operation tracking not implemented
|
markOperationApplied: (_fileId: FileId, _operationId: string) => {}, // Operation tracking not implemented
|
||||||
markOperationFailed: (fileId: FileId, operationId: string, error: string) => {}, // Operation tracking not implemented
|
markOperationFailed: (_fileId: FileId, _operationId: string, _error: string) => {}, // Operation tracking not implemented
|
||||||
|
|
||||||
// File ID lookup
|
// File ID lookup
|
||||||
findFileId: (file: File) => {
|
findFileId: (file: File) => {
|
||||||
|
@ -50,7 +50,7 @@ export class FileLifecycleManager {
|
|||||||
this.blobUrls.forEach(url => {
|
this.blobUrls.forEach(url => {
|
||||||
try {
|
try {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Ignore revocation errors
|
// Ignore revocation errors
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -134,7 +134,7 @@ export class FileLifecycleManager {
|
|||||||
if (record.thumbnailUrl && record.thumbnailUrl.startsWith('blob:')) {
|
if (record.thumbnailUrl && record.thumbnailUrl.startsWith('blob:')) {
|
||||||
try {
|
try {
|
||||||
URL.revokeObjectURL(record.thumbnailUrl);
|
URL.revokeObjectURL(record.thumbnailUrl);
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Ignore revocation errors
|
// Ignore revocation errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,18 +142,18 @@ export class FileLifecycleManager {
|
|||||||
if (record.blobUrl && record.blobUrl.startsWith('blob:')) {
|
if (record.blobUrl && record.blobUrl.startsWith('blob:')) {
|
||||||
try {
|
try {
|
||||||
URL.revokeObjectURL(record.blobUrl);
|
URL.revokeObjectURL(record.blobUrl);
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Ignore revocation errors
|
// Ignore revocation errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up processed file thumbnails
|
// Clean up processed file thumbnails
|
||||||
if (record.processedFile?.pages) {
|
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:')) {
|
if (page.thumbnail && page.thumbnail.startsWith('blob:')) {
|
||||||
try {
|
try {
|
||||||
URL.revokeObjectURL(page.thumbnail);
|
URL.revokeObjectURL(page.thumbnail);
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Ignore revocation errors
|
// Ignore revocation errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 { renderHook } from '@testing-library/react';
|
||||||
import { useAddPasswordOperation } from './useAddPasswordOperation';
|
import { useAddPasswordOperation } from './useAddPasswordOperation';
|
||||||
import type { AddPasswordFullParameters, AddPasswordParameters } from './useAddPasswordParameters';
|
import type { AddPasswordFullParameters } from './useAddPasswordParameters';
|
||||||
|
|
||||||
// Mock the useToolOperation hook
|
// Mock the useToolOperation hook
|
||||||
vi.mock('../shared/useToolOperation', async () => {
|
vi.mock('../shared/useToolOperation', async () => {
|
||||||
|
@ -3,7 +3,6 @@ import { useCallback } from 'react';
|
|||||||
import { executeAutomationSequence } from '../../../utils/automationExecutor';
|
import { executeAutomationSequence } from '../../../utils/automationExecutor';
|
||||||
import { useFlatToolRegistry } from '../../../data/useTranslatedToolRegistry';
|
import { useFlatToolRegistry } from '../../../data/useTranslatedToolRegistry';
|
||||||
import { AutomateParameters } from '../../../types/automation';
|
import { AutomateParameters } from '../../../types/automation';
|
||||||
import { AUTOMATION_CONSTANTS } from '../../../constants/automation';
|
|
||||||
|
|
||||||
export function useAutomateOperation() {
|
export function useAutomateOperation() {
|
||||||
const toolRegistry = useFlatToolRegistry();
|
const toolRegistry = useFlatToolRegistry();
|
||||||
|
@ -46,7 +46,7 @@ export function useSavedAutomations() {
|
|||||||
const { automationStorage } = await import('../../../services/automationStorage');
|
const { automationStorage } = await import('../../../services/automationStorage');
|
||||||
|
|
||||||
// Map suggested automation icons to MUI icon keys
|
// 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
|
// Check the automation ID or name to determine the appropriate icon
|
||||||
switch (suggestedAutomation.id) {
|
switch (suggestedAutomation.id) {
|
||||||
case 'secure-pdf-ingestion':
|
case 'secure-pdf-ingestion':
|
||||||
|
@ -6,7 +6,6 @@ import { SuggestedAutomation } from '../../../types/automation';
|
|||||||
|
|
||||||
// Create icon components
|
// Create icon components
|
||||||
const CompressIcon = () => React.createElement(LocalIcon, { icon: 'compress', width: '1.5rem', height: '1.5rem' });
|
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 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' });
|
const StarIcon = () => React.createElement(LocalIcon, { icon: 'star', width: '1.5rem', height: '1.5rem' });
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useToolOperation, ToolOperationConfig, ToolType } from '../shared/useToolOperation';
|
import { useToolOperation, ToolType } from '../shared/useToolOperation';
|
||||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||||
import { CompressParameters, defaultParameters } from './useCompressParameters';
|
import { CompressParameters, defaultParameters } from './useCompressParameters';
|
||||||
|
|
||||||
|
@ -2,9 +2,8 @@ import { useCallback } from 'react';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ConvertParameters, defaultParameters } from './useConvertParameters';
|
import { ConvertParameters, defaultParameters } from './useConvertParameters';
|
||||||
import { detectFileExtension } from '../../../utils/fileUtils';
|
|
||||||
import { createFileFromApiResponse } from '../../../utils/fileResponseUtils';
|
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';
|
import { getEndpointUrl, isImageFormat, isWebFormat } from '../../../utils/convertUtils';
|
||||||
|
|
||||||
// Static function that can be used by both the hook and automation executor
|
// Static function that can be used by both the hook and automation executor
|
||||||
|
@ -2,7 +2,6 @@ import {
|
|||||||
COLOR_TYPES,
|
COLOR_TYPES,
|
||||||
OUTPUT_OPTIONS,
|
OUTPUT_OPTIONS,
|
||||||
FIT_OPTIONS,
|
FIT_OPTIONS,
|
||||||
TO_FORMAT_OPTIONS,
|
|
||||||
CONVERSION_MATRIX,
|
CONVERSION_MATRIX,
|
||||||
type ColorType,
|
type ColorType,
|
||||||
type OutputOption,
|
type OutputOption,
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, test, expect } from 'vitest';
|
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';
|
import { useConvertParameters } from './useConvertParameters';
|
||||||
|
|
||||||
describe('useConvertParameters - Auto Detection & Smart Conversion', () => {
|
describe('useConvertParameters - Auto Detection & Smart Conversion', () => {
|
||||||
|
@ -4,7 +4,7 @@ import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
|||||||
import { RemoveCertificateSignParameters, defaultParameters } from './useRemoveCertificateSignParameters';
|
import { RemoveCertificateSignParameters, defaultParameters } from './useRemoveCertificateSignParameters';
|
||||||
|
|
||||||
// Static function that can be used by both the hook and automation executor
|
// 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();
|
const formData = new FormData();
|
||||||
formData.append("fileInput", file);
|
formData.append("fileInput", file);
|
||||||
return formData;
|
return formData;
|
||||||
|
@ -4,7 +4,7 @@ import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
|||||||
import { RepairParameters, defaultParameters } from './useRepairParameters';
|
import { RepairParameters, defaultParameters } from './useRepairParameters';
|
||||||
|
|
||||||
// Static function that can be used by both the hook and automation executor
|
// 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();
|
const formData = new FormData();
|
||||||
formData.append("fileInput", file);
|
formData.append("fileInput", file);
|
||||||
return formData;
|
return formData;
|
||||||
|
@ -128,7 +128,7 @@ export const useToolOperation = <TParams>(
|
|||||||
config: ToolOperationConfig<TParams>
|
config: ToolOperationConfig<TParams>
|
||||||
): ToolOperationHook<TParams> => {
|
): ToolOperationHook<TParams> => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { addFiles, consumeFiles, undoConsumeFiles, actions: fileActions, selectors } = useFileContext();
|
const { addFiles, consumeFiles, undoConsumeFiles, selectors } = useFileContext();
|
||||||
|
|
||||||
// Composed hooks
|
// Composed hooks
|
||||||
const { state, actions } = useToolState();
|
const { state, actions } = useToolState();
|
||||||
|
@ -4,7 +4,7 @@ import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
|||||||
import { SingleLargePageParameters, defaultParameters } from './useSingleLargePageParameters';
|
import { SingleLargePageParameters, defaultParameters } from './useSingleLargePageParameters';
|
||||||
|
|
||||||
// Static function that can be used by both the hook and automation executor
|
// 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();
|
const formData = new FormData();
|
||||||
formData.append("fileInput", file);
|
formData.append("fileInput", file);
|
||||||
return formData;
|
return formData;
|
||||||
|
@ -71,7 +71,7 @@ export const useSplitOperation = () => {
|
|||||||
|
|
||||||
// Custom response handler that extracts ZIP files
|
// 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
|
// 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
|
// Split operations return ZIP files with multiple PDF pages
|
||||||
return await extractZipFiles(blob);
|
return await extractZipFiles(blob);
|
||||||
}, [extractZipFiles]);
|
}, [extractZipFiles]);
|
||||||
|
@ -4,7 +4,7 @@ import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
|||||||
import { UnlockPdfFormsParameters, defaultParameters } from './useUnlockPdfFormsParameters';
|
import { UnlockPdfFormsParameters, defaultParameters } from './useUnlockPdfFormsParameters';
|
||||||
|
|
||||||
// Static function that can be used by both the hook and automation executor
|
// 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();
|
const formData = new FormData();
|
||||||
formData.append("fileInput", file);
|
formData.append("fileInput", file);
|
||||||
return formData;
|
return formData;
|
||||||
|
@ -184,11 +184,6 @@ export const useCookieConsent = ({ analyticsEnabled = false }: CookieConsentConf
|
|||||||
// Force show after initialization
|
// Force show after initialization
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.CookieConsent.show();
|
window.CookieConsent.show();
|
||||||
|
|
||||||
// Debug: Check if modal elements exist
|
|
||||||
const ccMain = document.getElementById('cc-main');
|
|
||||||
const consentModal = document.querySelector('.cm-wrapper');
|
|
||||||
|
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -105,7 +105,6 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const endpointsKey = endpoints.join(',');
|
|
||||||
fetchAllEndpointStatuses();
|
fetchAllEndpointStatuses();
|
||||||
}, [endpoints.join(',')]); // Re-run when endpoints array changes
|
}, [endpoints.join(',')]); // Re-run when endpoints array changes
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ export function useEnhancedProcessedFiles(
|
|||||||
updatedFiles.set(file, processed);
|
updatedFiles.set(file, processed);
|
||||||
hasNewFiles = true;
|
hasNewFiles = true;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Ignore errors in completion check
|
// Ignore errors in completion check
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { useIndexedDB } from '../contexts/IndexedDBContext';
|
import { useIndexedDB } from '../contexts/IndexedDBContext';
|
||||||
import { FileMetadata } from '../types/file';
|
import { FileMetadata } from '../types/file';
|
||||||
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
|
|
||||||
import { FileId } from '../types/fileContext';
|
import { FileId } from '../types/fileContext';
|
||||||
|
|
||||||
export const useFileManager = () => {
|
export const useFileManager = () => {
|
||||||
|
@ -4,20 +4,6 @@ import { useIndexedDB } from "../contexts/IndexedDBContext";
|
|||||||
import { generateThumbnailForFile } from "../utils/thumbnailUtils";
|
import { generateThumbnailForFile } from "../utils/thumbnailUtils";
|
||||||
import { FileId } from "../types/fileContext";
|
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
|
* Hook for IndexedDB-aware thumbnail loading
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import * as pdfjsLib from 'pdfjs-dist';
|
|
||||||
import { pdfWorkerManager } from '../services/pdfWorkerManager';
|
import { pdfWorkerManager } from '../services/pdfWorkerManager';
|
||||||
import { StirlingFile } from '../types/fileContext';
|
import { StirlingFile } from '../types/fileContext';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useRef } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { thumbnailGenerationService } from '../services/thumbnailGenerationService';
|
import { thumbnailGenerationService } from '../services/thumbnailGenerationService';
|
||||||
import { createQuickKey } from '../types/fileContext';
|
import { createQuickKey } from '../types/fileContext';
|
||||||
import { FileId } from '../types/file';
|
import { FileId } from '../types/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 { useTranslation } from 'react-i18next';
|
||||||
import { useFlatToolRegistry } from "../data/useTranslatedToolRegistry";
|
import { useFlatToolRegistry } from "../data/useTranslatedToolRegistry";
|
||||||
import { getAllEndpoints, type ToolRegistryEntry } from "../data/toolsTaxonomy";
|
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
|
// Build endpoints list from registry entries with fallback to legacy mapping
|
||||||
const baseRegistry = useFlatToolRegistry();
|
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 allEndpoints = useMemo(() => getAllEndpoints(baseRegistry), [baseRegistry]);
|
||||||
const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints);
|
const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints);
|
||||||
|
@ -10,8 +10,8 @@ type ToolParameterValues = Record<string, any>;
|
|||||||
* Register tool parameters and get current values
|
* Register tool parameters and get current values
|
||||||
*/
|
*/
|
||||||
export function useToolParameters(
|
export function useToolParameters(
|
||||||
toolName: string,
|
_toolName: string,
|
||||||
parameters: Record<string, any>
|
_parameters: Record<string, any>
|
||||||
): [ToolParameterValues, (updates: Partial<ToolParameterValues>) => void] {
|
): [ToolParameterValues, (updates: Partial<ToolParameterValues>) => void] {
|
||||||
|
|
||||||
// Return empty values and noop updater
|
// Return empty values and noop updater
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { clamp } from '../utils/genericUtils';
|
import { clamp } from '../utils/genericUtils';
|
||||||
import { getSidebarInfo } from '../utils/sidebarUtils';
|
import { getSidebarInfo } from '../utils/sidebarUtils';
|
||||||
import { SidebarRefs, SidebarState } from '../types/sidebar';
|
import { SidebarRefs, SidebarState } from '../types/sidebar';
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import React, { useEffect } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
|
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
|
||||||
import { Group } from "@mantine/core";
|
import { Group } from "@mantine/core";
|
||||||
@ -11,7 +10,6 @@ import Workbench from "../components/layout/Workbench";
|
|||||||
import QuickAccessBar from "../components/shared/QuickAccessBar";
|
import QuickAccessBar from "../components/shared/QuickAccessBar";
|
||||||
import RightRail from "../components/shared/RightRail";
|
import RightRail from "../components/shared/RightRail";
|
||||||
import FileManager from "../components/FileManager";
|
import FileManager from "../components/FileManager";
|
||||||
import Footer from "../components/shared/Footer";
|
|
||||||
|
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as pdfjsLib from 'pdfjs-dist';
|
import { ProcessedFile, ProcessingState, PDFPage, ProcessingConfig, ProcessingMetrics } from '../types/processing';
|
||||||
import { ProcessedFile, ProcessingState, PDFPage, ProcessingStrategy, ProcessingConfig, ProcessingMetrics } from '../types/processing';
|
|
||||||
import { ProcessingCache } from './processingCache';
|
import { ProcessingCache } from './processingCache';
|
||||||
import { FileHasher } from '../utils/fileHash';
|
import { FileHasher } from '../utils/fileHash';
|
||||||
import { FileAnalyzer } from './fileAnalyzer';
|
import { FileAnalyzer } from './fileAnalyzer';
|
||||||
@ -355,7 +354,7 @@ export class EnhancedPDFProcessingService {
|
|||||||
*/
|
*/
|
||||||
private async processMetadataOnly(
|
private async processMetadataOnly(
|
||||||
file: File,
|
file: File,
|
||||||
config: ProcessingConfig,
|
_config: ProcessingConfig,
|
||||||
state: ProcessingState
|
state: ProcessingState
|
||||||
): Promise<ProcessedFile> {
|
): Promise<ProcessedFile> {
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
@ -510,7 +509,7 @@ export class EnhancedPDFProcessingService {
|
|||||||
*/
|
*/
|
||||||
clearAllProcessing(): void {
|
clearAllProcessing(): void {
|
||||||
// Cancel all ongoing processing
|
// Cancel all ongoing processing
|
||||||
this.processing.forEach((state, key) => {
|
this.processing.forEach((state) => {
|
||||||
if (state.cancellationToken) {
|
if (state.cancellationToken) {
|
||||||
state.cancellationToken.abort();
|
state.cancellationToken.abort();
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ export class FileAnalyzer {
|
|||||||
* Estimate processing time based on file characteristics and strategy
|
* Estimate processing time based on file characteristics and strategy
|
||||||
*/
|
*/
|
||||||
private static estimateProcessingTime(
|
private static estimateProcessingTime(
|
||||||
fileSize: number,
|
_fileSize: number,
|
||||||
pageCount: number = 0,
|
pageCount: number = 0,
|
||||||
strategy: ProcessingStrategy
|
strategy: ProcessingStrategy
|
||||||
): number {
|
): number {
|
||||||
@ -234,7 +234,7 @@ export class FileAnalyzer {
|
|||||||
const headerString = String.fromCharCode(...headerBytes);
|
const headerString = String.fromCharCode(...headerBytes);
|
||||||
|
|
||||||
return headerString.startsWith('%PDF-');
|
return headerString.startsWith('%PDF-');
|
||||||
} catch (error) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
* Called when files are added to FileContext, before any view sees them
|
* Called when files are added to FileContext, before any view sees them
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as pdfjsLib from 'pdfjs-dist';
|
|
||||||
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
|
import { generateThumbnailForFile } from '../utils/thumbnailUtils';
|
||||||
import { pdfWorkerManager } from './pdfWorkerManager';
|
import { pdfWorkerManager } from './pdfWorkerManager';
|
||||||
import { FileId } from '../types/file';
|
import { FileId } from '../types/file';
|
||||||
|
@ -496,7 +496,7 @@ class FileStorageService {
|
|||||||
async updateThumbnail(id: FileId, thumbnail: string): Promise<boolean> {
|
async updateThumbnail(id: FileId, thumbnail: string): Promise<boolean> {
|
||||||
const db = await this.getDatabase();
|
const db = await this.getDatabase();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
try {
|
try {
|
||||||
const transaction = db.transaction([this.storeName], 'readwrite');
|
const transaction = db.transaction([this.storeName], 'readwrite');
|
||||||
const store = transaction.objectStore(this.storeName);
|
const store = transaction.objectStore(this.storeName);
|
||||||
|
@ -31,7 +31,7 @@ export class PDFExportService {
|
|||||||
const originalPDFBytes = await pdfDocument.file.arrayBuffer();
|
const originalPDFBytes = await pdfDocument.file.arrayBuffer();
|
||||||
const sourceDoc = await PDFLibDocument.load(originalPDFBytes);
|
const sourceDoc = await PDFLibDocument.load(originalPDFBytes);
|
||||||
const blob = await this.createSingleDocument(sourceDoc, pagesToExport);
|
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 };
|
return { blob, filename: exportFilename };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -62,7 +62,7 @@ export class PDFExportService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const blob = await this.createMultiSourceDocument(sourceFiles, pagesToExport);
|
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 };
|
return { blob, filename: exportFilename };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -183,7 +183,7 @@ export class PDFExportService {
|
|||||||
/**
|
/**
|
||||||
* Generate appropriate filename for export
|
* 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, '');
|
const baseName = originalName.replace(/\.pdf$/i, '');
|
||||||
return `${baseName}.pdf`;
|
return `${baseName}.pdf`;
|
||||||
}
|
}
|
||||||
@ -210,7 +210,7 @@ export class PDFExportService {
|
|||||||
/**
|
/**
|
||||||
* Download multiple files as a ZIP
|
* 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) => {
|
blobs.forEach((blob, index) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.downloadFile(blob, filenames[index]);
|
this.downloadFile(blob, filenames[index]);
|
||||||
|
@ -93,7 +93,7 @@ class PDFWorkerManager {
|
|||||||
if (loadingTask) {
|
if (loadingTask) {
|
||||||
try {
|
try {
|
||||||
loadingTask.destroy();
|
loadingTask.destroy();
|
||||||
} catch (destroyError) {
|
} catch {
|
||||||
// Ignore errors
|
// Ignore errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ class PDFWorkerManager {
|
|||||||
pdf.destroy();
|
pdf.destroy();
|
||||||
this.activeDocuments.delete(pdf);
|
this.activeDocuments.delete(pdf);
|
||||||
this.workerCount = Math.max(0, this.workerCount - 1);
|
this.workerCount = Math.max(0, this.workerCount - 1);
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Still remove from tracking even if destroy failed
|
// Still remove from tracking even if destroy failed
|
||||||
this.activeDocuments.delete(pdf);
|
this.activeDocuments.delete(pdf);
|
||||||
this.workerCount = Math.max(0, this.workerCount - 1);
|
this.workerCount = Math.max(0, this.workerCount - 1);
|
||||||
@ -166,7 +166,7 @@ class PDFWorkerManager {
|
|||||||
this.activeDocuments.forEach(pdf => {
|
this.activeDocuments.forEach(pdf => {
|
||||||
try {
|
try {
|
||||||
pdf.destroy();
|
pdf.destroy();
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Ignore errors
|
// Ignore errors
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -277,7 +277,7 @@ export class ZipFileService {
|
|||||||
bytes[2] === 0x44 && // D
|
bytes[2] === 0x44 && // D
|
||||||
bytes[3] === 0x46 && // F
|
bytes[3] === 0x46 && // F
|
||||||
bytes[4] === 0x2D; // -
|
bytes[4] === 0x2D; // -
|
||||||
} catch (error) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -324,7 +324,7 @@ export class ZipFileService {
|
|||||||
await zip.loadAsync(file);
|
await zip.loadAsync(file);
|
||||||
|
|
||||||
// Check if any files are encrypted
|
// 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) {
|
if (zipEntry.options?.compression === 'STORE' && getData(zipEntry)?.compressedSize === 0) {
|
||||||
// This might indicate encryption, but JSZip doesn't provide direct encryption detection
|
// This might indicate encryption, but JSZip doesn't provide direct encryption detection
|
||||||
// We'll handle this in the extraction phase
|
// We'll handle this in the extraction phase
|
||||||
|
@ -63,7 +63,7 @@ for (let i = 0; i < 32; i++) {
|
|||||||
Object.defineProperty(globalThis, 'crypto', {
|
Object.defineProperty(globalThis, 'crypto', {
|
||||||
value: {
|
value: {
|
||||||
subtle: {
|
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
|
// Always return the mock hash buffer regardless of input
|
||||||
return mockHashBuffer.slice();
|
return mockHashBuffer.slice();
|
||||||
}),
|
}),
|
||||||
|
@ -17,7 +17,6 @@ import * as fs from 'fs';
|
|||||||
|
|
||||||
// Test configuration
|
// Test configuration
|
||||||
const BASE_URL = process.env.BASE_URL || 'http://localhost:5173';
|
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.
|
* 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
|
// Discover conversions at module level before tests are defined
|
||||||
let allConversions: ConversionEndpoint[] = [];
|
|
||||||
let availableConversions: ConversionEndpoint[] = [];
|
let availableConversions: ConversionEndpoint[] = [];
|
||||||
let unavailableConversions: ConversionEndpoint[] = [];
|
let unavailableConversions: ConversionEndpoint[] = [];
|
||||||
|
|
||||||
@ -275,7 +273,6 @@ let unavailableConversions: ConversionEndpoint[] = [];
|
|||||||
try {
|
try {
|
||||||
availableConversions = await conversionDiscovery.getAvailableConversions();
|
availableConversions = await conversionDiscovery.getAvailableConversions();
|
||||||
unavailableConversions = await conversionDiscovery.getUnavailableConversions();
|
unavailableConversions = await conversionDiscovery.getUnavailableConversions();
|
||||||
allConversions = [...availableConversions, ...unavailableConversions];
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to discover conversions during module load:', error);
|
console.error('Failed to discover conversions during module load:', error);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { describe, test, expect, vi, beforeEach, afterEach, Mock } from 'vitest';
|
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 { useConvertOperation } from '../../hooks/tools/convert/useConvertOperation';
|
||||||
import { ConvertParameters } from '../../hooks/tools/convert/useConvertParameters';
|
import { ConvertParameters } from '../../hooks/tools/convert/useConvertParameters';
|
||||||
import { FileContextProvider } from '../../contexts/FileContext';
|
import { FileContextProvider } from '../../contexts/FileContext';
|
||||||
@ -53,10 +53,6 @@ vi.mock('../../services/thumbnailGenerationService', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Create realistic test files
|
// Create realistic test files
|
||||||
const createTestFile = (name: string, content: string, type: string): File => {
|
|
||||||
return new File([content], name, { type });
|
|
||||||
};
|
|
||||||
|
|
||||||
const createPDFFile = (): StirlingFile => {
|
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';
|
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');
|
return createTestStirlingFile('test.pdf', pdfContent, 'application/pdf');
|
||||||
|
@ -15,7 +15,6 @@ import axios from 'axios';
|
|||||||
import { detectFileExtension } from '../../utils/fileUtils';
|
import { detectFileExtension } from '../../utils/fileUtils';
|
||||||
import { FIT_OPTIONS } from '../../constants/convertConstants';
|
import { FIT_OPTIONS } from '../../constants/convertConstants';
|
||||||
import { createTestStirlingFile, createTestFilesWithId } from '../utils/testFileHelpers';
|
import { createTestStirlingFile, createTestFilesWithId } from '../utils/testFileHelpers';
|
||||||
import { StirlingFile } from '../../types/fileContext';
|
|
||||||
|
|
||||||
// Mock axios
|
// Mock axios
|
||||||
vi.mock('axios');
|
vi.mock('axios');
|
||||||
@ -507,7 +506,7 @@ describe('Convert Tool - Smart Detection Integration Tests', () => {
|
|||||||
describe('Real File Extension Detection', () => {
|
describe('Real File Extension Detection', () => {
|
||||||
|
|
||||||
test('should correctly detect various file extensions', async () => {
|
test('should correctly detect various file extensions', async () => {
|
||||||
const { result } = renderHook(() => useConvertParameters(), {
|
renderHook(() => useConvertParameters(), {
|
||||||
wrapper: TestWrapper
|
wrapper: TestWrapper
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ export const mantineTheme = createTheme({
|
|||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
// Custom button variant for PDF tools
|
// Custom button variant for PDF tools
|
||||||
pdfTool: (theme: any) => ({
|
pdfTool: (_theme: any) => ({
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: 'var(--bg-surface)',
|
backgroundColor: 'var(--bg-surface)',
|
||||||
border: '1px solid var(--border-default)',
|
border: '1px solid var(--border-default)',
|
||||||
@ -108,7 +108,7 @@ export const mantineTheme = createTheme({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Textarea: {
|
Textarea: {
|
||||||
styles: (theme: any) => ({
|
styles: (_theme: any) => ({
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: 'var(--bg-surface)',
|
backgroundColor: 'var(--bg-surface)',
|
||||||
borderColor: 'var(--border-default)',
|
borderColor: 'var(--border-default)',
|
||||||
@ -126,7 +126,7 @@ export const mantineTheme = createTheme({
|
|||||||
},
|
},
|
||||||
|
|
||||||
TextInput: {
|
TextInput: {
|
||||||
styles: (theme: any) => ({
|
styles: (_theme: any) => ({
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: 'var(--bg-surface)',
|
backgroundColor: 'var(--bg-surface)',
|
||||||
borderColor: 'var(--border-default)',
|
borderColor: 'var(--border-default)',
|
||||||
@ -144,7 +144,7 @@ export const mantineTheme = createTheme({
|
|||||||
},
|
},
|
||||||
|
|
||||||
PasswordInput: {
|
PasswordInput: {
|
||||||
styles: (theme: any) => ({
|
styles: (_theme: any) => ({
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: 'var(--bg-surface)',
|
backgroundColor: 'var(--bg-surface)',
|
||||||
borderColor: 'var(--border-default)',
|
borderColor: 'var(--border-default)',
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
import { useFileSelection } from "../contexts/FileContext";
|
import { useFileSelection } from "../contexts/FileContext";
|
||||||
import { useNavigationActions } from "../contexts/NavigationContext";
|
|
||||||
|
|
||||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
|
|
||||||
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
|
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
|
||||||
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
|
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 { useAddPasswordOperation } from "../hooks/tools/addPassword/useAddPasswordOperation";
|
||||||
import { useAddPasswordTips } from "../components/tooltips/useAddPasswordTips";
|
import { useAddPasswordTips } from "../components/tooltips/useAddPasswordTips";
|
||||||
import { useAddPasswordPermissionsTips } from "../components/tooltips/useAddPasswordPermissionsTips";
|
import { useAddPasswordPermissionsTips } from "../components/tooltips/useAddPasswordPermissionsTips";
|
||||||
@ -17,7 +16,6 @@ import { BaseToolProps, ToolComponent } from "../types/tool";
|
|||||||
|
|
||||||
const AddPassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
const AddPassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { actions } = useNavigationActions();
|
|
||||||
const { selectedFiles } = useFileSelection();
|
const { selectedFiles } = useFileSelection();
|
||||||
|
|
||||||
const [collapsedPermissions, setCollapsedPermissions] = useState(true);
|
const [collapsedPermissions, setCollapsedPermissions] = useState(true);
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
import { useFileSelection } from "../contexts/FileContext";
|
import { useFileSelection } from "../contexts/FileContext";
|
||||||
import { useNavigationActions } from "../contexts/NavigationContext";
|
|
||||||
|
|
||||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
|
|
||||||
@ -25,7 +24,6 @@ import { BaseToolProps, ToolComponent } from "../types/tool";
|
|||||||
|
|
||||||
const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
const AddWatermark = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { actions } = useNavigationActions();
|
|
||||||
const { selectedFiles } = useFileSelection();
|
const { selectedFiles } = useFileSelection();
|
||||||
|
|
||||||
const [collapsedType, setCollapsedType] = useState(false);
|
const [collapsedType, setCollapsedType] = useState(false);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useState, useMemo, useEffect } from "react";
|
import React, { useState, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useFileContext } from "../contexts/FileContext";
|
|
||||||
import { useFileSelection } from "../contexts/FileContext";
|
import { useFileSelection } from "../contexts/FileContext";
|
||||||
import { useNavigationActions } from "../contexts/NavigationContext";
|
import { useNavigationActions } from "../contexts/NavigationContext";
|
||||||
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
|
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
import { useFileState, useFileSelection } from "../contexts/FileContext";
|
import { useFileState, useFileSelection } from "../contexts/FileContext";
|
||||||
import { useNavigationActions } from "../contexts/NavigationContext";
|
|
||||||
|
|
||||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
|
|
||||||
@ -15,7 +14,6 @@ import { BaseToolProps, ToolComponent } from "../types/tool";
|
|||||||
const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { selectors } = useFileState();
|
const { selectors } = useFileState();
|
||||||
const { actions } = useNavigationActions();
|
|
||||||
const activeFiles = selectors.getFiles();
|
const activeFiles = selectors.getFiles();
|
||||||
const { selectedFiles } = useFileSelection();
|
const { selectedFiles } = useFileSelection();
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
import { useFileSelection } from "../contexts/FileContext";
|
import { useFileSelection } from "../contexts/FileContext";
|
||||||
import { useNavigationActions } from "../contexts/NavigationContext";
|
|
||||||
|
|
||||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
import SplitSettings from "../components/tools/split/SplitSettings";
|
import SplitSettings from "../components/tools/split/SplitSettings";
|
||||||
|
6
frontend/src/types/fileIdSafety.d.ts
vendored
6
frontend/src/types/fileIdSafety.d.ts
vendored
@ -2,19 +2,19 @@
|
|||||||
* Type safety declarations to prevent file.name/UUID confusion
|
* Type safety declarations to prevent file.name/UUID confusion
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FileId, StirlingFile, OperationType, FileOperation } from './fileContext';
|
import { FileId, StirlingFile } from './fileContext';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace FileIdSafety {
|
namespace FileIdSafety {
|
||||||
// Mark functions that should never accept file.name as parameters
|
// 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[]]
|
? P extends readonly [string, ...any[]]
|
||||||
? never // Reject string parameters in first position for FileId functions
|
? never // Reject string parameters in first position for FileId functions
|
||||||
: T
|
: T
|
||||||
: T;
|
: T;
|
||||||
|
|
||||||
// Mark functions that should only accept StirlingFile, not regular File
|
// 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[]]
|
? P extends readonly [File, ...any[]]
|
||||||
? never // Reject File parameters in first position for StirlingFile functions
|
? never // Reject File parameters in first position for StirlingFile functions
|
||||||
: T
|
: T
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { ToolRegistry } from '../data/toolsTaxonomy';
|
import { ToolRegistry } from '../data/toolsTaxonomy';
|
||||||
import { AutomationConfig, AutomationExecutionCallbacks } from '../types/automation';
|
|
||||||
import { AUTOMATION_CONSTANTS } from '../constants/automation';
|
import { AUTOMATION_CONSTANTS } from '../constants/automation';
|
||||||
import { AutomationFileProcessor } from './automationFileProcessor';
|
import { AutomationFileProcessor } from './automationFileProcessor';
|
||||||
import { ResourceManager } from './resourceManager';
|
import { ResourceManager } from './resourceManager';
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* File processing utilities specifically for automation workflows
|
* File processing utilities specifically for automation workflows
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import axios, { AxiosResponse } from 'axios';
|
import axios from 'axios';
|
||||||
import { zipFileService } from '../services/zipFileService';
|
import { zipFileService } from '../services/zipFileService';
|
||||||
import { ResourceManager } from './resourceManager';
|
import { ResourceManager } from './resourceManager';
|
||||||
import { AUTOMATION_CONSTANTS } from '../constants/automation';
|
import { AUTOMATION_CONSTANTS } from '../constants/automation';
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user