From 08ce55f1a69f4cf074aed5237960ab03f72b2909 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 16 Mar 2021 17:22:46 +1100 Subject: [PATCH] move LeftPane items to hooks --- ts/components/ConversationListItem.tsx | 2 +- ts/components/LeftPane.tsx | 241 +++++++++--------- ts/components/SessionMainPanel.tsx | 18 +- ts/components/session/ActionsPanel.tsx | 24 +- .../session/LeftPaneSettingSection.tsx | 14 +- ts/components/session/SessionInboxView.tsx | 9 +- .../session/network/SessionExpiredWarning.tsx | 2 +- .../session/network/SessionOffline.tsx | 4 +- ts/state/ducks/conversations.ts | 2 +- ts/state/ducks/search.ts | 6 +- ts/state/ducks/theme.tsx | 5 +- ts/state/selectors/conversations.ts | 16 +- ts/state/selectors/search.ts | 2 +- ts/state/smart/LeftPane.tsx | 45 ---- ts/state/smart/SessionMainPanel.tsx | 18 -- 15 files changed, 162 insertions(+), 246 deletions(-) delete mode 100644 ts/state/smart/LeftPane.tsx delete mode 100644 ts/state/smart/SessionMainPanel.tsx diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index 1ea917170..c436a7992 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -26,7 +26,7 @@ import { PubKey } from '../session/types'; import { ConversationType } from '../state/ducks/conversations'; export interface ConversationListItemProps extends ConversationType { - index: number; // used to force a refresh when one conversation is removed on top of the list + index?: number; // used to force a refresh when one conversation is removed on top of the list memberAvatars?: Array; // this is added by usingClosedConversationDetails } diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx index c0f62dc6f..6c02b48bc 100644 --- a/ts/components/LeftPane.tsx +++ b/ts/components/LeftPane.tsx @@ -3,17 +3,29 @@ import React from 'react'; import { ActionsPanel, SectionType } from './session/ActionsPanel'; import { LeftPaneMessageSection } from './session/LeftPaneMessageSection'; -import { ConversationListItemProps } from './ConversationListItem'; -import { SearchResultsProps } from './SearchResults'; -import { SearchOptions } from '../types/Search'; -import { ConversationType } from '../state/ducks/conversations'; +import { openConversationExternal } from '../state/ducks/conversations'; import { LeftPaneContactSection } from './session/LeftPaneContactSection'; import { LeftPaneSettingSection } from './session/LeftPaneSettingSection'; import { SessionTheme } from '../state/ducks/SessionTheme'; -import { DefaultTheme } from 'styled-components'; -import { SessionSettingCategory } from './session/settings/SessionSettings'; import { SessionOffline } from './session/network/SessionOffline'; import { SessionExpiredWarning } from './session/network/SessionExpiredWarning'; +import { getFocusedSection } from '../state/selectors/section'; +import { useDispatch, useSelector } from 'react-redux'; +import { + getLeftPaneLists, + getOurPrimaryConversation, + getUnreadMessageCount, +} from '../state/selectors/conversations'; +import { + getQuery, + getSearchResults, + isSearching, +} from '../state/selectors/search'; +import { clearSearch, search, updateSearchTerm } from '../state/ducks/search'; +import { showLeftPaneSection } from '../state/ducks/section'; +import { getOurNumber } from '../state/selectors/user'; +import { getTheme } from '../state/selectors/theme'; +import { applyTheme, ThemeStateType } from '../state/ducks/theme'; // from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5 export type RowRendererParamsType = { @@ -25,132 +37,115 @@ export type RowRendererParamsType = { style: Object; }; -interface Props { - ourPrimaryConversation: ConversationType; - conversations: Array; - contacts: Array; - - unreadMessageCount: number; - searchResults?: SearchResultsProps; - searchTerm: string; - - focusedSection: SectionType; - focusedSettingsSection?: SessionSettingCategory; - showLeftPaneSection: (section: SectionType) => void; - showSettingsSection: (section: SessionSettingCategory) => void; - +type Props = { isExpired: boolean; +}; - openConversationExternal: (id: string, messageId?: string) => void; - updateSearchTerm: (searchTerm: string) => void; - search: (query: string, options: SearchOptions) => void; - clearSearch: () => void; - - theme: DefaultTheme; -} - -export class LeftPane extends React.Component { - public constructor(props: any) { - super(props); - this.handleSectionSelected = this.handleSectionSelected.bind(this); - } +const InnerLeftPaneMessageSection = (props: { isExpired: boolean }) => { + const dispatch = useDispatch(); + + const showSearch = useSelector(isSearching); + const searchTerm = useSelector(getQuery); + + const searchResults = showSearch ? useSelector(getSearchResults) : undefined; + + const lists = showSearch ? undefined : useSelector(getLeftPaneLists); + const theme = useSelector(getTheme); + // tslint:disable: use-simple-attributes + + return ( + <> + + {props.isExpired && } + + dispatch(openConversationExternal(id, messageId)) + } + conversations={lists?.conversations || []} + contacts={lists?.contacts || []} + searchResults={searchResults} + searchTerm={searchTerm} + updateSearchTerm={query => dispatch(updateSearchTerm(query))} + search={(query, options) => dispatch(search(query, options))} + clearSearch={() => dispatch(clearSearch())} + /> + + ); +}; - public handleSectionSelected(section: SectionType) { - this.props.clearSearch(); - this.props.showLeftPaneSection(section); - } +const InnerLeftPaneContactSection = () => { + const dispatch = useDispatch(); + const theme = useSelector(getTheme); + const showSearch = useSelector(isSearching); + + const lists = showSearch ? undefined : useSelector(getLeftPaneLists); + + const directContacts = lists?.contacts || []; + + return ( + <> + + + dispatch(openConversationExternal(id, messageId)) + } + directContacts={directContacts} + theme={theme} + /> + + ); +}; - public render(): JSX.Element { - return ( - -
- -
{this.renderSection()}
-
-
- ); - } +const LeftPaneSettingsSection = () => { + return ; +}; - private renderSection(): JSX.Element | undefined { - switch (this.props.focusedSection) { - case SectionType.Message: - return this.renderMessageSection(); - case SectionType.Contact: - return this.renderContactSection(); - case SectionType.Settings: - return this.renderSettingSection(); - default: - return undefined; - } - } +const LeftPaneSection = (props: { isExpired: boolean }) => { + const focusedSection = useSelector(getFocusedSection); - private renderMessageSection() { - const { - openConversationExternal, - conversations, - contacts, - searchResults, - searchTerm, - updateSearchTerm, - search, - clearSearch, - isExpired, - } = this.props; - - return ( - <> - - {isExpired && } - - - ); + if (focusedSection === SectionType.Message) { + return ; } - private renderContactSection() { - const { openConversationExternal } = this.props; - - const directContacts = this.getDirectContactsOnly(); - - return ( - <> - - - - ); + if (focusedSection === SectionType.Contact) { + return ; } - - private getDirectContactsOnly() { - return this.props.contacts.filter(f => f.type === 'direct'); + if (focusedSection === SectionType.Settings) { + return ; } + return <>; +}; - private renderSettingSection() { - const settingsCategory = - this.props.focusedSettingsSection || SessionSettingCategory.Appearance; - return ( - <> - { + const theme = useSelector(getTheme); + const dispatch = useDispatch(); + const focusedSection = useSelector(getFocusedSection); + const unreadMessageCount = useSelector(getUnreadMessageCount); + const ourPrimaryConversation = useSelector(getOurPrimaryConversation); + const ourNumber = useSelector(getOurNumber); + + return ( + +
+ { + dispatch(clearSearch()); + dispatch(showLeftPaneSection(section)); + }} + unreadMessageCount={unreadMessageCount} + ourPrimaryConversation={ourPrimaryConversation} + ourNumber={ourNumber} + theme={theme} + applyTheme={(newTheme: ThemeStateType) => + dispatch(applyTheme(newTheme)) + } /> - - ); - } -} +
+ +
+
+
+ ); +}; diff --git a/ts/components/SessionMainPanel.tsx b/ts/components/SessionMainPanel.tsx index 063ee8ed0..d3cfb3e0e 100644 --- a/ts/components/SessionMainPanel.tsx +++ b/ts/components/SessionMainPanel.tsx @@ -1,22 +1,18 @@ import React from 'react'; +import { useSelector } from 'react-redux'; +import { getFocusedSettingsSection } from '../state/selectors/section'; import { SmartSessionConversation } from '../state/smart/SessionConversation'; -import { - SessionSettingCategory, - SmartSettingsView, -} from './session/settings/SessionSettings'; +import { SmartSettingsView } from './session/settings/SessionSettings'; const FilteredSettingsView = SmartSettingsView as any; -type Props = { - focusedSettingsSection?: SessionSettingCategory; -}; - -export const SessionMainPanel = (props: Props) => { - const isSettingsView = props.focusedSettingsSection !== undefined; +export const SessionMainPanel = () => { + const focusedSettingsSection = useSelector(getFocusedSettingsSection); + const isSettingsView = focusedSettingsSection !== undefined; if (isSettingsView) { - return ; + return ; } return (
diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index 078002257..0cab1bde6 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -1,17 +1,11 @@ import React from 'react'; -import { connect } from 'react-redux'; import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; import { Avatar } from '../Avatar'; import { darkTheme, lightTheme } from '../../state/ducks/SessionTheme'; import { SessionToastContainer } from './SessionToastContainer'; -import { mapDispatchToProps } from '../../state/actions'; import { ConversationType } from '../../state/ducks/conversations'; import { DefaultTheme } from 'styled-components'; -import { StateType } from '../../state/reducer'; import { ConversationController } from '../../session/conversations'; -import { getFocusedSection } from '../../state/selectors/section'; -import { getTheme } from '../../state/selectors/theme'; -import { getOurNumber } from '../../state/selectors/user'; import { UserUtils } from '../../session/utils'; import { syncConfigurationIfNeeded } from '../../session/utils/syncUtils'; import { DAYS } from '../../session/utils/Number'; @@ -35,12 +29,12 @@ export enum SectionType { } interface Props { - onSectionSelected: any; + onSectionSelected: (section: SectionType) => void; selectedSection: SectionType; unreadMessageCount: number; ourPrimaryConversation: ConversationType; ourNumber: string; - applyTheme?: any; + applyTheme: any; theme: DefaultTheme; } @@ -48,7 +42,7 @@ interface Props { * ActionsPanel is the far left banner (not the left pane). * The panel with buttons to switch between the message/contact/settings/theme views */ -class ActionsPanelPrivate extends React.Component { +export class ActionsPanel extends React.Component { private syncInterval: NodeJS.Timeout | null = null; constructor(props: Props) { @@ -261,15 +255,3 @@ class ActionsPanelPrivate extends React.Component { window.showResetSessionIdDialog(); } } - -const mapStateToProps = (state: StateType) => { - return { - section: getFocusedSection(state), - theme: getTheme(state), - ourNumber: getOurNumber(state), - }; -}; - -const smart = connect(mapStateToProps, mapDispatchToProps); - -export const ActionsPanel = smart(ActionsPanelPrivate); diff --git a/ts/components/session/LeftPaneSettingSection.tsx b/ts/components/session/LeftPaneSettingSection.tsx index 1780ccb79..c2aeb0710 100644 --- a/ts/components/session/LeftPaneSettingSection.tsx +++ b/ts/components/session/LeftPaneSettingSection.tsx @@ -1,8 +1,6 @@ import React from 'react'; import classNames from 'classnames'; -import { LeftPane } from '../LeftPane'; - import { SessionButton, SessionButtonColor, @@ -10,14 +8,14 @@ import { } from './SessionButton'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon'; -import { SessionSearchInput } from './SessionSearchInput'; import { SessionSettingCategory } from './settings/SessionSettings'; -import { DefaultTheme, useTheme } from 'styled-components'; +import { DefaultTheme } from 'styled-components'; import { LeftPaneSectionHeader } from './LeftPaneSectionHeader'; import { deleteAccount } from '../../util/accountManager'; import { useDispatch, useSelector } from 'react-redux'; import { showSettingsSection } from '../../state/ducks/section'; import { getFocusedSettingsSection } from '../../state/selectors/section'; +import { getTheme } from '../../state/selectors/theme'; type Props = { settingsCategory: SessionSettingCategory; @@ -59,7 +57,7 @@ const LeftPaneSettingsCategoryRow = (props: { item: any }) => { const { item } = props; const dispatch = useDispatch(); - const theme = useTheme(); + const theme = useSelector(getTheme); const focusedSettingsSection = useSelector(getFocusedSettingsSection); return ( @@ -153,12 +151,14 @@ const LeftPaneBottomButtons = () => { ); }; -export const LeftPaneSettingSection = (props: Props) => { +export const LeftPaneSettingSection = () => { + const theme = useSelector(getTheme); + return (
diff --git a/ts/components/session/SessionInboxView.tsx b/ts/components/session/SessionInboxView.tsx index 043425f36..b12e042a7 100644 --- a/ts/components/session/SessionInboxView.tsx +++ b/ts/components/session/SessionInboxView.tsx @@ -6,13 +6,12 @@ import { ConversationController } from '../../session/conversations'; import { UserUtils } from '../../session/utils'; import { createStore } from '../../state/createStore'; import { actions as conversationActions } from '../../state/ducks/conversations'; -import { SmartLeftPane } from '../../state/smart/LeftPane'; -import { SmartSessionMainPanel } from '../../state/smart/SessionMainPanel'; import { makeLookup } from '../../util'; +import { LeftPane } from '../LeftPane'; +import { SessionMainPanel } from '../SessionMainPanel'; // Workaround: A react component's required properties are filtering up through connect() // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363 -const FilteredLeftPane = SmartLeftPane as any; type State = { isInitialLoadComplete: boolean; @@ -52,13 +51,13 @@ export class SessionInboxView extends React.Component {
{this.renderLeftPane()}
- + ); } private renderLeftPane() { - return ; + return ; } private async setupLeftPane() { diff --git a/ts/components/session/network/SessionExpiredWarning.tsx b/ts/components/session/network/SessionExpiredWarning.tsx index e577e664b..bc20f1660 100644 --- a/ts/components/session/network/SessionExpiredWarning.tsx +++ b/ts/components/session/network/SessionExpiredWarning.tsx @@ -12,7 +12,7 @@ const SessionExpiredWarningLink = styled.a` color: black; `; -export const SessionExpiredWarning = (props: { theme: DefaultTheme }) => { +export const SessionExpiredWarning = () => { return (
{window.i18n('expiredWarning')}
diff --git a/ts/components/session/network/SessionOffline.tsx b/ts/components/session/network/SessionOffline.tsx index 795f38469..8230520c3 100644 --- a/ts/components/session/network/SessionOffline.tsx +++ b/ts/components/session/network/SessionOffline.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import styled, { DefaultTheme } from 'styled-components'; +import styled from 'styled-components'; import { useNetwork } from '../../../hooks/useNetwork'; type ContainerProps = { @@ -23,7 +23,7 @@ const OfflineTitle = styled.h3` const OfflineMessage = styled.div``; -export const SessionOffline = (props: { theme: DefaultTheme }) => { +export const SessionOffline = () => { const isOnline = useNetwork(); return ( diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index a842c8333..f06f85492 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -426,7 +426,7 @@ function conversationReset({ }; } -function openConversationExternal( +export function openConversationExternal( id: string, messageId?: string ): SelectedConversationChangedActionType { diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index f039a91eb..6faaf564b 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -76,7 +76,7 @@ export const actions = { updateSearchTerm, }; -function search( +export function search( query: string, options: SearchOptions ): SearchResultsKickoffActionType { @@ -125,13 +125,13 @@ async function doSearch( messages: getMessageProps(filteredMessages) || [], }; } -function clearSearch(): ClearSearchActionType { +export function clearSearch(): ClearSearchActionType { return { type: 'SEARCH_CLEAR', payload: null, }; } -function updateSearchTerm(query: string): UpdateSearchTermActionType { +export function updateSearchTerm(query: string): UpdateSearchTermActionType { return { type: 'SEARCH_UPDATE', payload: { diff --git a/ts/state/ducks/theme.tsx b/ts/state/ducks/theme.tsx index 611020353..6fe0e28d1 100644 --- a/ts/state/ducks/theme.tsx +++ b/ts/state/ducks/theme.tsx @@ -1,6 +1,7 @@ export const APPLY_THEME = 'APPLY_THEME'; +export type ThemeStateType = typeof lightTheme; -export const applyTheme = (theme: any) => { +export const applyTheme = (theme: ThemeStateType) => { return { type: APPLY_THEME, payload: theme, @@ -8,8 +9,6 @@ export const applyTheme = (theme: any) => { }; import { lightTheme } from './SessionTheme'; -export type ThemeStateType = typeof lightTheme; - const initialState = lightTheme; export const reducer = ( diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 9929f57e7..d9e28e3a2 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -95,6 +95,7 @@ export const getConversationComparator = createSelector( _getConversationComparator ); +// export only because we use it in some of our tests export const _getLeftPaneLists = ( lookup: ConversationLookupType, comparator: (left: ConversationType, right: ConversationType) => number, @@ -108,7 +109,7 @@ export const _getLeftPaneLists = ( const sorted = values.sort(comparator); const conversations: Array = []; - const allContacts: Array = []; + const directConversations: Array = []; let index = 0; @@ -155,8 +156,8 @@ export const _getLeftPaneLists = ( continue; } - if (conversation.activeAt !== undefined) { - allContacts.push(conversation); + if (conversation.activeAt !== undefined && conversation.type === 'direct') { + directConversations.push(conversation); } if (unreadCount < 9 && conversation.unreadCount > 0) { @@ -169,7 +170,7 @@ export const _getLeftPaneLists = ( return { conversations, - contacts: allContacts, + contacts: directConversations, unreadCount, }; }; @@ -223,3 +224,10 @@ export const getMe = createSelector( return lookup[ourNumber]; } ); + +export const getUnreadMessageCount = createSelector( + getLeftPaneLists, + (state): number => { + return state.unreadCount; + } +); diff --git a/ts/state/selectors/search.ts b/ts/state/selectors/search.ts index 64850e4d3..638a6167e 100644 --- a/ts/state/selectors/search.ts +++ b/ts/state/selectors/search.ts @@ -28,7 +28,7 @@ export const isSearching = createSelector( (state: SearchStateType) => { const { query } = state; - return query && query.trim().length > 1; + return Boolean(query && query.trim().length > 1); } ); diff --git a/ts/state/smart/LeftPane.tsx b/ts/state/smart/LeftPane.tsx deleted file mode 100644 index 6feac3cca..000000000 --- a/ts/state/smart/LeftPane.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { connect } from 'react-redux'; -import { LeftPane } from '../../components/LeftPane'; -import { StateType } from '../reducer'; - -import { getQuery, getSearchResults, isSearching } from '../selectors/search'; -import { getIntl, getOurNumber } from '../selectors/user'; -import { - getLeftPaneLists, - getOurPrimaryConversation, -} from '../selectors/conversations'; -import { mapDispatchToProps } from '../actions'; -import { - getFocusedSection, - getFocusedSettingsSection, -} from '../selectors/section'; -import { getTheme } from '../selectors/theme'; - -// Workaround: A react component's required properties are filtering up through connect() -// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/3136k3 - -const mapStateToProps = (state: StateType) => { - const showSearch = isSearching(state); - - const leftPaneList = getLeftPaneLists(state); - const lists = showSearch ? undefined : leftPaneList; - const searchResults = showSearch ? getSearchResults(state) : undefined; - const ourPrimaryConversation = getOurPrimaryConversation(state); - - return { - ...lists, - ourPrimaryConversation, // used in actionPanel - searchTerm: getQuery(state), - ourNumber: getOurNumber(state), - searchResults, - i18n: getIntl(state), - unreadMessageCount: leftPaneList.unreadCount, - theme: getTheme(state), - focusedSection: getFocusedSection(state), - focusedSettingsSection: getFocusedSettingsSection(state), - }; -}; - -const smart = connect(mapStateToProps, mapDispatchToProps); - -export const SmartLeftPane = smart(LeftPane); diff --git a/ts/state/smart/SessionMainPanel.tsx b/ts/state/smart/SessionMainPanel.tsx deleted file mode 100644 index 16a919437..000000000 --- a/ts/state/smart/SessionMainPanel.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { connect } from 'react-redux'; -import { StateType } from '../reducer'; - -import { mapDispatchToProps } from '../actions'; -import { getFocusedSettingsSection } from '../selectors/section'; -import { getTheme } from '../selectors/theme'; -import { SessionMainPanel } from '../../components/SessionMainPanel'; - -const mapStateToProps = (state: StateType) => { - return { - theme: getTheme(state), - focusedSettingsSection: getFocusedSettingsSection(state), - }; -}; - -const smart = connect(mapStateToProps, mapDispatchToProps); - -export const SmartSessionMainPanel = smart(SessionMainPanel);