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:
@@ -2,7 +2,7 @@ import { Box, Button, Snackbar, useTheme } from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import dayjs from "dayjs";
|
||||
import { t } from "i18next";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
|
||||
import { deleteConnection } from "@/services/cmds";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
@@ -11,45 +11,43 @@ export interface ConnectionDetailRef {
|
||||
open: (detail: IConnectionsItem) => void;
|
||||
}
|
||||
|
||||
export const ConnectionDetail = forwardRef<ConnectionDetailRef>(
|
||||
(props, ref) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [detail, setDetail] = useState<IConnectionsItem>(null!);
|
||||
const theme = useTheme();
|
||||
export const ConnectionDetail = ({ ref, ...props }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [detail, setDetail] = useState<IConnectionsItem>(null!);
|
||||
const theme = useTheme();
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: (detail: IConnectionsItem) => {
|
||||
if (open) return;
|
||||
setOpen(true);
|
||||
setDetail(detail);
|
||||
},
|
||||
}));
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: (detail: IConnectionsItem) => {
|
||||
if (open) return;
|
||||
setOpen(true);
|
||||
setDetail(detail);
|
||||
},
|
||||
}));
|
||||
|
||||
const onClose = () => setOpen(false);
|
||||
const onClose = () => setOpen(false);
|
||||
|
||||
return (
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
sx={{
|
||||
".MuiSnackbarContent-root": {
|
||||
maxWidth: "520px",
|
||||
maxHeight: "480px",
|
||||
overflowY: "auto",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
message={
|
||||
detail ? (
|
||||
<InnerConnectionDetail data={detail} onClose={onClose} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
return (
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
sx={{
|
||||
".MuiSnackbarContent-root": {
|
||||
maxWidth: "520px",
|
||||
maxHeight: "480px",
|
||||
overflowY: "auto",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
message={
|
||||
detail ? (
|
||||
<InnerConnectionDetail data={detail} onClose={onClose} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface InnerProps {
|
||||
data: IConnectionsItem;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import { useTheme } from "@mui/material";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
|
||||
import { useEffect, useImperativeHandle, useRef } from "react";
|
||||
|
||||
const maxPoint = 30;
|
||||
|
||||
@@ -24,7 +24,7 @@ export interface TrafficRef {
|
||||
/**
|
||||
* draw the traffic graph
|
||||
*/
|
||||
export const TrafficGraph = forwardRef<TrafficRef>((props, ref) => {
|
||||
export const TrafficGraph = ({ ref, ...props }) => {
|
||||
const countRef = useRef(0);
|
||||
const styleRef = useRef(true);
|
||||
const listRef = useRef<TrafficData[]>(defaultList);
|
||||
@@ -196,4 +196,4 @@ export const TrafficGraph = forwardRef<TrafficRef>((props, ref) => {
|
||||
}, [palette]);
|
||||
|
||||
return <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,13 +9,7 @@ import {
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -38,304 +32,280 @@ export interface ProfileViewerRef {
|
||||
|
||||
// create or edit the profile
|
||||
// remote / local
|
||||
export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
||||
(props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [openType, setOpenType] = useState<"new" | "edit">("new");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { profiles } = useProfiles();
|
||||
export const ProfileViewer = ({
|
||||
ref,
|
||||
...props
|
||||
}: Props & { ref?: React.RefObject<ProfileViewerRef | null> }) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [openType, setOpenType] = useState<"new" | "edit">("new");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { profiles } = useProfiles();
|
||||
|
||||
// file input
|
||||
const fileDataRef = useRef<string | null>(null);
|
||||
// file input
|
||||
const fileDataRef = useRef<string | null>(null);
|
||||
|
||||
const {
|
||||
control,
|
||||
watch,
|
||||
register: _register,
|
||||
...formIns
|
||||
} = useForm<IProfileItem>({
|
||||
defaultValues: {
|
||||
type: "remote",
|
||||
name: "",
|
||||
desc: "",
|
||||
url: "",
|
||||
option: {
|
||||
with_proxy: false,
|
||||
self_proxy: false,
|
||||
},
|
||||
const {
|
||||
control,
|
||||
watch,
|
||||
register: _register,
|
||||
...formIns
|
||||
} = useForm<IProfileItem>({
|
||||
defaultValues: {
|
||||
type: "remote",
|
||||
name: "",
|
||||
desc: "",
|
||||
url: "",
|
||||
option: {
|
||||
with_proxy: false,
|
||||
self_proxy: false,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
create: () => {
|
||||
setOpenType("new");
|
||||
setOpen(true);
|
||||
},
|
||||
edit: (item) => {
|
||||
if (item) {
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
formIns.setValue(key as any, value);
|
||||
});
|
||||
}
|
||||
setOpenType("edit");
|
||||
setOpen(true);
|
||||
},
|
||||
}));
|
||||
useImperativeHandle(ref, () => ({
|
||||
create: () => {
|
||||
setOpenType("new");
|
||||
setOpen(true);
|
||||
},
|
||||
edit: (item) => {
|
||||
if (item) {
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
formIns.setValue(key as any, value);
|
||||
});
|
||||
}
|
||||
setOpenType("edit");
|
||||
setOpen(true);
|
||||
},
|
||||
}));
|
||||
|
||||
const selfProxy = watch("option.self_proxy");
|
||||
const withProxy = watch("option.with_proxy");
|
||||
const selfProxy = watch("option.self_proxy");
|
||||
const withProxy = watch("option.with_proxy");
|
||||
|
||||
useEffect(() => {
|
||||
if (selfProxy) formIns.setValue("option.with_proxy", false);
|
||||
}, [selfProxy]);
|
||||
useEffect(() => {
|
||||
if (selfProxy) formIns.setValue("option.with_proxy", false);
|
||||
}, [selfProxy]);
|
||||
|
||||
useEffect(() => {
|
||||
if (withProxy) formIns.setValue("option.self_proxy", false);
|
||||
}, [withProxy]);
|
||||
useEffect(() => {
|
||||
if (withProxy) formIns.setValue("option.self_proxy", false);
|
||||
}, [withProxy]);
|
||||
|
||||
const handleOk = useLockFn(
|
||||
formIns.handleSubmit(async (form) => {
|
||||
if (form.option?.timeout_seconds) {
|
||||
form.option.timeout_seconds = +form.option.timeout_seconds;
|
||||
const handleOk = useLockFn(
|
||||
formIns.handleSubmit(async (form) => {
|
||||
if (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.type) throw new Error("`Type` should not be null");
|
||||
if (form.type === "remote" && !form.url) {
|
||||
throw new Error("The URL should not be null");
|
||||
}
|
||||
// 处理表单数据
|
||||
if (form.option?.update_interval) {
|
||||
form.option.update_interval = +form.option.update_interval;
|
||||
} else {
|
||||
delete form.option?.update_interval;
|
||||
}
|
||||
if (form.option?.user_agent === "") {
|
||||
delete form.option.user_agent;
|
||||
}
|
||||
|
||||
// 处理表单数据
|
||||
if (form.option?.update_interval) {
|
||||
form.option.update_interval = +form.option.update_interval;
|
||||
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") {
|
||||
await createProfile(item, fileDataRef.current);
|
||||
} 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 === "") {
|
||||
delete form.option.user_agent;
|
||||
}
|
||||
|
||||
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) {
|
||||
} else {
|
||||
// 远程配置使用回退机制
|
||||
try {
|
||||
// 尝试正常操作
|
||||
if (openType === "new") {
|
||||
await createProfile(item, fileDataRef.current);
|
||||
} else {
|
||||
if (!form.uid) throw new Error("UID not found");
|
||||
await patchProfile(form.uid, item);
|
||||
}
|
||||
} else {
|
||||
// 远程配置使用回退机制
|
||||
try {
|
||||
// 尝试正常操作
|
||||
if (openType === "new") {
|
||||
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..."),
|
||||
);
|
||||
} catch {
|
||||
// 首次创建/更新失败,尝试使用自身代理
|
||||
showNotice(
|
||||
"info",
|
||||
t("Profile creation failed, retrying with Clash proxy..."),
|
||||
);
|
||||
|
||||
// 使用自身代理的配置
|
||||
const retryItem = {
|
||||
...item,
|
||||
option: {
|
||||
...item.option,
|
||||
with_proxy: false,
|
||||
self_proxy: true,
|
||||
},
|
||||
};
|
||||
// 使用自身代理的配置
|
||||
const retryItem = {
|
||||
...item,
|
||||
option: {
|
||||
...item.option,
|
||||
with_proxy: false,
|
||||
self_proxy: true,
|
||||
},
|
||||
};
|
||||
|
||||
// 使用自身代理再次尝试
|
||||
if (openType === "new") {
|
||||
await createProfile(retryItem, fileDataRef.current);
|
||||
} else {
|
||||
if (!form.uid) throw new Error("UID not found");
|
||||
await patchProfile(form.uid, retryItem);
|
||||
// 使用自身代理再次尝试
|
||||
if (openType === "new") {
|
||||
await createProfile(retryItem, fileDataRef.current);
|
||||
} else {
|
||||
if (!form.uid) throw new Error("UID not found");
|
||||
await patchProfile(form.uid, retryItem);
|
||||
|
||||
// 编辑模式下恢复原始代理设置
|
||||
await patchProfile(form.uid, { option: originalOptions });
|
||||
}
|
||||
|
||||
showNotice(
|
||||
"success",
|
||||
t("Profile creation succeeded with Clash proxy"),
|
||||
);
|
||||
// 编辑模式下恢复原始代理设置
|
||||
await patchProfile(form.uid, { option: originalOptions });
|
||||
}
|
||||
|
||||
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);
|
||||
fileDataRef.current = null;
|
||||
setTimeout(() => formIns.reset(), 500);
|
||||
} catch (e) {
|
||||
console.warn("[ProfileViewer] handleClose error:", e);
|
||||
fileDataRef.current = null;
|
||||
|
||||
// 优化:UI先关闭,异步通知父组件
|
||||
setTimeout(() => {
|
||||
props.onChange(isActivating);
|
||||
}, 0);
|
||||
} catch (err: any) {
|
||||
showNotice("error", err.message || err.toString());
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const text = {
|
||||
fullWidth: true,
|
||||
size: "small",
|
||||
margin: "normal",
|
||||
variant: "outlined",
|
||||
autoComplete: "off",
|
||||
autoCorrect: "off",
|
||||
} as const;
|
||||
const handleClose = () => {
|
||||
try {
|
||||
setOpen(false);
|
||||
fileDataRef.current = null;
|
||||
setTimeout(() => formIns.reset(), 500);
|
||||
} catch (e) {
|
||||
console.warn("[ProfileViewer] handleClose error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
const formType = watch("type");
|
||||
const isRemote = formType === "remote";
|
||||
const isLocal = formType === "local";
|
||||
const text = {
|
||||
fullWidth: true,
|
||||
size: "small",
|
||||
margin: "normal",
|
||||
variant: "outlined",
|
||||
autoComplete: "off",
|
||||
autoCorrect: "off",
|
||||
} as const;
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
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>
|
||||
)}
|
||||
/>
|
||||
const formType = watch("type");
|
||||
const isRemote = formType === "remote";
|
||||
const isLocal = formType === "local";
|
||||
|
||||
<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
|
||||
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}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...text}
|
||||
{...field}
|
||||
type="number"
|
||||
placeholder="60"
|
||||
label={t("HTTP Request Timeout")}
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{t("seconds")}
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
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>
|
||||
)}
|
||||
/>
|
||||
|
||||
{(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
|
||||
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}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...text}
|
||||
{...field}
|
||||
type="number"
|
||||
label={t("Update Interval")}
|
||||
placeholder="60"
|
||||
label={t("HTTP Request Timeout")}
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{t("mins")}
|
||||
{t("seconds")}
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
@@ -343,57 +313,79 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{isLocal && openType === "new" && (
|
||||
<FileInput
|
||||
onChange={(file, val) => {
|
||||
formIns.setValue("name", formIns.getValues("name") || file.name);
|
||||
fileDataRef.current = val;
|
||||
}}
|
||||
{(isRemote || isLocal) && (
|
||||
<Controller
|
||||
name="option.update_interval"
|
||||
control={control}
|
||||
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.with_proxy"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<StyledBox>
|
||||
<InputLabel>{t("Use System Proxy")}</InputLabel>
|
||||
<Switch checked={field.value} {...field} color="primary" />
|
||||
</StyledBox>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="option.self_proxy"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<StyledBox>
|
||||
<InputLabel>{t("Use Clash Proxy")}</InputLabel>
|
||||
<Switch checked={field.value} {...field} color="primary" />
|
||||
</StyledBox>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="option.self_proxy"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<StyledBox>
|
||||
<InputLabel>{t("Use Clash Proxy")}</InputLabel>
|
||||
<Switch checked={field.value} {...field} color="primary" />
|
||||
</StyledBox>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="option.danger_accept_invalid_certs"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<StyledBox>
|
||||
<InputLabel>{t("Accept Invalid Certs (Danger)")}</InputLabel>
|
||||
<Switch checked={field.value} {...field} color="primary" />
|
||||
</StyledBox>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</BaseDialog>
|
||||
);
|
||||
},
|
||||
);
|
||||
<Controller
|
||||
name="option.danger_accept_invalid_certs"
|
||||
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)(() => ({
|
||||
margin: "8px 0 8px 8px",
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import { ExpandMoreRounded } from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Snackbar,
|
||||
Alert,
|
||||
Chip,
|
||||
Stack,
|
||||
Typography,
|
||||
IconButton,
|
||||
Collapse,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Divider,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import { ArchiveOutlined, ExpandMoreRounded } from "@mui/icons-material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useRef, useState, useEffect, useCallback, useMemo } from "react";
|
||||
import useSWR from "swr";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
@@ -34,8 +30,8 @@ import { BaseEmpty } from "../base";
|
||||
import { ScrollTopButton } from "../layout/scroll-top-button";
|
||||
|
||||
import { ProxyChain } from "./proxy-chain";
|
||||
import { ProxyRender } from "./proxy-render";
|
||||
import { ProxyGroupNavigator } from "./proxy-group-navigator";
|
||||
import { ProxyRender } from "./proxy-render";
|
||||
import { useRenderList } from "./use-render-list";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import { Box, Paper, Divider } from "@mui/material";
|
||||
import dayjs from "dayjs";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import {
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useState,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { useImperativeHandle, useState, useCallback, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { BaseDialog } from "@/components/base";
|
||||
import { BaseLoadingOverlay } from "@/components/base";
|
||||
import { listWebDavBackup } from "@/services/cmds";
|
||||
|
||||
@@ -25,7 +19,7 @@ dayjs.extend(customParseFormat);
|
||||
const DATE_FORMAT = "YYYY-MM-DD_HH-mm-ss";
|
||||
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 [open, setOpen] = useState(false);
|
||||
|
||||
@@ -131,4 +125,4 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</Box>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,11 +12,11 @@ import {
|
||||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { mutate } from "swr";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { BaseDialog } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { changeClashCore, restartCore } from "@/services/cmds";
|
||||
import {
|
||||
@@ -31,7 +31,7 @@ const VALID_CORE = [
|
||||
{ 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 { verge, mutateVerge } = useVerge();
|
||||
@@ -169,4 +169,4 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useLockFn, useRequest } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, Switch } from "@/components/base";
|
||||
@@ -30,10 +30,12 @@ interface ClashPortViewerRef {
|
||||
const generateRandomPort = () =>
|
||||
Math.floor(Math.random() * (65535 - 1025 + 1)) + 1025;
|
||||
|
||||
export const ClashPortViewer = forwardRef<
|
||||
ClashPortViewerRef,
|
||||
ClashPortViewerProps
|
||||
>((props, ref) => {
|
||||
export const ClashPortViewer = ({
|
||||
ref,
|
||||
...props
|
||||
}: ClashPortViewerProps & {
|
||||
ref?: React.RefObject<ClashPortViewerRef | null>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { clashInfo, patchInfo } = useClashInfo();
|
||||
const { verge, patchVerge } = useVerge();
|
||||
@@ -348,4 +350,4 @@ export const ClashPortViewer = forwardRef<
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Box, Chip } from "@mui/material";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { DialogRef } from "@/components/base";
|
||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
import { getRuntimeYaml } from "@/services/cmds";
|
||||
|
||||
export const ConfigViewer = forwardRef<DialogRef>((_, ref) => {
|
||||
export const ConfigViewer = ({ ref, ..._ }) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [runtimeConfig, setRuntimeConfig] = useState("");
|
||||
@@ -38,4 +37,4 @@ export const ConfigViewer = forwardRef<DialogRef>((_, ref) => {
|
||||
onClose={() => setOpen(false)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,15 +12,15 @@ import {
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
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 { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
|
||||
export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
export const ControllerViewer = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [copySuccess, setCopySuccess] = useState<null | string>(null);
|
||||
@@ -217,4 +217,4 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</Snackbar>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -15,11 +15,11 @@ import {
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useLockFn } from "ahooks";
|
||||
import yaml from "js-yaml";
|
||||
import { forwardRef, useImperativeHandle, useState, useEffect } from "react";
|
||||
import { useImperativeHandle, useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 { showNotice } from "@/services/noticeService";
|
||||
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 { clash, mutateClash } = useClash();
|
||||
const themeMode = useThemeMode();
|
||||
@@ -1034,4 +1034,4 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
)}
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Delete as DeleteIcon } from "@mui/icons-material";
|
||||
import { Box, Button, Divider, List, ListItem, TextField } from "@mui/material";
|
||||
import { useLockFn, useRequest } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, Switch } from "@/components/base";
|
||||
@@ -71,201 +71,194 @@ interface ClashHeaderConfigingRef {
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
|
||||
(props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { clash, mutateClash, patchClash } = useClash();
|
||||
const [open, setOpen] = useState(false);
|
||||
export const HeaderConfiguration = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const { clash, mutateClash, patchClash } = useClash();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
// CORS配置状态管理
|
||||
const [corsConfig, setCorsConfig] = useState<{
|
||||
allowPrivateNetwork: boolean;
|
||||
allowOrigins: string[];
|
||||
}>(() => {
|
||||
// CORS配置状态管理
|
||||
const [corsConfig, setCorsConfig] = useState<{
|
||||
allowPrivateNetwork: boolean;
|
||||
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 origins = cors?.["allow-origins"] ?? [];
|
||||
return {
|
||||
setCorsConfig({
|
||||
allowPrivateNetwork: cors?.["allow-private-network"] ?? true,
|
||||
allowOrigins: filterBaseOriginsForUI(origins),
|
||||
};
|
||||
});
|
||||
});
|
||||
setOpen(true);
|
||||
},
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
// 处理CORS配置变更
|
||||
const handleCorsConfigChange = (
|
||||
key: "allowPrivateNetwork" | "allowOrigins",
|
||||
value: boolean | string[],
|
||||
) => {
|
||||
setCorsConfig((prev) => ({
|
||||
...prev,
|
||||
[key]: value,
|
||||
}));
|
||||
};
|
||||
const handleSave = useLockFn(async () => {
|
||||
await saveConfig();
|
||||
});
|
||||
|
||||
// 添加新的允许来源
|
||||
const handleAddOrigin = () => {
|
||||
handleCorsConfigChange("allowOrigins", [...corsConfig.allowOrigins, ""]);
|
||||
};
|
||||
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>
|
||||
|
||||
// 更新允许来源列表中的某一项
|
||||
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>
|
||||
<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={{
|
||||
marginTop: 12,
|
||||
padding: 8,
|
||||
backgroundColor: "#f5f5f5",
|
||||
borderRadius: 4,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ color: "#666", fontSize: 12, fontStyle: "italic" }}
|
||||
<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}
|
||||
>
|
||||
{t("Always included origins: {{urls}}", {
|
||||
urls: DEV_URLS.join(", "),
|
||||
})}
|
||||
</div>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</Button>
|
||||
</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>
|
||||
</ListItem>
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
},
|
||||
);
|
||||
</div>
|
||||
</ListItem>
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { styled, Typography } from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
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 { showNotice } from "@/services/noticeService";
|
||||
|
||||
@@ -26,7 +26,7 @@ const HOTKEY_FUNC = [
|
||||
"entry_lightweight_mode",
|
||||
];
|
||||
|
||||
export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
export const HotkeyViewer = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -117,4 +117,4 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
))}
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,10 +12,10 @@ import { convertFileSrc } from "@tauri-apps/api/core";
|
||||
import { join } from "@tauri-apps/api/path";
|
||||
import { open as openDialog } from "@tauri-apps/plugin-dialog";
|
||||
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 { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { BaseDialog, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
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 { verge, patchVerge, mutateVerge } = useVerge();
|
||||
|
||||
@@ -387,7 +387,7 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const Item = styled(ListItem)(() => ({
|
||||
padding: "5px 2px",
|
||||
|
||||
@@ -7,16 +7,16 @@ import {
|
||||
InputAdornment,
|
||||
} from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
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 { useVerge } from "@/hooks/use-verge";
|
||||
import { entry_lightweight_mode } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
|
||||
export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
export const LiteModeViewer = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const { verge, patchVerge } = useVerge();
|
||||
|
||||
@@ -143,4 +143,4 @@ export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,15 +8,15 @@ import {
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
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 { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
|
||||
export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
export const MiscViewer = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const { verge, patchVerge } = useVerge();
|
||||
|
||||
@@ -319,4 +319,4 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { ContentCopyRounded } from "@mui/icons-material";
|
||||
import { alpha, Box, Button, IconButton } from "@mui/material";
|
||||
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 useSWR from "swr";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { BaseDialog } from "@/components/base";
|
||||
import { getNetworkInterfacesInfo } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
|
||||
export const NetworkInterfaceViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
export const NetworkInterfaceViewer = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [isV4, setIsV4] = useState(true);
|
||||
@@ -99,7 +99,7 @@ export const NetworkInterfaceViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
))}
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const AddressDisplay = (props: { label: string; content: string }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -11,25 +11,19 @@ import {
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useEffect, useImperativeHandle, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useAppData } from "@/providers/app-data-provider";
|
||||
import { getClashConfig } from "@/services/cmds";
|
||||
import {
|
||||
getAutotemProxy,
|
||||
getClashConfig,
|
||||
getNetworkInterfacesInfo,
|
||||
getSystemHostname,
|
||||
getSystemProxy,
|
||||
@@ -75,7 +69,7 @@ const getValidReg = (isWindows: boolean) => {
|
||||
return new RegExp(rValid);
|
||||
};
|
||||
|
||||
export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
export const SysproxyViewer = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const isWindows = getSystem() === "windows";
|
||||
const validReg = useMemo(() => getValidReg(isWindows), [isWindows]);
|
||||
@@ -619,7 +613,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const FlexBox = styled("div")`
|
||||
display: flex;
|
||||
|
||||
@@ -9,16 +9,16 @@ import {
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { BaseDialog } from "@/components/base";
|
||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { defaultTheme, defaultDarkTheme } from "@/pages/_theme";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
|
||||
export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
export const ThemeViewer = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -144,7 +144,7 @@ export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const Item = styled(ListItem)(() => ({
|
||||
padding: "5px 2px",
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
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 { enhanceProfiles } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
@@ -21,7 +21,7 @@ import { StackModeSwitch } from "./stack-mode-switch";
|
||||
|
||||
const OS = getSystem();
|
||||
|
||||
export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
export const TunViewer = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { clash, mutateClash, patchClash } = useClash();
|
||||
@@ -238,4 +238,4 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,24 +4,18 @@ import { relaunch } from "@tauri-apps/plugin-process";
|
||||
import { open as openUrl } from "@tauri-apps/plugin-shell";
|
||||
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
|
||||
import { useLockFn } from "ahooks";
|
||||
import {
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useState,
|
||||
useMemo,
|
||||
useEffect,
|
||||
} from "react";
|
||||
import { useImperativeHandle, useState, useMemo, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { BaseDialog } from "@/components/base";
|
||||
import { useListen } from "@/hooks/use-listen";
|
||||
import { portableFlag } from "@/pages/_layout";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { useUpdateState, useSetUpdateState } from "@/services/states";
|
||||
|
||||
export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
export const UpdateViewer = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -167,4 +161,4 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
)}
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Button, Box, Typography } from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
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 { useVerge } from "@/hooks/use-verge";
|
||||
import { openWebUrl } from "@/services/cmds";
|
||||
@@ -11,7 +11,7 @@ import { showNotice } from "@/services/noticeService";
|
||||
|
||||
import { WebUIItem } from "./web-ui-item";
|
||||
|
||||
export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
export const WebUIViewer = ({ ref, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { clashInfo } = useClashInfo();
|
||||
@@ -139,4 +139,4 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
)}
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TextField } from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { nanoid } from "nanoid";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -19,7 +19,10 @@ export interface TestViewerRef {
|
||||
}
|
||||
|
||||
// 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 [open, setOpen] = useState(false);
|
||||
const [openType, setOpenType] = useState<"new" | "edit">("new");
|
||||
@@ -173,4 +176,4 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from "react";
|
||||
import React, { createContext, use, useEffect, useMemo, useRef } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
@@ -589,14 +583,12 @@ export const AppDataProvider = ({
|
||||
refreshAll,
|
||||
]);
|
||||
|
||||
return (
|
||||
<AppDataContext.Provider value={value}>{children}</AppDataContext.Provider>
|
||||
);
|
||||
return <AppDataContext value={value}>{children}</AppDataContext>;
|
||||
};
|
||||
|
||||
// 自定义Hook访问全局数据
|
||||
export const useAppData = () => {
|
||||
const context = useContext(AppDataContext);
|
||||
const context = use(AppDataContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useAppData必须在AppDataProvider内使用");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { createContext, useCallback, useContext, useState } from "react";
|
||||
import React, { createContext, useCallback, use, useState } from "react";
|
||||
|
||||
interface ChainProxyContextType {
|
||||
isChainMode: boolean;
|
||||
@@ -26,7 +26,7 @@ export const ChainProxyProvider = ({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ChainProxyContext.Provider
|
||||
<ChainProxyContext
|
||||
value={{
|
||||
isChainMode,
|
||||
setChainMode,
|
||||
@@ -35,12 +35,12 @@ export const ChainProxyProvider = ({
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ChainProxyContext.Provider>
|
||||
</ChainProxyContext>
|
||||
);
|
||||
};
|
||||
|
||||
export const useChainProxy = () => {
|
||||
const context = useContext(ChainProxyContext);
|
||||
const context = use(ChainProxyContext);
|
||||
if (!context) {
|
||||
throw new Error("useChainProxy must be used within a ChainProxyProvider");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user