mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-30 16:21:39 +00:00
feat: add system theme mode
This commit is contained in:
+3
-2
@@ -49,7 +49,7 @@ const queryStore = useQueryStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const savedSqlStore = useSavedSqlStore();
|
||||
const { message: toastMessage, visible: toastVisible, toast } = useToast();
|
||||
const { isDark, applyTheme, toggleTheme } = useTheme();
|
||||
const { isDark, themeMode, applyTheme, setThemeMode } = useTheme();
|
||||
const {
|
||||
checkingUpdates,
|
||||
updateInfo,
|
||||
@@ -537,6 +537,7 @@ onUnmounted(() => {
|
||||
<div class="h-screen w-screen flex flex-col bg-background text-foreground overflow-hidden">
|
||||
<AppToolbar
|
||||
:is-dark="isDark"
|
||||
:theme-mode="themeMode"
|
||||
:show-ai-panel="showAiPanel"
|
||||
:show-history="showHistory"
|
||||
:checking-updates="checkingUpdates"
|
||||
@@ -544,7 +545,7 @@ onUnmounted(() => {
|
||||
:has-sql-file-connections="hasSqlFileConnections"
|
||||
@new-connection="showConnectionDialog = true"
|
||||
@new-query="newQuery"
|
||||
@toggle-theme="toggleTheme"
|
||||
@set-theme-mode="setThemeMode"
|
||||
@toggle-ai="toggleAiPanel"
|
||||
@toggle-history="showHistory = !showHistory"
|
||||
@open-github="openGitHub"
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
Globe,
|
||||
Moon,
|
||||
Sun,
|
||||
Monitor,
|
||||
Check,
|
||||
History,
|
||||
Bot,
|
||||
ArrowLeftRight,
|
||||
@@ -27,6 +29,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
|
||||
import WindowControls from "@/components/layout/WindowControls.vue";
|
||||
import { useWindowControls } from "@/composables/useWindowControls";
|
||||
import { currentLocale, setLocale, type Locale } from "@/i18n";
|
||||
import type { AppThemeMode } from "@/lib/appTheme";
|
||||
|
||||
const localeOptions: { value: Locale; flag: string; label: string }[] = [
|
||||
{ value: "en", flag: "🇺🇸", label: "English" },
|
||||
@@ -36,6 +39,7 @@ const localeOptions: { value: Locale; flag: string; label: string }[] = [
|
||||
|
||||
defineProps<{
|
||||
isDark: boolean;
|
||||
themeMode: AppThemeMode;
|
||||
showAiPanel: boolean;
|
||||
showHistory: boolean;
|
||||
checkingUpdates: boolean;
|
||||
@@ -46,7 +50,7 @@ defineProps<{
|
||||
const emit = defineEmits<{
|
||||
"new-connection": [];
|
||||
"new-query": [];
|
||||
"toggle-theme": [];
|
||||
"set-theme-mode": [mode: AppThemeMode];
|
||||
"toggle-ai": [];
|
||||
"toggle-history": [];
|
||||
"open-github": [];
|
||||
@@ -178,15 +182,44 @@ function onToolbarDblClick(e: MouseEvent) {
|
||||
<TooltipContent>AI</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" @click="emit('toggle-theme')">
|
||||
<Moon v-if="!isDark" class="h-4 w-4" />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" :title="t('toolbar.theme')">
|
||||
<Monitor v-if="themeMode === 'system'" class="h-4 w-4" />
|
||||
<Moon v-else-if="isDark" class="h-4 w-4" />
|
||||
<Sun v-else class="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{{ isDark ? "Light" : "Dark" }}</TooltipContent>
|
||||
</Tooltip>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
class="gap-2"
|
||||
:class="{ 'bg-accent': themeMode === 'light' }"
|
||||
@select="emit('set-theme-mode', 'light')"
|
||||
>
|
||||
<Sun class="h-4 w-4" />
|
||||
{{ t("toolbar.themeLight") }}
|
||||
<Check v-if="themeMode === 'light'" class="ml-auto h-4 w-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
class="gap-2"
|
||||
:class="{ 'bg-accent': themeMode === 'dark' }"
|
||||
@select="emit('set-theme-mode', 'dark')"
|
||||
>
|
||||
<Moon class="h-4 w-4" />
|
||||
{{ t("toolbar.themeDark") }}
|
||||
<Check v-if="themeMode === 'dark'" class="ml-auto h-4 w-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
class="gap-2"
|
||||
:class="{ 'bg-accent': themeMode === 'system' }"
|
||||
@select="emit('set-theme-mode', 'system')"
|
||||
>
|
||||
<Monitor class="h-4 w-4" />
|
||||
{{ t("toolbar.themeSystem") }}
|
||||
<Check v-if="themeMode === 'system'" class="ml-auto h-4 w-4" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
|
||||
+55
-17
@@ -1,25 +1,63 @@
|
||||
import { ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import {
|
||||
APP_THEME_STORAGE_KEY,
|
||||
getTauriThemeForMode,
|
||||
normalizeAppThemeMode,
|
||||
resolveAppThemeAppearance,
|
||||
type AppThemeMode,
|
||||
} from "@/lib/appTheme";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import type { Theme } from "@tauri-apps/api/window";
|
||||
|
||||
const themeMode = ref<AppThemeMode>(
|
||||
normalizeAppThemeMode(typeof localStorage === "undefined" ? null : localStorage.getItem(APP_THEME_STORAGE_KEY)),
|
||||
);
|
||||
const systemPrefersDark = ref(readSystemPrefersDark());
|
||||
const isDark = computed(() => resolveAppThemeAppearance(themeMode.value, systemPrefersDark.value) === "dark");
|
||||
|
||||
let mediaQuery: MediaQueryList | null = null;
|
||||
let isListeningForSystemTheme = false;
|
||||
|
||||
function readSystemPrefersDark() {
|
||||
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
}
|
||||
|
||||
function setupSystemThemeListener() {
|
||||
if (isListeningForSystemTheme || typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
||||
mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
systemPrefersDark.value = mediaQuery.matches;
|
||||
const onChange = (event: MediaQueryListEvent) => {
|
||||
systemPrefersDark.value = event.matches;
|
||||
if (themeMode.value === "system") applyTheme();
|
||||
};
|
||||
mediaQuery.addEventListener("change", onChange);
|
||||
isListeningForSystemTheme = true;
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
if (typeof document !== "undefined") {
|
||||
document.documentElement.classList.toggle("dark", isDark.value);
|
||||
}
|
||||
if (!isTauriRuntime()) return;
|
||||
import("@tauri-apps/api/window").then(({ getCurrentWindow }) => {
|
||||
getCurrentWindow()
|
||||
.setTheme(getTauriThemeForMode(themeMode.value))
|
||||
.catch(() => {});
|
||||
});
|
||||
}
|
||||
|
||||
function setThemeMode(mode: AppThemeMode) {
|
||||
themeMode.value = mode;
|
||||
localStorage.setItem(APP_THEME_STORAGE_KEY, mode);
|
||||
applyTheme();
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
const isDark = ref(localStorage.getItem("dbx-theme") === "dark");
|
||||
|
||||
function applyTheme() {
|
||||
document.documentElement.classList.toggle("dark", isDark.value);
|
||||
if (!isTauriRuntime()) return;
|
||||
import("@tauri-apps/api/window").then(({ getCurrentWindow }) => {
|
||||
getCurrentWindow()
|
||||
.setTheme(isDark.value ? ("dark" as Theme) : ("light" as Theme))
|
||||
.catch(() => {});
|
||||
});
|
||||
}
|
||||
setupSystemThemeListener();
|
||||
|
||||
function toggleTheme() {
|
||||
isDark.value = !isDark.value;
|
||||
localStorage.setItem("dbx-theme", isDark.value ? "dark" : "light");
|
||||
applyTheme();
|
||||
setThemeMode(isDark.value ? "light" : "dark");
|
||||
}
|
||||
|
||||
return { isDark, applyTheme, toggleTheme };
|
||||
return { isDark, themeMode, applyTheme, setThemeMode, toggleTheme };
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ export default {
|
||||
formatSqlFailed: "Failed to format SQL",
|
||||
saveSql: "Save to SQL Library",
|
||||
openSql: "Open SQL file",
|
||||
theme: "Theme",
|
||||
themeLight: "Light",
|
||||
themeDark: "Dark",
|
||||
themeSystem: "Follow System",
|
||||
sqlSaved: "SQL saved",
|
||||
sqlOpenFailed: "Failed to open file: {message}",
|
||||
sqlSaveFailed: "Failed to save file: {message}",
|
||||
|
||||
@@ -34,6 +34,10 @@ export default {
|
||||
formatSqlFailed: "Error al formatear el SQL",
|
||||
saveSql: "Guardar en biblioteca SQL",
|
||||
openSql: "Abrir archivo SQL",
|
||||
theme: "Tema",
|
||||
themeLight: "Claro",
|
||||
themeDark: "Oscuro",
|
||||
themeSystem: "Seguir sistema",
|
||||
sqlSaved: "SQL guardado",
|
||||
sqlOpenFailed: "Error al abrir el archivo: {message}",
|
||||
sqlSaveFailed: "Error al guardar el archivo: {message}",
|
||||
|
||||
@@ -34,6 +34,10 @@ export default {
|
||||
formatSqlFailed: "SQL 格式化失败",
|
||||
saveSql: "保存到 SQL 库",
|
||||
openSql: "打开 SQL 文件",
|
||||
theme: "主题",
|
||||
themeLight: "亮色",
|
||||
themeDark: "暗色",
|
||||
themeSystem: "跟随系统",
|
||||
sqlSaved: "SQL 已保存",
|
||||
sqlOpenFailed: "打开文件失败:{message}",
|
||||
sqlSaveFailed: "保存文件失败:{message}",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Theme } from "@tauri-apps/api/window";
|
||||
|
||||
export const APP_THEME_STORAGE_KEY = "dbx-theme";
|
||||
|
||||
export type AppThemeMode = "light" | "dark" | "system";
|
||||
export type AppThemeAppearance = "light" | "dark";
|
||||
|
||||
export function normalizeAppThemeMode(value: string | null): AppThemeMode {
|
||||
if (value === "dark" || value === "light" || value === "system") return value;
|
||||
return "light";
|
||||
}
|
||||
|
||||
export function resolveAppThemeAppearance(mode: AppThemeMode, systemPrefersDark: boolean): AppThemeAppearance {
|
||||
if (mode === "system") return systemPrefersDark ? "dark" : "light";
|
||||
return mode;
|
||||
}
|
||||
|
||||
export function getTauriThemeForMode(mode: AppThemeMode): Theme | null {
|
||||
return mode === "system" ? null : mode;
|
||||
}
|
||||
Reference in New Issue
Block a user