_layout.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import dayjs from "dayjs";
  2. import i18next from "i18next";
  3. import relativeTime from "dayjs/plugin/relativeTime";
  4. import { SWRConfig, mutate } from "swr";
  5. import { useEffect } from "react";
  6. import { useTranslation } from "react-i18next";
  7. import { Route, Routes } from "react-router-dom";
  8. import { alpha, List, Paper, ThemeProvider } from "@mui/material";
  9. import { listen } from "@tauri-apps/api/event";
  10. import { appWindow } from "@tauri-apps/api/window";
  11. import { routers } from "./_routers";
  12. import { getAxios } from "@/services/api";
  13. import { useVerge } from "@/hooks/use-verge";
  14. import LogoSvg from "@/assets/image/logo.svg?react";
  15. import { BaseErrorBoundary, Notice } from "@/components/base";
  16. import { LayoutItem } from "@/components/layout/layout-item";
  17. import { LayoutControl } from "@/components/layout/layout-control";
  18. import { LayoutTraffic } from "@/components/layout/layout-traffic";
  19. import { UpdateButton } from "@/components/layout/update-button";
  20. import { useCustomTheme } from "@/components/layout/use-custom-theme";
  21. import getSystem from "@/utils/get-system";
  22. import "dayjs/locale/ru";
  23. import "dayjs/locale/zh-cn";
  24. dayjs.extend(relativeTime);
  25. const OS = getSystem();
  26. const Layout = () => {
  27. const { t } = useTranslation();
  28. const { theme } = useCustomTheme();
  29. const { verge } = useVerge();
  30. const { theme_blur, language } = verge || {};
  31. useEffect(() => {
  32. window.addEventListener("keydown", (e) => {
  33. // macOS有cmd+w
  34. if (e.key === "Escape" && OS !== "macos") {
  35. appWindow.close();
  36. }
  37. });
  38. listen("verge://refresh-clash-config", async () => {
  39. // the clash info may be updated
  40. await getAxios(true);
  41. mutate("getProxies");
  42. mutate("getVersion");
  43. mutate("getClashConfig");
  44. mutate("getProviders");
  45. });
  46. // update the verge config
  47. listen("verge://refresh-verge-config", () => mutate("getVergeConfig"));
  48. // 设置提示监听
  49. listen("verge://notice-message", ({ payload }) => {
  50. const [status, msg] = payload as [string, string];
  51. switch (status) {
  52. case "set_config::ok":
  53. Notice.success("Refresh clash config");
  54. break;
  55. case "set_config::error":
  56. Notice.error(msg);
  57. break;
  58. default:
  59. break;
  60. }
  61. });
  62. }, []);
  63. useEffect(() => {
  64. if (language) {
  65. dayjs.locale(language === "zh" ? "zh-cn" : language);
  66. i18next.changeLanguage(language);
  67. }
  68. }, [language]);
  69. return (
  70. <SWRConfig value={{ errorRetryCount: 3 }}>
  71. <ThemeProvider theme={theme}>
  72. <Paper
  73. square
  74. elevation={0}
  75. className={`${OS} layout`}
  76. onPointerDown={(e: any) => {
  77. if (e.target?.dataset?.windrag) appWindow.startDragging();
  78. }}
  79. onContextMenu={(e) => {
  80. // only prevent it on Windows
  81. const validList = ["input", "textarea"];
  82. const target = e.currentTarget;
  83. if (
  84. OS === "windows" &&
  85. !(
  86. validList.includes(target.tagName.toLowerCase()) ||
  87. target.isContentEditable
  88. )
  89. ) {
  90. e.preventDefault();
  91. }
  92. }}
  93. sx={[
  94. ({ palette }) => ({
  95. bgcolor: alpha(palette.background.paper, theme_blur ? 0.8 : 1),
  96. }),
  97. ]}
  98. >
  99. <div className="layout__left" data-windrag>
  100. <div className="the-logo" data-windrag>
  101. <LogoSvg />
  102. {!(OS === "windows" && WIN_PORTABLE) && (
  103. <UpdateButton className="the-newbtn" />
  104. )}
  105. </div>
  106. <List className="the-menu">
  107. {routers.map((router) => (
  108. <LayoutItem key={router.label} to={router.link}>
  109. {t(router.label)}
  110. </LayoutItem>
  111. ))}
  112. </List>
  113. <div className="the-traffic" data-windrag>
  114. <LayoutTraffic />
  115. </div>
  116. </div>
  117. <div className="layout__right" data-windrag>
  118. {OS === "windows" && (
  119. <div className="the-bar">
  120. <LayoutControl />
  121. </div>
  122. )}
  123. <div className="the-content">
  124. <Routes>
  125. {routers.map(({ label, link, ele: Ele }) => (
  126. <Route
  127. key={label}
  128. path={link}
  129. element={
  130. <BaseErrorBoundary key={label}>
  131. <Ele />
  132. </BaseErrorBoundary>
  133. }
  134. />
  135. ))}
  136. </Routes>
  137. </div>
  138. </div>
  139. </Paper>
  140. </ThemeProvider>
  141. </SWRConfig>
  142. );
  143. };
  144. export default Layout;