Homepage refactor, tool management refactor

This commit is contained in:
Reece 2025-07-14 23:38:33 +01:00
parent 5d7c572929
commit cb5c35249f
5 changed files with 194 additions and 347 deletions

View File

@ -1,13 +1,10 @@
import React from "react";
import { FileWithUrl } from "../../types/file";
import { useToolManagement } from "../../hooks/useToolManagement";
interface ToolRendererProps {
selectedToolKey: string;
selectedTool: any;
pdfFile: any;
files: FileWithUrl[];
downloadUrl: string | null;
setDownloadUrl: (url: string | null) => void;
toolParams: any;
updateParams: (params: any) => void;
toolSelectedFiles?: File[];
@ -16,18 +13,18 @@ interface ToolRendererProps {
const ToolRenderer = ({
selectedToolKey,
selectedTool,
pdfFile,
files,
downloadUrl,
setDownloadUrl,
toolParams,
updateParams,
toolSelectedFiles = [],
onPreviewFile,
}: ToolRendererProps) => {
// Get the tool from registry
const { toolRegistry } = useToolManagement();
const selectedTool = toolRegistry[selectedToolKey];
if (!selectedTool || !selectedTool.component) {
return <div>Tool not found</div>;
return <div>Tool not found: {selectedToolKey}</div>;
}
const ToolComponent = selectedTool.component;
@ -37,8 +34,6 @@ const ToolRenderer = ({
case "split":
return (
<ToolComponent
params={toolParams}
updateParams={updateParams}
selectedFiles={toolSelectedFiles}
onPreviewFile={onPreviewFile}
/>
@ -47,7 +42,6 @@ const ToolRenderer = ({
return (
<ToolComponent
files={files}
setDownloadUrl={setDownloadUrl}
setLoading={(loading: boolean) => {}}
params={toolParams}
updateParams={updateParams}
@ -57,7 +51,6 @@ const ToolRenderer = ({
return (
<ToolComponent
files={files}
setDownloadUrl={setDownloadUrl}
params={toolParams}
updateParams={updateParams}
/>
@ -66,7 +59,6 @@ const ToolRenderer = ({
return (
<ToolComponent
files={files}
setDownloadUrl={setDownloadUrl}
params={toolParams}
updateParams={updateParams}
/>

View File

@ -13,6 +13,8 @@ import CloseIcon from "@mui/icons-material/Close";
import { useLocalStorage } from "@mantine/hooks";
import { fileStorage } from "../../services/fileStorage";
import SkeletonLoader from '../shared/SkeletonLoader';
import { useFileContext } from "../../contexts/FileContext";
import { useFileWithUrl } from "../../hooks/useFileWithUrl";
GlobalWorkerOptions.workerSrc = "/pdf.worker.js";
@ -132,8 +134,6 @@ const LazyPageImage = ({
};
export interface ViewerProps {
pdfFile: { file: File; url: string } | null; // First file in the array
setPdfFile: (file: { file: File; url: string } | null) => void;
sidebarsVisible: boolean;
setSidebarsVisible: (v: boolean) => void;
onClose?: () => void;
@ -141,8 +141,6 @@ export interface ViewerProps {
}
const Viewer = ({
pdfFile,
setPdfFile,
sidebarsVisible,
setSidebarsVisible,
onClose,
@ -150,6 +148,14 @@ const Viewer = ({
}: ViewerProps) => {
const { t } = useTranslation();
const theme = useMantineTheme();
// Get current file from FileContext
const { getCurrentFile, getCurrentProcessedFile, clearAllFiles, addFiles } = useFileContext();
const currentFile = getCurrentFile();
const processedFile = getCurrentProcessedFile();
// Convert File to FileWithUrl format for viewer
const pdfFile = useFileWithUrl(currentFile);
const [numPages, setNumPages] = useState<number>(0);
const [pageImages, setPageImages] = useState<string[]>([]);
const [loading, setLoading] = useState<boolean>(false);
@ -448,8 +454,7 @@ const Viewer = ({
onChange={(e) => {
const file = e.target.files?.[0];
if (file && file.type === "application/pdf") {
const fileUrl = URL.createObjectURL(file);
setPdfFile({ file, url: fileUrl });
addFiles([file]);
}
}}
/>

View File

@ -0,0 +1,129 @@
import React, { useState, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import AddToPhotosIcon from "@mui/icons-material/AddToPhotos";
import ContentCutIcon from "@mui/icons-material/ContentCut";
import ZoomInMapIcon from "@mui/icons-material/ZoomInMap";
import SplitPdfPanel from "../tools/Split";
import CompressPdfPanel from "../tools/Compress";
import MergePdfPanel from "../tools/Merge";
type ToolRegistryEntry = {
icon: React.ReactNode;
name: string;
component: React.ComponentType<any>;
view: string;
};
type ToolRegistry = {
[key: string]: ToolRegistryEntry;
};
// Base tool registry without translations
const baseToolRegistry = {
split: { icon: <ContentCutIcon />, component: SplitPdfPanel, view: "split" },
compress: { icon: <ZoomInMapIcon />, component: CompressPdfPanel, view: "viewer" },
merge: { icon: <AddToPhotosIcon />, component: MergePdfPanel, view: "pageEditor" },
};
// Tool parameter defaults
const getToolDefaults = (toolKey: string) => {
switch (toolKey) {
case 'split':
return {
mode: '',
pages: '',
hDiv: '2',
vDiv: '2',
merge: false,
splitType: 'size',
splitValue: '',
bookmarkLevel: '1',
includeMetadata: false,
allowDuplicates: false,
};
case 'compress':
return {
quality: 80,
imageCompression: true,
removeMetadata: false
};
case 'merge':
return {
sortOrder: 'name',
includeMetadata: true
};
default:
return {};
}
};
export const useToolManagement = () => {
const { t } = useTranslation();
const [selectedToolKey, setSelectedToolKey] = useState<string | null>(null);
const [toolSelectedFileIds, setToolSelectedFileIds] = useState<string[]>([]);
const [toolParams, setToolParams] = useState<Record<string, any>>({});
// Tool registry with translations
const toolRegistry: ToolRegistry = useMemo(() => ({
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") },
}), [t]);
// Get tool parameters with defaults
const getToolParams = useCallback((toolKey: string | null) => {
if (!toolKey) return {};
const storedParams = toolParams[toolKey] || {};
const defaultParams = getToolDefaults(toolKey);
return { ...defaultParams, ...storedParams };
}, [toolParams]);
// Update tool parameters
const updateToolParams = useCallback((toolKey: string, newParams: any) => {
setToolParams(prev => ({
...prev,
[toolKey]: {
...prev[toolKey],
...newParams
}
}));
}, []);
// Select tool
const selectTool = useCallback((toolKey: string) => {
setSelectedToolKey(toolKey);
}, []);
// Clear tool selection
const clearToolSelection = useCallback(() => {
setSelectedToolKey(null);
}, []);
// Get currently selected tool
const selectedTool = selectedToolKey ? toolRegistry[selectedToolKey] : null;
return {
// State
selectedToolKey,
selectedTool,
toolSelectedFileIds,
toolParams: getToolParams(selectedToolKey),
toolRegistry,
// Actions
selectTool,
clearToolSelection,
updateToolParams: (newParams: any) => {
if (selectedToolKey) {
updateToolParams(selectedToolKey, newParams);
}
},
setToolSelectedFileIds,
// Utilities
getToolParams,
};
};

View File

@ -1,12 +1,8 @@
import React, { useState, useCallback, useEffect } from "react";
import React, { useState, useCallback} from "react";
import { useTranslation } from 'react-i18next';
import { useFileWithUrl } from "../hooks/useFileWithUrl";
import { useFileContext } from "../contexts/FileContext";
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 { useToolManagement } from "../hooks/useToolManagement";
import { Group, Box, Button, Container } from "@mantine/core";
import { useRainbowThemeContext } from "../components/shared/RainbowThemeProvider";
import rainbowStyles from '../styles/rainbow.module.css';
@ -17,163 +13,50 @@ 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<any>;
view: string;
};
type ToolRegistry = {
[key: string]: ToolRegistryEntry;
};
// Base tool registry without translations
const baseToolRegistry = {
split: { icon: <ContentCutIcon />, component: SplitPdfPanel, view: "split" },
compress: { icon: <ZoomInMapIcon />, component: CompressPdfPanel, view: "viewer" },
merge: { icon: <AddToPhotosIcon />, component: MergePdfPanel, view: "pageEditor" },
};
export default function HomePage() {
const { t } = useTranslation();
const theme = useMantineTheme();
const { isRainbowMode } = useRainbowThemeContext();
// Get file context
const fileContext = useFileContext();
const { activeFiles, currentView, currentMode, setCurrentView, addFiles } = fileContext;
// Core app state
const [selectedToolKey, setSelectedToolKey] = useState<string | null>(null);
const {
selectedToolKey,
selectedTool,
toolParams,
toolRegistry,
selectTool,
clearToolSelection,
updateToolParams,
} = useToolManagement();
const [storedFiles, setStoredFiles] = useState<any[]>([]);
const [preSelectedFiles, setPreSelectedFiles] = useState([]);
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
const [toolSelectedFiles, setToolSelectedFiles] = useState<File[]>([]);
const [sidebarsVisible, setSidebarsVisible] = useState(true);
const [leftPanelView, setLeftPanelView] = useState<'toolPicker' | 'toolContent'>('toolPicker');
const [readerMode, setReaderMode] = useState(false);
const [pageEditorFunctions, setPageEditorFunctions] = useState<any>(null);
const [toolSelectedFiles, setToolSelectedFiles] = useState<File[]>([]);
const [toolParams, setToolParams] = useState<Record<string, any>>({});
const [previewFile, setPreviewFile] = useState<File | null>(null);
// 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 with state management
const getToolParams = (toolKey: string | null) => {
if (!toolKey) return {};
// Get stored params for this tool, or use defaults
const storedParams = toolParams[toolKey] || {};
const defaultParams = (() => {
switch (toolKey) {
case 'split':
return {
mode: '',
pages: '',
hDiv: '2',
vDiv: '2',
merge: false,
splitType: 'size',
splitValue: '',
bookmarkLevel: '1',
includeMetadata: false,
allowDuplicates: false,
};
case 'compress':
return {
quality: 80,
imageCompression: true,
removeMetadata: false
};
case 'merge':
return {
sortOrder: 'name',
includeMetadata: true
};
default:
return {};
}
})();
return { ...defaultParams, ...storedParams };
};
const updateToolParams = useCallback((toolKey: string, newParams: any) => {
setToolParams(prev => ({
...prev,
[toolKey]: {
...prev[toolKey],
...newParams
}
}));
}, []);
useEffect(() => {
const activeFileData = activeFiles.map(file => ({
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified
}));
localStorage.setItem('activeFiles', JSON.stringify(activeFileData));
}, [activeFiles]);
useEffect(() => {
const loadStoredFiles = async () => {
try {
const files = await fileStorage.getAllFiles();
setStoredFiles(files);
} catch (error) {
console.warn('Failed to load stored files:', error);
}
};
loadStoredFiles();
}, []);
useEffect(() => {
const restoreActiveFiles = async () => {
try {
const savedFileData = JSON.parse(localStorage.getItem('activeFiles') || '[]');
if (savedFileData.length > 0) {
// File restoration handled by FileContext
}
} catch (error) {
console.warn('Failed to restore active files:', error);
}
};
restoreActiveFiles();
}, []);
const handleToolSelect = useCallback(
(id: string) => {
setSelectedToolKey(id);
selectTool(id);
if (toolRegistry[id]?.view) setCurrentView(toolRegistry[id].view);
setLeftPanelView('toolContent');
setReaderMode(false);
},
[toolRegistry, setCurrentView]
[selectTool, toolRegistry, setCurrentView]
);
const handleQuickAccessTools = useCallback(() => {
setLeftPanelView('toolPicker');
setReaderMode(false);
setSelectedToolKey(null);
}, []);
clearToolSelection();
}, [clearToolSelection]);
const handleReaderToggle = useCallback(() => {
setReaderMode(!readerMode);
@ -190,123 +73,7 @@ export default function HomePage() {
}
}, [activeFiles, addFiles]);
const removeFromActiveFiles = useCallback((file: File) => {
fileContext.removeFiles([file.name]);
}, [fileContext]);
const setCurrentActiveFile = useCallback(async (file: File) => {
const filtered = activeFiles.filter(f => !(f.name === file.name && f.size === file.size));
await addFiles([file, ...filtered]);
}, [activeFiles, addFiles]);
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);
await addFiles(validFiles);
setPreSelectedFiles([]); // Clear preselected since we're using activeFiles now
handleViewChange("fileEditor");
} catch (error) {
console.error('Error converting selected files:', error);
}
}, [handleViewChange, addFiles]);
// 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);
await addFiles(validFiles);
handleViewChange("pageEditor");
} catch (error) {
console.error('Error converting selected files for page editor:', error);
}
}, [handleViewChange, addFiles]);
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 (
<Group
@ -324,17 +91,12 @@ export default function HomePage() {
readerMode={readerMode}
/>
{/* Left: Tool Picker OR Selected Tool Panel */}
{/* Left: Tool Picker or Selected Tool Panel */}
<div
className={`h-screen z-sticky flex flex-col ${isRainbowMode ? rainbowStyles.rainbowPaper : ''} overflow-hidden`}
className={`h-screen flex flex-col overflow-hidden bg-[var(--bg-surface)] border-r border-[var(--border-subtle)] transition-all duration-300 ease-out ${isRainbowMode ? rainbowStyles.rainbowPaper : ''}`}
style={{
backgroundColor: 'var(--bg-surface)',
borderRight: '1px solid var(--border-subtle)',
width: sidebarsVisible && !readerMode ? '25vw' : '0px',
minWidth: sidebarsVisible && !readerMode ? '300px' : '0px',
maxWidth: sidebarsVisible && !readerMode ? '450px' : '0px',
transition: 'width 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), min-width 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), max-width 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
padding: sidebarsVisible && !readerMode ? '1rem' : '0rem'
width: sidebarsVisible && !readerMode ? '14vw' : '0',
padding: sidebarsVisible && !readerMode ? '1rem' : '0'
}}
>
<div
@ -379,13 +141,6 @@ export default function HomePage() {
<div className="flex-1 min-h-0">
<ToolRenderer
selectedToolKey={selectedToolKey}
selectedTool={selectedTool}
pdfFile={activeFiles[0] || null}
files={activeFiles}
downloadUrl={downloadUrl}
setDownloadUrl={setDownloadUrl}
toolParams={getToolParams(selectedToolKey)}
updateParams={(newParams) => updateToolParams(selectedToolKey, newParams)}
toolSelectedFiles={toolSelectedFiles}
onPreviewFile={setPreviewFile}
/>
@ -423,7 +178,6 @@ export default function HomePage() {
: t("fileUpload.selectPdfToEdit", "Select a PDF to edit")
}
subtitle={t("fileUpload.chooseFromStorage", "Choose a file from storage or upload a new PDF")}
sharedFiles={storedFiles}
onFileSelect={(file) => {
addToActiveFiles(file);
}}
@ -439,7 +193,6 @@ export default function HomePage() {
) : currentView === "fileEditor" ? (
<FileEditor
onOpenPageEditor={(file) => {
setCurrentActiveFile(file);
handleViewChange("pageEditor");
}}
onMergeFiles={(filesToMerge) => {
@ -450,14 +203,6 @@ export default function HomePage() {
/>
) : currentView === "viewer" ? (
<Viewer
pdfFile={currentFileWithUrl}
setPdfFile={(fileObj) => {
if (fileObj) {
setCurrentActiveFile(fileObj.file);
} else {
fileContext.clearAllFiles();
}
}}
sidebarsVisible={sidebarsVisible}
setSidebarsVisible={setSidebarsVisible}
previewFile={previewFile}
@ -466,7 +211,7 @@ export default function HomePage() {
setPreviewFile(null); // Clear preview file
const previousMode = sessionStorage.getItem('previousMode');
if (previousMode === 'split') {
setSelectedToolKey('split');
selectTool('split');
setCurrentView('split');
setLeftPanelView('toolContent');
sessionStorage.removeItem('previousMode');
@ -512,20 +257,12 @@ export default function HomePage() {
) : selectedToolKey && selectedTool ? (
<ToolRenderer
selectedToolKey={selectedToolKey}
selectedTool={selectedTool}
pdfFile={activeFiles[0] || null}
files={activeFiles}
downloadUrl={downloadUrl}
setDownloadUrl={setDownloadUrl}
toolParams={getToolParams(selectedToolKey)}
updateParams={() => {}}
/>
) : (
<Container size="lg" p="xl" h="100%" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<FileUploadSelector
title="File Management"
subtitle="Choose files from storage or upload new PDFs"
sharedFiles={storedFiles}
onFileSelect={(file) => {
addToActiveFiles(file);
}}

View File

@ -26,26 +26,11 @@ import { generateThumbnailForFile } from "../utils/thumbnailUtils";
import FileEditor from "../components/fileEditor/FileEditor";
export interface SplitPdfPanelProps {
params: {
mode: string;
pages: string;
hDiv: string;
vDiv: string;
merge: boolean;
splitType: string;
splitValue: string;
bookmarkLevel: string;
includeMetadata: boolean;
allowDuplicates: boolean;
};
updateParams: (newParams: Partial<SplitPdfPanelProps["params"]>) => void;
selectedFiles?: File[];
onPreviewFile?: (file: File | null) => void;
}
const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
params,
updateParams,
selectedFiles = [],
onPreviewFile,
}) => {
@ -53,6 +38,18 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
const fileContext = useFileContext();
const { activeFiles, selectedFileIds, updateProcessedFile, recordOperation, markOperationApplied, markOperationFailed, setCurrentMode } = fileContext;
// Internal split parameter state
const [mode, setMode] = useState('');
const [pages, setPages] = useState('');
const [hDiv, setHDiv] = useState('2');
const [vDiv, setVDiv] = useState('2');
const [merge, setMerge] = useState(false);
const [splitType, setSplitType] = useState('size');
const [splitValue, setSplitValue] = useState('');
const [bookmarkLevel, setBookmarkLevel] = useState('1');
const [includeMetadata, setIncludeMetadata] = useState(false);
const [allowDuplicates, setAllowDuplicates] = useState(false);
const [status, setStatus] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
@ -67,19 +64,6 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
isGeneratingThumbnails: false
});
const {
mode,
pages,
hDiv,
vDiv,
merge,
splitType,
splitValue,
bookmarkLevel,
includeMetadata,
allowDuplicates,
} = params;
// Clear download when parameters or files change
React.useEffect(() => {
if (downloadUrl) {
@ -339,7 +323,7 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
label="Choose split method"
placeholder="Select how to split the PDF"
value={mode}
onChange={(v) => v && updateParams({ mode: v })}
onChange={(v) => v && setMode(v)}
data={[
{ value: "byPages", label: t("split.header", "Split by Pages") + " (e.g. 1,3,5-10)" },
{ value: "bySections", label: t("split-by-sections.title", "Split by Grid Sections") },
@ -354,7 +338,7 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
label={t("split.splitPages", "Pages")}
placeholder={t("pageSelectionPrompt", "e.g. 1,3,5-10")}
value={pages}
onChange={(e) => updateParams({ pages: e.target.value })}
onChange={(e) => setPages(e.target.value)}
/>
)}
@ -366,7 +350,7 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
min="0"
max="300"
value={hDiv}
onChange={(e) => updateParams({ hDiv: e.target.value })}
onChange={(e) => setHDiv(e.target.value)}
placeholder={t("split-by-sections.horizontal.placeholder", "Enter number of horizontal divisions")}
/>
<TextInput
@ -375,13 +359,13 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
min="0"
max="300"
value={vDiv}
onChange={(e) => updateParams({ vDiv: e.target.value })}
onChange={(e) => setVDiv(e.target.value)}
placeholder={t("split-by-sections.vertical.placeholder", "Enter number of vertical divisions")}
/>
<Checkbox
label={t("split-by-sections.merge", "Merge sections into one PDF")}
checked={merge}
onChange={(e) => updateParams({ merge: e.currentTarget.checked })}
onChange={(e) => setMerge(e.currentTarget.checked)}
/>
</Stack>
)}
@ -391,7 +375,7 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
<Select
label={t("split-by-size-or-count.type.label", "Split Type")}
value={splitType}
onChange={(v) => v && updateParams({ splitType: v })}
onChange={(v) => v && setSplitType(v)}
data={[
{ value: "size", label: t("split-by-size-or-count.type.size", "By Size") },
{ value: "pages", label: t("split-by-size-or-count.type.pageCount", "By Page Count") },
@ -402,7 +386,7 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
label={t("split-by-size-or-count.value.label", "Split Value")}
placeholder={t("split-by-size-or-count.value.placeholder", "e.g. 10MB or 5 pages")}
value={splitValue}
onChange={(e) => updateParams({ splitValue: e.target.value })}
onChange={(e) => setSplitValue(e.target.value)}
/>
</Stack>
)}
@ -413,17 +397,17 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
label={t("splitByChapters.bookmarkLevel", "Bookmark Level")}
type="number"
value={bookmarkLevel}
onChange={(e) => updateParams({ bookmarkLevel: e.target.value })}
onChange={(e) => setBookmarkLevel(e.target.value)}
/>
<Checkbox
label={t("splitByChapters.includeMetadata", "Include Metadata")}
checked={includeMetadata}
onChange={(e) => updateParams({ includeMetadata: e.currentTarget.checked })}
onChange={(e) => setIncludeMetadata(e.currentTarget.checked)}
/>
<Checkbox
label={t("splitByChapters.allowDuplicates", "Allow Duplicate Bookmarks")}
checked={allowDuplicates}
onChange={(e) => updateParams({ allowDuplicates: e.currentTarget.checked })}
onChange={(e) => setAllowDuplicates(e.currentTarget.checked)}
/>
</Stack>
)}