mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-12 09:47:51 +02:00
feat: automatically update browsers on new versions
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
* - Progress bars for downloads/updates
|
||||
* - Success/error states
|
||||
* - Customizable icons and content
|
||||
* - Auto-update notifications
|
||||
*
|
||||
* Usage Examples:
|
||||
*
|
||||
@@ -23,6 +24,11 @@
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Auto-update toast:
|
||||
* ```
|
||||
* showAutoUpdateToast("Firefox", "125.0.1");
|
||||
* ```
|
||||
*
|
||||
* Download progress toast:
|
||||
* ```
|
||||
* showToast({
|
||||
@@ -47,6 +53,7 @@ import {
|
||||
LuCheckCheck,
|
||||
LuDownload,
|
||||
LuRefreshCw,
|
||||
LuRocket,
|
||||
LuTriangleAlert,
|
||||
} from "react-icons/lu";
|
||||
|
||||
@@ -139,6 +146,10 @@ function getToastIcon(type: ToastProps["type"], stage?: string) {
|
||||
return (
|
||||
<LuRefreshCw className="flex-shrink-0 w-4 h-4 text-purple-500 animate-spin" />
|
||||
);
|
||||
case "loading":
|
||||
return (
|
||||
<div className="flex-shrink-0 w-4 h-4 rounded-full border-2 border-blue-500 animate-spin border-t-transparent" />
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div className="flex-shrink-0 w-4 h-4 rounded-full border-2 border-blue-500 animate-spin border-t-transparent" />
|
||||
@@ -151,11 +162,33 @@ export function UnifiedToast(props: ToastProps) {
|
||||
const stage = "stage" in props ? props.stage : undefined;
|
||||
const progress = "progress" in props ? props.progress : undefined;
|
||||
|
||||
// Check if this is an auto-update toast
|
||||
const isAutoUpdate = title.includes("update started");
|
||||
|
||||
return (
|
||||
<div className="flex items-start p-3 w-96 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">{getToastIcon(type, stage)}</div>
|
||||
<div
|
||||
className={`flex items-start p-3 w-96 rounded-lg border shadow-lg ${
|
||||
isAutoUpdate
|
||||
? "bg-emerald-50 border-emerald-200 dark:bg-emerald-950 dark:border-emerald-800"
|
||||
: "bg-white border-gray-200 dark:bg-gray-800 dark:border-gray-700"
|
||||
}`}
|
||||
data-toast-type={isAutoUpdate ? "auto-update" : "default"}
|
||||
>
|
||||
<div className="mr-3 mt-0.5">
|
||||
{isAutoUpdate ? (
|
||||
<LuRocket className="flex-shrink-0 w-4 h-4 text-emerald-500" />
|
||||
) : (
|
||||
getToastIcon(type, stage)
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium leading-tight text-gray-900 dark:text-white">
|
||||
<p
|
||||
className={`text-sm font-medium leading-tight ${
|
||||
isAutoUpdate
|
||||
? "text-emerald-900 dark:text-emerald-100"
|
||||
: "text-gray-900 dark:text-white"
|
||||
}`}
|
||||
>
|
||||
{title}
|
||||
</p>
|
||||
|
||||
@@ -225,7 +258,13 @@ export function UnifiedToast(props: ToastProps) {
|
||||
|
||||
{/* Description */}
|
||||
{description && (
|
||||
<p className="mt-1 text-xs leading-tight text-gray-600 dark:text-gray-300">
|
||||
<p
|
||||
className={`mt-1 text-xs leading-tight ${
|
||||
isAutoUpdate
|
||||
? "text-emerald-700 dark:text-emerald-300"
|
||||
: "text-gray-600 dark:text-gray-300"
|
||||
}`}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -275,70 +274,6 @@ export function ProfilesDataTable({
|
||||
return browserA.localeCompare(browserB);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "version",
|
||||
header: "Version",
|
||||
},
|
||||
{
|
||||
id: "status",
|
||||
header: ({ column }) => {
|
||||
const isSorted = column.getIsSorted();
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
column.toggleSorting(column.getIsSorted() === "asc");
|
||||
}}
|
||||
className="p-0 h-auto font-semibold hover:bg-transparent"
|
||||
>
|
||||
Status
|
||||
{isSorted === "asc" && <LuChevronUp className="ml-2 w-4 h-4" />}
|
||||
{isSorted === "desc" && (
|
||||
<LuChevronDown className="ml-2 w-4 h-4" />
|
||||
)}
|
||||
{!isSorted && (
|
||||
<LuChevronDown className="ml-2 w-4 h-4 opacity-50" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const profile = row.original;
|
||||
const isRunning = isClient && runningProfiles.has(profile.name);
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<Badge
|
||||
variant={isRunning ? "default" : "secondary"}
|
||||
className="text-xs w-fit"
|
||||
>
|
||||
{isClient ? (isRunning ? "Running" : "Stopped") : "Loading..."}
|
||||
</Badge>
|
||||
{isClient && isRunning && profile.process_id && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
PID: {profile.process_id}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
enableSorting: true,
|
||||
sortingFn: (rowA, rowB) => {
|
||||
// If not on client, sort by name only to ensure consistency
|
||||
if (!isClient) {
|
||||
return rowA.original.name.localeCompare(rowB.original.name);
|
||||
}
|
||||
|
||||
const isRunningA = runningProfiles.has(rowA.original.name);
|
||||
const isRunningB = runningProfiles.has(rowB.original.name);
|
||||
|
||||
// Running profiles come first, then stopped ones
|
||||
// Secondary sort by profile name
|
||||
if (isRunningA === isRunningB) {
|
||||
return rowA.original.name.localeCompare(rowB.original.name);
|
||||
}
|
||||
return isRunningA ? -1 : 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "proxy",
|
||||
header: "Proxy",
|
||||
|
||||
Reference in New Issue
Block a user