local notification plumbing

This commit is contained in:
zeppi 2022-04-29 17:12:48 -04:00
parent 43dcb39045
commit deb3d5bdc3
15 changed files with 173 additions and 65 deletions

View file

@ -1,7 +1,4 @@
// @flow // @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 { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
@ -15,14 +12,11 @@ type HeaderMenuButtonProps = {
authenticated: boolean, authenticated: boolean,
automaticDarkModeEnabled: boolean, automaticDarkModeEnabled: boolean,
currentTheme: string, currentTheme: string,
user: ?User,
handleThemeToggle: (boolean, string) => void, handleThemeToggle: (boolean, string) => void,
}; };
export default function HeaderMenuButtons(props: HeaderMenuButtonProps) { export default function HeaderMenuButtons(props: HeaderMenuButtonProps) {
const { automaticDarkModeEnabled, currentTheme, user, handleThemeToggle } = props; const { automaticDarkModeEnabled, currentTheme, handleThemeToggle } = props;
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
return ( return (
<div className="header__buttons"> <div className="header__buttons">
@ -39,7 +33,7 @@ export default function HeaderMenuButtons(props: HeaderMenuButtonProps) {
</MenuList> </MenuList>
</Menu> </Menu>
{notificationsEnabled && <NotificationHeaderButton />} <NotificationHeaderButton />
<Menu> <Menu>
<Tooltip title={__('Settings')}> <Tooltip title={__('Settings')}>

View file

@ -1,14 +1,16 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUnseenNotificationCount } from 'redux/selectors/notifications'; import { selectUnseenNotificationCount, selectUnseenLocalNotificationCount } from 'redux/selectors/notifications';
import { doLbryioSeeAllNotifications } from 'redux/actions/notifications'; import { doLbryioSeeAllNotifications, doLocalSeeAllNotifications } from 'redux/actions/notifications';
import { selectUser } from 'redux/selectors/user'; import { selectUser } from 'redux/selectors/user';
import NotificationHeaderButton from './view'; import NotificationHeaderButton from './view';
const select = (state) => ({ const select = (state) => ({
unseenCount: selectUnseenNotificationCount(state), unseenCount: selectUnseenNotificationCount(state),
unseenLocalCount: selectUnseenLocalNotificationCount(state),
user: selectUser(state), user: selectUser(state),
}); });
export default connect(select, { export default connect(select, {
doLbryioSeeAllNotifications, doLbryioSeeAllNotifications,
doLocalSeeAllNotifications,
})(NotificationHeaderButton); })(NotificationHeaderButton);

View file

@ -1,7 +1,4 @@
// @flow // @flow
// import 'scss/component/_header.scss'; // ody codesplits this; no. REMOVE THESE
import { ENABLE_UI_NOTIFICATIONS } from 'config';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
@ -13,23 +10,22 @@ import Tooltip from 'component/common/tooltip';
type Props = { type Props = {
unseenCount: number, unseenCount: number,
user: ?User, unseenLocalCount: number,
doLbryioSeeAllNotifications: () => void, doLbryioSeeAllNotifications: () => void,
doLocalSeeAllNotifications: () => void,
}; };
export default function NotificationHeaderButton(props: Props) { export default function NotificationHeaderButton(props: Props) {
const { unseenCount, user, doLbryioSeeAllNotifications } = props; const { unseenCount, unseenLocalCount, doLbryioSeeAllNotifications, doLocalSeeAllNotifications } = props;
const { push } = useHistory(); const { push } = useHistory();
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
function handleMenuClick() { function handleMenuClick() {
if (unseenCount > 0) doLbryioSeeAllNotifications(); if (unseenCount > 0) doLbryioSeeAllNotifications();
if (unseenLocalCount > 0) doLocalSeeAllNotifications();
push(`/$/${PAGES.NOTIFICATIONS}`); push(`/$/${PAGES.NOTIFICATIONS}`);
} }
if (!notificationsEnabled) return null;
return ( return (
<Tooltip title={__('Notifications')}> <Tooltip title={__('Notifications')}>
<Button onClick={handleMenuClick} className="header__navigationItem--icon"> <Button onClick={handleMenuClick} className="header__navigationItem--icon">

View file

@ -1,10 +1,15 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doLbryioNotificationsMarkRead, doLbryioDeleteNotification } from 'redux/actions/notifications'; import {
doLbryioNotificationsMarkRead,
doLbryioDeleteNotification,
doLocalDeleteNotification,
} from 'redux/actions/notifications';
import Notification from './view'; import Notification from './view';
const perform = (dispatch, ownProps) => ({ const perform = (dispatch, ownProps) => ({
readNotification: () => dispatch(doLbryioNotificationsMarkRead([ownProps.notification.id])), readNotification: () => dispatch(doLbryioNotificationsMarkRead([ownProps.notification.id])),
deleteNotification: () => dispatch(doLbryioDeleteNotification(ownProps.notification.id)), deleteNotification: () => dispatch(doLbryioDeleteNotification(ownProps.notification.id)),
deleteLocalNotification: () => dispatch(doLocalDeleteNotification(ownProps.notification.id)),
}); });
export default connect(null, perform)(Notification); export default connect(null, perform)(Notification);

View file

@ -5,7 +5,6 @@ import { parseURI } from 'util/lbryURI';
import Button from 'component/button'; import Button from 'component/button';
import useHover from 'effects/use-hover'; import useHover from 'effects/use-hover';
import { useIsMobile } from 'effects/use-screensize'; import { useIsMobile } from 'effects/use-screensize';
import { ENABLE_UI_NOTIFICATIONS } from 'config';
type SubscriptionArgs = { type SubscriptionArgs = {
channelName: string, channelName: string,
@ -34,7 +33,6 @@ export default function SubscribeButton(props: Props) {
doToast, doToast,
shrinkOnMobile = false, shrinkOnMobile = false,
notificationsDisabled, notificationsDisabled,
user,
uri, uri,
} = props; } = props;
@ -42,7 +40,6 @@ export default function SubscribeButton(props: Props) {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
let isHovering = useHover(buttonRef); let isHovering = useHover(buttonRef);
isHovering = isMobile ? true : isHovering; isHovering = isMobile ? true : isHovering;
const uiNotificationsEnabled = (user && user.experimental_ui) || ENABLE_UI_NOTIFICATIONS;
const { channelName: rawChannelName } = parseURI(uri); const { channelName: rawChannelName } = parseURI(uri);
@ -119,7 +116,7 @@ export default function SubscribeButton(props: Props) {
); );
}} }}
/> />
{isSubscribed && uiNotificationsEnabled && ( {isSubscribed && (
<> <>
<Button <Button
button="alt" button="alt"

View file

@ -324,9 +324,6 @@ export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED'; export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED'; export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE'; export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
export const FETCH_SUBSCRIPTIONS_START = 'FETCH_SUBSCRIPTIONS_START';
export const FETCH_SUBSCRIPTIONS_FAIL = 'FETCH_SUBSCRIPTIONS_FAIL';
export const FETCH_SUBSCRIPTIONS_SUCCESS = 'FETCH_SUBSCRIPTIONS_SUCCESS';
export const SET_VIEW_MODE = 'SET_VIEW_MODE'; export const SET_VIEW_MODE = 'SET_VIEW_MODE';
// Publishing // Publishing
@ -357,6 +354,9 @@ export const LBRYIO_NOTIFICATION_SEEN_FAILED = 'LBRYIO_NOTIFICATION_SEEN_FAILED'
export const LBRYIO_NOTIFICATION_DELETE_STARTED = 'LBRYIO_NOTIFICATION_DELETE_STARTED'; export const LBRYIO_NOTIFICATION_DELETE_STARTED = 'LBRYIO_NOTIFICATION_DELETE_STARTED';
export const LBRYIO_NOTIFICATION_DELETE_COMPLETED = 'LBRYIO_NOTIFICATION_DELETE_COMPLETED'; export const LBRYIO_NOTIFICATION_DELETE_COMPLETED = 'LBRYIO_NOTIFICATION_DELETE_COMPLETED';
export const LBRYIO_NOTIFICATION_DELETE_FAILED = 'LBRYIO_NOTIFICATION_DELETE_FAILED'; export const LBRYIO_NOTIFICATION_DELETE_FAILED = 'LBRYIO_NOTIFICATION_DELETE_FAILED';
export const LOCAL_NOTIFICATION_DELETE_COMPLETED = 'LBRYIO_NOTIFICATION_DELETE_COMPLETED';
export const LOCAL_NOTIFICATION_SEEN_COMPLETED = 'LBRYIO_NOTIFICATION_DELETE_COMPLETED';
export const CREATE_TOAST = 'CREATE_TOAST'; export const CREATE_TOAST = 'CREATE_TOAST';
export const DISMISS_TOAST = 'DISMISS_TOAST'; export const DISMISS_TOAST = 'DISMISS_TOAST';
export const CREATE_ERROR = 'CREATE_ERROR'; export const CREATE_ERROR = 'CREATE_ERROR';

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import React from 'react'; import React, { Fragment } from 'react';
import { parseURI } from 'util/lbryURI'; import { parseURI } from 'util/lbryURI';
import { YOUTUBE_STATUSES } from 'lbryinc'; import { YOUTUBE_STATUSES } from 'lbryinc';
import Page from 'component/page'; import Page from 'component/page';
@ -26,6 +26,7 @@ import I18nMessage from 'component/i18nMessage';
import TruncatedText from 'component/common/truncated-text'; import TruncatedText from 'component/common/truncated-text';
// $FlowFixMe cannot resolve ... // $FlowFixMe cannot resolve ...
import PlaceholderTx from 'static/img/placeholderTx.gif'; import PlaceholderTx from 'static/img/placeholderTx.gif';
import { FormField } from 'component/common/form-components/form-field';
export const PAGE_VIEW_QUERY = `view`; export const PAGE_VIEW_QUERY = `view`;
export const DISCUSSION_PAGE = `discussion`; export const DISCUSSION_PAGE = `discussion`;
@ -36,6 +37,7 @@ const PAGE = {
ABOUT: 'about', ABOUT: 'about',
DISCUSSION: DISCUSSION_PAGE, DISCUSSION: DISCUSSION_PAGE,
EDIT: 'edit', EDIT: 'edit',
SUBSCRIPTION: 'subscription',
}; };
type Props = { type Props = {
@ -159,6 +161,9 @@ function ChannelPage(props: Props) {
case PAGE.DISCUSSION: case PAGE.DISCUSSION:
tabIndex = 3; tabIndex = 3;
break; break;
case PAGE.SUBSCRIPTION:
tabIndex = 4;
break;
default: default:
tabIndex = 0; tabIndex = 0;
break; break;
@ -174,8 +179,10 @@ function ChannelPage(props: Props) {
search += `${PAGE_VIEW_QUERY}=${PAGE.LISTS}`; search += `${PAGE_VIEW_QUERY}=${PAGE.LISTS}`;
} else if (newTabIndex === 2) { } else if (newTabIndex === 2) {
search += `${PAGE_VIEW_QUERY}=${PAGE.ABOUT}`; search += `${PAGE_VIEW_QUERY}=${PAGE.ABOUT}`;
} else { } else if (newTabIndex === 3) {
search += `${PAGE_VIEW_QUERY}=${PAGE.DISCUSSION}`; search += `${PAGE_VIEW_QUERY}=${PAGE.DISCUSSION}`;
} else {
search += `${PAGE_VIEW_QUERY}=${PAGE.SUBSCRIPTION}`;
} }
push(`${url}${search}`); push(`${url}${search}`);
@ -286,6 +293,7 @@ function ChannelPage(props: Props) {
<Tab disabled={editing}>{__('Playlists')}</Tab> <Tab disabled={editing}>{__('Playlists')}</Tab>
<Tab>{editing ? __('Editing Your Channel') : __('About --[tab title in Channel Page]--')}</Tab> <Tab>{editing ? __('Editing Your Channel') : __('About --[tab title in Channel Page]--')}</Tab>
<Tab disabled={editing}>{__('Community')}</Tab> <Tab disabled={editing}>{__('Community')}</Tab>
<Tab disabled={editing}>{__('Subscription')}</Tab>
</TabList> </TabList>
<TabPanels> <TabPanels>
<TabPanel> <TabPanel>
@ -316,6 +324,27 @@ function ChannelPage(props: Props) {
<TabPanel> <TabPanel>
{(discussionWasMounted || currentView === PAGE.DISCUSSION) && <ChannelDiscussion uri={uri} />} {(discussionWasMounted || currentView === PAGE.DISCUSSION) && <ChannelDiscussion uri={uri} />}
</TabPanel> </TabPanel>
<TabPanel>
<div className="card">
<section className="section card--section">
<Fragment>
<div className="media__info-text">
<label>{__('New Content Notifications')}</label>
<FormField name={'notify'} type={'checkbox'} checked />
</div>
<div className="media__info-text">
<label>{__('Download and Host new content')}</label>
<FormField name={'host'} type={'checkbox'} checked />
</div>
<div className="media__info-text">
<label>{__('Items to Host')}</label>
<FormField name={'host_count'} type={'number'} value={3} />
</div>
</Fragment>
</section>
</div>
</TabPanel>
</TabPanels> </TabPanels>
</Tabs> </Tabs>
)} )}

View file

@ -15,6 +15,7 @@ import {
doLbryioSeeAllNotifications, doLbryioSeeAllNotifications,
} from 'redux/actions/notifications'; } from 'redux/actions/notifications';
import NotificationsPage from './view'; import NotificationsPage from './view';
import { selectUser } from 'redux/selectors/user';
const select = (state) => ({ const select = (state) => ({
notifications: selectNotifications(state), notifications: selectNotifications(state),
@ -24,6 +25,7 @@ const select = (state) => ({
unreadCount: selectUnreadNotificationCount(state), unreadCount: selectUnreadNotificationCount(state),
unseenCount: selectUnseenNotificationCount(state), unseenCount: selectUnseenNotificationCount(state),
activeChannel: selectActiveChannelClaim(state), activeChannel: selectActiveChannelClaim(state),
user: selectUser(state),
}); });
export default connect(select, { export default connect(select, {

View file

@ -14,6 +14,7 @@ import { RULE } from 'constants/notifications';
type Props = { type Props = {
notifications: Array<Notification>, notifications: Array<Notification>,
localNotifications: Array<Notification>,
notificationsFiltered: Array<Notification>, notificationsFiltered: Array<Notification>,
notificationCategories: Array<NotificationCategory>, notificationCategories: Array<NotificationCategory>,
fetching: boolean, fetching: boolean,
@ -24,12 +25,14 @@ type Props = {
doLbryioNotificationList: (?Array<string>) => void, doLbryioNotificationList: (?Array<string>) => void,
activeChannel: ?ChannelClaim, activeChannel: ?ChannelClaim,
doCommentReactList: (Array<string>) => Promise<any>, doCommentReactList: (Array<string>) => Promise<any>,
user: User,
}; };
export default function NotificationsPage(props: Props) { export default function NotificationsPage(props: Props) {
const { const {
notifications, notifications,
notificationsFiltered, notificationsFiltered,
localNotifications,
fetching, fetching,
unreadCount, unreadCount,
unseenCount, unseenCount,
@ -39,7 +42,10 @@ export default function NotificationsPage(props: Props) {
notificationCategories, notificationCategories,
activeChannel, activeChannel,
doCommentReactList, doCommentReactList,
user,
} = props; } = props;
// const localCategories = [{ name: 'New Content', types: ['new_content'] }];
const legacyNotificationsEnabled = user && user.experimental_ui;
const initialFetchDone = useFetched(fetching); const initialFetchDone = useFetched(fetching);
const [name, setName] = usePersistedState('notifications--rule', NOTIFICATIONS.NOTIFICATION_NAME_ALL); const [name, setName] = usePersistedState('notifications--rule', NOTIFICATIONS.NOTIFICATION_NAME_ALL);
const isFiltered = name !== NOTIFICATIONS.NOTIFICATION_NAME_ALL; const isFiltered = name !== NOTIFICATIONS.NOTIFICATION_NAME_ALL;
@ -113,7 +119,7 @@ export default function NotificationsPage(props: Props) {
<div className="claim-list__alt-controls--wrap"> <div className="claim-list__alt-controls--wrap">
{fetching && <Spinner type="small" />} {fetching && <Spinner type="small" />}
{unreadCount > 0 && ( {legacyNotificationsEnabled && unreadCount > 0 && (
<Button <Button
icon={ICONS.EYE} icon={ICONS.EYE}
onClick={doLbryioNotificationsMarkRead} onClick={doLbryioNotificationsMarkRead}
@ -121,7 +127,10 @@ export default function NotificationsPage(props: Props) {
label={__('Mark all as read')} label={__('Mark all as read')}
/> />
)} )}
{notificationCategories && ( {!legacyNotificationsEnabled && unreadCount > 0 && (
<Button icon={ICONS.EYE} onClick={() => alert('clear')} button="secondary" label={__('Mark all as read')} />
)}
{legacyNotificationsEnabled && notificationCategories && (
<FormField <FormField
className="notification__filter" className="notification__filter"
type="select" type="select"
@ -140,7 +149,7 @@ export default function NotificationsPage(props: Props) {
)} )}
</div> </div>
</div> </div>
{list && list.length > 0 && !(isFiltered && fetching) ? ( {legacyNotificationsEnabled && list && list.length > 0 && !(isFiltered && fetching) && (
<div className="card"> <div className="card">
<div className="notification_list"> <div className="notification_list">
{list.map((notification) => { {list.map((notification) => {
@ -148,7 +157,17 @@ export default function NotificationsPage(props: Props) {
})} })}
</div> </div>
</div> </div>
) : ( )}
{!legacyNotificationsEnabled && localNotifications && localNotifications.length > 0 && (
<div className="card">
<div className="notification_list">
{localNotifications.map((notification) => {
return <Notification key={notification.id} notification={notification} local />;
})}
</div>
</div>
)}
{!(legacyNotificationsEnabled && list && list.length > 0 && !(isFiltered && fetching)) && (
<div className="main--empty"> <div className="main--empty">
{!fetching && ( {!fetching && (
<Yrbl <Yrbl

View file

@ -5,6 +5,7 @@ import { Lbryio } from 'lbryinc';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { import {
selectNotifications, selectNotifications,
selectNotificationsLocal,
selectNotificationsFiltered, selectNotificationsFiltered,
selectNotificationCategories, selectNotificationCategories,
} from 'redux/selectors/notifications'; } from 'redux/selectors/notifications';
@ -184,3 +185,36 @@ export function doLbryioDeleteNotification(notificationId: number) {
}); });
}; };
} }
export function doLocalDeleteNotification(notificationId: number) {
return (dispatch: Dispatch) => {
dispatch({ type: ACTIONS.LOCAL_NOTIFICATION_DELETE_COMPLETED, data: { notificationId } });
};
}
export function doLocalSeeNotification(notificationIds: Array<string>) {
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));
};
}

View file

@ -4,6 +4,7 @@ import { handleActions } from 'util/redux-utils';
const defaultState: NotificationState = { const defaultState: NotificationState = {
notifications: [], notifications: [],
localNotifications: [],
notificationsFiltered: [], notificationsFiltered: [],
notificationCategories: undefined, notificationCategories: undefined,
fetchingNotifications: false, 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 // Errors
[ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoError) => { [ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoError) => {
const error: ErrorNotification = action.data; const error: ErrorNotification = action.data;

View file

@ -6,10 +6,17 @@ import { handleActions } from 'util/redux-utils';
const defaultState: SubscriptionState = { const defaultState: SubscriptionState = {
subscriptions: [], // Deprecated subscriptions: [], // Deprecated
following: [], following: [],
autoDownloads: {},
loading: false, loading: false,
firstRunCompleted: 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( export default handleActions(
{ {
[ACTIONS.CHANNEL_SUBSCRIBE]: (state: SubscriptionState, action): SubscriptionState => { [ACTIONS.CHANNEL_SUBSCRIBE]: (state: SubscriptionState, action): SubscriptionState => {
@ -61,19 +68,6 @@ export default handleActions(
following: newFollowing, 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 => ({ [ACTIONS.SET_VIEW_MODE]: (state: SubscriptionState, action): SubscriptionState => ({
...state, ...state,
viewMode: action.data, viewMode: action.data,

View file

@ -3,6 +3,7 @@ import { createSelector } from 'reselect';
export const selectState = (state) => state.notifications || {}; export const selectState = (state) => state.notifications || {};
export const selectNotifications = createSelector(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); 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; 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) => { export const selectToast = createSelector(selectState, (state) => {
if (state.toasts.length) { if (state.toasts.length) {
const { id, params } = state.toasts[0]; const { id, params } = state.toasts[0];

View file

@ -710,14 +710,11 @@ svg + .button__label {
} }
} }
// bring back special button-following style later // bring back special button-following style later
//.button-following { .button-following {
// color: var(--color-primary-contrast) !important; .icon {
// //background-color: rgba(var(--color-primary-dynamic),0.5) !important; stroke: var(--color-text) !important;
// background-color: rgba(125, 125, 125, 0.5) !important; }
// .icon { }
// stroke: var(--color-primary-contrast) !important;
// }
//}
.recommended-content__bubble { .recommended-content__bubble {
// margin-top: var(--spacing-xs); // margin-top: var(--spacing-xs);

View file

@ -11,18 +11,14 @@ $actions-z-index: 2;
} }
} }
// bring back later // bring back later
//.button-following { .button-following {
// &.button--alt { &.button--alt {
// color: var(--color-text); color: var(--color-text);
// background-color: var(--color-button-alt-bg); .icon {
// .icon { stroke: var(--color-text);
// stroke: var(--color-text); }
// } }
// &:hover { }
// color: var(--color-primary);
// }
// }
//}
.button-following:last-of-type { .button-following:last-of-type {
margin-left: 2px; margin-left: 2px;
} }