Rewrite notification system

This commit is contained in:
LASER-Yi 2021-05-09 11:22:24 +08:00
parent 28ad20de88
commit a417f35856
8 changed files with 33 additions and 43 deletions

View File

@ -19,7 +19,7 @@ export const bootstrap = createCallbackAction(
() => siteInitializationFailed()
);
// TODO: Override error message
// TODO: Override error messages
export const siteInitializationFailed = createAction(SITE_INITIALIZE_FAILED);
const siteInitialized = createAction(SITE_INITIALIZED);
@ -37,7 +37,7 @@ export const siteAddNotifications = createAction(
export const siteRemoveNotifications = createAction(
SITE_NOTIFICATIONS_REMOVE,
(date: Date[]) => date
(id: string) => id
);
export const siteChangeSidebar = createAction(

View File

@ -1,26 +1,21 @@
import { useCallback, useEffect } from "react";
import { useSystemSettings } from ".";
import {
siteAddNotifications,
siteChangeSidebar,
siteRemoveNotifications,
} from "../actions";
import { siteAddNotifications, siteChangeSidebar } from "../actions";
import { useReduxAction, useReduxStore } from "./base";
export function useNotification(timeout: number = 5000) {
export function useNotification(id: string, timeout: number = 5000) {
const add = useReduxAction(siteAddNotifications);
const remove = useReduxAction(siteRemoveNotifications);
return useCallback(
(msg: Omit<ReduxStore.Notification, "timestamp">) => {
const error: ReduxStore.Notification = {
(msg: Omit<ReduxStore.Notification, "id" | "timeout">) => {
const notification: ReduxStore.Notification = {
...msg,
timestamp: new Date(),
id,
timeout,
};
add([error]);
setTimeout(() => remove([error.timestamp]), timeout);
add([notification]);
},
[add, remove, timeout]
[add, timeout, id]
);
}

View File

@ -1,4 +1,4 @@
import { differenceWith } from "lodash";
import { remove, uniqBy } from "lodash";
import { Action, handleActions } from "redux-actions";
import apis from "../../apis";
import {
@ -36,16 +36,16 @@ const reducer = handleActions<ReduxStore.Site, any>(
state,
action: Action<ReduxStore.Notification[]>
) => {
const alerts = [...state.notifications, ...action.payload];
return { ...state, notifications: alerts };
},
[SITE_NOTIFICATIONS_REMOVE]: (state, action: Action<Date[]>) => {
const alerts = differenceWith(
state.notifications,
action.payload,
(n, t) => n.timestamp === t
const notifications = uniqBy(
[...action.payload, ...state.notifications],
(n) => n.id
);
return { ...state, notifications: alerts };
return { ...state, notifications };
},
[SITE_NOTIFICATIONS_REMOVE]: (state, action: Action<string>) => {
const notifications = [...state.notifications];
remove(notifications, (n) => n.id === action.payload);
return { ...state, notifications };
},
[SITE_SIDEBAR_UPDATE]: (state, action: Action<string>) => {
return {

View File

@ -8,8 +8,9 @@ interface ReduxStore {
namespace ReduxStore {
interface Notification {
type: "error" | "warning" | "info";
id: string;
message: string;
timestamp: Date;
timeout: number;
}
interface Site {

View File

@ -7,7 +7,6 @@ import {
seriesUpdateList,
siteAddNotifications,
siteInitializationFailed,
siteRemoveNotifications,
siteUpdateOffline,
systemUpdateLanguagesAll,
systemUpdateSettings,
@ -50,16 +49,11 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
const notifications = msg.map<ReduxStore.Notification>((message) => ({
message,
type: "info",
timestamp: new Date(),
id: "backend-message",
timeout: 5 * 1000,
}));
reduxStore.dispatch(siteAddNotifications(notifications));
const ts = notifications.map((n) => n.timestamp);
setTimeout(
() => reduxStore.dispatch(siteRemoveNotifications(ts)),
5000
);
}
},
},

View File

@ -25,7 +25,7 @@ interface Props {}
const App: FunctionComponent<Props> = () => {
const { initialized, auth } = useReduxStore((s) => s.site);
const notify = useNotification(10 * 1000);
const notify = useNotification("has-update", 10 * 1000);
// Has any update?
const hasUpdate = useHasUpdateInject();

View File

@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { capitalize } from "lodash";
import React, { FunctionComponent, useCallback, useMemo } from "react";
import { Toast } from "react-bootstrap";
import { useTimeoutWhen } from "rooks";
import { siteRemoveNotifications } from "../../@redux/actions";
import { useReduxAction, useReduxStore } from "../../@redux/hooks/base";
import "./style.scss";
@ -15,10 +16,7 @@ const NotificationContainer: FunctionComponent<NotificationContainerProps> = ()
const items = useMemo(
() =>
list.map((v) => (
<NotificationToast
key={v.timestamp.getTime()}
{...v}
></NotificationToast>
<NotificationToast key={v.id} {...v}></NotificationToast>
)),
[list]
);
@ -32,14 +30,16 @@ const NotificationContainer: FunctionComponent<NotificationContainerProps> = ()
type MessageHolderProps = ReduxStore.Notification & {};
const NotificationToast: FunctionComponent<MessageHolderProps> = (props) => {
const { message, type, timestamp } = props;
const { message, type, id, timeout } = props;
const removeNotification = useReduxAction(siteRemoveNotifications);
const remove = useCallback(() => removeNotification([timestamp]), [
const remove = useCallback(() => removeNotification(id), [
removeNotification,
timestamp,
id,
]);
useTimeoutWhen(remove, timeout);
return (
<Toast onClose={remove} animation={false}>
<Toast.Header>

View File

@ -48,7 +48,7 @@ export function AsyncStateOverlay<T>(props: AsyncStateOverlayProps<T>) {
const { exist, state, children } = props;
const missing = exist ? !exist(state.data) : !defaultExist(state.data);
const onError = useNotification();
const onError = useNotification("async-loading");
useEffect(() => {
if (!state.updating && state.error !== undefined && !missing) {