proxy-group.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { useRef, useState } from "react";
  2. import { useSWRConfig } from "swr";
  3. import { Virtuoso } from "react-virtuoso";
  4. import {
  5. Box,
  6. Collapse,
  7. Divider,
  8. IconButton,
  9. List,
  10. ListItem,
  11. ListItemText,
  12. } from "@mui/material";
  13. import {
  14. SendRounded,
  15. ExpandLessRounded,
  16. ExpandMoreRounded,
  17. MyLocationRounded,
  18. NetworkCheckRounded,
  19. } from "@mui/icons-material";
  20. import { ApiType } from "../../services/types";
  21. import { updateProxy } from "../../services/api";
  22. import { getProfiles, patchProfile } from "../../services/cmds";
  23. import delayManager from "../../services/delay";
  24. import ProxyItem from "./proxy-item";
  25. interface Props {
  26. group: ApiType.ProxyGroupItem;
  27. }
  28. const ProxyGroup = ({ group }: Props) => {
  29. const { mutate } = useSWRConfig();
  30. const listRef = useRef<any>();
  31. const [open, setOpen] = useState(false);
  32. const [now, setNow] = useState(group.now);
  33. const proxies = group.all ?? [];
  34. const onSelect = async (name: string) => {
  35. // can not call update
  36. if (group.type !== "Selector") {
  37. // Todo
  38. // error Tips
  39. return;
  40. }
  41. const oldValue = now;
  42. try {
  43. setNow(name);
  44. await updateProxy(group.name, name);
  45. const profiles = await getProfiles().catch(console.error);
  46. if (!profiles) return;
  47. const profile = profiles.items![profiles.current!]!;
  48. if (!profile) return;
  49. if (!profile.selected) profile.selected = [];
  50. const index = profile.selected.findIndex(
  51. (item) => item.name === group.name
  52. );
  53. if (index < 0) {
  54. profile.selected.push({ name: group.name, now: name });
  55. } else {
  56. profile.selected[index] = { name: group.name, now: name };
  57. }
  58. patchProfile(profiles.current!, profile).catch(console.error);
  59. } catch {
  60. setNow(oldValue);
  61. // Todo
  62. // error tips
  63. }
  64. };
  65. const onLocation = () => {
  66. const index = proxies.findIndex((p) => p.name === now);
  67. if (index >= 0) {
  68. listRef.current?.scrollToIndex?.({
  69. index,
  70. align: "center",
  71. behavior: "smooth",
  72. });
  73. }
  74. };
  75. const onCheckAll = async () => {
  76. let names = proxies.map((p) => p.name);
  77. while (names.length) {
  78. const list = names.slice(0, 10);
  79. names = names.slice(10);
  80. await Promise.all(
  81. list.map((n) => delayManager.checkDelay(n, group.name))
  82. );
  83. mutate("getProxies");
  84. }
  85. };
  86. return (
  87. <>
  88. <ListItem button onClick={() => setOpen(!open)} dense>
  89. <ListItemText
  90. primary={group.name}
  91. secondary={
  92. <>
  93. <SendRounded color="primary" sx={{ mr: 1, fontSize: 14 }} />
  94. <span>{now}</span>
  95. </>
  96. }
  97. secondaryTypographyProps={{
  98. sx: { display: "flex", alignItems: "center" },
  99. }}
  100. />
  101. {open ? <ExpandLessRounded /> : <ExpandMoreRounded />}
  102. </ListItem>
  103. <Collapse in={open} timeout="auto" unmountOnExit>
  104. <Box sx={{ pl: 4, pr: 3, my: 0.5 }}>
  105. <IconButton size="small" title="location" onClick={onLocation}>
  106. <MyLocationRounded />
  107. </IconButton>
  108. <IconButton size="small" title="check" onClick={onCheckAll}>
  109. <NetworkCheckRounded />
  110. </IconButton>
  111. </Box>
  112. {proxies.length >= 10 ? (
  113. <Virtuoso
  114. ref={listRef}
  115. style={{ height: "320px", marginBottom: "4px" }}
  116. totalCount={proxies.length}
  117. itemContent={(index) => (
  118. <ProxyItem
  119. groupName={group.name}
  120. proxy={proxies[index]}
  121. selected={proxies[index].name === now}
  122. sx={{ py: 0, pl: 4 }}
  123. onClick={onSelect}
  124. />
  125. )}
  126. />
  127. ) : (
  128. <List
  129. component="div"
  130. disablePadding
  131. sx={{ maxHeight: "320px", overflow: "auto", mb: "4px" }}
  132. >
  133. {proxies.map((proxy) => (
  134. <ProxyItem
  135. key={proxy.name}
  136. groupName={group.name}
  137. proxy={proxy}
  138. selected={proxy.name === now}
  139. sx={{ py: 0, pl: 4 }}
  140. onClick={onSelect}
  141. />
  142. ))}
  143. </List>
  144. )}
  145. <Divider variant="middle" />
  146. </Collapse>
  147. </>
  148. );
  149. };
  150. export default ProxyGroup;