mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-01 04:41:36 +02:00
188 lines
5.9 KiB
TypeScript
188 lines
5.9 KiB
TypeScript
"use client";
|
|
|
|
import { FaDownload, FaTimes } from "react-icons/fa";
|
|
import { LuCheckCheck, LuCog, LuRefreshCw } from "react-icons/lu";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import type { AppUpdateInfo, AppUpdateProgress } from "@/types";
|
|
|
|
interface AppUpdateToastProps {
|
|
updateInfo: AppUpdateInfo;
|
|
onUpdate: (updateInfo: AppUpdateInfo) => Promise<void>;
|
|
onDismiss: () => void;
|
|
isUpdating?: boolean;
|
|
updateProgress?: AppUpdateProgress | null;
|
|
}
|
|
|
|
function getStageIcon(stage?: string, isUpdating?: boolean) {
|
|
if (!isUpdating) {
|
|
return <FaDownload className="flex-shrink-0 w-5 h-5 text-blue-500" />;
|
|
}
|
|
|
|
switch (stage) {
|
|
case "downloading":
|
|
return <FaDownload className="flex-shrink-0 w-5 h-5 text-blue-500" />;
|
|
case "extracting":
|
|
return (
|
|
<LuRefreshCw className="flex-shrink-0 w-5 h-5 text-blue-500 animate-spin" />
|
|
);
|
|
case "installing":
|
|
return (
|
|
<LuCog className="flex-shrink-0 w-5 h-5 text-blue-500 animate-spin" />
|
|
);
|
|
case "completed":
|
|
return <LuCheckCheck className="flex-shrink-0 w-5 h-5 text-green-500" />;
|
|
default:
|
|
return (
|
|
<LuRefreshCw className="flex-shrink-0 w-5 h-5 text-blue-500 animate-spin" />
|
|
);
|
|
}
|
|
}
|
|
|
|
function getStageDisplayName(stage?: string) {
|
|
switch (stage) {
|
|
case "downloading":
|
|
return "Downloading";
|
|
case "extracting":
|
|
return "Extracting";
|
|
case "installing":
|
|
return "Installing";
|
|
case "completed":
|
|
return "Completed";
|
|
default:
|
|
return "Updating";
|
|
}
|
|
}
|
|
|
|
export function AppUpdateToast({
|
|
updateInfo,
|
|
onUpdate,
|
|
onDismiss,
|
|
isUpdating = false,
|
|
updateProgress,
|
|
}: AppUpdateToastProps) {
|
|
const handleUpdateClick = async () => {
|
|
await onUpdate(updateInfo);
|
|
};
|
|
|
|
const showProgress =
|
|
isUpdating &&
|
|
updateProgress?.stage === "downloading" &&
|
|
updateProgress.percentage !== undefined;
|
|
|
|
return (
|
|
<div className="flex items-start p-4 w-full max-w-md bg-white rounded-lg border border-gray-200 shadow-lg dark:bg-gray-800 dark:border-gray-700">
|
|
<div className="mr-3 mt-0.5">
|
|
{getStageIcon(updateProgress?.stage, isUpdating)}
|
|
</div>
|
|
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex gap-2 justify-between items-start">
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex gap-2 items-center">
|
|
<span className="text-sm font-semibold text-foreground">
|
|
{isUpdating
|
|
? `${getStageDisplayName(updateProgress?.stage)} Donut Browser Update`
|
|
: "Donut Browser Update Available"}
|
|
</span>
|
|
<Badge
|
|
variant={updateInfo.is_nightly ? "secondary" : "default"}
|
|
className="text-xs"
|
|
>
|
|
{updateInfo.is_nightly ? "Nightly" : "Stable"}
|
|
</Badge>
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{isUpdating ? (
|
|
updateProgress?.message || "Updating..."
|
|
) : (
|
|
<>
|
|
Update from {updateInfo.current_version} to{" "}
|
|
<span className="font-medium">{updateInfo.new_version}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{!isUpdating && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={onDismiss}
|
|
className="p-0 w-6 h-6 shrink-0"
|
|
>
|
|
<FaTimes className="w-3 h-3" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Download progress */}
|
|
{showProgress && updateProgress && (
|
|
<div className="mt-2 space-y-1">
|
|
<div className="flex justify-between items-center">
|
|
<p className="flex-1 min-w-0 text-xs text-muted-foreground">
|
|
{updateProgress.percentage?.toFixed(1)}%
|
|
{updateProgress.speed && ` • ${updateProgress.speed} MB/s`}
|
|
{updateProgress.eta && ` • ${updateProgress.eta} remaining`}
|
|
</p>
|
|
</div>
|
|
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5">
|
|
<div
|
|
className="bg-blue-500 h-1.5 rounded-full transition-all duration-300"
|
|
style={{ width: `${updateProgress.percentage}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Other stage progress (without percentage) */}
|
|
{isUpdating &&
|
|
updateProgress &&
|
|
updateProgress.stage !== "downloading" && (
|
|
<div className="mt-2">
|
|
<p className="text-xs text-muted-foreground">
|
|
{updateProgress.message}
|
|
</p>
|
|
{updateProgress.stage === "extracting" && (
|
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
Preparing update files...
|
|
</p>
|
|
)}
|
|
{updateProgress.stage === "installing" && (
|
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
Installing new version...
|
|
</p>
|
|
)}
|
|
{updateProgress.stage === "completed" && (
|
|
<p className="mt-1 text-xs text-green-600 dark:text-green-400">
|
|
Update completed! Restarting application...
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{!isUpdating && (
|
|
<div className="flex gap-2 items-center mt-3">
|
|
<Button
|
|
onClick={() => void handleUpdateClick()}
|
|
size="sm"
|
|
className="flex gap-2 items-center text-xs"
|
|
>
|
|
<FaDownload className="w-3 h-3" />
|
|
Update Now
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={onDismiss}
|
|
size="sm"
|
|
className="text-xs"
|
|
>
|
|
Later
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|