fixed dark mode

This commit is contained in:
coolcoala
2025-07-09 04:43:16 +03:00
parent 7cc893383e
commit ec99e24ca1
7 changed files with 143 additions and 316 deletions

View File

@@ -0,0 +1,105 @@
import { useEffect } from "react";
import { useVerge } from "@/hooks/use-verge";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { Theme as TauriTheme } from "@tauri-apps/api/window";
import { defaultTheme, defaultDarkTheme } from "@/pages/_theme";
type ThemeProviderProps = {
children: React.ReactNode;
};
function hexToHsl(hex?: string): string | undefined {
if (!hex) return undefined;
let r = 0, g = 0, b = 0;
hex = hex.replace("#", "");
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
r = parseInt(hex.substring(0, 2), 16) / 255;
g = parseInt(hex.substring(2, 4), 16) / 255;
b = parseInt(hex.substring(4, 6), 16) / 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h = 0, s = 0, l = (max + min) / 2;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return `${(h * 360).toFixed(1)} ${s.toFixed(3)} ${l.toFixed(3)}`;
}
export function ThemeProvider({ children }: ThemeProviderProps) {
const { verge } = useVerge();
const themeModeSetting = verge?.theme_mode || "system";
const customThemeSettings = verge?.theme_setting || {};
useEffect(() => {
const root = window.document.documentElement; // <html> тег
const appWindow = getCurrentWebviewWindow();
const applyTheme = (mode: 'light' | 'dark') => {
root.classList.remove("light", "dark");
root.classList.add(mode);
appWindow.setTheme(mode as TauriTheme).catch(console.error);
const basePalette = mode === 'light' ? defaultTheme : defaultDarkTheme;
const variables = {
"--background": hexToHsl(basePalette.background_color),
"--foreground": hexToHsl(customThemeSettings.primary_text || basePalette.primary_text),
"--card": hexToHsl(basePalette.background_color), // Используем тот же фон
"--card-foreground": hexToHsl(customThemeSettings.primary_text || basePalette.primary_text),
"--popover": hexToHsl(basePalette.background_color),
"--popover-foreground": hexToHsl(customThemeSettings.primary_text || basePalette.primary_text),
"--primary": hexToHsl(customThemeSettings.primary_color || basePalette.primary_color),
"--primary-foreground": hexToHsl("#ffffff"), // Предполагаем белый текст на основном цвете
"--secondary": hexToHsl(customThemeSettings.secondary_color || basePalette.secondary_color),
"--secondary-foreground": hexToHsl(customThemeSettings.primary_text || basePalette.primary_text),
"--muted-foreground": hexToHsl(customThemeSettings.secondary_text || basePalette.secondary_text),
"--destructive": hexToHsl(customThemeSettings.error_color || basePalette.error_color),
"--ring": hexToHsl(customThemeSettings.primary_color || basePalette.primary_color),
};
for (const [key, value] of Object.entries(variables)) {
if(value) root.style.setProperty(key, value);
}
if (customThemeSettings.font_family) {
root.style.setProperty("--font-sans", customThemeSettings.font_family);
} else {
root.style.removeProperty("--font-sans");
}
let styleElement = document.querySelector("style#verge-theme");
if (!styleElement) {
styleElement = document.createElement("style");
styleElement.id = "verge-theme";
document.head.appendChild(styleElement!);
}
if (styleElement) {
styleElement.innerHTML = customThemeSettings.css_injection || "";
}
};
if (themeModeSetting === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
applyTheme(systemTheme);
const unlistenPromise = appWindow.onThemeChanged(({ payload }) => {
if (verge?.theme_mode === 'system') applyTheme(payload);
});
return () => { unlistenPromise.then(f => f()); };
} else {
applyTheme(themeModeSetting);
}
}, [themeModeSetting, customThemeSettings, verge?.theme_mode]);
return <>{children}</>;
}

View File

