- 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:22:26 +01:00
parent 41c82b15da
commit d216811317
9 changed files with 740 additions and 348 deletions

View File

@@ -1,4 +1,5 @@
import React, { useState } from "react";
import { useSearchParams } from "react-router-dom";
import { Stack, Slider, Group, Text, Button, Checkbox, TextInput, Paper } from "@mantine/core";
export interface CompressProps {
@@ -12,6 +13,9 @@ const CompressPdfPanel: React.FC<CompressProps> = ({
setDownloadUrl,
setLoading,
}) => {
const [searchParams] = useSearchParams();
const [selected, setSelected] = useState<boolean[]>(files.map(() => false));
const [compressionLevel, setCompressionLevel] = useState<number>(5);
const [grayscale, setGrayscale] = useState<boolean>(false);
@@ -56,8 +60,8 @@ const CompressPdfPanel: React.FC<CompressProps> = ({
}
};
return (
<Paper shadow="xs" p="md" radius="md" withBorder>
<Stack>
<Text fw={500} mb={4}>Select files to compress:</Text>
<Stack gap={4}>
@@ -118,7 +122,6 @@ const CompressPdfPanel: React.FC<CompressProps> = ({
Compress Selected PDF{selected.filter(Boolean).length > 1 ? "s" : ""}
</Button>
</Stack>
</Paper>
);
};

View File

@@ -1,12 +1,24 @@
import React, { useState, useEffect } from "react";
import { Paper, Button, Checkbox, Stack, Text, Group, Loader, Alert } from "@mantine/core";
import { useSearchParams } from "react-router-dom";
export interface MergePdfPanelProps {
files: File[];
setDownloadUrl: (url: string) => void;
params: {
order: string;
removeDuplicates: boolean;
};
updateParams: (newParams: Partial<MergePdfPanelProps["params"]>) => void;
}
const MergePdfPanel: React.FC<MergePdfPanelProps> = ({ files, setDownloadUrl }) => {
const MergePdfPanel: React.FC<MergePdfPanelProps> = ({
files,
setDownloadUrl,
params,
updateParams,
}) => {
const [searchParams] = useSearchParams();
const [selectedFiles, setSelectedFiles] = useState<boolean[]>([]);
const [downloadUrl, setLocalDownloadUrl] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
@@ -59,8 +71,9 @@ const MergePdfPanel: React.FC<MergePdfPanelProps> = ({ files, setDownloadUrl })
const selectedCount = selectedFiles.filter(Boolean).length;
const { order, removeDuplicates } = params;
return (
<Paper shadow="xs" radius="md" p="md" withBorder>
<Stack>
<Text fw={500} size="lg">Merge PDFs</Text>
<Stack gap={4}>
@@ -104,8 +117,12 @@ const MergePdfPanel: React.FC<MergePdfPanelProps> = ({ files, setDownloadUrl })
Download Merged PDF
</Button>
)}
<Checkbox
label="Remove Duplicates"
checked={removeDuplicates}
onChange={() => updateParams({ removeDuplicates: !removeDuplicates })}
/>
</Stack>
</Paper>
);
};

View File

@@ -7,7 +7,9 @@ import {
Checkbox,
Notification,
Stack,
Paper,
} from "@mantine/core";
import { useSearchParams } from "react-router-dom";
import DownloadIcon from "@mui/icons-material/Download";
export interface SplitPdfPanelProps {
@@ -26,7 +28,7 @@ export interface SplitPdfPanelProps {
includeMetadata: boolean;
allowDuplicates: boolean;
};
updateParams: (newParams: Partial<SplitPdfPanelProps['params']>) => void;
updateParams: (newParams: Partial<SplitPdfPanelProps["params"]>) => void;
}
const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
@@ -36,16 +38,26 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
params,
updateParams,
}) => {
const [searchParams] = useSearchParams();
const [status, setStatus] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const {
mode, pages, hDiv, vDiv, merge,
splitType, splitValue, bookmarkLevel,
includeMetadata, allowDuplicates
mode,
pages,
hDiv,
vDiv,
merge,
splitType,
splitValue,
bookmarkLevel,
includeMetadata,
allowDuplicates,
} = params;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!file) {
@@ -109,123 +121,123 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
};
return (
<form onSubmit={handleSubmit}>
<Stack gap="sm" mb={16}>
<Select
label="Split Mode"
value={mode}
onChange={(v) => v && updateParams({ mode: v })}
data={[
{ value: "byPages", label: "Split by Pages (e.g. 1,3,5-10)" },
{ value: "bySections", label: "Split by Grid Sections" },
{ value: "bySizeOrCount", label: "Split by Size or Count" },
{ value: "byChapters", label: "Split by Chapters" },
]}
/>
{mode === "byPages" && (
<TextInput
label="Pages"
placeholder="e.g. 1,3,5-10"
value={pages}
onChange={(e) => updateParams({ pages: e.target.value })}
<form onSubmit={handleSubmit}>
<Stack gap="sm" mb={16}>
<Select
label="Split Mode"
value={mode}
onChange={(v) => v && updateParams({ mode: v })}
data={[
{ value: "byPages", label: "Split by Pages (e.g. 1,3,5-10)" },
{ value: "bySections", label: "Split by Grid Sections" },
{ value: "bySizeOrCount", label: "Split by Size or Count" },
{ value: "byChapters", label: "Split by Chapters" },
]}
/>
)}
{mode === "bySections" && (
<Stack gap="sm">
{mode === "byPages" && (
<TextInput
label="Horizontal Divisions"
type="number"
min="0"
max="300"
value={hDiv}
onChange={(e) => updateParams({ hDiv: e.target.value })}
label="Pages"
placeholder="e.g. 1,3,5-10"
value={pages}
onChange={(e) => updateParams({ pages: e.target.value })}
/>
<TextInput
label="Vertical Divisions"
type="number"
min="0"
max="300"
value={vDiv}
onChange={(e) => updateParams({ vDiv: e.target.value })}
/>
<Checkbox
label="Merge sections into one PDF"
checked={merge}
onChange={(e) => updateParams({ merge: e.currentTarget.checked })}
/>
</Stack>
)}
)}
{mode === "bySizeOrCount" && (
<Stack gap="sm">
<Select
label="Split Type"
value={splitType}
onChange={(v) => v && updateParams({ splitType: v })}
data={[
{ value: "size", label: "By Size" },
{ value: "pages", label: "By Page Count" },
{ value: "docs", label: "By Document Count" },
]}
/>
<TextInput
label="Split Value"
placeholder="e.g. 10MB or 5 pages"
value={splitValue}
onChange={(e) => updateParams({ splitValue: e.target.value })}
/>
</Stack>
)}
{mode === "bySections" && (
<Stack gap="sm">
<TextInput
label="Horizontal Divisions"
type="number"
min="0"
max="300"
value={hDiv}
onChange={(e) => updateParams({ hDiv: e.target.value })}
/>
<TextInput
label="Vertical Divisions"
type="number"
min="0"
max="300"
value={vDiv}
onChange={(e) => updateParams({ vDiv: e.target.value })}
/>
<Checkbox
label="Merge sections into one PDF"
checked={merge}
onChange={(e) => updateParams({ merge: e.currentTarget.checked })}
/>
</Stack>
)}
{mode === "byChapters" && (
<Stack gap="sm">
<TextInput
label="Bookmark Level"
type="number"
value={bookmarkLevel}
onChange={(e) => updateParams({ bookmarkLevel: e.target.value })}
/>
<Checkbox
label="Include Metadata"
checked={includeMetadata}
onChange={(e) => updateParams({ includeMetadata: e.currentTarget.checked })}
/>
<Checkbox
label="Allow Duplicate Bookmarks"
checked={allowDuplicates}
onChange={(e) => updateParams({ allowDuplicates: e.currentTarget.checked })}
/>
</Stack>
)}
{mode === "bySizeOrCount" && (
<Stack gap="sm">
<Select
label="Split Type"
value={splitType}
onChange={(v) => v && updateParams({ splitType: v })}
data={[
{ value: "size", label: "By Size" },
{ value: "pages", label: "By Page Count" },
{ value: "docs", label: "By Document Count" },
]}
/>
<TextInput
label="Split Value"
placeholder="e.g. 10MB or 5 pages"
value={splitValue}
onChange={(e) => updateParams({ splitValue: e.target.value })}
/>
</Stack>
)}
<Button type="submit" loading={isLoading} fullWidth>
{isLoading ? "Processing..." : "Split PDF"}
</Button>
{mode === "byChapters" && (
<Stack gap="sm">
<TextInput
label="Bookmark Level"
type="number"
value={bookmarkLevel}
onChange={(e) => updateParams({ bookmarkLevel: e.target.value })}
/>
<Checkbox
label="Include Metadata"
checked={includeMetadata}
onChange={(e) => updateParams({ includeMetadata: e.currentTarget.checked })}
/>
<Checkbox
label="Allow Duplicate Bookmarks"
checked={allowDuplicates}
onChange={(e) => updateParams({ allowDuplicates: e.currentTarget.checked })}
/>
</Stack>
)}
{status && <p className="text-xs text-gray-600">{status}</p>}
{errorMessage && (
<Notification color="red" title="Error" onClose={() => setErrorMessage(null)}>
{errorMessage}
</Notification>
)}
{status === "Download ready." && downloadUrl && (
<Button
component="a"
href={downloadUrl}
download="split_output.zip"
leftSection={<DownloadIcon />}
color="green"
fullWidth
>
Download Split PDF
<Button type="submit" loading={isLoading} fullWidth>
{isLoading ? "Processing..." : "Split PDF"}
</Button>
)}
</Stack>
</form>
{status && <p className="text-xs text-gray-600">{status}</p>}
{errorMessage && (
<Notification color="red" title="Error" onClose={() => setErrorMessage(null)}>
{errorMessage}
</Notification>
)}
{status === "Download ready." && downloadUrl && (
<Button
component="a"
href={downloadUrl}
download="split_output.zip"
leftSection={<DownloadIcon />}
color="green"
fullWidth
>
Download Split PDF
</Button>
)}
</Stack>
</form>
);
};