Compare commits
6 Commits
12
README.md
12
README.md
@@ -13,6 +13,7 @@ A <a href="https://github.com/Dreamacro/clash">Clash</a> GUI based on <a href="h
|
||||
|
||||
- Full `clash` config supported, Partial `clash premium` config supported.
|
||||
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://github.com/zzzgydi/clash-verge/issues/12)
|
||||
- Simple UI and supports custom theme color.
|
||||
- System proxy setting and guard.
|
||||
|
||||
## Install
|
||||
@@ -64,6 +65,17 @@ yarn build
|
||||
<img src="./docs/demo6.png" alt="demo6" width="32%" />
|
||||
</div>
|
||||
|
||||
### Custom Theme
|
||||
|
||||
<div align="center">
|
||||
<img src="./docs/color1.png" alt="demo1" width="16%" />
|
||||
<img src="./docs/color2.png" alt="demo2" width="16%" />
|
||||
<img src="./docs/color3.png" alt="demo3" width="16%" />
|
||||
<img src="./docs/color4.png" alt="demo4" width="16%" />
|
||||
<img src="./docs/color5.png" alt="demo5" width="16%" />
|
||||
<img src="./docs/color6.png" alt="demo6" width="16%" />
|
||||
</div>
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This is a learning project for Rust practice.
|
||||
|
||||
13
UPDATELOG.md
13
UPDATELOG.md
@@ -1,3 +1,16 @@
|
||||
## v0.0.28
|
||||
|
||||
### Features
|
||||
|
||||
- enable to use clash config fields (UI)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- remove the character
|
||||
- fix some icon color
|
||||
|
||||
---
|
||||
|
||||
## v0.0.27
|
||||
|
||||
### Features
|
||||
|
||||
BIN
docs/color1.png
Normal file
BIN
docs/color1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/color2.png
Normal file
BIN
docs/color2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/color3.png
Normal file
BIN
docs/color3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/color4.png
Normal file
BIN
docs/color4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/color5.png
Normal file
BIN
docs/color5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/color6.png
Normal file
BIN
docs/color6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "0.0.27",
|
||||
"version": "0.0.28",
|
||||
"license": "GPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "tauri dev",
|
||||
|
||||
@@ -125,6 +125,23 @@ pub fn change_profile_chain(
|
||||
wrap_err!(clash.activate_enhanced(&profiles, false, false))
|
||||
}
|
||||
|
||||
/// change the profile valid fields
|
||||
#[tauri::command]
|
||||
pub fn change_profile_valid(
|
||||
valid: Option<Vec<String>>,
|
||||
app_handle: tauri::AppHandle,
|
||||
clash_state: State<'_, ClashState>,
|
||||
profiles_state: State<'_, ProfilesState>,
|
||||
) -> Result<(), String> {
|
||||
let mut clash = clash_state.0.lock().unwrap();
|
||||
let mut profiles = profiles_state.0.lock().unwrap();
|
||||
|
||||
profiles.put_valid(valid);
|
||||
clash.set_window(app_handle.get_window("main"));
|
||||
|
||||
wrap_err!(clash.activate_enhanced(&profiles, false, false))
|
||||
}
|
||||
|
||||
/// manually exec enhanced profile
|
||||
#[tauri::command]
|
||||
pub fn enhance_profiles(
|
||||
|
||||
@@ -316,6 +316,9 @@ pub struct Profiles {
|
||||
/// same as PrfConfig.chain
|
||||
chain: Option<Vec<String>>,
|
||||
|
||||
/// record valid fields for clash
|
||||
valid: Option<Vec<String>>,
|
||||
|
||||
/// profile list
|
||||
items: Option<Vec<PrfItem>>,
|
||||
}
|
||||
@@ -399,6 +402,11 @@ impl Profiles {
|
||||
self.chain = chain;
|
||||
}
|
||||
|
||||
/// just change the `field`
|
||||
pub fn put_valid(&mut self, valid: Option<Vec<String>>) {
|
||||
self.valid = valid;
|
||||
}
|
||||
|
||||
/// find the item by the uid
|
||||
pub fn get_item(&self, uid: &String) -> Result<&PrfItem> {
|
||||
if self.items.is_some() {
|
||||
@@ -599,9 +607,12 @@ impl Profiles {
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
let valid = self.valid.clone().unwrap_or(vec![]);
|
||||
|
||||
Ok(PrfEnhanced {
|
||||
current,
|
||||
chain,
|
||||
valid,
|
||||
callback,
|
||||
})
|
||||
}
|
||||
@@ -613,6 +624,8 @@ pub struct PrfEnhanced {
|
||||
|
||||
pub chain: Vec<PrfData>,
|
||||
|
||||
pub valid: Vec<String>,
|
||||
|
||||
pub callback: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ fn main() -> std::io::Result<()> {
|
||||
cmds::sync_profiles,
|
||||
cmds::enhance_profiles,
|
||||
cmds::change_profile_chain,
|
||||
cmds::change_profile_valid,
|
||||
cmds::read_profile_file,
|
||||
cmds::save_profile_file
|
||||
]);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "Clash Verge",
|
||||
"version": "0.0.27"
|
||||
"version": "0.0.28"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -1,26 +1,49 @@
|
||||
import useSWR from "swr";
|
||||
import { useState } from "react";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { Box, Grid, IconButton, Stack } from "@mui/material";
|
||||
import { RestartAltRounded } from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Grid,
|
||||
IconButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
AddchartRounded,
|
||||
CheckRounded,
|
||||
MenuRounded,
|
||||
RestartAltRounded,
|
||||
} from "@mui/icons-material";
|
||||
import {
|
||||
getProfiles,
|
||||
deleteProfile,
|
||||
enhanceProfiles,
|
||||
changeProfileChain,
|
||||
changeProfileValid,
|
||||
} from "../../services/cmds";
|
||||
import { CmdType } from "../../services/types";
|
||||
import Notice from "../base/base-notice";
|
||||
import getSystem from "../../utils/get-system";
|
||||
import ProfileMore from "./profile-more";
|
||||
import Notice from "../base/base-notice";
|
||||
|
||||
interface Props {
|
||||
items: CmdType.ProfileItem[];
|
||||
chain: string[];
|
||||
}
|
||||
|
||||
const OS = getSystem();
|
||||
|
||||
const EnhancedMode = (props: Props) => {
|
||||
const { items, chain } = props;
|
||||
|
||||
const { mutate } = useSWR("getProfiles", getProfiles);
|
||||
const { data, mutate } = useSWR("getProfiles", getProfiles);
|
||||
const valid = data?.valid || [];
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<any>(null);
|
||||
|
||||
// handler
|
||||
const onEnhance = useLockFn(async () => {
|
||||
@@ -74,6 +97,19 @@ const EnhancedMode = (props: Props) => {
|
||||
mutate((conf = {}) => ({ ...conf, chain: newChain }), true);
|
||||
});
|
||||
|
||||
// update valid list
|
||||
const onToggleValid = useLockFn(async (key: string) => {
|
||||
try {
|
||||
const newValid = valid.includes(key)
|
||||
? valid.filter((i) => i !== key)
|
||||
: valid.concat(key);
|
||||
await changeProfileValid(newValid);
|
||||
mutate();
|
||||
} catch (err: any) {
|
||||
Notice.error(err.message || err.toString());
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<Stack
|
||||
@@ -92,9 +128,74 @@ const EnhancedMode = (props: Props) => {
|
||||
<RestartAltRounded />
|
||||
</IconButton>
|
||||
|
||||
{/* <IconButton size="small" color="inherit">
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
id="profile-use-button"
|
||||
title="enable clash fields"
|
||||
aria-controls={!!anchorEl ? "profile-use-menu" : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={!!anchorEl ? "true" : undefined}
|
||||
onClick={(e) => setAnchorEl(e.currentTarget)}
|
||||
>
|
||||
<MenuRounded />
|
||||
</IconButton> */}
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
id="profile-use-menu"
|
||||
open={!!anchorEl}
|
||||
anchorEl={anchorEl}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
transitionDuration={225}
|
||||
TransitionProps={
|
||||
OS === "macos" ? { style: { transitionDuration: "225ms" } } : {}
|
||||
}
|
||||
MenuListProps={{
|
||||
dense: true,
|
||||
"aria-labelledby": "profile-use-button",
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
setAnchorEl(null);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<MenuItem>
|
||||
<ListItemIcon color="inherit">
|
||||
<AddchartRounded />
|
||||
</ListItemIcon>
|
||||
Use Clash Fields
|
||||
</MenuItem>
|
||||
|
||||
<Divider />
|
||||
|
||||
{[
|
||||
"tun",
|
||||
"dns",
|
||||
"hosts",
|
||||
"script",
|
||||
"profile",
|
||||
"payload",
|
||||
"interface-name",
|
||||
"routing-mark",
|
||||
].map((key) => {
|
||||
const has = valid.includes(key);
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={key}
|
||||
sx={{ width: 180 }}
|
||||
onClick={() => onToggleValid(key)}
|
||||
>
|
||||
{has && (
|
||||
<ListItemIcon color="inherit">
|
||||
<CheckRounded />
|
||||
</ListItemIcon>
|
||||
)}
|
||||
<ListItemText inset={!has}>{key}</ListItemText>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</Stack>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
|
||||
@@ -124,18 +124,25 @@ const ProxyGlobal = (props: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
title="location"
|
||||
color="inherit"
|
||||
onClick={() => onLocation(true)}
|
||||
>
|
||||
<MyLocationRounded />
|
||||
</IconButton>
|
||||
|
||||
<IconButton size="small" title="delay check" onClick={onCheckAll}>
|
||||
<IconButton
|
||||
size="small"
|
||||
title="delay check"
|
||||
color="inherit"
|
||||
onClick={onCheckAll}
|
||||
>
|
||||
<NetworkCheckRounded />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
size="small"
|
||||
title="proxy detail"
|
||||
color="inherit"
|
||||
onClick={() => setShowType(!showType)}
|
||||
>
|
||||
{showType ? <VisibilityRounded /> : <VisibilityOffRounded />}
|
||||
@@ -144,6 +151,7 @@ const ProxyGlobal = (props: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
title="filter"
|
||||
color="inherit"
|
||||
onClick={() => setShowFilter(!showFilter)}
|
||||
>
|
||||
{showFilter ? <FilterAltRounded /> : <FilterAltOffRounded />}
|
||||
|
||||
@@ -149,18 +149,25 @@ const ProxyGroup = ({ group }: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
title="location"
|
||||
color="inherit"
|
||||
onClick={() => onLocation(true)}
|
||||
>
|
||||
<MyLocationRounded />
|
||||
</IconButton>
|
||||
|
||||
<IconButton size="small" title="delay check" onClick={onCheckAll}>
|
||||
<IconButton
|
||||
size="small"
|
||||
title="delay check"
|
||||
color="inherit"
|
||||
onClick={onCheckAll}
|
||||
>
|
||||
<NetworkCheckRounded />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
size="small"
|
||||
title="proxy detail"
|
||||
color="inherit"
|
||||
onClick={() => setShowType(!showType)}
|
||||
>
|
||||
{showType ? <VisibilityRounded /> : <VisibilityOffRounded />}
|
||||
@@ -169,6 +176,7 @@ const ProxyGroup = ({ group }: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
title="filter"
|
||||
color="inherit"
|
||||
onClick={() => setShowFilter(!showFilter)}
|
||||
>
|
||||
{showFilter ? <FilterAltRounded /> : <FilterAltOffRounded />}
|
||||
|
||||
@@ -165,7 +165,7 @@ const ProfilePage = () => {
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{enhanceItems.length && (
|
||||
{enhanceItems.length > 0 && (
|
||||
<EnhancedMode items={enhanceItems} chain={profiles.chain || []} />
|
||||
)}
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@ export async function changeProfileChain(chain?: string[]) {
|
||||
return invoke<void>("change_profile_chain", { chain });
|
||||
}
|
||||
|
||||
export async function changeProfileValid(valid?: string[]) {
|
||||
return invoke<void>("change_profile_valid", { valid });
|
||||
}
|
||||
|
||||
export async function getClashInfo() {
|
||||
return invoke<CmdType.ClashInfo | null>("get_clash_info");
|
||||
}
|
||||
|
||||
@@ -145,11 +145,12 @@ class Enhance {
|
||||
// enhanced mode runner
|
||||
private async runner(payload: CmdType.EnhancedPayload) {
|
||||
const chain = payload.chain || [];
|
||||
const valid = payload.valid || [];
|
||||
|
||||
if (!Array.isArray(chain)) throw new Error("unhandle error");
|
||||
|
||||
let pdata = payload.current || {};
|
||||
let useList = [] as string[];
|
||||
let useList = valid;
|
||||
|
||||
for (const each of chain) {
|
||||
const { uid, type = "" } = each.item;
|
||||
|
||||
@@ -116,6 +116,7 @@ export namespace CmdType {
|
||||
export interface ProfilesConfig {
|
||||
current?: string;
|
||||
chain?: string[];
|
||||
valid?: string[];
|
||||
items?: ProfileItem[];
|
||||
}
|
||||
|
||||
@@ -191,6 +192,7 @@ export namespace CmdType {
|
||||
|
||||
export interface EnhancedPayload {
|
||||
chain: ChainItem[];
|
||||
valid: string[];
|
||||
current: ProfileData;
|
||||
callback: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user