Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

126 lines
4.0 KiB
TypeScript
Raw Normal View History

2025-05-15 20:07:33 +01:00
import React, { useState } from "react";
import { Stack, Slider, Group, Text, Button, Checkbox, TextInput, Paper } from "@mantine/core";
export interface CompressProps {
files?: File[];
setDownloadUrl?: (url: string) => void;
setLoading?: (loading: boolean) => void;
}
const CompressPdfPanel: React.FC<CompressProps> = ({
files = [],
setDownloadUrl,
setLoading,
}) => {
const [selected, setSelected] = useState<boolean[]>(files.map(() => false));
const [compressionLevel, setCompressionLevel] = useState<number>(5);
const [grayscale, setGrayscale] = useState<boolean>(false);
const [removeMetadata, setRemoveMetadata] = useState<boolean>(false);
const [expectedSize, setExpectedSize] = useState<string>("");
const [aggressive, setAggressive] = useState<boolean>(false);
const [localLoading, setLocalLoading] = useState<boolean>(false);
2025-05-15 20:07:33 +01:00
// Update selection state if files prop changes
React.useEffect(() => {
setSelected(files.map(() => false));
}, [files]);
const handleCheckbox = (idx: number) => {
2025-05-15 20:07:33 +01:00
setSelected(sel => sel.map((v, i) => (i === idx ? !v : v)));
};
const handleCompress = async () => {
const selectedFiles = files.filter((_, i) => selected[i]);
if (selectedFiles.length === 0) return;
setLocalLoading(true);
setLoading?.(true);
const formData = new FormData();
selectedFiles.forEach(file => formData.append("fileInput", file));
formData.append("compressionLevel", compressionLevel.toString());
formData.append("grayscale", grayscale.toString());
formData.append("removeMetadata", removeMetadata.toString());
formData.append("aggressive", aggressive.toString());
2025-05-15 20:07:33 +01:00
if (expectedSize) formData.append("expectedSize", expectedSize);
try {
const res = await fetch("/api/v1/general/compress-pdf", {
method: "POST",
body: formData,
});
const blob = await res.blob();
setDownloadUrl?.(URL.createObjectURL(blob));
2025-05-15 20:07:33 +01:00
} finally {
setLocalLoading(false);
setLoading?.(false);
}
};
return (
<Paper shadow="xs" p="md" radius="md" withBorder>
<Stack>
<Text fw={500} mb={4}>Select files to compress:</Text>
<Stack gap={4}>
{files.length === 0 && <Text c="dimmed" size="sm">No files loaded.</Text>}
2025-05-15 20:07:33 +01:00
{files.map((file, idx) => (
<Checkbox
key={file.name + idx}
label={file.name}
checked={selected[idx] || false}
onChange={() => handleCheckbox(idx)}
/>
))}
</Stack>
<Stack gap={4} mb={14}>
2025-05-15 20:07:33 +01:00
<Text size="sm" style={{ minWidth: 140 }}>Compression Level</Text>
<Slider
min={1}
max={9}
step={1}
value={compressionLevel}
onChange={setCompressionLevel}
marks={[
{ value: 1, label: "1" },
{ value: 5, label: "5" },
{ value: 9, label: "9" },
]}
style={{ flex: 1 }}
/>
</Stack>
2025-05-15 20:07:33 +01:00
<Checkbox
label="Convert images to grayscale"
checked={grayscale}
onChange={e => setGrayscale(e.currentTarget.checked)}
/>
<Checkbox
label="Remove PDF metadata"
checked={removeMetadata}
onChange={e => setRemoveMetadata(e.currentTarget.checked)}
/>
<Checkbox
label="Aggressive compression (may reduce quality)"
checked={aggressive}
onChange={e => setAggressive(e.currentTarget.checked)}
/>
<TextInput
label="Expected output size (e.g. 2MB, 500KB)"
placeholder="Optional"
value={expectedSize}
onChange={e => setExpectedSize(e.currentTarget.value)}
/>
<Button
onClick={handleCompress}
loading={localLoading}
disabled={selected.every(v => !v)}
fullWidth
mt="md"
>
Compress Selected PDF{selected.filter(Boolean).length > 1 ? "s" : ""}
</Button>
</Stack>
</Paper>
);
};
export default CompressPdfPanel;