proxy-item.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import { useEffect, useState } from "react";
  2. import { useLockFn } from "ahooks";
  3. import { CheckCircleOutlineRounded } from "@mui/icons-material";
  4. import {
  5. alpha,
  6. Box,
  7. ListItem,
  8. ListItemButton,
  9. ListItemIcon,
  10. ListItemText,
  11. styled,
  12. SxProps,
  13. Theme,
  14. Typography,
  15. } from "@mui/material";
  16. import { BaseLoading } from "@/components/base";
  17. import delayManager from "@/services/delay";
  18. import { useVerge } from "@/hooks/use-verge";
  19. interface Props {
  20. groupName: string;
  21. proxy: IProxyItem;
  22. selected: boolean;
  23. showType?: boolean;
  24. sx?: SxProps<Theme>;
  25. onClick?: (name: string) => void;
  26. }
  27. const Widget = styled(Box)(() => ({
  28. padding: "3px 6px",
  29. fontSize: 14,
  30. borderRadius: "4px",
  31. }));
  32. const TypeBox = styled(Box)(({ theme }) => ({
  33. display: "inline-block",
  34. border: "1px solid #ccc",
  35. borderColor: alpha(theme.palette.text.secondary, 0.36),
  36. color: alpha(theme.palette.text.secondary, 0.42),
  37. borderRadius: 4,
  38. fontSize: 10,
  39. marginRight: "4px",
  40. padding: "0 2px",
  41. lineHeight: 1.25,
  42. }));
  43. export const ProxyItem = (props: Props) => {
  44. const { groupName, proxy, selected, showType = true, sx, onClick } = props;
  45. // -1/<=0 为 不显示
  46. // -2 为 loading
  47. const [delay, setDelay] = useState(-1);
  48. const { verge } = useVerge();
  49. const timeout = verge?.default_latency_timeout || 10000;
  50. useEffect(() => {
  51. delayManager.setListener(proxy.name, groupName, setDelay);
  52. return () => {
  53. delayManager.removeListener(proxy.name, groupName);
  54. };
  55. }, [proxy.name, groupName]);
  56. useEffect(() => {
  57. if (!proxy) return;
  58. setDelay(delayManager.getDelayFix(proxy, groupName));
  59. }, [proxy]);
  60. const onDelay = useLockFn(async () => {
  61. setDelay(-2);
  62. setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout));
  63. });
  64. return (
  65. <ListItem sx={sx}>
  66. <ListItemButton
  67. dense
  68. selected={selected}
  69. onClick={() => onClick?.(proxy.name)}
  70. sx={[
  71. { borderRadius: 1 },
  72. ({ palette: { mode, primary } }) => {
  73. const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
  74. const selectColor = mode === "light" ? primary.main : primary.light;
  75. const showDelay = delay > 0;
  76. return {
  77. "&:hover .the-check": { display: !showDelay ? "block" : "none" },
  78. "&:hover .the-delay": { display: showDelay ? "block" : "none" },
  79. "&:hover .the-icon": { display: "none" },
  80. "&:hover ": {
  81. bgcolor:
  82. mode === "light"
  83. ? alpha(primary.main, 0.15)
  84. : alpha(primary.main, 0.35),
  85. },
  86. "&.Mui-selected": {
  87. borderLeft: `3px solid ${selectColor}`,
  88. bgcolor,
  89. },
  90. // "&.Mui-selected .MuiListItemText-secondary": { bgcolor },
  91. backgroundColor: bgcolor,
  92. marginBottom: "8px",
  93. height: "40px",
  94. };
  95. },
  96. ]}
  97. >
  98. <ListItemText
  99. title={proxy.name}
  100. secondary={
  101. <>
  102. <span
  103. style={{
  104. marginRight: "8px",
  105. fontSize: "13px",
  106. color: "text.primary",
  107. fontWeight: "700",
  108. }}
  109. >
  110. {proxy.name}
  111. {showType && proxy.now && ` - ${proxy.now}`}
  112. </span>
  113. {showType && !!proxy.provider && (
  114. <TypeBox component="span">{proxy.provider}</TypeBox>
  115. )}
  116. {showType && <TypeBox component="span">{proxy.type}</TypeBox>}
  117. {showType && proxy.udp && <TypeBox component="span">UDP</TypeBox>}
  118. {showType && proxy.xudp && (
  119. <TypeBox component="span">XUDP</TypeBox>
  120. )}
  121. {showType && proxy.tfo && <TypeBox component="span">TFO</TypeBox>}
  122. </>
  123. }
  124. />
  125. <ListItemIcon
  126. sx={{ justifyContent: "flex-end", color: "primary.main" }}
  127. >
  128. {delay === -2 && (
  129. <Widget>
  130. <BaseLoading />
  131. </Widget>
  132. )}
  133. {!proxy.provider && delay !== -2 && (
  134. // provider的节点不支持检测
  135. <Widget
  136. className="the-check"
  137. onClick={(e) => {
  138. e.preventDefault();
  139. e.stopPropagation();
  140. onDelay();
  141. }}
  142. sx={({ palette }) => ({
  143. display: "none", // hover才显示
  144. ":hover": { bgcolor: alpha(palette.primary.main, 0.15) },
  145. })}
  146. >
  147. Check
  148. </Widget>
  149. )}
  150. {delay > 0 && (
  151. // 显示延迟
  152. <Widget
  153. className="the-delay"
  154. onClick={(e) => {
  155. if (proxy.provider) return;
  156. e.preventDefault();
  157. e.stopPropagation();
  158. onDelay();
  159. }}
  160. color={delayManager.formatDelayColor(delay, timeout)}
  161. sx={({ palette }) =>
  162. !proxy.provider
  163. ? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
  164. : {}
  165. }
  166. >
  167. {delayManager.formatDelay(delay, timeout)}
  168. </Widget>
  169. )}
  170. {delay !== -2 && delay <= 0 && selected && (
  171. // 展示已选择的icon
  172. <CheckCircleOutlineRounded
  173. className="the-icon"
  174. sx={{ fontSize: 16 }}
  175. />
  176. )}
  177. </ListItemIcon>
  178. </ListItemButton>
  179. </ListItem>
  180. );
  181. };