Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b9cc90414 | ||
|
|
c1eb539a5c | ||
|
|
e1793f57ef | ||
|
|
dbd09a8743 | ||
|
|
0d189ca617 | ||
|
|
dc9bcc40ee | ||
|
|
4991f7ff39 | ||
|
|
a393b8b122 | ||
|
|
c73b354386 | ||
|
|
392ecee3ff | ||
|
|
bae721c49e | ||
|
|
4e806e21a6 | ||
|
|
ec0fdf83b2 | ||
|
|
cb94d8414f | ||
|
|
8890051c17 | ||
|
|
cf00c9476f |
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -53,9 +53,9 @@ jobs:
|
|||||||
yarn run check
|
yarn run check
|
||||||
|
|
||||||
- name: Tauri build
|
- name: Tauri build
|
||||||
uses: tauri-apps/tauri-action@b9ce5d7dc68082d21d30a60103b0ab8c5ddae3a1
|
uses: tauri-apps/tauri-action@0e558392ccadcb49bcc89e7df15a400e8f0c954d
|
||||||
# enable cache even though failed
|
# enable cache even though failed
|
||||||
continue-on-error: true
|
# continue-on-error: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
@@ -67,6 +67,10 @@ jobs:
|
|||||||
releaseDraft: false
|
releaseDraft: false
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
|
||||||
|
- name: Green zip bundle
|
||||||
|
run: |
|
||||||
|
yarn run green
|
||||||
|
|
||||||
release-update:
|
release-update:
|
||||||
needs: release
|
needs: release
|
||||||
runs-on: macos-11
|
runs-on: macos-11
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clash-verge",
|
"name": "clash-verge",
|
||||||
"version": "0.0.22",
|
"version": "0.0.23",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tauri dev",
|
"dev": "tauri dev",
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
"web:build": "tsc && vite build",
|
"web:build": "tsc && vite build",
|
||||||
"web:serve": "vite preview",
|
"web:serve": "vite preview",
|
||||||
"check": "node scripts/check.mjs",
|
"check": "node scripts/check.mjs",
|
||||||
|
"green": "node scripts/green.mjs",
|
||||||
"publish": "node scripts/publish.mjs",
|
"publish": "node scripts/publish.mjs",
|
||||||
"release": "node scripts/release.mjs",
|
"release": "node scripts/release.mjs",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
@@ -23,8 +24,10 @@
|
|||||||
"ahooks": "^3.1.13",
|
"ahooks": "^3.1.13",
|
||||||
"axios": "^0.26.0",
|
"axios": "^0.26.0",
|
||||||
"dayjs": "^1.10.8",
|
"dayjs": "^1.10.8",
|
||||||
|
"i18next": "^21.6.14",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-i18next": "^11.15.6",
|
||||||
"react-router-dom": "^6.2.2",
|
"react-router-dom": "^6.2.2",
|
||||||
"react-virtuoso": "^2.7.0",
|
"react-virtuoso": "^2.7.0",
|
||||||
"recoil": "^0.6.1",
|
"recoil": "^0.6.1",
|
||||||
|
|||||||
56
scripts/green.mjs
Normal file
56
scripts/green.mjs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import fs from "fs-extra";
|
||||||
|
import path from "path";
|
||||||
|
import AdmZip from "adm-zip";
|
||||||
|
import { createRequire } from "module";
|
||||||
|
import { getOctokit, context } from "@actions/github";
|
||||||
|
|
||||||
|
/// Script for ci
|
||||||
|
/// 打包绿色版/便携版 (only Windows)
|
||||||
|
async function resolveGreen() {
|
||||||
|
if (process.platform !== "win32") return;
|
||||||
|
|
||||||
|
const releaseDir = "./src-tauri/target/release";
|
||||||
|
|
||||||
|
if (!(await fs.pathExists(releaseDir))) {
|
||||||
|
throw new Error("could not found the release dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
const zip = new AdmZip();
|
||||||
|
|
||||||
|
zip.addLocalFile(path.join(releaseDir, "Clash Verge.exe"));
|
||||||
|
zip.addLocalFile(path.join(releaseDir, "clash.exe"));
|
||||||
|
zip.addLocalFolder(path.join(releaseDir, "resources"), "resources");
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const packageJson = require("../package.json");
|
||||||
|
const { version } = packageJson;
|
||||||
|
|
||||||
|
const zipFile = `Clash.Verge_${version}_x64_green.zip`;
|
||||||
|
zip.writeZip(zipFile);
|
||||||
|
|
||||||
|
console.log("[INFO]: create green zip successfully");
|
||||||
|
|
||||||
|
// push release assets
|
||||||
|
if (process.env.GITHUB_TOKEN === undefined) {
|
||||||
|
throw new Error("GITHUB_TOKEN is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = { owner: context.repo.owner, repo: context.repo.repo };
|
||||||
|
const github = getOctokit(process.env.GITHUB_TOKEN);
|
||||||
|
|
||||||
|
const { data: release } = await github.rest.repos.getReleaseByTag({
|
||||||
|
...options,
|
||||||
|
tag: `v${version}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(release.name);
|
||||||
|
|
||||||
|
await github.rest.repos.uploadReleaseAsset({
|
||||||
|
...options,
|
||||||
|
release_id: release.id,
|
||||||
|
name: zipFile,
|
||||||
|
data: zip.toBuffer(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveGreen().catch(console.error);
|
||||||
@@ -59,19 +59,27 @@ pub async fn update_profile(
|
|||||||
clash_state: State<'_, ClashState>,
|
clash_state: State<'_, ClashState>,
|
||||||
profiles_state: State<'_, ProfilesState>,
|
profiles_state: State<'_, ProfilesState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let url = {
|
let (url, opt) = {
|
||||||
// must release the lock here
|
// must release the lock here
|
||||||
let profiles = profiles_state.0.lock().unwrap();
|
let profiles = profiles_state.0.lock().unwrap();
|
||||||
let item = wrap_err!(profiles.get_item(&index))?;
|
let item = wrap_err!(profiles.get_item(&index))?;
|
||||||
|
|
||||||
|
// check the profile type
|
||||||
|
if let Some(typ) = item.itype.as_ref() {
|
||||||
|
if *typ != "remote" {
|
||||||
|
ret_err!(format!("could not update the `{typ}` profile"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if item.url.is_none() {
|
if item.url.is_none() {
|
||||||
ret_err!("failed to get the item url");
|
ret_err!("failed to get the item url");
|
||||||
}
|
}
|
||||||
|
|
||||||
item.url.clone().unwrap()
|
(item.url.clone().unwrap(), item.option.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
|
let fetch_opt = PrfOption::merge(opt, option);
|
||||||
|
let item = wrap_err!(PrfItem::from_url(&url, None, None, fetch_opt).await)?;
|
||||||
|
|
||||||
let mut profiles = profiles_state.0.lock().unwrap();
|
let mut profiles = profiles_state.0.lock().unwrap();
|
||||||
wrap_err!(profiles.update_item(index.clone(), item))?;
|
wrap_err!(profiles.update_item(index.clone(), item))?;
|
||||||
@@ -313,27 +321,54 @@ pub fn open_logs_dir() -> Result<(), String> {
|
|||||||
open_path_cmd(log_dir, "failed to open logs dir")
|
open_path_cmd(log_dir, "failed to open logs dir")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get open/explorer command
|
/// use the os default open command to open file or dir
|
||||||
fn open_path_cmd(dir: PathBuf, err_str: &str) -> Result<(), String> {
|
fn open_path_cmd(path: PathBuf, err_str: &str) -> Result<(), String> {
|
||||||
|
let result;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
|
|
||||||
if let Err(err) = Command::new("explorer")
|
result = Command::new("explorer")
|
||||||
.creation_flags(0x08000000)
|
.creation_flags(0x08000000)
|
||||||
.arg(dir)
|
.arg(&path)
|
||||||
.spawn()
|
.spawn();
|
||||||
{
|
}
|
||||||
log::error!("{err}");
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
result = Command::new("open").arg(&path).spawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
result = Command::new("xdg-open").arg(&path).spawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(child) => match child.wait_with_output() {
|
||||||
|
Ok(out) => {
|
||||||
|
if let Some(code) = out.status.code() {
|
||||||
|
if code != 0 {
|
||||||
|
log::error!(
|
||||||
|
"failed to open path {:?} for {} (code {code})",
|
||||||
|
&path,
|
||||||
|
String::from_utf8_lossy(&out.stderr),
|
||||||
|
);
|
||||||
|
return Err(err_str.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("failed to open path {:?} for {err}", &path);
|
||||||
|
return Err(err_str.into());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("failed to open path {:?} for {err}", &path);
|
||||||
return Err(err_str.into());
|
return Err(err_str.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
if let Err(err) = Command::new("open").arg(dir).spawn() {
|
|
||||||
log::error!("{err}");
|
|
||||||
return Err(err_str.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,18 @@ impl Clash {
|
|||||||
|
|
||||||
let server = match clash_config.get(&key_server) {
|
let server = match clash_config.get(&key_server) {
|
||||||
Some(value) => match value {
|
Some(value) => match value {
|
||||||
Value::String(val_str) => Some(val_str.clone()),
|
Value::String(val_str) => {
|
||||||
|
// `external-controller` could be
|
||||||
|
// "127.0.0.1:9090" or ":9090"
|
||||||
|
// Todo: maybe it could support single port
|
||||||
|
let server = val_str.clone();
|
||||||
|
let server = match server.starts_with(":") {
|
||||||
|
true => format!("127.0.0.1{server}"),
|
||||||
|
false => server,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(server)
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -253,19 +264,13 @@ impl Clash {
|
|||||||
/// activate the profile
|
/// activate the profile
|
||||||
/// generate a new profile to the temp_dir
|
/// generate a new profile to the temp_dir
|
||||||
/// then put the path to the clash core
|
/// then put the path to the clash core
|
||||||
fn _activate(info: ClashInfo, config: Mapping) -> Result<()> {
|
fn _activate(info: ClashInfo, config: Mapping, window: Option<Window>) -> Result<()> {
|
||||||
let temp_path = dirs::profiles_temp_path();
|
let temp_path = dirs::profiles_temp_path();
|
||||||
config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
|
config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
// `external-controller` could be
|
|
||||||
// "127.0.0.1:9090" or ":9090"
|
|
||||||
// Todo: maybe it could support single port
|
|
||||||
let server = info.server.unwrap();
|
let server = info.server.unwrap();
|
||||||
let server = match server.starts_with(":") {
|
let server = format!("http://{server}/configs");
|
||||||
true => format!("http://127.0.0.1{server}/configs"),
|
|
||||||
false => format!("http://{server}/configs"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert("Content-Type", "application/json".parse().unwrap());
|
headers.insert("Content-Type", "application/json".parse().unwrap());
|
||||||
@@ -289,6 +294,12 @@ impl Clash {
|
|||||||
if resp.status() != 204 {
|
if resp.status() != 204 {
|
||||||
log::error!("failed to activate clash for status \"{}\"", resp.status());
|
log::error!("failed to activate clash for status \"{}\"", resp.status());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emit the window to update something
|
||||||
|
if let Some(window) = window {
|
||||||
|
window.emit("verge://refresh-clash-config", "yes").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// do not retry
|
// do not retry
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -320,6 +331,7 @@ impl Clash {
|
|||||||
|
|
||||||
// generate the payload
|
// generate the payload
|
||||||
let payload = profiles.gen_enhanced(event_name.clone())?;
|
let payload = profiles.gen_enhanced(event_name.clone())?;
|
||||||
|
let window = self.window.clone();
|
||||||
|
|
||||||
win.once(&event_name, move |event| {
|
win.once(&event_name, move |event| {
|
||||||
if let Some(result) = event.payload() {
|
if let Some(result) = event.payload() {
|
||||||
@@ -328,7 +340,7 @@ impl Clash {
|
|||||||
if let Some(data) = result.data {
|
if let Some(data) = result.data {
|
||||||
// all of these can not be revised by script
|
// all of these can not be revised by script
|
||||||
// http/https/socks port should be under control
|
// http/https/socks port should be under control
|
||||||
let not_allow: Vec<Value> = vec![
|
let not_allow = vec![
|
||||||
"port",
|
"port",
|
||||||
"socks-port",
|
"socks-port",
|
||||||
"mixed-port",
|
"mixed-port",
|
||||||
@@ -337,23 +349,29 @@ impl Clash {
|
|||||||
"external-controller",
|
"external-controller",
|
||||||
"secret",
|
"secret",
|
||||||
"log-level",
|
"log-level",
|
||||||
]
|
];
|
||||||
.iter()
|
|
||||||
.map(|&i| Value::from(i))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for (key, value) in data.into_iter() {
|
for (key, value) in data.into_iter() {
|
||||||
if not_allow.iter().find(|&i| i == &key).is_none() {
|
key.as_str().map(|key_str| {
|
||||||
config.insert(key, value);
|
// change to lowercase
|
||||||
}
|
let mut key_str = String::from(key_str);
|
||||||
|
key_str.make_ascii_lowercase();
|
||||||
|
|
||||||
|
// filter
|
||||||
|
if !not_allow.contains(&&*key_str) {
|
||||||
|
config.insert(Value::String(key_str), value);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::_activate(info, config).unwrap();
|
log::info!("profile enhanced status {}", result.status);
|
||||||
|
|
||||||
|
Self::_activate(info, config, window).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("profile enhanced status {}", result.status);
|
if let Some(error) = result.error {
|
||||||
|
log::error!("{error}");
|
||||||
result.error.map(|error| log::error!("{error}"));
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -379,7 +397,7 @@ impl Clash {
|
|||||||
config.insert(key, value);
|
config.insert(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::_activate(info, config)?;
|
Self::_activate(info, config, self.window.clone())?;
|
||||||
self.activate_enhanced(profiles, delay)
|
self.activate_enhanced(profiles, delay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,31 @@ pub struct PrfOption {
|
|||||||
pub with_proxy: Option<bool>,
|
pub with_proxy: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PrfOption {
|
||||||
|
pub fn merge(one: Option<Self>, other: Option<Self>) -> Option<Self> {
|
||||||
|
if one.is_some() && other.is_some() {
|
||||||
|
let mut one = one.unwrap();
|
||||||
|
let other = other.unwrap();
|
||||||
|
|
||||||
|
if let Some(val) = other.user_agent {
|
||||||
|
one.user_agent = Some(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(val) = other.with_proxy {
|
||||||
|
one.with_proxy = Some(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(one);
|
||||||
|
}
|
||||||
|
|
||||||
|
if one.is_none() {
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
return one;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for PrfItem {
|
impl Default for PrfItem {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PrfItem {
|
PrfItem {
|
||||||
@@ -414,9 +439,7 @@ impl Profiles {
|
|||||||
patch!(each, item, selected);
|
patch!(each, item, selected);
|
||||||
patch!(each, item, extra);
|
patch!(each, item, extra);
|
||||||
patch!(each, item, updated);
|
patch!(each, item, updated);
|
||||||
|
patch!(each, item, option);
|
||||||
// can be removed
|
|
||||||
each.option = item.option;
|
|
||||||
|
|
||||||
self.items = Some(items);
|
self.items = Some(items);
|
||||||
return self.save_file();
|
return self.save_file();
|
||||||
@@ -543,13 +566,17 @@ impl Profiles {
|
|||||||
"rules",
|
"rules",
|
||||||
];
|
];
|
||||||
|
|
||||||
valid_keys.iter().for_each(|key| {
|
for (key, value) in def_config.into_iter() {
|
||||||
let key = Value::String(key.to_string());
|
key.as_str().map(|key_str| {
|
||||||
if def_config.contains_key(&key) {
|
// change to lowercase
|
||||||
let value = def_config[&key].clone();
|
let mut key_str = String::from(key_str);
|
||||||
new_config.insert(key, value);
|
key_str.make_ascii_lowercase();
|
||||||
}
|
|
||||||
});
|
if valid_keys.contains(&&*key_str) {
|
||||||
|
new_config.insert(Value::String(key_str), value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(new_config);
|
return Ok(new_config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
use crate::log_if_err;
|
||||||
use crate::{
|
use crate::{
|
||||||
core::Clash,
|
core::Clash,
|
||||||
log_if_err,
|
|
||||||
utils::{config, dirs, sysopt::SysProxyConfig},
|
utils::{config, dirs, sysopt::SysProxyConfig},
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
@@ -12,6 +12,9 @@ use tauri::{async_runtime::Mutex, utils::platform::current_exe};
|
|||||||
/// ### `verge.yaml` schema
|
/// ### `verge.yaml` schema
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct VergeConfig {
|
pub struct VergeConfig {
|
||||||
|
// i18n
|
||||||
|
pub language: Option<String>,
|
||||||
|
|
||||||
/// `light` or `dark`
|
/// `light` or `dark`
|
||||||
pub theme_mode: Option<String>,
|
pub theme_mode: Option<String>,
|
||||||
|
|
||||||
@@ -188,6 +191,9 @@ impl Verge {
|
|||||||
/// so call the save_file at the end is savely
|
/// so call the save_file at the end is savely
|
||||||
pub fn patch_config(&mut self, patch: VergeConfig) -> Result<()> {
|
pub fn patch_config(&mut self, patch: VergeConfig) -> Result<()> {
|
||||||
// only change it
|
// only change it
|
||||||
|
if patch.language.is_some() {
|
||||||
|
self.config.language = patch.language;
|
||||||
|
}
|
||||||
if patch.theme_mode.is_some() {
|
if patch.theme_mode.is_some() {
|
||||||
self.config.theme_mode = patch.theme_mode;
|
self.config.theme_mode = patch.theme_mode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,13 +45,8 @@ fn main() -> std::io::Result<()> {
|
|||||||
let profiles_state = app_handle.state::<states::ProfilesState>();
|
let profiles_state = app_handle.state::<states::ProfilesState>();
|
||||||
let mut clash = clash_state.0.lock().unwrap();
|
let mut clash = clash_state.0.lock().unwrap();
|
||||||
let mut profiles = profiles_state.0.lock().unwrap();
|
let mut profiles = profiles_state.0.lock().unwrap();
|
||||||
match clash.restart_sidecar(&mut profiles) {
|
|
||||||
Ok(_) => {
|
crate::log_if_err!(clash.restart_sidecar(&mut profiles));
|
||||||
let window = app_handle.get_window("main").unwrap();
|
|
||||||
window.emit("restart_clash", "yes").unwrap();
|
|
||||||
}
|
|
||||||
Err(err) => log::error!("{}", err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"quit" => {
|
"quit" => {
|
||||||
resolve::resolve_reset(app_handle);
|
resolve::resolve_reset(app_handle);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ macro_rules! wrap_err {
|
|||||||
/// return the string literal error
|
/// return the string literal error
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! ret_err {
|
macro_rules! ret_err {
|
||||||
($str: literal) => {
|
($str: expr) => {
|
||||||
return Err($str.into())
|
return Err($str.into())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ items: ~
|
|||||||
/// template for `verge.yaml`
|
/// template for `verge.yaml`
|
||||||
pub const VERGE_CONFIG: &[u8] = b"# Defaulf Config For Clash Verge
|
pub const VERGE_CONFIG: &[u8] = b"# Defaulf Config For Clash Verge
|
||||||
|
|
||||||
|
language: en
|
||||||
theme_mode: light
|
theme_mode: light
|
||||||
theme_blur: false
|
theme_blur: false
|
||||||
traffic_graph: true
|
traffic_graph: true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Clash Verge",
|
"productName": "Clash Verge",
|
||||||
"version": "0.0.22"
|
"version": "0.0.23"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"distDir": "../dist",
|
"distDir": "../dist",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import useLogSetup from "./use-log-setup";
|
|||||||
import useTrafficGraph from "./use-traffic-graph";
|
import useTrafficGraph from "./use-traffic-graph";
|
||||||
import parseTraffic from "../../utils/parse-traffic";
|
import parseTraffic from "../../utils/parse-traffic";
|
||||||
|
|
||||||
|
// setup the traffic
|
||||||
const LayoutTraffic = () => {
|
const LayoutTraffic = () => {
|
||||||
const portValue = useRecoilValue(atomClashPort);
|
const portValue = useRecoilValue(atomClashPort);
|
||||||
const [traffic, setTraffic] = useState({ up: 0, down: 0 });
|
const [traffic, setTraffic] = useState({ up: 0, down: 0 });
|
||||||
@@ -26,12 +27,14 @@ const LayoutTraffic = () => {
|
|||||||
useLogSetup();
|
useLogSetup();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let unlisten: () => void = null!;
|
|
||||||
|
|
||||||
// should reconnect the traffic ws
|
// should reconnect the traffic ws
|
||||||
listen("restart_clash", () => setRefresh({})).then((fn) => (unlisten = fn));
|
const unlisten = listen("verge://refresh-clash-config", () =>
|
||||||
|
setRefresh({})
|
||||||
|
);
|
||||||
|
|
||||||
return () => unlisten?.();
|
return () => {
|
||||||
|
unlisten.then((fn) => fn());
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useSetRecoilState } from "recoil";
|
import { useSetRecoilState } from "recoil";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { ApiType } from "../../services/types";
|
import { ApiType } from "../../services/types";
|
||||||
@@ -10,11 +10,11 @@ const MAX_LOG_NUM = 1000;
|
|||||||
|
|
||||||
// setup the log websocket
|
// setup the log websocket
|
||||||
export default function useLogSetup() {
|
export default function useLogSetup() {
|
||||||
|
const [refresh, setRefresh] = useState({});
|
||||||
const setLogData = useSetRecoilState(atomLogData);
|
const setLogData = useSetRecoilState(atomLogData);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let ws: WebSocket = null!;
|
let ws: WebSocket = null!;
|
||||||
let unlisten: () => void = null!;
|
|
||||||
|
|
||||||
const handler = (event: MessageEvent<any>) => {
|
const handler = (event: MessageEvent<any>) => {
|
||||||
const data = JSON.parse(event.data) as ApiType.LogItem;
|
const data = JSON.parse(event.data) as ApiType.LogItem;
|
||||||
@@ -25,25 +25,19 @@ export default function useLogSetup() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
(async () => {
|
getInfomation().then((info) => {
|
||||||
const { server = "", secret = "" } = await getInfomation();
|
const { server = "", secret = "" } = info;
|
||||||
|
|
||||||
ws = new WebSocket(`ws://${server}/logs?token=${secret}`);
|
ws = new WebSocket(`ws://${server}/logs?token=${secret}`);
|
||||||
ws.addEventListener("message", handler);
|
ws.addEventListener("message", handler);
|
||||||
|
});
|
||||||
|
|
||||||
// reconnect the websocket
|
const unlisten = listen("verge://refresh-clash-config", () =>
|
||||||
unlisten = await listen("restart_clash", async () => {
|
setRefresh({})
|
||||||
const { server = "", secret = "" } = await getInfomation();
|
);
|
||||||
|
|
||||||
ws?.close();
|
|
||||||
ws = new WebSocket(`ws://${server}/logs?token=${secret}`);
|
|
||||||
ws.addEventListener("message", handler);
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
ws?.close();
|
ws?.close();
|
||||||
unlisten?.();
|
unlisten?.then((fn) => fn());
|
||||||
};
|
};
|
||||||
}, []);
|
}, [refresh]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const ProfileEdit = (props: Props) => {
|
|||||||
try {
|
try {
|
||||||
const { uid } = itemData;
|
const { uid } = itemData;
|
||||||
const { name, desc, url } = form;
|
const { name, desc, url } = form;
|
||||||
const option_ = showOpt ? option : undefined;
|
const option_ = itemData.type === "remote" ? option : undefined;
|
||||||
|
|
||||||
if (itemData.type === "remote" && !url) {
|
if (itemData.type === "remote" && !url) {
|
||||||
throw new Error("Remote URL should not be null");
|
throw new Error("Remote URL should not be null");
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const ProfileNew = (props: Props) => {
|
|||||||
throw new Error("The URL should not be null");
|
throw new Error("The URL should not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
const option_ = showOpt ? option : undefined;
|
const option_ = form.type === "remote" ? option : undefined;
|
||||||
await createProfile({ ...form, name, option: option_ });
|
await createProfile({ ...form, name, option: option_ });
|
||||||
setForm({ type: "remote", name: "", desc: "", url: "" });
|
setForm({ type: "remote", name: "", desc: "", url: "" });
|
||||||
setOption({ user_agent: "" });
|
setOption({ user_agent: "" });
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import useSWR, { useSWRConfig } from "swr";
|
import useSWR, { useSWRConfig } from "swr";
|
||||||
import { useSetRecoilState } from "recoil";
|
import { useSetRecoilState } from "recoil";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
ListItemText,
|
ListItemText,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -21,15 +22,16 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SettingClash = ({ onError }: Props) => {
|
const SettingClash = ({ onError }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
|
const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
|
||||||
const { data: versionData } = useSWR("getVersion", getVersion);
|
const { data: versionData } = useSWR("getVersion", getVersion);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ipv6 = false,
|
ipv6,
|
||||||
"allow-lan": allowLan = false,
|
"allow-lan": allowLan,
|
||||||
"log-level": logLevel = "silent",
|
"log-level": logLevel,
|
||||||
"mixed-port": mixedPort = 0,
|
"mixed-port": mixedPort,
|
||||||
} = clashConfig ?? {};
|
} = clashConfig ?? {};
|
||||||
|
|
||||||
const setGlobalClashPort = useSetRecoilState(atomClashPort);
|
const setGlobalClashPort = useSetRecoilState(atomClashPort);
|
||||||
@@ -64,11 +66,11 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
: versionData?.version || "-";
|
: versionData?.version || "-";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingList title="Clash Setting">
|
<SettingList title={t("Clash Setting")}>
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Allow Lan" />
|
<ListItemText primary={t("Allow Lan")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={allowLan}
|
value={allowLan ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
@@ -80,9 +82,9 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="IPv6" />
|
<ListItemText primary={t("IPv6")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={ipv6}
|
value={ipv6 ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
@@ -94,9 +96,9 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Log Level" />
|
<ListItemText primary={t("Log Level")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={logLevel}
|
value={logLevel ?? "info"}
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={(e: any) => e.target.value}
|
onFormat={(e: any) => e.target.value}
|
||||||
onChange={(e) => onChangeData({ "log-level": e })}
|
onChange={(e) => onChangeData({ "log-level": e })}
|
||||||
@@ -113,9 +115,9 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Mixed Port" />
|
<ListItemText primary={t("Mixed Port")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={mixedPort!}
|
value={mixedPort ?? 0}
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={(e: any) => +e.target.value?.replace(/\D+/, "")}
|
onFormat={(e: any) => +e.target.value?.replace(/\D+/, "")}
|
||||||
onChange={(e) => onChangeData({ "mixed-port": e })}
|
onChange={(e) => onChangeData({ "mixed-port": e })}
|
||||||
@@ -127,7 +129,7 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Clash core" />
|
<ListItemText primary={t("Clash core")} />
|
||||||
<Typography sx={{ py: 1 }}>{clashVer}</Typography>
|
<Typography sx={{ py: 1 }}>{clashVer}</Typography>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
</SettingList>
|
</SettingList>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import useSWR, { useSWRConfig } from "swr";
|
import useSWR, { useSWRConfig } from "swr";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Box, ListItemText, Switch, TextField } from "@mui/material";
|
import { Box, ListItemText, Switch, TextField } from "@mui/material";
|
||||||
import { getVergeConfig, patchVergeConfig } from "../../services/cmds";
|
import { getVergeConfig, patchVergeConfig } from "../../services/cmds";
|
||||||
import { SettingList, SettingItem } from "./setting";
|
import { SettingList, SettingItem } from "./setting";
|
||||||
@@ -11,15 +12,16 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SettingSystem = ({ onError }: Props) => {
|
const SettingSystem = ({ onError }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig);
|
const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
enable_tun_mode = false,
|
enable_tun_mode,
|
||||||
enable_auto_launch = false,
|
enable_auto_launch,
|
||||||
enable_system_proxy = false,
|
enable_system_proxy,
|
||||||
system_proxy_bypass = "",
|
system_proxy_bypass,
|
||||||
enable_proxy_guard = false,
|
enable_proxy_guard,
|
||||||
} = vergeConfig ?? {};
|
} = vergeConfig ?? {};
|
||||||
|
|
||||||
const onSwitchFormat = (_e: any, value: boolean) => value;
|
const onSwitchFormat = (_e: any, value: boolean) => value;
|
||||||
@@ -28,11 +30,11 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingList title="System Setting">
|
<SettingList title={t("System Setting")}>
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Tun Mode" />
|
<ListItemText primary={t("Tun Mode")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={enable_tun_mode}
|
value={enable_tun_mode ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
@@ -44,9 +46,9 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Auto Launch" />
|
<ListItemText primary={t("Auto Launch")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={enable_auto_launch}
|
value={enable_auto_launch ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
@@ -61,13 +63,13 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
System Proxy
|
{t("System Proxy")}
|
||||||
<SysproxyTooltip />
|
<SysproxyTooltip />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<GuardState
|
<GuardState
|
||||||
value={enable_system_proxy}
|
value={enable_system_proxy ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
@@ -83,9 +85,9 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
|
|
||||||
{enable_system_proxy && (
|
{enable_system_proxy && (
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Proxy Guard" />
|
<ListItemText primary={t("Proxy Guard")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={enable_proxy_guard}
|
value={enable_proxy_guard ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
@@ -99,7 +101,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
|
|
||||||
{enable_system_proxy && (
|
{enable_system_proxy && (
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Proxy Bypass" />
|
<ListItemText primary={t("Proxy Bypass")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={system_proxy_bypass ?? ""}
|
value={system_proxy_bypass ?? ""}
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import useSWR, { useSWRConfig } from "swr";
|
import useSWR, { useSWRConfig } from "swr";
|
||||||
import { IconButton, ListItemText, Switch, Typography } from "@mui/material";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
ListItemText,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
import {
|
import {
|
||||||
getVergeConfig,
|
getVergeConfig,
|
||||||
openAppDir,
|
openAppDir,
|
||||||
@@ -18,11 +26,11 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SettingVerge = ({ onError }: Props) => {
|
const SettingVerge = ({ onError }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig);
|
const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig);
|
||||||
|
|
||||||
const { theme_mode = "light", theme_blur = false, traffic_graph } =
|
const { theme_mode, theme_blur, traffic_graph, language } = 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>) => {
|
||||||
@@ -30,9 +38,9 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingList title="Verge Setting">
|
<SettingList title={t("Verge Setting")}>
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Theme Mode" />
|
<ListItemText primary={t("Theme Mode")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={theme_mode === "dark"}
|
value={theme_mode === "dark"}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
@@ -48,9 +56,9 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Theme Blur" />
|
<ListItemText primary={t("Theme Blur")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={theme_blur}
|
value={theme_blur ?? false}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
onCatch={onError}
|
onCatch={onError}
|
||||||
onFormat={onSwitchFormat}
|
onFormat={onSwitchFormat}
|
||||||
@@ -62,7 +70,7 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Traffic Graph" />
|
<ListItemText primary={t("Traffic Graph")} />
|
||||||
<GuardState
|
<GuardState
|
||||||
value={traffic_graph ?? true}
|
value={traffic_graph ?? true}
|
||||||
valueProps="checked"
|
valueProps="checked"
|
||||||
@@ -76,21 +84,37 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Open App Dir" />
|
<ListItemText primary={t("Language")} />
|
||||||
|
<GuardState
|
||||||
|
value={language ?? "en"}
|
||||||
|
onCatch={onError}
|
||||||
|
onFormat={(e: any) => e.target.value}
|
||||||
|
onChange={(e) => onChangeData({ language: e })}
|
||||||
|
onGuard={(e) => patchVergeConfig({ language: e })}
|
||||||
|
>
|
||||||
|
<Select size="small" sx={{ width: 100 }}>
|
||||||
|
<MenuItem value="zh">中文</MenuItem>
|
||||||
|
<MenuItem value="en">English</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</GuardState>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
|
<SettingItem>
|
||||||
|
<ListItemText primary={t("Open App Dir")} />
|
||||||
<IconButton color="inherit" size="small" onClick={openAppDir}>
|
<IconButton color="inherit" size="small" onClick={openAppDir}>
|
||||||
<ArrowForward />
|
<ArrowForward />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Open Logs Dir" />
|
<ListItemText primary={t("Open Logs Dir")} />
|
||||||
<IconButton color="inherit" size="small" onClick={openLogsDir}>
|
<IconButton color="inherit" size="small" onClick={openLogsDir}>
|
||||||
<ArrowForward />
|
<ArrowForward />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<ListItemText primary="Version" />
|
<ListItemText primary={t("Version")} />
|
||||||
<Typography sx={{ py: "6px" }}>v{version}</Typography>
|
<Typography sx={{ py: "6px" }}>v{version}</Typography>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
</SettingList>
|
</SettingList>
|
||||||
|
|||||||
42
src/locales/en.json
Normal file
42
src/locales/en.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"Label-Proxies": "Proxies",
|
||||||
|
"Label-Profiles": "Profiles",
|
||||||
|
"Label-Connections": "Connections",
|
||||||
|
"Label-Logs": "Logs",
|
||||||
|
"Label-Settings": "Settings",
|
||||||
|
|
||||||
|
"Connections": "Connections",
|
||||||
|
"Logs": "Logs",
|
||||||
|
"Clear": "Clear",
|
||||||
|
"Proxies": "Proxies",
|
||||||
|
"Proxy Groups": "Proxy Groups",
|
||||||
|
"rule": "rule",
|
||||||
|
"global": "global",
|
||||||
|
"direct": "direct",
|
||||||
|
"Profiles": "Profiles",
|
||||||
|
"Profile URL": "Profile URL",
|
||||||
|
"Import": "Import",
|
||||||
|
"New": "New",
|
||||||
|
|
||||||
|
"Settings": "Settings",
|
||||||
|
"Clash Setting": "Clash Setting",
|
||||||
|
"System Setting": "System Setting",
|
||||||
|
"Verge Setting": "Verge Setting",
|
||||||
|
"Allow Lan": "Allow Lan",
|
||||||
|
"IPv6": "IPv6",
|
||||||
|
"Log Level": "Log Level",
|
||||||
|
"Mixed Port": "Mixed Port",
|
||||||
|
"Clash core": "Clash core",
|
||||||
|
"Tun Mode": "Tun Mode",
|
||||||
|
"Auto Launch": "Auto Launch",
|
||||||
|
"System Proxy": "System Proxy",
|
||||||
|
"Proxy Guard": "Proxy Guard",
|
||||||
|
"Proxy Bypass": "Proxy Bypass",
|
||||||
|
"Theme Mode": "Theme Mode",
|
||||||
|
"Theme Blur": "Theme Blur",
|
||||||
|
"Traffic Graph": "Traffic Graph",
|
||||||
|
"Language": "Language",
|
||||||
|
"Open App Dir": "Open App Dir",
|
||||||
|
"Open Logs Dir": "Open Logs Dir",
|
||||||
|
"Version": "Version"
|
||||||
|
}
|
||||||
42
src/locales/zh.json
Normal file
42
src/locales/zh.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"Label-Proxies": "代 理",
|
||||||
|
"Label-Profiles": "配 置",
|
||||||
|
"Label-Connections": "连 接",
|
||||||
|
"Label-Logs": "日 志",
|
||||||
|
"Label-Settings": "设 置",
|
||||||
|
|
||||||
|
"Connections": "连接",
|
||||||
|
"Logs": "日志",
|
||||||
|
"Clear": "清除",
|
||||||
|
"Proxies": "代理",
|
||||||
|
"Proxy Groups": "代理组",
|
||||||
|
"rule": "规则",
|
||||||
|
"global": "全局",
|
||||||
|
"direct": "直连",
|
||||||
|
"Profiles": "配置",
|
||||||
|
"Profile URL": "配置文件链接",
|
||||||
|
"Import": "导入",
|
||||||
|
"New": "新建",
|
||||||
|
|
||||||
|
"Settings": "设置",
|
||||||
|
"Clash Setting": "Clash 设置",
|
||||||
|
"System Setting": "系统设置",
|
||||||
|
"Verge Setting": "Verge 设置",
|
||||||
|
"Allow Lan": "局域网连接",
|
||||||
|
"IPv6": "IPv6",
|
||||||
|
"Log Level": "日志等级",
|
||||||
|
"Mixed Port": "端口设置",
|
||||||
|
"Clash core": "Clash 内核",
|
||||||
|
"Tun Mode": "Tun 模式",
|
||||||
|
"Auto Launch": "开机自启",
|
||||||
|
"System Proxy": "系统代理",
|
||||||
|
"Proxy Guard": "系统代理守卫",
|
||||||
|
"Proxy Bypass": "Proxy Bypass",
|
||||||
|
"Theme Mode": "暗夜模式",
|
||||||
|
"Theme Blur": "背景模糊",
|
||||||
|
"Traffic Graph": "流量图显",
|
||||||
|
"Language": "语言设置",
|
||||||
|
"Open App Dir": "应用目录",
|
||||||
|
"Open Logs Dir": "日志目录",
|
||||||
|
"Version": "版本"
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { RecoilRoot } from "recoil";
|
|||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import Layout from "./pages/_layout";
|
import Layout from "./pages/_layout";
|
||||||
import enhance from "./services/enhance";
|
import enhance from "./services/enhance";
|
||||||
|
import "./services/i18n";
|
||||||
|
|
||||||
enhance.setup();
|
enhance.setup();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import i18next from "i18next";
|
||||||
import useSWR, { SWRConfig, useSWRConfig } from "swr";
|
import useSWR, { SWRConfig, useSWRConfig } from "swr";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Route, Routes } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
import { alpha, createTheme, List, Paper, ThemeProvider } from "@mui/material";
|
import { alpha, createTheme, List, Paper, ThemeProvider } from "@mui/material";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
@@ -16,6 +18,7 @@ import UpdateButton from "../components/layout/update-button";
|
|||||||
const isMacos = navigator.userAgent.includes("Mac OS X");
|
const isMacos = navigator.userAgent.includes("Mac OS X");
|
||||||
|
|
||||||
const Layout = () => {
|
const Layout = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
const { data } = useSWR("getVergeConfig", getVergeConfig);
|
const { data } = useSWR("getVergeConfig", getVergeConfig);
|
||||||
|
|
||||||
@@ -27,16 +30,20 @@ const Layout = () => {
|
|||||||
if (e.key === "Escape") appWindow.hide();
|
if (e.key === "Escape") appWindow.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
listen("restart_clash", async () => {
|
listen("verge://refresh-clash-config", async () => {
|
||||||
// the clash info may be updated
|
// the clash info may be updated
|
||||||
await getAxios(true);
|
await getAxios(true);
|
||||||
// make sure that the clash is ok
|
mutate("getProxies");
|
||||||
setTimeout(() => mutate("getProxies"), 1000);
|
|
||||||
setTimeout(() => mutate("getProxies"), 2000);
|
|
||||||
mutate("getClashConfig");
|
mutate("getClashConfig");
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.language) {
|
||||||
|
i18next.changeLanguage(data.language);
|
||||||
|
}
|
||||||
|
}, [data?.language]);
|
||||||
|
|
||||||
const theme = useMemo(() => {
|
const theme = useMemo(() => {
|
||||||
// const background = mode === "light" ? "#f5f5f5" : "#000";
|
// const background = mode === "light" ? "#f5f5f5" : "#000";
|
||||||
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
|
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
|
||||||
@@ -87,7 +94,7 @@ const Layout = () => {
|
|||||||
<List className="the-menu" data-windrag>
|
<List className="the-menu" data-windrag>
|
||||||
{routers.map((router) => (
|
{routers.map((router) => (
|
||||||
<LayoutItem key={router.label} to={router.link}>
|
<LayoutItem key={router.label} to={router.link}>
|
||||||
{router.label}
|
{t(router.label)}
|
||||||
</LayoutItem>
|
</LayoutItem>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
|
|||||||
@@ -6,27 +6,27 @@ import ConnectionsPage from "./connections";
|
|||||||
|
|
||||||
export const routers = [
|
export const routers = [
|
||||||
{
|
{
|
||||||
label: "Proxies",
|
label: "Label-Proxies",
|
||||||
link: "/",
|
link: "/",
|
||||||
ele: ProxiesPage,
|
ele: ProxiesPage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Profiles",
|
label: "Label-Profiles",
|
||||||
link: "/profile",
|
link: "/profile",
|
||||||
ele: ProfilesPage,
|
ele: ProfilesPage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Connections",
|
label: "Label-Connections",
|
||||||
link: "/connections",
|
link: "/connections",
|
||||||
ele: ConnectionsPage,
|
ele: ConnectionsPage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Logs",
|
label: "Label-Logs",
|
||||||
link: "/logs",
|
link: "/logs",
|
||||||
ele: LogsPage,
|
ele: LogsPage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Settings",
|
label: "Label-Settings",
|
||||||
link: "/settings",
|
link: "/settings",
|
||||||
ele: SettingsPage,
|
ele: SettingsPage,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Paper } from "@mui/material";
|
import { Paper } from "@mui/material";
|
||||||
import { Virtuoso } from "react-virtuoso";
|
import { Virtuoso } from "react-virtuoso";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { ApiType } from "../services/types";
|
import { ApiType } from "../services/types";
|
||||||
import { getInfomation } from "../services/api";
|
import { getInfomation } from "../services/api";
|
||||||
import BasePage from "../components/base/base-page";
|
import BasePage from "../components/base/base-page";
|
||||||
@@ -8,6 +9,8 @@ import ConnectionItem from "../components/connection/connection-item";
|
|||||||
|
|
||||||
const ConnectionsPage = () => {
|
const ConnectionsPage = () => {
|
||||||
const initConn = { uploadTotal: 0, downloadTotal: 0, connections: [] };
|
const initConn = { uploadTotal: 0, downloadTotal: 0, connections: [] };
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
const [conn, setConn] = useState<ApiType.Connections>(initConn);
|
const [conn, setConn] = useState<ApiType.Connections>(initConn);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -27,7 +30,7 @@ const ConnectionsPage = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage title="Connections" contentStyle={{ height: "100%" }}>
|
<BasePage title={t("Connections")} contentStyle={{ height: "100%" }}>
|
||||||
<Paper sx={{ boxShadow: 2, height: "100%" }}>
|
<Paper sx={{ boxShadow: 2, height: "100%" }}>
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
data={conn.connections}
|
data={conn.connections}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { useRecoilState } from "recoil";
|
import { useRecoilState } from "recoil";
|
||||||
import { Button, Paper } from "@mui/material";
|
import { Button, Paper } from "@mui/material";
|
||||||
import { Virtuoso } from "react-virtuoso";
|
import { Virtuoso } from "react-virtuoso";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { atomLogData } from "../services/states";
|
import { atomLogData } from "../services/states";
|
||||||
import BasePage from "../components/base/base-page";
|
import BasePage from "../components/base/base-page";
|
||||||
import LogItem from "../components/log/log-item";
|
import LogItem from "../components/log/log-item";
|
||||||
|
|
||||||
const LogPage = () => {
|
const LogPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [logData, setLogData] = useRecoilState(atomLogData);
|
const [logData, setLogData] = useRecoilState(atomLogData);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage
|
<BasePage
|
||||||
title="Logs"
|
title={t("Logs")}
|
||||||
contentStyle={{ height: "100%" }}
|
contentStyle={{ height: "100%" }}
|
||||||
header={
|
header={
|
||||||
<Button
|
<Button
|
||||||
@@ -19,7 +21,7 @@ const LogPage = () => {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => setLogData([])}
|
onClick={() => setLogData([])}
|
||||||
>
|
>
|
||||||
Clear
|
{t("Clear")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import useSWR, { useSWRConfig } from "swr";
|
|||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { Box, Button, Grid, TextField } from "@mui/material";
|
import { Box, Button, Grid, TextField } from "@mui/material";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
getProfiles,
|
getProfiles,
|
||||||
patchProfile,
|
patchProfile,
|
||||||
@@ -19,6 +20,7 @@ import ProfileItem from "../components/profile/profile-item";
|
|||||||
import ProfileMore from "../components/profile/profile-more";
|
import ProfileMore from "../components/profile/profile-more";
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
|
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
@@ -175,12 +177,12 @@ const ProfilePage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage title="Profiles">
|
<BasePage title={t("Profiles")}>
|
||||||
<Box sx={{ display: "flex", mb: 2.5 }}>
|
<Box sx={{ display: "flex", mb: 2.5 }}>
|
||||||
<TextField
|
<TextField
|
||||||
id="clas_verge_profile_url"
|
id="clas_verge_profile_url"
|
||||||
name="profile_url"
|
name="profile_url"
|
||||||
label="Profile URL"
|
label={t("Profile URL")}
|
||||||
size="small"
|
size="small"
|
||||||
fullWidth
|
fullWidth
|
||||||
value={url}
|
value={url}
|
||||||
@@ -193,10 +195,10 @@ const ProfilePage = () => {
|
|||||||
onClick={onImport}
|
onClick={onImport}
|
||||||
sx={{ mr: 1 }}
|
sx={{ mr: 1 }}
|
||||||
>
|
>
|
||||||
Import
|
{t("Import")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" onClick={() => setDialogOpen(true)}>
|
<Button variant="contained" onClick={() => setDialogOpen(true)}>
|
||||||
New
|
{t("New")}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import useSWR, { useSWRConfig } from "swr";
|
import useSWR, { useSWRConfig } from "swr";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button, ButtonGroup, List, Paper } from "@mui/material";
|
import { Button, ButtonGroup, List, Paper } from "@mui/material";
|
||||||
import { getClashConfig, updateConfigs } from "../services/api";
|
import { getClashConfig, updateConfigs } from "../services/api";
|
||||||
import { patchClashConfig } from "../services/cmds";
|
import { patchClashConfig } from "../services/cmds";
|
||||||
@@ -10,6 +11,7 @@ import ProxyGroup from "../components/proxy/proxy-group";
|
|||||||
import ProxyGlobal from "../components/proxy/proxy-global";
|
import ProxyGlobal from "../components/proxy/proxy-global";
|
||||||
|
|
||||||
const ProxyPage = () => {
|
const ProxyPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
const { data: proxiesData } = useSWR("getProxies", getProxies);
|
const { data: proxiesData } = useSWR("getProxies", getProxies);
|
||||||
const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
|
const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
|
||||||
@@ -45,7 +47,7 @@ const ProxyPage = () => {
|
|||||||
return (
|
return (
|
||||||
<BasePage
|
<BasePage
|
||||||
contentStyle={pageStyle}
|
contentStyle={pageStyle}
|
||||||
title={showGroup ? "Proxy Groups" : "Proxies"}
|
title={showGroup ? t("Proxy Groups") : t("Proxies")}
|
||||||
header={
|
header={
|
||||||
<ButtonGroup size="small">
|
<ButtonGroup size="small">
|
||||||
{modeList.map((mode) => (
|
{modeList.map((mode) => (
|
||||||
@@ -55,7 +57,7 @@ const ProxyPage = () => {
|
|||||||
onClick={() => onChangeMode(mode)}
|
onClick={() => onChangeMode(mode)}
|
||||||
sx={{ textTransform: "capitalize" }}
|
sx={{ textTransform: "capitalize" }}
|
||||||
>
|
>
|
||||||
{mode}
|
{t(mode)}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Paper } from "@mui/material";
|
import { Paper } from "@mui/material";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import Notice from "../components/base/base-notice";
|
import Notice from "../components/base/base-notice";
|
||||||
import BasePage from "../components/base/base-page";
|
import BasePage from "../components/base/base-page";
|
||||||
import SettingVerge from "../components/setting/setting-verge";
|
import SettingVerge from "../components/setting/setting-verge";
|
||||||
@@ -6,12 +7,14 @@ import SettingClash from "../components/setting/setting-clash";
|
|||||||
import SettingSystem from "../components/setting/setting-system";
|
import SettingSystem from "../components/setting/setting-system";
|
||||||
|
|
||||||
const SettingPage = () => {
|
const SettingPage = () => {
|
||||||
const onError = (error: any) => {
|
const { t } = useTranslation();
|
||||||
error && Notice.error(error.toString());
|
|
||||||
|
const onError = (err: any) => {
|
||||||
|
Notice.error(err?.message || err.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage title="Settings">
|
<BasePage title={t("Settings")}>
|
||||||
<Paper sx={{ borderRadius: 1, boxShadow: 2, mb: 3 }}>
|
<Paper sx={{ borderRadius: 1, boxShadow: 2, mb: 3 }}>
|
||||||
<SettingClash onError={onError} />
|
<SettingClash onError={onError} />
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { invoke } from "@tauri-apps/api/tauri";
|
import { invoke } from "@tauri-apps/api/tauri";
|
||||||
import { ApiType, CmdType } from "./types";
|
import { ApiType, CmdType } from "./types";
|
||||||
|
import Notice from "../components/base/base-notice";
|
||||||
|
|
||||||
export async function getProfiles() {
|
export async function getProfiles() {
|
||||||
return invoke<CmdType.ProfilesConfig>("get_profiles");
|
return invoke<CmdType.ProfilesConfig>("get_profiles");
|
||||||
@@ -83,9 +84,13 @@ export async function killSidecars() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function openAppDir() {
|
export async function openAppDir() {
|
||||||
return invoke<void>("open_app_dir");
|
return invoke<void>("open_app_dir").catch((err) =>
|
||||||
|
Notice.error(err?.message || err.toString(), 1500)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openLogsDir() {
|
export async function openLogsDir() {
|
||||||
return invoke<void>("open_logs_dir");
|
return invoke<void>("open_logs_dir").catch((err) =>
|
||||||
|
Notice.error(err?.message || err.toString(), 1500)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/services/i18n.ts
Normal file
17
src/services/i18n.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import i18n from "i18next";
|
||||||
|
import { initReactI18next } from "react-i18next";
|
||||||
|
import en from "../locales/en.json";
|
||||||
|
import zh from "../locales/zh.json";
|
||||||
|
|
||||||
|
const resources = {
|
||||||
|
en: { translation: en },
|
||||||
|
zh: { translation: zh },
|
||||||
|
};
|
||||||
|
|
||||||
|
i18n.use(initReactI18next).init({
|
||||||
|
resources,
|
||||||
|
lng: "en",
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -120,6 +120,7 @@ export namespace CmdType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface VergeConfig {
|
export interface VergeConfig {
|
||||||
|
language?: string;
|
||||||
theme_mode?: "light" | "dark";
|
theme_mode?: "light" | "dark";
|
||||||
theme_blur?: boolean;
|
theme_blur?: boolean;
|
||||||
traffic_graph?: boolean;
|
traffic_graph?: boolean;
|
||||||
|
|||||||
35
yarn.lock
35
yarn.lock
@@ -227,7 +227,7 @@
|
|||||||
"@babel/plugin-syntax-jsx" "^7.16.7"
|
"@babel/plugin-syntax-jsx" "^7.16.7"
|
||||||
"@babel/types" "^7.17.0"
|
"@babel/types" "^7.17.0"
|
||||||
|
|
||||||
"@babel/runtime@^7.13.10", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7":
|
"@babel/runtime@^7.13.10", "@babel/runtime@^7.14.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7":
|
||||||
version "7.17.2"
|
version "7.17.2"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
|
||||||
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
|
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
|
||||||
@@ -1291,6 +1291,18 @@ hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react-is "^16.7.0"
|
react-is "^16.7.0"
|
||||||
|
|
||||||
|
html-escaper@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||||
|
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
||||||
|
|
||||||
|
html-parse-stringify@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||||
|
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
|
||||||
|
dependencies:
|
||||||
|
void-elements "3.1.0"
|
||||||
|
|
||||||
human-signals@^1.1.1:
|
human-signals@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
||||||
@@ -1301,6 +1313,13 @@ husky@^7.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535"
|
resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535"
|
||||||
integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==
|
integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==
|
||||||
|
|
||||||
|
i18next@^21.6.14:
|
||||||
|
version "21.6.14"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.6.14.tgz#2bc199fba7f4da44b5952d7df0a3814a6e5c3943"
|
||||||
|
integrity sha512-XL6WyD+xlwQwbieXRlXhKWoLb/rkch50/rA+vl6untHnJ+aYnkQ0YDZciTWE78PPhOpbi2gR0LTJCJpiAhA+uQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.17.2"
|
||||||
|
|
||||||
ignore@^5.1.4:
|
ignore@^5.1.4:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
|
||||||
@@ -1653,6 +1672,15 @@ react-dom@^17.0.2:
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
scheduler "^0.20.2"
|
scheduler "^0.20.2"
|
||||||
|
|
||||||
|
react-i18next@^11.15.6:
|
||||||
|
version "11.15.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.15.6.tgz#693430fbee5ac7d0774bd88683575d62adb24afb"
|
||||||
|
integrity sha512-OUWcFdNgIA9swVx3JGIreuquglAinpRwB/HYrCprTN+s9BQDt9LSiY7x5DGc2JzVpwqtpoTV7oRUTOxEPNyUPw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.14.5"
|
||||||
|
html-escaper "^2.0.2"
|
||||||
|
html-parse-stringify "^3.0.1"
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.7.0:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
@@ -1901,6 +1929,11 @@ vite@^2.8.6:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
void-elements@3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||||
|
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
|
||||||
|
|
||||||
web-streams-polyfill@^3.0.3:
|
web-streams-polyfill@^3.0.3:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965"
|
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965"
|
||||||
|
|||||||
Reference in New Issue
Block a user