Rewrite notification system

pull/1405/head
LASER-Yi 3 years ago
parent 28ad20de88
commit a417f35856

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

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

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

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

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

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

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

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

Loading…
Cancel
Save