فهرست منبع

refactor: Associate Profile with Merge/Script.

MystiPanda 11 ماه پیش
والد
کامیت
cf61a96ef6

+ 74 - 29
src-tauri/src/config/prfitem.rs

@@ -94,6 +94,16 @@ pub struct PrfOption {
     /// default is `false`
     #[serde(skip_serializing_if = "Option::is_none")]
     pub danger_accept_invalid_certs: Option<bool>,
+
+    pub merge: Option<String>,
+
+    pub script: Option<String>,
+
+    pub rules: Option<String>,
+
+    pub proxies: Option<String>,
+
+    pub groups: Option<String>,
 }
 
 impl PrfOption {
@@ -107,6 +117,11 @@ impl PrfOption {
                     .danger_accept_invalid_certs
                     .or(a.danger_accept_invalid_certs);
                 a.update_interval = b.update_interval.or(a.update_interval);
+                a.merge = b.merge.or(a.merge);
+                a.script = b.script.or(a.script);
+                a.rules = b.rules.or(a.rules);
+                a.proxies = b.proxies.or(a.proxies);
+                a.groups = b.groups.or(a.groups);
                 Some(a)
             }
             t => t.0.or(t.1),
@@ -137,16 +152,8 @@ impl PrfItem {
                 let desc = item.desc.unwrap_or("".into());
                 PrfItem::from_local(name, desc, file_data, item.option)
             }
-            "merge" => {
-                let name = item.name.unwrap_or("Merge".into());
-                let desc = item.desc.unwrap_or("".into());
-                PrfItem::from_merge(name, desc)
-            }
-            "script" => {
-                let name = item.name.unwrap_or("Script".into());
-                let desc = item.desc.unwrap_or("".into());
-                PrfItem::from_script(name, desc)
-            }
+            "merge" => PrfItem::from_merge(),
+            "script" => PrfItem::from_script(),
             typ => bail!("invalid profile item type \"{typ}\""),
         }
     }
