Remove url params for now. small refactor

This commit is contained in:
Reece 2025-07-09 19:46:44 +01:00
parent 33420c7e80
commit 666c15fabd
14 changed files with 265 additions and 267 deletions

View File

@ -54,15 +54,19 @@ const createViewOptions = (switchingTo: string | null) => [
interface TopControlsProps { interface TopControlsProps {
currentView: string; currentView: string;
setCurrentView: (view: string) => void; setCurrentView: (view: string) => void;
selectedToolKey?: string | null;
} }
const TopControls = ({ const TopControls = ({
currentView, currentView,
setCurrentView, setCurrentView,
selectedToolKey,
}: TopControlsProps) => { }: TopControlsProps) => {
const { themeMode, isRainbowMode, isToggleDisabled, toggleTheme } = useRainbowThemeContext(); const { themeMode, isRainbowMode, isToggleDisabled, toggleTheme } = useRainbowThemeContext();
const [switchingTo, setSwitchingTo] = useState<string | null>(null); const [switchingTo, setSwitchingTo] = useState<string | null>(null);
const isToolSelected = selectedToolKey !== null;
const handleViewChange = useCallback((view: string) => { const handleViewChange = useCallback((view: string) => {
// Show immediate feedback // Show immediate feedback
setSwitchingTo(view); setSwitchingTo(view);
@ -108,22 +112,24 @@ const TopControls = ({
</Button> </Button>
<LanguageSelector /> <LanguageSelector />
</div> </div>
<div className="flex justify-center items-center h-full pointer-events-auto"> {!isToolSelected && (
<SegmentedControl <div className="flex justify-center items-center h-full pointer-events-auto">
data={createViewOptions(switchingTo)} <SegmentedControl
value={currentView} data={createViewOptions(switchingTo)}
onChange={handleViewChange} value={currentView}
color="blue" onChange={handleViewChange}
radius="xl" color="blue"
size="md" radius="xl"
fullWidth size="md"
className={isRainbowMode ? rainbowStyles.rainbowSegmentedControl : ''} fullWidth
style={{ className={isRainbowMode ? rainbowStyles.rainbowSegmentedControl : ''}
transition: 'all 0.2s ease', style={{
opacity: switchingTo ? 0.8 : 1, transition: 'all 0.2s ease',
}} opacity: switchingTo ? 0.8 : 1,
/> }}
</div> />
</div>
)}
</div> </div>
); );
}; };

View File

@ -12,7 +12,7 @@ type ToolRegistry = {
}; };
interface ToolPickerProps { interface ToolPickerProps {
selectedToolKey: string; selectedToolKey: string | null;
onSelect: (id: string) => void; onSelect: (id: string) => void;
toolRegistry: ToolRegistry; toolRegistry: ToolRegistry;
} }

View File

@ -45,7 +45,7 @@ const ToolRenderer = ({
<ToolComponent <ToolComponent
files={files} files={files}
setDownloadUrl={setDownloadUrl} setDownloadUrl={setDownloadUrl}
setLoading={(loading: boolean) => {}} // TODO: Add loading state setLoading={(loading: boolean) => {}}
params={toolParams} params={toolParams}
updateParams={updateParams} updateParams={updateParams}
/> />

View File

@ -3,11 +3,11 @@
*/ */
import React, { createContext, useContext, useReducer, useCallback, useEffect, useRef } from 'react'; import React, { createContext, useContext, useReducer, useCallback, useEffect, useRef } from 'react';
import { useSearchParams } from 'react-router-dom';
import { import {
FileContextValue, FileContextValue,
FileContextState, FileContextState,
FileContextProviderProps, FileContextProviderProps,
ModeType,
ViewType, ViewType,
ToolType, ToolType,
FileOperation, FileOperation,
@ -33,8 +33,9 @@ const initialViewerConfig: ViewerConfig = {
const initialState: FileContextState = { const initialState: FileContextState = {
activeFiles: [], activeFiles: [],
processedFiles: new Map(), processedFiles: new Map(),
currentView: 'fileEditor', currentMode: 'pageEditor',
currentTool: null, currentView: 'fileEditor', // Legacy field
currentTool: null, // Legacy field
fileEditHistory: new Map(), fileEditHistory: new Map(),
globalFileOperations: [], globalFileOperations: [],
selectedFileIds: [], selectedFileIds: [],
@ -55,6 +56,7 @@ type FileContextAction =
| { type: 'REMOVE_FILES'; payload: string[] } | { type: 'REMOVE_FILES'; payload: string[] }
| { type: 'SET_PROCESSED_FILES'; payload: Map<File, ProcessedFile> } | { type: 'SET_PROCESSED_FILES'; payload: Map<File, ProcessedFile> }
| { type: 'UPDATE_PROCESSED_FILE'; payload: { file: File; processedFile: ProcessedFile } } | { type: 'UPDATE_PROCESSED_FILE'; payload: { file: File; processedFile: ProcessedFile } }
| { type: 'SET_CURRENT_MODE'; payload: ModeType }
| { type: 'SET_CURRENT_VIEW'; payload: ViewType } | { type: 'SET_CURRENT_VIEW'; payload: ViewType }
| { type: 'SET_CURRENT_TOOL'; payload: ToolType } | { type: 'SET_CURRENT_TOOL'; payload: ToolType }
| { type: 'SET_SELECTED_FILES'; payload: string[] } | { type: 'SET_SELECTED_FILES'; payload: string[] }
@ -112,17 +114,33 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
processedFiles: updatedProcessedFiles processedFiles: updatedProcessedFiles
}; };
case 'SET_CURRENT_VIEW': case 'SET_CURRENT_MODE':
const coreViews = ['viewer', 'pageEditor', 'fileEditor'];
const isToolMode = !coreViews.includes(action.payload);
return { return {
...state, ...state,
currentMode: action.payload,
// Update legacy fields for backward compatibility
currentView: isToolMode ? 'fileEditor' : action.payload as ViewType,
currentTool: isToolMode ? action.payload as ToolType : null
};
case 'SET_CURRENT_VIEW':
// Legacy action - just update currentMode
return {
...state,
currentMode: action.payload as ModeType,
currentView: action.payload, currentView: action.payload,
// Clear tool when switching views
currentTool: null currentTool: null
}; };
case 'SET_CURRENT_TOOL': case 'SET_CURRENT_TOOL':
// Legacy action - just update currentMode
return { return {
...state, ...state,
currentMode: action.payload ? action.payload as ModeType : 'pageEditor',
currentView: action.payload ? 'fileEditor' : 'pageEditor',
currentTool: action.payload currentTool: action.payload
}; };
@ -233,7 +251,6 @@ export function FileContextProvider({
maxCacheSize = 1024 * 1024 * 1024 // 1GB maxCacheSize = 1024 * 1024 * 1024 // 1GB
}: FileContextProviderProps) { }: FileContextProviderProps) {
const [state, dispatch] = useReducer(fileContextReducer, initialState); const [state, dispatch] = useReducer(fileContextReducer, initialState);
const [searchParams, setSearchParams] = useSearchParams();
// Cleanup timers and refs // Cleanup timers and refs
const cleanupTimers = useRef<Map<string, NodeJS.Timeout>>(new Map()); const cleanupTimers = useRef<Map<string, NodeJS.Timeout>>(new Map());
@ -266,69 +283,6 @@ export function FileContextProvider({
}); });
}, [processedFiles, globalProcessing, processingProgress.overall]); }, [processedFiles, globalProcessing, processingProgress.overall]);
// URL synchronization
const syncUrlParams = useCallback(() => {
if (!enableUrlSync) return;
const params: FileContextUrlParams = {};
if (state.currentView !== 'fileEditor') params.view = state.currentView;
if (state.currentTool) params.tool = state.currentTool;
if (state.selectedFileIds.length > 0) params.fileIds = state.selectedFileIds;
// Note: selectedPageIds intentionally excluded from URL sync - page selection is transient UI state
if (state.viewerConfig.zoom !== 1.0) params.zoom = state.viewerConfig.zoom;
if (state.viewerConfig.currentPage !== 1) params.page = state.viewerConfig.currentPage;
// Update URL params without causing navigation
const newParams = new URLSearchParams(searchParams);
Object.entries(params).forEach(([key, value]) => {
if (Array.isArray(value)) {
newParams.set(key, value.join(','));
} else if (value !== undefined) {
newParams.set(key, value.toString());
}
});
// Remove empty params
Object.keys(params).forEach(key => {
if (!params[key as keyof FileContextUrlParams]) {
newParams.delete(key);
}
});
setSearchParams(newParams, { replace: true });
}, [state, searchParams, setSearchParams, enableUrlSync]);
// Load from URL params on mount
useEffect(() => {
if (!enableUrlSync) return;
const view = searchParams.get('view') as ViewType;
const tool = searchParams.get('tool') as ToolType;
const zoom = searchParams.get('zoom');
const page = searchParams.get('page');
if (view && view !== state.currentView) {
dispatch({ type: 'SET_CURRENT_VIEW', payload: view });
}
if (tool && tool !== state.currentTool) {
dispatch({ type: 'SET_CURRENT_TOOL', payload: tool });
}
if (zoom || page) {
dispatch({
type: 'UPDATE_VIEWER_CONFIG',
payload: {
...(zoom && { zoom: parseFloat(zoom) }),
...(page && { currentPage: parseInt(page) })
}
});
}
}, []);
// Sync URL when state changes
useEffect(() => {
syncUrlParams();
}, [syncUrlParams]);
// Centralized memory management // Centralized memory management
const trackBlobUrl = useCallback((url: string) => { const trackBlobUrl = useCallback((url: string) => {
@ -524,6 +478,20 @@ export function FileContextProvider({
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: false }); dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: false });
}, []); }, []);
const setCurrentMode = useCallback((mode: ModeType) => {
requestNavigation(() => {
dispatch({ type: 'SET_CURRENT_MODE', payload: mode });
if (state.currentMode !== mode && state.activeFiles.length > 0) {
if (window.requestIdleCallback && typeof window !== 'undefined' && window.gc) {
window.requestIdleCallback(() => {
window.gc();
}, { timeout: 5000 });
}
}
});
}, [requestNavigation, state.currentMode, state.activeFiles]);
const setCurrentView = useCallback((view: ViewType) => { const setCurrentView = useCallback((view: ViewType) => {
requestNavigation(() => { requestNavigation(() => {
dispatch({ type: 'SET_CURRENT_VIEW', payload: view }); dispatch({ type: 'SET_CURRENT_VIEW', payload: view });
@ -572,7 +540,6 @@ export function FileContextProvider({
}, []); }, []);
const undoLastOperation = useCallback((fileId?: string) => { const undoLastOperation = useCallback((fileId?: string) => {
// TODO: Implement undo logic
console.warn('Undo not yet implemented'); console.warn('Undo not yet implemented');
}, []); }, []);
@ -676,6 +643,7 @@ export function FileContextProvider({
removeFiles, removeFiles,
replaceFile, replaceFile,
clearAllFiles, clearAllFiles,
setCurrentMode,
setCurrentView, setCurrentView,
setCurrentTool, setCurrentTool,
setSelectedFiles, setSelectedFiles,

View File

@ -0,0 +1,90 @@
/**
* Example of how tools use the new URL parameter system
* This shows how compress, split, merge tools would integrate
*/
import React from 'react';
import { useToolParameters, useToolParameter } from '../hooks/useToolParameters';
// Example: Compress Tool
export function CompressTool() {
const [params, updateParams] = useToolParameters('compress', {
quality: { type: 'string', default: 'medium' },
method: { type: 'string', default: 'lossless' },
optimization: { type: 'boolean', default: true }
});
return (
<div>
<h3>Compress Tool</h3>
<p>Quality: {params.quality}</p>
<p>Method: {params.method}</p>
<p>Optimization: {params.optimization ? 'On' : 'Off'}</p>
<button onClick={() => updateParams({ quality: 'high' })}>
Set High Quality
</button>
<button onClick={() => updateParams({ method: 'lossy' })}>
Set Lossy Method
</button>
</div>
);
}
// Example: Split Tool with single parameter hook
export function SplitTool() {
const [pages, setPages] = useToolParameter('split', 'pages', {
type: 'string',
default: '1-5'
});
const [strategy, setStrategy] = useToolParameter('split', 'strategy', {
type: 'string',
default: 'range'
});
return (
<div>
<h3>Split Tool</h3>
<p>Pages: {pages}</p>
<p>Strategy: {strategy}</p>
<input
value={pages}
onChange={(e) => setPages(e.target.value)}
placeholder="Enter page range"
/>
<select value={strategy} onChange={(e) => setStrategy(e.target.value)}>
<option value="range">Range</option>
<option value="bookmarks">Bookmarks</option>
<option value="size">File Size</option>
</select>
</div>
);
}
// Example: How URLs would look
/*
User interactions -> URL changes:
1. Navigate to compress tool:
?mode=compress
2. Change compress quality to high:
?mode=compress&quality=high
3. Change method to lossy and enable optimization:
?mode=compress&quality=high&method=lossy&optimization=true
4. Switch to split tool:
?mode=split
5. Set split pages and strategy:
?mode=split&pages=1-10&strategy=bookmarks
6. Switch to pageEditor:
?mode=pageEditor (or no params for default)
All URLs are shareable and will restore exact tool state!
*/

View File

@ -279,7 +279,7 @@ export function useEnhancedProcessedFile(
const processedFile = file ? result.processedFiles.get(file) || null : null; const processedFile = file ? result.processedFiles.get(file) || null : null;
// Note: This is async but we can't await in hook return - consider refactoring if needed // Note: This is async but we can't await in hook return - consider refactoring if needed
const fileKey = file ? '' : ''; // TODO: Handle async file key generation const fileKey = file ? '' : '';
const processingState = fileKey ? result.processingStates.get(fileKey) || null : null; const processingState = fileKey ? result.processingStates.get(fileKey) || null : null;
const isProcessing = !!processingState; const isProcessing = !!processingState;
const error = processingState?.error?.message || null; const error = processingState?.error?.message || null;

View File

@ -0,0 +1,51 @@
/**
* React hooks for tool parameter management (URL logic removed)
*/
import { useCallback, useMemo } from 'react';
type ToolParameterValues = Record<string, any>;
/**
* Register tool parameters and get current values
*/
export function useToolParameters(
toolName: string,
parameters: Record<string, any>
): [ToolParameterValues, (updates: Partial<ToolParameterValues>) => void] {
// Return empty values and noop updater
const currentValues = useMemo(() => ({}), []);
const updateParameters = useCallback(() => {}, []);
return [currentValues, updateParameters];
}
/**
* Hook for managing a single tool parameter
*/
export function useToolParameter<T = any>(
toolName: string,
paramName: string,
definition: any
): [T, (value: T) => void] {
const [allParams, updateParams] = useToolParameters(toolName, { [paramName]: definition });
const value = allParams[paramName] as T;
const setValue = useCallback((newValue: T) => {
updateParams({ [paramName]: newValue });
}, [paramName, updateParams]);
return [value, setValue];
}
/**
* Hook for getting/setting global parameters (zoom, page, etc.)
*/
export function useGlobalParameters() {
const currentValues = useMemo(() => ({}), []);
const updateParameters = useCallback(() => {}, []);
return [currentValues, updateParameters];
}

View File

@ -1,130 +0,0 @@
import { useSearchParams } from "react-router-dom";
import { useEffect } from "react";
// Tool parameter definitions (shortened URLs)
const TOOL_PARAMS = {
split: [
"mode", "p", "hd", "vd", "m",
"type", "val", "level", "meta", "dupes"
],
compress: [
"level", "gray", "rmeta", "size", "agg"
],
merge: [
"order", "rdupes"
]
};
// Extract params for a specific tool from URL
function getToolParams(toolKey: string, searchParams: URLSearchParams) {
switch (toolKey) {
case "split":
return {
mode: searchParams.get("mode") || "byPages",
pages: searchParams.get("p") || "",
hDiv: searchParams.get("hd") || "",
vDiv: searchParams.get("vd") || "",
merge: searchParams.get("m") === "true",
splitType: searchParams.get("type") || "size",
splitValue: searchParams.get("val") || "",
bookmarkLevel: searchParams.get("level") || "0",
includeMetadata: searchParams.get("meta") === "true",
allowDuplicates: searchParams.get("dupes") === "true",
};
case "compress":
return {
compressionLevel: parseInt(searchParams.get("level") || "5"),
grayscale: searchParams.get("gray") === "true",
removeMetadata: searchParams.get("rmeta") === "true",
expectedSize: searchParams.get("size") || "",
aggressive: searchParams.get("agg") === "true",
};
case "merge":
return {
order: searchParams.get("order") || "default",
removeDuplicates: searchParams.get("rdupes") === "true",
};
default:
return {};
}
}
// Update tool-specific params in URL
function updateToolParams(toolKey: string, searchParams: URLSearchParams, setSearchParams: any, newParams: any) {
const params = new URLSearchParams(searchParams);
// Clear tool-specific params
if (toolKey === "split") {
["mode", "p", "hd", "vd", "m", "type", "val", "level", "meta", "dupes"].forEach((k) => params.delete(k));
// Set new split params
const merged = { ...getToolParams("split", searchParams), ...newParams };
params.set("mode", merged.mode);
if (merged.mode === "byPages") params.set("p", merged.pages);
else if (merged.mode === "bySections") {
params.set("hd", merged.hDiv);
params.set("vd", merged.vDiv);
params.set("m", String(merged.merge));
} else if (merged.mode === "bySizeOrCount") {
params.set("type", merged.splitType);
params.set("val", merged.splitValue);
} else if (merged.mode === "byChapters") {
params.set("level", merged.bookmarkLevel);
params.set("meta", String(merged.includeMetadata));
params.set("dupes", String(merged.allowDuplicates));
}
} else if (toolKey === "compress") {
["level", "gray", "rmeta", "size", "agg"].forEach((k) => params.delete(k));
const merged = { ...getToolParams("compress", searchParams), ...newParams };
params.set("level", String(merged.compressionLevel));
params.set("gray", String(merged.grayscale));
params.set("rmeta", String(merged.removeMetadata));
if (merged.expectedSize) params.set("size", merged.expectedSize);
params.set("agg", String(merged.aggressive));
} else if (toolKey === "merge") {
["order", "rdupes"].forEach((k) => params.delete(k));
const merged = { ...getToolParams("merge", searchParams), ...newParams };
params.set("order", merged.order);
params.set("rdupes", String(merged.removeDuplicates));
}
setSearchParams(params, { replace: true });
}
export function useToolParams(selectedToolKey: string, currentView: string) {
const [searchParams, setSearchParams] = useSearchParams();
const toolParams = getToolParams(selectedToolKey, searchParams);
const updateParams = (newParams: any) =>
updateToolParams(selectedToolKey, searchParams, setSearchParams, newParams);
// Update URL when core state changes
useEffect(() => {
const params = new URLSearchParams(searchParams);
// Remove all tool-specific params except for the current tool
Object.entries(TOOL_PARAMS).forEach(([tool, keys]) => {
if (tool !== selectedToolKey) {
keys.forEach((k) => params.delete(k));
}
});
// Collect all params except 'v'
const entries = Array.from(params.entries()).filter(([key]) => key !== "v");
// Rebuild params with 'v' first
const newParams = new URLSearchParams();
newParams.set("v", currentView);
newParams.set("t", selectedToolKey);
entries.forEach(([key, value]) => {
if (key !== "t") newParams.set(key, value);
});
setSearchParams(newParams, { replace: true });
}, [selectedToolKey, currentView, setSearchParams]);
return {
toolParams,
updateParams,
};
}

View File

@ -1,7 +1,5 @@
import React, { useState, useCallback, useEffect } from "react"; import React, { useState, useCallback, useEffect } from "react";
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSearchParams } from "react-router-dom";
import { useToolParams } from "../hooks/useToolParams";
import { useFileWithUrl } from "../hooks/useFileWithUrl"; import { useFileWithUrl } from "../hooks/useFileWithUrl";
import { useFileContext } from "../contexts/FileContext"; import { useFileContext } from "../contexts/FileContext";
import { fileStorage } from "../services/fileStorage"; import { fileStorage } from "../services/fileStorage";
@ -45,35 +43,67 @@ const baseToolRegistry = {
export default function HomePage() { export default function HomePage() {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchParams] = useSearchParams();
const theme = useMantineTheme(); const theme = useMantineTheme();
const { isRainbowMode } = useRainbowThemeContext(); const { isRainbowMode } = useRainbowThemeContext();
// Get file context // Get file context
const fileContext = useFileContext(); const fileContext = useFileContext();
const { activeFiles, currentView, setCurrentView, addFiles } = fileContext; const { activeFiles, currentView, currentMode, setCurrentView, addFiles } = fileContext;
// Core app state // Core app state
const [selectedToolKey, setSelectedToolKey] = useState<string>(searchParams.get("t") || "split"); const [selectedToolKey, setSelectedToolKey] = useState<string | null>(null);
// File state separation const [storedFiles, setStoredFiles] = useState<any[]>([]);
const [storedFiles, setStoredFiles] = useState<any[]>([]); // IndexedDB files (FileManager)
const [preSelectedFiles, setPreSelectedFiles] = useState([]); const [preSelectedFiles, setPreSelectedFiles] = useState([]);
const [downloadUrl, setDownloadUrl] = useState<string | null>(null); const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
const [sidebarsVisible, setSidebarsVisible] = useState(true); const [sidebarsVisible, setSidebarsVisible] = useState(true);
const [leftPanelView, setLeftPanelView] = useState<'toolPicker' | 'toolContent'>('toolPicker'); const [leftPanelView, setLeftPanelView] = useState<'toolPicker' | 'toolContent'>('toolPicker');
const [readerMode, setReaderMode] = useState(false); const [readerMode, setReaderMode] = useState(false);
// Page editor functions
const [pageEditorFunctions, setPageEditorFunctions] = useState<any>(null); const [pageEditorFunctions, setPageEditorFunctions] = useState<any>(null);
// URL parameter management // Tool registry
const { toolParams, updateParams } = useToolParams(selectedToolKey, currentView); 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") },
};
// Tool parameters (simplified for now)
const getToolParams = (toolKey: string | null) => {
if (!toolKey) return {};
switch (toolKey) {
case 'split':
return {
mode: 'grid',
pages: '',
hDiv: 2,
vDiv: 2,
merge: false,
splitType: 'pages',
splitValue: 1,
bookmarkLevel: 1,
includeMetadata: true,
allowDuplicates: false
};
case 'compress':
return {
quality: 80,
imageCompression: true,
removeMetadata: false
};
case 'merge':
return {
sortOrder: 'name',
includeMetadata: true
};
default:
return {};
}
};
// Persist active files across reloads
useEffect(() => { useEffect(() => {
// Save active files to localStorage (just metadata)
const activeFileData = activeFiles.map(file => ({ const activeFileData = activeFiles.map(file => ({
name: file.name, name: file.name,
size: file.size, size: file.size,
@ -83,7 +113,6 @@ export default function HomePage() {
localStorage.setItem('activeFiles', JSON.stringify(activeFileData)); localStorage.setItem('activeFiles', JSON.stringify(activeFileData));
}, [activeFiles]); }, [activeFiles]);
// Load stored files from IndexedDB on mount
useEffect(() => { useEffect(() => {
const loadStoredFiles = async () => { const loadStoredFiles = async () => {
try { try {
@ -96,14 +125,12 @@ export default function HomePage() {
loadStoredFiles(); loadStoredFiles();
}, []); }, []);
// Restore active files on load
useEffect(() => { useEffect(() => {
const restoreActiveFiles = async () => { const restoreActiveFiles = async () => {
try { try {
const savedFileData = JSON.parse(localStorage.getItem('activeFiles') || '[]'); const savedFileData = JSON.parse(localStorage.getItem('activeFiles') || '[]');
if (savedFileData.length > 0) { if (savedFileData.length > 0) {
// TODO: Reconstruct files from IndexedDB when fileStorage is available // File restoration handled by FileContext
console.log('Would restore active files:', savedFileData);
} }
} catch (error) { } catch (error) {
console.warn('Failed to restore active files:', error); console.warn('Failed to restore active files:', error);
@ -112,45 +139,30 @@ export default function HomePage() {
restoreActiveFiles(); 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( const handleToolSelect = useCallback(
(id: string) => { (id: string) => {
setSelectedToolKey(id); setSelectedToolKey(id);
if (toolRegistry[id]?.view) setCurrentView(toolRegistry[id].view); if (toolRegistry[id]?.view) setCurrentView(toolRegistry[id].view);
setLeftPanelView('toolContent'); // Switch to tool content view when a tool is selected setLeftPanelView('toolContent');
setReaderMode(false); // Exit reader mode when selecting a tool setReaderMode(false);
}, },
[toolRegistry, setCurrentView] [toolRegistry, setCurrentView]
); );
// Handle quick access actions
const handleQuickAccessTools = useCallback(() => { const handleQuickAccessTools = useCallback(() => {
setLeftPanelView('toolPicker'); setLeftPanelView('toolPicker');
setReaderMode(false); setReaderMode(false);
setSelectedToolKey(null);
}, []); }, []);
const handleReaderToggle = useCallback(() => { const handleReaderToggle = useCallback(() => {
setReaderMode(!readerMode); setReaderMode(!readerMode);
}, [readerMode]); }, [readerMode]);
// Update URL when view changes
const handleViewChange = useCallback((view: string) => { const handleViewChange = useCallback((view: string) => {
setCurrentView(view as any); setCurrentView(view as any);
const params = new URLSearchParams(window.location.search);
params.set('view', view);
const newUrl = `${window.location.pathname}?${params.toString()}`;
window.history.replaceState({}, '', newUrl);
}, [setCurrentView]); }, [setCurrentView]);
// Active file management using context
const addToActiveFiles = useCallback(async (file: File) => { const addToActiveFiles = useCallback(async (file: File) => {
// Check if file already exists
const exists = activeFiles.some(f => f.name === file.name && f.size === file.size); const exists = activeFiles.some(f => f.name === file.name && f.size === file.size);
if (!exists) { if (!exists) {
await addFiles([file]); await addFiles([file]);
@ -162,12 +174,10 @@ export default function HomePage() {
}, [fileContext]); }, [fileContext]);
const setCurrentActiveFile = useCallback(async (file: File) => { const setCurrentActiveFile = useCallback(async (file: File) => {
// Remove if exists, then add to front
const filtered = activeFiles.filter(f => !(f.name === file.name && f.size === file.size)); const filtered = activeFiles.filter(f => !(f.name === file.name && f.size === file.size));
await addFiles([file, ...filtered]); await addFiles([file, ...filtered]);
}, [activeFiles, addFiles]); }, [activeFiles, addFiles]);
// Handle file selection from upload (adds to active files)
const handleFileSelect = useCallback((file: File) => { const handleFileSelect = useCallback((file: File) => {
addToActiveFiles(file); addToActiveFiles(file);
}, [addToActiveFiles]); }, [addToActiveFiles]);
@ -332,7 +342,7 @@ export default function HomePage() {
<Button <Button
variant="subtle" variant="subtle"
size="sm" size="sm"
onClick={() => setLeftPanelView('toolPicker')} onClick={handleQuickAccessTools}
className="text-sm" className="text-sm"
> >
{t("fileUpload.backToTools", "Back to Tools")} {t("fileUpload.backToTools", "Back to Tools")}
@ -353,8 +363,8 @@ export default function HomePage() {
files={activeFiles} files={activeFiles}
downloadUrl={downloadUrl} downloadUrl={downloadUrl}
setDownloadUrl={setDownloadUrl} setDownloadUrl={setDownloadUrl}
toolParams={toolParams} toolParams={getToolParams(selectedToolKey)}
updateParams={updateParams} updateParams={() => {}}
/> />
</div> </div>
</div> </div>
@ -373,6 +383,7 @@ export default function HomePage() {
<TopControls <TopControls
currentView={currentView} currentView={currentView}
setCurrentView={handleViewChange} setCurrentView={handleViewChange}
selectedToolKey={selectedToolKey}
/> />
{/* Main content area */} {/* Main content area */}
<Box <Box

View File

@ -186,7 +186,6 @@ export class PDFExportService {
*/ */
async downloadAsZip(blobs: Blob[], filenames: string[], zipFilename: string): Promise<void> { async downloadAsZip(blobs: Blob[], filenames: string[], zipFilename: string): Promise<void> {
// For now, download files individually // For now, download files individually
// TODO: Implement ZIP creation when needed
blobs.forEach((blob, index) => { blobs.forEach((blob, index) => {
setTimeout(() => { setTimeout(() => {
this.downloadFile(blob, filenames[index]); this.downloadFile(blob, filenames[index]);

View File

@ -1,5 +1,4 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useSearchParams } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Stack, Slider, Group, Text, Button, Checkbox, TextInput, Paper } from "@mantine/core"; import { Stack, Slider, Group, Text, Button, Checkbox, TextInput, Paper } from "@mantine/core";
import { FileWithUrl } from "../types/file"; import { FileWithUrl } from "../types/file";

View File

@ -1,6 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Paper, Button, Checkbox, Stack, Text, Group, Loader, Alert } from "@mantine/core"; import { Paper, Button, Checkbox, Stack, Text, Group, Loader, Alert } from "@mantine/core";
import { useSearchParams } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FileWithUrl } from "../types/file"; import { FileWithUrl } from "../types/file";
import { fileStorage } from "../services/fileStorage"; import { fileStorage } from "../services/fileStorage";
@ -22,7 +21,6 @@ const MergePdfPanel: React.FC<MergePdfPanelProps> = ({
updateParams, updateParams,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchParams] = useSearchParams();
const [selectedFiles, setSelectedFiles] = useState<boolean[]>([]); const [selectedFiles, setSelectedFiles] = useState<boolean[]>([]);
const [downloadUrl, setLocalDownloadUrl] = useState<string | null>(null); const [downloadUrl, setLocalDownloadUrl] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);

View File

@ -9,7 +9,6 @@ import {
Stack, Stack,
Paper, Paper,
} from "@mantine/core"; } from "@mantine/core";
import { useSearchParams } from "react-router-dom";
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 { FileWithUrl } from "../types/file"; import { FileWithUrl } from "../types/file";
@ -42,7 +41,6 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
updateParams, updateParams,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchParams] = useSearchParams();
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);

View File

@ -5,8 +5,10 @@
import { ProcessedFile } from './processing'; import { ProcessedFile } from './processing';
import { PDFDocument, PDFPage, PageOperation } from './pageEditor'; import { PDFDocument, PDFPage, PageOperation } from './pageEditor';
export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor'; export type ModeType = 'viewer' | 'pageEditor' | 'fileEditor' | 'merge' | 'split' | 'compress';
// Legacy types for backward compatibility during transition
export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor';
export type ToolType = 'merge' | 'split' | 'compress' | null; export type ToolType = 'merge' | 'split' | 'compress' | null;
export interface FileOperation { export interface FileOperation {
@ -36,6 +38,8 @@ export interface FileContextState {
processedFiles: Map<File, ProcessedFile>; processedFiles: Map<File, ProcessedFile>;
// Current navigation state // Current navigation state
currentMode: ModeType;
// Legacy fields for backward compatibility
currentView: ViewType; currentView: ViewType;
currentTool: ToolType; currentTool: ToolType;
@ -73,6 +77,8 @@ export interface FileContextActions {
clearAllFiles: () => void; clearAllFiles: () => void;
// Navigation // Navigation
setCurrentMode: (mode: ModeType) => void;
// Legacy navigation functions for backward compatibility
setCurrentView: (view: ViewType) => void; setCurrentView: (view: ViewType) => void;
setCurrentTool: (tool: ToolType) => void; setCurrentTool: (tool: ToolType) => void;
@ -134,6 +140,8 @@ export interface WithFileContext {
// URL parameter types for deep linking // URL parameter types for deep linking
export interface FileContextUrlParams { export interface FileContextUrlParams {
mode?: ModeType;
// Legacy parameters for backward compatibility
view?: ViewType; view?: ViewType;
tool?: ToolType; tool?: ToolType;
fileIds?: string[]; fileIds?: string[];