From a417f35856faf18086adbc05af43a52a45838880 Mon Sep 17 00:00:00 2001 From: LASER-Yi Date: Sun, 9 May 2021 11:22:24 +0800 Subject: [PATCH] Rewrite notification system --- frontend/src/@redux/actions/site.ts | 4 ++-- frontend/src/@redux/hooks/site.ts | 21 ++++++++------------- frontend/src/@redux/reducers/site.ts | 20 ++++++++++---------- frontend/src/@redux/redux.d.ts | 3 ++- frontend/src/@socketio/reducer.ts | 10 ++-------- frontend/src/App/index.tsx | 2 +- frontend/src/App/notifications/index.tsx | 14 +++++++------- frontend/src/components/async.tsx | 2 +- 8 files changed, 33 insertions(+), 43 deletions(-) diff --git a/frontend/src/@redux/actions/site.ts b/frontend/src/@redux/actions/site.ts index 258d03502..d7ab89562 100644 --- a/frontend/src/@redux/actions/site.ts +++ b/frontend/src/@redux/actions/site.ts @@ -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( diff --git a/frontend/src/@redux/hooks/site.ts b/frontend/src/@redux/hooks/site.ts index 08f5b0e29..21f6034f9 100644 --- a/frontend/src/@redux/hooks/site.ts +++ b/frontend/src/@redux/hooks/site.ts @@ -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) => { - const error: ReduxStore.Notification = { + (msg: Omit) => { + 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] ); } diff --git a/frontend/src/@redux/reducers/site.ts b/frontend/src/@redux/reducers/site.ts index 6c8cd3917..2a2d9d626 100644 --- a/frontend/src/@redux/reducers/site.ts +++ b/frontend/src/@redux/reducers/site.ts @@ -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( state, action: Action ) => { - const alerts = [...state.notifications, ...action.payload]; - return { ...state, notifications: alerts }; - }, - [SITE_NOTIFICATIONS_REMOVE]: (state, action: Action) => { - 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) => { + const notifications = [...state.notifications]; + remove(notifications, (n) => n.id === action.payload); + return { ...state, notifications }; }, [SITE_SIDEBAR_UPDATE]: (state, action: Action) => { return { diff --git a/frontend/src/@redux/redux.d.ts b/frontend/src/@redux/redux.d.ts index d48d1f221..e080fac43 100644 --- a/frontend/src/@redux/redux.d.ts +++ b/frontend/src/@redux/redux.d.ts @@ -8,8 +8,9 @@ interface ReduxStore { namespace ReduxStore { interface Notification { type: "error" | "warning" | "info"; + id: string; message: string; - timestamp: Date; + timeout: number; } interface Site { diff --git a/frontend/src/@socketio/reducer.ts b/frontend/src/@socketio/reducer.ts index 1454ea4a0..7d686b4d1 100644 --- a/frontend/src/@socketio/reducer.ts +++ b/frontend/src/@socketio/reducer.ts @@ -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((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 - ); } }, }, diff --git a/frontend/src/App/index.tsx b/frontend/src/App/index.tsx index 0fb016e45..995a17ebc 100644 --- a/frontend/src/App/index.tsx +++ b/frontend/src/App/index.tsx @@ -25,7 +25,7 @@ interface Props {} const App: FunctionComponent = () => { 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(); diff --git a/frontend/src/App/notifications/index.tsx b/frontend/src/App/notifications/index.tsx index 4fbc76399..2484ba793 100644 --- a/frontend/src/App/notifications/index.tsx +++ b/frontend/src/App/notifications/index.tsx @@ -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 = () const items = useMemo( () => list.map((v) => ( - + )), [list] ); @@ -32,14 +30,16 @@ const NotificationContainer: FunctionComponent = () type MessageHolderProps = ReduxStore.Notification & {}; const NotificationToast: FunctionComponent = (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 ( diff --git a/frontend/src/components/async.tsx b/frontend/src/components/async.tsx index c5487b534..9f5ac273c 100644 --- a/frontend/src/components/async.tsx +++ b/frontend/src/components/async.tsx @@ -48,7 +48,7 @@ export function AsyncStateOverlay(props: AsyncStateOverlayProps) { 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) {