6 Commits

Author SHA1 Message Date
GyDi
451afdb660 v0.0.28 2022-04-07 01:28:30 +08:00
GyDi
d298bda92c chore: update log 2022-04-07 01:27:48 +08:00
GyDi
fd99ba6255 feat: handle remote clash config fields 2022-04-07 01:20:44 +08:00
GyDi
6ade0b2b1a fix: icon button color inherit 2022-04-06 22:52:00 +08:00
GyDi
9902003da9 fix: remove the lonely zero 2022-04-06 22:48:10 +08:00
GyDi
0ff2fcac11 chore: readme 2022-04-06 01:52:20 +08:00
20 changed files with 192 additions and 12 deletions

View File

@@ -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. - 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) - 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. - System proxy setting and guard.
## Install ## Install
@@ -64,6 +65,17 @@ yarn build
<img src="./docs/demo6.png" alt="demo6" width="32%" /> <img src="./docs/demo6.png" alt="demo6" width="32%" />
</div> </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 ## Disclaimer
This is a learning project for Rust practice. This is a learning project for Rust practice.

View File

@@ -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 ## v0.0.27
### Features ### Features

BIN
docs/color1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/color2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/color3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/color4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/color5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/color6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "clash-verge", "name": "clash-verge",
"version": "0.0.27", "version": "0.0.28",
"license": "GPL-3.0", "license": "GPL-3.0",
"scripts": { "scripts": {
"dev": "tauri dev", "dev": "tauri dev",

View File

@@ -125,6 +125,23 @@ pub fn change_profile_chain(
wrap_err!(clash.activate_enhanced(&profiles, false, false)) 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 /// manually exec enhanced profile
#[tauri::command] #[tauri::command]
pub fn enhance_profiles( pub fn enhance_profiles(

View File

@@ -316,6 +316,9 @@ pub struct Profiles {
/// same as PrfConfig.chain /// same as PrfConfig.chain
chain: Option<Vec<String>>, chain: Option<Vec<String>>,
/// record valid fields for clash
valid: Option<Vec<String>>,
/// profile list /// profile list
items: Option<Vec<PrfItem>>, items: Option<Vec<PrfItem>>,
} }
@@ -399,6 +402,11 @@ impl Profiles {
self.chain = chain; 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 /// find the item by the uid
pub fn get_item(&self, uid: &String) -> Result<&PrfItem> { pub fn get_item(&self, uid: &String) -> Result<&PrfItem> {
if self.items.is_some() { if self.items.is_some() {
@@ -599,9 +607,12 @@ impl Profiles {
None => vec![], None => vec![],
}; };
let valid = self.valid.clone().unwrap_or(vec![]);
Ok(PrfEnhanced { Ok(PrfEnhanced {
current, current,
chain, chain,
valid,
callback, callback,
}) })
} }
@@ -613,6 +624,8 @@ pub struct PrfEnhanced {
pub chain: Vec<PrfData>, pub chain: Vec<PrfData>,
pub valid: Vec<String>,
pub callback: String, pub callback: String,
} }

View File

@@ -119,6 +119,7 @@ fn main() -> std::io::Result<()> {
cmds::sync_profiles, cmds::sync_profiles,
cmds::enhance_profiles, cmds::enhance_profiles,
cmds::change_profile_chain, cmds::change_profile_chain,
cmds::change_profile_valid,
cmds::read_profile_file, cmds::read_profile_file,
cmds::save_profile_file cmds::save_profile_file
]); ]);

View File

@@ -1,7 +1,7 @@
{ {
"package": { "package": {
"productName": "Clash Verge", "productName": "Clash Verge",
"version": "0.0.27" "version": "0.0.28"
}, },
"build": { "build": {
"distDir": "../dist", "distDir": "../dist",

View File

@@ -1,26 +1,49 @@
import useSWR from "swr"; import useSWR from "swr";
import { useState } from "react";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { Box, Grid, IconButton, Stack } from "@mui/material"; import {
import { RestartAltRounded } from "@mui/icons-material"; Box,
Divider,
Grid,
IconButton,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
Stack,
} from "@mui/material";
import {
AddchartRounded,
CheckRounded,
MenuRounded,
RestartAltRounded,
} from "@mui/icons-material";
import { import {
getProfiles, getProfiles,
deleteProfile, deleteProfile,
enhanceProfiles, enhanceProfiles,
changeProfileChain, changeProfileChain,
changeProfileValid,
} from "../../services/cmds"; } from "../../services/cmds";
import { CmdType } from "../../services/types"; import { CmdType } from "../../services/types";
import Notice from "../base/base-notice"; import getSystem from "../../utils/get-system";
import ProfileMore from "./profile-more"; import ProfileMore from "./profile-more";
import Notice from "../base/base-notice";
interface Props { interface Props {
items: CmdType.ProfileItem[]; items: CmdType.ProfileItem[];
chain: string[]; chain: string[];
} }
const OS = getSystem();
const EnhancedMode = (props: Props) => { const EnhancedMode = (props: Props) => {
const { items, chain } = 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 // handler
const onEnhance = useLockFn(async () => { const onEnhance = useLockFn(async () => {
@@ -74,6 +97,19 @@ const EnhancedMode = (props: Props) => {
mutate((conf = {}) => ({ ...conf, chain: newChain }), true); 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 ( return (
<Box sx={{ mt: 4 }}> <Box sx={{ mt: 4 }}>
<Stack <Stack
@@ -92,9 +128,74 @@ const EnhancedMode = (props: Props) => {
<RestartAltRounded /> <RestartAltRounded />
</IconButton> </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 /> <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> </Stack>
<Grid container spacing={2}> <Grid container spacing={2}>

View File

@@ -124,18 +124,25 @@ const ProxyGlobal = (props: Props) => {
<IconButton <IconButton
size="small" size="small"
title="location" title="location"
color="inherit"
onClick={() => onLocation(true)} onClick={() => onLocation(true)}
> >
<MyLocationRounded /> <MyLocationRounded />
</IconButton> </IconButton>
<IconButton size="small" title="delay check" onClick={onCheckAll}> <IconButton
size="small"
title="delay check"
color="inherit"
onClick={onCheckAll}
>
<NetworkCheckRounded /> <NetworkCheckRounded />
</IconButton> </IconButton>
<IconButton <IconButton
size="small" size="small"
title="proxy detail" title="proxy detail"
color="inherit"
onClick={() => setShowType(!showType)} onClick={() => setShowType(!showType)}
> >
{showType ? <VisibilityRounded /> : <VisibilityOffRounded />} {showType ? <VisibilityRounded /> : <VisibilityOffRounded />}
@@ -144,6 +151,7 @@ const ProxyGlobal = (props: Props) => {
<IconButton <IconButton
size="small" size="small"
title="filter" title="filter"
color="inherit"
onClick={() => setShowFilter(!showFilter)} onClick={() => setShowFilter(!showFilter)}
> >
{showFilter ? <FilterAltRounded /> : <FilterAltOffRounded />} {showFilter ? <FilterAltRounded /> : <FilterAltOffRounded />}

View File

@@ -149,18 +149,25 @@ const ProxyGroup = ({ group }: Props) => {
<IconButton <IconButton
size="small" size="small"
title="location" title="location"
color="inherit"
onClick={() => onLocation(true)} onClick={() => onLocation(true)}
> >
<MyLocationRounded /> <MyLocationRounded />
</IconButton> </IconButton>
<IconButton size="small" title="delay check" onClick={onCheckAll}> <IconButton
size="small"
title="delay check"
color="inherit"
onClick={onCheckAll}
>
<NetworkCheckRounded /> <NetworkCheckRounded />
</IconButton> </IconButton>
<IconButton <IconButton
size="small" size="small"
title="proxy detail" title="proxy detail"
color="inherit"
onClick={() => setShowType(!showType)} onClick={() => setShowType(!showType)}
> >
{showType ? <VisibilityRounded /> : <VisibilityOffRounded />} {showType ? <VisibilityRounded /> : <VisibilityOffRounded />}
@@ -169,6 +176,7 @@ const ProxyGroup = ({ group }: Props) => {
<IconButton <IconButton
size="small" size="small"
title="filter" title="filter"
color="inherit"
onClick={() => setShowFilter(!showFilter)} onClick={() => setShowFilter(!showFilter)}
> >
{showFilter ? <FilterAltRounded /> : <FilterAltOffRounded />} {showFilter ? <FilterAltRounded /> : <FilterAltOffRounded />}

View File

@@ -165,7 +165,7 @@ const ProfilePage = () => {
))} ))}
</Grid> </Grid>
{enhanceItems.length && ( {enhanceItems.length > 0 && (
<EnhancedMode items={enhanceItems} chain={profiles.chain || []} /> <EnhancedMode items={enhanceItems} chain={profiles.chain || []} />
)} )}

View File

@@ -66,6 +66,10 @@ export async function changeProfileChain(chain?: string[]) {
return invoke<void>("change_profile_chain", { chain }); return invoke<void>("change_profile_chain", { chain });
} }
export async function changeProfileValid(valid?: string[]) {
return invoke<void>("change_profile_valid", { valid });
}
export async function getClashInfo() { export async function getClashInfo() {
return invoke<CmdType.ClashInfo | null>("get_clash_info"); return invoke<CmdType.ClashInfo | null>("get_clash_info");
} }

View File

@@ -145,11 +145,12 @@ class Enhance {
// enhanced mode runner // enhanced mode runner
private async runner(payload: CmdType.EnhancedPayload) { private async runner(payload: CmdType.EnhancedPayload) {
const chain = payload.chain || []; const chain = payload.chain || [];
const valid = payload.valid || [];
if (!Array.isArray(chain)) throw new Error("unhandle error"); if (!Array.isArray(chain)) throw new Error("unhandle error");
let pdata = payload.current || {}; let pdata = payload.current || {};
let useList = [] as string[]; let useList = valid;
for (const each of chain) { for (const each of chain) {
const { uid, type = "" } = each.item; const { uid, type = "" } = each.item;

View File

@@ -116,6 +116,7 @@ export namespace CmdType {
export interface ProfilesConfig { export interface ProfilesConfig {
current?: string; current?: string;
chain?: string[]; chain?: string[];
valid?: string[];
items?: ProfileItem[]; items?: ProfileItem[];
} }
@@ -191,6 +192,7 @@ export namespace CmdType {
export interface EnhancedPayload { export interface EnhancedPayload {
chain: ChainItem[]; chain: ChainItem[];
valid: string[];
current: ProfileData; current: ProfileData;
callback: string; callback: string;
} }