Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6779bc7459 | ||
|
|
664be2d0ba | ||
|
|
6113898b69 | ||
|
|
79aad6b5c2 | ||
|
|
6da7757d36 | ||
|
|
dbb3cb8cc8 | ||
|
|
579f36a1dd | ||
|
|
72c2b306cf | ||
|
|
c2673cd396 | ||
|
|
8eb152816a | ||
|
|
a0bc8a21a5 | ||
|
|
08e4d72758 | ||
|
|
66340a27fa |
6
.github/workflows/release.yml
vendored
@@ -54,6 +54,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
tagName: v__VERSION__
|
tagName: v__VERSION__
|
||||||
releaseName: "Clash Verge v__VERSION__"
|
releaseName: "Clash Verge v__VERSION__"
|
||||||
releaseBody: "This is a release."
|
releaseBody: "Clash Verge now supports Windows and macos(intel)."
|
||||||
releaseDraft: true
|
releaseDraft: false
|
||||||
prerelease: false
|
prerelease: true
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -3,3 +3,4 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
update.json
|
||||||
|
|||||||
10
README.md
@@ -26,7 +26,7 @@ yarn install
|
|||||||
Then download the clash binary... Or you can download it from [clash premium release](https://github.com/Dreamacro/clash/releases/tag/premium) and rename it according to [tauri config](https://tauri.studio/en/docs/api/config#tauri.bundle.externalBin).
|
Then download the clash binary... Or you can download it from [clash premium release](https://github.com/Dreamacro/clash/releases/tag/premium) and rename it according to [tauri config](https://tauri.studio/en/docs/api/config#tauri.bundle.externalBin).
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
yarn run predev
|
yarn run check
|
||||||
```
|
```
|
||||||
|
|
||||||
Then run
|
Then run
|
||||||
@@ -42,8 +42,12 @@ yarn dev
|
|||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="./docs/demo1.png" alt="demo1" width="42%" />
|
<img src="./docs/demo1.png" alt="demo1" width="32%" />
|
||||||
<img src="./docs/demo2.png" alt="demo2" width="42%" />
|
<img src="./docs/demo2.png" alt="demo2" width="32%" />
|
||||||
|
<img src="./docs/demo3.png" alt="demo3" width="32%" />
|
||||||
|
<img src="./docs/demo4.png" alt="demo4" width="32%" />
|
||||||
|
<img src="./docs/demo5.png" alt="demo5" width="32%" />
|
||||||
|
<img src="./docs/demo6.png" alt="demo6" width="32%" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|||||||
BIN
docs/demo1.png
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 21 KiB |
BIN
docs/demo2.png
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 20 KiB |
BIN
docs/demo3.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/demo4.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/demo5.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/demo6.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clash-verge",
|
"name": "clash-verge",
|
||||||
"version": "0.0.6",
|
"version": "0.0.8",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cargo tauri dev",
|
"dev": "cargo tauri dev",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ serde_yaml = "0.8"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
# tauri = { version = "1.0.0-beta.8", features = ["api-all", "system-tray"] }
|
# tauri = { version = "1.0.0-beta.8", features = ["api-all", "system-tray"] }
|
||||||
# tauri = { git = "https://github.com/tauri-apps/tauri", rev = "5e0d59ec", features = ["api-all", "system-tray"] }
|
# tauri = { git = "https://github.com/tauri-apps/tauri", rev = "5e0d59ec", features = ["api-all", "system-tray"] }
|
||||||
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["api-all", "system-tray"] }
|
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["api-all", "system-tray", "updater"] }
|
||||||
tauri-plugin-shadows = { git = "https://github.com/tauri-apps/tauri-plugin-shadows", features = ["tauri-impl"] }
|
tauri-plugin-shadows = { git = "https://github.com/tauri-apps/tauri-plugin-shadows", features = ["tauri-impl"] }
|
||||||
|
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
|
|||||||
@@ -142,10 +142,27 @@ pub fn patch_profile(
|
|||||||
|
|
||||||
/// restart the sidecar
|
/// restart the sidecar
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn restart_sidecar(clash_state: State<'_, ClashState>) {
|
pub fn restart_sidecar(
|
||||||
let mut clash_arc = clash_state.0.lock().unwrap();
|
clash_state: State<'_, ClashState>,
|
||||||
if let Err(err) = clash_arc.restart_sidecar() {
|
profiles_state: State<'_, ProfilesState>,
|
||||||
log::error!("{}", err);
|
) -> Result<(), String> {
|
||||||
|
let mut clash = clash_state.0.lock().unwrap();
|
||||||
|
|
||||||
|
match clash.restart_sidecar() {
|
||||||
|
Ok(_) => {
|
||||||
|
let profiles = profiles_state.0.lock().unwrap();
|
||||||
|
match profiles.activate(clash.info.clone()) {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("{}", err);
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("{}", err);
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,24 +193,7 @@ pub fn patch_clash_config(payload: Mapping) -> Result<(), String> {
|
|||||||
save_clash(&config)
|
save_clash(&config)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// set the system proxy
|
|
||||||
/// Tips: only support windows now
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn set_sys_proxy(enable: bool, verge_state: State<'_, VergeState>) -> Result<(), String> {
|
|
||||||
let mut verge = verge_state.0.lock().unwrap();
|
|
||||||
|
|
||||||
if let Some(mut sysproxy) = verge.cur_sysproxy.take() {
|
|
||||||
sysproxy.enable = enable;
|
|
||||||
if sysproxy.set_sys().is_err() {
|
|
||||||
log::error!("failed to set system proxy");
|
|
||||||
}
|
|
||||||
verge.cur_sysproxy = Some(sysproxy);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get the system proxy
|
/// get the system proxy
|
||||||
/// Tips: only support windows now
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
|
pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
|
||||||
match SysProxyConfig::get_sys() {
|
match SysProxyConfig::get_sys() {
|
||||||
@@ -202,6 +202,8 @@ pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get the current proxy config
|
||||||
|
/// which may not the same as system proxy
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_cur_proxy(verge_state: State<'_, VergeState>) -> Result<Option<SysProxyConfig>, String> {
|
pub fn get_cur_proxy(verge_state: State<'_, VergeState>) -> Result<Option<SysProxyConfig>, String> {
|
||||||
match verge_state.0.lock() {
|
match verge_state.0.lock() {
|
||||||
@@ -227,24 +229,7 @@ pub async fn patch_verge_config(
|
|||||||
verge_state: State<'_, VergeState>,
|
verge_state: State<'_, VergeState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut verge = verge_state.0.lock().unwrap();
|
let mut verge = verge_state.0.lock().unwrap();
|
||||||
|
verge.patch_config(payload)
|
||||||
if payload.theme_mode.is_some() {
|
|
||||||
verge.config.theme_mode = payload.theme_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if payload.enable_self_startup.is_some() {
|
|
||||||
verge.config.enable_self_startup = payload.enable_self_startup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if payload.enable_system_proxy.is_some() {
|
|
||||||
verge.config.enable_system_proxy = payload.enable_system_proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
if payload.system_proxy_bypass.is_some() {
|
|
||||||
verge.config.system_proxy_bypass = payload.system_proxy_bypass;
|
|
||||||
}
|
|
||||||
|
|
||||||
verge.config.save_file()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// start dragging window
|
/// start dragging window
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use crate::utils::{config, dirs, sysopt::SysProxyConfig};
|
use crate::utils::{config, dirs, startup, sysopt::SysProxyConfig};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tauri::api::path::resource_dir;
|
||||||
|
|
||||||
/// ### `verge.yaml` schema
|
/// ### `verge.yaml` schema
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
@@ -34,6 +36,7 @@ impl VergeConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verge App abilities
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Verge {
|
pub struct Verge {
|
||||||
pub config: VergeConfig,
|
pub config: VergeConfig,
|
||||||
@@ -41,6 +44,8 @@ pub struct Verge {
|
|||||||
pub old_sysproxy: Option<SysProxyConfig>,
|
pub old_sysproxy: Option<SysProxyConfig>,
|
||||||
|
|
||||||
pub cur_sysproxy: Option<SysProxyConfig>,
|
pub cur_sysproxy: Option<SysProxyConfig>,
|
||||||
|
|
||||||
|
pub exe_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Verge {
|
impl Default for Verge {
|
||||||
@@ -55,6 +60,7 @@ impl Verge {
|
|||||||
config: VergeConfig::new(),
|
config: VergeConfig::new(),
|
||||||
old_sysproxy: None,
|
old_sysproxy: None,
|
||||||
cur_sysproxy: None,
|
cur_sysproxy: None,
|
||||||
|
exe_path: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,4 +96,95 @@ impl Verge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// set the exe_path
|
||||||
|
pub fn set_exe_path(&mut self, package_info: &tauri::PackageInfo) {
|
||||||
|
let exe = if cfg!(target_os = "windows") {
|
||||||
|
"clash-verge.exe"
|
||||||
|
} else {
|
||||||
|
"clash-verge"
|
||||||
|
};
|
||||||
|
let path = resource_dir(package_info).unwrap().join(exe);
|
||||||
|
self.exe_path = Some(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sync the startup when run the app
|
||||||
|
pub fn sync_startup(&self) -> Result<(), String> {
|
||||||
|
let enable = self.config.enable_self_startup.clone().unwrap_or(false);
|
||||||
|
|
||||||
|
if !enable {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if self.exe_path.is_none() {
|
||||||
|
return Err("should init the exe_path first".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let exe_path = self.exe_path.clone().unwrap();
|
||||||
|
match startup::get_startup(&exe_path) {
|
||||||
|
Ok(sys_enable) => {
|
||||||
|
if sys_enable || (!sys_enable && startup::set_startup(true, &exe_path).is_ok()) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("failed to sync startup".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Err("failed to get system startup info".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// update the startup
|
||||||
|
fn update_startup(&mut self, enable: bool) -> Result<(), String> {
|
||||||
|
let conf_enable = self.config.enable_self_startup.clone().unwrap_or(false);
|
||||||
|
|
||||||
|
if enable == conf_enable {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if self.exe_path.is_none() {
|
||||||
|
return Err("should init the exe_path first".into());
|
||||||
|
}
|
||||||
|
let exe_path = self.exe_path.clone().unwrap();
|
||||||
|
match startup::set_startup(enable, &exe_path) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(_) => Err("failed to set system startup info".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// patch verge config
|
||||||
|
/// There should be only one update at a time here
|
||||||
|
/// so call the save_file at the end is savely
|
||||||
|
pub fn patch_config(&mut self, patch: VergeConfig) -> Result<(), String> {
|
||||||
|
// only change it
|
||||||
|
if patch.theme_mode.is_some() {
|
||||||
|
self.config.theme_mode = patch.theme_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// should update system startup
|
||||||
|
if patch.enable_self_startup.is_some() {
|
||||||
|
let enable = patch.enable_self_startup.unwrap();
|
||||||
|
self.update_startup(enable)?;
|
||||||
|
self.config.enable_self_startup = Some(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// should update system proxy
|
||||||
|
if patch.enable_system_proxy.is_some() {
|
||||||
|
let enable = patch.enable_system_proxy.unwrap();
|
||||||
|
if let Some(mut sysproxy) = self.cur_sysproxy.take() {
|
||||||
|
sysproxy.enable = enable;
|
||||||
|
if sysproxy.set_sys().is_err() {
|
||||||
|
log::error!("failed to set system proxy");
|
||||||
|
return Err("failed to set system proxy".into());
|
||||||
|
}
|
||||||
|
self.cur_sysproxy = Some(sysproxy);
|
||||||
|
}
|
||||||
|
self.config.enable_system_proxy = Some(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo
|
||||||
|
// should update system proxt too
|
||||||
|
if patch.system_proxy_bypass.is_some() {
|
||||||
|
self.config.system_proxy_bypass = patch.system_proxy_bypass;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.config.save_file()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,9 +42,16 @@ fn main() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
"restart_clash" => {
|
"restart_clash" => {
|
||||||
let clash_state = app_handle.state::<states::ClashState>();
|
let clash_state = app_handle.state::<states::ClashState>();
|
||||||
let mut clash_arc = clash_state.0.lock().unwrap();
|
let mut clash = clash_state.0.lock().unwrap();
|
||||||
if let Err(err) = clash_arc.restart_sidecar() {
|
match clash.restart_sidecar() {
|
||||||
log::error!("{}", err);
|
Ok(_) => {
|
||||||
|
let profiles = app_handle.state::<states::ProfilesState>();
|
||||||
|
let profiles = profiles.0.lock().unwrap();
|
||||||
|
if let Err(err) = profiles.activate(clash.info.clone()) {
|
||||||
|
log::error!("{}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => log::error!("{}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"quit" => {
|
"quit" => {
|
||||||
@@ -64,7 +71,6 @@ fn main() -> std::io::Result<()> {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
// common
|
// common
|
||||||
cmds::restart_sidecar,
|
cmds::restart_sidecar,
|
||||||
cmds::set_sys_proxy,
|
|
||||||
cmds::get_sys_proxy,
|
cmds::get_sys_proxy,
|
||||||
cmds::get_cur_proxy,
|
cmds::get_cur_proxy,
|
||||||
cmds::win_drag,
|
cmds::win_drag,
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ pub mod fetch;
|
|||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod resolve;
|
pub mod resolve;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
pub mod startup;
|
||||||
pub mod sysopt;
|
pub mod sysopt;
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ pub fn resolve_setup(app: &App) {
|
|||||||
log::error!("{}", err);
|
log::error!("{}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verge.set_exe_path(app.package_info());
|
||||||
verge.init_sysproxy(clash.info.port.clone());
|
verge.init_sysproxy(clash.info.port.clone());
|
||||||
|
if let Err(err) = verge.sync_startup() {
|
||||||
|
log::error!("{}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// reset system proxy
|
/// reset system proxy
|
||||||
|
|||||||
62
src-tauri/src/utils/startup.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
use std::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
static APP_KEY: &str = "ClashVerge";
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
/// get the startup value
|
||||||
|
/// whether as same as the exe_path
|
||||||
|
pub fn get_startup(exe_path: &PathBuf) -> io::Result<bool> {
|
||||||
|
use winreg::enums::*;
|
||||||
|
use winreg::RegKey;
|
||||||
|
|
||||||
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
|
let cur_var = hkcu.open_subkey_with_flags(
|
||||||
|
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
|
||||||
|
KEY_READ,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match cur_var.get_value::<String, _>(APP_KEY) {
|
||||||
|
Ok(path) => {
|
||||||
|
let exe_path = exe_path.clone();
|
||||||
|
let exe_path = exe_path.as_os_str().to_str().unwrap();
|
||||||
|
Ok(path == exe_path)
|
||||||
|
}
|
||||||
|
Err(_) => Ok(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
/// set the startup on windows
|
||||||
|
/// delete the reg key if disabled
|
||||||
|
pub fn set_startup(enable: bool, exe_path: &PathBuf) -> io::Result<()> {
|
||||||
|
use winreg::enums::*;
|
||||||
|
use winreg::RegKey;
|
||||||
|
|
||||||
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
|
let cur_var = hkcu.open_subkey_with_flags(
|
||||||
|
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
|
||||||
|
KEY_SET_VALUE,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match enable {
|
||||||
|
true => {
|
||||||
|
let exe_path = exe_path.clone();
|
||||||
|
let exe_path = exe_path.as_os_str().to_str().unwrap();
|
||||||
|
cur_var.set_value::<&str, _>(APP_KEY, &exe_path)
|
||||||
|
}
|
||||||
|
false => cur_var.delete_value(APP_KEY),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let path = PathBuf::from(r"D:\Software\Clash Verge\clash-verge.exe");
|
||||||
|
|
||||||
|
assert!(set_startup(true, &path).is_ok());
|
||||||
|
assert_eq!(get_startup(&path).unwrap(), true);
|
||||||
|
|
||||||
|
assert!(set_startup(false, &path).is_ok());
|
||||||
|
assert_eq!(get_startup(&path).unwrap(), false);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "clash-verge",
|
"productName": "clash-verge",
|
||||||
"version": "0.0.6"
|
"version": "0.0.8"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"distDir": "../dist",
|
"distDir": "../dist",
|
||||||
@@ -50,7 +50,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"active": false
|
"active": true,
|
||||||
|
"endpoints": [
|
||||||
|
"https://github.com/zzzgydi/clash-verge/releases/download/updater/update.json"
|
||||||
|
],
|
||||||
|
"dialog": false
|
||||||
},
|
},
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
"all": true
|
"all": true
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.the-logo {
|
.the-logo {
|
||||||
|
position: relative;
|
||||||
flex: 0 1 180px;
|
flex: 0 1 180px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
@@ -25,6 +26,13 @@
|
|||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.the-newbtn {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 12px;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.the-menu {
|
.the-menu {
|
||||||
|
|||||||
@@ -6,16 +6,12 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import {
|
import { getVergeConfig, patchVergeConfig } from "../services/cmds";
|
||||||
getVergeConfig,
|
|
||||||
patchVergeConfig,
|
|
||||||
setSysProxy,
|
|
||||||
} from "../services/cmds";
|
|
||||||
import { CmdType } from "../services/types";
|
import { CmdType } from "../services/types";
|
||||||
|
import { version } from "../../package.json";
|
||||||
import GuardState from "./guard-state";
|
import GuardState from "./guard-state";
|
||||||
import SettingItem from "./setting-item";
|
import SettingItem from "./setting-item";
|
||||||
import PaletteSwitch from "./palette-switch";
|
import PaletteSwitch from "./palette-switch";
|
||||||
import { version } from "../../package.json";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onError?: (err: Error) => void;
|
onError?: (err: Error) => void;
|
||||||
@@ -32,7 +28,6 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
} = vergeConfig ?? {};
|
} = vergeConfig ?? {};
|
||||||
|
|
||||||
const onSwitchFormat = (_e: any, value: boolean) => value;
|
const onSwitchFormat = (_e: any, value: boolean) => value;
|
||||||
|
|
||||||
const onChangeData = (patch: Partial<CmdType.VergeConfig>) => {
|
const onChangeData = (patch: Partial<CmdType.VergeConfig>) => {
|
||||||
mutate("getVergeConfig", { ...vergeConfig, ...patch }, false);
|
mutate("getVergeConfig", { ...vergeConfig, ...patch }, false);
|
||||||
};
|
};
|
||||||
@@ -51,25 +46,23 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
onChange={(e) => onChangeData({ theme_mode: e ? "dark" : "light" })}
|
onChange={(e) => onChangeData({ theme_mode: e ? "dark" : "light" })}
|
||||||
onGuard={async (c) => {
|
onGuard={(c) =>
|
||||||
await patchVergeConfig({ theme_mode: c ? "dark" : "light" });
|
patchVergeConfig({ theme_mode: c ? "dark" : "light" })
|
||||||
}}
|
}
|
||||||
>
|
>
|
||||||
<PaletteSwitch edge="end" />
|
<PaletteSwitch edge="end" />
|
||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Self Start" />
|
<ListItemText primary="Self Startup" />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={startup}
|
value={startup}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
onChange={(e) => onChangeData({ enable_self_startup: e })}
|
onChange={(e) => onChangeData({ enable_self_startup: e })}
|
||||||
onGuard={async (e) => {
|
onGuard={(e) => patchVergeConfig({ enable_self_startup: e })}
|
||||||
await patchVergeConfig({ enable_self_startup: e });
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Switch edge="end" />
|
<Switch edge="end" />
|
||||||
</GuardState>
|
</GuardState>
|
||||||
@@ -83,10 +76,7 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
onChange={(e) => onChangeData({ enable_system_proxy: e })}
|
onChange={(e) => onChangeData({ enable_system_proxy: e })}
|
||||||
onGuard={async (e) => {
|
onGuard={(e) => patchVergeConfig({ enable_system_proxy: e })}
|
||||||
await setSysProxy(e);
|
|
||||||
await patchVergeConfig({ enable_system_proxy: e });
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Switch edge="end" />
|
<Switch edge="end" />
|
||||||
</GuardState>
|
</GuardState>
|
||||||
|
|||||||
66
src/components/update-dialog.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import useSWR from "swr";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { checkUpdate, installUpdate } from "@tauri-apps/api/updater";
|
||||||
|
import { relaunch } from "@tauri-apps/api/process";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let uploadingState = false;
|
||||||
|
|
||||||
|
const UpdateDialog = (props: Props) => {
|
||||||
|
const { open, onClose } = props;
|
||||||
|
const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, {
|
||||||
|
errorRetryCount: 2,
|
||||||
|
revalidateIfStale: false,
|
||||||
|
focusThrottleInterval: 36e5, // 1 hour
|
||||||
|
});
|
||||||
|
const [uploading, setUploading] = useState(uploadingState);
|
||||||
|
|
||||||
|
const onUpdate = async () => {
|
||||||
|
try {
|
||||||
|
setUploading(true);
|
||||||
|
uploadingState = true;
|
||||||
|
await installUpdate();
|
||||||
|
await relaunch();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
window.alert("Failed to upload, please try again.");
|
||||||
|
} finally {
|
||||||
|
setUploading(true);
|
||||||
|
uploadingState = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose}>
|
||||||
|
<DialogTitle>New Version v{updateInfo?.manifest?.version}</DialogTitle>
|
||||||
|
<DialogContent sx={{ minWidth: 360, maxWidth: 400, maxHeight: "50vh" }}>
|
||||||
|
<DialogContentText>{updateInfo?.manifest?.body}</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
autoFocus
|
||||||
|
onClick={onUpdate}
|
||||||
|
disabled={uploading}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateDialog;
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import useSWR, { SWRConfig } from "swr";
|
import useSWR, { SWRConfig } from "swr";
|
||||||
import { Route, Routes } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
import { useRecoilState } from "recoil";
|
import { useRecoilState } from "recoil";
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
createTheme,
|
createTheme,
|
||||||
IconButton,
|
IconButton,
|
||||||
List,
|
List,
|
||||||
@@ -10,6 +11,7 @@ import {
|
|||||||
ThemeProvider,
|
ThemeProvider,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { HorizontalRuleRounded, CloseRounded } from "@mui/icons-material";
|
import { HorizontalRuleRounded, CloseRounded } from "@mui/icons-material";
|
||||||
|
import { checkUpdate } from "@tauri-apps/api/updater";
|
||||||
import { atomPaletteMode } from "../states/setting";
|
import { atomPaletteMode } from "../states/setting";
|
||||||
import { getVergeConfig, windowDrag, windowHide } from "../services/cmds";
|
import { getVergeConfig, windowDrag, windowHide } from "../services/cmds";
|
||||||
import LogoSvg from "../assets/image/logo.svg";
|
import LogoSvg from "../assets/image/logo.svg";
|
||||||
@@ -20,6 +22,7 @@ import SettingPage from "./setting";
|
|||||||
import ConnectionsPage from "./connections";
|
import ConnectionsPage from "./connections";
|
||||||
import LayoutItem from "../components/layout-item";
|
import LayoutItem from "../components/layout-item";
|
||||||
import Traffic from "../components/traffic";
|
import Traffic from "../components/traffic";
|
||||||
|
import UpdateDialog from "../components/update-dialog";
|
||||||
|
|
||||||
const routers = [
|
const routers = [
|
||||||
{
|
{
|
||||||
@@ -52,6 +55,12 @@ const routers = [
|
|||||||
const Layout = () => {
|
const Layout = () => {
|
||||||
const [mode, setMode] = useRecoilState(atomPaletteMode);
|
const [mode, setMode] = useRecoilState(atomPaletteMode);
|
||||||
const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig);
|
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(() => {
|
useEffect(() => {
|
||||||
setMode(vergeConfig?.theme_mode ?? "light");
|
setMode(vergeConfig?.theme_mode ?? "light");
|
||||||
@@ -92,6 +101,18 @@ const Layout = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{updateInfo?.shouldUpdate && (
|
||||||
|
<Button
|
||||||
|
color="error"
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
className="the-newbtn"
|
||||||
|
onClick={() => setDialogOpen(true)}
|
||||||
|
>
|
||||||
|
New
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<List className="the-menu">
|
<List className="the-menu">
|
||||||
@@ -126,13 +147,14 @@ const Layout = () => {
|
|||||||
|
|
||||||
<div className="the-content">
|
<div className="the-content">
|
||||||
<Routes>
|
<Routes>
|
||||||
{routers.map(({ link, ele: Ele }) => (
|
{routers.map(({ label, link, ele: Ele }) => (
|
||||||
<Route path={link} element={<Ele />} />
|
<Route key={label} path={link} element={<Ele />} />
|
||||||
))}
|
))}
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<UpdateDialog open={dialogOpen} onClose={() => setDialogOpen(false)} />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</SWRConfig>
|
</SWRConfig>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
import useSWR from "swr";
|
import useSWR, { useSWRConfig } from "swr";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { Box, List, Paper, Typography } from "@mui/material";
|
import { Box, List, Paper, Typography } from "@mui/material";
|
||||||
import { getProxies } from "../services/api";
|
import { getProxies } from "../services/api";
|
||||||
import ProxyGroup from "../components/proxy-group";
|
import ProxyGroup from "../components/proxy-group";
|
||||||
|
|
||||||
const ProxyPage = () => {
|
const ProxyPage = () => {
|
||||||
|
const { mutate } = useSWRConfig();
|
||||||
const { data: proxiesData } = useSWR("getProxies", getProxies);
|
const { data: proxiesData } = useSWR("getProxies", getProxies);
|
||||||
const { groups = [] } = proxiesData ?? {};
|
const { groups = [] } = proxiesData ?? {};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: 0.9, maxWidth: "850px", mx: "auto", mb: 2 }}>
|
<Box sx={{ width: 0.9, maxWidth: "850px", mx: "auto", mb: 2 }}>
|
||||||
<Typography variant="h4" component="h1" sx={{ py: 2 }}>
|
<Typography variant="h4" component="h1" sx={{ py: 2 }}>
|
||||||
|
|||||||
@@ -56,10 +56,6 @@ export async function patchClashConfig(payload: Partial<ApiType.ConfigData>) {
|
|||||||
return invoke<void>("patch_clash_config", { payload });
|
return invoke<void>("patch_clash_config", { payload });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setSysProxy(enable: boolean) {
|
|
||||||
return invoke<void>("set_sys_proxy", { enable });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getVergeConfig() {
|
export async function getVergeConfig() {
|
||||||
return invoke<CmdType.VergeConfig>("get_verge_config");
|
return invoke<CmdType.VergeConfig>("get_verge_config");
|
||||||
}
|
}
|
||||||
|
|||||||