Files
bea-edit/src/routes/tools/encrypt/+page.svelte
T
rehlert ea21fc000f
Build and Push Docker Image / build (push) Successful in 1m35s
batch + stamping
2026-08-28 08:02:10 +02:00

193 lines
5.9 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { PDFDocument } from '@cantoo/pdf-lib';
import BatchFileList from '$lib/components/BatchFileList.svelte';
import FileDropzone from '$lib/components/FileDropzone.svelte';
import Button from '$lib/components/ui/button.svelte';
import { createBatchItem, runBatch, type BatchItem } from '$lib/batch';
import { downloadBlob } from '$lib/download';
import { Lock } from '@lucide/svelte';
let items = $state<BatchItem[]>([]);
let password = $state('');
let confirmPassword = $state('');
let allowPrinting = $state(true);
let allowCopying = $state(true);
let isRunning = $state(false);
let error = $state<string | null>(null);
const pendingCount = $derived(items.filter((item) => item.status === 'pending').length);
const canRun = $derived(
!isRunning && pendingCount > 0 && password.length > 0 && password === confirmPassword
);
const handleFilesSelected = (files: File[]) => {
const knownKeys = new Set(items.map((item) => `${item.file.name}:${item.file.size}`));
const additions = files
.filter((file) => !knownKeys.has(`${file.name}:${file.size}`))
.map((file) => createBatchItem(file));
items = [...items, ...additions];
error = null;
};
const updateItem = (id: string, patch: Partial<BatchItem>) => {
items = items.map((item) => (item.id === id ? { ...item, ...patch } : item));
};
const processFile = async (file: File) => {
// The password and permissions are read per file so a change during a
// run only affects the still pending files.
const bytes = new Uint8Array(await file.arrayBuffer());
const pdfDoc = await PDFDocument.load(bytes);
pdfDoc.encrypt({
userPassword: password,
ownerPassword: crypto.randomUUID(),
permissions: {
printing: allowPrinting,
copying: allowCopying
}
});
const encryptedBytes = await pdfDoc.save();
return { bytes: encryptedBytes, name: `${file.name.replace(/\.pdf$/i, '')}_geschuetzt.pdf` };
};
const startBatch = async () => {
if (isRunning) return;
const queue = items.filter((item) => item.status === 'pending');
if (queue.length === 0) return;
isRunning = true;
try {
await runBatch(queue, processFile, updateItem, {
errorMessage: 'Diese PDF konnte nicht verschlüsselt werden.',
// Skip rows removed while they were still waiting in the queue.
shouldProcess: (id) => items.some((item) => item.id === id)
});
} finally {
isRunning = false;
}
};
const retryItem = async (id: string) => {
if (isRunning) return;
const item = items.find((entry) => entry.id === id);
if (!item) return;
isRunning = true;
try {
await runBatch([item], processFile, updateItem, {
errorMessage: 'Diese PDF konnte nicht verschlüsselt werden.'
});
} finally {
isRunning = false;
}
};
const removeItem = (id: string) => {
items = items.filter((item) => item.id !== id);
};
const downloadItem = (item: BatchItem) => {
if (item.resultBytes && item.resultName) downloadBlob(item.resultBytes, item.resultName);
};
</script>
<div class="mx-auto max-w-4xl space-y-6">
<div>
<h1 class="text-2xl font-bold text-foreground">Passwort setzen</h1>
<p class="mt-2 text-sm text-primary">
Verschlüsseln Sie Ihre PDFs mit einem Passwort, um sie vor unbefugtem Zugriff zu schützen
einzeln oder mehrere in einem Durchlauf.
</p>
</div>
<FileDropzone
onFilesSelected={handleFilesSelected}
multiple
label={items.length === 0 ? 'Dateien hinzufügen' : 'Weitere Dateien ablegen'}
sublabel={items.length === 0
? 'PDFs hier ablegen oder zum Auswählen klicken'
: 'Weitere PDFs hinzufügen bestehende Dateien bleiben erhalten'}
/>
{#if items.length > 0}
<div class="rounded-2xl border border-border bg-card p-4 space-y-4">
<div class="flex items-center gap-2 text-foreground">
<Lock class="h-5 w-5" />
<h2 class="text-lg font-semibold">Passwort</h2>
</div>
<p class="text-xs text-primary">Das Passwort gilt für alle Dateien der Liste.</p>
<div>
<label class="block text-sm font-medium text-foreground mb-1" for="encrypt-password">
Passwort
</label>
<input
id="encrypt-password"
type="password"
bind:value={password}
class="w-full rounded-lg border border-border px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
placeholder="Passwort eingeben"
/>
</div>
<div>
<label
class="block text-sm font-medium text-foreground mb-1"
for="encrypt-password-confirmation"
>
Passwort bestätigen
</label>
<input
id="encrypt-password-confirmation"
type="password"
bind:value={confirmPassword}
class="w-full rounded-lg border border-border px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
placeholder="Passwort wiederholen"
/>
</div>
<div class="space-y-2">
<p class="text-sm font-medium text-foreground">Berechtigungen</p>
<div class="flex items-center gap-2">
<input type="checkbox" id="allow-printing" bind:checked={allowPrinting} class="h-4 w-4" />
<label for="allow-printing" class="text-sm text-primary">Drucken erlauben</label>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" id="allow-copying" bind:checked={allowCopying} class="h-4 w-4" />
<label for="allow-copying" class="text-sm text-primary">Kopieren erlauben</label>
</div>
</div>
{#if isRunning}
<p class="text-xs text-primary" role="note">
Änderungen am Passwort gelten für die noch ausstehenden Dateien.
</p>
{/if}
</div>
<BatchFileList
{items}
zipBaseName="geschuetzt"
onDownload={downloadItem}
onRemove={removeItem}
onRetry={retryItem}
/>
{/if}
{#if error}
<div class="rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800">
{error}
</div>
{/if}
{#if items.length > 0}
<div class="flex justify-end">
<Button onclick={startBatch} disabled={!canRun}>
{isRunning ? 'Wird verschlüsselt...' : 'Passwort setzen'}
</Button>
</div>
{/if}
</div>