mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
Remove url params for now. small refactor
This commit is contained in:
parent
33420c7e80
commit
666c15fabd
@ -54,14 +54,18 @@ const createViewOptions = (switchingTo: string | null) => [
|
||||
interface TopControlsProps {
|
||||
currentView: string;
|
||||
setCurrentView: (view: string) => void;
|
||||
selectedToolKey?: string | null;
|
||||
}
|
||||
|
||||
const TopControls = ({
|
||||
currentView,
|
||||
setCurrentView,
|
||||
selectedToolKey,
|
||||
}: TopControlsProps) => {
|
||||
const { themeMode, isRainbowMode, isToggleDisabled, toggleTheme } = useRainbowThemeContext();
|
||||
const [switchingTo, setSwitchingTo] = useState<string | null>(null);
|
||||
|
||||
const isToolSelected = selectedToolKey !== null;
|
||||
|
||||
const handleViewChange = useCallback((view: string) => {
|
||||
// Show immediate feedback
|
||||
@ -108,22 +112,24 @@ const TopControls = ({
|
||||
</Button>
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<div className="flex justify-center items-center h-full pointer-events-auto">
|
||||
<SegmentedControl
|
||||
data={createViewOptions(switchingTo)}
|
||||
value={currentView}
|
||||
onChange={handleViewChange}
|
||||
color="blue"
|
||||
radius="xl"
|
||||
size="md"
|
||||
fullWidth
|
||||
className={isRainbowMode ? rainbowStyles.rainbowSegmentedControl : ''}
|
||||
style={{
|
||||
transition: 'all 0.2s ease',
|
||||
opacity: switchingTo ? 0.8 : 1,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{!isToolSelected && (
|
||||
<div className="flex justify-center items-center h-full pointer-events-auto">
|
||||
<SegmentedControl
|
||||
data={createViewOptions(switchingTo)}
|
||||
value={currentView}
|
||||
onChange={handleViewChange}
|
||||
color="blue"
|
||||
radius="xl"
|
||||
size="md"
|
||||
fullWidth
|
||||
className={isRainbowMode ? rainbowStyles.rainbowSegmentedControl : ''}
|
||||
style={{
|
||||
transition: 'all 0.2s ease',
|
||||
opacity: switchingTo ? 0.8 : 1,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ type ToolRegistry = {
|
||||
};
|
||||
|
||||
interface ToolPickerProps {
|
||||
selectedToolKey: string;
|
||||
selectedToolKey: string | null;
|
||||
onSelect: (id: string) => void;
|
||||
toolRegistry: ToolRegistry;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ const ToolRenderer = ({
|
||||
<ToolComponent
|
||||
files={files}
|
||||
setDownloadUrl={setDownloadUrl}
|
||||
setLoading={(loading: boolean) => {}} // TODO: Add loading state
|
||||
setLoading={(loading: boolean) => {}}
|
||||
params={toolParams}
|
||||
updateParams={updateParams}
|
||||
/>
|
||||
|
@ -3,11 +3,11 @@
|
||||
*/
|
||||
|
||||
import React, { createContext, useContext, useReducer, useCallback, useEffect, useRef } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
FileContextValue,
|
||||
FileContextState,
|
||||
FileContextProviderProps,
|
||||
ModeType,
|
||||
ViewType,
|
||||
ToolType,
|
||||
FileOperation,
|
||||
@ -33,8 +33,9 @@ const initialViewerConfig: ViewerConfig = {
|
||||
const initialState: FileContextState = {
|
||||
activeFiles: [],
|
||||
processedFiles: new Map(),
|
||||
currentView: 'fileEditor',
|
||||
currentTool: null,
|
||||
currentMode: 'pageEditor',
|
||||
currentView: 'fileEditor', // Legacy field
|
||||
currentTool: null, // Legacy field
|
||||
fileEditHistory: new Map(),
|
||||
globalFileOperations: [],
|
||||
selectedFileIds: [],
|
||||
@ -55,6 +56,7 @@ type FileContextAction =
|
||||
| { type: 'REMOVE_FILES'; payload: string[] }
|
||||
| { type: 'SET_PROCESSED_FILES'; payload: Map<File, 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_TOOL'; payload: ToolType }
|
||||
| { type: 'SET_SELECTED_FILES'; payload: string[] }
|
||||
@ -112,17 +114,33 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
|
||||
processedFiles: updatedProcessedFiles
|
||||
};
|
||||
|
||||
case 'SET_CURRENT_VIEW':
|
||||
case 'SET_CURRENT_MODE':
|
||||
const coreViews = ['viewer', 'pageEditor', 'fileEditor'];
|
||||
const isToolMode = !coreViews.includes(action.payload);
|
||||
|
||||
return {
|
||||
...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,
|
||||
// Clear tool when switching views
|
||||
currentTool: null
|
||||
};
|
||||
|
||||
case 'SET_CURRENT_TOOL':
|
||||
// Legacy action - just update currentMode
|
||||
return {
|
||||
...state,
|
||||
currentMode: action.payload ? action.payload as ModeType : 'pageEditor',
|
||||
currentView: action.payload ? 'fileEditor' : 'pageEditor',
|
||||
currentTool: action.payload
|
||||
};
|
||||
|
||||
@ -233,7 +251,6 @@ export function FileContextProvider({
|
||||
maxCacheSize = 1024 * 1024 * 1024 // 1GB
|
||||
}: FileContextProviderProps) {
|
||||
const [state, dispatch] = useReducer(fileContextReducer, initialState);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
// Cleanup timers and refs
|
||||
const cleanupTimers = useRef<Map<string, NodeJS.Timeout>>(new Map());
|
||||
@ -266,69 +283,6 @@ export function FileContextProvider({
|
||||
});
|
||||
}, [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
|
||||
const trackBlobUrl = useCallback((url: string) => {
|
||||
@ -524,6 +478,20 @@ export function FileContextProvider({
|
||||
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) => {
|
||||
requestNavigation(() => {
|
||||
dispatch({ type: 'SET_CURRENT_VIEW', payload: view });
|
||||
@ -572,7 +540,6 @@ export function FileContextProvider({
|
||||
}, []);
|
||||
|
||||
const undoLastOperation = useCallback((fileId?: string) => {
|
||||
// TODO: Implement undo logic
|
||||
console.warn('Undo not yet implemented');
|
||||
}, []);
|
||||
|
||||
@ -676,6 +643,7 @@ export function FileContextProvider({
|
||||
removeFiles,
|
||||
replaceFile,
|
||||
clearAllFiles,
|
||||
setCurrentMode,
|
||||
setCurrentView,
|
||||
setCurrentTool,
|
||||
setSelectedFiles,
|
||||
|
90
frontend/src/examples/ExampleToolUsage.tsx
Normal file
90
frontend/src/examples/ExampleToolUsage.tsx
Normal 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!
|
||||
*/
|
@ -279,7 +279,7 @@ export function useEnhancedProcessedFile(
|
||||
|
||||
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
|
||||
const fileKey = file ? '' : ''; // TODO: Handle async file key generation
|
||||
const fileKey = file ? '' : '';
|
||||
const processingState = fileKey ? result.processingStates.get(fileKey) || null : null;
|
||||
const isProcessing = !!processingState;
|
||||
const error = processingState?.error?.message || null;
|
||||
|
51
frontend/src/hooks/useToolParameters.ts
Normal file
51
frontend/src/hooks/useToolParameters.ts
Normal 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];
|
||||
}
|
@ -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,
|
||||
};
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
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 { useFileContext } from "../contexts/FileContext";
|
||||
import { fileStorage } from "../services/fileStorage";
|
||||
@ -45,35 +43,67 @@ const baseToolRegistry = {
|
||||
|
||||
export default function HomePage() {
|
||||
const { t } = useTranslation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const theme = useMantineTheme();
|
||||
const { isRainbowMode } = useRainbowThemeContext();
|
||||
|
||||
// Get file context
|
||||
const fileContext = useFileContext();
|
||||
const { activeFiles, currentView, setCurrentView, addFiles } = fileContext;
|
||||
const { activeFiles, currentView, currentMode, setCurrentView, addFiles } = fileContext;
|
||||
|
||||
// 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[]>([]); // IndexedDB files (FileManager)
|
||||
const [storedFiles, setStoredFiles] = useState<any[]>([]);
|
||||
const [preSelectedFiles, setPreSelectedFiles] = useState([]);
|
||||
|
||||
const [downloadUrl, setDownloadUrl] = useState<string | null>(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<any>(null);
|
||||
|
||||
// URL parameter management
|
||||
const { toolParams, updateParams } = useToolParams(selectedToolKey, currentView);
|
||||
// Tool registry
|
||||
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(() => {
|
||||
// Save active files to localStorage (just metadata)
|
||||
const activeFileData = activeFiles.map(file => ({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
@ -83,7 +113,6 @@ export default function HomePage() {
|
||||
localStorage.setItem('activeFiles', JSON.stringify(activeFileData));
|
||||
}, [activeFiles]);
|
||||
|
||||
// Load stored files from IndexedDB on mount
|
||||
useEffect(() => {
|
||||
const loadStoredFiles = async () => {
|
||||
try {
|
||||
@ -96,14 +125,12 @@ export default function HomePage() {
|
||||
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);
|
||||
// File restoration handled by FileContext
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to restore active files:', error);
|
||||
@ -112,45 +139,30 @@ export default function HomePage() {
|
||||
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
|
||||
setLeftPanelView('toolContent');
|
||||
setReaderMode(false);
|
||||
},
|
||||
[toolRegistry, setCurrentView]
|
||||
);
|
||||
|
||||
// Handle quick access actions
|
||||
const handleQuickAccessTools = useCallback(() => {
|
||||
setLeftPanelView('toolPicker');
|
||||
setReaderMode(false);
|
||||
setSelectedToolKey(null);
|
||||
}, []);
|
||||
|
||||
const handleReaderToggle = useCallback(() => {
|
||||
setReaderMode(!readerMode);
|
||||
}, [readerMode]);
|
||||
|
||||
// Update URL when view changes
|
||||
const handleViewChange = useCallback((view: string) => {
|
||||
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]);
|
||||
|
||||
// Active file management using context
|
||||
const addToActiveFiles = useCallback(async (file: File) => {
|
||||
// Check if file already exists
|
||||
const exists = activeFiles.some(f => f.name === file.name && f.size === file.size);
|
||||
if (!exists) {
|
||||
await addFiles([file]);
|
||||
@ -162,12 +174,10 @@ export default function HomePage() {
|
||||
}, [fileContext]);
|
||||
|
||||
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));
|
||||
await addFiles([file, ...filtered]);
|
||||
}, [activeFiles, addFiles]);
|
||||
|
||||
// Handle file selection from upload (adds to active files)
|
||||
const handleFileSelect = useCallback((file: File) => {
|
||||
addToActiveFiles(file);
|
||||
}, [addToActiveFiles]);
|
||||
@ -332,7 +342,7 @@ export default function HomePage() {
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={() => setLeftPanelView('toolPicker')}
|
||||
onClick={handleQuickAccessTools}
|
||||
className="text-sm"
|
||||
>
|
||||
← {t("fileUpload.backToTools", "Back to Tools")}
|
||||
@ -353,8 +363,8 @@ export default function HomePage() {
|
||||
files={activeFiles}
|
||||
downloadUrl={downloadUrl}
|
||||
setDownloadUrl={setDownloadUrl}
|
||||
toolParams={toolParams}
|
||||
updateParams={updateParams}
|
||||
toolParams={getToolParams(selectedToolKey)}
|
||||
updateParams={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -373,6 +383,7 @@ export default function HomePage() {
|
||||
<TopControls
|
||||
currentView={currentView}
|
||||
setCurrentView={handleViewChange}
|
||||
selectedToolKey={selectedToolKey}
|
||||
/>
|
||||
{/* Main content area */}
|
||||
<Box
|
||||
|
@ -186,7 +186,6 @@ export class PDFExportService {
|
||||
*/
|
||||
async downloadAsZip(blobs: Blob[], filenames: string[], zipFilename: string): Promise<void> {
|
||||
// For now, download files individually
|
||||
// TODO: Implement ZIP creation when needed
|
||||
blobs.forEach((blob, index) => {
|
||||
setTimeout(() => {
|
||||
this.downloadFile(blob, filenames[index]);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Stack, Slider, Group, Text, Button, Checkbox, TextInput, Paper } from "@mantine/core";
|
||||
import { FileWithUrl } from "../types/file";
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Paper, Button, Checkbox, Stack, Text, Group, Loader, Alert } from "@mantine/core";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FileWithUrl } from "../types/file";
|
||||
import { fileStorage } from "../services/fileStorage";
|
||||
@ -22,7 +21,6 @@ const MergePdfPanel: React.FC<MergePdfPanelProps> = ({
|
||||
updateParams,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [selectedFiles, setSelectedFiles] = useState<boolean[]>([]);
|
||||
const [downloadUrl, setLocalDownloadUrl] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
Stack,
|
||||
Paper,
|
||||
} from "@mantine/core";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import { FileWithUrl } from "../types/file";
|
||||
@ -42,7 +41,6 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
|
||||
updateParams,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const [status, setStatus] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
@ -5,8 +5,10 @@
|
||||
import { ProcessedFile } from './processing';
|
||||
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 interface FileOperation {
|
||||
@ -36,6 +38,8 @@ export interface FileContextState {
|
||||
processedFiles: Map<File, ProcessedFile>;
|
||||
|
||||
// Current navigation state
|
||||
currentMode: ModeType;
|
||||
// Legacy fields for backward compatibility
|
||||
currentView: ViewType;
|
||||
currentTool: ToolType;
|
||||
|
||||
@ -73,6 +77,8 @@ export interface FileContextActions {
|
||||
clearAllFiles: () => void;
|
||||
|
||||
// Navigation
|
||||
setCurrentMode: (mode: ModeType) => void;
|
||||
// Legacy navigation functions for backward compatibility
|
||||
setCurrentView: (view: ViewType) => void;
|
||||
setCurrentTool: (tool: ToolType) => void;
|
||||
|
||||
@ -134,6 +140,8 @@ export interface WithFileContext {
|
||||
|
||||
// URL parameter types for deep linking
|
||||
export interface FileContextUrlParams {
|
||||
mode?: ModeType;
|
||||
// Legacy parameters for backward compatibility
|
||||
view?: ViewType;
|
||||
tool?: ToolType;
|
||||
fileIds?: string[];
|
||||
|
Loading…
Reference in New Issue
Block a user