mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 08:45:41 +08:00
fix(current-proxy-card): stabilize match rule lookup and dependencies (#5138)
- memoize policy name normalization and include it in hook deps - guard MATCH rule checks against partial controller data - register MATCH policy groups when rebuilding selector selectors
This commit is contained in:
@@ -100,7 +100,7 @@ export const CurrentProxyCard = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { proxies, clashConfig, refreshProxy } = useAppData();
|
const { proxies, clashConfig, refreshProxy, rules } = useAppData();
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const { current: currentProfile } = useProfiles();
|
const { current: currentProfile } = useProfiles();
|
||||||
const autoDelayEnabled = verge?.enable_auto_delay_detection ?? false;
|
const autoDelayEnabled = verge?.enable_auto_delay_detection ?? false;
|
||||||
@@ -163,17 +163,47 @@ export const CurrentProxyCard = () => {
|
|||||||
const isGlobalMode = mode === "global";
|
const isGlobalMode = mode === "global";
|
||||||
const isDirectMode = mode === "direct";
|
const isDirectMode = mode === "direct";
|
||||||
|
|
||||||
// 添加排序类型状态
|
// Sorting type state
|
||||||
const [sortType, setSortType] = useState<ProxySortType>(() => {
|
const [sortType, setSortType] = useState<ProxySortType>(() => {
|
||||||
const savedSortType = localStorage.getItem(STORAGE_KEY_SORT_TYPE);
|
const savedSortType = localStorage.getItem(STORAGE_KEY_SORT_TYPE);
|
||||||
return savedSortType ? (Number(savedSortType) as ProxySortType) : 0;
|
return savedSortType ? (Number(savedSortType) as ProxySortType) : 0;
|
||||||
});
|
});
|
||||||
const [delaySortRefresh, setDelaySortRefresh] = useState(0);
|
const [delaySortRefresh, setDelaySortRefresh] = useState(0);
|
||||||
|
|
||||||
// 定义状态类型
|
const normalizePolicyName = useCallback(
|
||||||
|
(value?: string | null) => (typeof value === "string" ? value.trim() : ""),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const matchPolicyName = useMemo(() => {
|
||||||
|
if (!Array.isArray(rules)) return "";
|
||||||
|
for (let index = rules.length - 1; index >= 0; index -= 1) {
|
||||||
|
const rule = rules[index];
|
||||||
|
if (!rule) continue;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof rule?.type === "string" &&
|
||||||
|
rule.type.toUpperCase() === "MATCH"
|
||||||
|
) {
|
||||||
|
const policy = normalizePolicyName(rule.proxy);
|
||||||
|
if (policy) {
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}, [rules, normalizePolicyName]);
|
||||||
|
|
||||||
|
type ProxyGroupOption = {
|
||||||
|
name: string;
|
||||||
|
now: string;
|
||||||
|
all: string[];
|
||||||
|
type?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type ProxyState = {
|
type ProxyState = {
|
||||||
proxyData: {
|
proxyData: {
|
||||||
groups: { name: string; now: string; all: string[] }[];
|
groups: ProxyGroupOption[];
|
||||||
records: Record<string, any>;
|
records: Record<string, any>;
|
||||||
};
|
};
|
||||||
selection: {
|
selection: {
|
||||||
@@ -279,26 +309,66 @@ export const CurrentProxyCard = () => {
|
|||||||
|
|
||||||
// eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect
|
// eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect
|
||||||
setState((prev) => {
|
setState((prev) => {
|
||||||
// 只保留 Selector 类型的组用于选择
|
const groupsMap = new Map<string, ProxyGroupOption>();
|
||||||
const filteredGroups = proxies.groups
|
|
||||||
.filter((g: { name: string; type?: string }) => g.type === "Selector")
|
const registerGroup = (group: any, fallbackName?: string) => {
|
||||||
.map(
|
if (!group && !fallbackName) return;
|
||||||
(g: { name: string; now: string; all: Array<{ name: string }> }) => ({
|
|
||||||
name: g.name,
|
const rawName =
|
||||||
now: g.now || "",
|
typeof group?.name === "string" && group.name.length > 0
|
||||||
all: g.all.map((p: { name: string }) => p.name),
|
? group.name
|
||||||
}),
|
: fallbackName;
|
||||||
);
|
const name = normalizePolicyName(rawName);
|
||||||
|
if (!name || groupsMap.has(name)) return;
|
||||||
|
|
||||||
|
const rawAll = (
|
||||||
|
Array.isArray(group?.all)
|
||||||
|
? (group.all as Array<string | { name?: string }>)
|
||||||
|
: []
|
||||||
|
) as Array<string | { name?: string }>;
|
||||||
|
const allNames = rawAll
|
||||||
|
.map((item) =>
|
||||||
|
typeof item === "string"
|
||||||
|
? normalizePolicyName(item)
|
||||||
|
: normalizePolicyName(item?.name),
|
||||||
|
)
|
||||||
|
.filter((value): value is string => value.length > 0);
|
||||||
|
|
||||||
|
const uniqueAll = Array.from(new Set(allNames));
|
||||||
|
if (uniqueAll.length === 0) return;
|
||||||
|
|
||||||
|
groupsMap.set(name, {
|
||||||
|
name,
|
||||||
|
now: normalizePolicyName(group?.now),
|
||||||
|
all: uniqueAll,
|
||||||
|
type: group?.type,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (matchPolicyName) {
|
||||||
|
const matchGroup =
|
||||||
|
proxies.groups?.find(
|
||||||
|
(g: { name: string }) => g.name === matchPolicyName,
|
||||||
|
) ||
|
||||||
|
(proxies.global?.name === matchPolicyName ? proxies.global : null) ||
|
||||||
|
proxies.records?.[matchPolicyName];
|
||||||
|
registerGroup(matchGroup, matchPolicyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
(proxies.groups || [])
|
||||||
|
.filter((g: { type?: string }) => g?.type === "Selector")
|
||||||
|
.forEach((selectorGroup: any) => registerGroup(selectorGroup));
|
||||||
|
|
||||||
|
const filteredGroups = Array.from(groupsMap.values());
|
||||||
|
|
||||||
let newProxy = "";
|
let newProxy = "";
|
||||||
let newDisplayProxy = null;
|
let newDisplayProxy = null;
|
||||||
let newGroup = prev.selection.group;
|
let newGroup = prev.selection.group;
|
||||||
|
|
||||||
// 根据模式确定新代理
|
|
||||||
if (isDirectMode) {
|
if (isDirectMode) {
|
||||||
newGroup = "DIRECT";
|
newGroup = "DIRECT";
|
||||||
newProxy = "DIRECT";
|
newProxy = "DIRECT";
|
||||||
newDisplayProxy = proxies.records?.DIRECT || { name: "DIRECT" }; // 确保非空
|
newDisplayProxy = proxies.records?.DIRECT || { name: "DIRECT" };
|
||||||
} else if (isGlobalMode && proxies.global) {
|
} else if (isGlobalMode && proxies.global) {
|
||||||
newGroup = "GLOBAL";
|
newGroup = "GLOBAL";
|
||||||
newProxy = proxies.global.now || "";
|
newProxy = proxies.global.now || "";
|
||||||
@@ -308,12 +378,11 @@ export const CurrentProxyCard = () => {
|
|||||||
(g: { name: string }) => g.name === prev.selection.group,
|
(g: { name: string }) => g.name === prev.selection.group,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 如果当前组不存在或为空,自动选择第一个 selector 类型的组
|
|
||||||
if (!currentGroup && filteredGroups.length > 0) {
|
if (!currentGroup && filteredGroups.length > 0) {
|
||||||
const selectorGroup = filteredGroups[0];
|
const firstGroup = filteredGroups[0];
|
||||||
if (selectorGroup) {
|
if (firstGroup) {
|
||||||
newGroup = selectorGroup.name;
|
newGroup = firstGroup.name;
|
||||||
newProxy = selectorGroup.now || selectorGroup.all[0] || "";
|
newProxy = firstGroup.now || firstGroup.all[0] || "";
|
||||||
newDisplayProxy = proxies.records?.[newProxy] || null;
|
newDisplayProxy = proxies.records?.[newProxy] || null;
|
||||||
|
|
||||||
if (!isGlobalMode && !isDirectMode) {
|
if (!isGlobalMode && !isDirectMode) {
|
||||||
@@ -329,7 +398,6 @@ export const CurrentProxyCard = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回新状态
|
|
||||||
return {
|
return {
|
||||||
proxyData: {
|
proxyData: {
|
||||||
groups: filteredGroups,
|
groups: filteredGroups,
|
||||||
@@ -342,7 +410,14 @@ export const CurrentProxyCard = () => {
|
|||||||
displayProxy: newDisplayProxy,
|
displayProxy: newDisplayProxy,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [proxies, isGlobalMode, isDirectMode, writeProfileScopedItem]);
|
}, [
|
||||||
|
proxies,
|
||||||
|
isGlobalMode,
|
||||||
|
isDirectMode,
|
||||||
|
writeProfileScopedItem,
|
||||||
|
normalizePolicyName,
|
||||||
|
matchPolicyName,
|
||||||
|
]);
|
||||||
|
|
||||||
// 使用防抖包装状态更新
|
// 使用防抖包装状态更新
|
||||||
const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user