feat: tray enhance (#5058)
* feat: proxy group sorting for tray * feat(tray): add inline proxy groups toggle
This commit is contained in:
@@ -198,6 +198,9 @@ pub struct IVerge {
|
||||
|
||||
pub enable_tray_icon: Option<bool>,
|
||||
|
||||
/// show proxy groups directly on tray root menu
|
||||
pub tray_inline_proxy_groups: Option<bool>,
|
||||
|
||||
/// 自动进入轻量模式
|
||||
pub enable_auto_light_weight_mode: Option<bool>,
|
||||
|
||||
@@ -398,6 +401,7 @@ impl IVerge {
|
||||
webdav_password: None,
|
||||
enable_tray_speed: Some(false),
|
||||
enable_tray_icon: Some(true),
|
||||
tray_inline_proxy_groups: Some(false),
|
||||
enable_global_hotkey: Some(true),
|
||||
enable_auto_light_weight_mode: Some(false),
|
||||
auto_light_weight_minutes: Some(10),
|
||||
@@ -489,6 +493,7 @@ impl IVerge {
|
||||
patch!(webdav_password);
|
||||
patch!(enable_tray_speed);
|
||||
patch!(enable_tray_icon);
|
||||
patch!(tray_inline_proxy_groups);
|
||||
patch!(enable_auto_light_weight_mode);
|
||||
patch!(auto_light_weight_minutes);
|
||||
patch!(enable_dns_settings);
|
||||
@@ -585,6 +590,7 @@ pub struct IVergeResponse {
|
||||
pub webdav_password: Option<String>,
|
||||
pub enable_tray_speed: Option<bool>,
|
||||
pub enable_tray_icon: Option<bool>,
|
||||
pub tray_inline_proxy_groups: Option<bool>,
|
||||
pub enable_auto_light_weight_mode: Option<bool>,
|
||||
pub auto_light_weight_minutes: Option<u64>,
|
||||
pub enable_dns_settings: Option<bool>,
|
||||
@@ -658,6 +664,7 @@ impl From<IVerge> for IVergeResponse {
|
||||
webdav_password: verge.webdav_password,
|
||||
enable_tray_speed: verge.enable_tray_speed,
|
||||
enable_tray_icon: verge.enable_tray_icon,
|
||||
tray_inline_proxy_groups: verge.tray_inline_proxy_groups,
|
||||
enable_auto_light_weight_mode: verge.enable_auto_light_weight_mode,
|
||||
auto_light_weight_minutes: verge.auto_light_weight_minutes,
|
||||
enable_dns_settings: verge.enable_dns_settings,
|
||||
|
||||
@@ -605,11 +605,46 @@ async fn create_tray_menu(
|
||||
|
||||
let proxy_nodes_data = handle::Handle::mihomo().await.get_proxies().await;
|
||||
|
||||
let runtime_proxy_groups_order = cmd::get_runtime_config()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
logging!(
|
||||
error,
|
||||
Type::Cmd,
|
||||
"Failed to fetch runtime proxy groups for tray menu: {e}"
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|config| {
|
||||
config
|
||||
.get("proxy-groups")
|
||||
.and_then(|groups| groups.as_sequence())
|
||||
.map(|groups| {
|
||||
groups
|
||||
.iter()
|
||||
.filter_map(|group| group.get("name"))
|
||||
.filter_map(|name| name.as_str())
|
||||
.map(|name| name.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let proxy_group_order_map = runtime_proxy_groups_order.as_ref().map(|group_names| {
|
||||
group_names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, name)| (name.clone(), index))
|
||||
.collect::<HashMap<String, usize>>()
|
||||
});
|
||||
|
||||
let verge_settings = Config::verge().await.latest_ref().clone();
|
||||
let show_proxy_groups_inline = verge_settings.tray_inline_proxy_groups.unwrap_or(false);
|
||||
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
let hotkeys = Config::verge()
|
||||
.await
|
||||
.latest_ref()
|
||||
let hotkeys = verge_settings
|
||||
.hotkeys
|
||||
.as_ref()
|
||||
.map(|h| {
|
||||
@@ -653,8 +688,7 @@ async fn create_tray_menu(
|
||||
|
||||
// 代理组子菜单
|
||||
let proxy_submenus: Vec<Submenu<Wry>> = {
|
||||
let mut submenus = Vec::new();
|
||||
let mut group_name_submenus_hash = HashMap::new();
|
||||
let mut submenus: Vec<(String, usize, Submenu<Wry>)> = Vec::new();
|
||||
|
||||
// TODO: 应用启动时,内核还未启动完全,无法获取代理节点信息
|
||||
if let Ok(proxy_nodes_data) = proxy_nodes_data {
|
||||
@@ -745,53 +779,32 @@ async fn create_tray_menu(
|
||||
true,
|
||||
&group_items_refs,
|
||||
) {
|
||||
group_name_submenus_hash.insert(group_name.to_string(), submenu);
|
||||
let insertion_index = submenus.len();
|
||||
submenus.push((group_name.to_string(), insertion_index, submenu));
|
||||
} else {
|
||||
log::warn!(target: "app", "创建代理组子菜单失败: {}", group_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取运行时代理组配置
|
||||
let runtime_proxy_groups_config = cmd::get_runtime_config()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
logging!(
|
||||
error,
|
||||
Type::Cmd,
|
||||
"Failed to fetch runtime proxy groups for tray menu: {e}"
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|config| {
|
||||
config
|
||||
.get("proxy-groups")
|
||||
.and_then(|groups| groups.as_sequence())
|
||||
.map(|groups| {
|
||||
groups
|
||||
.iter()
|
||||
.filter_map(|group| group.get("name"))
|
||||
.filter_map(|name| name.as_str())
|
||||
.map(|name| name.into())
|
||||
.collect::<Vec<String>>()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
if let Some(runtime_proxy_groups_config) = runtime_proxy_groups_config {
|
||||
for group_name in runtime_proxy_groups_config {
|
||||
if let Some(submenu) = group_name_submenus_hash.get(&group_name) {
|
||||
submenus.push(submenu.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (_, submenu) in group_name_submenus_hash {
|
||||
submenus.push(submenu);
|
||||
}
|
||||
if let Some(order_map) = proxy_group_order_map.as_ref() {
|
||||
submenus.sort_by(
|
||||
|(name_a, original_index_a, _), (name_b, original_index_b, _)| match (
|
||||
order_map.get(name_a),
|
||||
order_map.get(name_b),
|
||||
) {
|
||||
(Some(index_a), Some(index_b)) => index_a.cmp(index_b),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => original_index_a.cmp(original_index_b),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
submenus
|
||||
.into_iter()
|
||||
.map(|(_, _, submenu)| submenu)
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Pre-fetch all localized strings
|
||||
@@ -865,22 +878,34 @@ async fn create_tray_menu(
|
||||
)?;
|
||||
|
||||
// 创建代理主菜单
|
||||
let proxies_submenu = if !proxy_submenus.is_empty() {
|
||||
let proxy_submenu_refs: Vec<&dyn IsMenuItem<Wry>> = proxy_submenus
|
||||
.iter()
|
||||
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
|
||||
.collect();
|
||||
let (proxies_submenu, inline_proxy_items): (Option<Submenu<Wry>>, Vec<&dyn IsMenuItem<Wry>>) =
|
||||
if show_proxy_groups_inline {
|
||||
(
|
||||
None,
|
||||
proxy_submenus
|
||||
.iter()
|
||||
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
|
||||
.collect(),
|
||||
)
|
||||
} else if !proxy_submenus.is_empty() {
|
||||
let proxy_submenu_refs: Vec<&dyn IsMenuItem<Wry>> = proxy_submenus
|
||||
.iter()
|
||||
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
|
||||
.collect();
|
||||
|
||||
Some(Submenu::with_id_and_items(
|
||||
app_handle,
|
||||
"proxies",
|
||||
proxies_text,
|
||||
true,
|
||||
&proxy_submenu_refs,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(
|
||||
Some(Submenu::with_id_and_items(
|
||||
app_handle,
|
||||
"proxies",
|
||||
proxies_text,
|
||||
true,
|
||||
&proxy_submenu_refs,
|
||||
)?),
|
||||
Vec::new(),
|
||||
)
|
||||
} else {
|
||||
(None, Vec::new())
|
||||
};
|
||||
|
||||
let system_proxy = &CheckMenuItem::with_id(
|
||||
app_handle,
|
||||
@@ -991,7 +1016,11 @@ async fn create_tray_menu(
|
||||
];
|
||||
|
||||
// 如果有代理节点,添加代理节点菜单
|
||||
if let Some(ref proxies_menu) = proxies_submenu {
|
||||
if show_proxy_groups_inline {
|
||||
if !inline_proxy_items.is_empty() {
|
||||
menu_items.extend_from_slice(&inline_proxy_items);
|
||||
}
|
||||
} else if let Some(ref proxies_menu) = proxies_submenu {
|
||||
menu_items.push(proxies_menu);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useWindowDecorations } from "@/hooks/use-window";
|
||||
import { copyIconFile, getAppDir } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { GuardState } from "./guard-state";
|
||||
|
||||
const OS = getSystem();
|
||||
@@ -263,6 +264,19 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</GuardState>
|
||||
</Item>
|
||||
)}
|
||||
<Item>
|
||||
<ListItemText primary={t("Show Proxy Groups Inline")} />
|
||||
<GuardState
|
||||
value={verge?.tray_inline_proxy_groups ?? false}
|
||||
valueProps="checked"
|
||||
onCatch={onError}
|
||||
onFormat={onSwitchFormat}
|
||||
onChange={(e) => onChangeData({ tray_inline_proxy_groups: e })}
|
||||
onGuard={(e) => patchVerge({ tray_inline_proxy_groups: e })}
|
||||
>
|
||||
<Switch edge="end" />
|
||||
</GuardState>
|
||||
</Item>
|
||||
|
||||
<Item>
|
||||
<ListItemText primary={t("Common Tray Icon")} />
|
||||
|
||||
@@ -512,6 +512,7 @@
|
||||
"Direct Mode": "Direct Mode",
|
||||
"Enable Tray Speed": "Enable Tray Speed",
|
||||
"Enable Tray Icon": "Enable Tray Icon",
|
||||
"Show Proxy Groups Inline": "Show Proxy Groups Inline",
|
||||
"LightWeight Mode": "Lightweight Mode",
|
||||
"LightWeight Mode Info": "Close the GUI and keep only the kernel running",
|
||||
"LightWeight Mode Settings": "LightWeight Mode Settings",
|
||||
|
||||
@@ -512,6 +512,7 @@
|
||||
"Direct Mode": "直连模式",
|
||||
"Enable Tray Speed": "启用托盘速率",
|
||||
"Enable Tray Icon": "启用托盘图标",
|
||||
"Show Proxy Groups Inline": "将代理组显示在托盘一级菜单",
|
||||
"LightWeight Mode": "轻量模式",
|
||||
"LightWeight Mode Info": "关闭GUI界面,仅保留内核运行",
|
||||
"LightWeight Mode Settings": "轻量模式设置",
|
||||
|
||||
@@ -437,6 +437,7 @@
|
||||
"Direct Mode": "直連模式",
|
||||
"Enable Tray Speed": "啟用托盤速率",
|
||||
"Enable Tray Icon": "啟用托盤圖標",
|
||||
"Show Proxy Groups Inline": "將代理組顯示在托盤一級選單",
|
||||
"LightWeight Mode": "輕量模式",
|
||||
"LightWeight Mode Info": "關閉GUI界面,僅保留內核運行",
|
||||
"LightWeight Mode Settings": "輕量模式設置",
|
||||
|
||||
1
src/services/types.d.ts
vendored
1
src/services/types.d.ts
vendored
@@ -802,6 +802,7 @@ interface IVergeConfig {
|
||||
tun_tray_icon?: boolean;
|
||||
enable_tray_speed?: boolean;
|
||||
enable_tray_icon?: boolean;
|
||||
tray_inline_proxy_groups?: boolean;
|
||||
enable_tun_mode?: boolean;
|
||||
enable_auto_light_weight_mode?: boolean;
|
||||
auto_light_weight_minutes?: number;
|
||||
|
||||
Reference in New Issue
Block a user