info-editor.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import { mutate } from "swr";
  2. import { useEffect, useState } from "react";
  3. import { useLockFn, useSetState } from "ahooks";
  4. import { useTranslation } from "react-i18next";
  5. import {
  6. Button,
  7. Collapse,
  8. Dialog,
  9. DialogActions,
  10. DialogContent,
  11. DialogTitle,
  12. FormControlLabel,
  13. IconButton,
  14. Switch,
  15. TextField,
  16. } from "@mui/material";
  17. import { Settings } from "@mui/icons-material";
  18. import { patchProfile } from "@/services/cmds";
  19. import { version } from "@root/package.json";
  20. import { Notice } from "@/components/base";
  21. interface Props {
  22. open: boolean;
  23. itemData: IProfileItem;
  24. onClose: () => void;
  25. }
  26. // edit the profile item
  27. // remote / local file / merge / script
  28. const InfoEditor = (props: Props) => {
  29. const { open, itemData, onClose } = props;
  30. const { t } = useTranslation();
  31. const [form, setForm] = useSetState({ ...itemData });
  32. const [option, setOption] = useSetState(itemData.option ?? {});
  33. const [showOpt, setShowOpt] = useState(!!itemData.option);
  34. useEffect(() => {
  35. if (itemData) {
  36. const { option } = itemData;
  37. setForm({ ...itemData });
  38. setOption(option ?? {});
  39. setShowOpt(
  40. itemData.type === "remote" &&
  41. (!!option?.user_agent ||
  42. !!option?.update_interval ||
  43. !!option?.self_proxy ||
  44. !!option?.with_proxy)
  45. );
  46. }
  47. }, [itemData]);
  48. const onUpdate = useLockFn(async () => {
  49. try {
  50. const { uid } = itemData;
  51. const { name, desc, url } = form;
  52. const option_ =
  53. itemData.type === "remote" || itemData.type === "local"
  54. ? option
  55. : undefined;
  56. if (itemData.type === "remote" && !url) {
  57. throw new Error("Remote URL should not be null");
  58. }
  59. await patchProfile(uid, { uid, name, desc, url, option: option_ });
  60. mutate("getProfiles");
  61. onClose();
  62. } catch (err: any) {
  63. Notice.error(err?.message || err.toString());
  64. }
  65. });
  66. const textFieldProps = {
  67. fullWidth: true,
  68. size: "small",
  69. margin: "normal",
  70. variant: "outlined",
  71. } as const;
  72. const type =
  73. form.type ||
  74. (form.url ? "remote" : form.file?.endsWith(".js") ? "script" : "local");
  75. return (
  76. <Dialog open={open} onClose={onClose}>
  77. <DialogTitle sx={{ pb: 0.5 }}>{t("Edit Info")}</DialogTitle>
  78. <DialogContent sx={{ width: 336, pb: 1 }}>
  79. <TextField
  80. {...textFieldProps}
  81. disabled
  82. label={t("Type")}
  83. value={type}
  84. sx={{ input: { textTransform: "capitalize" } }}
  85. />
  86. <TextField
  87. {...textFieldProps}
  88. autoFocus
  89. label={t("Name")}
  90. value={form.name}
  91. onChange={(e) => setForm({ name: e.target.value })}
  92. onKeyDown={(e) => e.key === "Enter" && onUpdate()}
  93. />
  94. <TextField
  95. {...textFieldProps}
  96. label={t("Descriptions")}
  97. value={form.desc}
  98. onChange={(e) => setForm({ desc: e.target.value })}
  99. onKeyDown={(e) => e.key === "Enter" && onUpdate()}
  100. />
  101. {type === "remote" && (
  102. <TextField
  103. {...textFieldProps}
  104. label={t("Subscription URL")}
  105. value={form.url}
  106. onChange={(e) => setForm({ url: e.target.value })}
  107. onKeyDown={(e) => e.key === "Enter" && onUpdate()}
  108. />
  109. )}
  110. {(type === "remote" || type === "local") && (
  111. <TextField
  112. {...textFieldProps}
  113. label={t("Update Interval(mins)")}
  114. value={option.update_interval}
  115. onChange={(e) => {
  116. const str = e.target.value?.replace(/\D/, "");
  117. setOption({ update_interval: !!str ? +str : undefined });
  118. }}
  119. onKeyDown={(e) => e.key === "Enter" && onUpdate()}
  120. />
  121. )}
  122. <Collapse
  123. in={type === "remote" && showOpt}
  124. timeout="auto"
  125. unmountOnExit
  126. >
  127. <TextField
  128. {...textFieldProps}
  129. label="User Agent"
  130. value={option.user_agent}
  131. placeholder={`clash-verge/v${version}`}
  132. onChange={(e) => setOption({ user_agent: e.target.value })}
  133. onKeyDown={(e) => e.key === "Enter" && onUpdate()}
  134. />
  135. <FormControlLabel
  136. label={t("Use System Proxy")}
  137. labelPlacement="start"
  138. sx={{ ml: 0, my: 1 }}
  139. control={
  140. <Switch
  141. color="primary"
  142. checked={option.with_proxy ?? false}
  143. onChange={(_e, c) =>
  144. setOption((o) => ({
  145. self_proxy: c ? false : o.self_proxy ?? false,
  146. with_proxy: c,
  147. }))
  148. }
  149. />
  150. }
  151. />
  152. <FormControlLabel
  153. label={t("Use Clash Proxy")}
  154. labelPlacement="start"
  155. sx={{ ml: 0, my: 1 }}
  156. control={
  157. <Switch
  158. color="primary"
  159. checked={option.self_proxy ?? false}
  160. onChange={(_e, c) =>
  161. setOption((o) => ({
  162. with_proxy: c ? false : o.with_proxy ?? false,
  163. self_proxy: c,
  164. }))
  165. }
  166. />
  167. }
  168. />
  169. </Collapse>
  170. </DialogContent>
  171. <DialogActions sx={{ px: 2, pb: 2, position: "relative" }}>
  172. {form.type === "remote" && (
  173. <IconButton
  174. size="small"
  175. color="inherit"
  176. sx={{ position: "absolute", left: 18 }}
  177. onClick={() => setShowOpt((o) => !o)}
  178. >
  179. <Settings />
  180. </IconButton>
  181. )}
  182. <Button onClick={onClose} variant="outlined">
  183. {t("Cancel")}
  184. </Button>
  185. <Button onClick={onUpdate} variant="contained">
  186. {t("Save")}
  187. </Button>
  188. </DialogActions>
  189. </Dialog>
  190. );
  191. };
  192. export default InfoEditor;