mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 17:15:38 +08:00
feat: reactive after save when profile content changes
This commit is contained in:
@@ -27,10 +27,10 @@ export const ConfirmViewer = (props: Props) => {
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
|
||||
<DialogTitle>{t(title)}</DialogTitle>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ pb: 1, userSelect: "text" }}>
|
||||
{t(message)}
|
||||
{message}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
|
||||
@@ -32,7 +32,7 @@ interface Props {
|
||||
language: "yaml" | "javascript" | "css";
|
||||
schema?: "clash" | "merge";
|
||||
onClose: () => void;
|
||||
onChange?: (content?: string) => void;
|
||||
onChange?: (prev?: string, curr?: string) => void;
|
||||
}
|
||||
|
||||
// yaml worker
|
||||
@@ -90,6 +90,7 @@ export const EditorViewer = (props: Props) => {
|
||||
const editorRef = useRef<any>();
|
||||
const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||
const themeMode = useThemeMode();
|
||||
const prevData = useRef<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
@@ -136,6 +137,8 @@ export const EditorViewer = (props: Props) => {
|
||||
fontLigatures: true, // 连字符
|
||||
smoothScrolling: true, // 平滑滚动
|
||||
});
|
||||
|
||||
prevData.current = data;
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -147,15 +150,15 @@ export const EditorViewer = (props: Props) => {
|
||||
}, [open]);
|
||||
|
||||
const onSave = useLockFn(async () => {
|
||||
const value = instanceRef.current?.getValue();
|
||||
const currData = instanceRef.current?.getValue();
|
||||
|
||||
if (value == null) return;
|
||||
if (currData == null) return;
|
||||
|
||||
try {
|
||||
if (mode === "profile") {
|
||||
await saveProfileFile(property, value);
|
||||
await saveProfileFile(property, currData);
|
||||
}
|
||||
onChange?.(value);
|
||||
onChange?.(prevData.current, currData);
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
Notice.error(err.message || err.toString());
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "@mui/material";
|
||||
import { RefreshRounded, DragIndicator } from "@mui/icons-material";
|
||||
import { useLoadingCache, useSetLoadingCache } from "@/services/states";
|
||||
import { updateProfile, deleteProfile, viewProfile } from "@/services/cmds";
|
||||
import { updateProfile, viewProfile } from "@/services/cmds";
|
||||
import { Notice } from "@/components/base";
|
||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||
import { ProfileBox } from "./profile-box";
|
||||
@@ -36,10 +36,20 @@ interface Props {
|
||||
itemData: IProfileItem;
|
||||
onSelect: (force: boolean) => void;
|
||||
onEdit: () => void;
|
||||
onChange?: (prev?: string, curr?: string) => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export const ProfileItem = (props: Props) => {
|
||||
const { selected, activating, itemData, onSelect, onEdit } = props;
|
||||
const {
|
||||
selected,
|
||||
activating,
|
||||
itemData,
|
||||
onSelect,
|
||||
onEdit,
|
||||
onChange,
|
||||
onDelete,
|
||||
} = props;
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({ id: props.id });
|
||||
|
||||
@@ -53,6 +63,7 @@ export const ProfileItem = (props: Props) => {
|
||||
|
||||
// local file mode
|
||||
// remote file mode
|
||||
// remote file mode
|
||||
const hasUrl = !!itemData.url;
|
||||
const hasExtra = !!extra; // only subscription url has extra info
|
||||
const hasHome = !!itemData.home; // only subscription url has home page
|
||||
@@ -162,16 +173,6 @@ export const ProfileItem = (props: Props) => {
|
||||
}
|
||||
});
|
||||
|
||||
const onDelete = useLockFn(async () => {
|
||||
setAnchorEl(null);
|
||||
try {
|
||||
await deleteProfile(itemData.uid);
|
||||
mutate("getProfiles");
|
||||
} catch (err: any) {
|
||||
Notice.error(err?.message || err.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const urlModeMenu = (
|
||||
hasHome ? [{ label: "Home", handler: onOpenHome }] : []
|
||||
).concat([
|
||||
@@ -242,7 +243,7 @@ export const ProfileItem = (props: Props) => {
|
||||
backdropFilter: "blur(2px)",
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={20} />
|
||||
<CircularProgress color="inherit" size={20} />
|
||||
</Box>
|
||||
)}
|
||||
<Box position="relative">
|
||||
@@ -312,7 +313,7 @@ export const ProfileItem = (props: Props) => {
|
||||
</Typography>
|
||||
) : (
|
||||
hasUrl && (
|
||||
<Typography noWrap title={`From ${from}`}>
|
||||
<Typography noWrap title={`${t("From")} ${from}`}>
|
||||
{from}
|
||||
</Typography>
|
||||
)
|
||||
@@ -323,7 +324,7 @@ export const ProfileItem = (props: Props) => {
|
||||
flex="1 0 auto"
|
||||
fontSize={14}
|
||||
textAlign="right"
|
||||
title={`Updated Time: ${parseExpire(updated)}`}
|
||||
title={`${t("Update Time")}: ${parseExpire(updated)}`}
|
||||
>
|
||||
{updated > 0 ? dayjs(updated * 1000).fromNow() : ""}
|
||||
</Typography>
|
||||
@@ -334,17 +335,21 @@ export const ProfileItem = (props: Props) => {
|
||||
{/* the third line show extra info or last updated time */}
|
||||
{hasExtra ? (
|
||||
<Box sx={{ ...boxStyle, fontSize: 14 }}>
|
||||
<span title="Used / Total">
|
||||
<span title={t("Used / Total")}>
|
||||
{parseTraffic(upload + download)} / {parseTraffic(total)}
|
||||
</span>
|
||||
<span title="Expire Time">{expire}</span>
|
||||
<span title={t("Expire Time")}>{expire}</span>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ ...boxStyle, fontSize: 12, justifyContent: "flex-end" }}>
|
||||
<span title="Updated Time">{parseExpire(updated)}</span>
|
||||
<span title={t("Update Time")}>{parseExpire(updated)}</span>
|
||||
</Box>
|
||||
)}
|
||||
<LinearProgress variant="determinate" value={progress} />
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress}
|
||||
style={{ opacity: progress > 0 ? 1 : 0 }}
|
||||
/>
|
||||
</ProfileBox>
|
||||
|
||||
<Menu
|
||||
@@ -390,11 +395,12 @@ export const ProfileItem = (props: Props) => {
|
||||
open={fileOpen}
|
||||
language="yaml"
|
||||
schema="clash"
|
||||
onChange={onChange}
|
||||
onClose={() => setFileOpen(false)}
|
||||
/>
|
||||
<ConfirmViewer
|
||||
title="Confirm deletion"
|
||||
message="This operation is not reversible"
|
||||
title={t("Confirm deletion")}
|
||||
message={t("This operation is not reversible")}
|
||||
open={confirmOpen}
|
||||
onClose={() => setConfirmOpen(false)}
|
||||
onConfirm={() => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
MenuItem,
|
||||
Menu,
|
||||
IconButton,
|
||||
CircularProgress,
|
||||
} from "@mui/material";
|
||||
import { FeaturedPlayListRounded } from "@mui/icons-material";
|
||||
import { viewProfile } from "@/services/cmds";
|
||||
@@ -20,6 +21,7 @@ import { ConfirmViewer } from "./confirm-viewer";
|
||||
|
||||
interface Props {
|
||||
selected: boolean;
|
||||
activating: boolean;
|
||||
itemData: IProfileItem;
|
||||
enableNum: number;
|
||||
logInfo?: [string, string][];
|
||||
@@ -27,14 +29,16 @@ interface Props {
|
||||
onDisable: () => void;
|
||||
onMoveTop: () => void;
|
||||
onMoveEnd: () => void;
|
||||
onDelete: () => void;
|
||||
onEdit: () => void;
|
||||
onChange?: (prev?: string, curr?: string) => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
// profile enhanced item
|
||||
export const ProfileMore = (props: Props) => {
|
||||
const {
|
||||
selected,
|
||||
activating,
|
||||
itemData,
|
||||
enableNum,
|
||||
logInfo = [],
|
||||
@@ -44,6 +48,7 @@ export const ProfileMore = (props: Props) => {
|
||||
onMoveEnd,
|
||||
onDelete,
|
||||
onEdit,
|
||||
onChange,
|
||||
} = props;
|
||||
|
||||
const { uid, type } = itemData;
|
||||
@@ -132,6 +137,24 @@ export const ProfileMore = (props: Props) => {
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{activating && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
top: 10,
|
||||
left: 10,
|
||||
right: 10,
|
||||
bottom: 2,
|
||||
zIndex: 10,
|
||||
backdropFilter: "blur(2px)",
|
||||
}}
|
||||
>
|
||||
<CircularProgress color="inherit" size={20} />
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
@@ -237,11 +260,12 @@ export const ProfileMore = (props: Props) => {
|
||||
open={fileOpen}
|
||||
language={type === "merge" ? "yaml" : "javascript"}
|
||||
schema={type === "merge" ? "merge" : undefined}
|
||||
onChange={onChange}
|
||||
onClose={() => setFileOpen(false)}
|
||||
/>
|
||||
<ConfirmViewer
|
||||
title="Confirm deletion"
|
||||
message="This operation is not reversible"
|
||||
title={t("Confirm deletion")}
|
||||
message={t("This operation is not reversible")}
|
||||
open={confirmOpen}
|
||||
onClose={() => setConfirmOpen(false)}
|
||||
onConfirm={() => {
|
||||
|
||||
@@ -249,10 +249,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
property={value.pac_content ?? ""}
|
||||
open={editorOpen}
|
||||
language="javascript"
|
||||
onChange={(content) => {
|
||||
onChange={(_prev, curr) => {
|
||||
let pac = DEFAULT_PAC;
|
||||
if (content && content.trim().length > 0) {
|
||||
pac = content;
|
||||
if (curr && curr.trim().length > 0) {
|
||||
pac = curr;
|
||||
}
|
||||
setValue((v) => ({ ...v, pac_content: pac }));
|
||||
}}
|
||||
|
||||
@@ -129,8 +129,8 @@ export const ThemeViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
property={theme.css_injection ?? ""}
|
||||
open={editorOpen}
|
||||
language="css"
|
||||
onChange={(content) => {
|
||||
theme.css_injection = content;
|
||||
onChange={(_prev, curr) => {
|
||||
theme.css_injection = curr;
|
||||
handleChange("css_injection");
|
||||
}}
|
||||
onClose={() => {
|
||||
|
||||
Reference in New Issue
Block a user