Refactor to functional components

pull/1745/head
Warrick Corfe-Tan 4 years ago
parent 563dff64ae
commit cf6ac09300

@ -19,12 +19,18 @@ import {
} from './session/menu/ConversationListItemContextMenu'; } from './session/menu/ConversationListItemContextMenu';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { OutgoingMessageStatus } from './conversation/message/OutgoingMessageStatus'; import { OutgoingMessageStatus } from './conversation/message/OutgoingMessageStatus';
import { DefaultTheme, withTheme } from 'styled-components'; import { DefaultTheme, useTheme, withTheme } from 'styled-components';
import { PubKey } from '../session/types'; import { PubKey } from '../session/types';
import { ConversationType, openConversationExternal } from '../state/ducks/conversations'; import {
ConversationType,
LastMessageType,
openConversationExternal,
} from '../state/ducks/conversations';
import { SessionIcon, SessionIconSize, SessionIconType } from './session/icon'; import { SessionIcon, SessionIconSize, SessionIconType } from './session/icon';
import { useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { SectionType } from './session/ActionsPanel'; import { SectionType } from './session/ActionsPanel';
import { getTheme } from '../state/selectors/theme';
import { getFocusedSection } from '../state/selectors/section';
export interface ConversationListItemProps extends ConversationType { 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
@ -42,197 +48,274 @@ const Portal = ({ children }: { children: any }) => {
return createPortal(children, document.querySelector('.inbox.index') as Element); return createPortal(children, document.querySelector('.inbox.index') as Element);
}; };
class ConversationListItem extends React.PureComponent<Props> { const ConversationListItem = (props: Props) => {
public constructor(props: Props) { const {
super(props); phoneNumber,
} unreadCount,
id,
isSelected,
isBlocked,
style,
mentionedUs,
avatarPath,
name,
profileName,
activeAt,
isMe,
isPinned,
isTyping,
lastMessage,
memberAvatars,
} = props;
const triggerId: string = `conversation-item-${phoneNumber}-ctxmenu`;
const key: string = `conversation-item-${phoneNumber}`;
public renderAvatar() { const dispatch = useDispatch();
const { avatarPath, name, phoneNumber, profileName, memberAvatars } = this.props;
const userName = name || profileName || phoneNumber; const menuProps: PropsContextConversationItem = {
...props,
triggerId,
};
return ( return (
<div className="module-conversation-list-item__avatar-container"> <div key={key}>
<Avatar <div
role="button"
onClick={() => {
dispatch(openConversationExternal(id));
}}
onContextMenu={(e: any) => {
contextMenu.show({
id: triggerId,
event: e,
});
}}
style={style}
className={classNames(
'module-conversation-list-item',
unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null,
unreadCount > 0 && mentionedUs ? 'module-conversation-list-item--mentioned-us' : null,
isSelected ? 'module-conversation-list-item--is-selected' : null,
isBlocked ? 'module-conversation-list-item--is-blocked' : null
)}
>
<ConversationListItemAvatar
avatarPath={avatarPath} avatarPath={avatarPath}
name={userName} name={name}
size={AvatarSize.S} profileName={profileName}
memberAvatars={memberAvatars} memberAvatars={memberAvatars}
pubkey={phoneNumber}
/> />
<div className="module-conversation-list-item__content">
<ConversationListItemHeader
unreadCount={unreadCount}
mentionedUs={mentionedUs}
activeAt={activeAt}
isPinned={isPinned}
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
isMe={isMe}
/>
<ConversationListItemMessage
lastMessage={lastMessage}
isTyping={isTyping}
unreadCount={unreadCount}
/>
</div>
</div> </div>
); <Portal>
} <ConversationListItemContextMenu {...menuProps} />
</Portal>
</div>
);
};
public renderHeader() { export interface ConversationListItemAvatarProps {
const { unreadCount, mentionedUs, activeAt, isPinned } = this.props; avatarPath?: string;
name?: string;
let atSymbol = null; profileName?: string;
let unreadCountDiv = null; phoneNumber?: string;
if (unreadCount > 0) { memberAvatars?: Array<ConversationAvatar>;
atSymbol = mentionedUs ? <p className="at-symbol">@</p> : null; }
unreadCountDiv = <p className="module-conversation-list-item__unread-count">{unreadCount}</p>;
}
const isMessagesSection =
window.inboxStore?.getState().section.focusedSection === SectionType.Message;
const pinIcon =
isMessagesSection && isPinned ? (
<SessionIcon
iconType={SessionIconType.Pin}
iconColor={this.props.theme.colors.textColorSubtle}
iconSize={SessionIconSize.Tiny}
/>
) : null;
return ( export const ConversationListItemAvatar = (props: ConversationListItemAvatarProps) => {
<div className="module-conversation-list-item__header"> const { avatarPath, name, phoneNumber, profileName, memberAvatars } = props;
<div const userName = name || profileName || phoneNumber;
className={classNames(
'module-conversation-list-item__header__name',
unreadCount > 0 ? 'module-conversation-list-item__header__name--with-unread' : null
)}
>
{this.renderUser()}
</div>
{pinIcon} return (
{unreadCountDiv} <div className="module-conversation-list-item__avatar-container">
{atSymbol} <Avatar
{ avatarPath={avatarPath}
<div name={userName}
className={classNames( size={AvatarSize.S}
'module-conversation-list-item__header__date', memberAvatars={memberAvatars}
unreadCount > 0 ? 'module-conversation-list-item__header__date--has-unread' : null pubkey={phoneNumber}
)} />
> </div>
{ );
<Timestamp };
timestamp={activeAt}
extended={false}
isConversationListItem={true}
theme={this.props.theme}
/>
}
</div>
}
</div>
);
}
public renderMessage() { export interface ConversationListItemHeaderProps {
const { lastMessage, isTyping, unreadCount } = this.props; unreadCount: number;
mentionedUs: boolean;
activeAt?: number;
isPinned?: boolean;
if (!lastMessage && !isTyping) { name?: string;
return null; phoneNumber: string;
} profileName?: string;
const text = lastMessage && lastMessage.text ? lastMessage.text : ''; isMe: boolean;
}
if (isEmpty(text)) { export const ConversationListItemHeader = (props: ConversationListItemHeaderProps) => {
return null; const {
} unreadCount,
mentionedUs,
activeAt,
isPinned,
name,
phoneNumber,
profileName,
isMe,
} = props;
return ( const theme = useTheme();
<div className="module-conversation-list-item__message">
<div let atSymbol = null;
className={classNames( let unreadCountDiv = null;
'module-conversation-list-item__message__text', if (unreadCount > 0) {
unreadCount > 0 ? 'module-conversation-list-item__message__text--has-unread' : null atSymbol = mentionedUs ? <p className="at-symbol">@</p> : null;
)} unreadCountDiv = <p className="module-conversation-list-item__unread-count">{unreadCount}</p>;
>
{isTyping ? (
<TypingAnimation />
) : (
<MessageBody isGroup={true} text={text} disableJumbomoji={true} disableLinks={true} />
)}
</div>
{lastMessage && lastMessage.status ? (
<OutgoingMessageStatus
status={lastMessage.status}
iconColor={this.props.theme.colors.textColorSubtle}
theme={this.props.theme}
/>
) : null}
</div>
);
} }
public render() { const isMessagesSection = useSelector(getFocusedSection) === SectionType.Message;
const { phoneNumber, unreadCount, id, isSelected, isBlocked, style, mentionedUs } = this.props;
const triggerId = `conversation-item-${phoneNumber}-ctxmenu`; const pinIcon =
const key = `conversation-item-${phoneNumber}`; isMessagesSection && isPinned ? (
<SessionIcon
iconType={SessionIconType.Pin}
iconColor={theme.colors.textColorSubtle}
iconSize={SessionIconSize.Tiny}
/>
) : null;
return ( return (
<div key={key}> <div className="module-conversation-list-item__header">
<div
className={classNames(
'module-conversation-list-item__header__name',
unreadCount > 0 ? 'module-conversation-list-item__header__name--with-unread' : null
)}
>
<ConversationListItemUser
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
isMe={isMe}
/>
</div>
{pinIcon}
{unreadCountDiv}
{atSymbol}
{
<div <div
role="button"
onClick={() => {
window.inboxStore?.dispatch(openConversationExternal(id));
}}
onContextMenu={(e: any) => {
contextMenu.show({
id: triggerId,
event: e,
});
}}
style={style}
className={classNames( className={classNames(
'module-conversation-list-item', 'module-conversation-list-item__header__date',
unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null, unreadCount > 0 ? 'module-conversation-list-item__header__date--has-unread' : null
unreadCount > 0 && mentionedUs ? 'module-conversation-list-item--mentioned-us' : null,
isSelected ? 'module-conversation-list-item--is-selected' : null,
isBlocked ? 'module-conversation-list-item--is-blocked' : null
)} )}
> >
{this.renderAvatar()} {
<div className="module-conversation-list-item__content"> <Timestamp
{this.renderHeader()} timestamp={activeAt}
{this.renderMessage()} extended={false}
</div> isConversationListItem={true}
theme={theme}
/>
}
</div> </div>
<Portal> }
<ConversationListItemContextMenu {...this.getMenuProps(triggerId)} /> </div>
</Portal> );
</div> };
);
export interface ConversationListMessageProps {
lastMessage: LastMessageType;
isTyping: boolean;
unreadCount: number;
}
export const ConversationListItemMessage = (props: any) => {
const { lastMessage, isTyping, unreadCount } = props;
const theme = useTheme();
if (!lastMessage && !isTyping) {
return null;
} }
const text = lastMessage && lastMessage.text ? lastMessage.text : '';
private getMenuProps(triggerId: string): PropsContextConversationItem { if (isEmpty(text)) {
return { return null;
triggerId,
...this.props,
};
} }
private renderUser() { return (
const { name, phoneNumber, profileName, isMe } = this.props; <div className="module-conversation-list-item__message">
<div
className={classNames(
'module-conversation-list-item__message__text',
unreadCount > 0 ? 'module-conversation-list-item__message__text--has-unread' : null
)}
>
{isTyping ? (
<TypingAnimation />
) : (
<MessageBody isGroup={true} text={text} disableJumbomoji={true} disableLinks={true} />
)}
</div>
{lastMessage && lastMessage.status ? (
<OutgoingMessageStatus
status={lastMessage.status}
iconColor={theme.colors.textColorSubtle}
theme={theme}
/>
) : null}
</div>
);
};
const shortenedPubkey = PubKey.shorten(phoneNumber); export interface ConversationListItemUserProps {
name?: string;
phoneNumber: string;
profileName?: string;
isMe: boolean;
}
const displayedPubkey = profileName ? shortenedPubkey : phoneNumber; export const ConversationListItemUser = (props: ConversationListItemUserProps) => {
const displayName = isMe ? window.i18n('noteToSelf') : profileName; const { name, phoneNumber, profileName, isMe } = props;
let shouldShowPubkey = false; const shortenedPubkey = PubKey.shorten(phoneNumber);
if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) {
shouldShowPubkey = true;
}
return ( const displayedPubkey = profileName ? shortenedPubkey : phoneNumber;
<div className="module-conversation__user"> const displayName = isMe ? window.i18n('noteToSelf') : profileName;
<ContactName
phoneNumber={displayedPubkey} let shouldShowPubkey = false;
name={name} if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) {
profileName={displayName} shouldShowPubkey = true;
module="module-conversation__user"
boldProfileName={true}
shouldShowPubkey={shouldShowPubkey}
/>
</div>
);
} }
}
return (
<div className="module-conversation__user">
<ContactName
phoneNumber={displayedPubkey}
name={name}
profileName={displayName}
module="module-conversation__user"
boldProfileName={true}
shouldShowPubkey={shouldShowPubkey}
/>
</div>
);
};
export const ConversationListItemWithDetails = usingClosedConversationDetails( export const ConversationListItemWithDetails = usingClosedConversationDetails(
withTheme(ConversationListItem) withTheme(ConversationListItem)

@ -3,7 +3,7 @@ import React from 'react';
import { NotificationForConvoOption, TimerOption } from '../../conversation/ConversationHeader'; import { NotificationForConvoOption, TimerOption } from '../../conversation/ConversationHeader';
import { Item, Submenu } from 'react-contexify'; import { Item, Submenu } from 'react-contexify';
import { ConversationNotificationSettingType } from '../../../models/conversation'; import { ConversationNotificationSettingType } from '../../../models/conversation';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { changeNickNameModal, updateConfirmModal } from '../../../state/ducks/modalDialog'; import { changeNickNameModal, updateConfirmModal } from '../../../state/ducks/modalDialog';
import { getConversationController } from '../../../session/conversations'; import { getConversationController } from '../../../session/conversations';
import { import {

Loading…
Cancel
Save