diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 20707e6..bda856d 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -28,3 +28,4 @@ body { } @import "./layout.scss"; +@import "./page.scss"; diff --git a/src/assets/styles/layout.scss b/src/assets/styles/layout.scss index 509aaba..03ff3ee 100644 --- a/src/assets/styles/layout.scss +++ b/src/assets/styles/layout.scss @@ -27,6 +27,10 @@ text-align: center; box-sizing: border-box; + img { + width: 100%; + } + .the-newbtn { position: absolute; right: 20px; @@ -54,27 +58,24 @@ position: relative; flex: 1 1 75%; height: 100%; - display: flex; - flex-direction: column; - padding: 2px 0; - box-sizing: border-box; .the-bar { - flex: 0 0 30px; - width: 100%; - height: 30px; - padding: 0 16px; + position: absolute; + top: 2px; + right: 8px; + height: 36px; display: flex; align-items: center; - justify-content: flex-end; box-sizing: border-box; + z-index: 2; } .the-content { - flex: 1 1 100%; - overflow: auto; - box-sizing: border-box; - scrollbar-gutter: stable; + position: absolute; + left: 0; + right: 0; + top: 30px; + bottom: 10px; } } } diff --git a/src/assets/styles/page.scss b/src/assets/styles/page.scss new file mode 100644 index 0000000..645d308 --- /dev/null +++ b/src/assets/styles/page.scss @@ -0,0 +1,33 @@ +.base-page { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + + > header { + flex: 0 0 58px; + width: 90%; + max-width: 850px; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + } + + > section { + position: relative; + flex: 1 1 100%; + width: 100%; + height: 100%; + overflow: auto; + padding: 8px 0; + box-sizing: border-box; + scrollbar-gutter: stable; + + .base-content { + width: 90%; + max-width: 850px; + margin: 0 auto; + } + } +} diff --git a/src/components/base-page.tsx b/src/components/base-page.tsx new file mode 100644 index 0000000..10e16ac --- /dev/null +++ b/src/components/base-page.tsx @@ -0,0 +1,32 @@ +import { Typography } from "@mui/material"; +import React from "react"; + +interface Props { + title?: React.ReactNode; // the page title + header?: React.ReactNode; // something behind title + contentStyle?: React.CSSProperties; +} + +const BasePage: React.FC = (props) => { + const { title, header, contentStyle, children } = props; + + return ( +
+
+ + {title} + + + {header} +
+ +
+
+ {children} +
+
+
+ ); +}; + +export default BasePage; diff --git a/src/components/update-button.tsx b/src/components/update-button.tsx new file mode 100644 index 0000000..8a83824 --- /dev/null +++ b/src/components/update-button.tsx @@ -0,0 +1,40 @@ +import useSWR from "swr"; +import { useState } from "react"; +import { Button } from "@mui/material"; +import { checkUpdate } from "@tauri-apps/api/updater"; +import UpdateDialog from "./update-dialog"; + +interface Props { + className?: string; +} + +const UpdateButton = (props: Props) => { + const { className } = props; + + const [dialogOpen, setDialogOpen] = useState(false); + const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, { + errorRetryCount: 2, + revalidateIfStale: false, + focusThrottleInterval: 36e5, // 1 hour + }); + + if (!updateInfo?.shouldUpdate) return null; + + return ( + <> + + + setDialogOpen(false)} /> + + ); +}; + +export default UpdateButton; diff --git a/src/components/window-control.tsx b/src/components/window-control.tsx new file mode 100644 index 0000000..4b5e470 --- /dev/null +++ b/src/components/window-control.tsx @@ -0,0 +1,39 @@ +import { Button } from "@mui/material"; +import { appWindow } from "@tauri-apps/api/window"; +import { + CloseRounded, + CropLandscapeOutlined, + HorizontalRuleRounded, +} from "@mui/icons-material"; + +const WindowControl = () => { + return ( + <> + + + + + + + ); +}; + +export default WindowControl; diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index b847093..16e5839 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -1,72 +1,26 @@ -import { useEffect, useMemo, useState } from "react"; import useSWR, { SWRConfig } from "swr"; +import { useEffect, useMemo } from "react"; import { Route, Routes } from "react-router-dom"; import { useRecoilState } from "recoil"; -import { - alpha, - Button, - createTheme, - IconButton, - List, - Paper, - ThemeProvider, -} from "@mui/material"; -import { HorizontalRuleRounded, CloseRounded } from "@mui/icons-material"; -import { checkUpdate } from "@tauri-apps/api/updater"; +import { alpha, createTheme, List, Paper, ThemeProvider } from "@mui/material"; +import { appWindow } from "@tauri-apps/api/window"; import { atomPaletteMode, atomThemeBlur } from "../states/setting"; -import { getVergeConfig, windowDrag, windowHide } from "../services/cmds"; +import { getVergeConfig } from "../services/cmds"; +import { routers } from "./_routers"; import LogoSvg from "../assets/image/logo.svg"; -import LogPage from "./log"; -import ProfilePage from "./profile"; -import ProxyPage from "./proxy"; -import SettingPage from "./setting"; -import ConnectionsPage from "./connections"; -import LayoutItem from "../components/layout-item"; import Traffic from "../components/traffic"; -import UpdateDialog from "../components/update-dialog"; - -const routers = [ - { - label: "Proxy", - link: "/", - ele: ProxyPage, - }, - { - label: "Profile", - link: "/profile", - ele: ProfilePage, - }, - { - label: "Connections", - link: "/connections", - ele: ConnectionsPage, - }, - { - label: "Log", - link: "/log", - ele: LogPage, - }, - { - label: "Setting", - link: "/setting", - ele: SettingPage, - }, -]; +import LayoutItem from "../components/layout-item"; +import UpdateButton from "../components/update-button"; +import WindowControl from "../components/window-control"; const Layout = () => { const [mode, setMode] = useRecoilState(atomPaletteMode); const [blur, setBlur] = useRecoilState(atomThemeBlur); const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig); - const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, { - errorRetryCount: 2, - revalidateIfStale: false, - focusThrottleInterval: 36e5, // 1 hour - }); - const [dialogOpen, setDialogOpen] = useState(false); useEffect(() => { window.addEventListener("keydown", (e) => { - if (e.key === "Escape") windowHide(); + if (e.key === "Escape") appWindow.hide(); }); }, []); @@ -96,6 +50,12 @@ const Layout = () => { }); }, [mode]); + const onDragging = (e: any) => { + if (e?.target?.dataset?.windrag) { + appWindow.startDragging(); + } + }; + return ( @@ -103,38 +63,21 @@ const Layout = () => { square elevation={0} className="layout" + onPointerDown={onDragging} sx={[ (theme) => ({ bgcolor: alpha(theme.palette.background.paper, blur ? 0.85 : 1), }), ]} > -
-
- { - windowDrag(); - e.preventDefault(); - }} - /> +
+
+ - {updateInfo?.shouldUpdate && ( - - )} +
- + {routers.map((router) => ( {router.label} @@ -142,29 +85,17 @@ const Layout = () => { ))} -
+
-
-
- e.target === e.currentTarget && windowDrag() - } - > - {/* todo: onClick = windowMini */} - - - - - - - +
+
+
-
+
{routers.map(({ label, link, ele: Ele }) => ( } /> @@ -173,7 +104,6 @@ const Layout = () => {
- setDialogOpen(false)} /> ); diff --git a/src/pages/_routers.tsx b/src/pages/_routers.tsx new file mode 100644 index 0000000..66939e4 --- /dev/null +++ b/src/pages/_routers.tsx @@ -0,0 +1,33 @@ +import LogPage from "./log"; +import ProxyPage from "./proxy"; +import ProfilePage from "./profile"; +import SettingPage from "./setting"; +import ConnectionsPage from "./connections"; + +export const routers = [ + { + label: "Proxy", + link: "/", + ele: ProxyPage, + }, + { + label: "Profile", + link: "/profile", + ele: ProfilePage, + }, + { + label: "Connections", + link: "/connections", + ele: ConnectionsPage, + }, + { + label: "Log", + link: "/log", + ele: LogPage, + }, + { + label: "Setting", + link: "/setting", + ele: SettingPage, + }, +]; diff --git a/src/pages/connections.tsx b/src/pages/connections.tsx index c848c08..98bb8d3 100644 --- a/src/pages/connections.tsx +++ b/src/pages/connections.tsx @@ -1,8 +1,9 @@ import { useEffect, useState } from "react"; -import { Box, Paper, Typography } from "@mui/material"; +import { Paper } from "@mui/material"; import { Virtuoso } from "react-virtuoso"; -import { getInfomation } from "../services/api"; import { ApiType } from "../services/types"; +import { getInfomation } from "../services/api"; +import BasePage from "../components/base-page"; import ConnectionItem from "../components/connection-item"; const ConnectionsPage = () => { @@ -26,25 +27,14 @@ const ConnectionsPage = () => { }, []); return ( - - - Connections - - - + + } /> - + ); }; diff --git a/src/pages/log.tsx b/src/pages/log.tsx index 1a32e6a..c7a4a7e 100644 --- a/src/pages/log.tsx +++ b/src/pages/log.tsx @@ -1,9 +1,10 @@ import dayjs from "dayjs"; -import { useEffect, useRef, useState } from "react"; -import { Box, Button, Paper, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { Button, Paper } from "@mui/material"; import { Virtuoso } from "react-virtuoso"; import { ApiType } from "../services/types"; import { getInfomation } from "../services/api"; +import BasePage from "../components/base-page"; import LogItem from "../components/log-item"; let logCache: ApiType.LogItem[] = []; @@ -28,33 +29,27 @@ const LogPage = () => { return () => ws?.close(); }, []); + const onClear = () => { + setLogData([]); + logCache = []; + }; + return ( - + Clear + + } > - - Logs - - - - - + { followOutput={"smooth"} /> - + ); }; diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx index adeef37..57ef2d4 100644 --- a/src/pages/profile.tsx +++ b/src/pages/profile.tsx @@ -1,6 +1,6 @@ -import { useEffect, useRef, useState } from "react"; import useSWR, { useSWRConfig } from "swr"; -import { Box, Button, Grid, TextField, Typography } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +import { Box, Button, Grid, TextField } from "@mui/material"; import { getProfiles, selectProfile, @@ -8,9 +8,10 @@ import { importProfile, } from "../services/cmds"; import { getProxies, updateProxy } from "../services/api"; -import ProfileItemComp from "../components/profile-item"; -import useNotice from "../utils/use-notice"; import noop from "../utils/noop"; +import useNotice from "../utils/use-notice"; +import BasePage from "../components/base-page"; +import ProfileItemComp from "../components/profile-item"; const ProfilePage = () => { const [url, setUrl] = useState(""); @@ -97,11 +98,7 @@ const ProfilePage = () => { }; return ( - - - Profiles - - + { {noticeElement} - + ); }; diff --git a/src/pages/proxy.tsx b/src/pages/proxy.tsx index 02e5b0f..f491dd4 100644 --- a/src/pages/proxy.tsx +++ b/src/pages/proxy.tsx @@ -1,9 +1,10 @@ import useSWR, { useSWRConfig } from "swr"; import { useEffect } from "react"; -import { Box, List, Paper, Typography } from "@mui/material"; +import { List, Paper } from "@mui/material"; import { getProxies } from "../services/api"; -import ProxyGroup from "../components/proxy-group"; +import BasePage from "../components/base-page"; import ProxyItem from "../components/proxy-item"; +import ProxyGroup from "../components/proxy-group"; const ProxyPage = () => { const { mutate } = useSWRConfig(); @@ -19,12 +20,8 @@ const ProxyPage = () => { }, []); return ( - - - {groups.length ? "Proxy Groups" : "Proxies"} - - - + + {groups.length > 0 && ( {groups.map((group) => ( @@ -46,7 +43,7 @@ const ProxyPage = () => { )} - + ); }; diff --git a/src/pages/setting.tsx b/src/pages/setting.tsx index 6b3dfaf..98c0256 100644 --- a/src/pages/setting.tsx +++ b/src/pages/setting.tsx @@ -1,14 +1,11 @@ -import { Box, Paper, Typography } from "@mui/material"; +import { Paper } from "@mui/material"; +import BasePage from "../components/base-page"; import SettingVerge from "../components/setting-verge"; import SettingClash from "../components/setting-clash"; const SettingPage = () => { return ( - - - Setting - - + @@ -16,7 +13,7 @@ const SettingPage = () => { - + ); }; diff --git a/src/services/cmds.ts b/src/services/cmds.ts index 9508d1a..d309c19 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -36,18 +36,6 @@ export async function restartSidecar() { return invoke("restart_sidecar"); } -export async function windowDrag() { - return invoke("win_drag"); -} - -export async function windowHide() { - return invoke("win_hide"); -} - -export async function windowMini() { - return invoke("win_mini"); -} - export async function getClashInfo() { return invoke("get_clash_info"); }