Add configurable black app icons and logo (#564)
This commit is contained in:
parent
c0b231374d
commit
bc4e912504
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -4,6 +4,7 @@ import { useI18n } from "vue-i18n";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Lock, Loader2, ShieldCheck } from "lucide-vue-next";
|
||||
import AppLogo from "@/components/icons/AppLogo.vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
|
@ -55,7 +56,7 @@ async function submit() {
|
|||
>
|
||||
<div class="w-[360px] space-y-8">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<img src="/logo.png" alt="DBX" class="w-20 h-20 rounded-2xl shadow-lg shadow-blue-500/20" />
|
||||
<AppLogo class="w-20 h-20 rounded-2xl shadow-lg shadow-blue-500/20" />
|
||||
<div class="text-center">
|
||||
<h1 class="text-2xl font-bold tracking-tight">DBX</h1>
|
||||
<p class="text-sm text-muted-foreground mt-1">
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import {
|
|||
type AiProvider,
|
||||
type AiApiStyle,
|
||||
type EditorTheme,
|
||||
type DesktopIconTheme,
|
||||
} from "@/stores/settingsStore";
|
||||
import { loadEditorTheme, editorFontTheme } from "@/lib/editorThemes";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
|
|
@ -66,6 +67,7 @@ import type { SqlSnippet } from "@/types/database";
|
|||
import { uuid } from "@/lib/utils";
|
||||
import { DEFAULT_SQL_SNIPPETS } from "@/lib/sqlCompletion";
|
||||
import AiProviderLogo from "@/components/icons/AiProviderLogo.vue";
|
||||
import AppLogo from "@/components/icons/AppLogo.vue";
|
||||
import type { AppThemeAppearance } from "@/lib/appTheme";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
|
||||
|
|
@ -93,6 +95,7 @@ const editExecuteMode = ref(settingsStore.editorSettings.executeMode);
|
|||
const editWordWrap = ref(settingsStore.editorSettings.wordWrap);
|
||||
const editAppLayout = ref(settingsStore.editorSettings.appLayout);
|
||||
const editShowTrayIcon = ref(settingsStore.desktopSettings.show_tray_icon);
|
||||
const editIconTheme = ref<DesktopIconTheme>(settingsStore.desktopSettings.icon_theme);
|
||||
const editShowColumnCommentsInHeader = ref(settingsStore.editorSettings.showColumnCommentsInHeader);
|
||||
const editCompactColumnHeaderActions = ref(settingsStore.editorSettings.compactColumnHeaderActions);
|
||||
const editRedisScanPageSize = ref(settingsStore.editorSettings.redisScanPageSize);
|
||||
|
|
@ -228,6 +231,7 @@ watch(
|
|||
editWordWrap.value = settingsStore.editorSettings.wordWrap;
|
||||
editAppLayout.value = settingsStore.editorSettings.appLayout;
|
||||
editShowTrayIcon.value = settingsStore.desktopSettings.show_tray_icon;
|
||||
editIconTheme.value = settingsStore.desktopSettings.icon_theme;
|
||||
editShowColumnCommentsInHeader.value = settingsStore.editorSettings.showColumnCommentsInHeader;
|
||||
editCompactColumnHeaderActions.value = settingsStore.editorSettings.compactColumnHeaderActions;
|
||||
editRedisScanPageSize.value = settingsStore.editorSettings.redisScanPageSize;
|
||||
|
|
@ -266,6 +270,7 @@ function hasChanges(): boolean {
|
|||
editWordWrap.value !== settingsStore.editorSettings.wordWrap ||
|
||||
editAppLayout.value !== settingsStore.editorSettings.appLayout ||
|
||||
editShowTrayIcon.value !== settingsStore.desktopSettings.show_tray_icon ||
|
||||
editIconTheme.value !== settingsStore.desktopSettings.icon_theme ||
|
||||
editShowColumnCommentsInHeader.value !== settingsStore.editorSettings.showColumnCommentsInHeader ||
|
||||
editCompactColumnHeaderActions.value !== settingsStore.editorSettings.compactColumnHeaderActions ||
|
||||
editRedisScanPageSize.value !== settingsStore.editorSettings.redisScanPageSize ||
|
||||
|
|
@ -305,6 +310,7 @@ async function persistSettings() {
|
|||
});
|
||||
await settingsStore.updateDesktopSettings({
|
||||
show_tray_icon: editShowTrayIcon.value,
|
||||
icon_theme: editIconTheme.value,
|
||||
});
|
||||
if (sidebarObjectDisplayChanged) {
|
||||
await connectionStore.refreshAllTree();
|
||||
|
|
@ -329,6 +335,7 @@ function resetDefaults() {
|
|||
editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap;
|
||||
editAppLayout.value = DEFAULT_EDITOR_SETTINGS.appLayout;
|
||||
editShowTrayIcon.value = DEFAULT_DESKTOP_SETTINGS.show_tray_icon;
|
||||
editIconTheme.value = DEFAULT_DESKTOP_SETTINGS.icon_theme;
|
||||
editShowColumnCommentsInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader;
|
||||
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
|
||||
editRedisScanPageSize.value = DEFAULT_EDITOR_SETTINGS.redisScanPageSize;
|
||||
|
|
@ -362,6 +369,10 @@ function setSidebarObjectDisplay(value: "grouped" | "simple") {
|
|||
editSidebarObjectDisplay.value = value;
|
||||
}
|
||||
|
||||
function setIconTheme(value: DesktopIconTheme) {
|
||||
editIconTheme.value = value;
|
||||
}
|
||||
|
||||
function onShortcutChange(actionId: ShortcutActionId, value: any) {
|
||||
if (typeof value !== "string") return;
|
||||
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === actionId);
|
||||
|
|
@ -591,6 +602,7 @@ watch(
|
|||
await settingsStore.initAiConfig();
|
||||
await settingsStore.initDesktopSettings();
|
||||
editShowTrayIcon.value = settingsStore.desktopSettings.show_tray_icon;
|
||||
editIconTheme.value = settingsStore.desktopSettings.icon_theme;
|
||||
webdavPassword.value = "";
|
||||
await refreshWebDavPasswordStatus();
|
||||
syncAiEditState();
|
||||
|
|
@ -1164,6 +1176,43 @@ watch(
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isWeb" class="space-y-2">
|
||||
<Label>{{ t("settings.iconTheme") }}</Label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
class="h-auto justify-start border p-3"
|
||||
:class="editIconTheme === 'default' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''"
|
||||
@click="setIconTheme('default')"
|
||||
>
|
||||
<div class="flex items-center gap-3 text-left">
|
||||
<img src="/logo.png" alt="DBX" class="h-8 w-8 rounded-md" />
|
||||
<div>
|
||||
<div class="text-sm font-medium">{{ t("settings.iconThemeDefault") }}</div>
|
||||
<div class="text-xs text-muted-foreground">{{ t("settings.iconThemeDefaultDescription") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
class="h-auto justify-start border p-3"
|
||||
:class="editIconTheme === 'black' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''"
|
||||
@click="setIconTheme('black')"
|
||||
>
|
||||
<div class="flex items-center gap-3 text-left">
|
||||
<img src="/logo-black.png" alt="DBX" class="h-8 w-8 dark:invert" />
|
||||
<div>
|
||||
<div class="text-sm font-medium">{{ t("settings.iconThemeBlack") }}</div>
|
||||
<div class="text-xs text-muted-foreground">{{ t("settings.iconThemeBlackDescription") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!isWeb"
|
||||
class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2"
|
||||
|
|
@ -1889,7 +1938,7 @@ watch(
|
|||
{{ t("settings.project") }}
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
|
||||
<img src="/logo.png" alt="DBX" class="h-7 w-7 rounded-md" />
|
||||
<AppLogo class="h-7 w-7" />
|
||||
{{ t("settings.officialDocs") }}
|
||||
<ExternalLink class="ml-auto h-3.5 w-3.5 text-muted-foreground" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const isBlackLogo = computed(() => settingsStore.desktopSettings.icon_theme === "black");
|
||||
const logoSrc = computed(() => (isBlackLogo.value ? "/logo-black.png" : "/logo.png"));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img :src="logoSrc" alt="DBX" :class="isBlackLogo ? 'dark:invert' : ''" />
|
||||
</template>
|
||||
|
|
@ -1456,6 +1456,11 @@ export default {
|
|||
appLayoutSeparatedDescription: "Clear panel separation with block-style tabs.",
|
||||
appLayoutClassic: "Classic compact",
|
||||
appLayoutClassicDescription: "Continuous tab bar with compact panel headers.",
|
||||
iconTheme: "Icon and logo style",
|
||||
iconThemeDefault: "Default",
|
||||
iconThemeDefaultDescription: "Use the color DBX app icon.",
|
||||
iconThemeBlack: "Black",
|
||||
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.",
|
||||
|
|
|
|||
|
|
@ -1349,6 +1349,11 @@ export default {
|
|||
appLayoutSeparatedDescription: "Separación clara de paneles con pestañas en bloque.",
|
||||
appLayoutClassic: "Clásico compacto",
|
||||
appLayoutClassicDescription: "Barra de pestañas continua con encabezados de panel compactos.",
|
||||
iconTheme: "Estilo de icono y logo",
|
||||
iconThemeDefault: "Predeterminado",
|
||||
iconThemeDefaultDescription: "Usar el icono de color de DBX.",
|
||||
iconThemeBlack: "Negro",
|
||||
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.",
|
||||
|
|
|
|||
|
|
@ -1430,6 +1430,11 @@ export default {
|
|||
appLayoutSeparatedDescription: "面板分隔更清晰,标签页为块状样式。",
|
||||
appLayoutClassic: "经典紧凑",
|
||||
appLayoutClassicDescription: "使用连续标签栏和更紧凑的面板标题。",
|
||||
iconTheme: "图标与 Logo 样式",
|
||||
iconThemeDefault: "默认",
|
||||
iconThemeDefaultDescription: "使用彩色 DBX 应用图标。",
|
||||
iconThemeBlack: "黑色",
|
||||
iconThemeBlackDescription: "窗口、托盘和 Logo 都使用黑色 DBX 标识。",
|
||||
showTrayIcon: "显示系统托盘/菜单栏图标",
|
||||
showTrayIconDescription: "关闭后不显示图标,但关闭窗口仍会像之前一样隐藏到后台。",
|
||||
dataGridDisplay: "数据表格显示",
|
||||
|
|
|
|||
|
|
@ -789,7 +789,7 @@ export async function loadAiConfig(): Promise<AiConfig | null> {
|
|||
}
|
||||
|
||||
export async function loadDesktopSettings(): Promise<DesktopSettings> {
|
||||
return { show_tray_icon: true };
|
||||
return { show_tray_icon: true, icon_theme: "default" };
|
||||
}
|
||||
|
||||
export async function saveDesktopSettings(_settings: DesktopSettings): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ export interface DriverStoreUsage {
|
|||
|
||||
export interface DesktopSettings {
|
||||
show_tray_icon: boolean;
|
||||
icon_theme: "default" | "black";
|
||||
}
|
||||
|
||||
export interface WebDavConfig {
|
||||
|
|
|
|||
|
|
@ -38,12 +38,24 @@ export interface AiConfig {
|
|||
|
||||
export interface DesktopSettings {
|
||||
show_tray_icon: boolean;
|
||||
icon_theme: DesktopIconTheme;
|
||||
}
|
||||
|
||||
export type DesktopIconTheme = "default" | "black";
|
||||
|
||||
export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
||||
show_tray_icon: true,
|
||||
icon_theme: "default",
|
||||
};
|
||||
|
||||
function normalizeDesktopSettings(settings: Partial<DesktopSettings> | null | undefined): DesktopSettings {
|
||||
const iconTheme = settings?.icon_theme === "black" ? "black" : DEFAULT_DESKTOP_SETTINGS.icon_theme;
|
||||
return {
|
||||
show_tray_icon: settings?.show_tray_icon ?? DEFAULT_DESKTOP_SETTINGS.show_tray_icon,
|
||||
icon_theme: iconTheme,
|
||||
};
|
||||
}
|
||||
|
||||
export interface AiProviderPreset extends Omit<AiConfig, "apiKey"> {
|
||||
label: string;
|
||||
iconSlug?: string;
|
||||
|
|
@ -415,7 +427,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
|||
|
||||
async function initDesktopSettings() {
|
||||
if (isDesktopSettingsLoaded.value) return;
|
||||
desktopSettings.value = await api.loadDesktopSettings().catch(() => ({ ...DEFAULT_DESKTOP_SETTINGS }));
|
||||
desktopSettings.value = normalizeDesktopSettings(await api.loadDesktopSettings().catch(() => null));
|
||||
isDesktopSettingsLoaded.value = true;
|
||||
}
|
||||
|
||||
|
|
@ -425,9 +437,9 @@ export const useSettingsStore = defineStore("settings", () => {
|
|||
...desktopSettings.value,
|
||||
...partial,
|
||||
};
|
||||
desktopSettings.value = next;
|
||||
desktopSettings.value = normalizeDesktopSettings(next);
|
||||
try {
|
||||
await api.saveDesktopSettings(next);
|
||||
await api.saveDesktopSettings(desktopSettings.value);
|
||||
} catch (error) {
|
||||
desktopSettings.value = previous;
|
||||
throw error;
|
||||
|
|
|
|||
|
|
@ -18,11 +18,28 @@ pub struct Storage {
|
|||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct DesktopSettings {
|
||||
pub show_tray_icon: bool,
|
||||
pub icon_theme: DesktopIconTheme,
|
||||
}
|
||||
|
||||
impl Default for DesktopSettings {
|
||||
fn default() -> Self {
|
||||
Self { show_tray_icon: true }
|
||||
Self { show_tray_icon: true, icon_theme: DesktopIconTheme::Default }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DesktopIconTheme {
|
||||
Default,
|
||||
Black,
|
||||
}
|
||||
|
||||
impl DesktopIconTheme {
|
||||
fn from_settings_value(value: Option<&serde_json::Value>) -> Self {
|
||||
match value.and_then(|value| value.as_str()) {
|
||||
Some("black") => Self::Black,
|
||||
_ => Self::Default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -326,6 +343,10 @@ impl Storage {
|
|||
let mut settings = self.load_app_settings_json().await?;
|
||||
settings.remove("run_in_background");
|
||||
settings.insert("show_tray_icon".to_string(), serde_json::Value::Bool(desktop_settings.show_tray_icon));
|
||||
settings.insert(
|
||||
"icon_theme".to_string(),
|
||||
serde_json::to_value(desktop_settings.icon_theme).map_err(|e| e.to_string())?,
|
||||
);
|
||||
self.save_app_settings_json(&settings).await
|
||||
}
|
||||
|
||||
|
|
@ -337,6 +358,7 @@ impl Storage {
|
|||
.and_then(|value| value.as_bool())
|
||||
.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")),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1063,7 +1085,7 @@ fn map_from_sql_err(err: serde_json::Error) -> rusqlite::Error {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{DesktopSettings, Storage};
|
||||
use super::{DesktopIconTheme, DesktopSettings, Storage};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
fn temp_db_path(name: &str) -> std::path::PathBuf {
|
||||
|
|
@ -1076,7 +1098,7 @@ mod tests {
|
|||
let path = temp_db_path("desktop-settings-default");
|
||||
let storage = Storage::open(&path).await.unwrap();
|
||||
|
||||
assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { show_tray_icon: true });
|
||||
assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings::default());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -1087,7 +1109,10 @@ mod tests {
|
|||
settings.insert("run_in_background".to_string(), serde_json::Value::Bool(false));
|
||||
storage.save_app_settings_json(&settings).await.unwrap();
|
||||
|
||||
assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { show_tray_icon: false });
|
||||
assert_eq!(
|
||||
storage.load_desktop_settings().await.unwrap(),
|
||||
DesktopSettings { show_tray_icon: false, ..DesktopSettings::default() }
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -1096,10 +1121,16 @@ mod tests {
|
|||
let storage = Storage::open(&path).await.unwrap();
|
||||
|
||||
storage.save_password_hash("hash-1").await.unwrap();
|
||||
storage.save_desktop_settings(&DesktopSettings { show_tray_icon: false }).await.unwrap();
|
||||
storage
|
||||
.save_desktop_settings(&DesktopSettings { show_tray_icon: false, icon_theme: DesktopIconTheme::Black })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(storage.load_password_hash().await.unwrap(), Some("hash-1".to_string()));
|
||||
assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { show_tray_icon: false });
|
||||
assert_eq!(
|
||||
storage.load_desktop_settings().await.unwrap(),
|
||||
DesktopSettings { show_tray_icon: false, icon_theme: DesktopIconTheme::Black }
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -1110,11 +1141,18 @@ mod tests {
|
|||
settings.insert("run_in_background".to_string(), serde_json::Value::Bool(false));
|
||||
storage.save_app_settings_json(&settings).await.unwrap();
|
||||
|
||||
storage.save_desktop_settings(&DesktopSettings { show_tray_icon: true }).await.unwrap();
|
||||
storage
|
||||
.save_desktop_settings(&DesktopSettings {
|
||||
icon_theme: DesktopIconTheme::Black,
|
||||
..DesktopSettings::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let settings = storage.load_app_settings_json().await.unwrap();
|
||||
assert_eq!(settings.get("run_in_background"), None);
|
||||
assert_eq!(settings.get("show_tray_icon").and_then(|value| value.as_bool()), Some(true));
|
||||
assert_eq!(settings.get("icon_theme").and_then(|value| value.as_str()), Some("black"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -1122,11 +1160,17 @@ mod tests {
|
|||
let path = temp_db_path("password-preserve-desktop-settings");
|
||||
let storage = Storage::open(&path).await.unwrap();
|
||||
|
||||
storage.save_desktop_settings(&DesktopSettings { show_tray_icon: false }).await.unwrap();
|
||||
storage
|
||||
.save_desktop_settings(&DesktopSettings { show_tray_icon: false, icon_theme: DesktopIconTheme::Black })
|
||||
.await
|
||||
.unwrap();
|
||||
storage.save_password_hash("hash-2").await.unwrap();
|
||||
|
||||
assert_eq!(storage.load_password_hash().await.unwrap(), Some("hash-2".to_string()));
|
||||
assert_eq!(storage.load_desktop_settings().await.unwrap(), DesktopSettings { show_tray_icon: false });
|
||||
assert_eq!(
|
||||
storage.load_desktop_settings().await.unwrap(),
|
||||
DesktopSettings { show_tray_icon: false, icon_theme: DesktopIconTheme::Black }
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -4,7 +4,7 @@ use dbx_core::storage::DesktopSettings;
|
|||
use tauri::{AppHandle, State};
|
||||
|
||||
use super::connection::AppState;
|
||||
use crate::apply_desktop_tray_preference;
|
||||
use crate::apply_desktop_settings;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_desktop_settings(state: State<'_, Arc<AppState>>) -> Result<DesktopSettings, String> {
|
||||
|
|
@ -18,8 +18,8 @@ pub async fn save_desktop_settings(
|
|||
settings: DesktopSettings,
|
||||
) -> Result<(), String> {
|
||||
state.storage.save_desktop_settings(&settings).await?;
|
||||
if let Err(err) = apply_desktop_tray_preference(&app, settings.show_tray_icon) {
|
||||
eprintln!("Failed to apply desktop tray preference: {err}");
|
||||
if let Err(err) = apply_desktop_settings(&app, &settings) {
|
||||
eprintln!("Failed to apply desktop settings: {err}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ mod models;
|
|||
mod window_state_guard;
|
||||
|
||||
use commands::connection::AppState;
|
||||
use dbx_core::storage::Storage;
|
||||
use dbx_core::storage::{DesktopIconTheme, DesktopSettings, Storage};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
@ -21,6 +21,7 @@ use tauri_plugin_deep_link::DeepLinkExt;
|
|||
const DESKTOP_TRAY_ID: &str = "main-tray";
|
||||
#[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");
|
||||
|
||||
fn should_hide_window_on_close(target_os: &str) -> bool {
|
||||
matches!(target_os, "macos" | "windows")
|
||||
|
|
@ -54,15 +55,35 @@ fn open_connection_deep_links(app: &tauri::AppHandle, links: Vec<String>) {
|
|||
}
|
||||
|
||||
#[cfg_attr(not(any(target_os = "macos", target_os = "windows")), allow(dead_code))]
|
||||
fn setup_desktop_tray<R: tauri::Runtime, M: Manager<R>>(manager: &M) -> tauri::Result<()> {
|
||||
fn setup_desktop_tray<R: tauri::Runtime, M: Manager<R>>(
|
||||
manager: &M,
|
||||
icon_theme: DesktopIconTheme,
|
||||
) -> tauri::Result<()> {
|
||||
let menu = MenuBuilder::new(manager).text("show", "Show DBX").separator().text("quit", "Quit DBX").build()?;
|
||||
let mut tray =
|
||||
TrayIconBuilder::<R>::with_id(DESKTOP_TRAY_ID).tooltip("DBX").menu(&menu).show_menu_on_left_click(false);
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
tray = tray.icon(MACOS_TRAY_ICON).icon_as_template(true);
|
||||
match icon_theme {
|
||||
DesktopIconTheme::Default => {
|
||||
tray = tray.icon(MACOS_TRAY_ICON).icon_as_template(true);
|
||||
}
|
||||
DesktopIconTheme::Black => {
|
||||
tray = tray.icon(BLACK_APP_ICON).icon_as_template(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let icon = match icon_theme {
|
||||
DesktopIconTheme::Default => manager.app_handle().default_window_icon().cloned(),
|
||||
DesktopIconTheme::Black => Some(BLACK_APP_ICON),
|
||||
};
|
||||
if let Some(icon) = icon {
|
||||
tray = tray.icon(icon);
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
||||
{
|
||||
if let Some(icon) = manager.app_handle().default_window_icon().cloned() {
|
||||
tray = tray.icon(icon);
|
||||
|
|
@ -86,12 +107,59 @@ fn setup_desktop_tray<R: tauri::Runtime, M: Manager<R>>(manager: &M) -> tauri::R
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn apply_desktop_tray_preference(app: &tauri::AppHandle, show_tray_icon: bool) -> tauri::Result<()> {
|
||||
fn apply_desktop_icon_theme(app: &tauri::AppHandle, icon_theme: DesktopIconTheme) -> tauri::Result<()> {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
match icon_theme {
|
||||
DesktopIconTheme::Default => {
|
||||
if let Some(icon) = app.default_window_icon().cloned() {
|
||||
window.set_icon(icon)?;
|
||||
}
|
||||
}
|
||||
DesktopIconTheme::Black => window.set_icon(BLACK_APP_ICON)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_desktop_tray_icon_theme(app: &tauri::AppHandle, icon_theme: DesktopIconTheme) -> tauri::Result<()> {
|
||||
if let Some(tray) = app.tray_by_id(DESKTOP_TRAY_ID) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
match icon_theme {
|
||||
DesktopIconTheme::Default => {
|
||||
tray.set_icon(Some(MACOS_TRAY_ICON))?;
|
||||
tray.set_icon_as_template(true)?;
|
||||
}
|
||||
DesktopIconTheme::Black => {
|
||||
tray.set_icon(Some(BLACK_APP_ICON))?;
|
||||
tray.set_icon_as_template(false)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let icon = match icon_theme {
|
||||
DesktopIconTheme::Default => app.default_window_icon().cloned(),
|
||||
DesktopIconTheme::Black => Some(BLACK_APP_ICON),
|
||||
};
|
||||
tray.set_icon(icon)?;
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
||||
{
|
||||
let _ = (tray, icon_theme);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn apply_desktop_settings(app: &tauri::AppHandle, desktop_settings: &DesktopSettings) -> tauri::Result<()> {
|
||||
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) {
|
||||
tray.set_visible(show_tray_icon)?;
|
||||
} else if show_tray_icon {
|
||||
setup_desktop_tray(app)?;
|
||||
tray.set_visible(desktop_settings.show_tray_icon)?;
|
||||
apply_desktop_tray_icon_theme(app, desktop_settings.icon_theme)?;
|
||||
} else if desktop_settings.show_tray_icon {
|
||||
setup_desktop_tray(app, desktop_settings.icon_theme)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -121,7 +189,7 @@ mod tests {
|
|||
assert!(!should_setup_desktop_tray("linux", true));
|
||||
let source = include_str!("lib.rs");
|
||||
assert!(source.contains(
|
||||
"if should_setup_desktop_tray(std::env::consts::OS, desktop_settings.show_tray_icon) {\n setup_desktop_tray(app)?;"
|
||||
"if should_setup_desktop_tray(std::env::consts::OS, desktop_settings.show_tray_icon) {\n setup_desktop_tray(app, desktop_settings.icon_theme)?;"
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -133,10 +201,18 @@ mod tests {
|
|||
assert!(!source.contains(remove_call));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_apply_black_logo_icon_theme() {
|
||||
let source = include_str!("lib.rs");
|
||||
assert!(source.contains("const BLACK_APP_ICON"));
|
||||
assert!(source.contains("DesktopIconTheme::Black => window.set_icon(BLACK_APP_ICON)?"));
|
||||
assert!(source.contains("DesktopIconTheme::Black => Some(BLACK_APP_ICON)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_settings_save_treats_runtime_tray_update_as_best_effort() {
|
||||
let source = include_str!("commands/app_settings.rs");
|
||||
assert!(source.contains("if let Err(err) = apply_desktop_tray_preference"));
|
||||
assert!(source.contains("if let Err(err) = apply_desktop_settings"));
|
||||
assert!(!source.contains("map_err(|err| err.to_string())"));
|
||||
}
|
||||
|
||||
|
|
@ -233,8 +309,9 @@ pub fn run() {
|
|||
}
|
||||
}
|
||||
if should_setup_desktop_tray(std::env::consts::OS, desktop_settings.show_tray_icon) {
|
||||
setup_desktop_tray(app)?;
|
||||
setup_desktop_tray(app, desktop_settings.icon_theme)?;
|
||||
}
|
||||
apply_desktop_icon_theme(app.handle(), desktop_settings.icon_theme)?;
|
||||
window_state_guard::enforce_main_window_bounds(app.handle());
|
||||
if should_show_main_window_after_setup() {
|
||||
show_main_window(app.handle());
|
||||
|
|
|
|||
Loading…
Reference in New Issue