- Viewer overhaul

-Dark mode toggle
-URL params improvements
-app.js set up fix
- UI clean up
This commit is contained in:
Reece
2025-05-27 19:28:03 +01:00
parent 41c82b15da
commit d216811317
9 changed files with 740 additions and 348 deletions
+238 -168
View File
@@ -6,7 +6,7 @@ import ZoomInMapIcon from "@mui/icons-material/ZoomInMap";
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
import VisibilityIcon from "@mui/icons-material/Visibility";
import EditNoteIcon from "@mui/icons-material/EditNote";
import { Group, SegmentedControl, Paper, Center, Box } from "@mantine/core";
import { Group, SegmentedControl, Paper, Center, Box, Button, useMantineTheme, useMantineColorScheme } from "@mantine/core";
import ToolPicker from "../components/ToolPicker";
import FileManager from "../components/FileManager";
@@ -15,6 +15,8 @@ import CompressPdfPanel from "../tools/Compress";
import MergePdfPanel from "../tools/Merge";
import PageEditor from "../components/PageEditor";
import Viewer from "../components/Viewer";
import DarkModeIcon from '@mui/icons-material/DarkMode';
import LightModeIcon from '@mui/icons-material/LightMode';
type ToolRegistryEntry = {
icon: React.ReactNode;
@@ -60,8 +62,98 @@ const VIEW_OPTIONS = [
},
];
// Utility to extract params for a tool from searchParams
function getToolParams(toolKey: string, searchParams: URLSearchParams) {
switch (toolKey) {
case "split":
return {
mode: searchParams.get("splitMode") || "byPages",
pages: searchParams.get("pages") || "",
hDiv: searchParams.get("hDiv") || "0",
vDiv: searchParams.get("vDiv") || "1",
merge: searchParams.get("merge") === "true",
splitType: searchParams.get("splitType") || "size",
splitValue: searchParams.get("splitValue") || "",
bookmarkLevel: searchParams.get("bookmarkLevel") || "0",
includeMetadata: searchParams.get("includeMetadata") === "true",
allowDuplicates: searchParams.get("allowDuplicates") === "true",
};
case "compress":
return {
level: searchParams.get("compressLevel") || "medium",
keepQuality: searchParams.get("keepQuality") === "true",
};
case "merge":
return {
order: searchParams.get("mergeOrder") || "default",
removeDuplicates: searchParams.get("removeDuplicates") === "true",
};
// Add more tools here as needed
default:
return {};
}
}
// Utility to update params for a tool
function updateToolParams(toolKey: string, searchParams: URLSearchParams, setSearchParams: any, newParams: any) {
const params = new URLSearchParams(searchParams);
// Clear tool-specific params
if (toolKey === "split") {
[
"splitMode", "pages", "hDiv", "vDiv", "merge",
"splitType", "splitValue", "bookmarkLevel", "includeMetadata", "allowDuplicates"
].forEach((k) => params.delete(k));
// Set new split params
const merged = { ...getToolParams("split", searchParams), ...newParams };
params.set("splitMode", merged.mode);
if (merged.mode === "byPages") params.set("pages", merged.pages);
else if (merged.mode === "bySections") {
params.set("hDiv", merged.hDiv);
params.set("vDiv", merged.vDiv);
params.set("merge", String(merged.merge));
} else if (merged.mode === "bySizeOrCount") {
params.set("splitType", merged.splitType);
params.set("splitValue", merged.splitValue);
} else if (merged.mode === "byChapters") {
params.set("bookmarkLevel", merged.bookmarkLevel);
params.set("includeMetadata", String(merged.includeMetadata));
params.set("allowDuplicates", String(merged.allowDuplicates));
}
} else if (toolKey === "compress") {
["compressLevel", "keepQuality"].forEach((k) => params.delete(k));
const merged = { ...getToolParams("compress", searchParams), ...newParams };
params.set("compressLevel", merged.level);
params.set("keepQuality", String(merged.keepQuality));
} else if (toolKey === "merge") {
["mergeOrder", "removeDuplicates"].forEach((k) => params.delete(k));
const merged = { ...getToolParams("merge", searchParams), ...newParams };
params.set("mergeOrder", merged.order);
params.set("removeDuplicates", String(merged.removeDuplicates));
}
// Add more tools as needed
setSearchParams(params, { replace: true });
}
// List of all tool-specific params
const TOOL_PARAMS = {
split: [
"splitMode", "pages", "hDiv", "vDiv", "merge",
"splitType", "splitValue", "bookmarkLevel", "includeMetadata", "allowDuplicates"
],
compress: [
"compressLevel", "keepQuality"
],
merge: [
"mergeOrder", "removeDuplicates"
]
// Add more tools as needed
};
export default function HomePage() {
const [searchParams, setSearchParams] = useSearchParams();
const theme = useMantineTheme();
// Core app state
const [selectedToolKey, setSelectedToolKey] = useState<string>(searchParams.get("tool") || "split");
@@ -69,28 +161,37 @@ export default function HomePage() {
const [pdfFile, setPdfFile] = useState<any>(null);
const [files, setFiles] = useState<any[]>([]);
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
const [sidebarsVisible, setSidebarsVisible] = useState(true);
// Tool-specific parameters
const [splitParams, setSplitParams] = useState({
mode: searchParams.get("splitMode") || "byPages",
pages: searchParams.get("pages") || "",
hDiv: searchParams.get("hDiv") || "0",
vDiv: searchParams.get("vDiv") || "1",
merge: searchParams.get("merge") === "true",
splitType: searchParams.get("splitType") || "size",
splitValue: searchParams.get("splitValue") || "",
bookmarkLevel: searchParams.get("bookmarkLevel") || "0",
includeMetadata: searchParams.get("includeMetadata") === "true",
allowDuplicates: searchParams.get("allowDuplicates") === "true",
});
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);
params.set("tool", selectedToolKey);
params.set("view", currentView);
setSearchParams(params, { replace: true });
}, [selectedToolKey, currentView, setSearchParams]);
// 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 'view'
const entries = Array.from(params.entries()).filter(([key]) => key !== "view");
// Rebuild params with 'view' first
const newParams = new URLSearchParams();
newParams.set("view", currentView);
newParams.set("tool", selectedToolKey);
entries.forEach(([key, value]) => {
if (key !== "tool") newParams.set(key, value);
});
setSearchParams(newParams, { replace: true });
}, [selectedToolKey, currentView, setSearchParams, searchParams]);
// Handle tool selection
const handleToolSelect = useCallback(
@@ -101,51 +202,6 @@ export default function HomePage() {
[toolRegistry]
);
// Handle split parameter updates
const updateSplitParams = useCallback((newParams: Partial<typeof splitParams>) => {
setSplitParams(prev => {
const updated = { ...prev, ...newParams };
// Update URL with split params
const params = new URLSearchParams(searchParams);
// Clear old parameters when mode changes
if (newParams.mode && newParams.mode !== prev.mode) {
params.delete("pages");
params.delete("hDiv");
params.delete("vDiv");
params.delete("merge");
params.delete("splitType");
params.delete("splitValue");
params.delete("bookmarkLevel");
params.delete("includeMetadata");
params.delete("allowDuplicates");
}
// Set the mode
params.set("splitMode", updated.mode);
// Set mode-specific parameters
if (updated.mode === "byPages" && updated.pages) {
params.set("pages", updated.pages);
} else if (updated.mode === "bySections") {
params.set("hDiv", updated.hDiv);
params.set("vDiv", updated.vDiv);
params.set("merge", String(updated.merge));
} else if (updated.mode === "bySizeOrCount") {
params.set("splitType", updated.splitType);
if (updated.splitValue) params.set("splitValue", updated.splitValue);
} else if (updated.mode === "byChapters") {
params.set("bookmarkLevel", updated.bookmarkLevel);
params.set("includeMetadata", String(updated.includeMetadata));
params.set("allowDuplicates", String(updated.allowDuplicates));
}
setSearchParams(params, { replace: true });
return updated;
});
}, [searchParams, setSearchParams]);
const selectedTool = toolRegistry[selectedToolKey];
// Tool component rendering
@@ -154,130 +210,144 @@ export default function HomePage() {
return <div>Tool not found</div>;
}
// Pass appropriate props based on tool type
if (selectedToolKey === "split") {
return React.createElement(selectedTool.component, {
file: pdfFile,
setPdfFile,
downloadUrl,
setDownloadUrl,
// Tool-specific params and update function
params: splitParams,
updateParams: updateSplitParams
});
}
// For other tools, pass standard props
// Pass only the necessary props
return React.createElement(selectedTool.component, {
file: pdfFile,
setPdfFile,
files,
setFiles,
downloadUrl,
setDownloadUrl,
params: toolParams,
updateParams,
});
};
return (
<Group align="flex-start" gap={0} style={{ minHeight: "100vh" }}>
{/* Left: Tool Picker */}
<ToolPicker
selectedToolKey={selectedToolKey}
onSelect={handleToolSelect}
toolRegistry={toolRegistry}
/>
{sidebarsVisible && (
<ToolPicker
selectedToolKey={selectedToolKey}
onSelect={handleToolSelect}
toolRegistry={toolRegistry}
/>
)}
{/* Middle: Main View (Viewer, Editor, Manager) */}
<Box
style={{
width: "calc(100vw - 220px - 380px)",
marginLeft: 220,
marginRight: 380,
padding: 24,
background: "#fff",
position: "relative",
minHeight: "100vh",
width: sidebarsVisible
? "calc(100vw - 220px - 380px)"
: "100vw",
marginLeft: sidebarsVisible ? 220 : 0,
marginRight: sidebarsVisible ? 380 : 0,
position: "relative", // <-- important for absolute overlay
height: "100vh",
overflowY: "auto",
display: "flex",
flexDirection: "column",
transition: "all 0.3s",
}}
>
<Center>
<Paper
radius="xl"
shadow="sm"
p={4}
style={{
display: "inline-block",
marginTop: 8,
marginBottom: 24,
background: "#f8f9fa",
zIndex: 10,
}}
>
<SegmentedControl
data={VIEW_OPTIONS}
value={currentView}
onChange={setCurrentView} // Using the state setter directly
color="blue"
radius="xl"
size="md"
/>
</Paper>
</Center>
<Box>
{(currentView === "viewer" || currentView === "pageEditor") && !pdfFile ? (
<FileManager
files={files}
setFiles={setFiles}
setPdfFile={setPdfFile}
setCurrentView={setCurrentView}
/>
) : currentView === "viewer" ? (
<Viewer
pdfFile={pdfFile}
setPdfFile={setPdfFile}
/>
) : currentView === "pageEditor" ? (
<PageEditor
file={pdfFile}
setFile={setPdfFile}
downloadUrl={downloadUrl}
setDownloadUrl={setDownloadUrl}
/>
) : (
<FileManager
files={files}
setFiles={setFiles}
setPdfFile={setPdfFile}
setCurrentView={setCurrentView}
/>
)}
</Box>
{/* Overlayed View Switcher */}
<div
style={{
position: "absolute",
left: 0,
width: "100%",
display: "flex",
justifyContent: "center",
zIndex: 30,
}}
>
<div style={{ pointerEvents: "auto" }}>
<SegmentedControl
data={VIEW_OPTIONS}
value={currentView}
onChange={setCurrentView}
color="blue"
radius="xl"
size="md"
fullWidth
/>
</div>
</div>
{/* Main content area with matching Paper */}
<Paper
radius="0 0 xl xl"
shadow="sm"
p={0}
style={{
flex: 1,
minHeight: 0,
marginTop: 0,
boxSizing: "border-box",
overflow: "hidden",
display: "flex",
flexDirection: "column",
}}
>
<Box style={{ flex: 1, minHeight: 0 }}>
{(currentView === "viewer" || currentView === "pageEditor") && !pdfFile ? (
<FileManager
files={files}
setFiles={setFiles}
setPdfFile={setPdfFile}
setCurrentView={setCurrentView}
/>
) : currentView === "viewer" ? (
<Viewer
pdfFile={pdfFile}
setPdfFile={setPdfFile}
sidebarsVisible={sidebarsVisible}
setSidebarsVisible={setSidebarsVisible}
/>
) : currentView === "pageEditor" ? (
<PageEditor
file={pdfFile}
setFile={setPdfFile}
downloadUrl={downloadUrl}
setDownloadUrl={setDownloadUrl}
/>
) : (
<FileManager
files={files}
setFiles={setFiles}
setPdfFile={setPdfFile}
setCurrentView={setCurrentView}
/>
)}
</Box>
</Paper>
</Box>
{/* Right: Tool Interaction */}
<Box
style={{
width: 380,
background: "#f8f9fa",
borderLeft: "1px solid #e9ecef",
minHeight: "100vh",
padding: 24,
gap: 16,
position: "fixed",
right: 0,
top: 0,
bottom: 0,
zIndex: 100,
overflowY: "auto",
}}
{sidebarsVisible && (
<Box
style={{
width: 380,
borderLeft: "1px solid #e9ecef",
height: "100vh",
padding: 24,
gap: 16,
position: "fixed",
right: 0,
top: 0,
bottom: 0,
zIndex: 100,
}}
>
{selectedTool && selectedTool.component && (
<>
{renderTool()}
</>
)}
</Box>
)}
<Button
variant="light"
color="blue"
size="xs"
style={{ position: "absolute", top: 16, right: 16, zIndex: 200 }}
onClick={() => setSidebarsVisible(v => !v)}
>
{selectedTool && selectedTool.component && (
<>
{renderTool()}
</>
)}
</Box>
{sidebarsVisible ? "Hide Sidebars" : "Show Sidebars"}
</Button>
</Group>
);
}