diff --git a/web/navigation-panels/app-switcher.react.js b/web/navigation-panels/app-switcher.react.js index aab37d2f5..1a1adaa40 100644 --- a/web/navigation-panels/app-switcher.react.js +++ b/web/navigation-panels/app-switcher.react.js @@ -1,108 +1,106 @@ // @flow import * as React from 'react'; import { useDispatch } from 'react-redux'; import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; -import { - mostRecentlyReadThreadSelector, - unreadCount, -} from 'lib/selectors/thread-selectors.js'; +import { mostRecentlyReadThreadSelector } from 'lib/selectors/thread-selectors.js'; import NavigationPanel from './navigation-panel.react.js'; import css from './topbar.css'; import { updateNavInfoActionType } from '../redux/action-types.js'; import { useSelector } from '../redux/redux-utils.js'; import { navTabSelector } from '../selectors/nav-selectors.js'; +import { unreadCountInSelectedCommunity } from '../selectors/thread-selectors.js'; function AppSwitcher(): React.Node { const activeChatThreadID = useSelector( state => state.navInfo.activeChatThreadID, ); const mostRecentlyReadThread = useSelector(mostRecentlyReadThreadSelector); const isActiveThreadCurrentlyUnread = useSelector( state => !activeChatThreadID || !!state.threadStore.threadInfos[activeChatThreadID]?.currentUser.unread, ); const dispatch = useDispatch(); const onClickChat = React.useCallback( (event: SyntheticEvent) => { event.preventDefault(); dispatch({ type: updateNavInfoActionType, payload: { tab: 'chat', activeChatThreadID: isActiveThreadCurrentlyUnread ? mostRecentlyReadThread : activeChatThreadID, }, }); }, [ dispatch, isActiveThreadCurrentlyUnread, mostRecentlyReadThread, activeChatThreadID, ], ); - const boundUnreadCount = useSelector(unreadCount); + const boundUnreadCount = useSelector(unreadCountInSelectedCommunity); let chatBadge = null; if (boundUnreadCount > 0) { chatBadge = {boundUnreadCount}; } const chatNavigationItem = React.useMemo( () => ( {chatBadge}

Chat

), [chatBadge, onClickChat], ); const onClickCalendar = React.useCallback( (event: SyntheticEvent) => { event.preventDefault(); dispatch({ type: updateNavInfoActionType, payload: { tab: 'calendar' }, }); }, [dispatch], ); const isCalendarEnabled = useSelector(state => state.enabledApps.calendar); const calendarNavigationItem = React.useMemo(() => { if (!isCalendarEnabled) { return null; } return (

Calendar

); }, [isCalendarEnabled, onClickCalendar]); return ( {chatNavigationItem} {calendarNavigationItem} ); } export default AppSwitcher; diff --git a/web/selectors/thread-selectors.js b/web/selectors/thread-selectors.js index 3dffd4835..78224eae3 100644 --- a/web/selectors/thread-selectors.js +++ b/web/selectors/thread-selectors.js @@ -1,129 +1,152 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { useDispatch } from 'react-redux'; +import { createSelector } from 'reselect'; import { ENSCacheContext } from 'lib/components/ens-cache-provider.react.js'; import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js'; -import { createPendingSidebar } from 'lib/shared/thread-utils.js'; +import { + createPendingSidebar, + threadInHomeChatList, +} from 'lib/shared/thread-utils.js'; import type { ComposableMessageInfo, RobotextMessageInfo, } from 'lib/types/message-types.js'; -import type { ThreadInfo } from 'lib/types/thread-types.js'; +import type { ThreadInfo, RawThreadInfo } from 'lib/types/thread-types.js'; +import { values } from 'lib/utils/objects.js'; import { getDefaultTextMessageRules } from '../markdown/rules.react.js'; import { updateNavInfoActionType } from '../redux/action-types.js'; +import type { AppState } from '../redux/redux-setup.js'; import { useSelector } from '../redux/redux-utils.js'; function useOnClickThread( thread: ?ThreadInfo, ): (event: SyntheticEvent) => void { const dispatch = useDispatch(); return React.useCallback( (event: SyntheticEvent) => { invariant( thread?.id, 'useOnClickThread should be called with threadID set', ); event.preventDefault(); const { id: threadID } = thread; let payload; if (threadID.includes('pending')) { payload = { chatMode: 'view', activeChatThreadID: threadID, pendingThread: thread, tab: 'chat', }; } else { payload = { chatMode: 'view', activeChatThreadID: threadID, tab: 'chat', }; } dispatch({ type: updateNavInfoActionType, payload }); }, [dispatch, thread], ); } function useThreadIsActive(threadID: string): boolean { return useSelector(state => threadID === state.navInfo.activeChatThreadID); } function useOnClickPendingSidebar( messageInfo: ComposableMessageInfo | RobotextMessageInfo, threadInfo: ThreadInfo, ): (event: SyntheticEvent) => mixed { const dispatch = useDispatch(); const loggedInUserInfo = useLoggedInUserInfo(); const cacheContext = React.useContext(ENSCacheContext); const { getENSNames } = cacheContext; return React.useCallback( async (event: SyntheticEvent) => { event.preventDefault(); if (!loggedInUserInfo) { return; } const pendingSidebarInfo = await createPendingSidebar({ sourceMessageInfo: messageInfo, parentThreadInfo: threadInfo, loggedInUserInfo, markdownRules: getDefaultTextMessageRules().simpleMarkdownRules, getENSNames, }); dispatch({ type: updateNavInfoActionType, payload: { activeChatThreadID: pendingSidebarInfo.id, pendingThread: pendingSidebarInfo, }, }); }, [loggedInUserInfo, messageInfo, threadInfo, dispatch, getENSNames], ); } function useOnClickNewThread(): (event: SyntheticEvent) => void { const dispatch = useDispatch(); return React.useCallback( (event: SyntheticEvent) => { event.preventDefault(); dispatch({ type: updateNavInfoActionType, payload: { chatMode: 'create', selectedUserList: [], }, }); }, [dispatch], ); } function useDrawerSelectedThreadID(): ?string { const activeChatThreadID = useSelector( state => state.navInfo.activeChatThreadID, ); const pickedCommunityID = useSelector( state => state.communityPickerStore.calendar, ); const inCalendar = useSelector(state => state.navInfo.tab === 'calendar'); return inCalendar ? pickedCommunityID : activeChatThreadID; } +const unreadCountInSelectedCommunity: (state: AppState) => number = + createSelector( + (state: AppState) => state.threadStore.threadInfos, + (state: AppState) => state.communityPickerStore.chat, + ( + threadInfos: { +[id: string]: RawThreadInfo }, + communityID: ?string, + ): number => + values(threadInfos).filter( + threadInfo => + threadInHomeChatList(threadInfo) && + threadInfo.currentUser.unread && + (!communityID || communityID === threadInfo.community), + ).length, + ); + export { useOnClickThread, useThreadIsActive, useOnClickPendingSidebar, useOnClickNewThread, useDrawerSelectedThreadID, + unreadCountInSelectedCommunity, };