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";
|
|
|
|
|
2025-05-21 21:47:44 +01:00
|
|
|
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]);
|
|
|
|
|
2025-05-21 21:47:44 +01:00
|
|
|
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));
|
2025-05-21 21:47:44 +01:00
|
|
|
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();
|
2025-05-21 21:47:44 +01:00
|
|
|
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>
|
2025-05-21 21:47:44 +01:00
|
|
|
<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>
|
2025-05-21 21:47:44 +01:00
|
|
|
<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 }}
|
|
|
|
/>
|
2025-05-21 21:47:44 +01:00
|
|
|
</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>
|
|
|
|
);
|
2025-05-21 21:47:44 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
export default CompressPdfPanel;
|