ai-chatbot/components/canvas/CanvasImageDownloadProgressDialog.tsx

94 lines
3.6 KiB
TypeScript

"use client";
import { createPortal } from "react-dom";
import { Download, Loader2, X } from "lucide-react";
import type { CanvasImageDownloadProgress } from "@/lib/client/canvas-image-download";
import { cn } from "@/lib/utils";
type CanvasImageDownloadProgressDialogProps = {
progress: CanvasImageDownloadProgress | null;
onCancel?: () => void;
};
function formatBytes(bytes?: number) {
if (!bytes || !Number.isFinite(bytes) || bytes <= 0) return "";
if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))} KB`;
return `${(bytes / 1024 / 1024).toFixed(bytes < 10 * 1024 * 1024 ? 1 : 0)} MB`;
}
export function CanvasImageDownloadProgressDialog({
onCancel,
progress,
}: CanvasImageDownloadProgressDialogProps) {
if (!progress || typeof document === "undefined") return null;
const percent =
typeof progress.percent === "number" && Number.isFinite(progress.percent)
? Math.max(0, Math.min(100, Math.round(progress.percent)))
: null;
const loadedLabel = formatBytes(progress.loadedBytes);
const totalLabel = formatBytes(progress.totalBytes);
return createPortal(
<div
className="fixed inset-0 z-[10000] flex items-center justify-center bg-black/42 px-4 backdrop-blur-[2px]"
data-canvas-download-progress="true"
>
<div className="w-full max-w-[360px] rounded-[var(--ui-radius-panel)] border border-white/12 bg-[#111317] p-4 text-white shadow-[0_20px_64px_rgba(0,0,0,0.4)]">
<div className="flex items-start justify-between gap-3">
<div className="flex items-center gap-2.5">
<span className="flex h-9 w-9 items-center justify-center rounded-[var(--ui-radius-control)] bg-white/10 text-cyan-200">
{progress.phase === "saving" ? (
<Download className="h-4 w-4" />
) : (
<Loader2 className="h-4 w-4 animate-spin" />
)}
</span>
<div>
<div className="text-[14px] font-semibold">下载原图</div>
<div className="mt-0.5 text-[12px] text-white/58">{progress.message}</div>
</div>
</div>
{onCancel ? (
<button
className="flex h-7 w-7 items-center justify-center rounded-[var(--ui-radius-control)] text-white/60 transition hover:bg-white/10 hover:text-white"
onClick={onCancel}
title="取消下载"
type="button"
>
<X className="h-4 w-4" />
</button>
) : null}
</div>
<div className="mt-4">
<div className="mb-2 flex items-center justify-between text-[12px] text-white/55">
<span>{loadedLabel && totalLabel ? `${loadedLabel} / ${totalLabel}` : "正在处理文件"}</span>
<span>{percent !== null ? `${percent}%` : "..."}</span>
</div>
<div className="relative h-2 overflow-hidden rounded-full bg-white/10">
<div
className={cn(
"absolute inset-y-0 left-0 rounded-full bg-gradient-to-r from-cyan-300 via-sky-400 to-emerald-300",
percent === null && "w-1/3 animate-[download-indeterminate_1.15s_ease-in-out_infinite]"
)}
style={percent !== null ? { width: `${percent}%` } : undefined}
/>
</div>
</div>
</div>
<style jsx global>{`
@keyframes download-indeterminate {
0% {
transform: translateX(-120%);
}
100% {
transform: translateX(320%);
}
}
`}</style>
</div>,
document.body
);
}