Compare commits

..

6 Commits

10 changed files with 122 additions and 29 deletions

View File

@@ -58,6 +58,15 @@ This is a learning project for Rust practice.
PR welcome!
## Acknowledgement
Clash Verge was based on or inspired by these projects and so on:
- [tauri-apps/tauri](https://github.com/tauri-apps/tauri): Build smaller, faster, and more secure desktop applications with a web frontend.
- [Dreamacro/clash](https://github.com/Dreamacro/clash): A rule-based tunnel in Go.
- [Fndroid/clash_for_windows_pkg](https://github.com/Fndroid/clash_for_windows_pkg): A Windows/macOS GUI based on Clash.
- [vitejs/vite](https://github.com/vitejs/vite): Next generation frontend tooling. It's fast!
## License
GPL-3.0 License
GPL-3.0 License. See [License here](./LICENSE) for details.

View File

@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "0.0.12",
"version": "0.0.13",
"license": "GPL-3.0",
"scripts": {
"dev": "cargo tauri dev",

View File

@@ -4,4 +4,5 @@ mixed-port: 7890
log-level: info
allow-lan: false
external-controller: 127.0.0.1:9090
mode: rule
secret: ""

View File

@@ -306,6 +306,7 @@ pub async fn activate_profile(
"allow-lan",
"external-controller",
"secret",
"mode",
"ipv6",
];
valid_keys.iter().for_each(|key| {

View File

@@ -1,7 +1,7 @@
{
"package": {
"productName": "clash-verge",
"version": "0.0.12"
"version": "0.0.13"
},
"build": {
"distDir": "../dist",

View File

@@ -72,9 +72,9 @@
.the-content {
position: absolute;
left: 0;
right: 0;
top: 30px;
left: 0;
right: 2px;
bottom: 10px;
}
}

View File

@@ -9,6 +9,8 @@
width: 90%;
max-width: 850px;
margin: 0 auto;
padding-right: 4px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;

View File

@@ -20,7 +20,7 @@ const BasePage: React.FC<Props> = (props) => {
{header}
</header>
<section data-windrag>
<section>
<div className="base-content" style={contentStyle} data-windrag>
{children}
</div>

View File

@@ -107,7 +107,7 @@ const Layout = () => {
<LayoutControl />
</div>
<div className="the-content" data-windrag>
<div className="the-content">
<Routes>
{routers.map(({ label, link, ele: Ele }) => (
<Route key={label} path={link} element={<Ele />} />

View File

@@ -1,6 +1,9 @@
import useSWR, { useSWRConfig } from "swr";
import { useEffect } from "react";
import { List, Paper } from "@mui/material";
import { useEffect, useMemo, useRef, useState } from "react";
import { Virtuoso } from "react-virtuoso";
import { Button, ButtonGroup, List, Paper } from "@mui/material";
import { getClashConfig, updateConfigs, updateProxy } from "../services/api";
import { patchClashConfig } from "../services/cmds";
import { getProxies } from "../services/api";
import BasePage from "../components/base-page";
import ProxyItem from "../components/proxy-item";
@@ -9,38 +12,115 @@ import ProxyGroup from "../components/proxy-group";
const ProxyPage = () => {
const { mutate } = useSWRConfig();
const { data: proxiesData } = useSWR("getProxies", getProxies);
const { groups = [], proxies = [] } = proxiesData ?? {};
const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
const [curProxy, setCurProxy] = useState<string>("DIRECT");
const curMode = clashConfig?.mode.toLowerCase();
// proxy groups
const { groups = [] } = proxiesData ?? {};
// proxies and sorted
const filterProxies = useMemo(() => {
if (!proxiesData?.proxies) return [];
const list = Object.values(proxiesData.proxies);
const retList = list.filter(
(p) => !p.all?.length && p.name !== "DIRECT" && p.name !== "REJECT"
);
const direct = list.filter((p) => p.name === "DIRECT");
const reject = list.filter((p) => p.name === "REJECT");
return direct.concat(retList).concat(reject);
}, [proxiesData]);
const modeList = ["rule", "global", "direct"];
const asGroup = curMode === "rule" || !groups.length;
// make sure that fetch the proxies successfully
useEffect(() => {
// fix the empty proxies on the first sight
// this bud only show on the build version
// call twice to avoid something unknown or the delay of the clash startup
setTimeout(() => mutate("getProxies"), 250);
setTimeout(() => mutate("getProxies"), 1000);
}, []);
if (
(curMode === "rule" && !groups.length) ||
(curMode === "global" && filterProxies.length < 4)
) {
setTimeout(() => mutate("getProxies"), 500);
}
}, [groups, filterProxies, curMode]);
// update the current proxy
useEffect(() => {
if (curMode === "direct") setCurProxy("DIRECT");
if (curMode === "global") {
const globalNow = proxiesData?.proxies?.GLOBAL?.now;
setCurProxy(globalNow || "DIRECT");
}
}, [curMode, proxiesData]);
const changeLockRef = useRef(false);
const onChangeMode = async (mode: string) => {
if (changeLockRef.current) return;
changeLockRef.current = true;
try {
// switch rapidly
await updateConfigs({ mode });
await patchClashConfig({ mode });
mutate("getClashConfig");
} finally {
changeLockRef.current = false;
}
};
const onChangeProxy = async (name: string) => {
if (curMode !== "global") return;
await updateProxy("GLOBAL", name);
setCurProxy(name);
};
// difference style
const pageStyle = asGroup ? {} : { height: "100%" };
const paperStyle: any = asGroup
? { mb: 0.5 }
: { py: 1, height: "100%", boxSizing: "border-box" };
return (
<BasePage title={groups.length ? "Proxy Groups" : "Proxies"}>
<Paper sx={{ borderRadius: 1, boxShadow: 2, mb: 1 }}>
{groups.length > 0 && (
<BasePage
contentStyle={pageStyle}
title={asGroup ? "Proxy Groups" : "Proxies"}
header={
<ButtonGroup size="small">
{modeList.map((mode) => (
<Button
key={mode}
variant={mode === curMode ? "contained" : "outlined"}
onClick={() => onChangeMode(mode)}
sx={{ textTransform: "capitalize" }}
>
{mode}
</Button>
))}
</ButtonGroup>
}
>
<Paper sx={{ borderRadius: 1, boxShadow: 2, ...paperStyle }}>
{asGroup ? (
<List>
{groups.map((group) => (
<ProxyGroup key={group.name} group={group} />
))}
</List>
)}
{!groups.length && (
<List>
{Object.values(proxies).map((proxy) => (
) : (
// virtual list
<Virtuoso
style={{ height: "100%" }}
totalCount={filterProxies.length}
itemContent={(index) => (
<ProxyItem
key={proxy.name}
proxy={proxy}
selected={false}
proxy={filterProxies[index]}
selected={filterProxies[index].name === curProxy}
onClick={onChangeProxy}
sx={{ py: 0, px: 2 }}
/>
))}
</List>
)}
/>
)}
</Paper>
</BasePage>