mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-05 14:48:01 +02:00
feat: confirm minimize-to-tray or quit when closing the window
Intercept the main window CloseRequested event so the user can choose between minimizing the app to the system tray and quitting, instead of the close button immediately tearing the process down. - Add an on_window_event handler that prevents close, emits close-confirm-requested, and lets the next CloseRequested through once confirm_quit flips a QUIT_CONFIRMED flag. - Add a TrayIconBuilder in the main process with Show / Quit menu items and a left-click handler that restores the window. Tray icon is decoded via the image crate so the donut glyph renders on every platform. - Add hide_to_tray command used by the dialog's Minimize action. - New CloseConfirmDialog React component mounted in app/page.tsx. - Enable Tauri features tray-icon and image-png. - Add closeConfirm strings across all eight locale files. The existing standalone donut-daemon tray binary is left untouched.
This commit is contained in:
@@ -8,6 +8,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { AccountPage } from "@/components/account-page";
|
||||
import { CamoufoxConfigDialog } from "@/components/camoufox-config-dialog";
|
||||
import { CloneProfileDialog } from "@/components/clone-profile-dialog";
|
||||
import { CloseConfirmDialog } from "@/components/close-confirm-dialog";
|
||||
import { CommandPalette } from "@/components/command-palette";
|
||||
import { CommercialTrialModal } from "@/components/commercial-trial-modal";
|
||||
import { CookieCopyDialog } from "@/components/cookie-copy-dialog";
|
||||
@@ -1431,6 +1432,7 @@ export default function Home() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-background font-(family-name:--font-geist-sans)">
|
||||
<CloseConfirmDialog />
|
||||
<HomeHeader
|
||||
onCreateProfileDialogOpen={setCreateProfileDialogOpen}
|
||||
searchQuery={searchQuery}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { RippleButton } from "./ui/ripple";
|
||||
|
||||
export function CloseConfirmDialog() {
|
||||
const { t } = useTranslation();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const unlistenPromise = listen("close-confirm-requested", () => {
|
||||
setIsOpen(true);
|
||||
});
|
||||
return () => {
|
||||
void unlistenPromise.then((u) => {
|
||||
u();
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleMinimize = async () => {
|
||||
setIsOpen(false);
|
||||
try {
|
||||
await invoke("hide_to_tray");
|
||||
} catch (error) {
|
||||
console.error("Failed to hide to tray:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuit = async () => {
|
||||
setIsOpen(false);
|
||||
try {
|
||||
await invoke("confirm_quit");
|
||||
} catch (error) {
|
||||
console.error("Failed to quit app:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("closeConfirm.title")}</DialogTitle>
|
||||
<DialogDescription>{t("closeConfirm.description")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<RippleButton
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
void handleMinimize();
|
||||
}}
|
||||
>
|
||||
{t("closeConfirm.minimize")}
|
||||
</RippleButton>
|
||||
<RippleButton
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
void handleQuit();
|
||||
}}
|
||||
>
|
||||
{t("closeConfirm.quit")}
|
||||
</RippleButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1912,5 +1912,11 @@
|
||||
"goIntegrations": "Go to Integrations",
|
||||
"goAccount": "Go to Account",
|
||||
"goSettings": "Go to Settings"
|
||||
},
|
||||
"closeConfirm": {
|
||||
"title": "Close Donut Browser?",
|
||||
"description": "Would you like to send the app to the system tray or quit?",
|
||||
"minimize": "Minimize to Tray",
|
||||
"quit": "Quit"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1912,5 +1912,11 @@
|
||||
"goIntegrations": "Ir a Integraciones",
|
||||
"goAccount": "Ir a Cuenta",
|
||||
"goSettings": "Ir a Configuración"
|
||||
},
|
||||
"closeConfirm": {
|
||||
"title": "¿Cerrar Donut Browser?",
|
||||
"description": "¿Quieres enviar la aplicación a la bandeja del sistema o salir?",
|
||||
"minimize": "Minimizar a la bandeja",
|
||||
"quit": "Salir"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1912,5 +1912,11 @@
|
||||
"goIntegrations": "Aller à Intégrations",
|
||||
"goAccount": "Aller à Compte",
|
||||
"goSettings": "Aller à Paramètres"
|
||||
},
|
||||
"closeConfirm": {
|
||||
"title": "Fermer Donut Browser ?",
|
||||
"description": "Voulez-vous envoyer l'application dans la zone de notification ou quitter ?",
|
||||
"minimize": "Réduire dans la barre d'état",
|
||||
"quit": "Quitter"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1912,5 +1912,11 @@
|
||||
"goIntegrations": "統合へ移動",
|
||||
"goAccount": "アカウントへ移動",
|
||||
"goSettings": "設定へ移動"
|
||||
},
|
||||
"closeConfirm": {
|
||||
"title": "Donut Browser を閉じますか?",
|
||||
"description": "アプリをシステムトレイに格納しますか、それとも終了しますか?",
|
||||
"minimize": "トレイに格納",
|
||||
"quit": "終了"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1912,5 +1912,11 @@
|
||||
"goIntegrations": "통합으로 이동",
|
||||
"goAccount": "계정으로 이동",
|
||||
"goSettings": "설정으로 이동"
|
||||
},
|
||||
"closeConfirm": {
|
||||
"title": "Donut Browser를 닫으시겠습니까?",
|
||||
"description": "앱을 시스템 트레이로 보내시겠습니까, 아니면 종료하시겠습니까?",
|
||||
"minimize": "트레이로 최소화",
|
||||
"quit": "종료"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1912,5 +1912,11 @@
|
||||
"goIntegrations": "Ir para Integrações",
|
||||
"goAccount": "Ir para Conta",
|
||||
"goSettings": "Ir para Configurações"
|
||||
},
|
||||
"closeConfirm": {
|
||||
"title": "Fechar Donut Browser?",
|
||||
"description": "Você deseja enviar o aplicativo para a bandeja do sistema ou sair?",
|
||||
"minimize": "Minimizar para a bandeja",
|
||||
"quit": "Sair"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1912,5 +1912,11 @@
|
||||
"goIntegrations": "Перейти к Интеграциям",
|
||||
"goAccount": "Перейти к Аккаунту",
|
||||
"goSettings": "Перейти к Настройкам"
|
||||
},
|
||||
"closeConfirm": {
|
||||
"title": "Закрыть Donut Browser?",
|
||||
"description": "Свернуть приложение в системный трей или выйти?",
|
||||
"minimize": "Свернуть в трей",
|
||||
"quit": "Выйти"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1912,5 +1912,11 @@
|
||||
"goIntegrations": "转到集成",
|
||||
"goAccount": "转到账户",
|
||||
"goSettings": "转到设置"
|
||||
},
|
||||
"closeConfirm": {
|
||||
"title": "关闭 Donut Browser?",
|
||||
"description": "您想将应用最小化到系统托盘还是退出?",
|
||||
"minimize": "最小化到托盘",
|
||||
"quit": "退出"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user