code formatting with prettier
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { cn } from "@root/lib/utils";
|
||||
|
||||
// Компоненты и иконки
|
||||
@@ -9,23 +9,28 @@ import {
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { ChevronsUpDown, Timer, WholeWord } from 'lucide-react';
|
||||
} from "@/components/ui/select";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { ChevronsUpDown, Timer, WholeWord } from "lucide-react";
|
||||
|
||||
// Логика
|
||||
import { useVerge } from '@/hooks/use-verge';
|
||||
import { useAppData } from '@/providers/app-data-provider';
|
||||
import delayManager from '@/services/delay';
|
||||
import { updateProxy, deleteConnection } from '@/services/api';
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useAppData } from "@/providers/app-data-provider";
|
||||
import delayManager from "@/services/delay";
|
||||
import { updateProxy, deleteConnection } from "@/services/api";
|
||||
|
||||
// --- Типы и константы ---
|
||||
const STORAGE_KEY_GROUP = 'clash-verge-selected-proxy-group';
|
||||
const STORAGE_KEY_SORT_TYPE = 'clash-verge-proxy-sort-type';
|
||||
const STORAGE_KEY_GROUP = "clash-verge-selected-proxy-group";
|
||||
const STORAGE_KEY_SORT_TYPE = "clash-verge-proxy-sort-type";
|
||||
const presetList = ["DIRECT", "REJECT", "REJECT-DROP", "PASS", "COMPATIBLE"];
|
||||
type ProxySortType = 'default' | 'delay' | 'name';
|
||||
type ProxySortType = "default" | "delay" | "name";
|
||||
interface IProxyGroup {
|
||||
name: string;
|
||||
type: string;
|
||||
@@ -35,15 +40,25 @@ interface IProxyGroup {
|
||||
}
|
||||
|
||||
// --- Вспомогательная функция для цвета задержки ---
|
||||
function getDelayBadgeVariant(delayValue: number): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
if (delayValue < 0) return 'secondary';
|
||||
if (delayValue >= 150) return 'destructive';
|
||||
return 'default';
|
||||
function getDelayBadgeVariant(
|
||||
delayValue: number,
|
||||
): "default" | "secondary" | "destructive" | "outline" {
|
||||
if (delayValue < 0) return "secondary";
|
||||
if (delayValue >= 150) return "destructive";
|
||||
return "default";
|
||||
}
|
||||
|
||||
// --- Дочерний компонент для элемента списка с "живым" обновлением пинга ---
|
||||
const ProxySelectItem = ({ proxyName, groupName }: { proxyName: string, groupName: string }) => {
|
||||
const [delay, setDelay] = useState(() => delayManager.getDelay(proxyName, groupName));
|
||||
const ProxySelectItem = ({
|
||||
proxyName,
|
||||
groupName,
|
||||
}: {
|
||||
proxyName: string;
|
||||
groupName: string;
|
||||
}) => {
|
||||
const [delay, setDelay] = useState(() =>
|
||||
delayManager.getDelay(proxyName, groupName),
|
||||
);
|
||||
const [isJustUpdated, setIsJustUpdated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,44 +86,62 @@ const ProxySelectItem = ({ proxyName, groupName }: { proxyName: string, groupNam
|
||||
variant={getDelayBadgeVariant(delay)}
|
||||
className={cn(
|
||||
"ml-4 flex-shrink-0 px-2 h-5 justify-center transition-colors duration-300",
|
||||
isJustUpdated && "bg-primary/20 border-primary/50"
|
||||
isJustUpdated && "bg-primary/20 border-primary/50",
|
||||
)}
|
||||
>
|
||||
{(delay < 0) || (delay > 10000) ? '---' : delay}
|
||||
{delay < 0 || delay > 10000 ? "---" : delay}
|
||||
</Badge>
|
||||
</div>
|
||||
</SelectItem>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const ProxySelectors: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { verge } = useVerge();
|
||||
const { proxies, connections, clashConfig, refreshProxy } = useAppData();
|
||||
|
||||
const mode = clashConfig?.mode?.toLowerCase() || 'rule';
|
||||
const isGlobalMode = mode === 'global';
|
||||
const isDirectMode = mode ==='direct';
|
||||
const mode = clashConfig?.mode?.toLowerCase() || "rule";
|
||||
const isGlobalMode = mode === "global";
|
||||
const isDirectMode = mode === "direct";
|
||||
|
||||
const [selectedGroup, setSelectedGroup] = useState<string>('');
|
||||
const [selectedProxy, setSelectedProxy] = useState<string>('');
|
||||
const [sortType, setSortType] = useState<ProxySortType>(() => (localStorage.getItem(STORAGE_KEY_SORT_TYPE) as ProxySortType) || 'default');
|
||||
const [selectedGroup, setSelectedGroup] = useState<string>("");
|
||||
const [selectedProxy, setSelectedProxy] = useState<string>("");
|
||||
const [sortType, setSortType] = useState<ProxySortType>(
|
||||
() =>
|
||||
(localStorage.getItem(STORAGE_KEY_SORT_TYPE) as ProxySortType) ||
|
||||
"default",
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!proxies?.groups) return;
|
||||
if (isGlobalMode) { setSelectedGroup('GLOBAL'); return; }
|
||||
if (isDirectMode) { setSelectedGroup('DIRECT'); return; }
|
||||
if (isGlobalMode) {
|
||||
setSelectedGroup("GLOBAL");
|
||||
return;
|
||||
}
|
||||
if (isDirectMode) {
|
||||
setSelectedGroup("DIRECT");
|
||||
return;
|
||||
}
|
||||
|
||||
const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP);
|
||||
const primaryGroup = proxies.groups.find((g: IProxyGroup) => g.type === 'Selector' && g.name.toLowerCase().includes('auto')) || proxies.groups.find((g: IProxyGroup) => g.type === 'Selector');
|
||||
const primaryGroup =
|
||||
proxies.groups.find(
|
||||
(g: IProxyGroup) =>
|
||||
g.type === "Selector" && g.name.toLowerCase().includes("auto"),
|
||||
) || proxies.groups.find((g: IProxyGroup) => g.type === "Selector");
|
||||
|
||||
if (savedGroup && proxies.groups.some((g: IProxyGroup) => g.name === savedGroup)) {
|
||||
if (
|
||||
savedGroup &&
|
||||
proxies.groups.some((g: IProxyGroup) => g.name === savedGroup)
|
||||
) {
|
||||
setSelectedGroup(savedGroup);
|
||||
} else if (primaryGroup) {
|
||||
setSelectedGroup(primaryGroup.name);
|
||||
} else if (proxies.groups.length > 0) {
|
||||
const firstSelector = proxies.groups.find((g: IProxyGroup) => g.type === 'Selector');
|
||||
const firstSelector = proxies.groups.find(
|
||||
(g: IProxyGroup) => g.type === "Selector",
|
||||
);
|
||||
if (firstSelector) {
|
||||
setSelectedGroup(firstSelector.name);
|
||||
}
|
||||
@@ -117,38 +150,56 @@ export const ProxySelectors: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedGroup || !proxies) return;
|
||||
if (isGlobalMode) { setSelectedProxy(proxies.global?.now || ''); return; }
|
||||
if (isDirectMode) { setSelectedProxy('DIRECT'); return; }
|
||||
const group = proxies.groups.find((g: IProxyGroup) => g.name === selectedGroup);
|
||||
if (isGlobalMode) {
|
||||
setSelectedProxy(proxies.global?.now || "");
|
||||
return;
|
||||
}
|
||||
if (isDirectMode) {
|
||||
setSelectedProxy("DIRECT");
|
||||
return;
|
||||
}
|
||||
const group = proxies.groups.find(
|
||||
(g: IProxyGroup) => g.name === selectedGroup,
|
||||
);
|
||||
if (group) {
|
||||
const current = group.now;
|
||||
const firstInList = typeof group.all?.[0] === 'string' ? group.all[0] : group.all?.[0]?.name;
|
||||
setSelectedProxy(current || firstInList || '');
|
||||
const firstInList =
|
||||
typeof group.all?.[0] === "string"
|
||||
? group.all[0]
|
||||
: group.all?.[0]?.name;
|
||||
setSelectedProxy(current || firstInList || "");
|
||||
}
|
||||
}, [selectedGroup, proxies, isGlobalMode, isDirectMode]);
|
||||
|
||||
const handleProxyListOpen = useCallback((isOpen: boolean) => {
|
||||
if (!isOpen || isDirectMode) return;
|
||||
const handleProxyListOpen = useCallback(
|
||||
(isOpen: boolean) => {
|
||||
if (!isOpen || isDirectMode) return;
|
||||
|
||||
const timeout = verge?.default_latency_timeout || 5000;
|
||||
const timeout = verge?.default_latency_timeout || 5000;
|
||||
|
||||
if (isGlobalMode) {
|
||||
const proxyList = proxies?.global?.all;
|
||||
if (proxyList) {
|
||||
const proxyNames = proxyList
|
||||
.map((p: any) => (typeof p === 'string' ? p : p.name))
|
||||
.filter((name: string) => name && !presetList.includes(name));
|
||||
if (isGlobalMode) {
|
||||
const proxyList = proxies?.global?.all;
|
||||
if (proxyList) {
|
||||
const proxyNames = proxyList
|
||||
.map((p: any) => (typeof p === "string" ? p : p.name))
|
||||
.filter((name: string) => name && !presetList.includes(name));
|
||||
|
||||
delayManager.checkListDelay(proxyNames, 'GLOBAL', timeout);
|
||||
delayManager.checkListDelay(proxyNames, "GLOBAL", timeout);
|
||||
}
|
||||
} else {
|
||||
const group = proxies?.groups?.find(
|
||||
(g: IProxyGroup) => g.name === selectedGroup,
|
||||
);
|
||||
if (group && group.all) {
|
||||
const proxyNames = group.all
|
||||
.map((p: any) => (typeof p === "string" ? p : p.name))
|
||||
.filter(Boolean);
|
||||
delayManager.checkListDelay(proxyNames, selectedGroup, timeout);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const group = proxies?.groups?.find((g: IProxyGroup) => g.name === selectedGroup);
|
||||
if (group && group.all) {
|
||||
const proxyNames = group.all.map((p: any) => typeof p === 'string' ? p : p.name).filter(Boolean);
|
||||
delayManager.checkListDelay(proxyNames, selectedGroup, timeout);
|
||||
}
|
||||
}
|
||||
}, [selectedGroup, proxies, isGlobalMode, isDirectMode, verge]);
|
||||
},
|
||||
[selectedGroup, proxies, isGlobalMode, isDirectMode, verge],
|
||||
);
|
||||
|
||||
const handleGroupChange = (newGroup: string) => {
|
||||
if (isGlobalMode || isDirectMode) return;
|
||||
@@ -176,7 +227,11 @@ export const ProxySelectors: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleSortChange = () => {
|
||||
const nextSort: Record<ProxySortType, ProxySortType> = { default: 'delay', delay: 'name', name: 'default' };
|
||||
const nextSort: Record<ProxySortType, ProxySortType> = {
|
||||
default: "delay",
|
||||
delay: "name",
|
||||
name: "default",
|
||||
};
|
||||
const newSortType = nextSort[sortType];
|
||||
setSortType(newSortType);
|
||||
localStorage.setItem(STORAGE_KEY_SORT_TYPE, newSortType);
|
||||
@@ -187,27 +242,31 @@ export const ProxySelectors: React.FC = () => {
|
||||
|
||||
const allowedTypes = ["Selector", "URLTest", "Fallback"];
|
||||
|
||||
return proxies.groups.filter((g: IProxyGroup) =>
|
||||
allowedTypes.includes(g.type) &&
|
||||
!g.hidden
|
||||
return proxies.groups.filter(
|
||||
(g: IProxyGroup) => allowedTypes.includes(g.type) && !g.hidden,
|
||||
);
|
||||
}, [proxies]);
|
||||
|
||||
|
||||
const proxyOptions = useMemo(() => {
|
||||
let options: { name: string }[] = [];
|
||||
if (isDirectMode) return [{ name: "DIRECT" }];
|
||||
|
||||
const sourceList = isGlobalMode ? proxies?.global?.all : proxies?.groups?.find((g: IProxyGroup) => g.name === selectedGroup)?.all;
|
||||
const sourceList = isGlobalMode
|
||||
? proxies?.global?.all
|
||||
: proxies?.groups?.find((g: IProxyGroup) => g.name === selectedGroup)
|
||||
?.all;
|
||||
|
||||
if (sourceList) {
|
||||
options = sourceList.map((proxy: any) => ({
|
||||
name: typeof proxy === 'string' ? proxy : proxy.name,
|
||||
})).filter((p: { name: string }) => p.name);
|
||||
options = sourceList
|
||||
.map((proxy: any) => ({
|
||||
name: typeof proxy === "string" ? proxy : proxy.name,
|
||||
}))
|
||||
.filter((p: { name: string }) => p.name);
|
||||
}
|
||||
|
||||
if (sortType === 'name') return options.sort((a, b) => a.name.localeCompare(b.name));
|
||||
if (sortType === 'delay') {
|
||||
if (sortType === "name")
|
||||
return options.sort((a, b) => a.name.localeCompare(b.name));
|
||||
if (sortType === "delay") {
|
||||
return options.sort((a, b) => {
|
||||
const delayA = delayManager.getDelay(a.name, selectedGroup);
|
||||
const delayB = delayManager.getDelay(b.name, selectedGroup);
|
||||
@@ -223,8 +282,14 @@ export const ProxySelectors: React.FC = () => {
|
||||
<TooltipProvider>
|
||||
<div className="flex justify-center flex-col gap-2 md:items-end">
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<label className="text-sm font-medium text-muted-foreground">{t("Group")}</label>
|
||||
<Select value={selectedGroup} onValueChange={handleGroupChange} disabled={isGlobalMode || isDirectMode}>
|
||||
<label className="text-sm font-medium text-muted-foreground">
|
||||
{t("Group")}
|
||||
</label>
|
||||
<Select
|
||||
value={selectedGroup}
|
||||
onValueChange={handleGroupChange}
|
||||
disabled={isGlobalMode || isDirectMode}
|
||||
>
|
||||
<SelectTrigger className="w-100">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -239,7 +304,9 @@ export const ProxySelectors: React.FC = () => {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{selectorGroups.map((group: IProxyGroup) => (
|
||||
<SelectItem key={group.name} value={group.name}>{group.name}</SelectItem>
|
||||
<SelectItem key={group.name} value={group.name}>
|
||||
{group.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -247,26 +314,39 @@ export const ProxySelectors: React.FC = () => {
|
||||
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<div className="flex justify-between items-center w-100">
|
||||
<label className="text-sm font-medium text-muted-foreground">{t("Proxy")}</label>
|
||||
<label className="text-sm font-medium text-muted-foreground">
|
||||
{t("Proxy")}
|
||||
</label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="truncate">
|
||||
<Button variant="ghost" size="sm" onClick={handleSortChange} disabled={isDirectMode}>
|
||||
{sortType === 'default' && <ChevronsUpDown className="h-4 w-4" />}
|
||||
{sortType === 'delay' && <Timer className="h-4 w-4" />}
|
||||
{sortType === 'name' && <WholeWord className="h-4 w-4" />}
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{sortType === 'default' && <p>{t("Sort by default")}</p>}
|
||||
{sortType === 'delay' && <p>{t("Sort by delay")}</p>}
|
||||
{sortType === 'name' && <p>{t("Sort by name")}</p>}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<TooltipTrigger asChild>
|
||||
<span className="truncate">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleSortChange}
|
||||
disabled={isDirectMode}
|
||||
>
|
||||
{sortType === "default" && (
|
||||
<ChevronsUpDown className="h-4 w-4" />
|
||||
)}
|
||||
{sortType === "delay" && <Timer className="h-4 w-4" />}
|
||||
{sortType === "name" && <WholeWord className="h-4 w-4" />}
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{sortType === "default" && <p>{t("Sort by default")}</p>}
|
||||
{sortType === "delay" && <p>{t("Sort by delay")}</p>}
|
||||
{sortType === "name" && <p>{t("Sort by name")}</p>}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Select value={selectedProxy} onValueChange={handleProxyChange} disabled={isDirectMode} onOpenChange={handleProxyListOpen}>
|
||||
<Select
|
||||
value={selectedProxy}
|
||||
onValueChange={handleProxyChange}
|
||||
disabled={isDirectMode}
|
||||
onOpenChange={handleProxyListOpen}
|
||||
>
|
||||
<SelectTrigger className="w-100">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -280,11 +360,11 @@ export const ProxySelectors: React.FC = () => {
|
||||
</Tooltip>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{proxyOptions.map(proxy => (
|
||||
{proxyOptions.map((proxy) => (
|
||||
<ProxySelectItem
|
||||
key={proxy.name}
|
||||
proxyName={proxy.name}
|
||||
groupName={selectedGroup}
|
||||
key={proxy.name}
|
||||
proxyName={proxy.name}
|
||||
groupName={selectedGroup}
|
||||
/>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
||||
Reference in New Issue
Block a user