proxy-item-mini.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import { useEffect, useState } from "react";
  2. import { useLockFn } from "ahooks";
  3. import { CheckCircleOutlineRounded } from "@mui/icons-material";
  4. import { alpha, Box, ListItemButton, styled, Typography } from "@mui/material";
  5. import { BaseLoading } from "@/components/base";
  6. import delayManager from "@/services/delay";
  7. import { useVerge } from "@/hooks/use-verge";
  8. interface Props {
  9. group: IProxyGroupItem;
  10. proxy: IProxyItem;
  11. selected: boolean;
  12. showType?: boolean;
  13. onClick?: (name: string) => void;
  14. }
  15. // 多列布局
  16. export const ProxyItemMini = (props: Props) => {
  17. const { group, proxy, selected, showType = true, onClick } = props;
  18. // -1/<=0 为 不显示
  19. // -2 为 loading
  20. const [delay, setDelay] = useState(-1);
  21. const { verge } = useVerge();
  22. const timeout = verge?.default_latency_timeout || 10000;
  23. useEffect(() => {
  24. delayManager.setListener(proxy.name, group.name, setDelay);
  25. return () => {
  26. delayManager.removeListener(proxy.name, group.name);
  27. };
  28. }, [proxy.name, group.name]);
  29. useEffect(() => {
  30. if (!proxy) return;
  31. setDelay(delayManager.getDelayFix(proxy, group.name));
  32. }, [proxy]);
  33. const onDelay = useLockFn(async () => {
  34. setDelay(-2);
  35. setDelay(await delayManager.checkDelay(proxy.name, group.name, timeout));
  36. });
  37. return (
  38. <ListItemButton
  39. dense
  40. selected={selected}
  41. onClick={() => onClick?.(proxy.name)}
  42. sx={[
  43. {
  44. height: 56,
  45. borderRadius: 1.5,
  46. pl: 1.5,
  47. pr: 1,
  48. justifyContent: "space-between",
  49. alignItems: "center",
  50. },
  51. ({ palette: { mode, primary } }) => {
  52. const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
  53. const showDelay = delay > 0;
  54. const selectColor = mode === "light" ? primary.main : primary.light;
  55. return {
  56. "&:hover .the-check": { display: !showDelay ? "block" : "none" },
  57. "&:hover .the-delay": { display: showDelay ? "block" : "none" },
  58. "&:hover .the-icon": { display: "none" },
  59. "& .the-pin, & .the-unpin": {
  60. position: "absolute",
  61. top: "-8px",
  62. right: "-8px",
  63. },
  64. "& .the-unpin": { filter: "grayscale(1)" },
  65. "&.Mui-selected": {
  66. width: `calc(100% + 3px)`,
  67. marginLeft: `-3px`,
  68. borderLeft: `3px solid ${selectColor}`,
  69. bgcolor:
  70. mode === "light"
  71. ? alpha(primary.main, 0.15)
  72. : alpha(primary.main, 0.35),
  73. },
  74. backgroundColor: bgcolor,
  75. };
  76. },
  77. ]}
  78. >
  79. <Box title={proxy.name} sx={{ overflow: "hidden" }}>
  80. <Typography
  81. variant="body2"
  82. component="div"
  83. color="text.primary"
  84. sx={{
  85. display: "block",
  86. textOverflow: "ellipsis",
  87. wordBreak: "break-all",
  88. overflow: "hidden",
  89. whiteSpace: "nowrap",
  90. }}
  91. >
  92. {proxy.name}
  93. </Typography>
  94. {showType && (
  95. <Box
  96. sx={{
  97. display: "flex",
  98. flexWrap: "nowrap",
  99. flex: "none",
  100. marginTop: "4px",
  101. }}
  102. >
  103. {proxy.now && (
  104. <Typography
  105. variant="body2"
  106. component="div"
  107. color="text.secondary"
  108. sx={{
  109. display: "block",
  110. textOverflow: "ellipsis",
  111. wordBreak: "break-all",
  112. overflow: "hidden",
  113. whiteSpace: "nowrap",
  114. marginRight: "8px",
  115. }}
  116. >
  117. {proxy.now}
  118. </Typography>
  119. )}
  120. {!!proxy.provider && (
  121. <TypeBox color="text.secondary" component="span">
  122. {proxy.provider}
  123. </TypeBox>
  124. )}
  125. <TypeBox color="text.secondary" component="span">
  126. {proxy.type}
  127. </TypeBox>
  128. {proxy.udp && (
  129. <TypeBox color="text.secondary" component="span">
  130. UDP
  131. </TypeBox>
  132. )}
  133. {proxy.xudp && (
  134. <TypeBox color="text.secondary" component="span">
  135. XUDP
  136. </TypeBox>
  137. )}
  138. {proxy.tfo && (
  139. <TypeBox color="text.secondary" component="span">
  140. TFO
  141. </TypeBox>
  142. )}
  143. </Box>
  144. )}
  145. </Box>
  146. <Box sx={{ ml: 0.5, color: "primary.main" }}>
  147. {delay === -2 && (
  148. <Widget>
  149. <BaseLoading />
  150. </Widget>
  151. )}
  152. {!proxy.provider && delay !== -2 && (
  153. // provider的节点不支持检测
  154. <Widget
  155. className="the-check"
  156. onClick={(e) => {
  157. e.preventDefault();
  158. e.stopPropagation();
  159. onDelay();
  160. }}
  161. sx={({ palette }) => ({
  162. display: "none", // hover才显示
  163. ":hover": { bgcolor: alpha(palette.primary.main, 0.15) },
  164. })}
  165. >
  166. Check
  167. </Widget>
  168. )}
  169. {delay > 0 && (
  170. // 显示延迟
  171. <Widget
  172. className="the-delay"
  173. onClick={(e) => {
  174. if (proxy.provider) return;
  175. e.preventDefault();
  176. e.stopPropagation();
  177. onDelay();
  178. }}
  179. color={delayManager.formatDelayColor(delay, timeout)}
  180. sx={({ palette }) =>
  181. !proxy.provider
  182. ? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
  183. : {}
  184. }
  185. >
  186. {delayManager.formatDelay(delay, timeout)}
  187. </Widget>
  188. )}
  189. {delay !== -2 && delay <= 0 && selected && (
  190. // 展示已选择的icon
  191. <CheckCircleOutlineRounded
  192. className="the-icon"
  193. sx={{ fontSize: 16, mr: 0.5, display: "block" }}
  194. />
  195. )}
  196. </Box>
  197. {group.fixed && group.fixed === proxy.name && (
  198. // 展示fixed状态
  199. <span className={proxy.name === group.now ? "the-pin" : "the-unpin"}>
  200. 📌
  201. </span>
  202. )}
  203. </ListItemButton>
  204. );
  205. };
  206. const Widget = styled(Box)(({ theme: { typography } }) => ({
  207. padding: "2px 4px",
  208. fontSize: 14,
  209. fontFamily: typography.fontFamily,
  210. borderRadius: "4px",
  211. }));
  212. const TypeBox = styled(Box)(({ theme: { palette, typography } }) => ({
  213. display: "inline-block",
  214. border: "1px solid #ccc",
  215. borderColor: "text.secondary",
  216. color: "text.secondary",
  217. borderRadius: 4,
  218. fontSize: 10,
  219. fontFamily: typography.fontFamily,
  220. marginRight: "4px",
  221. marginTop: "auto",
  222. padding: "0 4px",
  223. lineHeight: 1.5,
  224. }));