feat(desktop): add close behavior prompt with quit-on-close setting
Co-authored-by: cherlin <544026227@qq.com>
This commit is contained in:
parent
f32298743d
commit
28a23de604
|
|
@ -26,6 +26,7 @@ import { useDialogSources } from "@/composables/useDialogSources";
|
|||
import { useNavigationTargets } from "@/composables/useNavigationTargets";
|
||||
import { useDataGridActions } from "@/composables/useDataGridActions";
|
||||
import { useTauriEvents } from "@/composables/useTauriEvents";
|
||||
import { useCloseActionPrompt } from "@/composables/useCloseActionPrompt";
|
||||
import { useVisibilityChange } from "@/composables/useVisibilityChange";
|
||||
import "@/i18n";
|
||||
import { translateBackendError } from "@/i18n/backend-errors";
|
||||
|
|
@ -80,6 +81,7 @@ const QueryHistory = defineAsyncComponent(() => import("@/components/editor/Quer
|
|||
const SqlLibraryPanel = defineAsyncComponent(() => import("@/components/layout/SqlLibraryPanel.vue"));
|
||||
const DriverStorePage = defineAsyncComponent(() => import("@/components/config/DriverStoreDialog.vue"));
|
||||
const UpdateDialog = defineAsyncComponent(() => import("@/components/layout/UpdateDialog.vue"));
|
||||
const CloseActionPromptDialog = defineAsyncComponent(() => import("@/components/layout/CloseActionPromptDialog.vue"));
|
||||
const LoginPage = defineAsyncComponent(() => import("@/components/auth/LoginPage.vue"));
|
||||
const QuickOpenDialog = defineAsyncComponent(() => import("@/components/quick-open/QuickOpenDialog.vue"));
|
||||
|
||||
|
|
@ -220,6 +222,7 @@ const { setupTauriListeners, cleanupTauriListeners } = useTauriEvents({
|
|||
openDbFilePath,
|
||||
openConnectionDeepLink,
|
||||
});
|
||||
const { showCloseActionPrompt, chooseQuit, chooseMinimize, setupCloseActionPromptListener, cleanupCloseActionPromptListener } = useCloseActionPrompt();
|
||||
useVisibilityChange();
|
||||
|
||||
const appVersion = ref("");
|
||||
|
|
@ -1310,6 +1313,7 @@ onMounted(async () => {
|
|||
})
|
||||
.catch(() => {});
|
||||
setupTauriListeners();
|
||||
setupCloseActionPromptListener();
|
||||
void openPendingSqlFiles();
|
||||
void openPendingDbFiles();
|
||||
void openPendingConnectionLinks();
|
||||
|
|
@ -1318,6 +1322,7 @@ onMounted(async () => {
|
|||
|
||||
onUnmounted(() => {
|
||||
cleanupTauriListeners();
|
||||
cleanupCloseActionPromptListener();
|
||||
if (updateCheckTimer) {
|
||||
clearInterval(updateCheckTimer);
|
||||
}
|
||||
|
|
@ -1538,6 +1543,12 @@ onUnmounted(() => {
|
|||
@download-and-install="downloadAndInstallUpdate"
|
||||
@restart="restartApp"
|
||||
/>
|
||||
<CloseActionPromptDialog
|
||||
v-if="isDesktop"
|
||||
v-model:open="showCloseActionPrompt"
|
||||
@quit="chooseQuit"
|
||||
@minimize="chooseMinimize"
|
||||
/>
|
||||
<QuickOpenDialog :open="showQuickOpen" @update:open="showQuickOpen = $event" @select="handleQuickOpenSelect" />
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ const editWordWrap = ref(settingsStore.editorSettings.wordWrap);
|
|||
const editConfirmDangerousSqlExecution = ref(settingsStore.editorSettings.confirmDangerousSqlExecution);
|
||||
const editAppLayout = ref(settingsStore.editorSettings.appLayout);
|
||||
const editShowTrayIcon = ref(settingsStore.desktopSettings.show_tray_icon);
|
||||
const editQuitOnClose = ref(settingsStore.desktopSettings.quit_on_close);
|
||||
const desktopCloseBehaviorResetPending = ref(false);
|
||||
const editIconTheme = ref<DesktopIconTheme>(settingsStore.desktopSettings.icon_theme);
|
||||
const editDebugLoggingEnabled = ref(settingsStore.desktopSettings.debug_logging_enabled);
|
||||
const editSidebarTablePageSize = ref(settingsStore.desktopSettings.sidebar_table_page_size ?? DEFAULT_SIDEBAR_TABLE_PAGE_SIZE);
|
||||
|
|
@ -365,6 +367,7 @@ watch(
|
|||
editConfirmDangerousSqlExecution.value = settingsStore.editorSettings.confirmDangerousSqlExecution;
|
||||
editAppLayout.value = settingsStore.editorSettings.appLayout;
|
||||
editShowTrayIcon.value = settingsStore.desktopSettings.show_tray_icon;
|
||||
editQuitOnClose.value = settingsStore.desktopSettings.quit_on_close;
|
||||
editIconTheme.value = settingsStore.desktopSettings.icon_theme;
|
||||
editDebugLoggingEnabled.value = settingsStore.desktopSettings.debug_logging_enabled;
|
||||
editSidebarTablePageSize.value = settingsStore.desktopSettings.sidebar_table_page_size ?? DEFAULT_SIDEBAR_TABLE_PAGE_SIZE;
|
||||
|
|
@ -421,6 +424,7 @@ function hasChanges(): boolean {
|
|||
editConfirmDangerousSqlExecution.value !== settingsStore.editorSettings.confirmDangerousSqlExecution ||
|
||||
editAppLayout.value !== settingsStore.editorSettings.appLayout ||
|
||||
editShowTrayIcon.value !== settingsStore.desktopSettings.show_tray_icon ||
|
||||
editQuitOnClose.value !== settingsStore.desktopSettings.quit_on_close ||
|
||||
editIconTheme.value !== settingsStore.desktopSettings.icon_theme ||
|
||||
editDebugLoggingEnabled.value !== settingsStore.desktopSettings.debug_logging_enabled ||
|
||||
editSidebarTablePageSize.value !== (settingsStore.desktopSettings.sidebar_table_page_size ?? DEFAULT_SIDEBAR_TABLE_PAGE_SIZE) ||
|
||||
|
|
@ -484,10 +488,13 @@ async function persistSettings() {
|
|||
});
|
||||
await settingsStore.updateDesktopSettings({
|
||||
show_tray_icon: editShowTrayIcon.value,
|
||||
quit_on_close: editQuitOnClose.value,
|
||||
close_action_prompted: desktopCloseBehaviorResetPending.value ? false : true,
|
||||
icon_theme: editIconTheme.value,
|
||||
debug_logging_enabled: editDebugLoggingEnabled.value,
|
||||
sidebar_table_page_size: editSidebarTablePageSize.value,
|
||||
});
|
||||
desktopCloseBehaviorResetPending.value = false;
|
||||
if (sidebarObjectDisplayChanged) {
|
||||
await connectionStore.refreshAllTree();
|
||||
}
|
||||
|
|
@ -514,6 +521,8 @@ function resetDefaults() {
|
|||
editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution;
|
||||
editAppLayout.value = DEFAULT_EDITOR_SETTINGS.appLayout;
|
||||
editShowTrayIcon.value = DEFAULT_DESKTOP_SETTINGS.show_tray_icon;
|
||||
editQuitOnClose.value = DEFAULT_DESKTOP_SETTINGS.quit_on_close;
|
||||
desktopCloseBehaviorResetPending.value = true;
|
||||
editIconTheme.value = DEFAULT_DESKTOP_SETTINGS.icon_theme;
|
||||
editDebugLoggingEnabled.value = DEFAULT_DESKTOP_SETTINGS.debug_logging_enabled;
|
||||
editSidebarTablePageSize.value = DEFAULT_SIDEBAR_TABLE_PAGE_SIZE;
|
||||
|
|
@ -1057,6 +1066,7 @@ watch(
|
|||
await settingsStore.initAiConfig();
|
||||
await settingsStore.initDesktopSettings();
|
||||
editShowTrayIcon.value = settingsStore.desktopSettings.show_tray_icon;
|
||||
editQuitOnClose.value = settingsStore.desktopSettings.quit_on_close;
|
||||
editIconTheme.value = settingsStore.desktopSettings.icon_theme;
|
||||
editDebugLoggingEnabled.value = settingsStore.desktopSettings.debug_logging_enabled;
|
||||
editSidebarTablePageSize.value = settingsStore.desktopSettings.sidebar_table_page_size ?? DEFAULT_SIDEBAR_TABLE_PAGE_SIZE;
|
||||
|
|
@ -1872,6 +1882,14 @@ watch(
|
|||
<Switch id="show-tray-icon" v-model="editShowTrayIcon" />
|
||||
</div>
|
||||
|
||||
<div v-if="!isWeb" class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="quit-on-close">{{ t("settings.quitOnClose") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t("settings.quitOnCloseDescription") }}</p>
|
||||
</div>
|
||||
<Switch id="quit-on-close" v-model="editQuitOnClose" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="update-notifications-enabled">{{ t("settings.updateNotificationsEnabled") }}</Label>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
|
||||
const open = defineModel<boolean>("open", { required: true });
|
||||
|
||||
const emit = defineEmits<{
|
||||
quit: [];
|
||||
minimize: [];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogContent class="sm:max-w-[440px]" @interact-outside.prevent @escape-key-down.prevent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("settings.closeActionPromptTitle") }}</DialogTitle>
|
||||
<DialogDescription>{{ t("settings.closeActionPromptDescription") }}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter class="gap-2 sm:gap-2">
|
||||
<Button type="button" variant="outline" @click="emit('minimize')">
|
||||
{{ t("settings.closeActionMinimize") }}
|
||||
</Button>
|
||||
<Button type="button" @click="emit('quit')">
|
||||
{{ t("settings.closeActionQuit") }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { ref } from "vue";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
|
||||
export function useCloseActionPrompt() {
|
||||
const settingsStore = useSettingsStore();
|
||||
const showCloseActionPrompt = ref(false);
|
||||
const unlistenHandles: Array<() => void> = [];
|
||||
|
||||
async function applyCloseChoice(quitOnClose: boolean) {
|
||||
showCloseActionPrompt.value = false;
|
||||
await settingsStore.updateDesktopSettings({
|
||||
quit_on_close: quitOnClose,
|
||||
close_action_prompted: true,
|
||||
});
|
||||
if (!isTauriRuntime()) return;
|
||||
if (quitOnClose) {
|
||||
const { exit } = await import("@tauri-apps/plugin-process");
|
||||
await exit(0);
|
||||
return;
|
||||
}
|
||||
const { getCurrentWindow } = await import("@tauri-apps/api/window");
|
||||
await getCurrentWindow().hide();
|
||||
}
|
||||
|
||||
function chooseQuit() {
|
||||
void applyCloseChoice(true);
|
||||
}
|
||||
|
||||
function chooseMinimize() {
|
||||
void applyCloseChoice(false);
|
||||
}
|
||||
|
||||
function setupCloseActionPromptListener() {
|
||||
if (!isTauriRuntime()) return;
|
||||
void import("@tauri-apps/api/event").then(({ listen }) => {
|
||||
listen("dbx-close-action-prompt", () => {
|
||||
showCloseActionPrompt.value = true;
|
||||
}).then((unlisten) => unlistenHandles.push(unlisten));
|
||||
});
|
||||
}
|
||||
|
||||
function cleanupCloseActionPromptListener() {
|
||||
unlistenHandles.forEach((unlisten) => unlisten());
|
||||
unlistenHandles.length = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
showCloseActionPrompt,
|
||||
chooseQuit,
|
||||
chooseMinimize,
|
||||
setupCloseActionPromptListener,
|
||||
cleanupCloseActionPromptListener,
|
||||
};
|
||||
}
|
||||
|
|
@ -2292,6 +2292,12 @@ export default {
|
|||
iconThemeBlackDescription: "Use the black DBX mark for the window, tray, and logo.",
|
||||
showTrayIcon: "Show tray/menu bar icon",
|
||||
showTrayIconDescription: "When disabled, no icon is shown, but closing the window still hides DBX in the background as before.",
|
||||
quitOnClose: "Quit when closing window",
|
||||
quitOnCloseDescription: "When enabled, clicking the close button exits DBX completely instead of hiding it to the tray. You can change this later in Appearance settings.",
|
||||
closeActionPromptTitle: "Close window",
|
||||
closeActionPromptDescription: "Choose what happens when you click the close button. You can change this later in Settings > Appearance.",
|
||||
closeActionQuit: "Quit DBX",
|
||||
closeActionMinimize: "Minimize to tray",
|
||||
updateNotificationsEnabled: "Enable update reminders",
|
||||
updateNotificationsEnabledDescription: "When disabled, DBX will not automatically check app or driver updates or show update badges. Manual checks are still available.",
|
||||
debugLoggingEnabled: "Enable debug logs",
|
||||
|
|
|
|||
|
|
@ -1954,6 +1954,12 @@ export default {
|
|||
iconThemeBlackDescription: "Usar la marca negra de DBX en ventana, bandeja y logo.",
|
||||
showTrayIcon: "Mostrar icono en bandeja/barra de menú",
|
||||
showTrayIconDescription: "Si está desactivado, no se muestra el icono, pero cerrar la ventana sigue ocultando DBX en segundo plano como antes.",
|
||||
quitOnClose: "Salir al cerrar la ventana",
|
||||
quitOnCloseDescription: "Si está activado, al pulsar el botón de cerrar DBX se cierra por completo en lugar de ocultarse en la bandeja.",
|
||||
closeActionPromptTitle: "Cerrar ventana",
|
||||
closeActionPromptDescription: "Elige qué ocurre al pulsar el botón de cerrar. Puedes cambiarlo después en Ajustes → Apariencia.",
|
||||
closeActionQuit: "Salir de DBX",
|
||||
closeActionMinimize: "Minimizar a la bandeja",
|
||||
updateNotificationsEnabled: "Activar recordatorios de actualización",
|
||||
updateNotificationsEnabledDescription: "Al desactivarlo, DBX no comprobará automáticamente actualizaciones de la app ni de los controladores, ni mostrará indicadores. Las comprobaciones manuales siguen disponibles.",
|
||||
debugLoggingEnabled: "Activar logs de depuración",
|
||||
|
|
|
|||
|
|
@ -2027,6 +2027,12 @@ export default {
|
|||
iconThemeBlackDescription: "Utilizza il marchio DBX nero per la finestra, la barra delle applicazioni e il logo.",
|
||||
showTrayIcon: "Mostra icona nella barra delle applicazioni/menu",
|
||||
showTrayIconDescription: "Se disattivato, non viene mostrata alcuna icona, ma la chiusura della finestra nasconderà comunque DBX in background.",
|
||||
quitOnClose: "Esci alla chiusura della finestra",
|
||||
quitOnCloseDescription: "Se attivato, il pulsante di chiusura termina completamente DBX invece di nasconderlo nella barra delle applicazioni.",
|
||||
closeActionPromptTitle: "Chiudi finestra",
|
||||
closeActionPromptDescription: "Scegli cosa succede quando clicchi il pulsante di chiusura. Puoi modificarlo in seguito in Impostazioni → Aspetto.",
|
||||
closeActionQuit: "Esci da DBX",
|
||||
closeActionMinimize: "Riduci a icona",
|
||||
updateNotificationsEnabled: "Abilita promemoria aggiornamenti",
|
||||
updateNotificationsEnabledDescription: "Se disattivato, DBX non controllerà automaticamente gli aggiornamenti dell'app o dei driver e non mostrerà badge. I controlli manuali restano disponibili.",
|
||||
debugLoggingEnabled: "Abilita log di debug",
|
||||
|
|
|
|||
|
|
@ -2250,6 +2250,12 @@ export default {
|
|||
iconThemeBlackDescription: "ウィンドウ、トレイ、ロゴに黒のDBXマークを使用。",
|
||||
showTrayIcon: "トレイ/メニューバーアイコンを表示",
|
||||
showTrayIconDescription: "無効時はアイコンが表示されませんが、ウィンドウを閉じると従来通りDBXはバックグラウンドに隠れます。",
|
||||
quitOnClose: "ウィンドウを閉じるときに終了",
|
||||
quitOnCloseDescription: "有効にすると、閉じるボタンでトレイに隠すのではなく、DBXを完全に終了します。",
|
||||
closeActionPromptTitle: "ウィンドウを閉じる",
|
||||
closeActionPromptDescription: "閉じるボタンの動作を選択してください。後から「設定 → 外観」で変更できます。",
|
||||
closeActionQuit: "DBXを終了",
|
||||
closeActionMinimize: "トレイに最小化",
|
||||
updateNotificationsEnabled: "アップデート通知を有効にする",
|
||||
updateNotificationsEnabledDescription: "無効時、DBXはアプリやドライバーのアップデートを自動確認せず、アップデートバッジも表示しません。手動確認は引き続き利用可能です。",
|
||||
debugLoggingEnabled: "デバッグログを有効にする",
|
||||
|
|
|
|||
|
|
@ -2038,6 +2038,12 @@ export default {
|
|||
iconThemeBlackDescription: "Usar a marca DBX em preto na janela, na bandeja e no logo.",
|
||||
showTrayIcon: "Mostrar ícone na bandeja/barra de menus",
|
||||
showTrayIconDescription: "Quando desativado, nenhum ícone é exibido, mas fechar a janela ainda oculta o DBX em segundo plano como antes.",
|
||||
quitOnClose: "Sair ao fechar a janela",
|
||||
quitOnCloseDescription: "Quando ativado, o botão de fechar encerra o DBX completamente em vez de minimizar para a bandeja.",
|
||||
closeActionPromptTitle: "Fechar janela",
|
||||
closeActionPromptDescription: "Escolha o que acontece ao clicar no botão de fechar. Você pode alterar depois em Configurações → Aparência.",
|
||||
closeActionQuit: "Sair do DBX",
|
||||
closeActionMinimize: "Minimizar para a bandeja",
|
||||
updateNotificationsEnabled: "Ativar lembretes de atualização",
|
||||
updateNotificationsEnabledDescription: "Quando desativado, o DBX não verificará automaticamente atualizações do app ou dos drivers nem mostrará indicadores. Verificações manuais continuam disponíveis.",
|
||||
debugLoggingEnabled: "Ativar logs de depuração",
|
||||
|
|
|
|||
|
|
@ -2316,6 +2316,12 @@ export default {
|
|||
iconThemeBlackDescription: "窗口、托盘和 Logo 都使用黑色 DBX 标识。",
|
||||
showTrayIcon: "显示系统托盘/菜单栏图标",
|
||||
showTrayIconDescription: "关闭后不显示图标,但关闭窗口仍会像之前一样隐藏到后台。",
|
||||
quitOnClose: "关闭窗口时退出程序",
|
||||
quitOnCloseDescription: "开启后,点击窗口关闭按钮将彻底退出 DBX,而不是隐藏到系统托盘。",
|
||||
closeActionPromptTitle: "关闭窗口",
|
||||
closeActionPromptDescription: "请选择点击关闭按钮时的行为。之后可在「设置 → 外观」中修改。",
|
||||
closeActionQuit: "退出程序",
|
||||
closeActionMinimize: "最小化到托盘",
|
||||
updateNotificationsEnabled: "启用更新提醒",
|
||||
updateNotificationsEnabledDescription: "关闭后,DBX 不会自动检查应用和驱动更新,也不会显示更新红点;仍可手动检查更新。",
|
||||
debugLoggingEnabled: "启用调试日志",
|
||||
|
|
|
|||
|
|
@ -2074,6 +2074,12 @@ export default {
|
|||
iconThemeBlackDescription: "視窗、系統匣和 Logo 都使用黑色 DBX 標誌。",
|
||||
showTrayIcon: "顯示系統匣/選單欄圖示",
|
||||
showTrayIconDescription: "關閉後不顯示圖示,但關閉視窗仍會像之前一樣隱藏到後台。",
|
||||
quitOnClose: "關閉視窗時退出程式",
|
||||
quitOnCloseDescription: "開啟後,點擊視窗關閉按鈕將徹底退出 DBX,而不是隱藏到系統匣。",
|
||||
closeActionPromptTitle: "關閉視窗",
|
||||
closeActionPromptDescription: "請選擇點擊關閉按鈕時的行為。之後可在「設定 → 外觀」中修改。",
|
||||
closeActionQuit: "退出程式",
|
||||
closeActionMinimize: "最小化到系統匣",
|
||||
updateNotificationsEnabled: "啟用更新提醒",
|
||||
updateNotificationsEnabledDescription: "關閉後,DBX 不會自動檢查應用程式和驅動程式更新,也不會顯示更新紅點;仍可手動檢查更新。",
|
||||
debugLoggingEnabled: "啟用偵錯日誌",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type {
|
||||
import type {
|
||||
ConnectionConfig,
|
||||
DatabaseInfo,
|
||||
LinkedServerInfo,
|
||||
|
|
@ -104,6 +104,8 @@ const DESKTOP_SETTINGS_STORAGE_KEY = "dbx-desktop-settings";
|
|||
const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
||||
show_tray_icon: true,
|
||||
icon_theme: "default",
|
||||
quit_on_close: false,
|
||||
close_action_prompted: false,
|
||||
debug_logging_enabled: false,
|
||||
saved_sql_sync_dir: null,
|
||||
driver_store_dir: null,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import type {
|
||||
ConnectionConfig,
|
||||
|
|
@ -126,6 +126,8 @@ export interface DriverRuntimeSummary {
|
|||
export interface DesktopSettings {
|
||||
show_tray_icon: boolean;
|
||||
icon_theme: "default" | "black";
|
||||
quit_on_close: boolean;
|
||||
close_action_prompted: boolean;
|
||||
debug_logging_enabled: boolean;
|
||||
saved_sql_sync_dir?: string | null;
|
||||
driver_store_dir?: string | null;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ export interface AiTestConnectionResult {
|
|||
export interface DesktopSettings {
|
||||
show_tray_icon: boolean;
|
||||
icon_theme: DesktopIconTheme;
|
||||
quit_on_close: boolean;
|
||||
close_action_prompted: boolean;
|
||||
debug_logging_enabled: boolean;
|
||||
saved_sql_sync_dir?: string | null;
|
||||
driver_store_dir?: string | null;
|
||||
|
|
@ -59,6 +61,8 @@ export const DEFAULT_SIDEBAR_TABLE_PAGE_SIZE = 1000;
|
|||
export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
||||
show_tray_icon: true,
|
||||
icon_theme: "default",
|
||||
quit_on_close: false,
|
||||
close_action_prompted: false,
|
||||
debug_logging_enabled: false,
|
||||
saved_sql_sync_dir: null,
|
||||
driver_store_dir: null,
|
||||
|
|
@ -73,6 +77,8 @@ function normalizeDesktopSettings(settings: Partial<DesktopSettings> | null | un
|
|||
return {
|
||||
show_tray_icon: settings?.show_tray_icon ?? DEFAULT_DESKTOP_SETTINGS.show_tray_icon,
|
||||
icon_theme: iconTheme,
|
||||
quit_on_close: settings?.quit_on_close ?? DEFAULT_DESKTOP_SETTINGS.quit_on_close,
|
||||
close_action_prompted: settings?.close_action_prompted ?? DEFAULT_DESKTOP_SETTINGS.close_action_prompted,
|
||||
debug_logging_enabled: settings?.debug_logging_enabled ?? DEFAULT_DESKTOP_SETTINGS.debug_logging_enabled,
|
||||
saved_sql_sync_dir: settings?.saved_sql_sync_dir?.trim() || DEFAULT_DESKTOP_SETTINGS.saved_sql_sync_dir,
|
||||
driver_store_dir: settings?.driver_store_dir?.trim() || DEFAULT_DESKTOP_SETTINGS.driver_store_dir,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ pub struct DesktopSettings {
|
|||
pub show_tray_icon: bool,
|
||||
pub icon_theme: DesktopIconTheme,
|
||||
#[serde(default)]
|
||||
pub quit_on_close: bool,
|
||||
#[serde(default)]
|
||||
pub close_action_prompted: bool,
|
||||
#[serde(default)]
|
||||
pub debug_logging_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub saved_sql_sync_dir: Option<String>,
|
||||
|
|
@ -59,6 +63,8 @@ impl Default for DesktopSettings {
|
|||
Self {
|
||||
show_tray_icon: true,
|
||||
icon_theme: DesktopIconTheme::Default,
|
||||
quit_on_close: false,
|
||||
close_action_prompted: false,
|
||||
debug_logging_enabled: false,
|
||||
saved_sql_sync_dir: None,
|
||||
driver_store_dir: None,
|
||||
|
|
@ -557,6 +563,11 @@ impl Storage {
|
|||
"icon_theme".to_string(),
|
||||
serde_json::to_value(desktop_settings.icon_theme).map_err(|e| e.to_string())?,
|
||||
);
|
||||
settings.insert("quit_on_close".to_string(), serde_json::Value::Bool(desktop_settings.quit_on_close));
|
||||
settings.insert(
|
||||
"close_action_prompted".to_string(),
|
||||
serde_json::Value::Bool(desktop_settings.close_action_prompted),
|
||||
);
|
||||
settings.insert(
|
||||
"debug_logging_enabled".to_string(),
|
||||
serde_json::Value::Bool(desktop_settings.debug_logging_enabled),
|
||||
|
|
@ -609,6 +620,14 @@ impl Storage {
|
|||
.or_else(|| settings.get("run_in_background").and_then(|value| value.as_bool()))
|
||||
.unwrap_or_else(|| DesktopSettings::default().show_tray_icon),
|
||||
icon_theme: DesktopIconTheme::from_settings_value(settings.get("icon_theme")),
|
||||
quit_on_close: settings
|
||||
.get("quit_on_close")
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or_else(|| DesktopSettings::default().quit_on_close),
|
||||
close_action_prompted: settings
|
||||
.get("close_action_prompted")
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or_else(|| DesktopSettings::default().close_action_prompted),
|
||||
debug_logging_enabled: settings
|
||||
.get("debug_logging_enabled")
|
||||
.and_then(|value| value.as_bool())
|
||||
|
|
@ -2084,6 +2103,8 @@ mod tests {
|
|||
.save_desktop_settings(&DesktopSettings {
|
||||
show_tray_icon: false,
|
||||
icon_theme: DesktopIconTheme::Black,
|
||||
quit_on_close: true,
|
||||
close_action_prompted: false,
|
||||
debug_logging_enabled: true,
|
||||
saved_sql_sync_dir: None,
|
||||
driver_store_dir: Some("/tmp/dbx-drivers".to_string()),
|
||||
|
|
@ -2100,6 +2121,8 @@ mod tests {
|
|||
DesktopSettings {
|
||||
show_tray_icon: false,
|
||||
icon_theme: DesktopIconTheme::Black,
|
||||
quit_on_close: true,
|
||||
close_action_prompted: false,
|
||||
debug_logging_enabled: true,
|
||||
saved_sql_sync_dir: None,
|
||||
driver_store_dir: Some("/tmp/dbx-drivers".to_string()),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ mod window_state_guard;
|
|||
|
||||
use commands::connection::AppState;
|
||||
use dbx_core::storage::{DesktopIconTheme, DesktopSettings, Storage};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tauri::RunEvent;
|
||||
|
|
@ -18,6 +19,33 @@ use tauri::{Emitter, Manager};
|
|||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
|
||||
const DESKTOP_TRAY_ID: &str = "main-tray";
|
||||
|
||||
pub struct CloseBehaviorState {
|
||||
quit_on_close: AtomicBool,
|
||||
prompted: AtomicBool,
|
||||
}
|
||||
|
||||
impl CloseBehaviorState {
|
||||
fn new(settings: &DesktopSettings) -> Self {
|
||||
Self {
|
||||
quit_on_close: AtomicBool::new(settings.quit_on_close),
|
||||
prompted: AtomicBool::new(settings.close_action_prompted),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply(&self, settings: &DesktopSettings) {
|
||||
self.quit_on_close.store(settings.quit_on_close, Ordering::Relaxed);
|
||||
self.prompted.store(settings.close_action_prompted, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn quit_on_close(&self) -> bool {
|
||||
self.quit_on_close.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn prompted(&self) -> bool {
|
||||
self.prompted.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
const MACOS_TRAY_ICON: tauri::image::Image<'_> = tauri::include_image!("icons/tray-macos-template.png");
|
||||
const BLACK_APP_ICON: tauri::image::Image<'_> = tauri::include_image!("icons/icon-black.png");
|
||||
|
|
@ -171,6 +199,9 @@ fn apply_desktop_tray_icon_theme(app: &tauri::AppHandle, icon_theme: DesktopIcon
|
|||
|
||||
pub(crate) fn apply_desktop_settings(app: &tauri::AppHandle, desktop_settings: &DesktopSettings) -> tauri::Result<()> {
|
||||
apply_debug_log_level(desktop_settings.debug_logging_enabled);
|
||||
if let Some(state) = app.try_state::<CloseBehaviorState>() {
|
||||
state.apply(desktop_settings);
|
||||
}
|
||||
apply_desktop_icon_theme(app, desktop_settings.icon_theme)?;
|
||||
if matches!(std::env::consts::OS, "macos" | "windows") {
|
||||
if let Some(tray) = app.tray_by_id(DESKTOP_TRAY_ID) {
|
||||
|
|
@ -309,6 +340,7 @@ pub fn run() {
|
|||
app.manage(commands::external_sql::ExternalSqlOpenState::default());
|
||||
app.manage(commands::external_db::ExternalDbOpenState::default());
|
||||
app.manage(commands::deep_link::DeepLinkOpenState::default());
|
||||
app.manage(CloseBehaviorState::new(&desktop_settings));
|
||||
let startup_links = commands::deep_link::connection_deep_links_from_args(std::env::args().skip(1));
|
||||
open_connection_deep_links(app.handle(), startup_links);
|
||||
|
||||
|
|
@ -336,10 +368,26 @@ pub fn run() {
|
|||
})
|
||||
.on_window_event(|window, event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
if should_hide_window_on_close(std::env::consts::OS) {
|
||||
if !should_hide_window_on_close(std::env::consts::OS) {
|
||||
return;
|
||||
}
|
||||
let app = window.app_handle();
|
||||
let Some(state) = app.try_state::<CloseBehaviorState>() else {
|
||||
let _ = window.hide();
|
||||
api.prevent_close();
|
||||
return;
|
||||
};
|
||||
if !state.prompted() {
|
||||
api.prevent_close();
|
||||
let _ = app.emit("dbx-close-action-prompt", ());
|
||||
return;
|
||||
}
|
||||
if state.quit_on_close() {
|
||||
app.exit(0);
|
||||
return;
|
||||
}
|
||||
let _ = window.hide();
|
||||
api.prevent_close();
|
||||
}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
|
|
|
|||
Loading…
Reference in New Issue