_layout.tsx 5.2 KB

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