This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import FileDropzone from '$lib/components/FileDropzone.svelte';
|
||||
import PdfPageGrid from '$lib/components/PdfPageGrid.svelte';
|
||||
import Button from '$lib/components/ui/button.svelte';
|
||||
import { downloadBlob } from '$lib/download';
|
||||
import { loadPdfDocument, type PdfDocumentHandle } from '$lib/pdf-thumbnails';
|
||||
|
||||
let pdfFile: File | null = $state(null);
|
||||
let pdfHandle: PdfDocumentHandle | null = $state(null);
|
||||
let selectedPages = $state(new Set<number>());
|
||||
let isProcessing = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let loadingError = $state<string | null>(null);
|
||||
|
||||
const handleFileSelected = async (files: File[]) => {
|
||||
const file = files[0];
|
||||
if (!file) return;
|
||||
|
||||
pdfHandle?.destroy();
|
||||
pdfHandle = null;
|
||||
pdfFile = file;
|
||||
selectedPages = new Set();
|
||||
loadingError = null;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const bytes = new Uint8Array(await file.arrayBuffer());
|
||||
pdfHandle = await loadPdfDocument(bytes);
|
||||
} catch (err) {
|
||||
console.error('Failed to load PDF:', err);
|
||||
loadingError = 'PDF konnte nicht geladen werden.';
|
||||
pdfHandle = null;
|
||||
}
|
||||
};
|
||||
|
||||
const togglePage = (pageNumber: number) => {
|
||||
const next = new Set(selectedPages);
|
||||
if (next.has(pageNumber)) {
|
||||
next.delete(pageNumber);
|
||||
} else {
|
||||
next.add(pageNumber);
|
||||
}
|
||||
selectedPages = next;
|
||||
};
|
||||
|
||||
const selectAll = () => {
|
||||
if (!pdfHandle) return;
|
||||
const all = new Set<number>();
|
||||
for (let i = 1; i <= pdfHandle.pageCount; i++) all.add(i);
|
||||
selectedPages = all;
|
||||
};
|
||||
|
||||
const selectNone = () => {
|
||||
selectedPages = new Set();
|
||||
};
|
||||
|
||||
const removePages = async () => {
|
||||
if (!pdfFile || !pdfHandle || selectedPages.size === 0) return;
|
||||
|
||||
isProcessing = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const bytes = new Uint8Array(await pdfFile.arrayBuffer());
|
||||
const sourcePdf = await PDFDocument.load(bytes);
|
||||
const outputPdf = await PDFDocument.create();
|
||||
|
||||
const pagesToKeep: number[] = [];
|
||||
for (let i = 0; i < sourcePdf.getPageCount(); i++) {
|
||||
if (!selectedPages.has(i + 1)) {
|
||||
pagesToKeep.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (pagesToKeep.length === 0) {
|
||||
error = 'Es muss mindestens eine Seite übrig bleiben.';
|
||||
isProcessing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const copiedPages = await outputPdf.copyPages(sourcePdf, pagesToKeep);
|
||||
copiedPages.forEach((page) => outputPdf.addPage(page));
|
||||
|
||||
const resultBytes = await outputPdf.save();
|
||||
const baseName = pdfFile.name.replace(/\.pdf$/i, '');
|
||||
downloadBlob(resultBytes, `${baseName}_ohne_seiten.pdf`);
|
||||
} catch (err) {
|
||||
console.error('Failed to remove pages:', err);
|
||||
error = 'Fehler beim Entfernen der Seiten.';
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
};
|
||||
|
||||
onDestroy(() => {
|
||||
pdfHandle?.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-primary-900">Seiten entfernen</h1>
|
||||
<p class="mt-2 text-sm text-primary-700">
|
||||
Wählen Sie Seiten aus, die Sie aus dem PDF entfernen möchten.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if !pdfHandle}
|
||||
<FileDropzone onFilesSelected={handleFileSelected} />
|
||||
{#if loadingError}
|
||||
<div class="mt-4 rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800">
|
||||
{loadingError}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="space-y-4">
|
||||
<FileDropzone
|
||||
onFilesSelected={handleFileSelected}
|
||||
label="Anderes PDF ablegen"
|
||||
sublabel={pdfFile?.name ?? ''}
|
||||
class="py-4"
|
||||
/>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h2 class="text-lg font-semibold text-primary-900">
|
||||
{pdfFile?.name} — {pdfHandle.pageCount} Seite{pdfHandle.pageCount === 1 ? '' : 'n'}
|
||||
</h2>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="tertiary" class="min-w-0 px-3" onclick={selectAll}>
|
||||
Alle auswählen
|
||||
</Button>
|
||||
<Button variant="tertiary" class="min-w-0 px-3" onclick={selectNone}>
|
||||
Auswahl aufheben
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PdfPageGrid thumbnails={pdfHandle.thumbnails} {selectedPages} onPageClick={togglePage} />
|
||||
|
||||
{#if error}
|
||||
<div class="rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm text-primary-700">
|
||||
{selectedPages.size} Seite{selectedPages.size === 1 ? '' : 'n'} zum Entfernen ausgewählt
|
||||
</p>
|
||||
<Button
|
||||
onclick={removePages}
|
||||
disabled={isProcessing ||
|
||||
selectedPages.size === 0 ||
|
||||
selectedPages.size >= pdfHandle.pageCount}
|
||||
>
|
||||
{isProcessing ? 'Wird verarbeitet...' : 'Seiten entfernen & herunterladen'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user