diff --git a/frontend/src/@redux/actions/site.ts b/frontend/src/@redux/actions/site.ts index fe595b7ca..088a25512 100644 --- a/frontend/src/@redux/actions/site.ts +++ b/frontend/src/@redux/actions/site.ts @@ -7,7 +7,6 @@ import { SITE_NEED_AUTH, SITE_NOTIFICATIONS_ADD, SITE_NOTIFICATIONS_REMOVE, - SITE_NOTIFICATIONS_REMOVE_BY_TIMESTAMP, SITE_OFFLINE_UPDATE, SITE_SIDEBAR_UPDATE, } from "../constants"; @@ -30,19 +29,14 @@ export const badgeUpdateAll = createAsyncAction(SITE_BADGE_UPDATE, () => BadgesApi.all() ); -export const siteAddNotification = createAction( +export const siteAddNotifications = createAction( SITE_NOTIFICATIONS_ADD, - (err: ReduxStore.Notification) => err + (err: ReduxStore.Notification[]) => err ); -export const siteRemoveNotification = createAction( +export const siteRemoveNotifications = createAction( SITE_NOTIFICATIONS_REMOVE, - (id: string) => id -); - -export const siteRemoveNotificationByTime = createAction( - SITE_NOTIFICATIONS_REMOVE_BY_TIMESTAMP, - (date: Date) => date + (date: Date[]) => date ); export const siteChangeSidebar = createAction( diff --git a/frontend/src/@redux/constants/index.ts b/frontend/src/@redux/constants/index.ts index ea2fa16a2..ae1db1f75 100644 --- a/frontend/src/@redux/constants/index.ts +++ b/frontend/src/@redux/constants/index.ts @@ -36,8 +36,6 @@ export const SITE_INITIALIZED = "SITE_SYSTEM_INITIALIZED"; export const SITE_INITIALIZE_FAILED = "SITE_INITIALIZE_FAILED"; export const SITE_NOTIFICATIONS_ADD = "SITE_NOTIFICATIONS_ADD"; export const SITE_NOTIFICATIONS_REMOVE = "SITE_NOTIFICATIONS_REMOVE"; -export const SITE_NOTIFICATIONS_REMOVE_BY_TIMESTAMP = - "SITE_NOTIFICATIONS_REMOVE_BY_TIMESTAMP"; export const SITE_SIDEBAR_UPDATE = "SITE_SIDEBAR_UPDATE"; export const SITE_BADGE_UPDATE = "SITE_BADGE_UPDATE"; export const SITE_OFFLINE_UPDATE = "SITE_OFFLINE_UPDATE"; diff --git a/frontend/src/@redux/hooks/site.ts b/frontend/src/@redux/hooks/site.ts index c789bb026..08f5b0e29 100644 --- a/frontend/src/@redux/hooks/site.ts +++ b/frontend/src/@redux/hooks/site.ts @@ -1,27 +1,26 @@ import { useCallback, useEffect } from "react"; import { useSystemSettings } from "."; import { - siteAddNotification, + siteAddNotifications, siteChangeSidebar, - siteRemoveNotificationByTime, + siteRemoveNotifications, } from "../actions"; import { useReduxAction, useReduxStore } from "./base"; -export function useNotification(id: string, sec: number = 5) { - const add = useReduxAction(siteAddNotification); - const remove = useReduxAction(siteRemoveNotificationByTime); +export function useNotification(timeout: number = 5000) { + const add = useReduxAction(siteAddNotifications); + const remove = useReduxAction(siteRemoveNotifications); return useCallback( - (msg: Omit) => { + (msg: Omit) => { const error: ReduxStore.Notification = { ...msg, - id, timestamp: new Date(), }; - add(error); - setTimeout(() => remove(error.timestamp), sec * 1000); + add([error]); + setTimeout(() => remove([error.timestamp]), timeout); }, - [add, remove, sec, id] + [add, remove, timeout] ); } diff --git a/frontend/src/@redux/reducers/site.ts b/frontend/src/@redux/reducers/site.ts index 5013c6a89..6c8cd3917 100644 --- a/frontend/src/@redux/reducers/site.ts +++ b/frontend/src/@redux/reducers/site.ts @@ -1,3 +1,4 @@ +import { differenceWith } from "lodash"; import { Action, handleActions } from "redux-actions"; import apis from "../../apis"; import { @@ -7,7 +8,6 @@ import { SITE_NEED_AUTH, SITE_NOTIFICATIONS_ADD, SITE_NOTIFICATIONS_REMOVE, - SITE_NOTIFICATIONS_REMOVE_BY_TIMESTAMP, SITE_OFFLINE_UPDATE, SITE_SIDEBAR_UPDATE, } from "../constants"; @@ -34,21 +34,16 @@ const reducer = handleActions( }), [SITE_NOTIFICATIONS_ADD]: ( state, - action: Action + action: Action ) => { - const alerts = [ - ...state.notifications.filter((v) => v.id !== action.payload.id), - action.payload, - ]; - return { ...state, notifications: alerts }; - }, - [SITE_NOTIFICATIONS_REMOVE]: (state, action: Action) => { - const alerts = state.notifications.filter((v) => v.id !== action.payload); + const alerts = [...state.notifications, ...action.payload]; return { ...state, notifications: alerts }; }, - [SITE_NOTIFICATIONS_REMOVE_BY_TIMESTAMP]: (state, action: Action) => { - const alerts = state.notifications.filter( - (v) => v.timestamp !== action.payload + [SITE_NOTIFICATIONS_REMOVE]: (state, action: Action) => { + const alerts = differenceWith( + state.notifications, + action.payload, + (n, t) => n.timestamp === t ); return { ...state, notifications: alerts }; }, diff --git a/frontend/src/@redux/redux.d.ts b/frontend/src/@redux/redux.d.ts index cf514b36b..d48d1f221 100644 --- a/frontend/src/@redux/redux.d.ts +++ b/frontend/src/@redux/redux.d.ts @@ -10,7 +10,6 @@ namespace ReduxStore { type: "error" | "warning" | "info"; message: string; timestamp: Date; - id: string; } interface Site { diff --git a/frontend/src/@socketio/reducer.ts b/frontend/src/@socketio/reducer.ts index 0507f89e2..b5b699b20 100644 --- a/frontend/src/@socketio/reducer.ts +++ b/frontend/src/@socketio/reducer.ts @@ -5,6 +5,8 @@ import { movieUpdateList, seriesDeleteItems, seriesUpdateList, + siteAddNotifications, + siteRemoveNotifications, siteUpdateOffline, systemUpdateLanguagesAll, systemUpdateSettings, @@ -29,6 +31,26 @@ export function createDefaultReducer(): SocketIO.Reducer[] { key: "disconnect", any: () => reduxStore.dispatch(siteUpdateOffline(true)), }, + { + key: "message", + update: (msg?: string[]) => { + if (msg) { + const notifications = msg.map((message) => ({ + message, + type: "info", + timestamp: new Date(), + })); + + reduxStore.dispatch(siteAddNotifications(notifications)); + + const ts = notifications.map((n) => n.timestamp); + setTimeout( + () => reduxStore.dispatch(siteRemoveNotifications(ts)), + 5000 + ); + } + }, + }, { key: "series", update: bindToReduxStore(seriesUpdateList), diff --git a/frontend/src/App/index.tsx b/frontend/src/App/index.tsx index e7cd556cf..0fb016e45 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("has-update", 10); + const notify = useNotification(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 dff37dd76..4fbc76399 100644 --- a/frontend/src/App/notifications/index.tsx +++ b/frontend/src/App/notifications/index.tsx @@ -3,7 +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 { siteRemoveNotification } from "../../@redux/actions"; +import { siteRemoveNotifications } from "../../@redux/actions"; import { useReduxAction, useReduxStore } from "../../@redux/hooks/base"; import "./style.scss"; @@ -14,8 +14,11 @@ const NotificationContainer: FunctionComponent = () const items = useMemo( () => - list.map((v, idx) => ( - + list.map((v) => ( + )), [list] ); @@ -29,12 +32,12 @@ const NotificationContainer: FunctionComponent = () type MessageHolderProps = ReduxStore.Notification & {}; const NotificationToast: FunctionComponent = (props) => { - const { message, id, type } = props; - const removeNotification = useReduxAction(siteRemoveNotification); + const { message, type, timestamp } = props; + const removeNotification = useReduxAction(siteRemoveNotifications); - const remove = useCallback(() => removeNotification(id), [ + const remove = useCallback(() => removeNotification([timestamp]), [ removeNotification, - id, + timestamp, ]); return ( diff --git a/frontend/src/components/async.tsx b/frontend/src/components/async.tsx index 092a08a8d..c5487b534 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("async-overlay"); + const onError = useNotification(); useEffect(() => { if (!state.updating && state.error !== undefined && !missing) {