import React, { useState, useCallback, useEffect } from "react"; import { useTranslation } from 'react-i18next'; import { useSearchParams } from "react-router-dom"; import { useToolParams } from "../hooks/useToolParams"; import { useFileWithUrl } from "../hooks/useFileWithUrl"; import { fileStorage } from "../services/fileStorage"; import AddToPhotosIcon from "@mui/icons-material/AddToPhotos"; import ContentCutIcon from "@mui/icons-material/ContentCut"; import ZoomInMapIcon from "@mui/icons-material/ZoomInMap"; import { Group, Paper, Box, Button, useMantineTheme, Container } from "@mantine/core"; import { useRainbowThemeContext } from "../components/shared/RainbowThemeProvider"; import rainbowStyles from '../styles/rainbow.module.css'; import ToolPicker from "../components/tools/ToolPicker"; import TopControls from "../components/shared/TopControls"; import FileManager from "../components/fileManagement/FileManager"; import FileEditor from "../components/pageEditor/FileEditor"; import PageEditor from "../components/pageEditor/PageEditor"; import PageEditorControls from "../components/pageEditor/PageEditorControls"; import Viewer from "../components/viewer/Viewer"; import FileUploadSelector from "../components/shared/FileUploadSelector"; import SplitPdfPanel from "../tools/Split"; import CompressPdfPanel from "../tools/Compress"; import MergePdfPanel from "../tools/Merge"; import ToolRenderer from "../components/tools/ToolRenderer"; import QuickAccessBar from "../components/shared/QuickAccessBar"; type ToolRegistryEntry = { icon: React.ReactNode; name: string; component: React.ComponentType; view: string; }; type ToolRegistry = { [key: string]: ToolRegistryEntry; }; // Base tool registry without translations const baseToolRegistry = { split: { icon: , component: SplitPdfPanel, view: "viewer" }, compress: { icon: , component: CompressPdfPanel, view: "viewer" }, merge: { icon: , component: MergePdfPanel, view: "fileManager" }, }; export default function HomePage() { const { t } = useTranslation(); const [searchParams] = useSearchParams(); const theme = useMantineTheme(); const { isRainbowMode } = useRainbowThemeContext(); // Core app state const [selectedToolKey, setSelectedToolKey] = useState(searchParams.get("t") || "split"); const [currentView, setCurrentView] = useState(searchParams.get("v") || "viewer"); // File state separation const [storedFiles, setStoredFiles] = useState([]); // IndexedDB files (FileManager) const [activeFiles, setActiveFiles] = useState([]); // Active working set (persisted) const [preSelectedFiles, setPreSelectedFiles] = useState([]); const [downloadUrl, setDownloadUrl] = useState(null); const [sidebarsVisible, setSidebarsVisible] = useState(true); const [leftPanelView, setLeftPanelView] = useState<'toolPicker' | 'toolContent'>('toolPicker'); const [readerMode, setReaderMode] = useState(false); // Page editor functions const [pageEditorFunctions, setPageEditorFunctions] = useState(null); // URL parameter management const { toolParams, updateParams } = useToolParams(selectedToolKey, currentView); // Persist active files across reloads useEffect(() => { // Save active files to localStorage (just metadata) const activeFileData = activeFiles.map(file => ({ name: file.name, size: file.size, type: file.type, lastModified: file.lastModified })); localStorage.setItem('activeFiles', JSON.stringify(activeFileData)); }, [activeFiles]); // Load stored files from IndexedDB on mount useEffect(() => { const loadStoredFiles = async () => { try { const files = await fileStorage.getAllFiles(); setStoredFiles(files); } catch (error) { console.warn('Failed to load stored files:', error); } }; loadStoredFiles(); }, []); // Restore active files on load useEffect(() => { const restoreActiveFiles = async () => { try { const savedFileData = JSON.parse(localStorage.getItem('activeFiles') || '[]'); if (savedFileData.length > 0) { // TODO: Reconstruct files from IndexedDB when fileStorage is available console.log('Would restore active files:', savedFileData); } } catch (error) { console.warn('Failed to restore active files:', error); } }; restoreActiveFiles(); }, []); const toolRegistry: ToolRegistry = { split: { ...baseToolRegistry.split, name: t("home.split.title", "Split PDF") }, compress: { ...baseToolRegistry.compress, name: t("home.compressPdfs.title", "Compress PDF") }, merge: { ...baseToolRegistry.merge, name: t("home.merge.title", "Merge PDFs") }, }; // Handle tool selection const handleToolSelect = useCallback( (id: string) => { setSelectedToolKey(id); if (toolRegistry[id]?.view) setCurrentView(toolRegistry[id].view); setLeftPanelView('toolContent'); // Switch to tool content view when a tool is selected setReaderMode(false); // Exit reader mode when selecting a tool }, [toolRegistry] ); // Handle quick access actions const handleQuickAccessTools = useCallback(() => { setLeftPanelView('toolPicker'); setReaderMode(false); }, []); const handleReaderToggle = useCallback(() => { setReaderMode(!readerMode); }, [readerMode]); // Update URL when view changes const handleViewChange = useCallback((view: string) => { setCurrentView(view); const params = new URLSearchParams(window.location.search); params.set('view', view); const newUrl = `${window.location.pathname}?${params.toString()}`; window.history.replaceState({}, '', newUrl); }, []); // Active file management const addToActiveFiles = useCallback((file: File) => { setActiveFiles(prev => { // Avoid duplicates based on name and size const exists = prev.some(f => f.name === file.name && f.size === file.size); if (exists) return prev; return [file, ...prev]; }); }, []); const removeFromActiveFiles = useCallback((file: File) => { setActiveFiles(prev => prev.filter(f => !(f.name === file.name && f.size === file.size))); }, []); const setCurrentActiveFile = useCallback((file: File) => { setActiveFiles(prev => { const filtered = prev.filter(f => !(f.name === file.name && f.size === file.size)); return [file, ...filtered]; }); }, []); // Handle file selection from upload (adds to active files) const handleFileSelect = useCallback((file: File) => { addToActiveFiles(file); }, [addToActiveFiles]); // Handle opening file editor with selected files const handleOpenFileEditor = useCallback(async (selectedFiles) => { if (!selectedFiles || selectedFiles.length === 0) { setPreSelectedFiles([]); handleViewChange("fileEditor"); return; } // Convert FileWithUrl[] to File[] and add to activeFiles try { const convertedFiles = await Promise.all( selectedFiles.map(async (fileItem) => { // If it's already a File, return as is if (fileItem instanceof File) { return fileItem; } // If it has a file property, use that if (fileItem.file && fileItem.file instanceof File) { return fileItem.file; } // If it's from IndexedDB storage, reconstruct the File if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') { const arrayBuffer = await fileItem.arrayBuffer(); const blob = new Blob([arrayBuffer], { type: fileItem.type || 'application/pdf' }); const file = new File([blob], fileItem.name, { type: fileItem.type || 'application/pdf', lastModified: fileItem.lastModified || Date.now() }); // Mark as from storage to avoid re-storing (file as any).storedInIndexedDB = true; return file; } console.warn('Could not convert file item:', fileItem); return null; }) ); // Filter out nulls and add to activeFiles const validFiles = convertedFiles.filter((f): f is File => f !== null); setActiveFiles(validFiles); setPreSelectedFiles([]); // Clear preselected since we're using activeFiles now handleViewChange("fileEditor"); } catch (error) { console.error('Error converting selected files:', error); } }, [handleViewChange, setActiveFiles]); // Handle opening page editor with selected files const handleOpenPageEditor = useCallback(async (selectedFiles) => { if (!selectedFiles || selectedFiles.length === 0) { handleViewChange("pageEditor"); return; } // Convert FileWithUrl[] to File[] and add to activeFiles try { const convertedFiles = await Promise.all( selectedFiles.map(async (fileItem) => { // If it's already a File, return as is if (fileItem instanceof File) { return fileItem; } // If it has a file property, use that if (fileItem.file && fileItem.file instanceof File) { return fileItem.file; } // If it's from IndexedDB storage, reconstruct the File if (fileItem.arrayBuffer && typeof fileItem.arrayBuffer === 'function') { const arrayBuffer = await fileItem.arrayBuffer(); const blob = new Blob([arrayBuffer], { type: fileItem.type || 'application/pdf' }); const file = new File([blob], fileItem.name, { type: fileItem.type || 'application/pdf', lastModified: fileItem.lastModified || Date.now() }); // Mark as from storage to avoid re-storing (file as any).storedInIndexedDB = true; return file; } console.warn('Could not convert file item:', fileItem); return null; }) ); // Filter out nulls and add to activeFiles const validFiles = convertedFiles.filter((f): f is File => f !== null); setActiveFiles(validFiles); handleViewChange("pageEditor"); } catch (error) { console.error('Error converting selected files for page editor:', error); } }, [handleViewChange, setActiveFiles]); const selectedTool = toolRegistry[selectedToolKey]; // For Viewer - convert first active file to expected format (only when needed) const currentFileWithUrl = useFileWithUrl( (currentView === "viewer" && activeFiles[0]) ? activeFiles[0] : null ); return ( {/* Quick Access Bar */} {/* Left: Tool Picker OR Selected Tool Panel */}
{leftPanelView === 'toolPicker' ? ( // Tool Picker View
) : ( // Selected Tool Content View
{/* Back button */}
{/* Tool title */}

{selectedTool?.name}

{/* Tool content */}
)}
{/* Main View */} {/* Top Controls */} {/* Main content area */} {currentView === "fileManager" ? ( ) : (currentView != "fileManager") && !activeFiles[0] ? ( { addToActiveFiles(file); }} allowMultiple={false} accept={["application/pdf"]} loading={false} /> ) : currentView === "fileEditor" ? ( setPreSelectedFiles([])} onOpenPageEditor={(file) => { setCurrentActiveFile(file); handleViewChange("pageEditor"); }} onMergeFiles={(filesToMerge) => { // Add merged files to active set filesToMerge.forEach(addToActiveFiles); handleViewChange("viewer"); }} /> ) : currentView === "viewer" ? ( { if (fileObj) { setCurrentActiveFile(fileObj.file); } else { setActiveFiles([]); } }} sidebarsVisible={sidebarsVisible} setSidebarsVisible={setSidebarsVisible} /> ) : currentView === "pageEditor" ? ( <> {activeFiles[0] && pageEditorFunctions && ( )} ) : ( )}
); }