123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- 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";
- 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;
- // -1/<=0 为 不显示
- // -2 为 loading
- const [delay, setDelay] = useState(-1);
- const { verge } = useVerge();
- const timeout = verge?.default_latency_timeout || 10000;
- useEffect(() => {
- 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",
- top: "-8px",
- right: "-8px",
- },
- "& .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} 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" }}>
- {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"}>
- 📌
- </span>
- )}
- </ListItemButton>
- );
- };
- const Widget = styled(Box)(({ theme: { typography } }) => ({
- padding: "2px 4px",
- fontSize: 14,
- fontFamily: typography.fontFamily,
- borderRadius: "4px",
- }));
- const TypeBox = styled(Box)(({ 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,
- }));
|