From deb3d5bdc3f4055753ca41f684155ca2470e4590 Mon Sep 17 00:00:00 2001 From: zeppi Date: Fri, 29 Apr 2022 17:12:48 -0400 Subject: [PATCH] local notification plumbing --- ui/component/headerMenuButtons/view.jsx | 10 ++---- .../headerNotificationButton/index.js | 6 ++-- .../headerNotificationButton/view.jsx | 12 +++---- ui/component/notification/index.js | 7 +++- ui/component/subscribeButton/view.jsx | 5 +-- ui/constants/action_types.js | 6 ++-- ui/page/channel/view.jsx | 33 ++++++++++++++++-- ui/page/notifications/index.js | 2 ++ ui/page/notifications/view.jsx | 27 ++++++++++++--- ui/redux/actions/notifications.js | 34 +++++++++++++++++++ ui/redux/reducers/notifications.js | 34 +++++++++++++++++++ ui/redux/reducers/subscriptions.js | 20 ++++------- ui/redux/selectors/notifications.js | 9 +++++ ui/scss/component/_button.scss | 13 +++---- ui/scss/component/_channel.scss | 20 +++++------ 15 files changed, 173 insertions(+), 65 deletions(-) diff --git a/ui/component/headerMenuButtons/view.jsx b/ui/component/headerMenuButtons/view.jsx index 687fe4b2e..9b9cb36a5 100644 --- a/ui/component/headerMenuButtons/view.jsx +++ b/ui/component/headerMenuButtons/view.jsx @@ -1,7 +1,4 @@ // @flow -// import 'scss/component/_header.scss'; // ody codesplits this; no. - -import { ENABLE_UI_NOTIFICATIONS } from 'config'; import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button'; import * as ICONS from 'constants/icons'; import * as PAGES from 'constants/pages'; @@ -15,14 +12,11 @@ type HeaderMenuButtonProps = { authenticated: boolean, automaticDarkModeEnabled: boolean, currentTheme: string, - user: ?User, handleThemeToggle: (boolean, string) => void, }; export default function HeaderMenuButtons(props: HeaderMenuButtonProps) { - const { automaticDarkModeEnabled, currentTheme, user, handleThemeToggle } = props; - - const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui); + const { automaticDarkModeEnabled, currentTheme, handleThemeToggle } = props; return (
@@ -39,7 +33,7 @@ export default function HeaderMenuButtons(props: HeaderMenuButtonProps) { - {notificationsEnabled && } + diff --git a/ui/component/headerNotificationButton/index.js b/ui/component/headerNotificationButton/index.js index c69e0b9fc..1ae75ac31 100644 --- a/ui/component/headerNotificationButton/index.js +++ b/ui/component/headerNotificationButton/index.js @@ -1,14 +1,16 @@ import { connect } from 'react-redux'; -import { selectUnseenNotificationCount } from 'redux/selectors/notifications'; -import { doLbryioSeeAllNotifications } from 'redux/actions/notifications'; +import { selectUnseenNotificationCount, selectUnseenLocalNotificationCount } from 'redux/selectors/notifications'; +import { doLbryioSeeAllNotifications, doLocalSeeAllNotifications } from 'redux/actions/notifications'; import { selectUser } from 'redux/selectors/user'; import NotificationHeaderButton from './view'; const select = (state) => ({ unseenCount: selectUnseenNotificationCount(state), + unseenLocalCount: selectUnseenLocalNotificationCount(state), user: selectUser(state), }); export default connect(select, { doLbryioSeeAllNotifications, + doLocalSeeAllNotifications, })(NotificationHeaderButton); diff --git a/ui/component/headerNotificationButton/view.jsx b/ui/component/headerNotificationButton/view.jsx index d479956e7..01d7508b6 100644 --- a/ui/component/headerNotificationButton/view.jsx +++ b/ui/component/headerNotificationButton/view.jsx @@ -1,7 +1,4 @@ // @flow -// import 'scss/component/_header.scss'; // ody codesplits this; no. REMOVE THESE - -import { ENABLE_UI_NOTIFICATIONS } from 'config'; import { useHistory } from 'react-router'; import * as ICONS from 'constants/icons'; import * as PAGES from 'constants/pages'; @@ -13,23 +10,22 @@ import Tooltip from 'component/common/tooltip'; type Props = { unseenCount: number, - user: ?User, + unseenLocalCount: number, doLbryioSeeAllNotifications: () => void, + doLocalSeeAllNotifications: () => void, }; export default function NotificationHeaderButton(props: Props) { - const { unseenCount, user, doLbryioSeeAllNotifications } = props; + const { unseenCount, unseenLocalCount, doLbryioSeeAllNotifications, doLocalSeeAllNotifications } = props; const { push } = useHistory(); - const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui); function handleMenuClick() { if (unseenCount > 0) doLbryioSeeAllNotifications(); + if (unseenLocalCount > 0) doLocalSeeAllNotifications(); push(`/$/${PAGES.NOTIFICATIONS}`); } - if (!notificationsEnabled) return null; - return (
- {list && list.length > 0 && !(isFiltered && fetching) ? ( + {legacyNotificationsEnabled && list && list.length > 0 && !(isFiltered && fetching) && (
{list.map((notification) => { @@ -148,7 +157,17 @@ export default function NotificationsPage(props: Props) { })}
- ) : ( + )} + {!legacyNotificationsEnabled && localNotifications && localNotifications.length > 0 && ( +
+
+ {localNotifications.map((notification) => { + return ; + })} +
+
+ )} + {!(legacyNotificationsEnabled && list && list.length > 0 && !(isFiltered && fetching)) && (
{!fetching && ( { + dispatch({ type: ACTIONS.LOCAL_NOTIFICATION_DELETE_COMPLETED, data: { notificationId } }); + }; +} + +export function doLocalSeeNotification(notificationIds: Array) { + return (dispatch: Dispatch) => { + dispatch({ + type: ACTIONS.LOCAL_NOTIFICATION_SEEN_COMPLETED, + data: { + notificationIds, + }, + }); + }; +} + +export function doLocalSeeAllNotifications() { + return (dispatch: Dispatch, getState: GetState) => { + const state = getState(); + const notifications = selectNotificationsLocal(state); + + if (!notifications) { + return; + } + + const getUnseenIds = (list) => list.filter((n) => !n.is_seen).map((n) => n.id); + const unseenIds = Array.from(new Set([...getUnseenIds(notifications)])); + + dispatch(doLbryioNotificationsMarkSeen(unseenIds)); + }; +} diff --git a/ui/redux/reducers/notifications.js b/ui/redux/reducers/notifications.js index 795acce79..d5c8df761 100644 --- a/ui/redux/reducers/notifications.js +++ b/ui/redux/reducers/notifications.js @@ -4,6 +4,7 @@ import { handleActions } from 'util/redux-utils'; const defaultState: NotificationState = { notifications: [], + localNotifications: [], notificationsFiltered: [], notificationCategories: undefined, fetchingNotifications: false, @@ -133,6 +134,39 @@ export default handleActions( }; }, + [ACTIONS.LOCAL_NOTIFICATION_DELETE_COMPLETED]: (state, action) => { + const { localNotifications } = state; + const { notificationId } = action.data; + + const deleteId = (list, id) => { + return localNotifications.filter((n) => n.id !== id); + }; + + return { + ...state, + localNotifications: deleteId(localNotifications, notificationId), + }; + }, + + [ACTIONS.LOCAL_NOTIFICATION_SEEN_COMPLETED]: (state, action) => { + const { localNotifications } = state; + const { notificationIds } = action.data; + + const markIdsAsSeen = (list, ids) => { + return list.map((n) => { + if (ids.includes(n.id)) { + return { ...n, is_seen: true }; + } + return n; + }); + }; + + return { + ...state, + localNotifications: markIdsAsSeen(localNotifications, notificationIds), + }; + }, + // Errors [ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoError) => { const error: ErrorNotification = action.data; diff --git a/ui/redux/reducers/subscriptions.js b/ui/redux/reducers/subscriptions.js index ffd358bcb..6017f856b 100644 --- a/ui/redux/reducers/subscriptions.js +++ b/ui/redux/reducers/subscriptions.js @@ -6,10 +6,17 @@ import { handleActions } from 'util/redux-utils'; const defaultState: SubscriptionState = { subscriptions: [], // Deprecated following: [], + autoDownloads: {}, loading: false, firstRunCompleted: false, }; +/* +For each channel, track number to keep downloaded (number), downloads (Array<{id, releaseTime}>) + AutoDownloadById + { channel_id: { count: n, downloads: [ { claimId: xyz, releaseTime: 123 ], ... } } + */ + export default handleActions( { [ACTIONS.CHANNEL_SUBSCRIBE]: (state: SubscriptionState, action): SubscriptionState => { @@ -61,19 +68,6 @@ export default handleActions( following: newFollowing, }; }, - [ACTIONS.FETCH_SUBSCRIPTIONS_START]: (state: SubscriptionState): SubscriptionState => ({ - ...state, - loading: true, - }), - [ACTIONS.FETCH_SUBSCRIPTIONS_FAIL]: (state: SubscriptionState): SubscriptionState => ({ - ...state, - loading: false, - }), - [ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS]: (state: SubscriptionState, action): SubscriptionState => ({ - ...state, - loading: false, - subscriptions: action.data, - }), [ACTIONS.SET_VIEW_MODE]: (state: SubscriptionState, action): SubscriptionState => ({ ...state, viewMode: action.data, diff --git a/ui/redux/selectors/notifications.js b/ui/redux/selectors/notifications.js index 825a6e3f8..78600c0b5 100644 --- a/ui/redux/selectors/notifications.js +++ b/ui/redux/selectors/notifications.js @@ -3,6 +3,7 @@ import { createSelector } from 'reselect'; export const selectState = (state) => state.notifications || {}; export const selectNotifications = createSelector(selectState, (state) => state.notifications); +export const selectNotificationsLocal = createSelector(selectState, (state) => state.localNotifications); export const selectNotificationsFiltered = createSelector(selectState, (state) => state.notificationsFiltered); @@ -31,6 +32,14 @@ export const selectUnseenNotificationCount = createSelector(selectNotifications, return notifications ? notifications.filter((notification) => !notification.is_seen).length : 0; }); +export const selectUnseenLocalNotificationCount = createSelector(selectNotificationsLocal, (notifications) => { + return notifications ? notifications.filter((notification) => !notification.is_seen).length : 0; +}); + +export const selectUnreadLocalNotificationCount = createSelector(selectNotificationsLocal, (notifications) => { + return notifications ? notifications.filter((notification) => !notification.is_read).length : 0; +}); + export const selectToast = createSelector(selectState, (state) => { if (state.toasts.length) { const { id, params } = state.toasts[0]; diff --git a/ui/scss/component/_button.scss b/ui/scss/component/_button.scss index a8cfd3b36..2269e8567 100644 --- a/ui/scss/component/_button.scss +++ b/ui/scss/component/_button.scss @@ -710,14 +710,11 @@ svg + .button__label { } } // bring back special button-following style later -//.button-following { -// color: var(--color-primary-contrast) !important; -// //background-color: rgba(var(--color-primary-dynamic),0.5) !important; -// background-color: rgba(125, 125, 125, 0.5) !important; -// .icon { -// stroke: var(--color-primary-contrast) !important; -// } -//} +.button-following { + .icon { + stroke: var(--color-text) !important; + } +} .recommended-content__bubble { // margin-top: var(--spacing-xs); diff --git a/ui/scss/component/_channel.scss b/ui/scss/component/_channel.scss index b5fc60ae0..9f0b79637 100644 --- a/ui/scss/component/_channel.scss +++ b/ui/scss/component/_channel.scss @@ -11,18 +11,14 @@ $actions-z-index: 2; } } // bring back later - //.button-following { - // &.button--alt { - // color: var(--color-text); - // background-color: var(--color-button-alt-bg); - // .icon { - // stroke: var(--color-text); - // } - // &:hover { - // color: var(--color-primary); - // } - // } - //} + .button-following { + &.button--alt { + color: var(--color-text); + .icon { + stroke: var(--color-text); + } + } + } .button-following:last-of-type { margin-left: 2px; }