Add StatContext to store global stat state

This commit is contained in:
Justin 2022-09-27 16:05:19 -04:00
parent 058d0cec0b
commit 002fef27a3
6 changed files with 82 additions and 41 deletions

View file

@ -3,6 +3,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { getSettings } from 'soapbox/actions/settings'; import { getSettings } from 'soapbox/actions/settings';
import DropdownMenu from 'soapbox/containers/dropdown_menu_container'; import DropdownMenu from 'soapbox/containers/dropdown_menu_container';
import { useStatContext } from 'soapbox/contexts/stat-context';
import ComposeButton from 'soapbox/features/ui/components/compose-button'; import ComposeButton from 'soapbox/features/ui/components/compose-button';
import { useAppSelector, useOwnAccount } from 'soapbox/hooks'; import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
import { getFeatures } from 'soapbox/utils/features'; import { getFeatures } from 'soapbox/utils/features';
@ -24,12 +25,12 @@ const messages = defineMessages({
/** Desktop sidebar with links to different views in the app. */ /** Desktop sidebar with links to different views in the app. */
const SidebarNavigation = () => { const SidebarNavigation = () => {
const intl = useIntl(); const intl = useIntl();
const { unreadChatsCount } = useStatContext();
const instance = useAppSelector((state) => state.instance); const instance = useAppSelector((state) => state.instance);
const settings = useAppSelector((state) => getSettings(state)); const settings = useAppSelector((state) => getSettings(state));
const account = useOwnAccount(); const account = useOwnAccount();
const notificationCount = useAppSelector((state) => state.notifications.get('unread')); const notificationCount = useAppSelector((state) => state.notifications.get('unread'));
const chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0));
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count()); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
@ -114,7 +115,7 @@ const SidebarNavigation = () => {
<SidebarNavigationLink <SidebarNavigationLink
to='/chats' to='/chats'
icon={require('@tabler/icons/mail.svg')} icon={require('@tabler/icons/mail.svg')}
count={chatsCount} count={unreadChatsCount}
text={<FormattedMessage id='navigation.direct_messages' defaultMessage='Messages' />} text={<FormattedMessage id='navigation.direct_messages' defaultMessage='Messages' />}
/> />
); );

View file

@ -2,13 +2,15 @@ import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ThumbNavigationLink from 'soapbox/components/thumb_navigation-link'; import ThumbNavigationLink from 'soapbox/components/thumb_navigation-link';
import { useStatContext } from 'soapbox/contexts/stat-context';
import { useAppSelector, useOwnAccount } from 'soapbox/hooks'; import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
import { getFeatures } from 'soapbox/utils/features'; import { getFeatures } from 'soapbox/utils/features';
const ThumbNavigation: React.FC = (): JSX.Element => { const ThumbNavigation: React.FC = (): JSX.Element => {
const account = useOwnAccount(); const account = useOwnAccount();
const { unreadChatsCount } = useStatContext();
const notificationCount = useAppSelector((state) => state.notifications.unread); const notificationCount = useAppSelector((state) => state.notifications.unread);
const chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0));
const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
const features = getFeatures(useAppSelector((state) => state.instance)); const features = getFeatures(useAppSelector((state) => state.instance));
@ -21,7 +23,7 @@ const ThumbNavigation: React.FC = (): JSX.Element => {
text={<FormattedMessage id='navigation.direct_messages' defaultMessage='Messages' />} text={<FormattedMessage id='navigation.direct_messages' defaultMessage='Messages' />}
to='/chats' to='/chats'
exact exact
count={chatsCount} count={unreadChatsCount}
/> />
); );
} }

View file

