feat: finish main layout

This commit is contained in:
GyDi 2021-12-08 23:36:34 +08:00
parent efc1669b3e
commit a1e99e5303
13 changed files with 273 additions and 92 deletions

View File

@ -1,5 +1,5 @@
html { html {
background-color: #fff; background-color: #fefefe;
} }
body { body {
@ -15,53 +15,4 @@ code {
monospace; monospace;
} }
.layout { @import "./layout.scss";
width: 100%;
height: 100%;
display: flex;
&__sidebar {
height: 100vh;
flex: 1 1 25%;
border-right: 1px solid #ccc;
> h1 {
text-align: center;
color: #303133;
}
> h3 {
text-align: center;
color: #909399;
}
}
&__links {
$link-height: 60px;
border-top: 1px solid #ccc;
> a {
display: block;
width: 100%;
height: $link-height;
line-height: $link-height;
text-align: center;
user-select: none;
font-size: 24px;
color: #606266;
border-bottom: 1px solid #ccc;
text-decoration: none;
&.active {
background-color: #eee;
}
}
}
&__content {
flex: 1 1 75%;
padding: 20px 30px;
box-sizing: border-box;
}
}

View File

@ -0,0 +1,28 @@
.layout {
width: 100%;
height: 100%;
display: flex;
&__sidebar {
position: relative;
height: 100vh;
flex: 1 1 25%;
}
&__traffic {
position: absolute;
left: 0;
right: 0;
bottom: 18px;
> div {
margin: 0 auto;
}
}
&__content {
flex: 1 1 75%;
padding: 20px 30px;
box-sizing: border-box;
}
}

View File

@ -0,0 +1,31 @@
import { ListItem, ListItemButton, ListItemText } from "@mui/material";
import { useMatch, useResolvedPath, useNavigate } from "react-router-dom";
import type { LinkProps } from "react-router-dom";
const ListItemLink = (props: LinkProps) => {
const { to, children } = props;
const resolved = useResolvedPath(to);
const match = useMatch({ path: resolved.pathname, end: true });
const navigate = useNavigate();
return (
<ListItem sx={{ py: 0.5, maxWidth: 250, mx: "auto" }}>
<ListItemButton
sx={{
borderRadius: 2,
textAlign: "center",
bgcolor: match ? "rgba(91,92,157,0.15)" : "transparent",
}}
onClick={() => navigate(to)}
>
<ListItemText
primary={children}
sx={{ color: match ? "primary.main" : "text.primary" }}
/>
</ListItemButton>
</ListItem>
);
};
export default ListItemLink;

View File

@ -0,0 +1,69 @@
import axios from "axios";
import { useEffect, useState } from "react";
import { ArrowDownward, ArrowUpward } from "@mui/icons-material";
import parseTraffic from "../utils/parse-traffic";
import { Typography } from "@mui/material";
import { Box } from "@mui/system";
const Traffic = () => {
const [traffic, setTraffic] = useState({ up: 0, down: 0 });
useEffect(() => {
const onTraffic = () => {
axios({
url: `http://127.0.0.1:9090/traffic`,
method: "GET",
onDownloadProgress: (progressEvent) => {
const data = progressEvent.currentTarget.response || "";
const lastData = data.slice(data.trim().lastIndexOf("\n") + 1);
try {
if (lastData) setTraffic(JSON.parse(lastData));
} catch {}
},
}).catch(() => setTimeout(onTraffic, 500));
};
onTraffic();
}, []);
const [up, upUnit] = parseTraffic(traffic.up);
const [down, downUnit] = parseTraffic(traffic.down);
const valStyle: any = {
component: "span",
color: "primary",
textAlign: "center",
sx: { flex: "1 1 54px" },
};
const unitStyle: any = {
component: "span",
color: "grey.500",
fontSize: "12px",
textAlign: "right",
sx: { flex: "0 1 28px", userSelect: "none" },
};
return (
<Box width="110px">
<Box mb={2} display="flex" alignItems="center" whiteSpace="nowrap">
<ArrowUpward
fontSize="small"
color={+up > 0 ? "primary" : "disabled"}
/>
<Typography {...valStyle}>{up}</Typography>
<Typography {...unitStyle}>{upUnit}</Typography>
</Box>
<Box display="flex" alignItems="center" whiteSpace="nowrap">
<ArrowDownward
fontSize="small"
color={+down > 0 ? "primary" : "disabled"}
/>
<Typography {...valStyle}>{down}</Typography>
<Typography {...unitStyle}>{downUnit}</Typography>
</Box>
</Box>
);
};
export default Traffic;

View File

@ -2,39 +2,32 @@ import "./assets/styles/index.scss";
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { BrowserRouter, NavLink, Route, Routes } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import HomePage from "./pages/home"; import { createTheme, ThemeProvider } from "@mui/material";
import ProfilesPage from "./pages/profiles"; import Layout from "./pages/_layout";
import { version } from "../package.json";
function Layout() { const theme = createTheme({
return ( palette: {
<div className="layout"> mode: "light",
<div className="layout__sidebar"> primary: {
<h1>Clash Verge</h1> main: "#5b5c9d",
<h3>{version}</h3> },
text: {
primary: "#637381",
secondary: "#909399",
},
},
});
<div className="layout__links"> // console.log(theme);
<NavLink to="/">Home</NavLink>
<NavLink to="/profiles">Profiles</NavLink>
</div>
</div>
<div className="layout__content">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/profiles" element={<ProfilesPage />} />
</Routes>
</div>
</div>
);
}
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<ThemeProvider theme={theme}>
<BrowserRouter> <BrowserRouter>
<Layout /> <Layout />
</BrowserRouter> </BrowserRouter>
</ThemeProvider>
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById("root")
); );

