mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-07 15:33:57 +02:00
chore: linting for both js and rs
This commit is contained in:
@@ -16,8 +16,8 @@ import { VersionSelector } from "@/components/version-selector";
|
||||
import { useBrowserDownload } from "@/hooks/use-browser-download";
|
||||
import type { BrowserProfile } from "@/types";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { LuTriangleAlert } from "react-icons/lu";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LuTriangleAlert } from "react-icons/lu";
|
||||
|
||||
interface ChangeVersionDialogProps {
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -43,12 +43,11 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { toast as sonnerToast } from "sonner";
|
||||
import {
|
||||
LuCheckCheck,
|
||||
LuTriangleAlert,
|
||||
LuDownload,
|
||||
LuRefreshCw,
|
||||
LuTriangleAlert,
|
||||
} from "react-icons/lu";
|
||||
|
||||
interface BaseToastProps {
|
||||
@@ -123,7 +122,6 @@ function getToastIcon(type: ToastProps["type"], stage?: string) {
|
||||
return (
|
||||
<LuRefreshCw className="h-4 w-4 text-blue-500 animate-spin flex-shrink-0" />
|
||||
);
|
||||
case "loading":
|
||||
default:
|
||||
return (
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-2 border-blue-500 border-t-transparent flex-shrink-0" />
|
||||
@@ -214,204 +212,3 @@ export function UnifiedToast(props: ToastProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Unified toast function
|
||||
export function showToast(props: ToastProps & { id?: string }) {
|
||||
const toastId = props.id ?? `toast-${props.type}-${Date.now()}`;
|
||||
|
||||
// Improved duration logic - make toasts disappear more quickly
|
||||
let duration: number;
|
||||
if (props.duration !== undefined) {
|
||||
duration = props.duration;
|
||||
} else {
|
||||
switch (props.type) {
|
||||
case "loading":
|
||||
case "fetching":
|
||||
duration = 10000; // 10 seconds instead of infinite
|
||||
break;
|
||||
case "download":
|
||||
// Only keep infinite for active downloading, others get shorter durations
|
||||
if ("stage" in props && props.stage === "downloading") {
|
||||
duration = Number.POSITIVE_INFINITY;
|
||||
} else if ("stage" in props && props.stage === "completed") {
|
||||
duration = 3000; // Shorter duration for completed downloads
|
||||
} else {
|
||||
duration = 8000; // 8 seconds for extracting/verifying
|
||||
}
|
||||
break;
|
||||
case "version-update":
|
||||
duration = 15000; // 15 seconds instead of infinite
|
||||
break;
|
||||
case "success":
|
||||
duration = 3000; // Shorter success duration
|
||||
break;
|
||||
case "error":
|
||||
duration = 5000; // Reasonable error duration
|
||||
break;
|
||||
default:
|
||||
duration = 4000;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.type === "success") {
|
||||
sonnerToast.success(<UnifiedToast {...props} />, {
|
||||
id: toastId,
|
||||
duration,
|
||||
style: {
|
||||
background: "transparent",
|
||||
border: "none",
|
||||
boxShadow: "none",
|
||||
padding: 0,
|
||||
},
|
||||
});
|
||||
} else if (props.type === "error") {
|
||||
sonnerToast.error(<UnifiedToast {...props} />, {
|
||||
id: toastId,
|
||||
duration,
|
||||
style: {
|
||||
background: "transparent",
|
||||
border: "none",
|
||||
boxShadow: "none",
|
||||
padding: 0,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
sonnerToast.custom((id) => <UnifiedToast {...props} />, {
|
||||
id: toastId,
|
||||
duration,
|
||||
style: {
|
||||
background: "transparent",
|
||||
border: "none",
|
||||
boxShadow: "none",
|
||||
padding: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return toastId;
|
||||
}
|
||||
|
||||
// Convenience functions for common use cases
|
||||
export function showLoadingToast(
|
||||
title: string,
|
||||
options?: {
|
||||
id?: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
}
|
||||
) {
|
||||
return showToast({
|
||||
type: "loading",
|
||||
title,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function showDownloadToast(
|
||||
browserName: string,
|
||||
version: string,
|
||||
stage: "downloading" | "extracting" | "verifying" | "completed",
|
||||
progress?: { percentage: number; speed?: string; eta?: string },
|
||||
options?: { suppressCompletionToast?: boolean }
|
||||
) {
|
||||
const title =
|
||||
stage === "completed"
|
||||
? `${browserName} ${version} downloaded successfully!`
|
||||
: stage === "downloading"
|
||||
? `Downloading ${browserName} ${version}`
|
||||
: stage === "extracting"
|
||||
? `Extracting ${browserName} ${version}`
|
||||
: `Verifying ${browserName} ${version}`;
|
||||
|
||||
// Don't show completion toast if suppressed (for auto-update scenarios)
|
||||
if (stage === "completed" && options?.suppressCompletionToast) {
|
||||
dismissToast(`download-${browserName.toLowerCase()}-${version}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return showToast({
|
||||
type: "download",
|
||||
title,
|
||||
stage,
|
||||
progress,
|
||||
id: `download-${browserName.toLowerCase()}-${version}`,
|
||||
});
|
||||
}
|
||||
|
||||
export function showVersionUpdateToast(
|
||||
title: string,
|
||||
options?: {
|
||||
id?: string;
|
||||
description?: string;
|
||||
progress?: {
|
||||
current: number;
|
||||
total: number;
|
||||
found: number;
|
||||
};
|
||||
duration?: number;
|
||||
}
|
||||
) {
|
||||
return showToast({
|
||||
type: "version-update",
|
||||
title,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function showFetchingToast(
|
||||
browserName: string,
|
||||
options?: {
|
||||
id?: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
}
|
||||
) {
|
||||
return showToast({
|
||||
type: "fetching",
|
||||
title: `Checking for new ${browserName} versions...`,
|
||||
description:
|
||||
options?.description ?? "Fetching latest release information...",
|
||||
browserName,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function showSuccessToast(
|
||||
title: string,
|
||||
options?: {
|
||||
id?: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
}
|
||||
) {
|
||||
return showToast({
|
||||
type: "success",
|
||||
title,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export function showErrorToast(
|
||||
title: string,
|
||||
options?: {
|
||||
id?: string;
|
||||
description?: string;
|
||||
duration?: number;
|
||||
}
|
||||
) {
|
||||
return showToast({
|
||||
type: "error",
|
||||
title,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
// Generic helper for dismissing toasts
|
||||
export function dismissToast(id: string) {
|
||||
sonnerToast.dismiss(id);
|
||||
}
|
||||
|
||||
// Dismiss all toasts
|
||||
export function dismissAllToasts() {
|
||||
sonnerToast.dismiss();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useTableSorting } from "@/hooks/use-table-sorting";
|
||||
import { getBrowserDisplayName, getBrowserIcon } from "@/lib/browser-utils";
|
||||
import type { BrowserProfile } from "@/types";
|
||||
import {
|
||||
type ColumnDef,
|
||||
@@ -40,14 +42,12 @@ import {
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { LuChevronDown, LuChevronUp } from "react-icons/lu";
|
||||
import { IoEllipsisHorizontal } from "react-icons/io5";
|
||||
import * as React from "react";
|
||||
import { CiCircleCheck } from "react-icons/ci";
|
||||
import { useTableSorting } from "@/hooks/use-table-sorting";
|
||||
import { IoEllipsisHorizontal } from "react-icons/io5";
|
||||
import { LuChevronDown, LuChevronUp } from "react-icons/lu";
|
||||
import { Input } from "./ui/input";
|
||||
import { Label } from "./ui/label";
|
||||
import { getBrowserDisplayName, getBrowserIcon } from "@/lib/browser-utils";
|
||||
|
||||
interface ProfilesDataTableProps {
|
||||
data: BrowserProfile[];
|
||||
@@ -392,7 +392,17 @@ export function ProfilesDataTable({
|
||||
},
|
||||
},
|
||||
],
|
||||
[isClient, runningProfiles, isUpdating, data]
|
||||
[
|
||||
isClient,
|
||||
runningProfiles,
|
||||
isUpdating,
|
||||
data,
|
||||
onLaunchProfile,
|
||||
onKillProfile,
|
||||
onProxySettings,
|
||||
onDeleteProfile,
|
||||
onChangeVersion,
|
||||
]
|
||||
);
|
||||
|
||||
const table = useReactTable({
|
||||
|
||||
@@ -23,12 +23,12 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { getBrowserDisplayName, getBrowserIcon } from "@/lib/browser-utils";
|
||||
import type { BrowserProfile } from "@/types";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { LuCopy } from "react-icons/lu";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LuCopy } from "react-icons/lu";
|
||||
import { toast } from "sonner";
|
||||
import { getBrowserDisplayName, getBrowserIcon } from "@/lib/browser-utils";
|
||||
|
||||
interface ProfileSelectorDialogProps {
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { LuCheck } from "react-icons/lu";
|
||||
import type * as React from "react";
|
||||
import { LuCheck } from "react-icons/lu";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||
import { LuCheck, LuChevronRight, LuCircle } from "react-icons/lu";
|
||||
import type * as React from "react";
|
||||
import { LuCheck, LuChevronRight, LuCircle } from "react-icons/lu";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { LuCheck, LuChevronDown, LuChevronUp } from "react-icons/lu";
|
||||
import type * as React from "react";
|
||||
import { LuCheck, LuChevronDown, LuChevronUp } from "react-icons/lu";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { toast } from "sonner";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { FaDownload, FaTimes } from "react-icons/fa";
|
||||
import { showToast } from "@/components/custom-toast";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getBrowserDisplayName } from "@/lib/browser-utils";
|
||||
import React from "react";
|
||||
import { FaDownload, FaTimes } from "react-icons/fa";
|
||||
import { LuDownload } from "react-icons/lu";
|
||||
|
||||
interface UpdateNotification {
|
||||
id: string;
|
||||
@@ -28,7 +26,7 @@ interface UpdateNotificationProps {
|
||||
isUpdating?: boolean;
|
||||
}
|
||||
|
||||
function UpdateNotificationComponent({
|
||||
export function UpdateNotificationComponent({
|
||||
notification,
|
||||
onUpdate,
|
||||
onDismiss,
|
||||
@@ -108,198 +106,3 @@ function UpdateNotificationComponent({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function useUpdateNotifications() {
|
||||
const [notifications, setNotifications] = useState<UpdateNotification[]>([]);
|
||||
const [updatingBrowsers, setUpdatingBrowsers] = useState<Set<string>>(
|
||||
new Set()
|
||||
);
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
// Ensure we're on the client side to prevent hydration mismatches
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
const checkForUpdates = useCallback(async () => {
|
||||
if (!isClient) return; // Only run on client side
|
||||
|
||||
try {
|
||||
const updates = await invoke<UpdateNotification[]>(
|
||||
"check_for_browser_updates"
|
||||
);
|
||||
setNotifications(updates);
|
||||
|
||||
// Show toasts for new notifications - we'll define handleUpdate and handleDismiss separately
|
||||
// to avoid circular dependencies
|
||||
} catch (error) {
|
||||
console.error("Failed to check for updates:", error);
|
||||
}
|
||||
}, [isClient]);
|
||||
|
||||
const handleUpdate = useCallback(
|
||||
async (browser: string, newVersion: string) => {
|
||||
try {
|
||||
setUpdatingBrowsers((prev) => new Set(prev).add(browser));
|
||||
const browserDisplayName = getBrowserDisplayName(browser);
|
||||
|
||||
// Dismiss all notifications for this browser first
|
||||
const browserNotifications = notifications.filter(
|
||||
(n) => n.browser === browser
|
||||
);
|
||||
for (const notification of browserNotifications) {
|
||||
toast.dismiss(notification.id);
|
||||
await invoke("dismiss_update_notification", {
|
||||
notificationId: notification.id,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if browser already exists before downloading
|
||||
const isDownloaded = await invoke<boolean>("check_browser_exists", {
|
||||
browserStr: browser,
|
||||
version: newVersion,
|
||||
});
|
||||
|
||||
if (isDownloaded) {
|
||||
// Browser already exists, skip download and go straight to profile update
|
||||
console.log(
|
||||
`${browserDisplayName} ${newVersion} already exists, skipping download`
|
||||
);
|
||||
} else {
|
||||
// Mark download as auto-update in the backend for toast suppression
|
||||
await invoke("mark_auto_update_download", {
|
||||
browser,
|
||||
version: newVersion,
|
||||
});
|
||||
|
||||
// Download the browser (progress will be handled by use-browser-download hook)
|
||||
await invoke("download_browser", {
|
||||
browserStr: browser,
|
||||
version: newVersion,
|
||||
});
|
||||
}
|
||||
|
||||
// Complete the update with auto-update of profile versions
|
||||
const updatedProfiles = await invoke<string[]>(
|
||||
"complete_browser_update_with_auto_update",
|
||||
{
|
||||
browser,
|
||||
newVersion,
|
||||
}
|
||||
);
|
||||
|
||||
// Show success message based on whether profiles were updated
|
||||
if (updatedProfiles.length > 0) {
|
||||
const profileText =
|
||||
updatedProfiles.length === 1
|
||||
? `Profile "${updatedProfiles[0]}" has been updated`
|
||||
: `${updatedProfiles.length} profiles have been updated`;
|
||||
|
||||
showToast({
|
||||
type: "success",
|
||||
title: `${browserDisplayName} update completed`,
|
||||
description: `${profileText} to version ${newVersion}. Running profiles were not updated and can be updated manually.`,
|
||||
duration: 5000,
|
||||
});
|
||||
} else {
|
||||
showToast({
|
||||
type: "success",
|
||||
title: `${browserDisplayName} update ready`,
|
||||
description:
|
||||
"All affected profiles are currently running. Stop them and manually update their versions to use the new version.",
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
} catch (downloadError) {
|
||||
console.error("Failed to download browser:", downloadError);
|
||||
|
||||
// Clean up auto-update tracking on error
|
||||
try {
|
||||
await invoke("remove_auto_update_download", {
|
||||
browser,
|
||||
version: newVersion,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to clean up auto-update tracking:", e);
|
||||
}
|
||||
|
||||
showToast({
|
||||
type: "error",
|
||||
title: `Failed to download ${browserDisplayName} ${newVersion}`,
|
||||
description: String(downloadError),
|
||||
duration: 6000,
|
||||
});
|
||||
throw downloadError;
|
||||
}
|
||||
|
||||
// Refresh notifications to clear any remaining ones
|
||||
await checkForUpdates();
|
||||
} catch (error) {
|
||||
console.error("Failed to start update:", error);
|
||||
const browserDisplayName = getBrowserDisplayName(browser);
|
||||
showToast({
|
||||
type: "error",
|
||||
title: `Failed to update ${browserDisplayName}`,
|
||||
description: String(error),
|
||||
duration: 6000,
|
||||
});
|
||||
} finally {
|
||||
setUpdatingBrowsers((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(browser);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
},
|
||||
[notifications, checkForUpdates]
|
||||
);
|
||||
|
||||
const handleDismiss = useCallback(
|
||||
async (notificationId: string) => {
|
||||
if (!isClient) return; // Only run on client side
|
||||
|
||||
try {
|
||||
toast.dismiss(notificationId);
|
||||
await invoke("dismiss_update_notification", { notificationId });
|
||||
await checkForUpdates();
|
||||
} catch (error) {
|
||||
console.error("Failed to dismiss notification:", error);
|
||||
}
|
||||
},
|
||||
[checkForUpdates, isClient]
|
||||
);
|
||||
|
||||
// Separate effect to show toasts when notifications change
|
||||
useEffect(() => {
|
||||
if (!isClient) return;
|
||||
|
||||
notifications.forEach((notification) => {
|
||||
const isUpdating = updatingBrowsers.has(notification.browser);
|
||||
|
||||
toast.custom(
|
||||
() => (
|
||||
<UpdateNotificationComponent
|
||||
notification={notification}
|
||||
onUpdate={handleUpdate}
|
||||
onDismiss={handleDismiss}
|
||||
isUpdating={isUpdating}
|
||||
/>
|
||||
),
|
||||
{
|
||||
id: notification.id,
|
||||
duration: Number.POSITIVE_INFINITY, // Persistent until user action
|
||||
position: "top-right",
|
||||
// Remove transparent styling to fix background issue
|
||||
style: undefined,
|
||||
}
|
||||
);
|
||||
});
|
||||
}, [notifications, updatingBrowsers, handleUpdate, handleDismiss, isClient]);
|
||||
|
||||
return {
|
||||
notifications,
|
||||
checkForUpdates,
|
||||
isUpdating: (browser: string) => updatingBrowsers.has(browser),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { LuDownload } from "react-icons/lu";
|
||||
import { useState } from "react";
|
||||
import { LuDownload } from "react-icons/lu";
|
||||
import { LuCheck, LuChevronsUpDown } from "react-icons/lu";
|
||||
import { ScrollArea } from "./ui/scroll-area";
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { useVersionUpdater } from "@/hooks/use-version-updater";
|
||||
import {
|
||||
LuRefreshCw,
|
||||
LuClock,
|
||||
LuCheckCheck,
|
||||
LuCircleAlert,
|
||||
LuClock,
|
||||
LuRefreshCw,
|
||||
} from "react-icons/lu";
|
||||
|
||||
export function VersionUpdateSettings() {
|
||||
|
||||
Reference in New Issue
Block a user