improve performamce by memoizing avatar and menus
This commit is contained in:
parent
87a8385629
commit
97b9156562
|
@ -4,6 +4,7 @@ import classNames from 'classnames';
|
|||
import { AvatarPlaceHolder, ClosedGroupAvatar } from './AvatarPlaceHolder';
|
||||
import { ConversationAvatar } from './session/usingClosedConversationDetails';
|
||||
import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch';
|
||||
import _ from 'underscore';
|
||||
|
||||
export enum AvatarSize {
|
||||
XS = 28,
|
||||
|
@ -84,7 +85,7 @@ const AvatarImage = (props: {
|
|||
);
|
||||
};
|
||||
|
||||
export const Avatar = (props: Props) => {
|
||||
const AvatarInner = (props: Props) => {
|
||||
const { avatarPath, base64Data, size, memberAvatars, name } = props;
|
||||
const [imageBroken, setImageBroken] = useState(false);
|
||||
// contentType is not important
|
||||
|
@ -130,3 +131,5 @@ export const Avatar = (props: Props) => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Avatar = React.memo(AvatarInner, _.isEqual);
|
||||
|
|
|
@ -13,15 +13,17 @@ import {
|
|||
ConversationAvatar,
|
||||
usingClosedConversationDetails,
|
||||
} from './session/usingClosedConversationDetails';
|
||||
import {
|
||||
ConversationListItemContextMenu,
|
||||
PropsContextConversationItem,
|
||||
} from './session/menu/ConversationListItemContextMenu';
|
||||
import { MemoConversationListItemContextMenu } from './session/menu/ConversationListItemContextMenu';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { OutgoingMessageStatus } from './conversation/message/OutgoingMessageStatus';
|
||||
import { DefaultTheme, withTheme } from 'styled-components';
|
||||
import { DefaultTheme, useTheme } from 'styled-components';
|
||||
import { PubKey } from '../session/types';
|
||||
import { ConversationType, openConversationExternal } from '../state/ducks/conversations';
|
||||
import {
|
||||
ConversationType,
|
||||
LastMessageType,
|
||||
openConversationExternal,
|
||||
} from '../state/ducks/conversations';
|
||||
import _ from 'underscore';
|
||||
|
||||
export interface ConversationListItemProps extends ConversationType {
|
||||
index?: number; // used to force a refresh when one conversation is removed on top of the list
|
||||
|
@ -30,7 +32,6 @@ export interface ConversationListItemProps extends ConversationType {
|
|||
|
||||
type PropsHousekeeping = {
|
||||
style?: Object;
|
||||
theme: DefaultTheme;
|
||||
};
|
||||
|
||||
type Props = ConversationListItemProps & PropsHousekeeping;
|
||||
|
@ -39,184 +40,234 @@ const Portal = ({ children }: { children: any }) => {
|
|||
return createPortal(children, document.querySelector('.inbox.index') as Element);
|
||||
};
|
||||
|
||||
class ConversationListItem extends React.PureComponent<Props> {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
const AvatarItem = (props: {
|
||||
avatarPath?: string;
|
||||
phoneNumber: string;
|
||||
memberAvatars?: Array<ConversationAvatar>;
|
||||
name?: string;
|
||||
profileName?: string;
|
||||
}) => {
|
||||
const { avatarPath, name, phoneNumber, profileName, memberAvatars } = props;
|
||||
|
||||
const userName = name || profileName || phoneNumber;
|
||||
|
||||
return (
|
||||
<div className="module-conversation-list-item__avatar-container">
|
||||
<Avatar
|
||||
avatarPath={avatarPath}
|
||||
name={userName}
|
||||
size={AvatarSize.S}
|
||||
memberAvatars={memberAvatars}
|
||||
pubkey={phoneNumber}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const UserItem = (props: {
|
||||
name?: string;
|
||||
profileName?: string;
|
||||
isMe: boolean;
|
||||
phoneNumber: string;
|
||||
}) => {
|
||||
const { name, phoneNumber, profileName, isMe } = props;
|
||||
|
||||
const shortenedPubkey = PubKey.shorten(phoneNumber);
|
||||
|
||||
const displayedPubkey = profileName ? shortenedPubkey : phoneNumber;
|
||||
const displayName = isMe ? window.i18n('noteToSelf') : profileName;
|
||||
|
||||
let shouldShowPubkey = false;
|
||||
if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) {
|
||||
shouldShowPubkey = true;
|
||||
}
|
||||
|
||||
public renderAvatar() {
|
||||
const { avatarPath, name, phoneNumber, profileName, memberAvatars } = this.props;
|
||||
return (
|
||||
<div className="module-conversation__user">
|
||||
<ContactName
|
||||
phoneNumber={displayedPubkey}
|
||||
name={name}
|
||||
profileName={displayName}
|
||||
module="module-conversation__user"
|
||||
boldProfileName={true}
|
||||
shouldShowPubkey={shouldShowPubkey}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const userName = name || profileName || phoneNumber;
|
||||
const MessageItem = (props: {
|
||||
isTyping: boolean;
|
||||
lastMessage?: LastMessageType;
|
||||
unreadCount: number;
|
||||
}) => {
|
||||
const { lastMessage, isTyping, unreadCount } = props;
|
||||
|
||||
return (
|
||||
<div className="module-conversation-list-item__avatar-container">
|
||||
<Avatar
|
||||
const theme = useTheme();
|
||||
|
||||
if (!lastMessage && !isTyping) {
|
||||
return null;
|
||||
}
|
||||
const text = lastMessage && lastMessage.text ? lastMessage.text : '';
|
||||
|
||||
if (isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<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}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const HeaderItem = (props: {
|
||||
unreadCount: number;
|
||||
isMe: boolean;
|
||||
mentionedUs: boolean;
|
||||
activeAt?: number;
|
||||
name?: string;
|
||||
profileName?: string;
|
||||
phoneNumber: string;
|
||||
}) => {
|
||||
const { unreadCount, mentionedUs, activeAt, isMe, phoneNumber, profileName, name } = props;
|
||||
|
||||
let atSymbol = null;
|
||||
let unreadCountDiv = null;
|
||||
if (unreadCount > 0) {
|
||||
atSymbol = mentionedUs ? <p className="at-symbol">@</p> : null;
|
||||
unreadCountDiv = <p className="module-conversation-list-item__unread-count">{unreadCount}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<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
|
||||
)}
|
||||
>
|
||||
<UserItem isMe={isMe} phoneNumber={phoneNumber} name={name} profileName={profileName} />
|
||||
</div>
|
||||
{unreadCountDiv}
|
||||
{atSymbol}
|
||||
{
|
||||
<div
|
||||
className={classNames(
|
||||
'module-conversation-list-item__header__date',
|
||||
unreadCount > 0 ? 'module-conversation-list-item__header__date--has-unread' : null
|
||||
)}
|
||||
>
|
||||
{<Timestamp timestamp={activeAt} extended={false} isConversationListItem={true} />}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ConversationListItem = (props: Props) => {
|
||||
console.warn('ConversationListItem', props.id.substr(-1), ': ', props);
|
||||
const {
|
||||
activeAt,
|
||||
phoneNumber,
|
||||
unreadCount,
|
||||
id,
|
||||
isSelected,
|
||||
isBlocked,
|
||||
style,
|
||||
mentionedUs,
|
||||
isMe,
|
||||
name,
|
||||
profileName,
|
||||
memberAvatars,
|
||||
isTyping,
|
||||
lastMessage,
|
||||
hasNickname,
|
||||
isKickedFromGroup,
|
||||
left,
|
||||
type,
|
||||
isPublic,
|
||||
avatarPath,
|
||||
} = props;
|
||||
const triggerId = `conversation-item-${phoneNumber}-ctxmenu`;
|
||||
const key = `conversation-item-${phoneNumber}`;
|
||||
|
||||
return (
|
||||
<div key={key}>
|
||||
<div
|
||||
role="button"
|
||||
onClick={() => {
|
||||
window.inboxStore?.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
|
||||
)}
|
||||
>
|
||||
<AvatarItem
|
||||
phoneNumber={phoneNumber}
|
||||
avatarPath={avatarPath}
|
||||
name={userName}
|
||||
size={AvatarSize.S}
|
||||
memberAvatars={memberAvatars}
|
||||
pubkey={phoneNumber}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderHeader() {
|
||||
const { unreadCount, mentionedUs, activeAt } = this.props;
|
||||
|
||||
let atSymbol = null;
|
||||
let unreadCountDiv = null;
|
||||
if (unreadCount > 0) {
|
||||
atSymbol = mentionedUs ? <p className="at-symbol">@</p> : null;
|
||||
unreadCountDiv = <p className="module-conversation-list-item__unread-count">{unreadCount}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<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
|
||||
)}
|
||||
>
|
||||
{this.renderUser()}
|
||||
</div>
|
||||
{unreadCountDiv}
|
||||
{atSymbol}
|
||||
{
|
||||
<div
|
||||
className={classNames(
|
||||
'module-conversation-list-item__header__date',
|
||||
unreadCount > 0 ? 'module-conversation-list-item__header__date--has-unread' : null
|
||||
)}
|
||||
>
|
||||
{
|
||||
<Timestamp
|
||||
timestamp={activeAt}
|
||||
extended={false}
|
||||
isConversationListItem={true}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderMessage() {
|
||||
const { lastMessage, isTyping, unreadCount } = this.props;
|
||||
|
||||
if (!lastMessage && !isTyping) {
|
||||
return null;
|
||||
}
|
||||
const text = lastMessage && lastMessage.text ? lastMessage.text : '';
|
||||
|
||||
if (isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<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={this.props.theme.colors.textColorSubtle}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { phoneNumber, unreadCount, id, isSelected, isBlocked, style, mentionedUs } = this.props;
|
||||
const triggerId = `conversation-item-${phoneNumber}-ctxmenu`;
|
||||
const key = `conversation-item-${phoneNumber}`;
|
||||
|
||||
return (
|
||||
<div key={key}>
|
||||
<div
|
||||
role="button"
|
||||
onClick={() => {
|
||||
window.inboxStore?.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
|
||||
)}
|
||||
>
|
||||
{this.renderAvatar()}
|
||||
<div className="module-conversation-list-item__content">
|
||||
{this.renderHeader()}
|
||||
{this.renderMessage()}
|
||||
</div>
|
||||
</div>
|
||||
<Portal>
|
||||
<ConversationListItemContextMenu {...this.getMenuProps(triggerId)} />
|
||||
</Portal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getMenuProps(triggerId: string): PropsContextConversationItem {
|
||||
return {
|
||||
triggerId,
|
||||
...this.props,
|
||||
};
|
||||
}
|
||||
|
||||
private renderUser() {
|
||||
const { name, phoneNumber, profileName, isMe } = this.props;
|
||||
|
||||
const shortenedPubkey = PubKey.shorten(phoneNumber);
|
||||
|
||||
const displayedPubkey = profileName ? shortenedPubkey : phoneNumber;
|
||||
const displayName = isMe ? window.i18n('noteToSelf') : profileName;
|
||||
|
||||
let shouldShowPubkey = false;
|
||||
if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) {
|
||||
shouldShowPubkey = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="module-conversation__user">
|
||||
<ContactName
|
||||
phoneNumber={displayedPubkey}
|
||||
profileName={profileName}
|
||||
name={name}
|
||||
profileName={displayName}
|
||||
module="module-conversation__user"
|
||||
boldProfileName={true}
|
||||
shouldShowPubkey={shouldShowPubkey}
|
||||
/>
|
||||
<div className="module-conversation-list-item__content">
|
||||
<HeaderItem
|
||||
mentionedUs={mentionedUs}
|
||||
unreadCount={unreadCount}
|
||||
activeAt={activeAt}
|
||||
isMe={isMe}
|
||||
phoneNumber={phoneNumber}
|
||||
name={name}
|
||||
profileName={profileName}
|
||||
/>
|
||||
<MessageItem isTyping={isTyping} unreadCount={unreadCount} lastMessage={lastMessage} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
<Portal>
|
||||
<MemoConversationListItemContextMenu
|
||||
triggerId={triggerId}
|
||||
conversationId={id}
|
||||
hasNickname={hasNickname}
|
||||
isBlocked={isBlocked}
|
||||
isKickedFromGroup={isKickedFromGroup}
|
||||
isMe={isMe}
|
||||
isPublic={isPublic}
|
||||
left={left}
|
||||
type={type}
|
||||
/>
|
||||
</Portal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ConversationListItemWithDetails = usingClosedConversationDetails(
|
||||
withTheme(ConversationListItem)
|
||||
export const MemoConversationListItemWithDetails = usingClosedConversationDetails(
|
||||
React.memo(ConversationListItem, _.isEqual)
|
||||
);
|
||||
|
|
|
@ -108,7 +108,7 @@ class MessageSearchResultInner extends React.PureComponent<Props> {
|
|||
<div className="module-message-search-result__header">
|
||||
{this.renderFrom()}
|
||||
<div className="module-message-search-result__header__timestamp">
|
||||
<Timestamp timestamp={receivedAt} theme={this.props.theme} />
|
||||
<Timestamp timestamp={receivedAt} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="module-message-search-result__body">
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import React from 'react';
|
||||
import { PropsForSearchResults } from '../state/ducks/conversations';
|
||||
import { ConversationListItemProps, ConversationListItemWithDetails } from './ConversationListItem';
|
||||
import {
|
||||
ConversationListItemProps,
|
||||
MemoConversationListItemWithDetails,
|
||||
} from './ConversationListItem';
|
||||
import { MessageSearchResult } from './MessageSearchResult';
|
||||
|
||||
export type SearchResultsProps = {
|
||||
|
@ -46,7 +49,7 @@ export class SearchResults extends React.Component<Props> {
|
|||
{window.i18n('conversationsHeader')}
|
||||
</div>
|
||||
{conversations.map(conversation => (
|
||||
<ConversationListItemWithDetails
|
||||
<MemoConversationListItemWithDetails
|
||||
key={conversation.phoneNumber}
|
||||
{...conversation}
|
||||
onClick={openConversationExternal}
|
||||
|
@ -82,7 +85,7 @@ export class SearchResults extends React.Component<Props> {
|
|||
<div className="module-search-results__contacts">
|
||||
<div className="module-search-results__contacts-header">{header}</div>
|
||||
{items.map(contact => (
|
||||
<ConversationListItemWithDetails
|
||||
<MemoConversationListItemWithDetails
|
||||
key={contact.phoneNumber}
|
||||
{...contact}
|
||||
onClick={openConversationExternal}
|
||||
|
|
|
@ -9,12 +9,9 @@ import {
|
|||
ConversationAvatar,
|
||||
usingClosedConversationDetails,
|
||||
} from '../session/usingClosedConversationDetails';
|
||||
import {
|
||||
ConversationHeaderMenu,
|
||||
PropsConversationHeaderMenu,
|
||||
} from '../session/menu/ConversationHeaderMenu';
|
||||
import { MemoConversationHeaderMenu } from '../session/menu/ConversationHeaderMenu';
|
||||
import { contextMenu } from 'react-contexify';
|
||||
import { DefaultTheme, withTheme } from 'styled-components';
|
||||
import { useTheme } from 'styled-components';
|
||||
import { ConversationNotificationSettingType } from '../../models/conversation';
|
||||
import autoBind from 'auto-bind';
|
||||
|
||||
|
@ -54,7 +51,7 @@ interface Props {
|
|||
showBackButton: boolean;
|
||||
notificationForConvo: Array<NotificationForConvoOption>;
|
||||
currentNotificationSetting: ConversationNotificationSettingType;
|
||||
hasNickname?: boolean;
|
||||
hasNickname: boolean;
|
||||
|
||||
isBlocked: boolean;
|
||||
|
||||
|
@ -68,9 +65,132 @@ interface Props {
|
|||
onGoBack: () => void;
|
||||
|
||||
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
|
||||
theme: DefaultTheme;
|
||||
}
|
||||
|
||||
const SelectionOverlay = (props: {
|
||||
onDeleteSelectedMessages: () => void;
|
||||
onCloseOverlay: () => void;
|
||||
isPublic: boolean;
|
||||
}) => {
|
||||
const { onDeleteSelectedMessages, onCloseOverlay, isPublic } = props;
|
||||
const { i18n } = window;
|
||||
const theme = useTheme();
|
||||
|
||||
const isServerDeletable = isPublic;
|
||||
const deleteMessageButtonText = i18n(isServerDeletable ? 'deleteForEveryone' : 'delete');
|
||||
|
||||
return (
|
||||
<div className="message-selection-overlay">
|
||||
<div className="close-button">
|
||||
<SessionIconButton
|
||||
iconType={SessionIconType.Exit}
|
||||
iconSize={SessionIconSize.Medium}
|
||||
onClick={onCloseOverlay}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="button-group">
|
||||
<SessionButton
|
||||
buttonType={SessionButtonType.Default}
|
||||
buttonColor={SessionButtonColor.Danger}
|
||||
text={deleteMessageButtonText}
|
||||
onClick={onDeleteSelectedMessages}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TripleDotsMenu = (props: { triggerId: string; showBackButton: boolean }) => {
|
||||
const { showBackButton } = props;
|
||||
const theme = useTheme();
|
||||
if (showBackButton) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
onClick={(e: any) => {
|
||||
contextMenu.show({
|
||||
id: props.triggerId,
|
||||
event: e,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SessionIconButton
|
||||
iconType={SessionIconType.Ellipses}
|
||||
iconSize={SessionIconSize.Medium}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ExpirationLength = (props: { expirationSettingName?: string }) => {
|
||||
const { expirationSettingName } = props;
|
||||
|
||||
if (!expirationSettingName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="module-conversation-header__expiration">
|
||||
<div className="module-conversation-header__expiration__clock-icon" />
|
||||
<div className="module-conversation-header__expiration__setting">{expirationSettingName}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AvatarHeader = (props: {
|
||||
avatarPath?: string;
|
||||
memberAvatars?: Array<ConversationAvatar>;
|
||||
name?: string;
|
||||
phoneNumber: string;
|
||||
profileName?: string;
|
||||
showBackButton: boolean;
|
||||
onAvatarClick?: (pubkey: string) => void;
|
||||
}) => {
|
||||
const { avatarPath, memberAvatars, name, phoneNumber, profileName } = props;
|
||||
const userName = name || profileName || phoneNumber;
|
||||
|
||||
return (
|
||||
<span className="module-conversation-header__avatar">
|
||||
<Avatar
|
||||
avatarPath={avatarPath}
|
||||
name={userName}
|
||||
size={AvatarSize.S}
|
||||
onAvatarClick={() => {
|
||||
// do not allow right panel to appear if another button is shown on the SessionConversation
|
||||
if (props.onAvatarClick && !props.showBackButton) {
|
||||
props.onAvatarClick(phoneNumber);
|
||||
}
|
||||
}}
|
||||
memberAvatars={memberAvatars}
|
||||
pubkey={phoneNumber}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const BackButton = (props: { onGoBack: () => void; showBackButton: boolean }) => {
|
||||
const { onGoBack, showBackButton } = props;
|
||||
const theme = useTheme();
|
||||
if (!showBackButton) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SessionIconButton
|
||||
iconType={SessionIconType.Chevron}
|
||||
iconSize={SessionIconSize.Large}
|
||||
iconRotation={90}
|
||||
onClick={onGoBack}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
class ConversationHeaderInner extends React.Component<Props> {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
@ -78,24 +198,6 @@ class ConversationHeaderInner extends React.Component<Props> {
|
|||
autoBind(this);
|
||||
}
|
||||
|
||||
public renderBackButton() {
|
||||
const { onGoBack, showBackButton } = this.props;
|
||||
|
||||
if (!showBackButton) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SessionIconButton
|
||||
iconType={SessionIconType.Chevron}
|
||||
iconSize={SessionIconSize.Large}
|
||||
iconRotation={90}
|
||||
onClick={onGoBack}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public renderTitle() {
|
||||
const {
|
||||
phoneNumber,
|
||||
|
@ -147,146 +249,65 @@ class ConversationHeaderInner extends React.Component<Props> {
|
|||
);
|
||||
}
|
||||
|
||||
public renderAvatar() {
|
||||
const { avatarPath, memberAvatars, name, phoneNumber, profileName } = this.props;
|
||||
|
||||
const userName = name || profileName || phoneNumber;
|
||||
|
||||
return (
|
||||
<span className="module-conversation-header__avatar">
|
||||
<Avatar
|
||||
avatarPath={avatarPath}
|
||||
name={userName}
|
||||
size={AvatarSize.S}
|
||||
onAvatarClick={() => {
|
||||
this.onAvatarClick(phoneNumber);
|
||||
}}
|
||||
memberAvatars={memberAvatars}
|
||||
pubkey={phoneNumber}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
public renderExpirationLength() {
|
||||
const { expirationSettingName } = this.props;
|
||||
|
||||
if (!expirationSettingName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="module-conversation-header__expiration">
|
||||
<div className="module-conversation-header__expiration__clock-icon" />
|
||||
<div className="module-conversation-header__expiration__setting">
|
||||
{expirationSettingName}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderSelectionOverlay() {
|
||||
const { onDeleteSelectedMessages, onCloseOverlay, isPublic } = this.props;
|
||||
const { i18n } = window;
|
||||
|
||||
const isServerDeletable = isPublic;
|
||||
const deleteMessageButtonText = i18n(isServerDeletable ? 'deleteForEveryone' : 'delete');
|
||||
|
||||
return (
|
||||
<div className="message-selection-overlay">
|
||||
<div className="close-button">
|
||||
<SessionIconButton
|
||||
iconType={SessionIconType.Exit}
|
||||
iconSize={SessionIconSize.Medium}
|
||||
onClick={onCloseOverlay}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="button-group">
|
||||
<SessionButton
|
||||
buttonType={SessionButtonType.Default}
|
||||
buttonColor={SessionButtonColor.Danger}
|
||||
text={deleteMessageButtonText}
|
||||
onClick={onDeleteSelectedMessages}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { isKickedFromGroup, selectionMode } = this.props;
|
||||
const { isKickedFromGroup, selectionMode, expirationSettingName, showBackButton } = this.props;
|
||||
const triggerId = 'conversation-header';
|
||||
console.warn('conversation header render', this.props);
|
||||
|
||||
return (
|
||||
<div className="module-conversation-header">
|
||||
<div className="conversation-header--items-wrapper">
|
||||
{this.renderBackButton()}
|
||||
<BackButton onGoBack={this.props.onGoBack} showBackButton={this.props.showBackButton} />
|
||||
|
||||
<div className="module-conversation-header__title-container">
|
||||
<div className="module-conversation-header__title-flex">
|
||||
{this.renderTripleDotsMenu(triggerId)}
|
||||
<TripleDotsMenu triggerId={triggerId} showBackButton={showBackButton} />
|
||||
{this.renderTitle()}
|
||||
</div>
|
||||
</div>
|
||||
{!isKickedFromGroup && this.renderExpirationLength()}
|
||||
{!isKickedFromGroup && <ExpirationLength expirationSettingName={expirationSettingName} />}
|
||||
|
||||
{!selectionMode && this.renderAvatar()}
|
||||
{!selectionMode && (
|
||||
<AvatarHeader
|
||||
onAvatarClick={this.props.onAvatarClick}
|
||||
phoneNumber={this.props.phoneNumber}
|
||||
showBackButton={this.props.showBackButton}
|
||||
avatarPath={this.props.avatarPath}
|
||||
memberAvatars={this.props.memberAvatars}
|
||||
name={this.props.name}
|
||||
profileName={this.props.profileName}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ConversationHeaderMenu {...this.getHeaderMenuProps(triggerId)} />
|
||||
<MemoConversationHeaderMenu
|
||||
conversationId={this.props.id}
|
||||
triggerId={triggerId}
|
||||
isMe={this.props.isMe}
|
||||
isPublic={this.props.isPublic}
|
||||
isGroup={this.props.isGroup}
|
||||
isKickedFromGroup={isKickedFromGroup}
|
||||
isAdmin={this.props.isAdmin}
|
||||
isBlocked={this.props.isBlocked}
|
||||
isPrivate={this.props.isPrivate}
|
||||
left={this.props.left}
|
||||
hasNickname={this.props.hasNickname}
|
||||
notificationForConvo={this.props.notificationForConvo}
|
||||
currentNotificationSetting={this.props.currentNotificationSetting}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{selectionMode && this.renderSelectionOverlay()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public onAvatarClick(userPubKey: string) {
|
||||
// do not allow right panel to appear if another button is shown on the SessionConversation
|
||||
if (this.props.onAvatarClick && !this.props.showBackButton) {
|
||||
this.props.onAvatarClick(userPubKey);
|
||||
}
|
||||
}
|
||||
|
||||
public highlightMessageSearch() {
|
||||
// This is a temporary fix. In future we want to search
|
||||
// messages in the current conversation
|
||||
($('.session-search-input input') as any).focus();
|
||||
}
|
||||
|
||||
private getHeaderMenuProps(triggerId: string): PropsConversationHeaderMenu {
|
||||
return {
|
||||
triggerId,
|
||||
conversationId: this.props.id,
|
||||
...this.props,
|
||||
};
|
||||
}
|
||||
|
||||
private renderTripleDotsMenu(triggerId: string) {
|
||||
const { showBackButton } = this.props;
|
||||
if (showBackButton) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
onClick={(e: any) => {
|
||||
contextMenu.show({
|
||||
id: triggerId,
|
||||
event: e,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SessionIconButton
|
||||
iconType={SessionIconType.Ellipses}
|
||||
iconSize={SessionIconSize.Medium}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
{selectionMode && (
|
||||
<SelectionOverlay
|
||||
isPublic={this.props.isPublic}
|
||||
onCloseOverlay={this.props.onCloseOverlay}
|
||||
onDeleteSelectedMessages={this.props.onDeleteSelectedMessages}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ConversationHeaderWithDetails = usingClosedConversationDetails(
|
||||
withTheme(ConversationHeaderInner)
|
||||
ConversationHeaderInner
|
||||
);
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import React, { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import moment from 'moment';
|
||||
|
||||
import { formatRelativeTime } from '../../util/formatRelativeTime';
|
||||
import { useInterval } from '../../hooks/useInterval';
|
||||
import styled, { DefaultTheme } from 'styled-components';
|
||||
import styled, { useTheme } from 'styled-components';
|
||||
import { OpacityMetadataComponent } from './message/MessageMetadata';
|
||||
|
||||
type Props = {
|
||||
|
@ -13,7 +12,6 @@ type Props = {
|
|||
module?: string;
|
||||
withImageNoCaption?: boolean;
|
||||
isConversationListItem?: boolean;
|
||||
theme: DefaultTheme;
|
||||
};
|
||||
|
||||
const UPDATE_FREQUENCY = 60 * 1000;
|
||||
|
@ -52,6 +50,8 @@ export const Timestamp = (props: Props) => {
|
|||
setLastUpdated(Date.now());
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
useInterval(update, UPDATE_FREQUENCY);
|
||||
|
||||
const { module, timestamp, withImageNoCaption, extended } = props;
|
||||
|
@ -79,7 +79,7 @@ export const Timestamp = (props: Props) => {
|
|||
dateString = dateString.replace('minutes', 'mins').replace('minute', 'min');
|
||||
}
|
||||
|
||||
const timestampColor = withImageNoCaption ? 'white' : props.theme.colors.textColor;
|
||||
const timestampColor = withImageNoCaption ? 'white' : theme.colors.textColor;
|
||||
const title = moment(timestamp).format('llll');
|
||||
if (props.isConversationListItem) {
|
||||
return (
|
||||
|
|
|
@ -91,7 +91,6 @@ export const MessageMetadata = (props: Props) => {
|
|||
extended={true}
|
||||
withImageNoCaption={withImageNoCaption}
|
||||
isConversationListItem={false}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
<MetadataBadges
|
||||
|
@ -115,7 +114,6 @@ export const MessageMetadata = (props: Props) => {
|
|||
{showStatus ? (
|
||||
<OutgoingMessageStatus
|
||||
iconColor={messageStatusColor}
|
||||
theme={theme}
|
||||
status={status}
|
||||
// do not show the error status, another component is shown on the right of the message itself here
|
||||
isInMessageView={true}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import styled, { DefaultTheme } from 'styled-components';
|
||||
import styled, { DefaultTheme, useTheme } from 'styled-components';
|
||||
import { MessageDeliveryStatus } from '../../../models/messageType';
|
||||
import { SessionIcon, SessionIconSize, SessionIconType } from '../../session/icon';
|
||||
import { OpacityMetadataComponent } from './MessageMetadata';
|
||||
|
@ -10,13 +10,12 @@ const MessageStatusSendingContainer = styled(props => <OpacityMetadataComponent
|
|||
margin-inline-start: 5px;
|
||||
`;
|
||||
|
||||
const MessageStatusSending = (props: { theme: DefaultTheme; iconColor: string }) => {
|
||||
const MessageStatusSending = (props: { iconColor: string }) => {
|
||||
return (
|
||||
<MessageStatusSendingContainer>
|
||||
<SessionIcon
|
||||
rotateDuration={2}
|
||||
iconColor={props.iconColor}
|
||||
theme={props.theme}
|
||||
iconType={SessionIconType.Sending}
|
||||
iconSize={SessionIconSize.Tiny}
|
||||
/>
|
||||
|
@ -24,12 +23,11 @@ const MessageStatusSending = (props: { theme: DefaultTheme; iconColor: string })
|
|||
);
|
||||
};
|
||||
|
||||
const MessageStatusSent = (props: { theme: DefaultTheme; iconColor: string }) => {
|
||||
const MessageStatusSent = (props: { iconColor: string }) => {
|
||||
return (
|
||||
<MessageStatusSendingContainer>
|
||||
<SessionIcon
|
||||
iconColor={props.iconColor}
|
||||
theme={props.theme}
|
||||
iconType={SessionIconType.CircleCheck}
|
||||
iconSize={SessionIconSize.Tiny}
|
||||
/>
|
||||
|
@ -37,12 +35,11 @@ const MessageStatusSent = (props: { theme: DefaultTheme; iconColor: string }) =>
|
|||
);
|
||||
};
|
||||
|
||||
const MessageStatusRead = (props: { theme: DefaultTheme; iconColor: string }) => {
|
||||
const MessageStatusRead = (props: { iconColor: string }) => {
|
||||
return (
|
||||
<MessageStatusSendingContainer>
|
||||
<SessionIcon
|
||||
iconColor={props.iconColor}
|
||||
theme={props.theme}
|
||||
iconType={SessionIconType.DoubleCheckCircleFilled}
|
||||
iconSize={SessionIconSize.Tiny}
|
||||
/>
|
||||
|
@ -50,12 +47,12 @@ const MessageStatusRead = (props: { theme: DefaultTheme; iconColor: string }) =>
|
|||
);
|
||||
};
|
||||
|
||||
const MessageStatusError = (props: { theme: DefaultTheme }) => {
|
||||
const MessageStatusError = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<MessageStatusSendingContainer>
|
||||
<SessionIcon
|
||||
iconColor={props.theme.colors.destructive}
|
||||
theme={props.theme}
|
||||
iconColor={theme.colors.destructive}
|
||||
iconType={SessionIconType.Error}
|
||||
iconSize={SessionIconSize.Tiny}
|
||||
/>
|
||||
|
@ -65,7 +62,6 @@ const MessageStatusError = (props: { theme: DefaultTheme }) => {
|
|||
|
||||
export const OutgoingMessageStatus = (props: {
|
||||
status?: MessageDeliveryStatus;
|
||||
theme: DefaultTheme;
|
||||
iconColor: string;
|
||||
isInMessageView?: boolean;
|
||||
}) => {
|
||||
|
@ -80,7 +76,7 @@ export const OutgoingMessageStatus = (props: {
|
|||
if (props.isInMessageView) {
|
||||
return null;
|
||||
}
|
||||
return <MessageStatusError {...props} />;
|
||||
return <MessageStatusError />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { ConversationListItemWithDetails } from '../ConversationListItem';
|
||||
import { MemoConversationListItemWithDetails } from '../ConversationListItem';
|
||||
import { RowRendererParamsType } from '../LeftPane';
|
||||
import { AutoSizer, List } from 'react-virtualized';
|
||||
import { ConversationType as ReduxConversationType } from '../../state/ducks/conversations';
|
||||
|
@ -38,7 +38,7 @@ export class LeftPaneContactSection extends React.Component<Props> {
|
|||
const item = directContacts[index];
|
||||
|
||||
return (
|
||||
<ConversationListItemWithDetails
|
||||
<MemoConversationListItemWithDetails
|
||||
key={item.id}
|
||||
style={style}
|
||||
{...item}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { AutoSizer, List } from 'react-virtualized';
|
|||
import { MainViewController } from '../MainViewController';
|
||||
import {
|
||||
ConversationListItemProps,
|
||||
ConversationListItemWithDetails,
|
||||
MemoConversationListItemWithDetails,
|
||||
} from '../ConversationListItem';
|
||||
import { ConversationType as ReduxConversationType } from '../../state/ducks/conversations';
|
||||
import { SearchResults, SearchResultsProps } from '../SearchResults';
|
||||
|
@ -90,7 +90,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
|
|||
const conversation = conversations[index];
|
||||
|
||||
return (
|
||||
<ConversationListItemWithDetails
|
||||
<MemoConversationListItemWithDetails
|
||||
key={key}
|
||||
style={style}
|
||||
{...conversation}
|
||||
|
|
|
@ -188,7 +188,7 @@ export const SessionIcon = (props: SessionIconProps) => {
|
|||
const iconDimensions = getIconDimensionFromIconSize(iconSize);
|
||||
const iconDef = icons[iconType];
|
||||
const ratio = iconDef?.ratio || 1;
|
||||
if (!theme) {
|
||||
if (!themeToUse) {
|
||||
window?.log?.error('Missing theme props in SessionIcon');
|
||||
}
|
||||
|
||||
|
|
|
@ -16,27 +16,27 @@ import {
|
|||
getRemoveModeratorsMenuItem,
|
||||
getUpdateGroupNameMenuItem,
|
||||
} from './Menu';
|
||||
import { NotificationForConvoOption, TimerOption } from '../../conversation/ConversationHeader';
|
||||
import { NotificationForConvoOption } from '../../conversation/ConversationHeader';
|
||||
import { ConversationNotificationSettingType } from '../../../models/conversation';
|
||||
import _ from 'lodash';
|
||||
|
||||
export type PropsConversationHeaderMenu = {
|
||||
conversationId: string;
|
||||
triggerId: string;
|
||||
isMe: boolean;
|
||||
isPublic?: boolean;
|
||||
isKickedFromGroup?: boolean;
|
||||
left?: boolean;
|
||||
isPublic: boolean;
|
||||
isKickedFromGroup: boolean;
|
||||
left: boolean;
|
||||
isGroup: boolean;
|
||||
isAdmin: boolean;
|
||||
notificationForConvo: Array<NotificationForConvoOption>;
|
||||
currentNotificationSetting: ConversationNotificationSettingType;
|
||||
isPrivate: boolean;
|
||||
isBlocked: boolean;
|
||||
theme: any;
|
||||
hasNickname?: boolean;
|
||||
hasNickname: boolean;
|
||||
};
|
||||
|
||||
export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
|
||||
const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
|
||||
const {
|
||||
conversationId,
|
||||
triggerId,
|
||||
|
@ -54,32 +54,35 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
|
|||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Menu id={triggerId} animation={animation.fade}>
|
||||
{getDisappearingMenuItem(isPublic, isKickedFromGroup, left, isBlocked, conversationId)}
|
||||
{getNotificationForConvoMenuItem(
|
||||
isKickedFromGroup,
|
||||
left,
|
||||
isBlocked,
|
||||
notificationForConvo,
|
||||
currentNotificationSetting,
|
||||
conversationId
|
||||
)}
|
||||
{getBlockMenuItem(isMe, isPrivate, isBlocked, conversationId)}
|
||||
<Menu id={triggerId} animation={animation.fade}>
|
||||
{getDisappearingMenuItem(isPublic, isKickedFromGroup, left, isBlocked, conversationId)}
|
||||
{getNotificationForConvoMenuItem(
|
||||
isKickedFromGroup,
|
||||
left,
|
||||
isBlocked,
|
||||
notificationForConvo,
|
||||
currentNotificationSetting,
|
||||
conversationId
|
||||
)}
|
||||
{getBlockMenuItem(isMe, isPrivate, isBlocked, conversationId)}
|
||||
|
||||
{getCopyMenuItem(isPublic, isGroup, conversationId)}
|
||||
{getMarkAllReadMenuItem(conversationId)}
|
||||
{getChangeNicknameMenuItem(isMe, isGroup, conversationId)}
|
||||
{getClearNicknameMenuItem(isMe, hasNickname, isGroup, conversationId)}
|
||||
{getDeleteMessagesMenuItem(isPublic, conversationId)}
|
||||
{getAddModeratorsMenuItem(isAdmin, isKickedFromGroup, conversationId)}
|
||||
{getRemoveModeratorsMenuItem(isAdmin, isKickedFromGroup, conversationId)}
|
||||
{getUpdateGroupNameMenuItem(isAdmin, isKickedFromGroup, left, conversationId)}
|
||||
{getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)}
|
||||
{/* TODO: add delete group */}
|
||||
{getInviteContactMenuItem(isGroup, isPublic, conversationId)}
|
||||
{getDeleteContactMenuItem(isMe, isGroup, isPublic, left, isKickedFromGroup, conversationId)}
|
||||
</Menu>
|
||||
</>
|
||||
{getCopyMenuItem(isPublic, isGroup, conversationId)}
|
||||
{getMarkAllReadMenuItem(conversationId)}
|
||||
{getChangeNicknameMenuItem(isMe, isGroup, conversationId)}
|
||||
{getClearNicknameMenuItem(isMe, hasNickname, isGroup, conversationId)}
|
||||
{getDeleteMessagesMenuItem(isPublic, conversationId)}
|
||||
{getAddModeratorsMenuItem(isAdmin, isKickedFromGroup, conversationId)}
|
||||
{getRemoveModeratorsMenuItem(isAdmin, isKickedFromGroup, conversationId)}
|
||||
{getUpdateGroupNameMenuItem(isAdmin, isKickedFromGroup, left, conversationId)}
|
||||
{getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)}
|
||||
{/* TODO: add delete group */}
|
||||
{getInviteContactMenuItem(isGroup, isPublic, conversationId)}
|
||||
{getDeleteContactMenuItem(isMe, isGroup, isPublic, left, isKickedFromGroup, conversationId)}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
function propsAreEqual(prev: PropsConversationHeaderMenu, next: PropsConversationHeaderMenu) {
|
||||
return _.isEqual(prev, next);
|
||||
}
|
||||
export const MemoConversationHeaderMenu = React.memo(ConversationHeaderMenu, propsAreEqual);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { animation, Menu } from 'react-contexify';
|
||||
import _ from 'underscore';
|
||||
import { ConversationTypeEnum } from '../../../models/conversation';
|
||||
|
||||
import {
|
||||
|
@ -15,21 +16,20 @@ import {
|
|||
} from './Menu';
|
||||
|
||||
export type PropsContextConversationItem = {
|
||||
id: string;
|
||||
conversationId: string;
|
||||
triggerId: string;
|
||||
type: ConversationTypeEnum;
|
||||
isMe: boolean;
|
||||
isPublic?: boolean;
|
||||
isBlocked?: boolean;
|
||||
hasNickname?: boolean;
|
||||
isKickedFromGroup?: boolean;
|
||||
left?: boolean;
|
||||
theme?: any;
|
||||
isPublic: boolean;
|
||||
isBlocked: boolean;
|
||||
hasNickname: boolean;
|
||||
isKickedFromGroup: boolean;
|
||||
left: boolean;
|
||||
};
|
||||
|
||||
export const ConversationListItemContextMenu = (props: PropsContextConversationItem) => {
|
||||
const ConversationListItemContextMenu = (props: PropsContextConversationItem) => {
|
||||
const {
|
||||
id: conversationId,
|
||||
conversationId,
|
||||
triggerId,
|
||||
isBlocked,
|
||||
isMe,
|
||||
|
@ -38,25 +38,29 @@ export const ConversationListItemContextMenu = (props: PropsContextConversationI
|
|||
type,
|
||||
left,
|
||||
isKickedFromGroup,
|
||||
theme,
|
||||
} = props;
|
||||
|
||||
const isGroup = type === 'group';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Menu id={triggerId} animation={animation.fade}>
|
||||
{getBlockMenuItem(isMe, type === ConversationTypeEnum.PRIVATE, isBlocked, conversationId)}
|
||||
{getCopyMenuItem(isPublic, isGroup, conversationId)}
|
||||
{getMarkAllReadMenuItem(conversationId)}
|
||||
{getChangeNicknameMenuItem(isMe, isGroup, conversationId)}
|
||||
{getClearNicknameMenuItem(isMe, hasNickname, isGroup, conversationId)}
|
||||
<Menu id={triggerId} animation={animation.fade}>
|
||||
{getBlockMenuItem(isMe, type === ConversationTypeEnum.PRIVATE, isBlocked, conversationId)}
|
||||
{getCopyMenuItem(isPublic, isGroup, conversationId)}
|
||||
{getMarkAllReadMenuItem(conversationId)}
|
||||
{getChangeNicknameMenuItem(isMe, isGroup, conversationId)}
|
||||
{getClearNicknameMenuItem(isMe, hasNickname, isGroup, conversationId)}
|
||||
|
||||
{getDeleteMessagesMenuItem(isPublic, conversationId)}
|
||||
{getInviteContactMenuItem(isGroup, isPublic, conversationId)}
|
||||
{getDeleteContactMenuItem(isMe, isGroup, isPublic, left, isKickedFromGroup, conversationId)}
|
||||
{getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)}
|
||||
</Menu>
|
||||
</>
|
||||
{getDeleteMessagesMenuItem(isPublic, conversationId)}
|
||||
{getInviteContactMenuItem(isGroup, isPublic, conversationId)}
|
||||
{getDeleteContactMenuItem(isMe, isGroup, isPublic, left, isKickedFromGroup, conversationId)}
|
||||
{getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
function propsAreEqual(prev: PropsContextConversationItem, next: PropsContextConversationItem) {
|
||||
return _.isEqual(prev, next);
|
||||
}
|
||||
export const MemoConversationListItemContextMenu = React.memo(
|
||||
ConversationListItemContextMenu,
|
||||
propsAreEqual
|
||||
);
|
||||
|
|
|
@ -41,10 +41,6 @@ function showNotificationConvo(
|
|||
return !left && !isKickedFromGroup && !isBlocked;
|
||||
}
|
||||
|
||||
function showMemberMenu(isPublic: boolean, isGroup: boolean): boolean {
|
||||
return !isPublic && isGroup;
|
||||
}
|
||||
|
||||
function showBlock(isMe: boolean, isPrivate: boolean): boolean {
|
||||
return !isMe && isPrivate;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export const PROTOCOLS = {
|
|||
// User Interface
|
||||
export const CONVERSATION = {
|
||||
DEFAULT_MEDIA_FETCH_COUNT: 50,
|
||||
DEFAULT_DOCUMENTS_FETCH_COUNT: 150,
|
||||
DEFAULT_DOCUMENTS_FETCH_COUNT: 100,
|
||||
DEFAULT_MESSAGE_FETCH_COUNT: 30,
|
||||
MAX_MESSAGE_FETCH_COUNT: 1000,
|
||||
// Maximum voice message duraton of 5 minutes
|
||||
|
|
|
@ -168,7 +168,7 @@ export interface ConversationType {
|
|||
id: string;
|
||||
name?: string;
|
||||
profileName?: string;
|
||||
hasNickname?: boolean;
|
||||
hasNickname: boolean;
|
||||
index?: number;
|
||||
|
||||
activeAt?: number;
|
||||
|
@ -176,7 +176,7 @@ export interface ConversationType {
|
|||
phoneNumber: string;
|
||||
type: ConversationTypeEnum;
|
||||
isMe: boolean;
|
||||
isPublic?: boolean;
|
||||
isPublic: boolean;
|
||||
unreadCount: number;
|
||||
mentionedUs: boolean;
|
||||
isSelected: boolean;
|
||||
|
|
|
@ -19,8 +19,8 @@ const timerOptionSlice = createSlice({
|
|||
name: 'timerOptions',
|
||||
initialState: initialTimerOptionsState,
|
||||
reducers: {
|
||||
updateTimerOptions: (_state, action: PayloadAction<TimerOptionsArray>) => {
|
||||
return { timerOptions: action.payload };
|
||||
updateTimerOptions: (state, action: PayloadAction<TimerOptionsArray>) => {
|
||||
return { ...state, timerOptions: action.payload };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -27,6 +27,8 @@ describe('state/selectors/conversations', () => {
|
|||
isBlocked: false,
|
||||
isKickedFromGroup: false,
|
||||
left: false,
|
||||
hasNickname: false,
|
||||
isPublic: false,
|
||||
},
|
||||
id2: {
|
||||
id: 'id2',
|
||||
|
@ -43,6 +45,8 @@ describe('state/selectors/conversations', () => {
|
|||
isBlocked: false,
|
||||
isKickedFromGroup: false,
|
||||
left: false,
|
||||
hasNickname: false,
|
||||
isPublic: false,
|
||||
},
|
||||
id3: {
|
||||
id: 'id3',
|
||||
|
@ -59,6 +63,8 @@ describe('state/selectors/conversations', () => {
|
|||
isBlocked: false,
|
||||
isKickedFromGroup: false,
|
||||
left: false,
|
||||
hasNickname: false,
|
||||
isPublic: false,
|
||||
},
|
||||
id4: {
|
||||
id: 'id4',
|
||||
|
@ -74,6 +80,8 @@ describe('state/selectors/conversations', () => {
|
|||
isBlocked: false,
|
||||
isKickedFromGroup: false,
|
||||
left: false,
|
||||
hasNickname: false,
|
||||
isPublic: false,
|
||||
},
|
||||
id5: {
|
||||
id: 'id5',
|
||||
|
@ -89,6 +97,8 @@ describe('state/selectors/conversations', () => {
|
|||
isBlocked: false,
|
||||
isKickedFromGroup: false,
|
||||
left: false,
|
||||
hasNickname: false,
|
||||
isPublic: false,
|
||||
},
|
||||
};
|
||||
const comparator = _getConversationComparator(i18n);
|
||||
|
|
Loading…
Reference in New Issue