94 lines
3.6 KiB
TypeScript
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
|
|
);
|
|
}
|