123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- import { useEffect, useRef, useState } from "react";
- import { useSWRConfig } from "swr";
- import { Virtuoso } from "react-virtuoso";
- import {
- Box,
- Collapse,
- Divider,
- IconButton,
- List,
- ListItem,
- ListItemText,
- } from "@mui/material";
- import {
- SendRounded,
- ExpandLessRounded,
- ExpandMoreRounded,
- MyLocationRounded,
- NetworkCheckRounded,
- } from "@mui/icons-material";
- import { ApiType } from "../../services/types";
- import { updateProxy } from "../../services/api";
- import { getProfiles, patchProfile } from "../../services/cmds";
- import delayManager from "../../services/delay";
- import ProxyItem from "./proxy-item";
- interface Props {
- group: ApiType.ProxyGroupItem;
- }
- const ProxyGroup = ({ group }: Props) => {
- const { mutate } = useSWRConfig();
- const [open, setOpen] = useState(false);
- const [now, setNow] = useState(group.now);
- const virtuosoRef = useRef<any>();
- const proxies = group.all ?? [];
- const selectLockRef = useRef(false);
- const onSelect = async (name: string) => {
- // Todo: support another proxy group type
- if (group.type !== "Selector") return;
- if (selectLockRef.current) return;
- selectLockRef.current = true;
- const oldValue = now;
- try {
- setNow(name);
- await updateProxy(group.name, name);
- } catch {
- setNow(oldValue);
- return; // do not update profile
- } finally {
- selectLockRef.current = false;
- }
- try {
- const profiles = await getProfiles();
- const profile = profiles.items![profiles.current!]!;
- if (!profile) return;
- if (!profile.selected) profile.selected = [];
- const index = profile.selected.findIndex(
- (item) => item.name === group.name
- );
- if (index < 0) {
- profile.selected.push({ name: group.name, now: name });
- } else {
- profile.selected[index] = { name: group.name, now: name };
- }
- await patchProfile(profiles.current!, profile);
- } catch (err) {
- console.error(err);
- }
- };
- const onLocation = (smooth = true) => {
- const index = proxies.findIndex((p) => p.name === now);
- if (index >= 0) {
- virtuosoRef.current?.scrollToIndex?.({
- index,
- align: "center",
- behavior: smooth ? "smooth" : "auto",
- });
- }
- };
- const checkLockRef = useRef(false);
- const onCheckAll = async () => {
- if (checkLockRef.current) return;
- checkLockRef.current = true;
- // rerender quickly
- if (proxies.length) setTimeout(() => mutate("getProxies"), 500);
- let names = proxies.map((p) => p.name);
- while (names.length) {
- const list = names.slice(0, 8);
- names = names.slice(8);
- await Promise.all(
- list.map((n) => delayManager.checkDelay(n, group.name))
- );
- mutate("getProxies");
- }
- checkLockRef.current = false;
- };
- // auto scroll to current index
- useEffect(() => {
- if (open) {
- setTimeout(() => onLocation(false), 5);
- }
- }, [open]);
- return (
- <>
- <ListItem button onClick={() => setOpen(!open)} dense>
- <ListItemText
- primary={group.name}
- secondary={
- <>
- <SendRounded color="primary" sx={{ mr: 1, fontSize: 14 }} />
- <span>{now}</span>
- </>
- }
- secondaryTypographyProps={{
- sx: { display: "flex", alignItems: "center" },
- }}
- />
- {open ? <ExpandLessRounded /> : <ExpandMoreRounded />}
- </ListItem>
- <Collapse in={open} timeout="auto" unmountOnExit>
- <Box sx={{ pl: 4, pr: 3, my: 0.5 }}>
- <IconButton
- size="small"
- title="location"
- onClick={() => onLocation(true)}
- >
- <MyLocationRounded />
- </IconButton>
- <IconButton size="small" title="check" onClick={onCheckAll}>
- <NetworkCheckRounded />
- </IconButton>
- </Box>
- {proxies.length >= 10 ? (
- <Virtuoso
- ref={virtuosoRef}
- style={{ height: "320px", marginBottom: "4px" }}
- totalCount={proxies.length}
- itemContent={(index) => (
- <ProxyItem
- groupName={group.name}
- proxy={proxies[index]}
- selected={proxies[index].name === now}
- sx={{ py: 0, pl: 4 }}
- onClick={onSelect}
- />
- )}
- />
- ) : (
- <List
- component="div"
- disablePadding
- sx={{ maxHeight: "320px", overflow: "auto", mb: "4px" }}
- >
- {proxies.map((proxy) => (
- <ProxyItem
- key={proxy.name}
- groupName={group.name}
- proxy={proxy}
- selected={proxy.name === now}
- sx={{ py: 0, pl: 4 }}
- onClick={onSelect}
- />
- ))}
- </List>
- )}
- <Divider variant="middle" />
- </Collapse>
- </>
- );
- };
- export default ProxyGroup;
|