Refactor components to remove forwardRef and simplify props handling

- Updated multiple components to remove the use of forwardRef, simplifying the props structure.
- Adjusted imports and component definitions accordingly.
- Ensured consistent handling of refs and props across various viewer components.
- Improved readability and maintainability of the codebase.
This commit is contained in:
Tunglies
2025-09-30 14:23:29 +08:00
parent 0c88568cd7
commit 1cd013fb94
25 changed files with 1380 additions and 1455 deletions

View File

@@ -2,7 +2,7 @@ import { Box, Button, Snackbar, useTheme } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { t } from "i18next"; import { t } from "i18next";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { deleteConnection } from "@/services/cmds"; import { deleteConnection } from "@/services/cmds";
import parseTraffic from "@/utils/parse-traffic"; import parseTraffic from "@/utils/parse-traffic";
@@ -11,45 +11,43 @@ export interface ConnectionDetailRef {
open: (detail: IConnectionsItem) => void; open: (detail: IConnectionsItem) => void;
} }
export const ConnectionDetail = forwardRef<ConnectionDetailRef>( export const ConnectionDetail = ({ ref, ...props }) => {
(props, ref) => { const [open, setOpen] = useState(false);
const [open, setOpen] = useState(false); const [detail, setDetail] = useState<IConnectionsItem>(null!);
const [detail, setDetail] = useState<IConnectionsItem>(null!); const theme = useTheme();
const theme = useTheme();
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
open: (detail: IConnectionsItem) => { open: (detail: IConnectionsItem) => {
if (open) return; if (open) return;
setOpen(true); setOpen(true);
setDetail(detail); setDetail(detail);
}, },
})); }));
const onClose = () => setOpen(false); const onClose = () => setOpen(false);
return ( return (
<Snackbar <Snackbar
anchorOrigin={{ vertical: "bottom", horizontal: "right" }} anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
open={open} open={open}
onClose={onClose} onClose={onClose}
sx={{ sx={{
".MuiSnackbarContent-root": { ".MuiSnackbarContent-root": {
maxWidth: "520px", maxWidth: "520px",
maxHeight: "480px", maxHeight: "480px",
overflowY: "auto", overflowY: "auto",
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary, color: theme.palette.text.primary,
}, },
}} }}
message={ message={
detail ? ( detail ? (
<InnerConnectionDetail data={detail} onClose={onClose} /> <InnerConnectionDetail data={detail} onClose={onClose} />
) : null ) : null
} }
/> />
); );
}, };
);
interface InnerProps { interface InnerProps {
data: IConnectionsItem; data: IConnectionsItem;

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
import { useTheme } from "@mui/material"; import { useTheme } from "@mui/material";
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"; import { useEffect, useImperativeHandle, useRef } from "react";
const maxPoint = 30; const maxPoint = 30;
@@ -24,7 +24,7 @@ export interface TrafficRef {
/** /**
* draw the traffic graph * draw the traffic graph
*/ */
export const TrafficGraph = forwardRef<TrafficRef>((props, ref) => { export const TrafficGraph = ({ ref, ...props }) => {
const countRef = useRef(0); const countRef = useRef(0);
const styleRef = useRef(true); const styleRef = useRef(true);
const listRef = useRef<TrafficData[]>(defaultList); const listRef = useRef<TrafficData[]>(defaultList);
@@ -196,4 +196,4 @@ export const TrafficGraph = forwardRef<TrafficRef>((props, ref) => {
}, [palette]); }, [palette]);
return <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />; return <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />;
}); };

View File

@@ -9,13 +9,7 @@ import {
TextField, TextField,
} from "@mui/material"; } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { import { useEffect, useImperativeHandle, useRef, useState } from "react";
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -38,304 +32,280 @@ export interface ProfileViewerRef {
// create or edit the profile // create or edit the profile
// remote / local // remote / local
export const ProfileViewer = forwardRef<ProfileViewerRef, Props>( export const ProfileViewer = ({
(props, ref) => { ref,
const { t } = useTranslation(); ...props
const [open, setOpen] = useState(false); }: Props & { ref?: React.RefObject<ProfileViewerRef | null> }) => {
const [openType, setOpenType] = useState<"new" | "edit">("new"); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [open, setOpen] = useState(false);
const { profiles } = useProfiles(); const [openType, setOpenType] = useState<"new" | "edit">("new");
const [loading, setLoading] = useState(false);
const { profiles } = useProfiles();
// file input // file input
const fileDataRef = useRef<string | null>(null); const fileDataRef = useRef<string | null>(null);
const { const {
control, control,
watch, watch,
register: _register, register: _register,
...formIns ...formIns
} = useForm<IProfileItem>({ } = useForm<IProfileItem>({
defaultValues: { defaultValues: {
type: "remote", type: "remote",
name: "", name: "",
desc: "", desc: "",
url: "", url: "",
option: { option: {
with_proxy: false, with_proxy: false,
self_proxy: false, self_proxy: false,
},
}, },
}); },
});
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
create: () => { create: () => {
setOpenType("new"); setOpenType("new");
setOpen(true); setOpen(true);
}, },
edit: (item) => { edit: (item) => {
if (item) { if (item) {
Object.entries(item).forEach(([key, value]) => { Object.entries(item).forEach(([key, value]) => {
formIns.setValue(key as any, value); formIns.setValue(key as any, value);
}); });
} }
setOpenType("edit"); setOpenType("edit");
setOpen(true); setOpen(true);
}, },
})); }));
const selfProxy = watch("option.self_proxy"); const selfProxy = watch("option.self_proxy");
const withProxy = watch("option.with_proxy"); const withProxy = watch("option.with_proxy");
useEffect(() => { useEffect(() => {
if (selfProxy) formIns.setValue("option.with_proxy", false); if (selfProxy) formIns.setValue("option.with_proxy", false);
}, [selfProxy]); }, [selfProxy]);
useEffect(() => { useEffect(() => {
if (withProxy) formIns.setValue("option.self_proxy", false); if (withProxy) formIns.setValue("option.self_proxy", false);
}, [withProxy]); }, [withProxy]);
const handleOk = useLockFn( const handleOk = useLockFn(
formIns.handleSubmit(async (form) => { formIns.handleSubmit(async (form) => {
if (form.option?.timeout_seconds) { if (form.option?.timeout_seconds) {
form.option.timeout_seconds = +form.option.timeout_seconds; form.option.timeout_seconds = +form.option.timeout_seconds;
}
setLoading(true);
try {
// 基本验证
if (!form.type) throw new Error("`Type` should not be null");
if (form.type === "remote" && !form.url) {
throw new Error("The URL should not be null");
} }
setLoading(true); // 处理表单数据
try { if (form.option?.update_interval) {
// 基本验证 form.option.update_interval = +form.option.update_interval;
if (!form.type) throw new Error("`Type` should not be null"); } else {
if (form.type === "remote" && !form.url) { delete form.option?.update_interval;
throw new Error("The URL should not be null"); }
} if (form.option?.user_agent === "") {
delete form.option.user_agent;
}
// 处理表单数据 const name = form.name || `${form.type} file`;
if (form.option?.update_interval) { const item = { ...form, name };
form.option.update_interval = +form.option.update_interval; const isRemote = form.type === "remote";
const isUpdate = openType === "edit";
// 判断是否是当前激活的配置
const isActivating = isUpdate && form.uid === (profiles?.current ?? "");
// 保存原始代理设置以便回退成功后恢复
const originalOptions = {
with_proxy: form.option?.with_proxy,
self_proxy: form.option?.self_proxy,
};
// 执行创建或更新操作,本地配置不需要回退机制
if (!isRemote) {
if (openType === "new") {
await createProfile(item, fileDataRef.current);
} else { } else {
delete form.option?.update_interval; if (!form.uid) throw new Error("UID not found");
await patchProfile(form.uid, item);
} }
if (form.option?.user_agent === "") { } else {
delete form.option.user_agent; // 远程配置使用回退机制
} try {
// 尝试正常操作
const name = form.name || `${form.type} file`;
const item = { ...form, name };
const isRemote = form.type === "remote";
const isUpdate = openType === "edit";
// 判断是否是当前激活的配置
const isActivating =
isUpdate && form.uid === (profiles?.current ?? "");
// 保存原始代理设置以便回退成功后恢复
const originalOptions = {
with_proxy: form.option?.with_proxy,
self_proxy: form.option?.self_proxy,
};
// 执行创建或更新操作,本地配置不需要回退机制
if (!isRemote) {
if (openType === "new") { if (openType === "new") {
await createProfile(item, fileDataRef.current); await createProfile(item, fileDataRef.current);
} else { } else {
if (!form.uid) throw new Error("UID not found"); if (!form.uid) throw new Error("UID not found");
await patchProfile(form.uid, item); await patchProfile(form.uid, item);
} }
} else { } catch {
// 远程配置使用回退机制 // 首次创建/更新失败,尝试使用自身代理
try { showNotice(
// 尝试正常操作 "info",
if (openType === "new") { t("Profile creation failed, retrying with Clash proxy..."),
await createProfile(item, fileDataRef.current); );
} else {
if (!form.uid) throw new Error("UID not found");
await patchProfile(form.uid, item);
}
} catch {
// 首次创建/更新失败,尝试使用自身代理
showNotice(
"info",
t("Profile creation failed, retrying with Clash proxy..."),
);
// 使用自身代理的配置 // 使用自身代理的配置
const retryItem = { const retryItem = {
...item, ...item,
option: { option: {
...item.option, ...item.option,
with_proxy: false, with_proxy: false,
self_proxy: true, self_proxy: true,
}, },
}; };
// 使用自身代理再次尝试 // 使用自身代理再次尝试
if (openType === "new") { if (openType === "new") {
await createProfile(retryItem, fileDataRef.current); await createProfile(retryItem, fileDataRef.current);
} else { } else {
if (!form.uid) throw new Error("UID not found"); if (!form.uid) throw new Error("UID not found");
await patchProfile(form.uid, retryItem); await patchProfile(form.uid, retryItem);
// 编辑模式下恢复原始代理设置 // 编辑模式下恢复原始代理设置
await patchProfile(form.uid, { option: originalOptions }); await patchProfile(form.uid, { option: originalOptions });
}
showNotice(
"success",
t("Profile creation succeeded with Clash proxy"),
);
} }
showNotice(
"success",
t("Profile creation succeeded with Clash proxy"),
);
} }
// 成功后的操作
setOpen(false);
setTimeout(() => formIns.reset(), 500);
fileDataRef.current = null;
// 优化UI先关闭异步通知父组件
setTimeout(() => {
props.onChange(isActivating);
}, 0);
} catch (err: any) {
showNotice("error", err.message || err.toString());
} finally {
setLoading(false);
} }
}),
);
const handleClose = () => { // 成功后的操作
try {
setOpen(false); setOpen(false);
fileDataRef.current = null;
setTimeout(() => formIns.reset(), 500); setTimeout(() => formIns.reset(), 500);
} catch (e) { fileDataRef.current = null;
console.warn("[ProfileViewer] handleClose error:", e);
// 优化UI先关闭异步通知父组件
setTimeout(() => {
props.onChange(isActivating);
}, 0);
} catch (err: any) {
showNotice("error", err.message || err.toString());
} finally {
setLoading(false);
} }
}; }),
);
const text = { const handleClose = () => {
fullWidth: true, try {
size: "small", setOpen(false);
margin: "normal", fileDataRef.current = null;
variant: "outlined", setTimeout(() => formIns.reset(), 500);
autoComplete: "off", } catch (e) {
autoCorrect: "off", console.warn("[ProfileViewer] handleClose error:", e);
} as const; }
};
const formType = watch("type"); const text = {
const isRemote = formType === "remote"; fullWidth: true,
const isLocal = formType === "local"; size: "small",
margin: "normal",
variant: "outlined",
autoComplete: "off",
autoCorrect: "off",
} as const;
return ( const formType = watch("type");
<BaseDialog const isRemote = formType === "remote";
open={open} const isLocal = formType === "local";
title={openType === "new" ? t("Create Profile") : t("Edit Profile")}
contentSx={{ width: 375, pb: 0, maxHeight: "80%" }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
onClose={handleClose}
onCancel={handleClose}
onOk={handleOk}
loading={loading}
>
<Controller
name="type"
control={control}
render={({ field }) => (
<FormControl size="small" fullWidth sx={{ mt: 1, mb: 1 }}>
<InputLabel>{t("Type")}</InputLabel>
<Select {...field} autoFocus label={t("Type")}>
<MenuItem value="remote">Remote</MenuItem>
<MenuItem value="local">Local</MenuItem>
</Select>
</FormControl>
)}
/>
<Controller return (
name="name" <BaseDialog
control={control} open={open}
render={({ field }) => ( title={openType === "new" ? t("Create Profile") : t("Edit Profile")}
<TextField {...text} {...field} label={t("Name")} /> contentSx={{ width: 375, pb: 0, maxHeight: "80%" }}
)} okBtn={t("Save")}
/> cancelBtn={t("Cancel")}
onClose={handleClose}
<Controller onCancel={handleClose}
name="desc" onOk={handleOk}
control={control} loading={loading}
render={({ field }) => ( >
<TextField {...text} {...field} label={t("Descriptions")} /> <Controller
)} name="type"
/> control={control}
render={({ field }) => (
{isRemote && ( <FormControl size="small" fullWidth sx={{ mt: 1, mb: 1 }}>
<> <InputLabel>{t("Type")}</InputLabel>
<Controller <Select {...field} autoFocus label={t("Type")}>
name="url" <MenuItem value="remote">Remote</MenuItem>
control={control} <MenuItem value="local">Local</MenuItem>
render={({ field }) => ( </Select>
<TextField </FormControl>
{...text}
{...field}
multiline
label={t("Subscription URL")}
/>
)}
/>
<Controller
name="option.user_agent"
control={control}
render={({ field }) => (
<TextField
{...text}
{...field}
placeholder={`clash-verge/v${version}`}
label="User Agent"
/>
)}
/>
<Controller
name="option.timeout_seconds"
control={control}
render={({ field }) => (
<TextField
{...text}
{...field}
type="number"
placeholder="60"
label={t("HTTP Request Timeout")}
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">
{t("seconds")}
</InputAdornment>
),
},
}}
/>
)}
/>
</>
)} )}
/>
{(isRemote || isLocal) && ( <Controller
name="name"
control={control}
render={({ field }) => (
<TextField {...text} {...field} label={t("Name")} />
)}
/>
<Controller
name="desc"
control={control}
render={({ field }) => (
<TextField {...text} {...field} label={t("Descriptions")} />
)}
/>
{isRemote && (
<>
<Controller <Controller
name="option.update_interval" name="url"
control={control}
render={({ field }) => (
<TextField
{...text}
{...field}
multiline
label={t("Subscription URL")}
/>
)}
/>
<Controller
name="option.user_agent"
control={control}
render={({ field }) => (
<TextField
{...text}
{...field}
placeholder={`clash-verge/v${version}`}
label="User Agent"
/>
)}
/>
<Controller
name="option.timeout_seconds"
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<TextField <TextField
{...text} {...text}
{...field} {...field}
type="number" type="number"
label={t("Update Interval")} placeholder="60"
label={t("HTTP Request Timeout")}
slotProps={{ slotProps={{
input: { input: {
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{t("mins")} {t("seconds")}
</InputAdornment> </InputAdornment>
), ),
}, },
@@ -343,57 +313,79 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
/> />
)} )}
/> />
)} </>
)}
{isLocal && openType === "new" && ( {(isRemote || isLocal) && (
<FileInput <Controller
onChange={(file, val) => { name="option.update_interval"
formIns.setValue("name", formIns.getValues("name") || file.name); control={control}
fileDataRef.current = val; render={({ field }) => (
}} <TextField
{...text}
{...field}
type="number"
label={t("Update Interval")}
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">{t("mins")}</InputAdornment>
),
},
}}
/>
)}
/>
)}
{isLocal && openType === "new" && (
<FileInput
onChange={(file, val) => {
formIns.setValue("name", formIns.getValues("name") || file.name);
fileDataRef.current = val;
}}
/>
)}
{isRemote && (
<>
<Controller
name="option.with_proxy"
control={control}
render={({ field }) => (
<StyledBox>
<InputLabel>{t("Use System Proxy")}</InputLabel>
<Switch checked={field.value} {...field} color="primary" />
</StyledBox>
)}
/> />
)}
{isRemote && ( <Controller
<> name="option.self_proxy"
<Controller control={control}
name="option.with_proxy" render={({ field }) => (
control={control} <StyledBox>
render={({ field }) => ( <InputLabel>{t("Use Clash Proxy")}</InputLabel>
<StyledBox> <Switch checked={field.value} {...field} color="primary" />
<InputLabel>{t("Use System Proxy")}</InputLabel> </StyledBox>
<Switch checked={field.value} {...field} color="primary" /> )}
</StyledBox> />
)}
/>
<Controller <Controller
name="option.self_proxy" name="option.danger_accept_invalid_certs"
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<StyledBox> <StyledBox>
<InputLabel>{t("Use Clash Proxy")}</InputLabel> <InputLabel>{t("Accept Invalid Certs (Danger)")}</InputLabel>
<Switch checked={field.value} {...field} color="primary" /> <Switch checked={field.value} {...field} color="primary" />
</StyledBox> </StyledBox>
)} )}
/> />
</>
<Controller )}
name="option.danger_accept_invalid_certs" </BaseDialog>
control={control} );
render={({ field }) => ( };
<StyledBox>
<InputLabel>{t("Accept Invalid Certs (Danger)")}</InputLabel>
<Switch checked={field.value} {...field} color="primary" />
</StyledBox>
)}
/>
</>
)}
</BaseDialog>
);
},
);
const StyledBox = styled(Box)(() => ({ const StyledBox = styled(Box)(() => ({
margin: "8px 0 8px 8px", margin: "8px 0 8px 8px",

View File

@@ -1,23 +1,19 @@
import { ExpandMoreRounded } from "@mui/icons-material";
import { import {
Box, Box,
Snackbar, Snackbar,
Alert, Alert,
Chip, Chip,
Stack,
Typography, Typography,
IconButton, IconButton,
Collapse,
Menu, Menu,
MenuItem, MenuItem,
Divider,
Button,
} from "@mui/material"; } from "@mui/material";
import { ArchiveOutlined, ExpandMoreRounded } from "@mui/icons-material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { useRef, useState, useEffect, useCallback, useMemo } from "react"; import { useRef, useState, useEffect, useCallback, useMemo } from "react";
import useSWR from "swr";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"; import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
import useSWR from "swr";
import { useProxySelection } from "@/hooks/use-proxy-selection"; import { useProxySelection } from "@/hooks/use-proxy-selection";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
@@ -34,8 +30,8 @@ import { BaseEmpty } from "../base";
import { ScrollTopButton } from "../layout/scroll-top-button"; import { ScrollTopButton } from "../layout/scroll-top-button";
import { ProxyChain } from "./proxy-chain"; import { ProxyChain } from "./proxy-chain";
import { ProxyRender } from "./proxy-render";
import { ProxyGroupNavigator } from "./proxy-group-navigator"; import { ProxyGroupNavigator } from "./proxy-group-navigator";
import { ProxyRender } from "./proxy-render";
import { useRenderList } from "./use-render-list"; import { useRenderList } from "./use-render-list";
interface Props { interface Props {

View File

@@ -1,16 +1,10 @@
import { Box, Paper, Divider } from "@mui/material"; import { Box, Paper, Divider } from "@mui/material";
import dayjs from "dayjs"; import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat"; import customParseFormat from "dayjs/plugin/customParseFormat";
import { import { useImperativeHandle, useState, useCallback, useMemo } from "react";
forwardRef,
useImperativeHandle,
useState,
useCallback,
useMemo,
} from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef } from "@/components/base"; import { BaseDialog } from "@/components/base";
import { BaseLoadingOverlay } from "@/components/base"; import { BaseLoadingOverlay } from "@/components/base";
import { listWebDavBackup } from "@/services/cmds"; import { listWebDavBackup } from "@/services/cmds";
@@ -25,7 +19,7 @@ dayjs.extend(customParseFormat);
const DATE_FORMAT = "YYYY-MM-DD_HH-mm-ss"; const DATE_FORMAT = "YYYY-MM-DD_HH-mm-ss";
const FILENAME_PATTERN = /\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/; const FILENAME_PATTERN = /\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/;
export const BackupViewer = forwardRef<DialogRef>((props, ref) => { export const BackupViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -131,4 +125,4 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
</Box> </Box>
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -12,11 +12,11 @@ import {
ListItemText, ListItemText,
} from "@mui/material"; } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { mutate } from "swr"; import { mutate } from "swr";
import { BaseDialog, DialogRef } from "@/components/base"; import { BaseDialog } from "@/components/base";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { changeClashCore, restartCore } from "@/services/cmds"; import { changeClashCore, restartCore } from "@/services/cmds";
import { import {
@@ -31,7 +31,7 @@ const VALID_CORE = [
{ name: "Mihomo Alpha", core: "verge-mihomo-alpha", chip: "Alpha Version" }, { name: "Mihomo Alpha", core: "verge-mihomo-alpha", chip: "Alpha Version" },
]; ];
export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => { export const ClashCoreViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { verge, mutateVerge } = useVerge(); const { verge, mutateVerge } = useVerge();
@@ -169,4 +169,4 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
</List> </List>
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -9,7 +9,7 @@ import {
TextField, TextField,
} from "@mui/material"; } from "@mui/material";
import { useLockFn, useRequest } from "ahooks"; import { useLockFn, useRequest } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, Switch } from "@/components/base"; import { BaseDialog, Switch } from "@/components/base";
@@ -30,10 +30,12 @@ interface ClashPortViewerRef {
const generateRandomPort = () => const generateRandomPort = () =>
Math.floor(Math.random() * (65535 - 1025 + 1)) + 1025; Math.floor(Math.random() * (65535 - 1025 + 1)) + 1025;
export const ClashPortViewer = forwardRef< export const ClashPortViewer = ({
ClashPortViewerRef, ref,
ClashPortViewerProps ...props
>((props, ref) => { }: ClashPortViewerProps & {
ref?: React.RefObject<ClashPortViewerRef | null>;
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { clashInfo, patchInfo } = useClashInfo(); const { clashInfo, patchInfo } = useClashInfo();
const { verge, patchVerge } = useVerge(); const { verge, patchVerge } = useVerge();
@@ -348,4 +350,4 @@ export const ClashPortViewer = forwardRef<
</List> </List>
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -1,12 +1,11 @@
import { Box, Chip } from "@mui/material"; import { Box, Chip } from "@mui/material";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { DialogRef } from "@/components/base";
import { EditorViewer } from "@/components/profile/editor-viewer"; import { EditorViewer } from "@/components/profile/editor-viewer";
import { getRuntimeYaml } from "@/services/cmds"; import { getRuntimeYaml } from "@/services/cmds";
export const ConfigViewer = forwardRef<DialogRef>((_, ref) => { export const ConfigViewer = ({ ref, ..._ }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [runtimeConfig, setRuntimeConfig] = useState(""); const [runtimeConfig, setRuntimeConfig] = useState("");
@@ -38,4 +37,4 @@ export const ConfigViewer = forwardRef<DialogRef>((_, ref) => {
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
/> />
); );
}); };

View File

@@ -12,15 +12,15 @@ import {
Tooltip, Tooltip,
} from "@mui/material"; } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base"; import { BaseDialog, Switch } from "@/components/base";
import { useClashInfo } from "@/hooks/use-clash"; import { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/noticeService"; import { showNotice } from "@/services/noticeService";
export const ControllerViewer = forwardRef<DialogRef>((props, ref) => { export const ControllerViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [copySuccess, setCopySuccess] = useState<null | string>(null); const [copySuccess, setCopySuccess] = useState<null | string>(null);
@@ -217,4 +217,4 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
</Snackbar> </Snackbar>
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -15,11 +15,11 @@ import {
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import yaml from "js-yaml"; import yaml from "js-yaml";
import { forwardRef, useImperativeHandle, useState, useEffect } from "react"; import { useImperativeHandle, useState, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import MonacoEditor from "react-monaco-editor"; import MonacoEditor from "react-monaco-editor";
import { BaseDialog, DialogRef, Switch } from "@/components/base"; import { BaseDialog, Switch } from "@/components/base";
import { useClash } from "@/hooks/use-clash"; import { useClash } from "@/hooks/use-clash";
import { showNotice } from "@/services/noticeService"; import { showNotice } from "@/services/noticeService";
import { useThemeMode } from "@/services/states"; import { useThemeMode } from "@/services/states";
@@ -87,7 +87,7 @@ const DEFAULT_DNS_CONFIG = {
}, },
}; };
export const DnsViewer = forwardRef<DialogRef>((props, ref) => { export const DnsViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { clash, mutateClash } = useClash(); const { clash, mutateClash } = useClash();
const themeMode = useThemeMode(); const themeMode = useThemeMode();
@@ -1034,4 +1034,4 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
)} )}
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -1,7 +1,7 @@
import { Delete as DeleteIcon } from "@mui/icons-material"; import { Delete as DeleteIcon } from "@mui/icons-material";
import { Box, Button, Divider, List, ListItem, TextField } from "@mui/material"; import { Box, Button, Divider, List, ListItem, TextField } from "@mui/material";
import { useLockFn, useRequest } from "ahooks"; import { useLockFn, useRequest } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, Switch } from "@/components/base"; import { BaseDialog, Switch } from "@/components/base";
@@ -71,201 +71,194 @@ interface ClashHeaderConfigingRef {
close: () => void; close: () => void;
} }
export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>( export const HeaderConfiguration = ({ ref, ...props }) => {
(props, ref) => { const { t } = useTranslation();
const { t } = useTranslation(); const { clash, mutateClash, patchClash } = useClash();
const { clash, mutateClash, patchClash } = useClash(); const [open, setOpen] = useState(false);
const [open, setOpen] = useState(false);
// CORS配置状态管理 // CORS配置状态管理
const [corsConfig, setCorsConfig] = useState<{ const [corsConfig, setCorsConfig] = useState<{
allowPrivateNetwork: boolean; allowPrivateNetwork: boolean;
allowOrigins: string[]; allowOrigins: string[];
}>(() => { }>(() => {
const cors = clash?.["external-controller-cors"];
const origins = cors?.["allow-origins"] ?? [];
return {
allowPrivateNetwork: cors?.["allow-private-network"] ?? true,
allowOrigins: filterBaseOriginsForUI(origins),
};
});
// 处理CORS配置变更
const handleCorsConfigChange = (
key: "allowPrivateNetwork" | "allowOrigins",
value: boolean | string[],
) => {
setCorsConfig((prev) => ({
...prev,
[key]: value,
}));
};
// 添加新的允许来源
const handleAddOrigin = () => {
handleCorsConfigChange("allowOrigins", [...corsConfig.allowOrigins, ""]);
};
// 更新允许来源列表中的某一项
const handleUpdateOrigin = (index: number, value: string) => {
const newOrigins = [...corsConfig.allowOrigins];
newOrigins[index] = value;
handleCorsConfigChange("allowOrigins", newOrigins);
};
// 删除允许来源列表中的某一项
const handleDeleteOrigin = (index: number) => {
const newOrigins = [...corsConfig.allowOrigins];
newOrigins.splice(index, 1);
handleCorsConfigChange("allowOrigins", newOrigins);
};
// 保存配置请求
const { loading, run: saveConfig } = useRequest(
async () => {
// 保存时使用完整的源列表包括开发URL
const fullOrigins = getFullOrigins(corsConfig.allowOrigins);
await patchClash({
"external-controller-cors": {
"allow-private-network": corsConfig.allowPrivateNetwork,
"allow-origins": fullOrigins.filter(
(origin: string) => origin.trim() !== "",
),
},
});
await mutateClash();
},
{
manual: true,
onSuccess: () => {
setOpen(false);
showNotice("success", t("Configuration saved successfully"));
},
onError: () => {
showNotice("error", t("Failed to save configuration"));
},
},
);
useImperativeHandle(ref, () => ({
open: () => {
const cors = clash?.["external-controller-cors"]; const cors = clash?.["external-controller-cors"];
const origins = cors?.["allow-origins"] ?? []; const origins = cors?.["allow-origins"] ?? [];
return { setCorsConfig({
allowPrivateNetwork: cors?.["allow-private-network"] ?? true, allowPrivateNetwork: cors?.["allow-private-network"] ?? true,
allowOrigins: filterBaseOriginsForUI(origins), allowOrigins: filterBaseOriginsForUI(origins),
}; });
}); setOpen(true);
},
close: () => setOpen(false),
}));
// 处理CORS配置变更 const handleSave = useLockFn(async () => {
const handleCorsConfigChange = ( await saveConfig();
key: "allowPrivateNetwork" | "allowOrigins", });
value: boolean | string[],
) => {
setCorsConfig((prev) => ({
...prev,
[key]: value,
}));
};
// 添加新的允许来源 return (
const handleAddOrigin = () => { <BaseDialog
handleCorsConfigChange("allowOrigins", [...corsConfig.allowOrigins, ""]); open={open}
}; title={t("External Cors Configuration")}
contentSx={{ width: 500 }}
okBtn={loading ? t("Saving...") : t("Save")}
cancelBtn={t("Cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={handleSave}
>
<List sx={{ width: "90%", padding: 2 }}>
<ListItem sx={{ padding: "8px 0" }}>
<Box
display="flex"
justifyContent="space-between"
alignItems="center"
width="100%"
>
<span style={{ fontWeight: "normal" }}>
{t("Allow private network access")}
</span>
<Switch
edge="end"
checked={corsConfig.allowPrivateNetwork}
onChange={(e) =>
handleCorsConfigChange("allowPrivateNetwork", e.target.checked)
}
/>
</Box>
</ListItem>
// 更新允许来源列表中的某一项 <Divider sx={{ my: 2 }} />
const handleUpdateOrigin = (index: number, value: string) => {
const newOrigins = [...corsConfig.allowOrigins];
newOrigins[index] = value;
handleCorsConfigChange("allowOrigins", newOrigins);
};
// 删除允许来源列表中的某一项
const handleDeleteOrigin = (index: number) => {
const newOrigins = [...corsConfig.allowOrigins];
newOrigins.splice(index, 1);
handleCorsConfigChange("allowOrigins", newOrigins);
};
// 保存配置请求
const { loading, run: saveConfig } = useRequest(
async () => {
// 保存时使用完整的源列表包括开发URL
const fullOrigins = getFullOrigins(corsConfig.allowOrigins);
await patchClash({
"external-controller-cors": {
"allow-private-network": corsConfig.allowPrivateNetwork,
"allow-origins": fullOrigins.filter(
(origin: string) => origin.trim() !== "",
),
},
});
await mutateClash();
},
{
manual: true,
onSuccess: () => {
setOpen(false);
showNotice("success", t("Configuration saved successfully"));
},
onError: () => {
showNotice("error", t("Failed to save configuration"));
},
},
);
useImperativeHandle(ref, () => ({
open: () => {
const cors = clash?.["external-controller-cors"];
const origins = cors?.["allow-origins"] ?? [];
setCorsConfig({
allowPrivateNetwork: cors?.["allow-private-network"] ?? true,
allowOrigins: filterBaseOriginsForUI(origins),
});
setOpen(true);
},
close: () => setOpen(false),
}));
const handleSave = useLockFn(async () => {
await saveConfig();
});
return (
<BaseDialog
open={open}
title={t("External Cors Configuration")}
contentSx={{ width: 500 }}
okBtn={loading ? t("Saving...") : t("Save")}
cancelBtn={t("Cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={handleSave}
>
<List sx={{ width: "90%", padding: 2 }}>
<ListItem sx={{ padding: "8px 0" }}>
<Box
display="flex"
justifyContent="space-between"
alignItems="center"
width="100%"
>
<span style={{ fontWeight: "normal" }}>
{t("Allow private network access")}
</span>
<Switch
edge="end"
checked={corsConfig.allowPrivateNetwork}
onChange={(e) =>
handleCorsConfigChange(
"allowPrivateNetwork",
e.target.checked,
)
}
/>
</Box>
</ListItem>
<Divider sx={{ my: 2 }} />
<ListItem sx={{ padding: "8px 0" }}>
<div style={{ width: "100%" }}>
<div style={{ marginBottom: 8, fontWeight: "bold" }}>
{t("Allowed Origins")}
</div>
{corsConfig.allowOrigins.map((origin, index) => (
<div
key={index}
style={{
display: "flex",
alignItems: "center",
marginBottom: 8,
}}
>
<TextField
fullWidth
size="small"
sx={{ fontSize: 14, marginRight: 2 }}
value={origin}
onChange={(e) => handleUpdateOrigin(index, e.target.value)}
placeholder={t("Please enter a valid url")}
inputProps={{ style: { fontSize: 14 } }}
/>
<Button
variant="contained"
color="error"
size="small"
onClick={() => handleDeleteOrigin(index)}
disabled={corsConfig.allowOrigins.length <= 0}
sx={deleteButtonStyle}
>
<DeleteIcon fontSize="small" />
</Button>
</div>
))}
<Button
variant="contained"
size="small"
onClick={handleAddOrigin}
sx={addButtonStyle}
>
{t("Add")}
</Button>
<ListItem sx={{ padding: "8px 0" }}>
<div style={{ width: "100%" }}>
<div style={{ marginBottom: 8, fontWeight: "bold" }}>
{t("Allowed Origins")}
</div>
{corsConfig.allowOrigins.map((origin, index) => (
<div <div
key={index}
style={{ style={{
marginTop: 12, display: "flex",
padding: 8, alignItems: "center",
backgroundColor: "#f5f5f5", marginBottom: 8,
borderRadius: 4,
}} }}
> >
<div <TextField
style={{ color: "#666", fontSize: 12, fontStyle: "italic" }} fullWidth
size="small"
sx={{ fontSize: 14, marginRight: 2 }}
value={origin}
onChange={(e) => handleUpdateOrigin(index, e.target.value)}
placeholder={t("Please enter a valid url")}
inputProps={{ style: { fontSize: 14 } }}
/>
<Button
variant="contained"
color="error"
size="small"
onClick={() => handleDeleteOrigin(index)}
disabled={corsConfig.allowOrigins.length <= 0}
sx={deleteButtonStyle}
> >
{t("Always included origins: {{urls}}", { <DeleteIcon fontSize="small" />
urls: DEV_URLS.join(", "), </Button>
})} </div>
</div> ))}
<Button
variant="contained"
size="small"
onClick={handleAddOrigin}
sx={addButtonStyle}
>
{t("Add")}
</Button>
<div
style={{
marginTop: 12,
padding: 8,
backgroundColor: "#f5f5f5",
borderRadius: 4,
}}
>
<div style={{ color: "#666", fontSize: 12, fontStyle: "italic" }}>
{t("Always included origins: {{urls}}", {
urls: DEV_URLS.join(", "),
})}
</div> </div>
</div> </div>
</ListItem> </div>
</List> </ListItem>
</BaseDialog> </List>
); </BaseDialog>
}, );
); };

View File

@@ -1,9 +1,9 @@
import { styled, Typography } from "@mui/material"; import { styled, Typography } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base"; import { BaseDialog, Switch } from "@/components/base";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/noticeService"; import { showNotice } from "@/services/noticeService";
@@ -26,7 +26,7 @@ const HOTKEY_FUNC = [
"entry_lightweight_mode", "entry_lightweight_mode",
]; ];
export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => { export const HotkeyViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -117,4 +117,4 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
))} ))}
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -12,10 +12,10 @@ import { convertFileSrc } from "@tauri-apps/api/core";
import { join } from "@tauri-apps/api/path"; import { join } from "@tauri-apps/api/path";
import { open as openDialog } from "@tauri-apps/plugin-dialog"; import { open as openDialog } from "@tauri-apps/plugin-dialog";
import { exists } from "@tauri-apps/plugin-fs"; import { exists } from "@tauri-apps/plugin-fs";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; import { useEffect, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base"; import { BaseDialog, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { copyIconFile, getAppDir } from "@/services/cmds"; import { copyIconFile, getAppDir } from "@/services/cmds";
@@ -38,7 +38,7 @@ const getIcons = async (icon_dir: string, name: string) => {
}; };
}; };
export const LayoutViewer = forwardRef<DialogRef>((props, ref) => { export const LayoutViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { verge, patchVerge, mutateVerge } = useVerge(); const { verge, patchVerge, mutateVerge } = useVerge();
@@ -387,7 +387,7 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
</List> </List>
</BaseDialog> </BaseDialog>
); );
}); };
const Item = styled(ListItem)(() => ({ const Item = styled(ListItem)(() => ({
padding: "5px 2px", padding: "5px 2px",

View File

@@ -7,16 +7,16 @@ import {
InputAdornment, InputAdornment,
} from "@mui/material"; } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base"; import { BaseDialog, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { entry_lightweight_mode } from "@/services/cmds"; import { entry_lightweight_mode } from "@/services/cmds";
import { showNotice } from "@/services/noticeService"; import { showNotice } from "@/services/noticeService";
export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => { export const LiteModeViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { verge, patchVerge } = useVerge(); const { verge, patchVerge } = useVerge();
@@ -143,4 +143,4 @@ export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
</List> </List>
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -8,15 +8,15 @@ import {
TextField, TextField,
} from "@mui/material"; } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base"; import { BaseDialog, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { showNotice } from "@/services/noticeService"; import { showNotice } from "@/services/noticeService";
export const MiscViewer = forwardRef<DialogRef>((props, ref) => { export const MiscViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { verge, patchVerge } = useVerge(); const { verge, patchVerge } = useVerge();
@@ -319,4 +319,4 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
</List> </List>
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -1,15 +1,15 @@
import { ContentCopyRounded } from "@mui/icons-material"; import { ContentCopyRounded } from "@mui/icons-material";
import { alpha, Box, Button, IconButton } from "@mui/material"; import { alpha, Box, Button, IconButton } from "@mui/material";
import { writeText } from "@tauri-apps/plugin-clipboard-manager"; import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import useSWR from "swr"; import useSWR from "swr";
import { BaseDialog, DialogRef } from "@/components/base"; import { BaseDialog } from "@/components/base";
import { getNetworkInterfacesInfo } from "@/services/cmds"; import { getNetworkInterfacesInfo } from "@/services/cmds";
import { showNotice } from "@/services/noticeService"; import { showNotice } from "@/services/noticeService";
export const NetworkInterfaceViewer = forwardRef<DialogRef>((props, ref) => { export const NetworkInterfaceViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [isV4, setIsV4] = useState(true); const [isV4, setIsV4] = useState(true);
@@ -99,7 +99,7 @@ export const NetworkInterfaceViewer = forwardRef<DialogRef>((props, ref) => {
))} ))}
</BaseDialog> </BaseDialog>
); );
}); };
const AddressDisplay = (props: { label: string; content: string }) => { const AddressDisplay = (props: { label: string; content: string }) => {
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -11,25 +11,19 @@ import {
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { import { useEffect, useImperativeHandle, useMemo, useState } from "react";
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useState,
} from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { BaseDialog, DialogRef, Switch } from "@/components/base"; import { BaseDialog, Switch } from "@/components/base";
import { BaseFieldset } from "@/components/base/base-fieldset"; import { BaseFieldset } from "@/components/base/base-fieldset";
import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { EditorViewer } from "@/components/profile/editor-viewer"; import { EditorViewer } from "@/components/profile/editor-viewer";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { useAppData } from "@/providers/app-data-provider"; import { useAppData } from "@/providers/app-data-provider";
import { getClashConfig } from "@/services/cmds";
import { import {
getAutotemProxy, getAutotemProxy,
getClashConfig,
getNetworkInterfacesInfo, getNetworkInterfacesInfo,
getSystemHostname, getSystemHostname,
getSystemProxy, getSystemProxy,
@@ -75,7 +69,7 @@ const getValidReg = (isWindows: boolean) => {
return new RegExp(rValid); return new RegExp(rValid);
}; };
export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => { export const SysproxyViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const isWindows = getSystem() === "windows"; const isWindows = getSystem() === "windows";
const validReg = useMemo(() => getValidReg(isWindows), [isWindows]); const validReg = useMemo(() => getValidReg(isWindows), [isWindows]);
@@ -619,7 +613,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
</List> </List>
</BaseDialog> </BaseDialog>
); );
}); };
const FlexBox = styled("div")` const FlexBox = styled("div")`
display: flex; display: flex;

