mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-13 10:17:54 +02:00
317 lines
7.5 KiB
TypeScript
317 lines
7.5 KiB
TypeScript
import { invoke } from "@tauri-apps/api/core";
|
|
import React from "react";
|
|
import { type ExternalToast, toast as sonnerToast } from "sonner";
|
|
import { UnifiedToast } from "@/components/custom-toast";
|
|
|
|
interface BaseToastProps {
|
|
id?: string;
|
|
title: string;
|
|
description?: string;
|
|
duration?: number;
|
|
action?: ExternalToast["action"];
|
|
onCancel?: () => void;
|
|
// When false, the toast cannot be dismissed by the user (no swipe; combine
|
|
// with duration: Infinity and no onCancel to make it fully non-closable).
|
|
dismissible?: boolean;
|
|
}
|
|
|
|
interface LoadingToastProps extends BaseToastProps {
|
|
type: "loading";
|
|
}
|
|
|
|
interface SuccessToastProps extends BaseToastProps {
|
|
type: "success";
|
|
}
|
|
|
|
interface ErrorToastProps extends BaseToastProps {
|
|
type: "error";
|
|
}
|
|
|
|
interface DownloadToastProps extends BaseToastProps {
|
|
type: "download";
|
|
stage?:
|
|
| "downloading"
|
|
| "extracting"
|
|
| "verifying"
|
|
| "completed"
|
|
| "downloading (twilight rolling release)";
|
|
progress?: {
|
|
percentage: number;
|
|
speed?: string;
|
|
eta?: string;
|
|
};
|
|
}
|
|
|
|
interface VersionUpdateToastProps extends BaseToastProps {
|
|
type: "version-update";
|
|
progress?: {
|
|
current: number;
|
|
total: number;
|
|
found: number;
|
|
current_browser?: string;
|
|
};
|
|
}
|
|
|
|
interface SyncProgressToastProps extends BaseToastProps {
|
|
type: "sync-progress";
|
|
progress?: {
|
|
completed_files: number;
|
|
total_files: number;
|
|
completed_bytes: number;
|
|
total_bytes: number;
|
|
speed_bytes_per_sec: number;
|
|
eta_seconds: number;
|
|
failed_count: number;
|
|
phase: string;
|
|
};
|
|
}
|
|
|
|
type ToastProps =
|
|
| SuccessToastProps
|
|
| ErrorToastProps
|
|
| DownloadToastProps
|
|
| LoadingToastProps
|
|
| VersionUpdateToastProps
|
|
| SyncProgressToastProps;
|
|
|
|
export function showToast(props: ToastProps & { id?: string }) {
|
|
const toastId = props.id ?? `toast-${props.type}-${Date.now()}`;
|
|
|
|
let duration: number;
|
|
if (props.duration !== undefined) {
|
|
duration = props.duration;
|
|
} else {
|
|
switch (props.type) {
|
|
case "loading":
|
|
duration = 10000;
|
|
break;
|
|
case "download":
|
|
if ("stage" in props && props.stage === "completed") {
|
|
duration = 3000;
|
|
} else {
|
|
duration = Number.POSITIVE_INFINITY;
|
|
}
|
|
break;
|
|
case "success":
|
|
duration = 3000;
|
|
break;
|
|
case "error":
|
|
duration = 10000;
|
|
break;
|
|
case "version-update":
|
|
duration = 15000;
|
|
break;
|
|
case "sync-progress":
|
|
duration = Number.POSITIVE_INFINITY;
|
|
break;
|
|
default:
|
|
duration = 5000;
|
|
}
|
|
}
|
|
|
|
if (props.type === "success") {
|
|
sonnerToast.custom(() => React.createElement(UnifiedToast, props), {
|
|
id: toastId,
|
|
duration,
|
|
dismissible: props.dismissible,
|
|
style: {
|
|
background: "transparent",
|
|
border: "none",
|
|
boxShadow: "none",
|
|
padding: 0,
|
|
zIndex: 10001,
|
|
pointerEvents: "auto",
|
|
},
|
|
});
|
|
} else if (props.type === "error") {
|
|
sonnerToast.custom(() => React.createElement(UnifiedToast, props), {
|
|
id: toastId,
|
|
duration,
|
|
dismissible: props.dismissible,
|
|
style: {
|
|
background: "transparent",
|
|
border: "none",
|
|
boxShadow: "none",
|
|
padding: 0,
|
|
zIndex: 10001,
|
|
pointerEvents: "auto",
|
|
},
|
|
});
|
|
} else {
|
|
sonnerToast.custom(() => React.createElement(UnifiedToast, props), {
|
|
id: toastId,
|
|
duration,
|
|
dismissible: props.dismissible,
|
|
style: {
|
|
background: "transparent",
|
|
border: "none",
|
|
boxShadow: "none",
|
|
padding: 0,
|
|
zIndex: 10001,
|
|
pointerEvents: "auto",
|
|
},
|
|
});
|
|
}
|
|
|
|
return toastId;
|
|
}
|
|
|
|
export function showDownloadToast(
|
|
browserName: string,
|
|
version: string,
|
|
stage:
|
|
| "downloading"
|
|
| "extracting"
|
|
| "verifying"
|
|
| "completed"
|
|
| "downloading (twilight rolling release)",
|
|
progress?: { percentage: number; speed?: string; eta?: string },
|
|
options?: { suppressCompletionToast?: boolean; onCancel?: () => void },
|
|
) {
|
|
const title =
|
|
stage === "completed"
|
|
? `${browserName} ${version} downloaded successfully!`
|
|
: stage === "downloading"
|
|
? `Downloading ${browserName} ${version}`
|
|
: stage === "extracting"
|
|
? `Extracting ${browserName} ${version}`
|
|
: stage === "downloading (twilight rolling release)"
|
|
? `Downloading ${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;
|
|
}
|
|
|
|
// Only show cancel button during active downloading, not for completed/extracting/verifying
|
|
const showCancel =
|
|
stage === "downloading" ||
|
|
stage === "downloading (twilight rolling release)";
|
|
|
|
return showToast({
|
|
type: "download",
|
|
title,
|
|
stage,
|
|
progress,
|
|
id: `download-${browserName.toLowerCase()}-${version}`,
|
|
onCancel: showCancel ? options?.onCancel : undefined,
|
|
});
|
|
}
|
|
|
|
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,
|
|
});
|
|
}
|
|
|
|
export function showAutoUpdateToast(
|
|
browserName: string,
|
|
version: string,
|
|
options?: {
|
|
id?: string;
|
|
description?: string;
|
|
duration?: number;
|
|
},
|
|
) {
|
|
return showToast({
|
|
type: "loading",
|
|
title: `${browserName} update started`,
|
|
description:
|
|
options?.description ??
|
|
`Automatically downloading ${browserName} ${version}. Progress will be shown in download notifications.`,
|
|
id: options?.id ?? `auto-update-${browserName.toLowerCase()}-${version}`,
|
|
duration: options?.duration ?? 4000,
|
|
});
|
|
}
|
|
|
|
export function dismissToast(id: string) {
|
|
sonnerToast.dismiss(id);
|
|
}
|
|
|
|
export function showSyncProgressToast(
|
|
profileName: string,
|
|
progress: {
|
|
completed_files: number;
|
|
total_files: number;
|
|
completed_bytes: number;
|
|
total_bytes: number;
|
|
speed_bytes_per_sec: number;
|
|
eta_seconds: number;
|
|
failed_count: number;
|
|
phase: string;
|
|
},
|
|
options?: { id?: string; profileId?: string },
|
|
) {
|
|
return showToast({
|
|
type: "sync-progress",
|
|
title: `Syncing profile '${profileName}'...`,
|
|
progress,
|
|
id: options?.id,
|
|
duration: Number.POSITIVE_INFINITY,
|
|
onCancel: () => {
|
|
if (options?.profileId) {
|
|
// Fire-and-forget — backend flips the cancel flag for the in-flight
|
|
// upload/download loops to drain.
|
|
void invoke("cancel_profile_sync", {
|
|
profileId: options.profileId,
|
|
}).catch((err: unknown) => {
|
|
console.error("Failed to cancel sync:", err);
|
|
});
|
|
}
|
|
if (options?.id) {
|
|
dismissToast(options.id);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
export function showUnifiedVersionUpdateToast(
|
|
title: string,
|
|
options?: {
|
|
id?: string;
|
|
description?: string;
|
|
progress?: {
|
|
current: number;
|
|
total: number;
|
|
found: number;
|
|
current_browser?: string;
|
|
};
|
|
duration?: number;
|
|
onCancel?: () => void;
|
|
},
|
|
) {
|
|
return showToast({
|
|
type: "version-update",
|
|
title,
|
|
id: "unified-version-update",
|
|
duration: Number.POSITIVE_INFINITY, // Keep showing until completed
|
|
...options,
|
|
});
|
|
}
|