diff --git a/frontend/index.html b/frontend/index.html
index 0fc165c66..c4a808349 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -7,12 +7,12 @@
-
Vite App
+ Stirling PDF
diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico
index a11777cc4..6d6c8521c 100644
Binary files a/frontend/public/favicon.ico and b/frontend/public/favicon.ico differ
diff --git a/frontend/public/logo192.png b/frontend/public/logo192.png
index fc44b0a37..2994ca293 100644
Binary files a/frontend/public/logo192.png and b/frontend/public/logo192.png differ
diff --git a/frontend/public/logo512.png b/frontend/public/logo512.png
index a4e47a654..b48155073 100644
Binary files a/frontend/public/logo512.png and b/frontend/public/logo512.png differ
diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx
index c0badafd8..9e0dc2171 100644
--- a/frontend/src/components/fileEditor/FileEditor.tsx
+++ b/frontend/src/components/fileEditor/FileEditor.tsx
@@ -7,6 +7,7 @@ import { Dropzone } from '@mantine/dropzone';
import { useTranslation } from 'react-i18next';
import UploadFileIcon from '@mui/icons-material/UploadFile';
import { useFileContext } from '../../contexts/FileContext';
+import { useFileSelection } from '../../contexts/FileSelectionContext';
import { FileOperation } from '../../types/fileContext';
import { fileStorage } from '../../services/fileStorage';
import { generateThumbnailForFile } from '../../utils/thumbnailUtils';
@@ -31,20 +32,16 @@ interface FileEditorProps {
onOpenPageEditor?: (file: File) => void;
onMergeFiles?: (files: File[]) => void;
toolMode?: boolean;
- multiSelect?: boolean;
showUpload?: boolean;
showBulkActions?: boolean;
- onFileSelect?: (files: File[]) => void;
}
const FileEditor = ({
onOpenPageEditor,
onMergeFiles,
toolMode = false,
- multiSelect = true,
showUpload = true,
- showBulkActions = true,
- onFileSelect
+ showBulkActions = true
}: FileEditorProps) => {
const { t } = useTranslation();
@@ -63,6 +60,14 @@ const FileEditor = ({
markOperationApplied
} = fileContext;
+ // Get file selection context
+ const {
+ selectedFiles: toolSelectedFiles,
+ setSelectedFiles: setToolSelectedFiles,
+ maxFiles,
+ isToolMode
+ } = useFileSelection();
+
const [files, setFiles] = useState([]);
const [status, setStatus] = useState(null);
const [error, setError] = useState(null);
@@ -99,14 +104,14 @@ const FileEditor = ({
const lastActiveFilesRef = useRef([]);
const lastProcessedFilesRef = useRef(0);
- // Map context selected file names to local file IDs
- // Defensive programming: ensure selectedFileIds is always an array
- const safeSelectedFileIds = Array.isArray(selectedFileIds) ? selectedFileIds : [];
+ // Get selected file IDs from context (defensive programming)
+ const contextSelectedIds = Array.isArray(selectedFileIds) ? selectedFileIds : [];
- const localSelectedFiles = files
+ // Map context selections to local file IDs for UI display
+ const localSelectedIds = files
.filter(file => {
const fileId = (file.file as any).id || file.name;
- return safeSelectedFileIds.includes(fileId);
+ return contextSelectedIds.includes(fileId);
})
.map(file => file.id);
@@ -396,44 +401,41 @@ const FileEditor = ({
if (!targetFile) return;
const contextFileId = (targetFile.file as any).id || targetFile.name;
+ const isSelected = contextSelectedIds.includes(contextFileId);
- if (!multiSelect) {
- // Single select mode for tools - toggle on/off
- const isCurrentlySelected = safeSelectedFileIds.includes(contextFileId);
- if (isCurrentlySelected) {
- // Deselect the file
- setContextSelectedFiles([]);
- if (onFileSelect) {
- onFileSelect([]);
- }
- } else {
- // Select the file
- setContextSelectedFiles([contextFileId]);
- if (onFileSelect) {
- onFileSelect([targetFile.file]);
- }
- }
+ let newSelection: string[];
+
+ if (isSelected) {
+ // Remove file from selection
+ newSelection = contextSelectedIds.filter(id => id !== contextFileId);
} else {
- // Multi select mode (default)
- setContextSelectedFiles(prev => {
- const safePrev = Array.isArray(prev) ? prev : [];
- return safePrev.includes(contextFileId)
- ? safePrev.filter(id => id !== contextFileId)
- : [...safePrev, contextFileId];
- });
-
- // Notify parent with selected files
- if (onFileSelect) {
- const selectedFiles = files
- .filter(f => {
- const fId = (f.file as any).id || f.name;
- return safeSelectedFileIds.includes(fId) || fId === contextFileId;
- })
- .map(f => f.file);
- onFileSelect(selectedFiles);
+ // Add file to selection
+ if (maxFiles === 1) {
+ newSelection = [contextFileId];
+ } else {
+ // Check if we've hit the selection limit
+ if (maxFiles > 1 && contextSelectedIds.length >= maxFiles) {
+ setStatus(`Maximum ${maxFiles} files can be selected`);
+ return;
+ }
+ newSelection = [...contextSelectedIds, contextFileId];
}
}
- }, [files, setContextSelectedFiles, multiSelect, onFileSelect, safeSelectedFileIds]);
+
+ // Update context
+ setContextSelectedFiles(newSelection);
+
+ // Update tool selection context if in tool mode
+ if (isToolMode || toolMode) {
+ const selectedFiles = files
+ .filter(f => {
+ const fId = (f.file as any).id || f.name;
+ return newSelection.includes(fId);
+ })
+ .map(f => f.file);
+ setToolSelectedFiles(selectedFiles);
+ }
+ }, [files, setContextSelectedFiles, maxFiles, contextSelectedIds, setStatus, isToolMode, toolMode, setToolSelectedFiles]);
const toggleSelectionMode = useCallback(() => {
setSelectionMode(prev => {
@@ -450,15 +452,15 @@ const FileEditor = ({
const handleDragStart = useCallback((fileId: string) => {
setDraggedFile(fileId);
- if (selectionMode && localSelectedFiles.includes(fileId) && localSelectedFiles.length > 1) {
+ if (selectionMode && localSelectedIds.includes(fileId) && localSelectedIds.length > 1) {
setMultiFileDrag({
- fileIds: localSelectedFiles,
- count: localSelectedFiles.length
+ fileIds: localSelectedIds,
+ count: localSelectedIds.length
});
} else {
setMultiFileDrag(null);
}
- }, [selectionMode, localSelectedFiles]);
+ }, [selectionMode, localSelectedIds]);
const handleDragEnd = useCallback(() => {
setDraggedFile(null);
@@ -519,8 +521,8 @@ const FileEditor = ({
if (targetIndex === -1) return;
}
- const filesToMove = selectionMode && localSelectedFiles.includes(draggedFile)
- ? localSelectedFiles
+ const filesToMove = selectionMode && localSelectedIds.includes(draggedFile)
+ ? localSelectedIds
: [draggedFile];
// Update the local files state and sync with activeFiles
@@ -545,7 +547,7 @@ const FileEditor = ({
const moveCount = multiFileDrag ? multiFileDrag.count : 1;
setStatus(`${moveCount > 1 ? `${moveCount} files` : 'File'} reordered`);
- }, [draggedFile, files, selectionMode, localSelectedFiles, multiFileDrag]);
+ }, [draggedFile, files, selectionMode, localSelectedIds, multiFileDrag]);
const handleEndZoneDragEnter = useCallback(() => {
if (draggedFile) {
@@ -764,7 +766,7 @@ const FileEditor = ({
) : (
+
+
+
+ {toolName ? `Loading ${toolName}...` : "Loading tool..."}
+
+
+
+ )
+}
diff --git a/frontend/src/components/tools/ToolPicker.tsx b/frontend/src/components/tools/ToolPicker.tsx
index cfb2bd3d4..c22a0f60f 100644
--- a/frontend/src/components/tools/ToolPicker.tsx
+++ b/frontend/src/components/tools/ToolPicker.tsx
@@ -1,15 +1,7 @@
import React, { useState } from "react";
import { Box, Text, Stack, Button, TextInput, Group } from "@mantine/core";
import { useTranslation } from "react-i18next";
-
-type Tool = {
- icon: React.ReactNode;
- name: string;
-};
-
-type ToolRegistry = {
- [id: string]: Tool;
-};
+import { ToolRegistry } from "../../types/tool";
interface ToolPickerProps {
selectedToolKey: string | null;
diff --git a/frontend/src/components/tools/ToolRenderer.tsx b/frontend/src/components/tools/ToolRenderer.tsx
index 032d84828..493470935 100644
--- a/frontend/src/components/tools/ToolRenderer.tsx
+++ b/frontend/src/components/tools/ToolRenderer.tsx
@@ -1,23 +1,18 @@
-import { FileWithUrl } from "../../types/file";
+import React, { Suspense } from "react";
import { useToolManagement } from "../../hooks/useToolManagement";
+import { BaseToolProps } from "../../types/tool";
+import ToolLoadingFallback from "./ToolLoadingFallback";
-interface ToolRendererProps {
+interface ToolRendererProps extends BaseToolProps {
selectedToolKey: string;
- pdfFile: any;
- files: FileWithUrl[];
- toolParams: any;
- updateParams: (params: any) => void;
- toolSelectedFiles?: File[];
- onPreviewFile?: (file: File | null) => void;
}
+
const ToolRenderer = ({
selectedToolKey,
-files,
- toolParams,
- updateParams,
- toolSelectedFiles = [],
onPreviewFile,
+ onComplete,
+ onError,
}: ToolRendererProps) => {
// Get the tool from registry
const { toolRegistry } = useToolManagement();
@@ -29,39 +24,16 @@ files,
const ToolComponent = selectedTool.component;
- // Pass tool-specific props
- switch (selectedToolKey) {
- case "split":
- return (
-
- );
- case "compress":
- return (
-
- );
- case "merge":
- return (
-
- );
- default:
- return (
-
- );
- }
+ // Wrap lazy-loaded component with Suspense
+ return (
+ }>
+
+
+ );
};
export default ToolRenderer;
diff --git a/frontend/src/contexts/FileContext.tsx b/frontend/src/contexts/FileContext.tsx
index 811a49db7..6e8a42fab 100644
--- a/frontend/src/contexts/FileContext.tsx
+++ b/frontend/src/contexts/FileContext.tsx
@@ -22,6 +22,7 @@ import { useEnhancedProcessedFiles } from '../hooks/useEnhancedProcessedFiles';
import { fileStorage } from '../services/fileStorage';
import { enhancedPDFProcessingService } from '../services/enhancedPDFProcessingService';
import { thumbnailGenerationService } from '../services/thumbnailGenerationService';
+import { getFileId } from '../utils/fileUtils';
// Initial state
const initialViewerConfig: ViewerConfig = {
@@ -98,7 +99,7 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
case 'REMOVE_FILES':
const remainingFiles = state.activeFiles.filter(file => {
- const fileId = (file as any).id || file.name;
+ const fileId = getFileId(file);
return !action.payload.includes(fileId);
});
const safeSelectedFileIds = Array.isArray(state.selectedFileIds) ? state.selectedFileIds : [];
@@ -347,7 +348,7 @@ export function FileContextProvider({
// Cleanup timers and refs
const cleanupTimers = useRef