Untitled
unknown
plain_text
10 months ago
6.1 kB
6
Indexable
// components/download-all.tsx
import { useState, useEffect, useRef } from "react";
import { Download } from "lucide-react";
import { motion } from "framer-motion";
import { useParams } from "next/navigation";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
export function DownloadAll() {
const [isDownloading, setIsDownloading] = useState(false);
const [progress, setProgress] = useState(0);
const [startTime, setStartTime] = useState<number | null>(null);
const [statusMessage, setStatusMessage] = useState("");
const workerRef = useRef<Worker | null>(null);
const params = useParams();
const clientId = params.clientId as string;
useEffect(() => {
if (typeof window !== 'undefined') {
workerRef.current = new Worker('/zip-worker.js');
workerRef.current.onmessage = (e) => {
const { type, payload } = e.data;
switch (type) {
case 'progress':
setProgress(Math.min(90, payload.percent));
setStatusMessage(`Verarbeite ${payload.filename} (${payload.processed} von ${payload.total})`);
break;
case 'generating':
setProgress(payload.percent);
setStatusMessage(payload.message);
break;
case 'complete':
if (payload instanceof Blob && payload.size > 0) {
handleDownloadComplete(payload);
} else {
handleError('Generated ZIP is empty');
}
break;
case 'fileError':
console.error(`Error processing ${payload.filename}: ${payload.error}`);
toast.error(`Fehler beim Verarbeiten von ${payload.filename}`);
break;
case 'error':
handleError(payload);
break;
case 'processing':
setStatusMessage(payload);
break;
}
};
}
return () => {
workerRef.current?.terminate();
};
}, []);
const handleDownloadComplete = (blob: Blob) => {
console.log(`ZIP file size: ${(blob.size / 1024 / 1024).toFixed(2)} MB`);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${clientId}-fotos.zip`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => {
URL.revokeObjectURL(url);
setProgress(100);
setStatusMessage("Download abgeschlossen!");
toast.success("Download erfolgreich");
setTimeout(() => {
setIsDownloading(false);
setProgress(0);
setStartTime(null);
setStatusMessage("");
}, 3000);
}, 1000);
};
const handleError = (error: string) => {
console.error("Download failed:", error);
setIsDownloading(false);
setProgress(0);
setStartTime(null);
setStatusMessage("");
toast.error("Download fehlgeschlagen", {
description: error || "Bitte versuchen Sie es später erneut."
});
};
const handleDownloadAll = async () => {
try {
setIsDownloading(true);
setProgress(0);
setStartTime(Date.now());
setStatusMessage("Sammle Dateien...");
toast.info("Download wird vorbereitet", {
description: "Die Bilder werden zusammengefasst. Dies kann einige Minuten dauern."
});
const response = await fetch(`/api/download-urls/${clientId}`);
if (!response.ok) throw new Error('Failed to get download URLs');
const { files } = await response.json();
console.log(`Starting download of ${files.length} files...`);
// Start the worker with all files
workerRef.current?.postMessage({
type: 'start',
payload: { files }
});
} catch (error) {
handleError(error.message);
}
};
const formatTimeRemaining = (seconds: number) => {
if (seconds < 60) return `${Math.round(seconds)} Sekunden`;
const minutes = Math.ceil(seconds / 60);
return `${minutes} ${minutes === 1 ? 'Minute' : 'Minuten'}`;
};
const getStatusMessage = () => {
if (statusMessage) return statusMessage;
if (!startTime) return "Wird vorbereitet...";
if (progress >= 100) return "Download abgeschlossen!";
const elapsedSeconds = (Date.now() - startTime) / 1000;
if (progress > 0) {
const estimatedTotalSeconds = (elapsedSeconds / progress) * 100;
const remainingSeconds = Math.max(0, estimatedTotalSeconds - elapsedSeconds);
return `Download läuft... ${progress.toFixed(1)}% (ca. ${formatTimeRemaining(remainingSeconds)} verbleibend)`;
}
return "Download startet...";
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="mb-12 rounded-lg border bg-card p-4 text-card-foreground shadow-sm"
>
<div className="flex flex-col items-center gap-4 text-center sm:flex-row sm:justify-between sm:text-left">
<div>
<h2 className="text-lg font-semibold">Alle Fotos herunterladen</h2>
<p className="text-sm text-muted-foreground">
Erhalten Sie alle Fotos in Originalqualität in einer einzigen Datei
</p>
</div>
<div className="flex flex-col gap-2 min-w-[200px]">
{isDownloading ? (
<>
<Progress value={progress} className="w-full" />
<p className="text-sm text-muted-foreground text-center">
{getStatusMessage()}
</p>
</>
) : (
<Button size="lg" onClick={handleDownloadAll}>
<Download className="mr-2 h-4 w-4" />
Alle herunterladen
</Button>
)}
</div>
</div>
</motion.div>
);
}Editor is loading...
Leave a Comment