|
@@ -0,0 +1,182 @@
|
|
|
+import { useEffect, useState } from "react";
|
|
|
+import { useLockFn } from "ahooks";
|
|
|
+import { CheckCircleOutlineRounded } from "@mui/icons-material";
|
|
|
+import {
|
|
|
+ alpha,
|
|
|
+ Box,
|
|
|
+ ListItemButton,
|
|
|
+ ListItemIcon,
|
|
|
+ ListItemText,
|
|
|
+ styled,
|
|
|
+} from "@mui/material";
|
|
|
+import { BaseLoading } from "@/components/base";
|
|
|
+import delayManager from "@/services/delay";
|
|
|
+
|
|
|
+interface Props {
|
|
|
+ groupName: string;
|
|
|
+ proxy: IProxyItem;
|
|
|
+ selected: boolean;
|
|
|
+ showType?: boolean;
|
|
|
+ onClick?: (name: string) => void;
|
|
|
+}
|
|
|
+
|
|
|
+// 多列布局
|
|
|
+export const ProxyItemMini = (props: Props) => {
|
|
|
+ const { groupName, proxy, selected, showType = true, onClick } = props;
|
|
|
+
|
|
|
+ // -1/<=0 为 不显示
|
|
|
+ // -2 为 loading
|
|
|
+ const [delay, setDelay] = useState(-1);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ delayManager.setListener(proxy.name, groupName, setDelay);
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ delayManager.removeListener(proxy.name, groupName);
|
|
|
+ };
|
|
|
+ }, [proxy.name, groupName]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (!proxy) return;
|
|
|
+ setDelay(delayManager.getDelayFix(proxy, groupName));
|
|
|
+ }, [proxy]);
|
|
|
+
|
|
|
+ const onDelay = useLockFn(async () => {
|
|
|
+ setDelay(-2);
|
|
|
+ setDelay(await delayManager.checkDelay(proxy.name, groupName));
|
|
|
+ });
|
|
|
+
|
|
|
+ return (
|
|
|
+ <ListItemButton
|
|
|
+ dense
|
|
|
+ selected={selected}
|
|
|
+ onClick={() => onClick?.(proxy.name)}
|
|
|
+ sx={[
|
|
|
+ { borderRadius: 1, pl: 1.5, pr: 1 },
|
|
|
+ ({ palette: { mode, primary } }) => {
|
|
|
+ const bgcolor =
|
|
|
+ mode === "light"
|
|
|
+ ? alpha(primary.main, 0.15)
|
|
|
+ : alpha(primary.main, 0.35);
|
|
|
+ const color = mode === "light" ? primary.main : primary.light;
|
|
|
+ const showDelay = delay > 0;
|
|
|
+
|
|
|
+ return {
|
|
|
+ "&:hover .the-check": { display: !showDelay ? "block" : "none" },
|
|
|
+ "&:hover .the-delay": { display: showDelay ? "block" : "none" },
|
|
|
+ "&:hover .the-icon": { display: "none" },
|
|
|
+ "&.Mui-selected": { bgcolor },
|
|
|
+ "&.Mui-selected .MuiListItemText-secondary": { color },
|
|
|
+ };
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <ListItemText
|
|
|
+ title={proxy.name}
|
|
|
+ secondary={
|
|
|
+ <div>
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ textOverflow: "ellipsis",
|
|
|
+ wordBreak: "break-all",
|
|
|
+ overflow: "hidden",
|
|
|
+ whiteSpace: showType ? "nowrap" : "inherit",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {proxy.name}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {showType && (
|
|
|
+ <>
|
|
|
+ {!!proxy.provider && (
|
|
|
+ <TypeBox component="span">{proxy.provider}</TypeBox>
|
|
|
+ )}
|
|
|
+ <TypeBox component="span">{proxy.type}</TypeBox>
|
|
|
+ {proxy.udp && <TypeBox component="span">UDP</TypeBox>}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ />
|
|
|
+
|
|
|
+ <ListItemIcon sx={{ justifyContent: "flex-end", 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={
|
|
|
+ delay > 500
|
|
|
+ ? "error.main"
|
|
|
+ : delay < 100
|
|
|
+ ? "success.main"
|
|
|
+ : "text.secondary"
|
|
|
+ }
|
|
|
+ sx={({ palette }) =>
|
|
|
+ !proxy.provider
|
|
|
+ ? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
|
|
+ : {}
|
|
|
+ }
|
|
|
+ >
|
|
|
+ {delay > 1e5 ? "Error" : delay > 3000 ? "Timeout" : `${delay}`}
|
|
|
+ </Widget>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {delay !== -2 && delay <= 0 && selected && (
|
|
|
+ // 展示已选择的icon
|
|
|
+ <CheckCircleOutlineRounded
|
|
|
+ className="the-icon"
|
|
|
+ sx={{ fontSize: 16 }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </ListItemIcon>
|
|
|
+ </ListItemButton>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const Widget = styled(Box)(() => ({
|
|
|
+ padding: "3px 6px",
|
|
|
+ fontSize: 14,
|
|
|
+ borderRadius: "4px",
|
|
|
+}));
|
|
|
+
|
|
|
+const TypeBox = styled(Box)(({ theme }) => ({
|
|
|
+ display: "inline-block",
|
|
|
+ border: "1px solid #ccc",
|
|
|
+ borderColor: alpha(theme.palette.text.secondary, 0.36),
|
|
|
+ color: alpha(theme.palette.text.secondary, 0.42),
|
|
|
+ borderRadius: 4,
|
|
|
+ fontSize: 10,
|
|
|
+ marginRight: "4px",
|
|
|
+ padding: "0 2px",
|
|
|
+ lineHeight: 1.25,
|
|
|
+}));
|