mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
Homepage refactor, tool management refactor
This commit is contained in:
parent
5d7c572929
commit
cb5c35249f
@ -1,13 +1,10 @@
|
|||||||
import React from "react";
|
|
||||||
import { FileWithUrl } from "../../types/file";
|
import { FileWithUrl } from "../../types/file";
|
||||||
|
import { useToolManagement } from "../../hooks/useToolManagement";
|
||||||
|
|
||||||
interface ToolRendererProps {
|
interface ToolRendererProps {
|
||||||
selectedToolKey: string;
|
selectedToolKey: string;
|
||||||
selectedTool: any;
|
|
||||||
pdfFile: any;
|
pdfFile: any;
|
||||||
files: FileWithUrl[];
|
files: FileWithUrl[];
|
||||||
downloadUrl: string | null;
|
|
||||||
setDownloadUrl: (url: string | null) => void;
|
|
||||||
toolParams: any;
|
toolParams: any;
|
||||||
updateParams: (params: any) => void;
|
updateParams: (params: any) => void;
|
||||||
toolSelectedFiles?: File[];
|
toolSelectedFiles?: File[];
|
||||||
@ -16,18 +13,18 @@ interface ToolRendererProps {
|
|||||||
|
|
||||||
const ToolRenderer = ({
|
const ToolRenderer = ({
|
||||||
selectedToolKey,
|
selectedToolKey,
|
||||||
selectedTool,
|
files,
|
||||||
pdfFile,
|
|
||||||
files,
|
|
||||||
downloadUrl,
|
|
||||||
setDownloadUrl,
|
|
||||||
toolParams,
|
toolParams,
|
||||||
updateParams,
|
updateParams,
|
||||||
toolSelectedFiles = [],
|
toolSelectedFiles = [],
|
||||||
onPreviewFile,
|
onPreviewFile,
|
||||||
}: ToolRendererProps) => {
|
}: ToolRendererProps) => {
|
||||||
|
// Get the tool from registry
|
||||||
|
const { toolRegistry } = useToolManagement();
|
||||||
|
const selectedTool = toolRegistry[selectedToolKey];
|
||||||
|
|
||||||
if (!selectedTool || !selectedTool.component) {
|
if (!selectedTool || !selectedTool.component) {
|
||||||
return <div>Tool not found</div>;
|
return <div>Tool not found: {selectedToolKey}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToolComponent = selectedTool.component;
|
const ToolComponent = selectedTool.component;
|
||||||
@ -37,8 +34,6 @@ const ToolRenderer = ({
|
|||||||
case "split":
|
case "split":
|
||||||
return (
|
return (
|
||||||
<ToolComponent
|
<ToolComponent
|
||||||
params={toolParams}
|
|
||||||
updateParams={updateParams}
|
|
||||||
selectedFiles={toolSelectedFiles}
|
selectedFiles={toolSelectedFiles}
|
||||||
onPreviewFile={onPreviewFile}
|
onPreviewFile={onPreviewFile}
|
||||||
/>
|
/>
|
||||||
@ -47,7 +42,6 @@ const ToolRenderer = ({
|
|||||||
return (
|
return (
|
||||||
<ToolComponent
|
<ToolComponent
|
||||||
files={files}
|
files={files}
|
||||||
setDownloadUrl={setDownloadUrl}
|
|
||||||
setLoading={(loading: boolean) => {}}
|
setLoading={(loading: boolean) => {}}
|
||||||
params={toolParams}
|
params={toolParams}
|
||||||
updateParams={updateParams}
|
updateParams={updateParams}
|
||||||
@ -57,7 +51,6 @@ const ToolRenderer = ({
|
|||||||
return (
|
return (
|
||||||
<ToolComponent
|
<ToolComponent
|
||||||
files={files}
|
files={files}
|
||||||
setDownloadUrl={setDownloadUrl}
|
|
||||||
params={toolParams}
|
params={toolParams}
|
||||||
updateParams={updateParams}
|
updateParams={updateParams}
|
||||||
/>
|
/>
|
||||||
@ -66,7 +59,6 @@ const ToolRenderer = ({
|
|||||||
return (
|
return (
|
||||||
<ToolComponent
|
<ToolComponent
|
||||||
files={files}
|
files={files}
|
||||||
setDownloadUrl={setDownloadUrl}
|
|
||||||
params={toolParams}
|
params={toolParams}
|
||||||
updateParams={updateParams}
|
updateParams={updateParams}
|
||||||
/>
|
/>
|
||||||
|
@ -13,6 +13,8 @@ import CloseIcon from "@mui/icons-material/Close";
|
|||||||
import { useLocalStorage } from "@mantine/hooks";
|
import { useLocalStorage } from "@mantine/hooks";
|
||||||
import { fileStorage } from "../../services/fileStorage";
|
import { fileStorage } from "../../services/fileStorage";
|
||||||
import SkeletonLoader from '../shared/SkeletonLoader';
|
import SkeletonLoader from '../shared/SkeletonLoader';
|
||||||
|
import { useFileContext } from "../../contexts/FileContext";
|
||||||
|
import { useFileWithUrl } from "../../hooks/useFileWithUrl";
|
||||||
|
|
||||||
GlobalWorkerOptions.workerSrc = "/pdf.worker.js";
|
GlobalWorkerOptions.workerSrc = "/pdf.worker.js";
|
||||||
|
|
||||||
@ -132,8 +134,6 @@ const LazyPageImage = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface ViewerProps {
|
export interface ViewerProps {
|
||||||
pdfFile: { file: File; url: string } | null; // First file in the array
|
|
||||||
setPdfFile: (file: { file: File; url: string } | null) => void;
|
|
||||||
sidebarsVisible: boolean;
|
sidebarsVisible: boolean;
|
||||||
setSidebarsVisible: (v: boolean) => void;
|
setSidebarsVisible: (v: boolean) => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
@ -141,8 +141,6 @@ export interface ViewerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Viewer = ({
|
const Viewer = ({
|
||||||
pdfFile,
|
|
||||||
setPdfFile,
|
|
||||||
sidebarsVisible,
|
sidebarsVisible,
|
||||||
setSidebarsVisible,
|
setSidebarsVisible,
|
||||||
onClose,
|
onClose,
|
||||||
@ -150,6 +148,14 @@ const Viewer = ({
|
|||||||
}: ViewerProps) => {
|
}: ViewerProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useMantineTheme();
|
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 [numPages, setNumPages] = useState<number>(0);
|
||||||
const [pageImages, setPageImages] = useState<string[]>([]);
|
const [pageImages, setPageImages] = useState<string[]>([]);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
@ -448,8 +454,7 @@ const Viewer = ({
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (file && file.type === "application/pdf") {
|
if (file && file.type === "application/pdf") {
|
||||||
const fileUrl = URL.createObjectURL(file);
|
addFiles([file]);
|
||||||
setPdfFile({ file, url: fileUrl });
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
129
frontend/src/hooks/useToolManagement.tsx
Normal file
129
frontend/src/hooks/useToolManagement.tsx
Normal 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,
|
||||||
|
};
|
||||||
|
};
|
@ -1,12 +1,8 @@
|
|||||||
import React, { useState, useCallback, useEffect } from "react";
|
import React, { useState, useCallback} from "react";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useFileWithUrl } from "../hooks/useFileWithUrl";
|
|
||||||
import { useFileContext } from "../contexts/FileContext";
|
import { useFileContext } from "../contexts/FileContext";
|
||||||
import { fileStorage } from "../services/fileStorage";
|
import { useToolManagement } from "../hooks/useToolManagement";
|
||||||
import AddToPhotosIcon from "@mui/icons-material/AddToPhotos";
|
import { Group, Box, Button, Container } from "@mantine/core";
|
||||||
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 { useRainbowThemeContext } from "../components/shared/RainbowThemeProvider";
|
||||||
import rainbowStyles from '../styles/rainbow.module.css';
|
import rainbowStyles from '../styles/rainbow.module.css';
|
||||||
|
|
||||||
@ -17,163 +13,50 @@ import PageEditor from "../components/pageEditor/PageEditor";
|
|||||||
import PageEditorControls from "../components/pageEditor/PageEditorControls";
|
import PageEditorControls from "../components/pageEditor/PageEditorControls";
|
||||||
import Viewer from "../components/viewer/Viewer";
|
import Viewer from "../components/viewer/Viewer";
|
||||||
import FileUploadSelector from "../components/shared/FileUploadSelector";
|
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 ToolRenderer from "../components/tools/ToolRenderer";
|
||||||
import QuickAccessBar from "../components/shared/QuickAccessBar";
|
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() {
|
export default function HomePage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useMantineTheme();
|
|
||||||
const { isRainbowMode } = useRainbowThemeContext();
|
const { isRainbowMode } = useRainbowThemeContext();
|
||||||
|
|
||||||
// Get file context
|
// Get file context
|
||||||
const fileContext = useFileContext();
|
const fileContext = useFileContext();
|
||||||
const { activeFiles, currentView, currentMode, setCurrentView, addFiles } = fileContext;
|
const { activeFiles, currentView, currentMode, setCurrentView, addFiles } = fileContext;
|
||||||
|
|
||||||
// Core app state
|
const {
|
||||||
const [selectedToolKey, setSelectedToolKey] = useState<string | null>(null);
|
selectedToolKey,
|
||||||
|
selectedTool,
|
||||||
|
toolParams,
|
||||||
|
toolRegistry,
|
||||||
|
selectTool,
|
||||||
|
clearToolSelection,
|
||||||
|
updateToolParams,
|
||||||
|
} = useToolManagement();
|
||||||
|
|
||||||
const [storedFiles, setStoredFiles] = useState<any[]>([]);
|
const [toolSelectedFiles, setToolSelectedFiles] = useState<File[]>([]);
|
||||||
const [preSelectedFiles, setPreSelectedFiles] = useState([]);
|
|
||||||
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);
|
||||||
const [pageEditorFunctions, setPageEditorFunctions] = useState<any>(null);
|
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);
|
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(
|
const handleToolSelect = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
setSelectedToolKey(id);
|
selectTool(id);
|
||||||
if (toolRegistry[id]?.view) setCurrentView(toolRegistry[id].view);
|
if (toolRegistry[id]?.view) setCurrentView(toolRegistry[id].view);
|
||||||
setLeftPanelView('toolContent');
|
setLeftPanelView('toolContent');
|
||||||
setReaderMode(false);
|
setReaderMode(false);
|
||||||
},
|
},
|
||||||
[toolRegistry, setCurrentView]
|
[selectTool, toolRegistry, setCurrentView]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleQuickAccessTools = useCallback(() => {
|
const handleQuickAccessTools = useCallback(() => {
|
||||||
setLeftPanelView('toolPicker');
|
setLeftPanelView('toolPicker');
|
||||||
setReaderMode(false);
|
setReaderMode(false);
|
||||||
setSelectedToolKey(null);
|
clearToolSelection();
|
||||||
}, []);
|
}, [clearToolSelection]);
|
||||||
|
|
||||||
const handleReaderToggle = useCallback(() => {
|
const handleReaderToggle = useCallback(() => {
|
||||||
setReaderMode(!readerMode);
|
setReaderMode(!readerMode);
|
||||||
@ -190,123 +73,7 @@ export default function HomePage() {
|
|||||||
}
|
}
|
||||||
}, [activeFiles, addFiles]);
|
}, [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 (
|
return (
|
||||||
<Group
|
<Group
|
||||||
@ -324,17 +91,12 @@ export default function HomePage() {
|
|||||||
readerMode={readerMode}
|
readerMode={readerMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Left: Tool Picker OR Selected Tool Panel */}
|
{/* Left: Tool Picker or Selected Tool Panel */}
|
||||||
<div
|
<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={{
|
style={{
|
||||||
backgroundColor: 'var(--bg-surface)',
|
width: sidebarsVisible && !readerMode ? '14vw' : '0',
|
||||||
borderRight: '1px solid var(--border-subtle)',
|
padding: sidebarsVisible && !readerMode ? '1rem' : '0'
|
||||||
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'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -379,13 +141,6 @@ export default function HomePage() {
|
|||||||
<div className="flex-1 min-h-0">
|
<div className="flex-1 min-h-0">
|
||||||
<ToolRenderer
|
<ToolRenderer
|
||||||
selectedToolKey={selectedToolKey}
|
selectedToolKey={selectedToolKey}
|
||||||
selectedTool={selectedTool}
|
|
||||||
pdfFile={activeFiles[0] || null}
|
|
||||||
files={activeFiles}
|
|
||||||
downloadUrl={downloadUrl}
|
|
||||||
setDownloadUrl={setDownloadUrl}
|
|
||||||
toolParams={getToolParams(selectedToolKey)}
|
|
||||||
updateParams={(newParams) => updateToolParams(selectedToolKey, newParams)}
|
|
||||||
toolSelectedFiles={toolSelectedFiles}
|
toolSelectedFiles={toolSelectedFiles}
|
||||||
onPreviewFile={setPreviewFile}
|
onPreviewFile={setPreviewFile}
|
||||||
/>
|
/>
|
||||||
@ -423,7 +178,6 @@ export default function HomePage() {
|
|||||||
: t("fileUpload.selectPdfToEdit", "Select a PDF to edit")
|
: t("fileUpload.selectPdfToEdit", "Select a PDF to edit")
|
||||||
}
|
}
|
||||||
subtitle={t("fileUpload.chooseFromStorage", "Choose a file from storage or upload a new PDF")}
|
subtitle={t("fileUpload.chooseFromStorage", "Choose a file from storage or upload a new PDF")}
|
||||||
sharedFiles={storedFiles}
|
|
||||||
onFileSelect={(file) => {
|
onFileSelect={(file) => {
|
||||||
addToActiveFiles(file);
|
addToActiveFiles(file);
|
||||||
}}
|
}}
|
||||||
@ -439,7 +193,6 @@ export default function HomePage() {
|
|||||||
) : currentView === "fileEditor" ? (
|
) : currentView === "fileEditor" ? (
|
||||||
<FileEditor
|
<FileEditor
|
||||||
onOpenPageEditor={(file) => {
|
onOpenPageEditor={(file) => {
|
||||||
setCurrentActiveFile(file);
|
|
||||||
handleViewChange("pageEditor");
|
handleViewChange("pageEditor");
|
||||||
}}
|
}}
|
||||||
onMergeFiles={(filesToMerge) => {
|
onMergeFiles={(filesToMerge) => {
|
||||||
@ -450,14 +203,6 @@ export default function HomePage() {
|
|||||||
/>
|
/>
|
||||||
) : currentView === "viewer" ? (
|
) : currentView === "viewer" ? (
|
||||||
<Viewer
|
<Viewer
|
||||||
pdfFile={currentFileWithUrl}
|
|
||||||
setPdfFile={(fileObj) => {
|
|
||||||
if (fileObj) {
|
|
||||||
setCurrentActiveFile(fileObj.file);
|
|
||||||
} else {
|
|
||||||
fileContext.clearAllFiles();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
sidebarsVisible={sidebarsVisible}
|
sidebarsVisible={sidebarsVisible}
|
||||||
setSidebarsVisible={setSidebarsVisible}
|
setSidebarsVisible={setSidebarsVisible}
|
||||||
previewFile={previewFile}
|
previewFile={previewFile}
|
||||||
@ -466,7 +211,7 @@ export default function HomePage() {
|
|||||||
setPreviewFile(null); // Clear preview file
|
setPreviewFile(null); // Clear preview file
|
||||||
const previousMode = sessionStorage.getItem('previousMode');
|
const previousMode = sessionStorage.getItem('previousMode');
|
||||||
if (previousMode === 'split') {
|
if (previousMode === 'split') {
|
||||||
setSelectedToolKey('split');
|
selectTool('split');
|
||||||
setCurrentView('split');
|
setCurrentView('split');
|
||||||
setLeftPanelView('toolContent');
|
setLeftPanelView('toolContent');
|
||||||
sessionStorage.removeItem('previousMode');
|
sessionStorage.removeItem('previousMode');
|
||||||
@ -512,20 +257,12 @@ export default function HomePage() {
|
|||||||
) : selectedToolKey && selectedTool ? (
|
) : selectedToolKey && selectedTool ? (
|
||||||
<ToolRenderer
|
<ToolRenderer
|
||||||
selectedToolKey={selectedToolKey}
|
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' }}>
|
<Container size="lg" p="xl" h="100%" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<FileUploadSelector
|
<FileUploadSelector
|
||||||
title="File Management"
|
title="File Management"
|
||||||
subtitle="Choose files from storage or upload new PDFs"
|
subtitle="Choose files from storage or upload new PDFs"
|
||||||
sharedFiles={storedFiles}
|
|
||||||
onFileSelect={(file) => {
|
onFileSelect={(file) => {
|
||||||
addToActiveFiles(file);
|
addToActiveFiles(file);
|
||||||
}}
|
}}
|
||||||
|
@ -26,26 +26,11 @@ import { generateThumbnailForFile } from "../utils/thumbnailUtils";
|
|||||||
import FileEditor from "../components/fileEditor/FileEditor";
|
import FileEditor from "../components/fileEditor/FileEditor";
|
||||||
|
|
||||||
export interface SplitPdfPanelProps {
|
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[];
|
selectedFiles?: File[];
|
||||||
onPreviewFile?: (file: File | null) => void;
|
onPreviewFile?: (file: File | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
|
const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
|
||||||
params,
|
|
||||||
updateParams,
|
|
||||||
selectedFiles = [],
|
selectedFiles = [],
|
||||||
onPreviewFile,
|
onPreviewFile,
|
||||||
}) => {
|
}) => {
|
||||||
@ -53,6 +38,18 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
|
|||||||
const fileContext = useFileContext();
|
const fileContext = useFileContext();
|
||||||
const { activeFiles, selectedFileIds, updateProcessedFile, recordOperation, markOperationApplied, markOperationFailed, setCurrentMode } = fileContext;
|
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 [status, setStatus] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
@ -67,19 +64,6 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
|
|||||||
isGeneratingThumbnails: false
|
isGeneratingThumbnails: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
|
||||||
mode,
|
|
||||||
pages,
|
|
||||||
hDiv,
|
|
||||||
vDiv,
|
|
||||||
merge,
|
|
||||||
splitType,
|
|
||||||
splitValue,
|
|
||||||
bookmarkLevel,
|
|
||||||
includeMetadata,
|
|
||||||
allowDuplicates,
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
// Clear download when parameters or files change
|
// Clear download when parameters or files change
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (downloadUrl) {
|
if (downloadUrl) {
|
||||||
@ -339,7 +323,7 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
|
|||||||
label="Choose split method"
|
label="Choose split method"
|
||||||
placeholder="Select how to split the PDF"
|
placeholder="Select how to split the PDF"
|
||||||
value={mode}
|
value={mode}
|
||||||
onChange={(v) => v && updateParams({ mode: v })}
|
onChange={(v) => v && setMode(v)}
|
||||||
data={[
|
data={[
|
||||||
{ value: "byPages", label: t("split.header", "Split by Pages") + " (e.g. 1,3,5-10)" },
|
{ 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") },
|
{ 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")}
|
label={t("split.splitPages", "Pages")}
|
||||||
placeholder={t("pageSelectionPrompt", "e.g. 1,3,5-10")}
|
placeholder={t("pageSelectionPrompt", "e.g. 1,3,5-10")}
|
||||||
value={pages}
|
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"
|
min="0"
|
||||||
max="300"
|
max="300"
|
||||||
value={hDiv}
|
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")}
|
placeholder={t("split-by-sections.horizontal.placeholder", "Enter number of horizontal divisions")}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
@ -375,13 +359,13 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
|
|||||||
min="0"
|
min="0"
|
||||||
max="300"
|
max="300"
|
||||||
value={vDiv}
|
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")}
|
placeholder={t("split-by-sections.vertical.placeholder", "Enter number of vertical divisions")}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={t("split-by-sections.merge", "Merge sections into one PDF")}
|
label={t("split-by-sections.merge", "Merge sections into one PDF")}
|
||||||
checked={merge}
|
checked={merge}
|
||||||
onChange={(e) => updateParams({ merge: e.currentTarget.checked })}
|
onChange={(e) => setMerge(e.currentTarget.checked)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
@ -391,7 +375,7 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
|
|||||||
<Select
|
<Select
|
||||||
label={t("split-by-size-or-count.type.label", "Split Type")}
|
label={t("split-by-size-or-count.type.label", "Split Type")}
|
||||||
value={splitType}
|
value={splitType}
|
||||||
onChange={(v) => v && updateParams({ splitType: v })}
|
onChange={(v) => v && setSplitType(v)}
|
||||||
data={[
|
data={[
|
||||||
{ value: "size", label: t("split-by-size-or-count.type.size", "By Size") },
|
{ 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") },
|
{ 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")}
|
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")}
|
placeholder={t("split-by-size-or-count.value.placeholder", "e.g. 10MB or 5 pages")}
|
||||||
value={splitValue}
|
value={splitValue}
|
||||||
onChange={(e) => updateParams({ splitValue: e.target.value })}
|
onChange={(e) => setSplitValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
@ -413,17 +397,17 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
|
|||||||
label={t("splitByChapters.bookmarkLevel", "Bookmark Level")}
|
label={t("splitByChapters.bookmarkLevel", "Bookmark Level")}
|
||||||
type="number"
|
type="number"
|
||||||
value={bookmarkLevel}
|
value={bookmarkLevel}
|
||||||
onChange={(e) => updateParams({ bookmarkLevel: e.target.value })}
|
onChange={(e) => setBookmarkLevel(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={t("splitByChapters.includeMetadata", "Include Metadata")}
|
label={t("splitByChapters.includeMetadata", "Include Metadata")}
|
||||||
checked={includeMetadata}
|
checked={includeMetadata}
|
||||||
onChange={(e) => updateParams({ includeMetadata: e.currentTarget.checked })}
|
onChange={(e) => setIncludeMetadata(e.currentTarget.checked)}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={t("splitByChapters.allowDuplicates", "Allow Duplicate Bookmarks")}
|
label={t("splitByChapters.allowDuplicates", "Allow Duplicate Bookmarks")}
|
||||||
checked={allowDuplicates}
|
checked={allowDuplicates}
|
||||||
onChange={(e) => updateParams({ allowDuplicates: e.currentTarget.checked })}
|
onChange={(e) => setAllowDuplicates(e.currentTarget.checked)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
Loading…
Reference in New Issue
Block a user