diff --git a/client/ui/App.tsx b/client/ui/App.tsx index a2e255d..a95c445 100644 --- a/client/ui/App.tsx +++ b/client/ui/App.tsx @@ -10,9 +10,10 @@ import RecentChat from "../api/client_data/RecentChat.ts" import * as React from 'react' import * as CryptoES from 'crypto-es' -import { Button, Dialog, NavigationRail, snackbar, TextField } from "mdui" +import { Button, Dialog, NavigationRail, TextField } from "mdui" import Split from 'split.js' import 'mdui/jsx.zh-cn.d.ts' +import { checkApiSuccessOrSncakbar } from "./snackbar.ts"; declare global { namespace React { @@ -74,10 +75,8 @@ export default function App() { account: account, password: CryptoES.SHA256(password), }) - if (re.code != 200) - snackbar({ - message: "登錄失敗: " + re.msg - }) + + if (checkApiSuccessOrSncakbar(re, "登錄失敗")) return }) React.useEffect(() => { @@ -95,9 +94,7 @@ export default function App() { if (re.code == 401) loginDialogRef.current!.open = true else if (re.code != 200) - snackbar({ - message: "驗證失敗: " + re.msg - }) + if (checkApiSuccessOrSncakbar(re, "驗證失敗")) return })() }, []) diff --git a/client/ui/snackbar.ts b/client/ui/snackbar.ts new file mode 100644 index 0000000..d00e49d --- /dev/null +++ b/client/ui/snackbar.ts @@ -0,0 +1,98 @@ +import { snackbar as mduiSnackbar, Snackbar } from "mdui" +import ApiCallbackMessage from "../api/ApiCallbackMessage.ts" + +interface Options { + /** + * Snackbar 出现的位置。默认为 `bottom`。可选值为: + * * `top`:位于顶部,居中对齐 + * * `top-start`:位于顶部,左对齐 + * * `top-end`:位于顶部,右对齐 + * * `bottom`:位于底部,居中对齐 + * * `bottom-start`:位于底部,左对齐 + * * `bottom-end`:位于底部,右对齐 + */ + placement?: 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end'; + /** + * 操作按钮的文本 + */ + action?: string; + /** + * 是否在右侧显示关闭按钮 + */ + closeable?: boolean; + /** + * 消息文本最多显示几行。默认不限制行数。可选值为 + * * `1`:消息文本最多显示一行 + * * `2`:消息文本最多显示两行 + */ + messageLine?: 1 | 2; + /** + * 在多长时间后自动关闭(单位为毫秒)。设置为 0 时,不自动关闭。默认为 5 秒后自动关闭。 + */ + autoCloseDelay?: number; + /** + * 点击或触摸 Snackbar 以外的区域时是否关闭 Snackbar + */ + closeOnOutsideClick?: boolean; + /** + * 队列名称。 + * 默认不启用队列,在多次调用该函数时,将同时显示多个 snackbar。 + * 可在该参数中传入一个队列名称,具有相同队列名称的 snackbar 函数,将在上一个 snackbar 关闭后才打开下一个 snackbar。 + */ + queue?: string; + /** + * 点击 Snackbar 时的回调函数。 + * 函数参数为 snackbar 实例,`this` 也指向 snackbar 实例。 + * @param snackbar + */ + onClick?: (snackbar: Snackbar) => void; + /** + * 点击操作按钮时的回调函数。 + * 函数参数为 snackbar 实例,`this` 也指向 snackbar 实例。 + * 默认点击后会关闭 snackbar;若返回值为 false,则不关闭 snackbar;若返回值为 promise,则将在 promise 被 resolve 后,关闭 snackbar。 + * @param snackbar + */ + onActionClick?: (snackbar: Snackbar) => void | boolean | Promise; + /** + * Snackbar 开始显示时的回调函数。 + * 函数参数为 snackbar 实例,`this` 也指向 snackbar 实例。 + * @param snackbar + */ + onOpen?: (snackbar: Snackbar) => void; + /** + * Snackbar 显示动画完成时的回调函数。 + * 函数参数为 snackbar 实例,`this` 也指向 snackbar 实例。 + * @param snackbar + */ + onOpened?: (snackbar: Snackbar) => void; + /** + * Snackbar 开始隐藏时的回调函数。 + * 函数参数为 snackbar 实例,`this` 也指向 snackbar 实例。 + * @param snackbar + */ + onClose?: (snackbar: Snackbar) => void; + /** + * Snackbar 隐藏动画完成时的回调函数。 + * 函数参数为 snackbar 实例,`this` 也指向 snackbar 实例。 + * @param snackbar + */ + onClosed?: (snackbar: Snackbar) => void; +} + +interface SnackbarOptions extends Options { + message: string +} + +export function checkApiSuccessOrSncakbar(re: ApiCallbackMessage, msg_ahead: string, opinions_override: Options = {}): Snackbar | null { + return re.code != 200 ? snackbar( + Object.assign({ + message: `${msg_ahead}: ${re.msg} [${re.code}]`, + placement: "top", + } as SnackbarOptions, opinions_override) + ) : null +} + +export function snackbar(opinions: SnackbarOptions) { + opinions.autoCloseDelay == null && (opinions.autoCloseDelay = 2500) + return mduiSnackbar(opinions) +}