@ -0,0 +1,29 @@
import React, { createContext, useContext, useMemo, useState } from 'react';
type IStatContext = {
unreadChatsCount: number,
setUnreadChatsCount: React.Dispatch<React.SetStateAction<number>>
}
const StatContext = createContext<any>({
unreadChatsCount: 0,
});
const StatProvider: React.FC = ({ children }) => {
const [unreadChatsCount, setUnreadChatsCount] = useState<number>(0);
const value = useMemo(() => ({
unreadChatsCount,
setUnreadChatsCount,
}), [unreadChatsCount]);
return (
<StatContext.Provider value={value}>
{children}
</StatContext.Provider>
);
};
const useStatContext = (): IStatContext => useContext(StatContext);
export { StatProvider, useStatContext };

View file

@ -3,6 +3,7 @@ import React, { useState } from 'react';
import { Stack } from 'soapbox/components/ui'; import { Stack } from 'soapbox/components/ui';
import { useChatContext } from 'soapbox/contexts/chat-context'; import { useChatContext } from 'soapbox/contexts/chat-context';
import { useStatContext } from 'soapbox/contexts/stat-context';
import { useDebounce, useFeatures } from 'soapbox/hooks'; import { useDebounce, useFeatures } from 'soapbox/hooks';
import { IChat, useChats } from 'soapbox/queries/chats'; import { IChat, useChats } from 'soapbox/queries/chats';
@ -19,6 +20,7 @@ import Blankslate from './blankslate';
const ChatPane = () => { const ChatPane = () => {
const features = useFeatures(); const features = useFeatures();
const debounce = useDebounce; const debounce = useDebounce;
const { unreadChatsCount } = useStatContext();
const [value, setValue] = useState<string>(); const [value, setValue] = useState<string>();
const debouncedValue = debounce(value as string, 300); const debouncedValue = debounce(value as string, 300);
@ -26,8 +28,6 @@ const ChatPane = () => {
const { chat, setChat, isOpen, isSearching, setSearching, toggleChatPane } = useChatContext(); const { chat, setChat, isOpen, isSearching, setSearching, toggleChatPane } = useChatContext();
const { chatsQuery: { data: chats, isLoading } } = useChats(debouncedValue); const { chatsQuery: { data: chats, isLoading } } = useChats(debouncedValue);
const unreadCount = sumBy(chats, (chat) => chat.unread);
const hasSearchValue = Number(debouncedValue?.length) > 0; const hasSearchValue = Number(debouncedValue?.length) > 0;
const handleClickChat = (chat: IChat) => { const handleClickChat = (chat: IChat) => {
@ -89,7 +89,7 @@ const ChatPane = () => {
<Pane isOpen={isOpen} index={0} main> <Pane isOpen={isOpen} index={0} main>
<ChatPaneHeader <ChatPaneHeader
title='Messages' title='Messages'
unreadCount={unreadCount} unreadCount={unreadChatsCount}
isOpen={isOpen} isOpen={isOpen}
onToggle={toggleChatPane} onToggle={toggleChatPane}
secondaryAction={() => { secondaryAction={() => {

View file

@ -118,6 +118,7 @@ import { WrappedRoute } from './util/react_router_helpers';
// Dummy import, to make sure that <Status /> ends up in the application bundle. // Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles. // Without this it ends up in ~8 very commonly used bundles.
import 'soapbox/components/status'; import 'soapbox/components/status';
import { StatProvider } from '../../contexts/stat-context';
const EmptyPage = HomePage; const EmptyPage = HomePage;
@ -651,52 +652,54 @@ const UI: React.FC = ({ children }) => {
}; };
return ( return (
<HotKeys keyMap={keyMap} handlers={me ? handlers : undefined} ref={setHotkeysRef} attach={window} focused> <StatProvider>
<div ref={node} style={style}> <HotKeys keyMap={keyMap} handlers={me ? handlers : undefined} ref={setHotkeysRef} attach={window} focused>
<BackgroundShapes /> <div ref={node} style={style}>
<BackgroundShapes />
<div className='z-10 flex flex-col'> <div className='z-10 flex flex-col'>
<Navbar /> <Navbar />
<Layout> <Layout>
<Layout.Sidebar> <Layout.Sidebar>
{!standalone && <SidebarNavigation />} {!standalone && <SidebarNavigation />}
</Layout.Sidebar> </Layout.Sidebar>
<SwitchingColumnsArea> <SwitchingColumnsArea>
{children} {children}
</SwitchingColumnsArea> </SwitchingColumnsArea>
</Layout> </Layout>
{me && floatingActionButton} {me && floatingActionButton}
<BundleContainer fetchComponent={UploadArea}> <BundleContainer fetchComponent={UploadArea}>
{Component => <Component active={draggingOver} onClose={closeUploadModal} />} {Component => <Component active={draggingOver} onClose={closeUploadModal} />}
</BundleContainer> </BundleContainer>
{me && ( {me && (
<BundleContainer fetchComponent={SidebarMenu}> <BundleContainer fetchComponent={SidebarMenu}>
{Component => <Component />}
</BundleContainer>
)}
{me && features.chats && !mobile && (
<BundleContainer fetchComponent={ChatWidget}>
{Component => <Component />}
</BundleContainer>
)}
<ThumbNavigation />
<BundleContainer fetchComponent={ProfileHoverCard}>
{Component => <Component />} {Component => <Component />}
</BundleContainer> </BundleContainer>
)}
{me && features.chats && !mobile && ( <BundleContainer fetchComponent={StatusHoverCard}>
<BundleContainer fetchComponent={ChatWidget}>
{Component => <Component />} {Component => <Component />}
</BundleContainer> </BundleContainer>
)} </div>
<ThumbNavigation />
<BundleContainer fetchComponent={ProfileHoverCard}>
{Component => <Component />}
</BundleContainer>
<BundleContainer fetchComponent={StatusHoverCard}>
{Component => <Component />}
</BundleContainer>
</div> </div>
</div> </HotKeys>
</HotKeys> </StatProvider>
); );
}; };

View file

@ -1,4 +1,5 @@
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'; import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
import sumBy from 'lodash/sumBy';
import { useState } from 'react'; import { useState } from 'react';
import { fetchRelationships } from 'soapbox/actions/accounts'; import { fetchRelationships } from 'soapbox/actions/accounts';
@ -6,6 +7,7 @@ import snackbar from 'soapbox/actions/snackbar';
import { getNextLink } from 'soapbox/api'; import { getNextLink } from 'soapbox/api';
import compareId from 'soapbox/compare_id'; import compareId from 'soapbox/compare_id';
import { useChatContext } from 'soapbox/contexts/chat-context'; import { useChatContext } from 'soapbox/contexts/chat-context';
import { useStatContext } from 'soapbox/contexts/stat-context';
import { useApi, useAppDispatch, useFeatures } from 'soapbox/hooks'; import { useApi, useAppDispatch, useFeatures } from 'soapbox/hooks';
import { normalizeChatMessage } from 'soapbox/normalizers'; import { normalizeChatMessage } from 'soapbox/normalizers';
import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries'; import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries';
@ -101,6 +103,7 @@ const useChats = (search?: string) => {
const api = useApi(); const api = useApi();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const features = useFeatures(); const features = useFeatures();
const { setUnreadChatsCount } = useStatContext();
const getChats = async (pageParam?: any): Promise<PaginatedResult<IChat>> => { const getChats = async (pageParam?: any): Promise<PaginatedResult<IChat>> => {
const endpoint = features.chatsV2 ? '/api/v2/pleroma/chats' : '/api/v1/pleroma/chats'; const endpoint = features.chatsV2 ? '/api/v2/pleroma/chats' : '/api/v1/pleroma/chats';
@ -116,6 +119,9 @@ const useChats = (search?: string) => {
const link = getNextLink(response); const link = getNextLink(response);
const hasMore = !!link; const hasMore = !!link;
// TODO: change to response header
setUnreadChatsCount(sumBy(data, (chat) => chat.unread));
// Set the relationships to these users in the redux store. // Set the relationships to these users in the redux store.
dispatch(fetchRelationships(data.map((item) => item.account.id))); dispatch(fetchRelationships(data.map((item) => item.account.id)));