refactor: notice caller
This commit is contained in:
parent
9ca83d3291
commit
4e2cb30db7
88
src/components/notice.tsx
Normal file
88
src/components/notice.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { ReactNode, useState } from "react";
|
||||||
|
import { Box, IconButton, Slide, Snackbar, Typography } from "@mui/material";
|
||||||
|
import { Close, CheckCircleRounded, ErrorRounded } from "@mui/icons-material";
|
||||||
|
|
||||||
|
interface InnerProps {
|
||||||
|
type: string;
|
||||||
|
duration?: number;
|
||||||
|
message: ReactNode;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NoticeInner = (props: InnerProps) => {
|
||||||
|
const { type, message, duration = 2000, onClose } = props;
|
||||||
|
const [visible, setVisible] = useState(true);
|
||||||
|
|
||||||
|
const onBtnClose = () => {
|
||||||
|
setVisible(false);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
const onAutoClose = (_e: any, reason: string) => {
|
||||||
|
if (reason !== "clickaway") onBtnClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const msgElement =
|
||||||
|
type === "info" ? (
|
||||||
|
message
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
{type === "error" && <ErrorRounded color="error" />}
|
||||||
|
{type === "success" && <CheckCircleRounded color="success" />}
|
||||||
|
|
||||||
|
<Typography sx={{ ml: 1 }}>{message}</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Snackbar
|
||||||
|
open={visible}
|
||||||
|
anchorOrigin={{ vertical: "top", horizontal: "right" }}
|
||||||
|
autoHideDuration={duration}
|
||||||
|
onClose={onAutoClose}
|
||||||
|
message={msgElement}
|
||||||
|
sx={{ maxWidth: 360 }}
|
||||||
|
TransitionComponent={(p) => <Slide {...p} direction="left" />}
|
||||||
|
transitionDuration={200}
|
||||||
|
action={
|
||||||
|
<IconButton size="small" color="inherit" onClick={onBtnClose}>
|
||||||
|
<Close fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface NoticeInstance {
|
||||||
|
(props: Omit<InnerProps, "onClose">): void;
|
||||||
|
|
||||||
|
info(message: ReactNode, duration?: number): void;
|
||||||
|
error(message: ReactNode, duration?: number): void;
|
||||||
|
success(message: ReactNode, duration?: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent: HTMLDivElement = null!;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const Notice: NoticeInstance = (props) => {
|
||||||
|
if (!parent) {
|
||||||
|
parent = document.createElement("div");
|
||||||
|
document.body.appendChild(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.createElement("div");
|
||||||
|
parent.appendChild(container);
|
||||||
|
|
||||||
|
const onUnmount = () => {
|
||||||
|
const result = ReactDOM.unmountComponentAtNode(container);
|
||||||
|
if (result && parent) parent.removeChild(container);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<NoticeInner {...props} onClose={onUnmount} />, container);
|
||||||
|
};
|
||||||
|
|
||||||
|
(["info", "error", "success"] as const).forEach((type) => {
|
||||||
|
Notice[type] = (message, duration) => Notice({ type, message, duration });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Notice;
|
@ -9,14 +9,13 @@ import {
|
|||||||
} from "../services/cmds";
|
} from "../services/cmds";
|
||||||
import { getProxies, updateProxy } from "../services/api";
|
import { getProxies, updateProxy } from "../services/api";
|
||||||
import noop from "../utils/noop";
|
import noop from "../utils/noop";
|
||||||
import useNotice from "../utils/use-notice";
|
import Notice from "../components/notice";
|
||||||
import BasePage from "../components/base-page";
|
import BasePage from "../components/base-page";
|
||||||
import ProfileItemComp from "../components/profile-item";
|
import ProfileItemComp from "../components/profile-item";
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
const [disabled, setDisabled] = useState(false);
|
const [disabled, setDisabled] = useState(false);
|
||||||
const [notice, noticeElement] = useNotice();
|
|
||||||
|
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
const { data: profiles = {} } = useSWR("getProfiles", getProfiles);
|
const { data: profiles = {} } = useSWR("getProfiles", getProfiles);
|
||||||
@ -72,9 +71,9 @@ const ProfilePage = () => {
|
|||||||
await importProfile(url);
|
await importProfile(url);
|
||||||
mutate("getProfiles", getProfiles());
|
mutate("getProfiles", getProfiles());
|
||||||
if (!profiles.items?.length) selectProfile(0).catch(noop);
|
if (!profiles.items?.length) selectProfile(0).catch(noop);
|
||||||
notice.success("Successfully import profile.");
|
Notice.success("Successfully import profile.");
|
||||||
} catch {
|
} catch {
|
||||||
notice.error("Failed to import profile.");
|
Notice.error("Failed to import profile.");
|
||||||
} finally {
|
} finally {
|
||||||
setDisabled(false);
|
setDisabled(false);
|
||||||
}
|
}
|
||||||
@ -108,7 +107,7 @@ const ProfilePage = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
value={url}
|
value={url}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
sx={{ mr: 4 }}
|
sx={{ mr: 2 }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
disabled={!url || disabled}
|
disabled={!url || disabled}
|
||||||
@ -131,8 +130,6 @@ const ProfilePage = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{noticeElement}
|
|
||||||
</BasePage>
|
</BasePage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
import { useMemo, useState } from "react";
|
|
||||||
import { Box, IconButton, Slide, Snackbar } from "@mui/material";
|
|
||||||
import { Close, CheckCircleRounded, ErrorRounded } from "@mui/icons-material";
|
|
||||||
|
|
||||||
interface NoticeInstance {
|
|
||||||
info: (msg: string) => void;
|
|
||||||
error: (msg: string) => void;
|
|
||||||
success: (msg: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useNotice = () => {
|
|
||||||
const [message, setMessage] = useState("");
|
|
||||||
const [level, setLevel] = useState<"info" | "error" | "success">("info");
|
|
||||||
|
|
||||||
const handleClose = (_e: any, reason: string) => {
|
|
||||||
if (reason !== "clickaway") setMessage("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const msgElement =
|
|
||||||
level === "info" ? (
|
|
||||||
message
|
|
||||||
) : (
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
|
||||||
{level === "error" && <ErrorRounded color="error" />}
|
|
||||||
{level === "success" && <CheckCircleRounded color="success" />}
|
|
||||||
<span style={{ marginLeft: 4 }}>{message}</span>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const element = useMemo(
|
|
||||||
() => (
|
|
||||||
<Snackbar
|
|
||||||
open={!!message}
|
|
||||||
anchorOrigin={{ vertical: "top", horizontal: "right" }}
|
|
||||||
autoHideDuration={3000}
|
|
||||||
onClose={handleClose}
|
|
||||||
message={msgElement}
|
|
||||||
sx={{ maxWidth: 360 }}
|
|
||||||
TransitionComponent={(p) => <Slide {...p} direction="left" />}
|
|
||||||
transitionDuration={200}
|
|
||||||
action={
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
color="inherit"
|
|
||||||
onClick={() => setMessage("")}
|
|
||||||
>
|
|
||||||
<Close fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[message]
|
|
||||||
);
|
|
||||||
|
|
||||||
const instance = (Object.fromEntries(
|
|
||||||
(["info", "error", "success"] as const).map((item) => [
|
|
||||||
item,
|
|
||||||
(msg: string) => {
|
|
||||||
setLevel(item);
|
|
||||||
setMessage(msg);
|
|
||||||
},
|
|
||||||
])
|
|
||||||
) as unknown) as NoticeInstance;
|
|
||||||
|
|
||||||
return [instance, element] as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useNotice;
|
|
Loading…
Reference in New Issue
Block a user