@@ -161,7 +168,24 @@ impl PrfItem {
     ) -> Result<PrfItem> {
         let uid = help::get_uid("l");
         let file = format!("{uid}.yaml");
-
+        let opt_ref = option.as_ref();
+        let update_interval = opt_ref.and_then(|o| o.update_interval);
+        let mut merge = opt_ref.and_then(|o| o.merge.clone());
+        let mut script = opt_ref.and_then(|o| o.script.clone());
+        let rules = opt_ref.and_then(|o| o.rules.clone());
+        let proxies = opt_ref.and_then(|o| o.proxies.clone());
+        let groups = opt_ref.and_then(|o| o.groups.clone());
+
+        if merge.is_none() {
+            let merge_item = PrfItem::from_merge()?;
+            Config::profiles().data().append_item(merge_item.clone())?;
+            merge = merge_item.uid;
+        }
+        if script.is_none() {
+            let script_item = PrfItem::from_script()?;
+            Config::profiles().data().append_item(script_item.clone())?;
+            script = script_item.uid;
+        }
         Ok(PrfItem {
             uid: Some(uid),
             itype: Some("local".into()),
@@ -172,7 +196,12 @@ impl PrfItem {
             selected: None,
             extra: None,
             option: Some(PrfOption {
-                update_interval: option.unwrap_or_default().update_interval,
+                update_interval,
+                merge,
+                script,
+                rules,
+                proxies,
+                groups,
                 ..PrfOption::default()
             }),
             home: None,
@@ -196,9 +225,23 @@ impl PrfItem {
             opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
         let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
         let update_interval = opt_ref.and_then(|o| o.update_interval);
-
+        let mut merge = opt_ref.and_then(|o| o.merge.clone());
+        let mut script = opt_ref.and_then(|o| o.script.clone());
+        let rules = opt_ref.and_then(|o| o.rules.clone());
+        let proxies = opt_ref.and_then(|o| o.proxies.clone());
+        let groups = opt_ref.and_then(|o| o.groups.clone());
         let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
 
+        if merge.is_none() {
+            let merge_item = PrfItem::from_merge()?;
+            Config::profiles().data().append_item(merge_item.clone())?;
+            merge = merge_item.uid;
+        }
+        if script.is_none() {
+            let script_item = PrfItem::from_script()?;
+            Config::profiles().data().append_item(script_item.clone())?;
+            script = script_item.uid;
+        }
         // 使用软件自己的代理
         if self_proxy {
             let port = Config::verge()
@@ -290,17 +333,11 @@ impl PrfItem {
                 crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()),
             ),
         };
-        let option = match update_interval {
-            Some(val) => Some(PrfOption {
-                update_interval: Some(val),
-                ..PrfOption::default()
-            }),
+        let update_interval = match update_interval {
+            Some(val) => Some(val),
             None => match header.get("profile-update-interval") {
                 Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
-                    Ok(val) => Some(PrfOption {
-                        update_interval: Some(val * 60), // hour -> min
-                        ..PrfOption::default()
-                    }),
+                    Ok(val) => Some(val * 60), // hour -> min
                     Err(_) => None,
                 },
                 None => None,
@@ -340,7 +377,15 @@ impl PrfItem {
             url: Some(url.into()),
             selected: None,
             extra,
-            option,
+            option: Some(PrfOption {
+                update_interval,
+                merge,
+                script,
+                rules,
+                proxies,
+                groups,
+                ..PrfOption::default()
+            }),
             home,
             updated: Some(chrono::Local::now().timestamp() as usize),
             file_data: Some(data.into()),
@@ -349,15 +394,15 @@ impl PrfItem {
 
     /// ## Merge type (enhance)
     /// create the enhanced item by using `merge` rule
-    pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
+    pub fn from_merge() -> Result<PrfItem> {
         let uid = help::get_uid("m");
         let file = format!("{uid}.yaml");
 
         Ok(PrfItem {
             uid: Some(uid),
             itype: Some("merge".into()),
-            name: Some(name),
-            desc: Some(desc),
+            name: None,
+            desc: None,
             file: Some(file),
             url: None,
             selected: None,
@@ -371,15 +416,15 @@ impl PrfItem {
 
     /// ## Script type (enhance)
     /// create the enhanced item by using javascript quick.js
-    pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
+    pub fn from_script() -> Result<PrfItem> {
         let uid = help::get_uid("s");
         let file = format!("{uid}.js"); // js ext
 
         Ok(PrfItem {
             uid: Some(uid),
             itype: Some("script".into()),
-            name: Some(name),
-            desc: Some(desc),
+            name: None,
+            desc: None,
             file: Some(file),
             url: None,
             home: None,

+ 77 - 8
src-tauri/src/config/profiles.rs

@@ -11,9 +11,6 @@ pub struct IProfiles {
     /// same as PrfConfig.current
     pub current: Option<String>,
 
-    /// same as PrfConfig.chain
-    pub chain: Option<Vec<String>>,
-
     /// profile list
     pub items: Option<Vec<PrfItem>>,
 }
@@ -80,10 +77,6 @@ impl IProfiles {
             }
         }
 
-        if let Some(chain) = patch.chain {
-            self.chain = Some(chain);
-        }
-
         Ok(())
     }
 
@@ -243,9 +236,19 @@ impl IProfiles {
     pub fn delete_item(&mut self, uid: String) -> Result<bool> {
         let current = self.current.as_ref().unwrap_or(&uid);
         let current = current.clone();
-
+        let item = self.get_item(&uid)?;
+        let merge_uid = item.option.as_ref().and_then(|e| e.merge.clone());
+        let script_uid = item.option.as_ref().and_then(|e| e.script.clone());
+        let rules_uid = item.option.as_ref().and_then(|e| e.rules.clone());
+        let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone());
+        let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
         let mut items = self.items.take().unwrap_or_default();
         let mut index = None;
+        let mut merge_index = None;
+        let mut script_index = None;
+        // let mut rules_index = None;
+        // let mut proxies_index = None;
+        // let mut groups_index = None;
 
         // get the index
         for (i, _) in items.iter().enumerate() {
@@ -266,6 +269,44 @@ impl IProfiles {
             }
         }
 
+        // get the merge index
+        for (i, _) in items.iter().enumerate() {
+            if items[i].uid == merge_uid {
+                merge_index = Some(i);
+                break;
+            }
+        }
+
+        if let Some(index) = merge_index {
+            if let Some(file) = items.remove(index).file {
+                let _ = dirs::app_profiles_dir().map(|path| {
+                    let path = path.join(file);
+                    if path.exists() {
+                        let _ = fs::remove_file(path);
+                    }
+                });
+            }
+        }
+
+        // get the script index
+        for (i, _) in items.iter().enumerate() {
+            if items[i].uid == script_uid {
+                script_index = Some(i);
+                break;
+            }
+        }
+
+        if let Some(index) = script_index {
+            if let Some(file) = items.remove(index).file {
+                let _ = dirs::app_profiles_dir().map(|path| {
+                    let path = path.join(file);
+                    if path.exists() {
+                        let _ = fs::remove_file(path);
+                    }
+                });
+            }
+        }
+
         // delete the original uid
         if current == uid {
             self.current = match !items.is_empty() {
@@ -295,4 +336,32 @@ impl IProfiles {
             _ => Ok(Mapping::new()),
         }
     }
+
+    /// 获取current指向的订阅的merge
+    pub fn current_merge(&self) -> Option<String> {
+        match (self.current.as_ref(), self.items.as_ref()) {
+            (Some(current), Some(items)) => {
+                if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
+                    let merge = item.option.as_ref().and_then(|e| e.merge.clone());
+                    return merge;
+                }
+                None
+            }
+            _ => None,
+        }
+    }
+
+    /// 获取current指向的订阅的script
+    pub fn current_script(&self) -> Option<String> {
+        match (self.current.as_ref(), self.items.as_ref()) {
+            (Some(current), Some(items)) => {
+                if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
+                    let script = item.option.as_ref().and_then(|e| e.script.clone());
+                    return script;
+                }
+                None
+            }
+            _ => None,
+        }
+    }
 }

+ 28 - 14
src-tauri/src/enhance/mod.rs

@@ -10,6 +10,7 @@ use self::merge::*;
 use self::script::*;
 use self::tun::*;
 use crate::config::Config;
+use crate::utils::tmpl;
 use serde_yaml::Mapping;
 use std::collections::HashMap;
 use std::collections::HashSet;
@@ -47,33 +48,45 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
     };
 
     // 从profiles里拿东西
-    let (mut config, chain) = {
+    let (mut config, merge_item, script_item) = {
         let profiles = Config::profiles();
         let profiles = profiles.latest();
 
         let current = profiles.current_mapping().unwrap_or_default();
+        let merge = profiles
+            .get_item(&profiles.current_merge().unwrap_or_default())
+            .ok()
+            .and_then(<Option<ChainItem>>::from)
+            .unwrap_or_else(|| ChainItem {
+                uid: "".into(),
+                data: ChainType::Merge(
+                    serde_yaml::from_str::<Mapping>(tmpl::ITEM_MERGE).unwrap_or_default(),
+                ),
+            });
+        let script = profiles
+            .get_item(&profiles.current_script().unwrap_or_default())
+            .ok()
+            .and_then(<Option<ChainItem>>::from)
+            .unwrap_or_else(|| ChainItem {
+                uid: "".into(),
+                data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
+            });
 
-        let chain = match profiles.chain.as_ref() {
-            Some(chain) => chain
-                .iter()
-                .filter_map(|uid| profiles.get_item(uid).ok())
-                .filter_map(<Option<ChainItem>>::from)
-                .collect::<Vec<ChainItem>>(),
-            None => vec![],
-        };
-
-        (current, chain)
+        (current, merge, script)
     };
 
     let mut result_map = HashMap::new(); // 保存脚本日志
     let mut exists_keys = use_keys(&config); // 保存出现过的keys
 
     // 处理用户的profile
-    chain.into_iter().for_each(|item| match item.data {
+    match merge_item.data {
         ChainType::Merge(merge) => {
             exists_keys.extend(use_keys(&merge));
             config = use_merge(merge, config.to_owned());
         }
+        _ => {}
+    }
+    match script_item.data {
         ChainType::Script(script) => {
             let mut logs = vec![];
 
@@ -86,9 +99,10 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
                 Err(err) => logs.push(("exception".into(), err.to_string())),
             }
 
-            result_map.insert(item.uid, logs);
+            result_map.insert(script_item.uid, logs);
         }
-    });
+        _ => {}
+    }
 
     // 合并默认的config
     for (key, value) in clash_config.into_iter() {

+ 0 - 1
src-tauri/src/feat.rs

@@ -292,7 +292,6 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
         Some((url, opt)) => {
             let merged_opt = PrfOption::merge(opt, option);
             let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
-
             let profiles = Config::profiles();
             let mut profiles = profiles.latest();
             profiles.update_item(uid.clone(), item)?;

+ 17 - 26
src-tauri/src/utils/resolve.rs

@@ -1,10 +1,6 @@
-use crate::config::{IVerge, PrfOption};
-use crate::{
-    config::{Config, PrfItem},
-    core::*,
-    utils::init,
-    utils::server,
-};
+use crate::cmds::import_profile;
+use crate::config::IVerge;
+use crate::{config::Config, core::*, utils::init, utils::server};
 use crate::{log_err, trace_err};
 use anyhow::Result;
 use once_cell::sync::OnceCell;
@@ -102,7 +98,7 @@ pub async fn resolve_setup(app: &mut App) {
     if argvs.len() > 1 {
         let param = argvs[1].as_str();
         if param.starts_with("clash:") {
-            resolve_scheme(argvs[1].to_owned()).await;
+            log_err!(resolve_scheme(argvs[1].to_owned()).await);
         }
     }
 }
@@ -240,31 +236,26 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) ->
     Ok(())
 }
 
-pub async fn resolve_scheme(param: String) {
+pub async fn resolve_scheme(param: String) -> Result<()> {
     let url = param
         .trim_start_matches("clash://install-config/?url=")
         .trim_start_matches("clash://install-config?url=");
-    let option = PrfOption {
-        user_agent: None,
-        with_proxy: Some(true),
-        self_proxy: None,
-        danger_accept_invalid_certs: None,
-        update_interval: None,
-    };
-    if let Ok(item) = PrfItem::from_url(url, None, None, Some(option)).await {
-        if Config::profiles().data().append_item(item).is_ok() {
+    match import_profile(url.to_string(), None).await {
+        Ok(_) => {
             notification::Notification::new(crate::utils::dirs::APP_ID)
                 .title("Clash Verge")
                 .body("Import profile success")
                 .show()
                 .unwrap();
-        };
-    } else {
-        notification::Notification::new(crate::utils::dirs::APP_ID)
-            .title("Clash Verge")
-            .body("Import profile failed")
-            .show()
-            .unwrap();
-        log::error!("failed to parse url: {}", url);
+        }
+        Err(e) => {
+            notification::Notification::new(crate::utils::dirs::APP_ID)
+                .title("Clash Verge")
+                .body(format!("Import profile failed: {e}"))
+                .show()
+                .unwrap();
+            log::error!("Import profile failed: {e}");
+        }
     }
+    Ok(())
 }

+ 5 - 2
src-tauri/src/utils/server.rs

@@ -1,7 +1,10 @@
 extern crate warp;
 
 use super::resolve;
-use crate::config::{Config, IVerge, DEFAULT_PAC};
+use crate::{
+    config::{Config, IVerge, DEFAULT_PAC},
+    log_err,
+};
 use anyhow::{bail, Result};
 use port_scanner::local_port_available;
 use std::convert::Infallible;
@@ -85,7 +88,7 @@ pub fn embed_server(app_handle: AppHandle) {
             .and_then(scheme_handler);
 
         async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
-            resolve::resolve_scheme(query.param).await;
+            log_err!(resolve::resolve_scheme(query.param).await);
             Ok("ok")
         }
         let commands = ping.or(visible).or(pac).or(scheme);

+ 65 - 12
src/components/profile/profile-item.tsx

@@ -59,7 +59,7 @@ export const ProfileItem = (props: Props) => {
   const loadingCache = useLoadingCache();
   const setLoadingCache = useSetLoadingCache();
 
-  const { uid, name = "Profile", extra, updated = 0 } = itemData;
+  const { uid, name = "Profile", extra, updated = 0, option } = itemData;
 
   // local file mode
   // remote file mode
@@ -105,6 +105,8 @@ export const ProfileItem = (props: Props) => {
   }, [hasUrl, updated]);
 
   const [fileOpen, setFileOpen] = useState(false);
+  const [mergeOpen, setMergeOpen] = useState(false);
+  const [scriptOpen, setScriptOpen] = useState(false);
   const [confirmOpen, setConfirmOpen] = useState(false);
 
   const onOpenHome = () => {
@@ -122,6 +124,16 @@ export const ProfileItem = (props: Props) => {
     setFileOpen(true);
   };
 
+  const onEditMerge = () => {
+    setAnchorEl(null);
+    setMergeOpen(true);
+  };
+
+  const onEditScript = () => {
+    setAnchorEl(null);
+    setScriptOpen(true);
+  };
+
   const onForceSelect = () => {
     setAnchorEl(null);
     onSelect(true);
@@ -174,33 +186,55 @@ export const ProfileItem = (props: Props) => {
   });
 
   const urlModeMenu = (
-    hasHome ? [{ label: "Home", handler: onOpenHome }] : []
+    hasHome ? [{ label: "Home", handler: onOpenHome, disabled: false }] : []
   ).concat([
-    { label: "Select", handler: onForceSelect },
-    { label: "Edit Info", handler: onEditInfo },
-    { label: "Edit File", handler: onEditFile },
-    { label: "Open File", handler: onOpenFile },
-    { label: "Update", handler: () => onUpdate(0) },
-    { label: "Update(Proxy)", handler: () => onUpdate(2) },
+    { label: "Select", handler: onForceSelect, disabled: false },
+    { label: "Edit Info", handler: onEditInfo, disabled: false },
+    { label: "Edit File", handler: onEditFile, disabled: false },
+    {
+      label: "Edit Merge",
+      handler: onEditMerge,
+      disabled: option?.merge === null,
+    },
+    {
+      label: "Edit Script",
+      handler: onEditScript,
+      disabled: option?.script === null,
+    },
+    { label: "Open File", handler: onOpenFile, disabled: false },
+    { label: "Update", handler: () => onUpdate(0), disabled: false },
+    { label: "Update(Proxy)", handler: () => onUpdate(2), disabled: false },
     {
       label: "Delete",
       handler: () => {
         setAnchorEl(null);
         setConfirmOpen(true);
       },
+      disabled: false,
     },
   ]);
   const fileModeMenu = [
-    { label: "Select", handler: onForceSelect },
-    { label: "Edit Info", handler: onEditInfo },
-    { label: "Edit File", handler: onEditFile },
-    { label: "Open File", handler: onOpenFile },
+    { label: "Select", handler: onForceSelect, disabled: false },
+    { label: "Edit Info", handler: onEditInfo, disabled: false },
+    { label: "Edit File", handler: onEditFile, disabled: false },
+    {
+      label: "Edit Merge",
+      handler: onEditMerge,
+      disabled: option?.merge === null,
+    },
+    {
+      label: "Edit Script",
+      handler: onEditScript,
+      disabled: option?.script === null,
+    },
+    { label: "Open File", handler: onOpenFile, disabled: false },
     {
       label: "Delete",
       handler: () => {
         setAnchorEl(null);
         setConfirmOpen(true);
       },
+      disabled: false,
     },
   ];
 
@@ -369,6 +403,7 @@ export const ProfileItem = (props: Props) => {
           <MenuItem
             key={item.label}
             onClick={item.handler}
+            disabled={item.disabled}
             sx={[
               {
                 minWidth: 120,
@@ -398,6 +433,24 @@ export const ProfileItem = (props: Props) => {
         onChange={onChange}
         onClose={() => setFileOpen(false)}
       />
+      <EditorViewer
+        mode="profile"
+        property={option?.merge ?? "123"}
+        open={mergeOpen}
+        language="yaml"
+        schema="merge"
+        onChange={onChange}
+        onClose={() => setMergeOpen(false)}
+      />
+      <EditorViewer
+        mode="profile"
+        property={option?.script ?? ""}
+        open={scriptOpen}
+        language="javascript"
+        schema={undefined}
+        onChange={onChange}
+        onClose={() => setScriptOpen(false)}
+      />
       <ConfirmViewer
         title={t("Confirm deletion")}
         message={t("This operation is not reversible")}

+ 0 - 285
src/components/profile/profile-more.tsx

@@ -1,285 +0,0 @@
-import { useState } from "react";
-import { useTranslation } from "react-i18next";
-import { useLockFn } from "ahooks";
-import {
-  Box,
-  Badge,
-  Chip,
-  Typography,
-  MenuItem,
-  Menu,
-  IconButton,
-  CircularProgress,
-} 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";
-import { ConfirmViewer } from "./confirm-viewer";
-
-interface Props {
-  selected: boolean;
-  activating: boolean;
-  itemData: IProfileItem;
-  enableNum: number;
-  logInfo?: [string, string][];
-  onEnable: () => void;
-  onDisable: () => void;
-  onMoveTop: () => void;
-  onMoveEnd: () => void;
-  onEdit: () => void;
-  onChange?: (prev?: string, curr?: string) => void;
-  onDelete: () => void;
-}
-
-// profile enhanced item
-export const ProfileMore = (props: Props) => {
-  const {
-    selected,
-    activating,
-    itemData,
-    enableNum,
-    logInfo = [],
-    onEnable,
-    onDisable,
-    onMoveTop,
-    onMoveEnd,
-    onDelete,
-    onEdit,
-    onChange,
-  } = props;
-
-  const { uid, type } = itemData;
-  const { t, i18n } = useTranslation();
-  const [anchorEl, setAnchorEl] = useState<any>(null);
-  const [position, setPosition] = useState({ left: 0, top: 0 });
-  const [fileOpen, setFileOpen] = useState(false);
-  const [confirmOpen, setConfirmOpen] = useState(false);
-  const [logOpen, setLogOpen] = useState(false);
-
-  const onEditInfo = () => {
-    setAnchorEl(null);
-    onEdit();
-  };
-
-  const onEditFile = () => {
-    setAnchorEl(null);
-    setFileOpen(true);
-  };
-
-  const onOpenFile = useLockFn(async () => {
-    setAnchorEl(null);
-    try {
-      await viewProfile(itemData.uid);
-    } 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 showMove = enableNum > 1 && !hasError;
-
-  const enableMenu = [
-    { label: "Disable", handler: fnWrapper(onDisable) },
-    { label: "Edit Info", handler: onEditInfo },
-    { label: "Edit File", handler: onEditFile },
-    { label: "Open File", handler: onOpenFile },
-    { label: "To Top", show: showMove, handler: fnWrapper(onMoveTop) },
-    { label: "To End", show: showMove, handler: fnWrapper(onMoveEnd) },
-    {
-      label: "Delete",
-      handler: () => {
-        setAnchorEl(null);
-        setConfirmOpen(true);
-      },
-    },
-  ];
-
-  const disableMenu = [
-    { label: "Enable", handler: fnWrapper(onEnable) },
-    { label: "Edit Info", handler: onEditInfo },
-    { label: "Edit File", handler: onEditFile },
-    { label: "Open File", handler: onOpenFile },
-    {
-      label: "Delete",
-      handler: () => {
-        setAnchorEl(null);
-        setConfirmOpen(true);
-      },
-    },
-  ];
-
-  const boxStyle = {
-    height: 26,
-    display: "flex",
-    alignItems: "center",
-    justifyContent: "space-between",
-    lineHeight: 1,
-  };
-
-  return (
-    <>
-      <ProfileBox
-        aria-selected={selected}
-        onDoubleClick={onEditFile}
-        // onClick={() => onSelect(false)}
-        onContextMenu={(event) => {
-          const { clientX, clientY } = event;
-          setPosition({ top: clientY, left: clientX });
-          setAnchorEl(event.currentTarget);
-          event.preventDefault();
-        }}
-      >
-        {activating && (
-          <Box
-            sx={{
-              position: "absolute",
-              display: "flex",
-              justifyContent: "center",
-              alignItems: "center",
-              top: 10,
-              left: 10,
-              right: 10,
-              bottom: 2,
-              zIndex: 10,
-              backdropFilter: "blur(2px)",
-            }}
-          >
-            <CircularProgress color="inherit" size={20} />
-          </Box>
-        )}
-        <Box
-          display="flex"
-          justifyContent="space-between"
-          alignItems="center"
-          mb={0.5}
-        >
-          <Typography
-            width="calc(100% - 52px)"
-            variant="h6"
-            component="h2"
-            noWrap
-            title={itemData.name}
-          >
-            {itemData.name}
-          </Typography>
-
-          <Chip
-            label={type}
-            color="primary"
-            size="small"
-            variant="outlined"
-            sx={{ height: 20, textTransform: "capitalize" }}
-          />
-        </Box>
-
-        <Box sx={boxStyle}>
-          {selected && type === "script" ? (
-            hasError ? (
-              <Badge color="error" variant="dot" overlap="circular">
-                <IconButton
-                  size="small"
-                  edge="start"
-                  color="error"
-                  title={t("Script Console")}
-                  onClick={() => setLogOpen(true)}
-                >
-                  <FeaturedPlayListRounded fontSize="inherit" />
-                </IconButton>
-              </Badge>
-            ) : (
-              <IconButton
-                size="small"
-                edge="start"
-                color="inherit"
-                title={t("Script Console")}
-                onClick={() => setLogOpen(true)}
-              >
-                <FeaturedPlayListRounded fontSize="inherit" />
-              </IconButton>
-            )
-          ) : (
-            <Typography
-              noWrap
-              title={itemData.desc}
-              sx={i18n.language === "zh" ? { width: "calc(100% - 75px)" } : {}}
-            >
-              {itemData.desc}
-            </Typography>
-          )}
-        </Box>
-      </ProfileBox>
-
-      <Menu
-        open={!!anchorEl}
-        anchorEl={anchorEl}
-        onClose={() => setAnchorEl(null)}
-        anchorPosition={position}
-        anchorReference="anchorPosition"
-        transitionDuration={225}
-        MenuListProps={{ sx: { py: 0.5 } }}
-        onContextMenu={(e) => {
-          setAnchorEl(null);
-          e.preventDefault();
-        }}
-      >
-        {(selected ? enableMenu : disableMenu)
-          .filter((item: any) => item.show !== false)
-          .map((item) => (
-            <MenuItem
-              key={item.label}
-              onClick={item.handler}
-              sx={[
-                { minWidth: 120 },
-                (theme) => {
-                  return {
-                    color:
-                      item.label === "Delete"
-                        ? theme.palette.error.main
-                        : undefined,
-                  };
-                },
-              ]}
-              dense
-            >
-              {t(item.label)}
-            </MenuItem>
-          ))}
-      </Menu>
-
-      <EditorViewer
-        mode="profile"
-        property={uid}
-        open={fileOpen}
-        language={type === "merge" ? "yaml" : "javascript"}
-        schema={type === "merge" ? "merge" : undefined}
-        onChange={onChange}
-        onClose={() => setFileOpen(false)}
-      />
-      <ConfirmViewer
-        title={t("Confirm deletion")}
-        message={t("This operation is not reversible")}
-        open={confirmOpen}
-        onClose={() => setConfirmOpen(false)}
-        onConfirm={() => {
-          onDelete();
-          setConfirmOpen(false);
-        }}
-      />
-      {selected && (
-        <LogViewer
-          open={logOpen}
-          logInfo={logInfo}
-          onClose={() => setLogOpen(false)}
-        />
-      )}
-    </>
-  );
-};

+ 1 - 6
src/components/profile/profile-viewer.tsx

@@ -33,7 +33,7 @@ export interface ProfileViewerRef {
 }
 
 // create or edit the profile
-// remote / local / merge / script
+// remote / local
 export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
   (props, ref) => {
     const { t } = useTranslation();
@@ -92,9 +92,6 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
           if (form.type === "remote" && !form.url) {
             throw new Error("The URL should not be null");
           }
-          if (form.type !== "remote" && form.type !== "local") {
-            delete form.option;
-          }
 
           if (form.option?.update_interval) {
             form.option.update_interval = +form.option.update_interval;
@@ -168,8 +165,6 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
               <Select {...field} autoFocus label={t("Type")}>
                 <MenuItem value="remote">Remote</MenuItem>
                 <MenuItem value="local">Local</MenuItem>
-                <MenuItem value="script">Script</MenuItem>
-                <MenuItem value="merge">Merge</MenuItem>
               </Select>
             </FormControl>
           )}

+ 2 - 14
src/locales/en.json

@@ -1,20 +1,17 @@
 {
   "millis": "millis",
   "mins": "mins",
-
   "Back": "Back",
   "Close": "Close",
   "Cancel": "Cancel",
   "Confirm": "Confirm",
   "Empty": "Empty",
-
   "New": "New",
   "Edit": "Edit",
   "Save": "Save",
   "Delete": "Delete",
   "Enable": "Enable",
   "Disable": "Disable",
-
   "Label-Proxies": "Proxies",
   "Label-Profiles": "Profiles",
   "Label-Connections": "Connections",
@@ -22,7 +19,6 @@
   "Label-Logs": "Logs",
   "Label-Test": "Test",
   "Label-Settings": "Settings",
-
   "Proxies": "Proxies",
   "Proxy Groups": "Proxy Groups",
   "Proxy Provider": "Proxy Provider",
@@ -41,7 +37,6 @@
   "Delay check to cancel fixed": "Delay check to cancel fixed",
   "Proxy basic": "Proxy basic",
   "Proxy detail": "Proxy detail",
-
   "Profiles": "Profiles",
   "Update All Profiles": "Update All Profiles",
   "View Runtime Config": "View Runtime Config",
@@ -55,6 +50,8 @@
   "Expire Time": "Expire Time",
   "Create Profile": "Create Profile",
   "Edit Profile": "Edit Profile",
+  "Edit Merge": "Edit Merge",
+  "Edit Script": "Edit Script",
   "Type": "Type",
   "Name": "Name",
   "Descriptions": "Descriptions",
@@ -77,7 +74,6 @@
   "Script Console": "Script Console",
   "To Top": "To Top",
   "To End": "To End",
-
   "Connections": "Connections",
   "Table View": "Table View",
   "List View": "List View",
@@ -97,21 +93,17 @@
   "Source": "Source",
   "Destination IP": "Destination IP",
   "Close Connection": "Close Connection",
-
   "Rules": "Rules",
   "Rule Provider": "Rule Provider",
-
   "Logs": "Logs",
   "Pause": "Pause",
   "Clear": "Clear",
-
   "Test": "Test",
   "Test All": "Test All",
   "Create Test": "Create Test",
   "Edit Test": "Edit Test",
   "Icon": "Icon",
   "Test URL": "Test URL",
-
   "Settings": "Settings",
   "System Setting": "System Setting",
   "Tun Mode": "Tun Mode",
@@ -157,7 +149,6 @@
   "Auto Launch": "Auto Launch",
   "Silent Start": "Silent Start",
   "Silent Start Info": "Start the program in background mode without displaying the panel",
-
   "Clash Setting": "Clash Setting",
   "Allow Lan": "Allow Lan",
   "IPv6": "IPv6",
@@ -181,7 +172,6 @@
   "Open UWP tool": "Open UWP tool",
   "Open UWP tool Info": "Since Windows 8, UWP apps (such as Microsoft Store) are restricted from directly accessing local host network services, and this tool can be used to bypass this restriction",
   "Update GeoData": "Update GeoData",
-
   "TG Channel": "Telegram Channel",
   "Manual": "Manual",
   "Github Repo": "Github Repo",
@@ -253,7 +243,6 @@
   "Open Dev Tools": "Open Dev Tools",
   "Exit": "Exit",
   "Verge Version": "Verge Version",
-
   "ReadOnly": "ReadOnly",
   "ReadOnlyMessage": "Cannot edit in read-only editor",
   "Filter": "Filter",
@@ -261,7 +250,6 @@
   "Match Case": "Match Case",
   "Match Whole Word": "Match Whole Word",
   "Use Regular Expression": "Use Regular Expression",
-
   "Profile Imported Successfully": "Profile Imported Successfully",
   "Clash Config Updated": "Clash Config Updated",
   "Profile Switched": "Profile Switched",

+ 2 - 14
src/locales/fa.json

@@ -1,20 +1,17 @@
 {
   "millis": "میلی‌ثانیه",
   "mins": "دقیقه",
-
   "Back": "بازگشت",
   "Close": "بستن",
   "Cancel": "لغو",
   "Confirm": "تأیید",
   "Empty": "خالی خالی",
-
   "New": "جدید",
   "Edit": "ویرایش",
   "Save": "ذخیره",
   "Delete": "حذف",
   "Enable": "فعال کردن",
   "Disable": "غیرفعال کردن",
-
   "Label-Proxies": "پراکسی‌ها",
   "Label-Profiles": "پروفایل‌ها",
   "Label-Connections": "اتصالات",
@@ -22,7 +19,6 @@
   "Label-Logs": "لاگ‌ها",
   "Label-Test": "آزمون",
   "Label-Settings": "تنظیمات",
-
   "Proxies": "پراکسی‌ها",
   "Proxy Groups": "گروه‌های پراکسی",
   "Proxy Provider": "تأمین‌کننده پروکسی",
@@ -41,7 +37,6 @@
   "Delay check to cancel fixed": "بررسی تأخیر برای لغو ثابت",
   "Proxy basic": "پراکسی پایه",
   "Proxy detail": "جزئیات پراکسی",
-
   "Profiles": "پروفایل‌ها",
   "Update All Profiles": "به‌روزرسانی همه پروفایل‌ها",
   "View Runtime Config": "مشاهده پیکربندی زمان اجرا",
@@ -55,6 +50,8 @@
   "Expire Time": "زمان انقضا",
   "Create Profile": "ایجاد پروفایل",
   "Edit Profile": "ویرایش پروفایل",
+  "Edit Merge": "ادغام ویرایش",
+  "Edit Script": "ویرایش اسکریپت",
   "Type": "نوع",
   "Name": "نام",
   "Descriptions": "توضیحات",
@@ -77,7 +74,6 @@
   "Script Console": "کنسول اسکریپت",
   "To Top": "به بالا",
   "To End": "به پایان",
-
   "Connections": "اتصالات",
   "Table View": "نمای جدولی",
   "List View": "نمای لیستی",
@@ -97,21 +93,17 @@
   "Source": "منبع",
   "Destination IP": "آدرس IP مقصد",
   "Close Connection": "بستن اتصال",
-
   "Rules": "قوانین",
   "Rule Provider": "تأمین‌کننده قانون",
-
   "Logs": "لاگ‌ها",
   "Pause": "توقف",
   "Clear": "پاک کردن",
-
   "Test": "آزمون",
   "Test All": "آزمون همه",
   "Create Test": "ایجاد آزمون",
   "Edit Test": "ویرایش آزمون",
   "Icon": "آیکون",
   "Test URL": "آدرس آزمون",
-
   "Settings": "تنظیمات",
   "System Setting": "تنظیمات سیستم",
   "Tun Mode": "حالت Tun",
@@ -157,7 +149,6 @@
   "Auto Launch": "راه‌اندازی خودکار",
   "Silent Start": "شروع بی‌صدا",
   "Silent Start Info": "برنامه را در حالت پس‌زمینه بدون نمایش پانل اجرا کنید",
-
   "Clash Setting": "تنظیمات Clash",
   "Allow Lan": "اجازه LAN",
   "IPv6": "IPv6",
@@ -186,7 +177,6 @@
   "Open UWP tool": "باز کردن ابزار UWP",
   "Open UWP tool Info": "از ویندوز 8 به بعد، برنامه‌های UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شده‌اند و این ابزار می‌تواند برای دور زدن این محدودیت استفاده شود",
   "Update GeoData": "به‌روزرسانی GeoData",
-
   "TG Channel": "کانال تلگرام",
   "Manual": "راهنما",
   "Github Repo": "مخزن GitHub",
@@ -258,7 +248,6 @@
   "Open Dev Tools": "باز کردن ابزارهای توسعه‌دهنده",
   "Exit": "خروج",
   "Verge Version": "نسخه Verge",
-
   "ReadOnly": "فقط خواندنی",
   "ReadOnlyMessage": "نمی‌توان در ویرایشگر فقط خواندنی ویرایش کرد",
   "Filter": "فیلتر",
@@ -266,7 +255,6 @@
   "Match Case": "تطبیق حروف کوچک و بزرگ",
   "Match Whole Word": "تطبیق کل کلمه",
   "Use Regular Expression": "استفاده از عبارت منظم",
-
   "Profile Imported Successfully": "پروفایل با موفقیت وارد شد",
   "Clash Config Updated": "پیکربندی Clash به‌روزرسانی شد",
   "Profile Switched": "پروفایل تغییر یافت",

+ 2 - 14
src/locales/ru.json

@@ -1,20 +1,17 @@
 {
   "millis": "миллисекунды",
   "mins": "минуты",
-
   "Back": "Назад",
   "Close": "Закрыть",
   "Cancel": "Отмена",
   "Confirm": "Подтвердить",
   "Empty": "Пусто",
-
   "New": "Новый",
   "Edit": "Редактировать",
   "Save": "Сохранить",
   "Delete": "Удалить",
   "Enable": "Включить",
   "Disable": "Отключить",
-
   "Label-Proxies": "Прокси",
   "Label-Profiles": "Профили",
   "Label-Connections": "Соединения",
@@ -22,7 +19,6 @@
   "Label-Logs": "Логи",
   "Label-Test": "Тест",
   "Label-Settings": "Настройки",
-
   "Proxies": "Прокси",
   "Proxy Groups": "Группы прокси",
   "Proxy Provider": "Провайдер прокси",
@@ -41,7 +37,6 @@
   "Delay check to cancel fixed": "Проверка задержки для отмены фиксированного",
   "Proxy basic": "Резюме о прокси",
   "Proxy detail": "Подробности о прокси",
-
   "Profiles": "Профили",
   "Update All Profiles": "Обновить все профили",
   "View Runtime Config": "Просмотреть используемый конфиг",
@@ -55,6 +50,8 @@
   "Expire Time": "Время окончания",
   "Create Profile": "Создать профиль",
   "Edit Profile": "Изменить профиль",
+  "Edit Merge": "Изменить Merge.",
+  "Edit Script": "Изменить Script",
   "Type": "Тип",
   "Name": "Название",
   "Descriptions": "Описания",
@@ -77,7 +74,6 @@
   "Script Console": "Консоль скрипта",
   "To Top": "Наверх",
   "To End": "Вниз",
-
   "Connections": "Соединения",
   "Table View": "Tablichnyy vid",
   "List View": "Spiskovyy vid",
@@ -97,21 +93,17 @@
   "Source": "Исходный адрес",
   "Destination IP": "IP-адрес назначения",
   "Close Connection": "Закрыть соединение",
-
   "Rules": "Правила",
   "Rule Provider": "Провайдер правило",
-
   "Logs": "Логи",
   "Pause": "Пауза",
   "Clear": "Очистить",
-
   "Test": "Тест",
   "Test All": "Тест Все",
   "Create Test": "Создать тест",
   "Edit Test": "Редактировать тест",
   "Icon": "Икона",
   "Test URL": "Тестовый URL",
-
   "Settings": "Настройки",
   "System Setting": "Настройки системы",
   "Tun Mode": "Режим туннеля",
@@ -157,7 +149,6 @@
   "Auto Launch": "Автозапуск",
   "Silent Start": "Тихий запуск",
   "Silent Start Info": "Запускать программу в фоновом режиме без отображения панели",
-
   "Clash Setting": "Настройки Clash",
   "Allow Lan": "Разрешить локальную сеть",
   "IPv6": "IPv6",
@@ -186,7 +177,6 @@
   "Open UWP tool": "Открыть UWP инструмент",
   "Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",
   "Update GeoData": "Обновление GeoData",
-
   "TG Channel": "Канал Telegram",
   "Manual": "Документация",
   "Github Repo": "GitHub репозиторий",
@@ -258,7 +248,6 @@
   "Open Dev Tools": "Открыть инструменты разработчика",
   "Exit": "Выход",
   "Verge Version": "Версия Verge",
-
   "ReadOnly": "Только для чтения",
   "ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",
   "Filter": "Фильтр",
@@ -266,7 +255,6 @@
   "Match Case": "Учитывать регистр",
   "Match Whole Word": "Полное совпадение слова",
   "Use Regular Expression": "Использовать регулярные выражения",
-
   "Profile Imported Successfully": "Профиль успешно импортирован",
   "Clash Config Updated": "Clash конфигурация Обновлена",
   "Profile Switched": "Профиль изменен",

+ 2 - 14
src/locales/zh.json

@@ -1,20 +1,17 @@
 {
   "millis": "毫秒",
   "mins": "分钟",
-
   "Back": "返回",
   "Close": "关闭",
   "Cancel": "取消",
   "Confirm": "确认",
   "Empty": "空空如也",
-
   "New": "新建",
   "Edit": "编辑",
   "Save": "保存",
   "Delete": "删除",
   "Enable": "启用",
   "Disable": "禁用",
-
   "Label-Proxies": "代 理",
   "Label-Profiles": "订 阅",
   "Label-Connections": "连 接",
@@ -22,7 +19,6 @@
   "Label-Logs": "日 志",
   "Label-Test": "测 试",
   "Label-Settings": "设 置",
-
   "Proxies": "代理",
   "Proxy Groups": "代理组",
   "Proxy Provider": "代理集合",
@@ -41,7 +37,6 @@
   "Delay check to cancel fixed": "进行延迟测试,以取消固定",
   "Proxy basic": "隐藏节点细节",
   "Proxy detail": "展示节点细节",
-
   "Profiles": "订阅",
   "Update All Profiles": "更新所有订阅",
   "View Runtime Config": "查看运行时订阅",
@@ -55,6 +50,8 @@
   "Expire Time": "到期时间",
   "Create Profile": "新建配置",
   "Edit Profile": "编辑配置",
+  "Edit Merge": "编辑 Merge",
+  "Edit Script": "编辑 Script",
   "Type": "类型",
   "Name": "名称",
   "Descriptions": "描述",
@@ -77,7 +74,6 @@
   "Script Console": "脚本控制台输出",
   "To Top": "移到最前",
   "To End": "移到末尾",
-
   "Connections": "连接",
   "Table View": "表格视图",
   "List View": "列表视图",
@@ -97,21 +93,17 @@
   "Source": "源地址",
   "Destination IP": "目标地址",
   "Close Connection": "关闭连接",
-
   "Rules": "规则",
   "Rule Provider": "规则集合",
-
   "Logs": "日志",
   "Pause": "暂停",
   "Clear": "清除",
-
   "Test": "测试",
   "Test All": "测试全部",
   "Create Test": "新建测试",
   "Edit Test": "编辑测试",
   "Icon": "图标",
   "Test URL": "测试地址",
-
   "Settings": "设置",
   "System Setting": "系统设置",
   "Tun Mode": "Tun 模式",
@@ -157,7 +149,6 @@
   "Auto Launch": "开机自启",
   "Silent Start": "静默启动",
   "Silent Start Info": "程序启动时以后台模式运行,不显示程序面板",
-
   "TG Channel": "Telegram 频道",
   "Manual": "使用手册",
   "Github Repo": "GitHub 项目地址",
@@ -189,7 +180,6 @@
   "Open UWP tool": "UWP 工具",
   "Open UWP tool Info": "Windows 8开始限制 UWP 应用(如微软商店)直接访问本地主机的网络服务,使用此工具可绕过该限制",
   "Update GeoData": "更新 GeoData",
-
   "Verge Setting": "Verge 设置",
   "Language": "语言设置",
   "Theme Mode": "主题模式",
@@ -258,7 +248,6 @@
   "Open Dev Tools": "打开开发者工具",
   "Exit": "退出",
   "Verge Version": "Verge 版本",
-
   "ReadOnly": "只读",
   "ReadOnlyMessage": "无法在只读模式下编辑",
   "Filter": "过滤节点",
@@ -266,7 +255,6 @@
   "Match Case": "区分大小写",
   "Match Whole Word": "全字匹配",
   "Use Regular Expression": "使用正则表达式",
-
   "Profile Imported Successfully": "导入订阅成功",
   "Clash Config Updated": "Clash 配置已更新",
   "Profile Switched": "订阅已切换",

+ 7 - 102
src/pages/profiles.tsx

@@ -42,7 +42,6 @@ import {
   ProfileViewerRef,
 } from "@/components/profile/profile-viewer";
 import { ProfileItem } from "@/components/profile/profile-item";
-import { ProfileMore } from "@/components/profile/profile-more";
 import { useProfiles } from "@/hooks/use-profiles";
 import { ConfigViewer } from "@/components/setting/mods/config-viewer";
 import { throttle } from "lodash-es";
@@ -105,31 +104,22 @@ const ProfilePage = () => {
     getRuntimeLogs
   );
 
-  const chain = profiles.chain || [];
   const viewerRef = useRef<ProfileViewerRef>(null);
   const configRef = useRef<DialogRef>(null);
 
   // distinguish type
-  const { regularItems, enhanceItems } = useMemo(() => {
+  const profileItems = useMemo(() => {
     const items = profiles.items || [];
-    const chain = profiles.chain || [];
 
     const type1 = ["local", "remote"];
-    const type2 = ["merge", "script"];
 
-    const regularItems = items.filter((i) => i && type1.includes(i.type!));
-    const restItems = items.filter((i) => i && type2.includes(i.type!));
-    const restMap = Object.fromEntries(restItems.map((i) => [i.uid, i]));
-    const enhanceItems = chain
-      .map((i) => restMap[i]!)
-      .filter(Boolean)
-      .concat(restItems.filter((i) => !chain.includes(i.uid)));
+    const profileItems = items.filter((i) => i && type1.includes(i.type!));
 
-    return { regularItems, enhanceItems };
+    return profileItems;
   }, [profiles]);
 
   const currentActivatings = () => {
-    return [...new Set([profiles.current ?? "", ...chain])].filter(Boolean);
+    return [...new Set([profiles.current ?? ""])].filter(Boolean);
   };
 
   const onImport = async () => {
@@ -205,38 +195,9 @@ const ProfilePage = () => {
     }
   });
 
-  const onEnable = useLockFn(async (uid: string) => {
-    if (chain.includes(uid)) return;
-    try {
-      setActivatings([...currentActivatings(), uid]);
-      const newChain = [...chain, uid];
-      await patchProfiles({ chain: newChain });
-      mutateLogs();
-    } catch (err: any) {
-      Notice.error(err.message || err.toString(), 3000);
-    } finally {
-      setActivatings([]);
-    }
-  });
-
-  const onDisable = useLockFn(async (uid: string) => {
-    if (!chain.includes(uid)) return;
-    try {
-      setActivatings([...currentActivatings(), uid]);
-      const newChain = chain.filter((i) => i !== uid);
-      await patchProfiles({ chain: newChain });
-      mutateLogs();
-    } catch (err: any) {
-      Notice.error(err.message || err.toString(), 3000);
-    } finally {
-      setActivatings([]);
-    }
-  });
-
   const onDelete = useLockFn(async (uid: string) => {
     const current = profiles.current === uid;
     try {
-      await onDisable(uid);
       setActivatings([...(current ? currentActivatings() : []), uid]);
       await deleteProfile(uid);
       mutateProfiles();
@@ -249,20 +210,6 @@ const ProfilePage = () => {
     }
   });
 
-  const onMoveTop = useLockFn(async (uid: string) => {
-    if (!chain.includes(uid)) return;
-    const newChain = [uid].concat(chain.filter((i) => i !== uid));
-    await patchProfiles({ chain: newChain });
-    mutateLogs();
-  });
-
-  const onMoveEnd = useLockFn(async (uid: string) => {
-    if (!chain.includes(uid)) return;
-    const newChain = chain.filter((i) => i !== uid).concat([uid]);
-    await patchProfiles({ chain: newChain });
-    mutateLogs();
-  });
-
   // 更新所有订阅
   const setLoadingCache = useSetLoadingCache();
   const onUpdateAll = useLockFn(async () => {
@@ -281,7 +228,7 @@ const ProfilePage = () => {
     return new Promise((resolve) => {
       setLoadingCache((cache) => {
         // 获取没有正在更新的订阅
-        const items = regularItems.filter(
+        const items = profileItems.filter(
           (e) => e.type === "remote" && !cache[e.uid]
         );
         const change = Object.fromEntries(items.map((e) => [e.uid, true]));
@@ -296,11 +243,6 @@ const ProfilePage = () => {
     const text = await readText();
     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 (
     <BasePage
@@ -415,11 +357,11 @@ const ProfilePage = () => {
           <Box sx={{ mb: 1.5 }}>
             <Grid container spacing={{ xs: 1, lg: 1 }}>
               <SortableContext
-                items={regularItems.map((x) => {
+                items={profileItems.map((x) => {
                   return x.uid;
                 })}
               >
-                {regularItems.map((item) => (
+                {profileItems.map((item) => (
                   <Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
                     <ProfileItem
                       id={item.uid}
@@ -441,43 +383,6 @@ const ProfilePage = () => {
             </Grid>
           </Box>
         </DndContext>
-
-        {enhanceItems.length > 0 && (
-          <Divider
-            variant="middle"
-            flexItem
-            sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
-          ></Divider>
-        )}
-
-        {enhanceItems.length > 0 && (
-          <Box sx={{ mt: 1.5 }}>
-            <Grid container spacing={{ xs: 1, lg: 1 }}>
-              {enhanceItems.map((item) => (
-                <Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
-                  <ProfileMore
-                    selected={!!chain.includes(item.uid)}
-                    activating={activatings.includes(item.uid)}
-                    itemData={item}
-                    enableNum={chain.length || 0}
-                    logInfo={chainLogs[item.uid]}
-                    onEnable={() => onEnable(item.uid)}
-                    onDisable={() => onDisable(item.uid)}
-                    onDelete={() => onDelete(item.uid)}
-                    onMoveTop={() => onMoveTop(item.uid)}
-                    onMoveEnd={() => onMoveEnd(item.uid)}
-                    onEdit={() => viewerRef.current?.edit(item)}
-                    onChange={async (prev, curr) => {
-                      if (prev !== curr && chain.includes(item.uid)) {
-                        await onEnhance();
-                      }
-                    }}
-                  />
-                </Grid>
-              ))}
-            </Grid>
-          </Box>
-        )}
       </Box>
       <ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
       <ConfigViewer ref={configRef} />

+ 5 - 73
src/services/types.d.ts

@@ -178,11 +178,15 @@ interface IProfileOption {
   self_proxy?: boolean;
   update_interval?: number;
   danger_accept_invalid_certs?: boolean;
+  merge?: string;
+  script?: string;
+  rules?: string;
+  proxies?: string;
+  groups?: string;
 }
 
 interface IProfilesConfig {
   current?: string;
-  chain?: string[];
   valid?: string[];
   items?: IProfileItem[];
 }
@@ -254,75 +258,3 @@ interface IVergeConfig {
   proxy_layout_column?: number;
   test_list?: IVergeTestItem[];
 }
-
-type IClashConfigValue = any;
-
-interface IProfileMerge {
-  // clash config fields (default supports)
-  rules?: IClashConfigValue;
-  proxies?: IClashConfigValue;
-  "proxy-groups"?: IClashConfigValue;
-  "proxy-providers"?: IClashConfigValue;
-  "rule-providers"?: IClashConfigValue;
-  // clash config fields (use flag)
-  tun?: IClashConfigValue;
-  dns?: IClashConfigValue;
-  hosts?: IClashConfigValue;
-  script?: IClashConfigValue;
-  profile?: IClashConfigValue;
-  payload?: IClashConfigValue;
-  "interface-name"?: IClashConfigValue;
-  "routing-mark"?: IClashConfigValue;
-  // functional fields
-  use?: string[];
-  "prepend-rules"?: any[];
-  "append-rules"?: any[];
-  "prepend-proxies"?: any[];
-  "append-proxies"?: any[];
-  "prepend-proxy-groups"?: any[];
-  "append-proxy-groups"?: any[];
-  // fix
-  ebpf?: any;
-  experimental?: any;
-  iptables?: any;
-  sniffer?: any;
-  authentication?: any;
-  "bind-address"?: any;
-  "external-ui"?: any;
-  "auto-redir"?: any;
-  "socks-port"?: any;
-  "redir-port"?: any;
-  "tproxy-port"?: any;
-  "geodata-mode"?: any;
-  "tcp-concurrent"?: any;
-}
-
-// partial of the clash config
-type IProfileData = Partial<{
-  rules: any[];
-  proxies: any[];
-  "proxy-groups": any[];
-  "proxy-providers": any[];
-  "rule-providers": any[];
-
-  [k: string]: any;
-}>;
-
-interface IChainItem {
-  item: IProfileItem;
-  merge?: IProfileMerge;
-  script?: string;
-}
-
-interface IEnhancedPayload {
-  chain: IChainItem[];
-  valid: string[];
-  current: IProfileData;
-  callback: string;
-}
-
-interface IEnhancedResult {
-  data: IProfileData;
-  status: string;
-  error?: string;
-}