mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
- Viewer overhaul
-Dark mode toggle -URL params improvements -app.js set up fix - UI clean up
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user