App.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { useColorScheme } from "@mui/joy";
  2. import { useEffect, Suspense } from "react";
  3. import { useTranslation } from "react-i18next";
  4. import { RouterProvider } from "react-router-dom";
  5. import router from "./router";
  6. import { useLocationStore, useGlobalStore } from "./store/module";
  7. import * as storage from "./helpers/storage";
  8. import { getSystemColorScheme } from "./helpers/utils";
  9. import Loading from "./pages/Loading";
  10. const App = () => {
  11. const { i18n } = useTranslation();
  12. const globalStore = useGlobalStore();
  13. const locationStore = useLocationStore();
  14. const { mode, setMode } = useColorScheme();
  15. const { appearance, locale, systemStatus } = globalStore.state;
  16. useEffect(() => {
  17. locationStore.updateStateWithLocation();
  18. window.onpopstate = () => {
  19. locationStore.updateStateWithLocation();
  20. };
  21. }, []);
  22. useEffect(() => {
  23. const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
  24. const handleColorSchemeChange = (e: MediaQueryListEvent) => {
  25. if (globalStore.getState().appearance === "system") {
  26. const mode = e.matches ? "dark" : "light";
  27. setMode(mode);
  28. }
  29. };
  30. try {
  31. if (darkMediaQuery.addEventListener) {
  32. darkMediaQuery.addEventListener("change", handleColorSchemeChange);
  33. } else {
  34. darkMediaQuery.addListener(handleColorSchemeChange);
  35. }
  36. } catch (error) {
  37. console.error("failed to initial color scheme listener", error);
  38. }
  39. }, []);
  40. // Inject additional style and script codes.
  41. useEffect(() => {
  42. if (systemStatus.additionalStyle) {
  43. const styleEl = document.createElement("style");
  44. styleEl.innerHTML = systemStatus.additionalStyle;
  45. styleEl.setAttribute("type", "text/css");
  46. document.body.insertAdjacentElement("beforeend", styleEl);
  47. }
  48. if (systemStatus.additionalScript) {
  49. const scriptEl = document.createElement("script");
  50. scriptEl.innerHTML = systemStatus.additionalScript;
  51. document.head.appendChild(scriptEl);
  52. }
  53. // dynamic update metadata with customized profile.
  54. document.title = systemStatus.customizedProfile.name;
  55. const link = document.querySelector("link[rel~='icon']") as HTMLLinkElement;
  56. link.href = systemStatus.customizedProfile.logoUrl || "/logo.png";
  57. }, [systemStatus]);
  58. useEffect(() => {
  59. document.documentElement.setAttribute("lang", locale);
  60. i18n.changeLanguage(locale);
  61. storage.set({
  62. locale: locale,
  63. });
  64. }, [locale]);
  65. useEffect(() => {
  66. storage.set({
  67. appearance: appearance,
  68. });
  69. let currentAppearance = appearance;
  70. if (appearance === "system") {
  71. currentAppearance = getSystemColorScheme();
  72. }
  73. setMode(currentAppearance);
  74. }, [appearance]);
  75. useEffect(() => {
  76. const root = document.documentElement;
  77. if (mode === "light") {
  78. root.classList.remove("dark");
  79. } else if (mode === "dark") {
  80. root.classList.add("dark");
  81. }
  82. }, [mode]);
  83. return (
  84. <Suspense fallback={<Loading />}>
  85. <RouterProvider router={router} />
  86. </Suspense>
  87. );
  88. };
  89. export default App;