From b1444b8635ca315badb2cb8baa98e9cd1bd35bbd Mon Sep 17 00:00:00 2001 From: MystiPanda Date: Mon, 1 Jul 2024 08:25:03 +0800 Subject: [PATCH] feat: global merge and script --- src-tauri/src/config/config.rs | 17 ++ src-tauri/src/config/prfitem.rs | 30 ++-- src-tauri/src/enhance/mod.rs | 61 +++++++- src/components/profile/profile-more.tsx | 197 ++++++++++++++++++++++++ src/pages/profiles.tsx | 39 +++++ 5 files changed, 330 insertions(+), 14 deletions(-) create mode 100644 src/components/profile/profile-more.tsx diff --git a/src-tauri/src/config/config.rs b/src-tauri/src/config/config.rs index 8664596..bc074b2 100644 --- a/src-tauri/src/config/config.rs +++ b/src-tauri/src/config/config.rs @@ -1,5 +1,6 @@ use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge}; use crate::{ + config::PrfItem, enhance, utils::{dirs, help}, }; @@ -47,6 +48,22 @@ impl Config { /// 初始化订阅 pub async fn init_config() -> Result<()> { + if Self::profiles() + .data() + .get_item(&"Merge".to_string()) + .is_err() + { + let merge_item = PrfItem::from_merge(Some("Merge".to_string()))?; + Self::profiles().data().append_item(merge_item.clone())?; + } + if Self::profiles() + .data() + .get_item(&"Script".to_string()) + .is_err() + { + let script_item = PrfItem::from_script(Some("Script".to_string()))?; + Self::profiles().data().append_item(script_item.clone())?; + } crate::log_err!(Self::generate().await); if let Err(err) = Self::generate_file(ConfigType::Run) { log::error!(target: "app", "{err}"); diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs index 32d846f..b5cfcf8 100644 --- a/src-tauri/src/config/prfitem.rs +++ b/src-tauri/src/config/prfitem.rs @@ -175,12 +175,12 @@ impl PrfItem { let mut groups = opt_ref.and_then(|o| o.groups.clone()); if merge.is_none() { - let merge_item = PrfItem::from_merge()?; + let merge_item = PrfItem::from_merge(None)?; Config::profiles().data().append_item(merge_item.clone())?; merge = merge_item.uid; } if script.is_none() { - let script_item = PrfItem::from_script()?; + let script_item = PrfItem::from_script(None)?; Config::profiles().data().append_item(script_item.clone())?; script = script_item.uid; } @@ -248,12 +248,12 @@ impl PrfItem { let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy(); if merge.is_none() { - let merge_item = PrfItem::from_merge()?; + let merge_item = PrfItem::from_merge(None)?; Config::profiles().data().append_item(merge_item.clone())?; merge = merge_item.uid; } if script.is_none() { - let script_item = PrfItem::from_script()?; + let script_item = PrfItem::from_script(None)?; Config::profiles().data().append_item(script_item.clone())?; script = script_item.uid; } @@ -426,12 +426,15 @@ impl PrfItem { /// ## Merge type (enhance) /// create the enhanced item by using `merge` rule - pub fn from_merge() -> Result { - let uid = help::get_uid("m"); - let file = format!("{uid}.yaml"); + pub fn from_merge(uid: Option) -> Result { + let mut id = help::get_uid("m"); + if let Some(uid) = uid { + id = uid; + } + let file = format!("{id}.yaml"); Ok(PrfItem { - uid: Some(uid), + uid: Some(id), itype: Some("merge".into()), name: None, desc: None, @@ -448,12 +451,15 @@ impl PrfItem { /// ## Script type (enhance) /// create the enhanced item by using javascript quick.js - pub fn from_script() -> Result { - let uid = help::get_uid("s"); - let file = format!("{uid}.js"); // js ext + pub fn from_script(uid: Option) -> Result { + let mut id = help::get_uid("s"); + if let Some(uid) = uid { + id = uid; + } + let file = format!("{id}.js"); // js ext Ok(PrfItem { - uid: Some(uid), + uid: Some(id), itype: Some("script".into()), name: None, desc: None, diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs index 9af9ebd..0ea77f5 100644 --- a/src-tauri/src/enhance/mod.rs +++ b/src-tauri/src/enhance/mod.rs @@ -50,7 +50,16 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { }; // 从profiles里拿东西 - let (mut config, merge_item, script_item, rules_item, proxies_item, groups_item) = { + let ( + mut config, + merge_item, + script_item, + rules_item, + proxies_item, + groups_item, + global_merge, + global_script, + ) = { let profiles = Config::profiles(); let profiles = profiles.latest(); @@ -96,7 +105,34 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { data: ChainType::Groups(SeqMap::default()), }); - (current, merge, script, rules, proxies, groups) + let global_merge = profiles + .get_item(&"Merge".to_string()) + .ok() + .and_then(>::from) + .unwrap_or_else(|| ChainItem { + uid: "Merge".into(), + data: ChainType::Merge(Mapping::new()), + }); + + let global_script = profiles + .get_item(&"Script".to_string()) + .ok() + .and_then(>::from) + .unwrap_or_else(|| ChainItem { + uid: "Script".into(), + data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), + }); + + ( + current, + merge, + script, + rules, + proxies, + groups, + global_merge, + global_script, + ) }; let mut result_map = HashMap::new(); // 保存脚本日志 @@ -136,6 +172,27 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { result_map.insert(script_item.uid, logs); } + // 全局Merge和Script + if let ChainType::Merge(merge) = global_merge.data { + exists_keys.extend(use_keys(&merge)); + config = use_merge(merge, config.to_owned()); + } + + if let ChainType::Script(script) = global_script.data { + let mut logs = vec![]; + + match use_script(script, config.to_owned()) { + Ok((res_config, res_logs)) => { + exists_keys.extend(use_keys(&res_config)); + config = res_config; + logs.extend(res_logs); + } + Err(err) => logs.push(("exception".into(), err.to_string())), + } + + result_map.insert(global_script.uid, logs); + } + // 合并默认的config for (key, value) in clash_config.into_iter() { if key.as_str() == Some("tun") { diff --git a/src/components/profile/profile-more.tsx b/src/components/profile/profile-more.tsx new file mode 100644 index 0000000..1603d76 --- /dev/null +++ b/src/components/profile/profile-more.tsx @@ -0,0 +1,197 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useLockFn } from "ahooks"; +import { + Box, + Badge, + Chip, + Typography, + MenuItem, + Menu, + IconButton, +} from "@mui/material"; +import { FeaturedPlayListRounded } from "@mui/icons-material"; +import { viewProfile } from "@/services/cmds"; +import { Notice } from "@/components/base"; +import { EditorViewer } from "@/components/profile/editor-viewer"; +import { ProfileBox } from "./profile-box"; +import { LogViewer } from "./log-viewer"; + +interface Props { + logInfo?: [string, string][]; + id: "Merge" | "Script"; + onChange?: (prev?: string, curr?: string) => void; +} + +// profile enhanced item +export const ProfileMore = (props: Props) => { + const { id, logInfo = [], onChange } = props; + + const { t, i18n } = useTranslation(); + const [anchorEl, setAnchorEl] = useState(null); + const [position, setPosition] = useState({ left: 0, top: 0 }); + const [fileOpen, setFileOpen] = useState(false); + const [logOpen, setLogOpen] = useState(false); + + const onEditFile = () => { + setAnchorEl(null); + setFileOpen(true); + }; + + const onOpenFile = useLockFn(async () => { + setAnchorEl(null); + try { + await viewProfile(id); + } catch (err: any) { + Notice.error(err?.message || err.toString()); + } + }); + + const fnWrapper = (fn: () => void) => () => { + setAnchorEl(null); + return fn(); + }; + + const hasError = !!logInfo.find((e) => e[0] === "exception"); + + const itemMenu = [ + { label: "Edit File", handler: onEditFile }, + { label: "Open File", handler: onOpenFile }, + ]; + + const boxStyle = { + height: 26, + display: "flex", + alignItems: "center", + justifyContent: "space-between", + lineHeight: 1, + }; + + return ( + <> + { + const { clientX, clientY } = event; + setPosition({ top: clientY, left: clientX }); + setAnchorEl(event.currentTarget); + event.preventDefault(); + }} + > + + + {t(`Global ${id}`)} + + + + + + + {id === "Script" ? ( + hasError ? ( + + setLogOpen(true)} + > + + + + ) : ( + setLogOpen(true)} + > + + + ) + ) : ( + + {t(`${id} Description`)} + + )} + + + + setAnchorEl(null)} + anchorPosition={position} + anchorReference="anchorPosition" + transitionDuration={225} + MenuListProps={{ sx: { py: 0.5 } }} + onContextMenu={(e) => { + setAnchorEl(null); + e.preventDefault(); + }} + > + {itemMenu + .filter((item: any) => item.show !== false) + .map((item) => ( + { + return { + color: + item.label === "Delete" + ? theme.palette.error.main + : undefined, + }; + }, + ]} + dense + > + {t(item.label)} + + ))} + + + setFileOpen(false)} + /> + + setLogOpen(false)} + /> + + ); +}; diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index 9c641e6..52914c1 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -41,6 +41,7 @@ import { ProfileViewer, ProfileViewerRef, } from "@/components/profile/profile-viewer"; +import { ProfileMore } from "@/components/profile/profile-more"; import { ProfileItem } from "@/components/profile/profile-item"; import { useProfiles } from "@/hooks/use-profiles"; import { ConfigViewer } from "@/components/setting/mods/config-viewer"; @@ -49,6 +50,7 @@ import { BaseStyledTextField } from "@/components/base/base-styled-text-field"; import { listen } from "@tauri-apps/api/event"; import { readTextFile } from "@tauri-apps/api/fs"; import { readText } from "@tauri-apps/api/clipboard"; +import { EditorViewer } from "@/components/profile/editor-viewer"; const ProfilePage = () => { const { t } = useTranslation(); @@ -244,6 +246,12 @@ const ProfilePage = () => { if (text) setUrl(text); }; + const mode = useThemeMode(); + const islight = mode === "light" ? true : false; + const dividercolor = islight + ? "rgba(0, 0, 0, 0.06)" + : "rgba(255, 255, 255, 0.06)"; + return ( { + + + + + { + if (prev !== curr) { + await onEnhance(); + } + }} + /> + + + { + if (prev !== curr) { + await onEnhance(); + } + }} + /> + + + + mutateProfiles()} />