From 4d2b35e09d34427dc663093d2dfbe07ba24e8a21 Mon Sep 17 00:00:00 2001 From: GyDi Date: Tue, 13 Dec 2022 17:34:39 +0800 Subject: [PATCH] feat: support to change proxy layout column --- src-tauri/src/config/verge.rs | 5 + src/components/proxy/proxy-groups.tsx | 11 +- src/components/proxy/proxy-item-mini.tsx | 182 ++++++++++++++++++++ src/components/proxy/proxy-render.tsx | 29 +++- src/components/proxy/use-render-list.ts | 46 ++++- src/components/setting/mods/misc-viewer.tsx | 54 +++++- src/services/types.d.ts | 2 + 7 files changed, 316 insertions(+), 13 deletions(-) create mode 100644 src/components/proxy/proxy-item-mini.tsx diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index 1cb058e..834fe5d 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -68,6 +68,9 @@ pub struct IVerge { /// 是否使用内部的脚本支持,默认为真 pub enable_builtin_enhanced: Option, + + /// proxy 页面布局 列数 + pub proxy_layout_column: Option, } #[derive(Default, Debug, Clone, Deserialize, Serialize)] @@ -117,6 +120,7 @@ impl IVerge { proxy_guard_duration: Some(30), auto_close_connection: Some(true), enable_builtin_enhanced: Some(true), + proxy_layout_column: Some(1), ..Self::default() } } @@ -159,6 +163,7 @@ impl IVerge { patch!(auto_close_connection); patch!(default_latency_test); patch!(enable_builtin_enhanced); + patch!(proxy_layout_column); } /// 在初始化前尝试拿到单例端口的值 diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx index c3cb26b..e852391 100644 --- a/src/components/proxy/proxy-groups.tsx +++ b/src/components/proxy/proxy-groups.tsx @@ -68,8 +68,10 @@ export const ProxyGroups = (props: Props) => { // 测全部延迟 const handleCheckAll = useLockFn(async (groupName: string) => { const proxies = renderList - .filter((e) => e.type === 2 && e.group?.name === groupName) - .map((e) => e.proxy!) + .filter( + (e) => e.group?.name === groupName && (e.type === 2 || e.type === 4) + ) + .flatMap((e) => e.proxyCol || e.proxy!) .filter(Boolean); const providers = new Set(proxies.map((p) => p!.provider!).filter(Boolean)); @@ -92,7 +94,10 @@ export const ProxyGroups = (props: Props) => { const { name, now } = group; const index = renderList.findIndex( - (e) => e.type === 2 && e.group?.name === name && e.proxy?.name === now + (e) => + e.group?.name === name && + ((e.type === 2 && e.proxy?.name === now) || + (e.type === 4 && e.proxyCol?.some((p) => p.name === now))) ); if (index >= 0) { diff --git a/src/components/proxy/proxy-item-mini.tsx b/src/components/proxy/proxy-item-mini.tsx new file mode 100644 index 0000000..3e82c9e --- /dev/null +++ b/src/components/proxy/proxy-item-mini.tsx @@ -0,0 +1,182 @@ +import { useEffect, useState } from "react"; +import { useLockFn } from "ahooks"; +import { CheckCircleOutlineRounded } from "@mui/icons-material"; +import { + alpha, + Box, + ListItemButton, + ListItemIcon, + ListItemText, + styled, +} from "@mui/material"; +import { BaseLoading } from "@/components/base"; +import delayManager from "@/services/delay"; + +interface Props { + groupName: string; + proxy: IProxyItem; + selected: boolean; + showType?: boolean; + onClick?: (name: string) => void; +} + +// 多列布局 +export const ProxyItemMini = (props: Props) => { + const { groupName, proxy, selected, showType = true, onClick } = props; + + // -1/<=0 为 不显示 + // -2 为 loading + const [delay, setDelay] = useState(-1); + + useEffect(() => { + delayManager.setListener(proxy.name, groupName, setDelay); + + return () => { + delayManager.removeListener(proxy.name, groupName); + }; + }, [proxy.name, groupName]); + + useEffect(() => { + if (!proxy) return; + setDelay(delayManager.getDelayFix(proxy, groupName)); + }, [proxy]); + + const onDelay = useLockFn(async () => { + setDelay(-2); + setDelay(await delayManager.checkDelay(proxy.name, groupName)); + }); + + return ( + onClick?.(proxy.name)} + sx={[ + { borderRadius: 1, pl: 1.5, pr: 1 }, + ({ palette: { mode, primary } }) => { + const bgcolor = + mode === "light" + ? alpha(primary.main, 0.15) + : alpha(primary.main, 0.35); + const color = mode === "light" ? primary.main : primary.light; + const showDelay = delay > 0; + + return { + "&:hover .the-check": { display: !showDelay ? "block" : "none" }, + "&:hover .the-delay": { display: showDelay ? "block" : "none" }, + "&:hover .the-icon": { display: "none" }, + "&.Mui-selected": { bgcolor }, + "&.Mui-selected .MuiListItemText-secondary": { color }, + }; + }, + ]} + > + +
+ {proxy.name} +
+ + {showType && ( + <> + {!!proxy.provider && ( + {proxy.provider} + )} + {proxy.type} + {proxy.udp && UDP} + + )} + + } + /> + + + {delay === -2 && ( + + + + )} + + {!proxy.provider && delay !== -2 && ( + // provider的节点不支持检测 + { + e.preventDefault(); + e.stopPropagation(); + onDelay(); + }} + sx={({ palette }) => ({ + display: "none", // hover才显示 + ":hover": { bgcolor: alpha(palette.primary.main, 0.15) }, + })} + > + Check + + )} + + {delay > 0 && ( + // 显示延迟 + { + if (proxy.provider) return; + e.preventDefault(); + e.stopPropagation(); + onDelay(); + }} + color={ + delay > 500 + ? "error.main" + : delay < 100 + ? "success.main" + : "text.secondary" + } + sx={({ palette }) => + !proxy.provider + ? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } } + : {} + } + > + {delay > 1e5 ? "Error" : delay > 3000 ? "Timeout" : `${delay}`} + + )} + + {delay !== -2 && delay <= 0 && selected && ( + // 展示已选择的icon + + )} + +
+ ); +}; + +const Widget = styled(Box)(() => ({ + padding: "3px 6px", + fontSize: 14, + borderRadius: "4px", +})); + +const TypeBox = styled(Box)(({ theme }) => ({ + display: "inline-block", + border: "1px solid #ccc", + borderColor: alpha(theme.palette.text.secondary, 0.36), + color: alpha(theme.palette.text.secondary, 0.42), + borderRadius: 4, + fontSize: 10, + marginRight: "4px", + padding: "0 2px", + lineHeight: 1.25, +})); diff --git a/src/components/proxy/proxy-render.tsx b/src/components/proxy/proxy-render.tsx index 2eb4b5f..b5f50d0 100644 --- a/src/components/proxy/proxy-render.tsx +++ b/src/components/proxy/proxy-render.tsx @@ -14,6 +14,7 @@ import { import { HeadState } from "./use-head-state"; import { ProxyHead } from "./proxy-head"; import { ProxyItem } from "./proxy-item"; +import { ProxyItemMini } from "./proxy-item-mini"; import type { IRenderItem } from "./use-render-list"; interface RenderProps { @@ -28,7 +29,7 @@ interface RenderProps { export const ProxyRender = (props: RenderProps) => { const { indent, item, onLocation, onCheckAll, onHeadState, onChangeProxy } = props; - const { type, group, headState, proxy } = item; + const { type, group, headState, proxy, proxyCol } = item; if (type === 0) { return ( @@ -105,6 +106,32 @@ export const ProxyRender = (props: RenderProps) => { ); } + if (type === 4) { + return ( + + {proxyCol?.map((proxy) => ( + onChangeProxy(group, proxy!)} + /> + ))} + + ); + } + return null; }; diff --git a/src/components/proxy/use-render-list.ts b/src/components/proxy/use-render-list.ts index b11a462..22fc96d 100644 --- a/src/components/proxy/use-render-list.ts +++ b/src/components/proxy/use-render-list.ts @@ -1,6 +1,7 @@ import useSWR from "swr"; -import { getProxies } from "@/services/api"; import { useEffect, useMemo } from "react"; +import { getProxies } from "@/services/api"; +import { useVerge } from "@/hooks/use-verge"; import { filterSort } from "./use-filter-sort"; import { useHeadStateNew, @@ -9,10 +10,13 @@ import { } from "./use-head-state"; export interface IRenderItem { - type: 0 | 1 | 2 | 3; // 组 | head | item | empty + // 组 | head | item | empty | item col + type: 0 | 1 | 2 | 3 | 4; key: string; group: IProxyGroupItem; proxy?: IProxyItem; + col?: number; + proxyCol?: IProxyItem[]; headState?: HeadState; } @@ -23,6 +27,9 @@ export const useRenderList = (mode: string) => { { refreshInterval: 45000 } ); + const { verge } = useVerge(); + const col = verge?.proxy_layout_column || 1; + const [headStates, setHeadState] = useHeadStateNew(); // make sure that fetch the proxies successfully @@ -62,10 +69,24 @@ export const useRenderList = (mode: string) => { headState.sortType ); - ret.push({ type: 1, key: `head${group.name}`, group, headState }); + ret.push({ type: 1, key: `head-${group.name}`, group, headState }); if (!proxies.length) { - ret.push({ type: 3, key: `empty${group.name}`, group, headState }); + ret.push({ type: 3, key: `empty-${group.name}`, group, headState }); + } + + // 支持多列布局 + if (col > 1) { + return ret.concat( + groupList(proxies, col).map((proxyCol) => ({ + type: 4, + key: `col-${group.name}`, + group, + headState, + col, + proxyCol, + })) + ); } return ret.concat( @@ -83,7 +104,7 @@ export const useRenderList = (mode: string) => { if (!useRule) return retList.slice(1); return retList; - }, [headStates, proxiesData, mode]); + }, [headStates, proxiesData, mode, col]); return { renderList, @@ -91,3 +112,18 @@ export const useRenderList = (mode: string) => { onHeadState: setHeadState, }; }; + +function groupList(list: T[], size: number): T[][] { + return list.reduce((p, n) => { + if (!p.length) return [[n]]; + + const i = p.length - 1; + if (p[i].length < size) { + p[i].push(n); + return p; + } + + p.push([n]); + return p; + }, [] as T[][]); +} diff --git a/src/components/setting/mods/misc-viewer.tsx b/src/components/setting/mods/misc-viewer.tsx index 81b235c..02a0692 100644 --- a/src/components/setting/mods/misc-viewer.tsx +++ b/src/components/setting/mods/misc-viewer.tsx @@ -1,7 +1,15 @@ import { forwardRef, useImperativeHandle, useState } from "react"; import { useLockFn } from "ahooks"; import { useTranslation } from "react-i18next"; -import { List, ListItem, ListItemText, Switch, TextField } from "@mui/material"; +import { + List, + ListItem, + ListItemText, + MenuItem, + Select, + Switch, + TextField, +} from "@mui/material"; import { useVerge } from "@/hooks/use-verge"; import { BaseDialog, DialogRef, Notice } from "@/components/base"; @@ -12,6 +20,8 @@ export const MiscViewer = forwardRef((props, ref) => { const [open, setOpen] = useState(false); const [values, setValues] = useState({ autoCloseConnection: false, + enableBuiltinEnhanced: true, + proxyLayoutColumn: 1, defaultLatencyTest: "", }); @@ -19,7 +29,9 @@ export const MiscViewer = forwardRef((props, ref) => { open: () => { setOpen(true); setValues({ - autoCloseConnection: verge?.auto_close_connection || false, + autoCloseConnection: verge?.auto_close_connection ?? false, + enableBuiltinEnhanced: verge?.enable_builtin_enhanced ?? true, + proxyLayoutColumn: verge?.proxy_layout_column || 1, defaultLatencyTest: verge?.default_latency_test || "", }); }, @@ -30,6 +42,8 @@ export const MiscViewer = forwardRef((props, ref) => { try { await patchVerge({ auto_close_connection: values.autoCloseConnection, + enable_builtin_enhanced: values.enableBuiltinEnhanced, + proxy_layout_column: values.proxyLayoutColumn, default_latency_test: values.defaultLatencyTest, }); setOpen(false); @@ -42,7 +56,7 @@ export const MiscViewer = forwardRef((props, ref) => { setOpen(false)} @@ -61,6 +75,38 @@ export const MiscViewer = forwardRef((props, ref) => { /> + + + + setValues((v) => ({ ...v, enableBuiltinEnhanced: c })) + } + /> + + + + + + + ((props, ref) => { autoCorrect="off" autoCapitalize="off" spellCheck="false" - sx={{ width: 200 }} + sx={{ width: 250 }} value={values.defaultLatencyTest} placeholder="http://www.gstatic.com/generate_204" onChange={(e) => diff --git a/src/services/types.d.ts b/src/services/types.d.ts index 35fb437..de3541d 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -163,6 +163,8 @@ interface IVergeConfig { }; auto_close_connection?: boolean; default_latency_test?: string; + enable_builtin_enhanced?: boolean; + proxy_layout_column?: number; } type IClashConfigValue = any;