@@ -1,213 +1,41 @@
import { useEffect, useMemo } from "react";
import { alpha, createTheme, Shadows, Theme as MuiTheme } from "@mui/material";
import {
getCurrentWebviewWindow,
WebviewWindow,
} from "@tauri-apps/api/webviewWindow";
import { useSetThemeMode, useThemeMode } from "@/services/states";
import { defaultTheme, defaultDarkTheme } from "@/pages/_theme";
import { useVerge } from "@/hooks/use-verge";
import {
zhCN as zhXDataGrid,
enUS as enXDataGrid,
ruRU as ruXDataGrid,
faIR as faXDataGrid,
arSD as arXDataGrid,
} from "@mui/x-data-grid/locales";
import { useTranslation } from "react-i18next";
import { Theme as TauriOsTheme } from "@tauri-apps/api/window";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { Theme } from "@tauri-apps/api/window";
const languagePackMap: Record<string, any> = {
zh: { ...zhXDataGrid },
fa: { ...faXDataGrid },
ru: { ...ruXDataGrid },
ar: { ...arXDataGrid },
en: { ...enXDataGrid },
};
const getLanguagePackMap = (key: string) =>
languagePackMap[key] || languagePackMap.en;
/**
* custom theme
*/
export const useCustomTheme = () => {
const appWindow: WebviewWindow = useMemo(() => getCurrentWebviewWindow(), []);
const appWindow = useMemo(() => getCurrentWebviewWindow(), []);
const { verge } = useVerge();
const { i18n } = useTranslation();
const { theme_mode, theme_setting } = verge ?? {};
const { theme_mode } = verge ?? {};
const mode = useThemeMode();
const setMode = useSetThemeMode();
useEffect(() => {
if (theme_mode === "light" || theme_mode === "dark") {
setMode(theme_mode);
}
setMode(theme_mode === "light" || theme_mode === "dark" ? theme_mode : "system");
}, [theme_mode, setMode]);
useEffect(() => {
if (theme_mode !== "system") {
return;
}
const root = document.documentElement;
let isMounted = true;
const activeTheme = mode === "system"
? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
: mode;
const timerId = setTimeout(() => {
if (!isMounted) return;
appWindow
.theme()
.then((systemTheme) => {
if (isMounted && systemTheme) {
setMode(systemTheme);
}
})
.catch((err) => {
console.error("Failed to get initial system theme:", err);
});
}, 0);
root.classList.remove("light", "dark");
root.classList.add(activeTheme);
appWindow.setTheme(activeTheme as Theme).catch(console.error);
const unlistenPromise = appWindow.onThemeChanged(({ payload }) => {
if (isMounted) {
setMode(payload);
}
});
return () => {
isMounted = false;
clearTimeout(timerId);
unlistenPromise
.then((unlistenFn) => {
if (typeof unlistenFn === "function") {
unlistenFn();
}
})
.catch((err) => {
console.error("Failed to unlisten from theme changes:", err);
});
};
}, [theme_mode, appWindow, setMode]);
}, [mode, appWindow]);
useEffect(() => {
if (theme_mode === undefined) {
return;
}
if (theme_mode !== "system") return;
const unlistenPromise = appWindow.onThemeChanged(({ payload }) => {
setMode(payload);
});
return () => { unlistenPromise.then(f => f()); };
}, [theme_mode, appWindow, setMode]);
if (theme_mode === "system") {
appWindow.setTheme(null).catch((err) => {
console.error(
"Failed to set window theme to follow system (setTheme(null)):",
err,
);
});
} else if (mode) {
appWindow.setTheme(mode as TauriOsTheme).catch((err) => {
console.error(`Failed to set window theme to ${mode}:`, err);
});
}
}, [mode, appWindow, theme_mode]);
const theme = useMemo(() => {
const setting = theme_setting || {};
const dt = mode === "light" ? defaultTheme : defaultDarkTheme;
let muiTheme: MuiTheme;
try {
muiTheme = createTheme(
{
breakpoints: {
values: { xs: 0, sm: 650, md: 900, lg: 1200, xl: 1536 },
},
palette: {
mode,
primary: { main: setting.primary_color || dt.primary_color },
secondary: { main: setting.secondary_color || dt.secondary_color },
info: { main: setting.info_color || dt.info_color },
error: { main: setting.error_color || dt.error_color },
warning: { main: setting.warning_color || dt.warning_color },
success: { main: setting.success_color || dt.success_color },
text: {
primary: setting.primary_text || dt.primary_text,
secondary: setting.secondary_text || dt.secondary_text,
},
background: {
paper: dt.background_color,
},
},
shadows: Array(25).fill("none") as Shadows,
typography: {
fontFamily: setting.font_family
? `${setting.font_family}, ${dt.font_family}`
: dt.font_family,
},
},
getLanguagePackMap(i18n.language),
);
} catch (e) {
console.error("Error creating MUI theme, falling back to defaults:", e);
muiTheme = createTheme({
breakpoints: {
values: { xs: 0, sm: 650, md: 900, lg: 1200, xl: 1536 },
},
palette: {
mode,
primary: { main: dt.primary_color },
secondary: { main: dt.secondary_color },
info: { main: dt.info_color },
error: { main: dt.error_color },
warning: { main: dt.warning_color },
success: { main: dt.success_color },
text: { primary: dt.primary_text, secondary: dt.secondary_text },
},
typography: { fontFamily: dt.font_family },
});
}
const rootEle = document.documentElement;
if (rootEle) {
const backgroundColor = mode === "light" ? "#ECECEC" : "#2e303d";
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
const scrollColor = mode === "light" ? "#90939980" : "#3E3E3Eee";
const dividerColor =
mode === "light" ? "rgba(0, 0, 0, 0.06)" : "rgba(255, 255, 255, 0.06)";
rootEle.style.setProperty("--divider-color", dividerColor);
rootEle.style.setProperty("--background-color", backgroundColor);
rootEle.style.setProperty("--selection-color", selectColor);
rootEle.style.setProperty("--scroller-color", scrollColor);
rootEle.style.setProperty(
"--primary-main",
muiTheme.palette.primary.main,
);
rootEle.style.setProperty(
"--background-color-alpha",
alpha(muiTheme.palette.primary.main, 0.1),
);
}
// inject css
let styleElement = document.querySelector("style#verge-theme");
if (!styleElement) {
styleElement = document.createElement("style");
styleElement.id = "verge-theme";
document.head.appendChild(styleElement!);
}
if (styleElement) {
styleElement.innerHTML = setting.css_injection || "";
}
const { palette } = muiTheme;
setTimeout(() => {
const dom = document.querySelector("#Gradient2");
if (dom) {
dom.innerHTML = `
<stop offset="0%" stop-color="${palette.primary.main}" />
<stop offset="80%" stop-color="${palette.primary.dark}" />
<stop offset="100%" stop-color="${palette.primary.dark}" />
`;
}
}, 0);
return muiTheme;
}, [mode, theme_setting, i18n.language]);
return { theme };
return {};
};