Files
clash-verge-rev-lite/src/components/layout/use-custom-theme.ts
2025-07-04 02:28:27 +03:00

214 lines
6.5 KiB
TypeScript

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";
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 { verge } = useVerge();
const { i18n } = useTranslation();
const { theme_mode, theme_setting } = verge ?? {};
const mode = useThemeMode();
const setMode = useSetThemeMode();
useEffect(() => {
if (theme_mode === "light" || theme_mode === "dark") {
setMode(theme_mode);
}
}, [theme_mode, setMode]);
useEffect(() => {
if (theme_mode !== "system") {
return;
}
let isMounted = true;
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);
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]);
useEffect(() => {
if (theme_mode === undefined) {
return;
}
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 };
};