mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
Merge branch 'clearnet' into message-requests
This commit is contained in:
commit
bef9058ffe
|
@ -192,27 +192,6 @@
|
|||
}
|
||||
});
|
||||
|
||||
Whisper.events.on('deleteLocalPublicMessages', async ({ messageServerIds, conversationId }) => {
|
||||
if (!Array.isArray(messageServerIds)) {
|
||||
return;
|
||||
}
|
||||
const messageIds = await window.Signal.Data.getMessageIdsFromServerIds(
|
||||
messageServerIds,
|
||||
conversationId
|
||||
);
|
||||
if (messageIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const conversation = window.getConversationController().get(conversationId);
|
||||
messageIds.forEach(id => {
|
||||
if (conversation) {
|
||||
conversation.removeMessage(id);
|
||||
}
|
||||
window.Signal.Data.removeMessage(id);
|
||||
});
|
||||
});
|
||||
|
||||
function manageExpiringData() {
|
||||
window.Signal.Data.cleanSeenMessages();
|
||||
window.Signal.Data.cleanLastHashes();
|
||||
|
|
|
@ -2,9 +2,10 @@ import React, { useCallback, useState } from 'react';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import { AvatarPlaceHolder, ClosedGroupAvatar } from './AvatarPlaceHolder';
|
||||
import { ConversationAvatar } from './session/usingClosedConversationDetails';
|
||||
import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch';
|
||||
import _ from 'underscore';
|
||||
import { useMembersAvatars } from '../hooks/useMembersAvatars';
|
||||
import { useAvatarPath, useConversationUsername } from '../hooks/useParamSelector';
|
||||
|
||||
export enum AvatarSize {
|
||||
XS = 28,
|
||||
|
@ -16,19 +17,18 @@ export enum AvatarSize {
|
|||
}
|
||||
|
||||
type Props = {
|
||||
avatarPath?: string | null;
|
||||
name?: string; // display name, profileName or pubkey, whatever is set first
|
||||
forcedAvatarPath?: string | null;
|
||||
forcedName?: string;
|
||||
pubkey?: string;
|
||||
size: AvatarSize;
|
||||
base64Data?: string; // if this is not empty, it will be used to render the avatar with base64 encoded data
|
||||
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
|
||||
onAvatarClick?: () => void;
|
||||
dataTestId?: string;
|
||||
};
|
||||
|
||||
const Identicon = (props: Props) => {
|
||||
const { size, name, pubkey } = props;
|
||||
const userName = name || '0';
|
||||
const { size, forcedName, pubkey } = props;
|
||||
const userName = forcedName || '0';
|
||||
|
||||
return (
|
||||
<AvatarPlaceHolder
|
||||
|
@ -41,26 +41,20 @@ const Identicon = (props: Props) => {
|
|||
);
|
||||
};
|
||||
|
||||
const NoImage = (props: {
|
||||
memberAvatars?: Array<ConversationAvatar>;
|
||||
name?: string;
|
||||
pubkey?: string;
|
||||
size: AvatarSize;
|
||||
onAvatarClick?: () => void;
|
||||
}) => {
|
||||
const { name, memberAvatars, size, pubkey } = props;
|
||||
const NoImage = (
|
||||
props: Pick<Props, 'forcedName' | 'size' | 'pubkey' | 'onAvatarClick'> & {
|
||||
isClosedGroup: boolean;
|
||||
}
|
||||
) => {
|
||||
const { forcedName, size, pubkey, isClosedGroup } = props;
|
||||
// if no image but we have conversations set for the group, renders group members avatars
|
||||
if (memberAvatars) {
|
||||
if (pubkey && isClosedGroup) {
|
||||
return (
|
||||
<ClosedGroupAvatar
|
||||
size={size}
|
||||
memberAvatars={memberAvatars}
|
||||
onAvatarClick={props.onAvatarClick}
|
||||
/>
|
||||
<ClosedGroupAvatar size={size} closedGroupId={pubkey} onAvatarClick={props.onAvatarClick} />
|
||||
);
|
||||
}
|
||||
|
||||
return <Identicon size={size} name={name} pubkey={pubkey} />;
|
||||
return <Identicon size={size} forcedName={forcedName} pubkey={pubkey} />;
|
||||
};
|
||||
|
||||
const AvatarImage = (props: {
|
||||
|
@ -93,20 +87,26 @@ const AvatarImage = (props: {
|
|||
};
|
||||
|
||||
const AvatarInner = (props: Props) => {
|
||||
const { avatarPath, base64Data, size, memberAvatars, name, dataTestId } = props;
|
||||
const { base64Data, size, pubkey, forcedAvatarPath, forcedName, dataTestId } = props;
|
||||
const [imageBroken, setImageBroken] = useState(false);
|
||||
|
||||
const closedGroupMembers = useMembersAvatars(pubkey);
|
||||
|
||||
const avatarPath = useAvatarPath(pubkey);
|
||||
const name = useConversationUsername(pubkey);
|
||||
|
||||
// contentType is not important
|
||||
const { urlToLoad } = useEncryptedFileFetch(avatarPath || '', '');
|
||||
const { urlToLoad } = useEncryptedFileFetch(forcedAvatarPath || avatarPath || '', '');
|
||||
const handleImageError = () => {
|
||||
window.log.warn(
|
||||
'Avatar: Image failed to load; failing over to placeholder',
|
||||
urlToLoad,
|
||||
avatarPath
|
||||
forcedAvatarPath || avatarPath
|
||||
);
|
||||
setImageBroken(true);
|
||||
};
|
||||
|
||||
const isClosedGroupAvatar = Boolean(memberAvatars?.length);
|
||||
const isClosedGroupAvatar = Boolean(closedGroupMembers?.length);
|
||||
const hasImage = (base64Data || urlToLoad) && !imageBroken && !isClosedGroupAvatar;
|
||||
|
||||
const isClickable = !!props.onAvatarClick;
|
||||
|
@ -130,11 +130,11 @@ const AvatarInner = (props: Props) => {
|
|||
avatarPath={urlToLoad}
|
||||
base64Data={base64Data}
|
||||
imageBroken={imageBroken}
|
||||
name={name}
|
||||
name={forcedName || name}
|
||||
handleImageError={handleImageError}
|
||||
/>
|
||||
) : (
|
||||
<NoImage {...props} />
|
||||
<NoImage {...props} isClosedGroup={isClosedGroupAvatar} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import React from 'react';
|
||||
import { useMembersAvatars } from '../../hooks/useMembersAvatars';
|
||||
import { Avatar, AvatarSize } from '../Avatar';
|
||||
import { ConversationAvatar } from '../session/usingClosedConversationDetails';
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
size: number;
|
||||
memberAvatars: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
|
||||
closedGroupId: string;
|
||||
onAvatarClick?: () => void;
|
||||
}
|
||||
};
|
||||
|
||||
export class ClosedGroupAvatar extends React.PureComponent<Props> {
|
||||
public getClosedGroupAvatarsSize(size: AvatarSize): AvatarSize {
|
||||
function getClosedGroupAvatarsSize(size: AvatarSize): AvatarSize {
|
||||
// Always use the size directly under the one requested
|
||||
switch (size) {
|
||||
case AvatarSize.S:
|
||||
|
@ -27,33 +26,18 @@ export class ClosedGroupAvatar extends React.PureComponent<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { memberAvatars, size, onAvatarClick } = this.props;
|
||||
const avatarsDiameter = this.getClosedGroupAvatarsSize(size);
|
||||
export const ClosedGroupAvatar = (props: Props) => {
|
||||
const { closedGroupId, size, onAvatarClick } = props;
|
||||
|
||||
const conv1 = memberAvatars.length > 0 ? memberAvatars[0] : undefined;
|
||||
const conv2 = memberAvatars.length > 1 ? memberAvatars[1] : undefined;
|
||||
const name1 = conv1?.name || conv1?.id || undefined;
|
||||
const name2 = conv2?.name || conv2?.id || undefined;
|
||||
const memberAvatars = useMembersAvatars(closedGroupId);
|
||||
const avatarsDiameter = getClosedGroupAvatarsSize(size);
|
||||
const firstMemberId = memberAvatars?.[0];
|
||||
const secondMemberID = memberAvatars?.[1];
|
||||
|
||||
// use the 2 first members as group avatars
|
||||
return (
|
||||
<div className="module-avatar__icon-closed">
|
||||
<Avatar
|
||||
avatarPath={conv1?.avatarPath}
|
||||
name={name1}
|
||||
size={avatarsDiameter}
|
||||
pubkey={conv1?.id}
|
||||
onAvatarClick={onAvatarClick}
|
||||
/>
|
||||
<Avatar
|
||||
avatarPath={conv2?.avatarPath}
|
||||
name={name2}
|
||||
size={avatarsDiameter}
|
||||
pubkey={conv2?.id}
|
||||
onAvatarClick={onAvatarClick}
|
||||
/>
|
||||
<Avatar size={avatarsDiameter} pubkey={firstMemberId} onAvatarClick={onAvatarClick} />
|
||||
<Avatar size={avatarsDiameter} pubkey={secondMemberID} onAvatarClick={onAvatarClick} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,39 +3,28 @@ import classNames from 'classnames';
|
|||
|
||||
import { Avatar, AvatarSize } from './Avatar';
|
||||
import { Emojify } from './conversation/Emojify';
|
||||
import { useConversationUsername, useIsMe } from '../hooks/useParamSelector';
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
pubkey: string;
|
||||
isMe?: boolean;
|
||||
name?: string;
|
||||
profileName?: string;
|
||||
avatarPath?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
};
|
||||
|
||||
export class ContactListItem extends React.Component<Props> {
|
||||
public renderAvatar() {
|
||||
const { avatarPath, name, pubkey, profileName } = this.props;
|
||||
const AvatarItem = (props: { pubkey: string }) => {
|
||||
const { pubkey } = props;
|
||||
|
||||
const userName = name || profileName || pubkey;
|
||||
return <Avatar size={AvatarSize.S} pubkey={pubkey} />;
|
||||
};
|
||||
|
||||
return <Avatar avatarPath={avatarPath} name={userName} size={AvatarSize.S} pubkey={pubkey} />;
|
||||
}
|
||||
export const ContactListItem = (props: Props) => {
|
||||
const { onClick, pubkey } = props;
|
||||
|
||||
public render() {
|
||||
const { name, onClick, isMe, pubkey, profileName } = this.props;
|
||||
const name = useConversationUsername(pubkey);
|
||||
const isMe = useIsMe(pubkey);
|
||||
|
||||
const title = name ? name : pubkey;
|
||||
const displayName = isMe ? window.i18n('me') : title;
|
||||
|
||||
const profileElement =
|
||||
!isMe && profileName && !name ? (
|
||||
<span className="module-contact-list-item__text__profile-name">
|
||||
~
|
||||
<Emojify text={profileName} key={`emojify-list-item-${pubkey}`} />
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
|
@ -45,13 +34,12 @@ export class ContactListItem extends React.Component<Props> {
|
|||
onClick ? 'module-contact-list-item--with-click-handler' : null
|
||||
)}
|
||||
>
|
||||
{this.renderAvatar()}
|
||||
<AvatarItem pubkey={pubkey} />
|
||||
<div className="module-contact-list-item__text">
|
||||
<div className="module-contact-list-item__text__name">
|
||||
<Emojify text={displayName} /> {profileElement}
|
||||
<Emojify text={displayName} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,7 +9,6 @@ import { Timestamp } from './conversation/Timestamp';
|
|||
import { ContactName } from './conversation/ContactName';
|
||||
import { TypingAnimation } from './conversation/TypingAnimation';
|
||||
|
||||
import { ConversationAvatar } from './session/usingClosedConversationDetails';
|
||||
import { MemoConversationListItemContextMenu } from './session/menu/ConversationListItemContextMenu';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { OutgoingMessageStatus } from './conversation/message/OutgoingMessageStatus';
|
||||
|
@ -21,8 +20,7 @@ import {
|
|||
ReduxConversationType,
|
||||
} from '../state/ducks/conversations';
|
||||
import _ from 'underscore';
|
||||
import { useMembersAvatars } from '../hooks/useMembersAvatar';
|
||||
import { SessionIcon, SessionIconButton } from './session/icon';
|
||||
import { SessionIcon } from './session/icon';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { SectionType } from '../state/ducks/section';
|
||||
import { getFocusedSection } from '../state/selectors/section';
|
||||
|
@ -31,6 +29,7 @@ import { Flex } from './basic/Flex';
|
|||
import { forceSyncConfigurationNowIfNeeded } from '../session/utils/syncUtils';
|
||||
import { updateUserDetailsModal } from '../state/ducks/modalDialog';
|
||||
import { approveConversation, blockConvoById } from '../interactions/conversationInteractions';
|
||||
import { useAvatarPath, useConversationUsername, useIsMe } from '../hooks/useParamSelector';
|
||||
|
||||
// tslint:disable-next-line: no-empty-interface
|
||||
export interface ConversationListItemProps extends ReduxConversationType {}
|
||||
|
@ -58,11 +57,8 @@ const Portal = ({ children }: { children: any }) => {
|
|||
|
||||
const HeaderItem = (props: {
|
||||
unreadCount: number;
|
||||
isMe: boolean;
|
||||
mentionedUs: boolean;
|
||||
activeAt?: number;
|
||||
name?: string;
|
||||
profileName?: string;
|
||||
conversationId: string;
|
||||
isPinned: boolean;
|
||||
currentNotificationSetting: ConversationNotificationSettingType;
|
||||
|
@ -71,11 +67,8 @@ const HeaderItem = (props: {
|
|||
unreadCount,
|
||||
mentionedUs,
|
||||
activeAt,
|
||||
isMe,
|
||||
isPinned,
|
||||
conversationId,
|
||||
profileName,
|
||||
name,
|
||||
currentNotificationSetting,
|
||||
} = props;
|
||||
|
||||
|
@ -122,12 +115,7 @@ const HeaderItem = (props: {
|
|||
unreadCount > 0 ? 'module-conversation-list-item__header__name--with-unread' : null
|
||||
)}
|
||||
>
|
||||
<UserItem
|
||||
isMe={isMe}
|
||||
conversationId={conversationId}
|
||||
name={name}
|
||||
profileName={profileName}
|
||||
/>
|
||||
<UserItem conversationId={conversationId} />
|
||||
</div>
|
||||
|
||||
<StyledConversationListItemIconWrapper>
|
||||
|
@ -149,21 +137,18 @@ const HeaderItem = (props: {
|
|||
);
|
||||
};
|
||||
|
||||
const UserItem = (props: {
|
||||
name?: string;
|
||||
profileName?: string;
|
||||
isMe: boolean;
|
||||
conversationId: string;
|
||||
}) => {
|
||||
const { name, conversationId, profileName, isMe } = props;
|
||||
const UserItem = (props: { conversationId: string }) => {
|
||||
const { conversationId } = props;
|
||||
|
||||
const shortenedPubkey = PubKey.shorten(conversationId);
|
||||
const isMe = useIsMe(conversationId);
|
||||
const username = useConversationUsername(conversationId);
|
||||
|
||||
const displayedPubkey = profileName ? shortenedPubkey : conversationId;
|
||||
const displayName = isMe ? window.i18n('noteToSelf') : profileName;
|
||||
const displayedPubkey = username ? shortenedPubkey : conversationId;
|
||||
const displayName = isMe ? window.i18n('noteToSelf') : username;
|
||||
|
||||
let shouldShowPubkey = false;
|
||||
if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) {
|
||||
if ((!username || username.length === 0) && (!displayName || displayName.length === 0)) {
|
||||
shouldShowPubkey = true;
|
||||
}
|
||||
|
||||
|
@ -171,7 +156,7 @@ const UserItem = (props: {
|
|||
<div className="module-conversation__user">
|
||||
<ContactName
|
||||
pubkey={displayedPubkey}
|
||||
name={name}
|
||||
name={username}
|
||||
profileName={displayName}
|
||||
module="module-conversation__user"
|
||||
boldProfileName={true}
|
||||
|
@ -218,33 +203,23 @@ const MessageItem = (props: {
|
|||
);
|
||||
};
|
||||
|
||||
const AvatarItem = (props: {
|
||||
avatarPath: string | null;
|
||||
conversationId: string;
|
||||
memberAvatars?: Array<ConversationAvatar>;
|
||||
name?: string;
|
||||
profileName?: string;
|
||||
isPrivate: boolean;
|
||||
}) => {
|
||||
const { avatarPath, name, isPrivate, conversationId, profileName, memberAvatars } = props;
|
||||
|
||||
const userName = name || profileName || conversationId;
|
||||
const AvatarItem = (props: { conversationId: string; isPrivate: boolean }) => {
|
||||
const { isPrivate, conversationId } = props;
|
||||
const userName = useConversationUsername(conversationId);
|
||||
const avatarPath = useAvatarPath(conversationId);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<div className="module-conversation-list-item__avatar-container">
|
||||
<Avatar
|
||||
avatarPath={avatarPath}
|
||||
name={userName}
|
||||
size={AvatarSize.S}
|
||||
memberAvatars={memberAvatars}
|
||||
pubkey={conversationId}
|
||||
onAvatarClick={() => {
|
||||
if (isPrivate) {
|
||||
dispatch(
|
||||
updateUserDetailsModal({
|
||||
conversationId: conversationId,
|
||||
userName,
|
||||
userName: userName || '',
|
||||
authorAvatarPath: avatarPath,
|
||||
})
|
||||
);
|
||||
|
@ -266,9 +241,7 @@ const ConversationListItem = (props: Props) => {
|
|||
style,
|
||||
mentionedUs,
|
||||
isMe,
|
||||
name,
|
||||
isPinned,
|
||||
profileName,
|
||||
isTyping,
|
||||
lastMessage,
|
||||
hasNickname,
|
||||
|
@ -284,8 +257,6 @@ const ConversationListItem = (props: Props) => {
|
|||
const triggerId = `conversation-item-${conversationId}-ctxmenu`;
|
||||
const key = `conversation-item-${conversationId}`;
|
||||
|
||||
const membersAvatar = useMembersAvatars(props);
|
||||
|
||||
const openConvo = useCallback(
|
||||
async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
// mousedown is invoked sooner than onClick, but for both right and left click
|
||||
|
@ -331,24 +302,14 @@ const ConversationListItem = (props: Props) => {
|
|||
isBlocked ? 'module-conversation-list-item--is-blocked' : null
|
||||
)}
|
||||
>
|
||||
<AvatarItem
|
||||
conversationId={conversationId}
|
||||
avatarPath={avatarPath || null}
|
||||
memberAvatars={membersAvatar}
|
||||
profileName={profileName}
|
||||
name={name}
|
||||
isPrivate={isPrivate || false}
|
||||
/>
|
||||
<AvatarItem conversationId={conversationId} isPrivate={isPrivate || false} />
|
||||
<div className="module-conversation-list-item__content">
|
||||
<HeaderItem
|
||||
mentionedUs={!!mentionedUs}
|
||||
unreadCount={unreadCount || 0}
|
||||
activeAt={activeAt}
|
||||
isMe={!!isMe}
|
||||
isPinned={!!isPinned}
|
||||
conversationId={conversationId}
|
||||
name={name}
|
||||
profileName={profileName}
|
||||
currentNotificationSetting={currentNotificationSetting || 'all'}
|
||||
/>
|
||||
<MessageItem
|
||||
|
@ -401,8 +362,6 @@ const ConversationListItem = (props: Props) => {
|
|||
type={type}
|
||||
currentNotificationSetting={currentNotificationSetting || 'all'}
|
||||
avatarPath={avatarPath || null}
|
||||
name={name}
|
||||
profileName={profileName}
|
||||
/>
|
||||
</Portal>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Avatar, AvatarSize } from '../Avatar';
|
|||
import { SessionIconButton } from '../session/icon';
|
||||
|
||||
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
|
||||
import { ConversationAvatar } from '../session/usingClosedConversationDetails';
|
||||
import { MemoConversationHeaderMenu } from '../session/menu/ConversationHeaderMenu';
|
||||
import { contextMenu } from 'react-contexify';
|
||||
import styled from 'styled-components';
|
||||
|
@ -16,7 +15,6 @@ import {
|
|||
getCurrentNotificationSettingText,
|
||||
getIsSelectedNoteToSelf,
|
||||
getIsSelectedPrivate,
|
||||
getSelectedConversation,
|
||||
getSelectedConversationIsPublic,
|
||||
getSelectedConversationKey,
|
||||
getSelectedMessageIds,
|
||||
|
@ -25,7 +23,6 @@ import {
|
|||
isRightPanelShowing,
|
||||
} from '../../state/selectors/conversations';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useMembersAvatars } from '../../hooks/useMembersAvatar';
|
||||
|
||||
import {
|
||||
deleteMessagesById,
|
||||
|
@ -165,30 +162,22 @@ const ExpirationLength = (props: { expirationSettingName?: string }) => {
|
|||
};
|
||||
|
||||
const AvatarHeader = (props: {
|
||||
avatarPath: string | null;
|
||||
memberAvatars?: Array<ConversationAvatar>;
|
||||
name?: string;
|
||||
pubkey: string;
|
||||
profileName?: string;
|
||||
showBackButton: boolean;
|
||||
onAvatarClick?: (pubkey: string) => void;
|
||||
}) => {
|
||||
const { avatarPath, memberAvatars, name, pubkey, profileName } = props;
|
||||
const userName = name || profileName || pubkey;
|
||||
const { pubkey, onAvatarClick, showBackButton } = props;
|
||||
|
||||
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(pubkey);
|
||||
if (onAvatarClick && !showBackButton) {
|
||||
onAvatarClick(pubkey);
|
||||
}
|
||||
}}
|
||||
memberAvatars={memberAvatars}
|
||||
pubkey={pubkey}
|
||||
/>
|
||||
</span>
|
||||
|
@ -344,8 +333,6 @@ export const ConversationHeaderWithDetails = () => {
|
|||
const headerProps = useSelector(getConversationHeaderProps);
|
||||
|
||||
const isSelectionMode = useSelector(isMessageSelectionMode);
|
||||
const selectedConversation = useSelector(getSelectedConversation);
|
||||
const memberDetails = useMembersAvatars(selectedConversation);
|
||||
const isMessageDetailOpened = useSelector(isMessageDetailView);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
@ -400,10 +387,6 @@ export const ConversationHeaderWithDetails = () => {
|
|||
}}
|
||||
pubkey={conversationKey}
|
||||
showBackButton={isMessageDetailOpened}
|
||||
avatarPath={avatarPath}
|
||||
memberAvatars={memberDetails}
|
||||
name={name}
|
||||
profileName={profileName}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -13,11 +13,10 @@ import {
|
|||
} from '../../state/selectors/conversations';
|
||||
import { deleteMessagesById } from '../../interactions/conversations/unsendingInteractions';
|
||||
|
||||
const AvatarItem = (props: { contact: ContactPropsMessageDetail }) => {
|
||||
const { avatarPath, pubkey, name, profileName } = props.contact;
|
||||
const userName = name || profileName || pubkey;
|
||||
const AvatarItem = (props: { pubkey: string | undefined }) => {
|
||||
const { pubkey } = props;
|
||||
|
||||
return <Avatar avatarPath={avatarPath} name={userName} size={AvatarSize.S} pubkey={pubkey} />;
|
||||
return <Avatar size={AvatarSize.S} pubkey={pubkey} />;
|
||||
};
|
||||
|
||||
const DeleteButtonItem = (props: { messageId: string; convoId: string; isDeletable: boolean }) => {
|
||||
|
@ -68,7 +67,7 @@ const ContactItem = (props: { contact: ContactPropsMessageDetail }) => {
|
|||
|
||||
return (
|
||||
<div key={contact.pubkey} className="module-message-detail__contact">
|
||||
<AvatarItem contact={contact} />
|
||||
<AvatarItem pubkey={contact.pubkey} />
|
||||
<div className="module-message-detail__contact__text">
|
||||
<div className="module-message-detail__contact__name">
|
||||
<ContactName
|
||||
|
|
|
@ -62,13 +62,7 @@ export const MessageAvatar = (props: Props) => {
|
|||
|
||||
return (
|
||||
<div className="module-message__author-avatar" key={`msg-avatar-${authorPhoneNumber}`}>
|
||||
<Avatar
|
||||
avatarPath={authorAvatarPath}
|
||||
name={userName}
|
||||
size={AvatarSize.S}
|
||||
onAvatarClick={onMessageAvatarClick}
|
||||
pubkey={authorPhoneNumber}
|
||||
/>
|
||||
<Avatar size={AvatarSize.S} onAvatarClick={onMessageAvatarClick} pubkey={authorPhoneNumber} />
|
||||
{isPublic && isSenderAdmin && (
|
||||
<div className="module-avatar__icon--crown-wrapper">
|
||||
<div className="module-avatar__icon--crown" />
|
||||
|
|
|
@ -246,7 +246,12 @@ export class EditProfileDialog extends React.Component<{}, State> {
|
|||
const userName = profileName || this.convo.id;
|
||||
|
||||
return (
|
||||
<Avatar avatarPath={avatar} name={userName} size={AvatarSize.XL} pubkey={this.convo.id} />
|
||||
<Avatar
|
||||
forcedAvatarPath={avatar}
|
||||
forcedName={userName}
|
||||
size={AvatarSize.XL}
|
||||
pubkey={this.convo.id}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -191,7 +191,7 @@ export class UpdateGroupNameDialog extends React.Component<Props, State> {
|
|||
return (
|
||||
<div className="avatar-center">
|
||||
<div className="avatar-center-inner">
|
||||
<Avatar avatarPath={this.state.avatar || ''} size={AvatarSize.XL} pubkey={pubkey} />
|
||||
<Avatar forcedAvatarPath={this.state.avatar || ''} size={AvatarSize.XL} pubkey={pubkey} />
|
||||
<div
|
||||
className="image-upload-section"
|
||||
role="button"
|
||||
|
|
|
@ -25,7 +25,6 @@ export const UserDetailsDialog = (props: Props) => {
|
|||
const convo = getConversationController().get(props.conversationId);
|
||||
|
||||
const size = isEnlargedImageShown ? AvatarSize.HUGE : AvatarSize.XL;
|
||||
const userName = props.userName || props.conversationId;
|
||||
|
||||
const [_, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
|
@ -57,8 +56,6 @@ export const UserDetailsDialog = (props: Props) => {
|
|||
<div className="avatar-center">
|
||||
<div className="avatar-center-inner">
|
||||
<Avatar
|
||||
avatarPath={props.authorAvatarPath}
|
||||
name={userName}
|
||||
size={size}
|
||||
onAvatarClick={() => {
|
||||
setIsEnlargedImageShown(!isEnlargedImageShown);
|
||||
|
|
|
@ -50,11 +50,11 @@ import { DraggableCallContainer } from './calling/DraggableCallContainer';
|
|||
import { IncomingCallDialog } from './calling/IncomingCallDialog';
|
||||
import { CallInFullScreenContainer } from './calling/CallInFullScreenContainer';
|
||||
|
||||
const Section = (props: { type: SectionType; avatarPath?: string | null }) => {
|
||||
const Section = (props: { type: SectionType }) => {
|
||||
const ourNumber = useSelector(getOurNumber);
|
||||
const unreadMessageCount = useSelector(getUnreadMessageCount);
|
||||
const dispatch = useDispatch();
|
||||
const { type, avatarPath } = props;
|
||||
const { type } = props;
|
||||
|
||||
const focusedSection = useSelector(getFocusedSection);
|
||||
const isSelected = focusedSection === props.type;
|
||||
|
@ -85,16 +85,10 @@ const Section = (props: { type: SectionType; avatarPath?: string | null }) => {
|
|||
};
|
||||
|
||||
if (type === SectionType.Profile) {
|
||||
const conversation = getConversationController().get(ourNumber);
|
||||
|
||||
const profile = conversation?.getLokiProfile();
|
||||
const userName = (profile && profile.displayName) || ourNumber;
|
||||
return (
|
||||
<Avatar
|
||||
avatarPath={avatarPath}
|
||||
size={AvatarSize.XS}
|
||||
onAvatarClick={handleClick}
|
||||
name={userName}
|
||||
pubkey={ourNumber}
|
||||
dataTestId="leftpane-primary-avatar"
|
||||
/>
|
||||
|
@ -287,12 +281,7 @@ export const ActionsPanel = () => {
|
|||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
|
||||
useInterval(
|
||||
() => {
|
||||
cleanUpOldDecryptedMedias();
|
||||
},
|
||||
startCleanUpMedia ? cleanUpMediasInterval : null
|
||||
);
|
||||
useInterval(cleanUpOldDecryptedMedias, startCleanUpMedia ? cleanUpMediasInterval : null);
|
||||
|
||||
if (!ourPrimaryConversation) {
|
||||
window?.log?.warn('ActionsPanel: ourPrimaryConversation is not set');
|
||||
|
@ -328,7 +317,7 @@ export const ActionsPanel = () => {
|
|||
className="module-left-pane__sections-container"
|
||||
data-testid="leftpane-section-container"
|
||||
>
|
||||
<Section type={SectionType.Profile} avatarPath={ourPrimaryConversation.avatarPath} />
|
||||
<Section type={SectionType.Profile} />
|
||||
<Section type={SectionType.Message} />
|
||||
<Section type={SectionType.Contact} />
|
||||
<Section type={SectionType.Settings} />
|
||||
|
|
|
@ -23,37 +23,17 @@ type Props = {
|
|||
isSelected: boolean;
|
||||
// this bool is used to make a zombie appear with less opacity than a normal member
|
||||
isZombie?: boolean;
|
||||
onSelect?: any;
|
||||
onUnselect?: any;
|
||||
onSelect?: (selectedMember: ContactType) => void;
|
||||
onUnselect?: (selectedMember: ContactType) => void;
|
||||
};
|
||||
|
||||
const AvatarItem = (props: { memberPubkey?: string }) => {
|
||||
return <Avatar size={AvatarSize.XS} pubkey={props.memberPubkey} />;
|
||||
};
|
||||
|
||||
export const SessionMemberListItem = (props: Props) => {
|
||||
const { isSelected, member, isZombie, onSelect, onUnselect } = props;
|
||||
|
||||
const renderAvatar = () => {
|
||||
const { authorAvatarPath, authorName, authorPhoneNumber, authorProfileName } = member;
|
||||
const userName = authorName || authorProfileName || authorPhoneNumber;
|
||||
return (
|
||||
<Avatar
|
||||
avatarPath={authorAvatarPath}
|
||||
name={userName}
|
||||
size={AvatarSize.XS}
|
||||
pubkey={authorPhoneNumber}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const selectMember = () => {
|
||||
onSelect?.(member);
|
||||
};
|
||||
const unselectMember = () => {
|
||||
onUnselect?.(member);
|
||||
};
|
||||
|
||||
const handleSelectionAction = () => {
|
||||
isSelected ? unselectMember() : selectMember();
|
||||
};
|
||||
|
||||
const name = member.authorProfileName || PubKey.shorten(member.authorPhoneNumber);
|
||||
|
||||
return (
|
||||
|
@ -64,11 +44,15 @@ export const SessionMemberListItem = (props: Props) => {
|
|||
isSelected && 'selected',
|
||||
isZombie && 'zombie'
|
||||
)}
|
||||
onClick={handleSelectionAction}
|
||||
onClick={() => {
|
||||
isSelected ? onUnselect?.(member) : onSelect?.(member);
|
||||
}}
|
||||
role="button"
|
||||
>
|
||||
<div className="session-member-item__info">
|
||||
<span className="session-member-item__avatar">{renderAvatar()}</span>
|
||||
<span className="session-member-item__avatar">
|
||||
<AvatarItem memberPubkey={member.id} />
|
||||
</span>
|
||||
<span className="session-member-item__name">{name}</span>
|
||||
</div>
|
||||
<span className={classNames('session-member-item__checkmark', isSelected && 'selected')}>
|
||||
|
|
|
@ -9,7 +9,6 @@ import { getHasOngoingCall, getHasOngoingCallWith } from '../../../state/selecto
|
|||
import { openConversationWithMessages } from '../../../state/ducks/conversations';
|
||||
import { Avatar, AvatarSize } from '../../Avatar';
|
||||
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener';
|
||||
import { useAvatarPath, useConversationUsername } from '../../../hooks/useParamSelector';
|
||||
import { VideoLoadingSpinner } from './InConversationCallContainer';
|
||||
|
||||
export const DraggableCallWindow = styled.div`
|
||||
|
@ -77,8 +76,6 @@ export const DraggableCallContainer = () => {
|
|||
'DraggableCallContainer',
|
||||
false
|
||||
);
|
||||
const ongoingCallUsername = useConversationUsername(ongoingCallPubkey);
|
||||
const avatarPath = useAvatarPath(ongoingCallPubkey);
|
||||
const videoRefRemote = useRef<HTMLVideoElement>(null);
|
||||
|
||||
function onWindowResize() {
|
||||
|
@ -140,12 +137,7 @@ export const DraggableCallContainer = () => {
|
|||
/>
|
||||
{remoteStreamVideoIsMuted && (
|
||||
<CenteredAvatarInDraggable>
|
||||
<Avatar
|
||||
size={AvatarSize.XL}
|
||||
avatarPath={avatarPath}
|
||||
name={ongoingCallUsername}
|
||||
pubkey={ongoingCallPubkey}
|
||||
/>
|
||||
<Avatar size={AvatarSize.XL} pubkey={ongoingCallPubkey} />
|
||||
</CenteredAvatarInDraggable>
|
||||
)}
|
||||
</DraggableCallWindowInner>
|
||||
|
|
|
@ -6,7 +6,6 @@ import _ from 'underscore';
|
|||
import { UserUtils } from '../../../session/utils';
|
||||
import {
|
||||
getCallIsInFullScreen,
|
||||
getHasOngoingCallWith,
|
||||
getHasOngoingCallWithFocusedConvo,
|
||||
getHasOngoingCallWithFocusedConvoIsOffering,
|
||||
getHasOngoingCallWithFocusedConvosIsConnecting,
|
||||
|
@ -16,11 +15,6 @@ import { StyledVideoElement } from './DraggableCallContainer';
|
|||
import { Avatar, AvatarSize } from '../../Avatar';
|
||||
|
||||
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener';
|
||||
import {
|
||||
useAvatarPath,
|
||||
useOurAvatarPath,
|
||||
useOurConversationUsername,
|
||||
} from '../../../hooks/useParamSelector';
|
||||
import { useModuloWithTripleDots } from '../../../hooks/useModuloWithTripleDots';
|
||||
import { CallWindowControls } from './CallButtons';
|
||||
import { SessionSpinner } from '../SessionSpinner';
|
||||
|
@ -118,23 +112,15 @@ export const VideoLoadingSpinner = (props: { fullWidth: boolean }) => {
|
|||
|
||||
// tslint:disable-next-line: max-func-body-length
|
||||
export const InConversationCallContainer = () => {
|
||||
const ongoingCallProps = useSelector(getHasOngoingCallWith);
|
||||
|
||||
const isInFullScreen = useSelector(getCallIsInFullScreen);
|
||||
|
||||
const ongoingCallPubkey = useSelector(getHasOngoingCallWithPubkey);
|
||||
const ongoingCallWithFocused = useSelector(getHasOngoingCallWithFocusedConvo);
|
||||
const ongoingCallUsername = ongoingCallProps?.profileName || ongoingCallProps?.name;
|
||||
const videoRefRemote = useRef<HTMLVideoElement>(null);
|
||||
const videoRefLocal = useRef<HTMLVideoElement>(null);
|
||||
|
||||
const ourPubkey = UserUtils.getOurPubKeyStrFromCache();
|
||||
|
||||
const remoteAvatarPath = useAvatarPath(ongoingCallPubkey);
|
||||
const ourAvatarPath = useOurAvatarPath();
|
||||
|
||||
const ourUsername = useOurConversationUsername();
|
||||
|
||||
const {
|
||||
currentConnectedAudioInputs,
|
||||
currentConnectedCameras,
|
||||
|
@ -190,12 +176,7 @@ export const InConversationCallContainer = () => {
|
|||
/>
|
||||
{remoteStreamVideoIsMuted && (
|
||||
<CenteredAvatarInConversation>
|
||||
<Avatar
|
||||
size={AvatarSize.XL}
|
||||
avatarPath={remoteAvatarPath}
|
||||
name={ongoingCallUsername}
|
||||
pubkey={ongoingCallPubkey}
|
||||
/>
|
||||
<Avatar size={AvatarSize.XL} pubkey={ongoingCallPubkey} />
|
||||
</CenteredAvatarInConversation>
|
||||
)}
|
||||
</VideoContainer>
|
||||
|
@ -208,12 +189,7 @@ export const InConversationCallContainer = () => {
|
|||
/>
|
||||
{localStreamVideoIsMuted && (
|
||||
<CenteredAvatarInConversation>
|
||||
<Avatar
|
||||
size={AvatarSize.XL}
|
||||
avatarPath={ourAvatarPath}
|
||||
name={ourUsername}
|
||||
pubkey={ourPubkey}
|
||||
/>
|
||||
<Avatar size={AvatarSize.XL} pubkey={ourPubkey} />
|
||||
</CenteredAvatarInConversation>
|
||||
)}
|
||||
</VideoContainer>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useSelector } from 'react-redux';
|
|||
|
||||
import styled from 'styled-components';
|
||||
import _ from 'underscore';
|
||||
import { useAvatarPath, useConversationUsername } from '../../../hooks/useParamSelector';
|
||||
import { useConversationUsername } from '../../../hooks/useParamSelector';
|
||||
import { ed25519Str } from '../../../session/onions/onionPath';
|
||||
import { CallManager } from '../../../session/utils';
|
||||
import { getHasIncomingCall, getHasIncomingCallFrom } from '../../../state/selectors/call';
|
||||
|
@ -70,7 +70,6 @@ export const IncomingCallDialog = () => {
|
|||
}
|
||||
};
|
||||
const from = useConversationUsername(incomingCallFromPubkey);
|
||||
const incomingAvatar = useAvatarPath(incomingCallFromPubkey);
|
||||
if (!hasIncomingCall) {
|
||||
return null;
|
||||
}
|
||||
|
@ -79,12 +78,7 @@ export const IncomingCallDialog = () => {
|
|||
return (
|
||||
<SessionWrapperModal title={window.i18n('incomingCallFrom', from)}>
|
||||
<IncomingCallAvatatContainer>
|
||||
<Avatar
|
||||
size={AvatarSize.XL}
|
||||
avatarPath={incomingAvatar}
|
||||
name={from}
|
||||
pubkey={incomingCallFromPubkey}
|
||||
/>
|
||||
<Avatar size={AvatarSize.XL} pubkey={incomingCallFromPubkey} />
|
||||
</IncomingCallAvatatContainer>
|
||||
<div className="session-modal__button-group">
|
||||
<SessionButton
|
||||
|
|
|
@ -31,7 +31,6 @@ import {
|
|||
getSelectedConversation,
|
||||
isRightPanelShowing,
|
||||
} from '../../../state/selectors/conversations';
|
||||
import { useMembersAvatars } from '../../../hooks/useMembersAvatar';
|
||||
import { closeRightPanel } from '../../../state/ducks/conversations';
|
||||
|
||||
async function getMediaGalleryProps(
|
||||
|
@ -110,24 +109,13 @@ async function getMediaGalleryProps(
|
|||
const HeaderItem = () => {
|
||||
const selectedConversation = useSelector(getSelectedConversation);
|
||||
const dispatch = useDispatch();
|
||||
const memberDetails = useMembersAvatars(selectedConversation);
|
||||
|
||||
if (!selectedConversation) {
|
||||
return null;
|
||||
}
|
||||
const {
|
||||
avatarPath,
|
||||
id,
|
||||
isGroup,
|
||||
isKickedFromGroup,
|
||||
profileName,
|
||||
isBlocked,
|
||||
left,
|
||||
name,
|
||||
} = selectedConversation;
|
||||
const { id, isGroup, isKickedFromGroup, isBlocked, left } = selectedConversation;
|
||||
|
||||
const showInviteContacts = isGroup && !isKickedFromGroup && !isBlocked && !left;
|
||||
const userName = name || profileName || id;
|
||||
|
||||
return (
|
||||
<div className="group-settings-header">
|
||||
|
@ -139,13 +127,7 @@ const HeaderItem = () => {
|
|||
dispatch(closeRightPanel());
|
||||
}}
|
||||
/>
|
||||
<Avatar
|
||||
avatarPath={avatarPath || ''}
|
||||
name={userName}
|
||||
size={AvatarSize.XL}
|
||||
memberAvatars={memberDetails}
|
||||
pubkey={id}
|
||||
/>
|
||||
<Avatar size={AvatarSize.XL} pubkey={id} />
|
||||
<div className="invite-friends-container">
|
||||
{showInviteContacts && (
|
||||
<SessionIconButton
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { animation, Menu } from 'react-contexify';
|
||||
import _ from 'underscore';
|
||||
import { useAvatarPath, useConversationUsername } from '../../../hooks/useParamSelector';
|
||||
import {
|
||||
ConversationNotificationSettingType,
|
||||
ConversationTypeEnum,
|
||||
|
@ -34,8 +35,6 @@ export type PropsContextConversationItem = {
|
|||
left: boolean;
|
||||
theme?: any;
|
||||
currentNotificationSetting: ConversationNotificationSettingType;
|
||||
name: string | undefined;
|
||||
profileName: string | undefined;
|
||||
avatarPath: string | null;
|
||||
};
|
||||
|
||||
|
@ -52,13 +51,12 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
|
|||
isKickedFromGroup,
|
||||
currentNotificationSetting,
|
||||
isPrivate,
|
||||
name,
|
||||
profileName,
|
||||
avatarPath,
|
||||
} = props;
|
||||
|
||||
const isGroup = type === 'group';
|
||||
const userName = name || profileName || conversationId;
|
||||
|
||||
const userName = useConversationUsername(conversationId);
|
||||
const avatarPath = useAvatarPath(conversationId);
|
||||
|
||||
return (
|
||||
<Menu id={triggerId} animation={animation.fade}>
|
||||
|
@ -80,7 +78,7 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
|
|||
{getInviteContactMenuItem(isGroup, isPublic, conversationId)}
|
||||
{getDeleteContactMenuItem(isGroup, isPublic, left, isKickedFromGroup, conversationId)}
|
||||
{getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)}
|
||||
{getShowUserDetailsMenuItem(isPrivate, conversationId, avatarPath, userName)}
|
||||
{getShowUserDetailsMenuItem(isPrivate, conversationId, avatarPath, userName || '')}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
import { GroupUtils, UserUtils } from '../../session/utils';
|
||||
import { PubKey } from '../../session/types';
|
||||
import React from 'react';
|
||||
import * as _ from 'lodash';
|
||||
import { getConversationController } from '../../session/conversations';
|
||||
|
||||
export type ConversationAvatar = {
|
||||
avatarPath?: string;
|
||||
id?: string; // member's pubkey
|
||||
name?: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
|
||||
};
|
||||
|
||||
export function usingClosedConversationDetails(WrappedComponent: any) {
|
||||
return class extends React.Component<any, State> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
memberAvatars: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.fetchClosedConversationDetails();
|
||||
}
|
||||
|
||||
public componentWillReceiveProps() {
|
||||
this.fetchClosedConversationDetails();
|
||||
}
|
||||
|
||||
public render() {
|
||||
return <WrappedComponent memberAvatars={this.state.memberAvatars} {...this.props} />;
|
||||
}
|
||||
|
||||
private fetchClosedConversationDetails() {
|
||||
const { isPublic, type, conversationType, isGroup, id } = this.props;
|
||||
|
||||
if (!isPublic && (conversationType === 'group' || type === 'group' || isGroup)) {
|
||||
const groupId = id;
|
||||
const ourPrimary = UserUtils.getOurPubKeyFromCache();
|
||||
let members = GroupUtils.getGroupMembers(PubKey.cast(groupId));
|
||||
|
||||
const ourself = members.find(m => m.key !== ourPrimary.key);
|
||||
// add ourself back at the back, so it's shown only if only 1 member and we are still a member
|
||||
members = members.filter(m => m.key !== ourPrimary.key);
|
||||
members.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
||||
if (ourself) {
|
||||
members.push(ourPrimary);
|
||||
}
|
||||
// no need to forward more than 2 conversations for rendering the group avatar
|
||||
members = members.slice(0, 2);
|
||||
const memberConvos = _.compact(members.map(m => getConversationController().get(m.key)));
|
||||
const memberAvatars = memberConvos.map(m => {
|
||||
return {
|
||||
avatarPath: m.getAvatarPath() || undefined,
|
||||
id: m.id,
|
||||
name: m.get('name') || m.get('profileName') || m.id,
|
||||
};
|
||||
});
|
||||
this.setState({ memberAvatars });
|
||||
} else {
|
||||
this.setState({ memberAvatars: undefined });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getConversationController } from '../session/conversations';
|
||||
import { UserUtils } from '../session/utils';
|
||||
import { ReduxConversationType } from '../state/ducks/conversations';
|
||||
|
||||
export function useMembersAvatars(conversation: ReduxConversationType | undefined) {
|
||||
const [membersAvatars, setMembersAvatars] = useState<
|
||||
| Array<{
|
||||
avatarPath: string | undefined;
|
||||
id: string;
|
||||
name: string;
|
||||
}>
|
||||
| undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!conversation) {
|
||||
setMembersAvatars(undefined);
|
||||
return;
|
||||
}
|
||||
const { isPublic, isGroup, members: convoMembers } = conversation;
|
||||
if (!isPublic && isGroup) {
|
||||
const ourPrimary = UserUtils.getOurPubKeyStrFromCache();
|
||||
|
||||
const ourself = convoMembers?.find(m => m !== ourPrimary) || undefined;
|
||||
// add ourself back at the back, so it's shown only if only 1 member and we are still a member
|
||||
let membersFiltered = convoMembers?.filter(m => m !== ourPrimary) || [];
|
||||
membersFiltered.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
||||
if (ourself) {
|
||||
membersFiltered.push(ourPrimary);
|
||||
}
|
||||
// no need to forward more than 2 conversations for rendering the group avatar
|
||||
membersFiltered = membersFiltered.slice(0, 2);
|
||||
const memberConvos = _.compact(
|
||||
membersFiltered.map(m => getConversationController().get(m))
|
||||
);
|
||||
const memberAvatars = memberConvos.map(m => {
|
||||
return {
|
||||
avatarPath: m.getAvatarPath() || undefined,
|
||||
id: m.id as string,
|
||||
name: (m.get('name') || m.get('profileName') || m.id) as string,
|
||||
};
|
||||
});
|
||||
setMembersAvatars(memberAvatars);
|
||||
} else {
|
||||
setMembersAvatars(undefined);
|
||||
}
|
||||
},
|
||||
conversation ? [conversation.members, conversation.id] : []
|
||||
);
|
||||
|
||||
return membersAvatars;
|
||||
}
|
40
ts/hooks/useMembersAvatars.tsx
Normal file
40
ts/hooks/useMembersAvatars.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { UserUtils } from '../session/utils';
|
||||
import * as _ from 'lodash';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { StateType } from '../state/reducer';
|
||||
|
||||
export function useMembersAvatars(closedGroupPubkey: string | undefined) {
|
||||
const ourPrimary = UserUtils.getOurPubKeyStrFromCache();
|
||||
|
||||
return useSelector((state: StateType): Array<string> | undefined => {
|
||||
if (!closedGroupPubkey) {
|
||||
return undefined;
|
||||
}
|
||||
const groupConvo = state.conversations.conversationLookup[closedGroupPubkey];
|
||||
|
||||
if (groupConvo.isPrivate || groupConvo.isPublic || !groupConvo.isGroup) {
|
||||
return undefined;
|
||||
}
|
||||
// this must be a closed group
|
||||
const originalMembers = groupConvo.members;
|
||||
if (!originalMembers || originalMembers.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const allMembersSorted = originalMembers.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
||||
|
||||
// no need to forward more than 2 conversations for rendering the group avatar
|
||||
const usAtTheEndMaxTwo = _.sortBy(allMembersSorted, a => (a === ourPrimary ? 1 : 0)).slice(
|
||||
0,
|
||||
2
|
||||
);
|
||||
const memberConvos = _.compact(
|
||||
usAtTheEndMaxTwo
|
||||
.map(m => state.conversations.conversationLookup[m])
|
||||
.map(m => {
|
||||
return m?.id || undefined;
|
||||
})
|
||||
);
|
||||
|
||||
return memberConvos && memberConvos.length ? memberConvos : undefined;
|
||||
});
|
||||
}
|
|
@ -5,9 +5,9 @@ import { StateType } from '../state/reducer';
|
|||
export function useAvatarPath(pubkey: string | undefined) {
|
||||
return useSelector((state: StateType) => {
|
||||
if (!pubkey) {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
return state.conversations.conversationLookup[pubkey]?.avatarPath;
|
||||
return state.conversations.conversationLookup[pubkey]?.avatarPath || null;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,19 @@ export function useOurAvatarPath() {
|
|||
return useAvatarPath(UserUtils.getOurPubKeyStrFromCache());
|
||||
}
|
||||
|
||||
export function useConversationUsername(pubkey: string | undefined) {
|
||||
/**
|
||||
*
|
||||
* @returns convo.profileName || convo.name || convo.id or undefined if the convo is not found
|
||||
*/
|
||||
export function useConversationUsername(pubkey?: string) {
|
||||
return useSelector((state: StateType) => {
|
||||
if (!pubkey) {
|
||||
return undefined;
|
||||
}
|
||||
const convo = state.conversations.conversationLookup[pubkey];
|
||||
if (!convo) {
|
||||
return pubkey;
|
||||
}
|
||||
return convo?.profileName || convo?.name || convo.id;
|
||||
});
|
||||
}
|
||||
|
@ -28,3 +35,7 @@ export function useConversationUsername(pubkey: string | undefined) {
|
|||
export function useOurConversationUsername() {
|
||||
return useConversationUsername(UserUtils.getOurPubKeyStrFromCache());
|
||||
}
|
||||
|
||||
export function useIsMe(pubkey?: string) {
|
||||
return pubkey && pubkey === UserUtils.getOurPubKeyStrFromCache();
|
||||
}
|
||||
|
|
|
@ -32,8 +32,6 @@ export const cleanUpOldDecryptedMedias = () => {
|
|||
countKept++;
|
||||
}
|
||||
}
|
||||
urlToDecryptedBlobMap.clear();
|
||||
urlToDecryptingPromise.clear();
|
||||
window?.log?.info(`Clean medias blobs: cleaned/kept: ${countCleaned}:${countKept}`);
|
||||
};
|
||||
|
||||
|
|
|
@ -326,6 +326,7 @@ export const _getConversationComparator = (testingi18n?: LocalizerType) => {
|
|||
return collator.compare(leftTitle, rightTitle);
|
||||
};
|
||||
};
|
||||
|
||||
export const getConversationComparator = createSelector(getIntl, _getConversationComparator);
|
||||
|
||||
// export only because we use it in some of our tests
|
||||
|
|
Loading…
Reference in a new issue