clash-verge-rev/src/components/proxy/proxy-item-mini.tsx
2024-11-12 20:05:28 +08:00

257 lines
7.3 KiB
TypeScript

import { useEffect, useState } from "react";
import { useLockFn } from "ahooks";
import { CheckCircleOutlineRounded } from "@mui/icons-material";
import { alpha, Box, ListItemButton, styled, Typography } from "@mui/material";
import { BaseLoading } from "@/components/base";
import delayManager from "@/services/delay";
import { useVerge } from "@/hooks/use-verge";
import { useTranslation } from "react-i18next";
interface Props {
group: IProxyGroupItem;
proxy: IProxyItem;
selected: boolean;
showType?: boolean;
onClick?: (name: string) => void;
}
// 多列布局
export const ProxyItemMini = (props: Props) => {
const { group, proxy, selected, showType = true, onClick } = props;
const { t } = useTranslation();
const presetList = ["DIRECT", "REJECT", "REJECT-DROP", "PASS", "COMPATIBLE"];
const isPreset = presetList.includes(proxy.name);
// -1/<=0 为 不显示
// -2 为 loading
const [delay, setDelay] = useState(-1);
const { verge } = useVerge();
const timeout = verge?.default_latency_timeout || 10000;
useEffect(() => {
if (isPreset) return;
delayManager.setListener(proxy.name, group.name, setDelay);
return () => {
delayManager.removeListener(proxy.name, group.name);
};
}, [proxy.name, group.name]);
useEffect(() => {
if (!proxy) return;
setDelay(delayManager.getDelayFix(proxy, group.name));
}, [proxy]);
const onDelay = useLockFn(async () => {
setDelay(-2);
setDelay(await delayManager.checkDelay(proxy.name, group.name, timeout));
});
return (
<ListItemButton
dense
selected={selected}
onClick={() => onClick?.(proxy.name)}
sx={[
{
height: 56,
borderRadius: 1.5,
pl: 1.5,
pr: 1,
justifyContent: "space-between",
alignItems: "center",
},
({ palette: { mode, primary } }) => {
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
const showDelay = delay > 0;
const selectColor = mode === "light" ? primary.main : primary.light;
return {
"&:hover .the-check": { display: !showDelay ? "block" : "none" },
"&:hover .the-delay": { display: showDelay ? "block" : "none" },
"&:hover .the-icon": { display: "none" },
"& .the-pin, & .the-unpin": {
position: "absolute",
fontSize: "12px",
top: "-5px",
right: "-5px",
},
"& .the-unpin": { filter: "grayscale(1)" },
"&.Mui-selected": {
width: `calc(100% + 3px)`,
marginLeft: `-3px`,
borderLeft: `3px solid ${selectColor}`,
bgcolor:
mode === "light"
? alpha(primary.main, 0.15)
: alpha(primary.main, 0.35),
},
backgroundColor: bgcolor,
};
},
]}
>
<Box
title={`${proxy.name}\n${proxy.now ?? ""}`}
sx={{ overflow: "hidden" }}
>
<Typography
variant="body2"
component="div"
color="text.primary"
sx={{
display: "block",
textOverflow: "ellipsis",
wordBreak: "break-all",
overflow: "hidden",
whiteSpace: "nowrap",
}}
>
{proxy.name}
</Typography>
{showType && (
<Box
sx={{
display: "flex",
flexWrap: "nowrap",
flex: "none",
marginTop: "4px",
}}
>
{proxy.now && (
<Typography
variant="body2"
component="div"
color="text.secondary"
sx={{
display: "block",
textOverflow: "ellipsis",
wordBreak: "break-all",
overflow: "hidden",
whiteSpace: "nowrap",
marginRight: "8px",
}}
>
{proxy.now}
</Typography>
)}
{!!proxy.provider && (
<TypeBox color="text.secondary" component="span">
{proxy.provider}
</TypeBox>
)}
<TypeBox color="text.secondary" component="span">
{proxy.type}
</TypeBox>
{proxy.udp && (
<TypeBox color="text.secondary" component="span">
UDP
</TypeBox>
)}
{proxy.xudp && (
<TypeBox color="text.secondary" component="span">
XUDP
</TypeBox>
)}
{proxy.tfo && (
<TypeBox color="text.secondary" component="span">
TFO
</TypeBox>
)}
</Box>
)}
</Box>
<Box
sx={{ ml: 0.5, color: "primary.main", display: isPreset ? "none" : "" }}
>
{delay === -2 && (
<Widget>
<BaseLoading />
</Widget>
)}
{!proxy.provider && delay !== -2 && (
// provider的节点不支持检测
<Widget
className="the-check"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onDelay();
}}
sx={({ palette }) => ({
display: "none", // hover才显示
":hover": { bgcolor: alpha(palette.primary.main, 0.15) },
})}
>
Check
</Widget>
)}
{delay > 0 && (
// 显示延迟
<Widget
className="the-delay"
onClick={(e) => {
if (proxy.provider) return;
e.preventDefault();
e.stopPropagation();
onDelay();
}}
color={delayManager.formatDelayColor(delay, timeout)}
sx={({ palette }) =>
!proxy.provider
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
: {}
}
>
{delayManager.formatDelay(delay, timeout)}
</Widget>
)}
{delay !== -2 && delay <= 0 && selected && (
// 展示已选择的icon
<CheckCircleOutlineRounded
className="the-icon"
sx={{ fontSize: 16, mr: 0.5, display: "block" }}
/>
)}
</Box>
{group.fixed && group.fixed === proxy.name && (
// 展示fixed状态
<span
className={proxy.name === group.now ? "the-pin" : "the-unpin"}
title={
group.type === "URLTest" ? t("Delay check to cancel fixed") : ""
}
>
📌
</span>
)}
</ListItemButton>
);
};
const Widget = styled(Box)(({ theme: { typography } }) => ({
padding: "2px 4px",
fontSize: 14,
fontFamily: typography.fontFamily,
borderRadius: "4px",
}));
const TypeBox = styled(Box, {
shouldForwardProp: (prop) => prop !== "component",
})<{ component?: React.ElementType }>(({ theme: { palette, typography } }) => ({
display: "inline-block",
border: "1px solid #ccc",
borderColor: "text.secondary",
color: "text.secondary",
borderRadius: 4,
fontSize: 10,
fontFamily: typography.fontFamily,
marginRight: "4px",
marginTop: "auto",
padding: "0 4px",
lineHeight: 1.5,
}));