proxy-item.tsx 5.0 KB

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