View File

@@ -9,16 +9,16 @@ import {
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef } from "@/components/base"; import { BaseDialog } from "@/components/base";
import { EditorViewer } from "@/components/profile/editor-viewer"; import { EditorViewer } from "@/components/profile/editor-viewer";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { defaultTheme, defaultDarkTheme } from "@/pages/_theme"; import { defaultTheme, defaultDarkTheme } from "@/pages/_theme";
import { showNotice } from "@/services/noticeService"; import { showNotice } from "@/services/noticeService";
export const ThemeViewer = forwardRef<DialogRef>((props, ref) => { export const ThemeViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -144,7 +144,7 @@ export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
</List> </List>
</BaseDialog> </BaseDialog>
); );
}); };
const Item = styled(ListItem)(() => ({ const Item = styled(ListItem)(() => ({
padding: "5px 2px", padding: "5px 2px",

View File

@@ -8,10 +8,10 @@ import {
TextField, TextField,
} from "@mui/material"; } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base"; import { BaseDialog, Switch } from "@/components/base";
import { useClash } from "@/hooks/use-clash"; import { useClash } from "@/hooks/use-clash";
import { enhanceProfiles } from "@/services/cmds"; import { enhanceProfiles } from "@/services/cmds";
import { showNotice } from "@/services/noticeService"; import { showNotice } from "@/services/noticeService";
@@ -21,7 +21,7 @@ import { StackModeSwitch } from "./stack-mode-switch";
const OS = getSystem(); const OS = getSystem();
export const TunViewer = forwardRef<DialogRef>((props, ref) => { export const TunViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { clash, mutateClash, patchClash } = useClash(); const { clash, mutateClash, patchClash } = useClash();
@@ -238,4 +238,4 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
</List> </List>
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -4,24 +4,18 @@ import { relaunch } from "@tauri-apps/plugin-process";
import { open as openUrl } from "@tauri-apps/plugin-shell"; import { open as openUrl } from "@tauri-apps/plugin-shell";
import { check as checkUpdate } from "@tauri-apps/plugin-updater"; import { check as checkUpdate } from "@tauri-apps/plugin-updater";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { import { useImperativeHandle, useState, useMemo, useEffect } from "react";
forwardRef,
useImperativeHandle,
useState,
useMemo,
useEffect,
} from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import useSWR from "swr"; import useSWR from "swr";
import { BaseDialog, DialogRef } from "@/components/base"; import { BaseDialog } from "@/components/base";
import { useListen } from "@/hooks/use-listen"; import { useListen } from "@/hooks/use-listen";
import { portableFlag } from "@/pages/_layout"; import { portableFlag } from "@/pages/_layout";
import { showNotice } from "@/services/noticeService"; import { showNotice } from "@/services/noticeService";
import { useUpdateState, useSetUpdateState } from "@/services/states"; import { useUpdateState, useSetUpdateState } from "@/services/states";
export const UpdateViewer = forwardRef<DialogRef>((props, ref) => { export const UpdateViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -167,4 +161,4 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
)} )}
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -1,9 +1,9 @@
import { Button, Box, Typography } from "@mui/material"; import { Button, Box, Typography } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, BaseEmpty, DialogRef } from "@/components/base"; import { BaseDialog, BaseEmpty } from "@/components/base";
import { useClashInfo } from "@/hooks/use-clash"; import { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { openWebUrl } from "@/services/cmds"; import { openWebUrl } from "@/services/cmds";
@@ -11,7 +11,7 @@ import { showNotice } from "@/services/noticeService";
import { WebUIItem } from "./web-ui-item"; import { WebUIItem } from "./web-ui-item";
export const WebUIViewer = forwardRef<DialogRef>((props, ref) => { export const WebUIViewer = ({ ref, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { clashInfo } = useClashInfo(); const { clashInfo } = useClashInfo();
@@ -139,4 +139,4 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
)} )}
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -1,7 +1,7 @@
import { TextField } from "@mui/material"; import { TextField } from "@mui/material";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { forwardRef, useImperativeHandle, useState } from "react"; import { useImperativeHandle, useState } from "react";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -19,7 +19,10 @@ export interface TestViewerRef {
} }
// create or edit the test item // create or edit the test item
export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => { export const TestViewer = ({
ref,
...props
}: Props & { ref?: React.RefObject<TestViewerRef | null> }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [openType, setOpenType] = useState<"new" | "edit">("new"); const [openType, setOpenType] = useState<"new" | "edit">("new");
@@ -173,4 +176,4 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
/> />
</BaseDialog> </BaseDialog>
); );
}); };

View File

@@ -1,11 +1,5 @@
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import React, { import React, { createContext, use, useEffect, useMemo, useRef } from "react";
createContext,
useContext,
useEffect,
useMemo,
useRef,
} from "react";
import useSWR from "swr"; import useSWR from "swr";
import { useClashInfo } from "@/hooks/use-clash"; import { useClashInfo } from "@/hooks/use-clash";
@@ -589,14 +583,12 @@ export const AppDataProvider = ({
refreshAll, refreshAll,
]); ]);
return ( return <AppDataContext value={value}>{children}</AppDataContext>;
<AppDataContext.Provider value={value}>{children}</AppDataContext.Provider>
);
}; };
// 自定义Hook访问全局数据 // 自定义Hook访问全局数据
export const useAppData = () => { export const useAppData = () => {
const context = useContext(AppDataContext); const context = use(AppDataContext);
if (!context) { if (!context) {
throw new Error("useAppData必须在AppDataProvider内使用"); throw new Error("useAppData必须在AppDataProvider内使用");

View File

@@ -1,4 +1,4 @@
import React, { createContext, useCallback, useContext, useState } from "react"; import React, { createContext, useCallback, use, useState } from "react";
interface ChainProxyContextType { interface ChainProxyContextType {
isChainMode: boolean; isChainMode: boolean;
@@ -26,7 +26,7 @@ export const ChainProxyProvider = ({
}, []); }, []);
return ( return (
<ChainProxyContext.Provider <ChainProxyContext
value={{ value={{
isChainMode, isChainMode,
setChainMode, setChainMode,
@@ -35,12 +35,12 @@ export const ChainProxyProvider = ({
}} }}
> >
{children} {children}
</ChainProxyContext.Provider> </ChainProxyContext>
); );
}; };
export const useChainProxy = () => { export const useChainProxy = () => {
const context = useContext(ChainProxyContext); const context = use(ChainProxyContext);
if (!context) { if (!context) {
throw new Error("useChainProxy must be used within a ChainProxyProvider"); throw new Error("useChainProxy must be used within a ChainProxyProvider");
} }