web-ui-viewer.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { forwardRef, useImperativeHandle, useState } from "react";
  2. import { useLockFn } from "ahooks";
  3. import { useTranslation } from "react-i18next";
  4. import { Button, Box, Typography } from "@mui/material";
  5. import { useVerge } from "@/hooks/use-verge";
  6. import { openWebUrl } from "@/services/cmds";
  7. import { BaseDialog, BaseEmpty, DialogRef, Notice } from "@/components/base";
  8. import { useClashInfo } from "@/hooks/use-clash";
  9. import { WebUIItem } from "./web-ui-item";
  10. export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
  11. const { t } = useTranslation();
  12. const { clashInfo } = useClashInfo();
  13. const { verge, patchVerge, mutateVerge } = useVerge();
  14. const [open, setOpen] = useState(false);
  15. const [editing, setEditing] = useState(false);
  16. useImperativeHandle(ref, () => ({
  17. open: () => setOpen(true),
  18. close: () => setOpen(false),
  19. }));
  20. const webUIList = verge?.web_ui_list || [];
  21. const handleAdd = useLockFn(async (value: string) => {
  22. const newList = [value, ...webUIList];
  23. mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false);
  24. await patchVerge({ web_ui_list: newList });
  25. });
  26. const handleChange = useLockFn(async (index: number, value?: string) => {
  27. const newList = [...webUIList];
  28. newList[index] = value ?? "";
  29. mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false);
  30. await patchVerge({ web_ui_list: newList });
  31. });
  32. const handleDelete = useLockFn(async (index: number) => {
  33. const newList = [...webUIList];
  34. newList.splice(index, 1);
  35. mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false);
  36. await patchVerge({ web_ui_list: newList });
  37. });
  38. const handleOpenUrl = useLockFn(async (value?: string) => {
  39. if (!value) return;
  40. try {
  41. let url = value.trim().replaceAll("%host", "127.0.0.1");
  42. if (url.includes("%port") || url.includes("%secret")) {
  43. if (!clashInfo) throw new Error("failed to get clash info");
  44. if (!clashInfo.server?.includes(":")) {
  45. throw new Error(`failed to parse the server "${clashInfo.server}"`);
  46. }
  47. const port = clashInfo.server
  48. .slice(clashInfo.server.indexOf(":") + 1)
  49. .trim();
  50. url = url.replaceAll("%port", port || "9090");
  51. url = url.replaceAll(
  52. "%secret",
  53. encodeURIComponent(clashInfo.secret || "")
  54. );
  55. }
  56. await openWebUrl(url);
  57. } catch (e: any) {
  58. Notice.error(e.message || e.toString());
  59. }
  60. });
  61. return (
  62. <BaseDialog
  63. open={open}
  64. title={
  65. <Box display="flex" justifyContent="space-between">
  66. {t("Web UI")}
  67. <Button
  68. variant="contained"
  69. size="small"
  70. disabled={editing}
  71. onClick={() => setEditing(true)}
  72. >
  73. {t("New")}
  74. </Button>
  75. </Box>
  76. }
  77. contentSx={{
  78. width: 450,
  79. height: 300,
  80. pb: 1,
  81. overflowY: "auto",
  82. userSelect: "text",
  83. }}
  84. cancelBtn={t("Back")}
  85. disableOk
  86. onClose={() => setOpen(false)}
  87. onCancel={() => setOpen(false)}
  88. >
  89. {editing && (
  90. <WebUIItem
  91. value=""
  92. onlyEdit
  93. onChange={(v) => {
  94. setEditing(false);
  95. handleAdd(v || "");
  96. }}
  97. onCancel={() => setEditing(false)}
  98. />
  99. )}
  100. {!editing && webUIList.length === 0 && (
  101. <BaseEmpty
  102. text="Empty List"
  103. extra={
  104. <Typography mt={2} sx={{ fontSize: "12px" }}>
  105. Replace host, port, secret with "%host" "%port" "%secret"
  106. </Typography>
  107. }
  108. />
  109. )}
  110. {webUIList.map((item, index) => (
  111. <WebUIItem
  112. key={index}
  113. value={item}
  114. onChange={(v) => handleChange(index, v)}
  115. onDelete={() => handleDelete(index)}
  116. onOpenUrl={handleOpenUrl}
  117. />
  118. ))}
  119. </BaseDialog>
  120. );
  121. });