74
src/pages/_layout.tsx Normal file
View File

@ -0,0 +1,74 @@
import { Route, Routes } from "react-router-dom";
import { List, Paper, Typography } from "@mui/material";
import LogPage from "../pages/log";
import HomePage from "../pages/home";
import ProxyPage from "../pages/proxy";
import SettingPage from "../pages/setting";
import ProfilesPage from "../pages/profiles";
import ConnectionsPage from "../pages/connections";
import ListItemLink from "../components/list-item-link";
import Traffic from "../components/traffic";
const Layout = () => {
const routers = [
{
label: "代理",
link: "/proxy",
},
{
label: "规则",
link: "/profiles",
},
{
label: "连接",
link: "/connections",
},
{
label: "日志",
link: "/log",
},
{
label: "设置",
link: "/setting",
},
];
return (
<Paper square elevation={0} className="layout">
<div className="layout__sidebar">
<Typography
variant="h3"
component="h1"
sx={{ my: 2, px: 2, textAlign: "center", userSelect: "none" }}
>
Clash Verge
</Typography>
<List sx={{ userSelect: "none" }}>
{routers.map((router) => (
<ListItemLink key={router.label} to={router.link}>
{router.label}
</ListItemLink>
))}
</List>
<div className="layout__traffic">
<Traffic />
</div>
</div>
<div className="layout__content">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/proxy" element={<ProxyPage />} />
<Route path="/profiles" element={<ProfilesPage />} />
<Route path="/log" element={<LogPage />} />
<Route path="/connections" element={<ConnectionsPage />} />
<Route path="/setting" element={<SettingPage />} />
</Routes>
</div>
</Paper>
);
};
export default Layout;

View File

@ -0,0 +1,5 @@
const ConnectionsPage = () => {
return <h1>Connection</h1>;
};
export default ConnectionsPage;

View File

@ -1,18 +1,10 @@
import { useState } from "react"; import { Typography } from "@mui/material";
import { TextField } from "@material-ui/core";
const HomePage = () => { const HomePage = () => {
const [port, setPort] = useState("7890");
return ( return (
<div> <Typography variant="h1" textAlign="center" mt={10}>
<TextField Hello Clash!
label="Port" </Typography>
fullWidth
value={port}
onChange={(e) => setPort(e.target.value)}
/>
</div>
); );
}; };

5
src/pages/log.tsx Normal file
View File

@ -0,0 +1,5 @@
const LogPage = () => {
return <h1>Log</h1>;
};
export default LogPage;

View File

@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { invoke } from "@tauri-apps/api"; import { invoke } from "@tauri-apps/api";
import { Button, Grid, TextField } from "@material-ui/core"; import { Button, Grid, TextField } from "@mui/material";
const ProfilesPage = () => { const ProfilesPage = () => {
const [url, setUrl] = useState(""); const [url, setUrl] = useState("");

5
src/pages/proxy.tsx Normal file
View File

@ -0,0 +1,5 @@
const ProxyPage = () => {
return <h1>Proxy</h1>;
};
export default ProxyPage;

5
src/pages/setting.tsx Normal file
View File

@ -0,0 +1,5 @@
const SettingPage = () => {
return <h1>Setting</h1>;
};
export default SettingPage;

View File

@ -0,0 +1,23 @@
const parseTraffic = (num: number) => {
const gb = 1024 ** 3;
const mb = 1024 ** 2;
const kb = 1024;
let t = num;
let u = "B";
if (num < 1000) return [`${Math.round(t)}`, "B/s"];
if (num <= mb) {
t = num / kb;
u = "KB";
} else if (num <= gb) {
t = num / mb;
u = "MB";
} else {
t = num / gb;
u = "GB";
}
if (t >= 100) return [`${Math.round(t)}`, `${u}/s`];
return [`${Math.round(t * 10) / 10}`, `${u}/s`];
};
export default parseTraffic;