2021-03-01 20:08:37 +00:00
|
|
|
// Copyright 2018-2021 Signal Messenger, LLC
|
2020-10-30 20:34:04 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
import React, { ReactNode } from 'react';
|
|
|
|
import Measure from 'react-measure';
|
2020-10-30 17:52:21 +00:00
|
|
|
import moment from 'moment';
|
2019-07-31 16:16:29 +00:00
|
|
|
import classNames from 'classnames';
|
2018-07-09 21:29:13 +00:00
|
|
|
import {
|
|
|
|
ContextMenu,
|
|
|
|
ContextMenuTrigger,
|
|
|
|
MenuItem,
|
|
|
|
SubMenu,
|
|
|
|
} from 'react-contextmenu';
|
|
|
|
|
2020-07-24 01:35:32 +00:00
|
|
|
import { Emojify } from './Emojify';
|
2020-12-12 00:45:14 +00:00
|
|
|
import { Avatar, AvatarSize } from '../Avatar';
|
2020-07-24 01:35:32 +00:00
|
|
|
import { InContactsIcon } from '../InContactsIcon';
|
|
|
|
|
2020-08-13 20:53:45 +00:00
|
|
|
import { LocalizerType } from '../../types/Util';
|
2021-04-30 19:40:25 +00:00
|
|
|
import { ConversationType } from '../../state/ducks/conversations';
|
2021-04-01 21:02:22 +00:00
|
|
|
import { MuteOption, getMuteOptions } from '../../util/getMuteOptions';
|
2021-05-03 23:24:40 +00:00
|
|
|
import * as expirationTimer from '../../util/expirationTimer';
|
2020-10-30 17:52:21 +00:00
|
|
|
import { isMuted } from '../../util/isMuted';
|
2020-11-19 16:37:56 +00:00
|
|
|
import { missingCaseError } from '../../util/missingCaseError';
|
|
|
|
|
|
|
|
export enum OutgoingCallButtonStyle {
|
|
|
|
None,
|
|
|
|
JustVideo,
|
|
|
|
Both,
|
2020-11-20 17:19:28 +00:00
|
|
|
Join,
|
2020-11-19 16:37:56 +00:00
|
|
|
}
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2021-01-14 18:07:05 +00:00
|
|
|
export type PropsDataType = {
|
2021-01-29 21:19:24 +00:00
|
|
|
conversationTitle?: string;
|
2020-10-30 17:52:21 +00:00
|
|
|
isMissingMandatoryProfileSharing?: boolean;
|
2020-11-19 16:37:56 +00:00
|
|
|
outgoingCallButtonStyle: OutgoingCallButtonStyle;
|
2021-04-30 19:40:25 +00:00
|
|
|
showBackButton?: boolean;
|
|
|
|
} & Pick<
|
|
|
|
ConversationType,
|
|
|
|
| 'acceptedMessageRequest'
|
|
|
|
| 'avatarPath'
|
|
|
|
| 'canChangeTimer'
|
|
|
|
| 'color'
|
|
|
|
| 'expireTimer'
|
|
|
|
| 'groupVersion'
|
|
|
|
| 'id'
|
|
|
|
| 'isArchived'
|
|
|
|
| 'isMe'
|
|
|
|
| 'isPinned'
|
|
|
|
| 'isVerified'
|
|
|
|
| 'left'
|
|
|
|
| 'markedUnread'
|
|
|
|
| 'muteExpiresAt'
|
|
|
|
| 'name'
|
|
|
|
| 'phoneNumber'
|
|
|
|
| 'profileName'
|
|
|
|
| 'sharedGroupNames'
|
|
|
|
| 'title'
|
|
|
|
| 'type'
|
|
|
|
| 'unblurredAvatarPath'
|
|
|
|
>;
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2021-01-14 18:07:05 +00:00
|
|
|
export type PropsActionsType = {
|
2020-08-27 19:45:08 +00:00
|
|
|
onSetMuteNotifications: (seconds: number) => void;
|
2018-07-09 21:29:13 +00:00
|
|
|
onSetDisappearingMessages: (seconds: number) => void;
|
2021-01-28 00:18:50 +00:00
|
|
|
onShowContactModal: (contactId: string) => void;
|
2018-07-09 21:29:13 +00:00
|
|
|
onDeleteMessages: () => void;
|
|
|
|
onResetSession: () => void;
|
2019-08-09 23:12:29 +00:00
|
|
|
onSearchInConversation: () => void;
|
2020-06-04 18:16:19 +00:00
|
|
|
onOutgoingAudioCallInConversation: () => void;
|
|
|
|
onOutgoingVideoCallInConversation: () => void;
|
2020-10-02 18:30:43 +00:00
|
|
|
onSetPin: (value: boolean) => void;
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2021-01-29 21:19:24 +00:00
|
|
|
onShowConversationDetails: () => void;
|
2018-07-09 21:29:13 +00:00
|
|
|
onShowSafetyNumber: () => void;
|
|
|
|
onShowAllMedia: () => void;
|
|
|
|
onShowGroupMembers: () => void;
|
|
|
|
onGoBack: () => void;
|
2019-03-12 00:20:16 +00:00
|
|
|
|
|
|
|
onArchive: () => void;
|
2020-10-28 22:54:32 +00:00
|
|
|
onMarkUnread: () => void;
|
2019-03-12 00:20:16 +00:00
|
|
|
onMoveToInbox: () => void;
|
2021-01-14 18:07:05 +00:00
|
|
|
};
|
2019-03-12 00:20:16 +00:00
|
|
|
|
2021-01-14 18:07:05 +00:00
|
|
|
export type PropsHousekeepingType = {
|
2019-03-12 00:20:16 +00:00
|
|
|
i18n: LocalizerType;
|
2021-01-14 18:07:05 +00:00
|
|
|
};
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2020-07-24 01:35:32 +00:00
|
|
|
export type PropsType = PropsDataType &
|
|
|
|
PropsActionsType &
|
|
|
|
PropsHousekeepingType;
|
2020-03-26 21:47:35 +00:00
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
type StateType = {
|
|
|
|
isNarrow: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
export class ConversationHeader extends React.Component<PropsType, StateType> {
|
|
|
|
private showMenuBound: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
2020-09-14 19:51:27 +00:00
|
|
|
|
|
|
|
// Comes from a third-party dependency
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
2021-03-01 20:08:37 +00:00
|
|
|
private menuTriggerRef: React.RefObject<any>;
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2020-07-24 01:35:32 +00:00
|
|
|
public constructor(props: PropsType) {
|
2018-07-09 21:29:13 +00:00
|
|
|
super(props);
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
this.state = { isNarrow: false };
|
|
|
|
|
2019-01-14 21:49:58 +00:00
|
|
|
this.menuTriggerRef = React.createRef();
|
2018-07-09 21:29:13 +00:00
|
|
|
this.showMenuBound = this.showMenu.bind(this);
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
private showMenu(event: React.MouseEvent<HTMLButtonElement>): void {
|
2019-01-14 21:49:58 +00:00
|
|
|
if (this.menuTriggerRef.current) {
|
|
|
|
this.menuTriggerRef.current.handleContextClick(event);
|
2018-07-09 21:29:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
private renderBackButton(): ReactNode {
|
2020-09-14 19:51:27 +00:00
|
|
|
const { i18n, onGoBack, showBackButton } = this.props;
|
2018-07-09 21:29:13 +00:00
|
|
|
|
|
|
|
return (
|
2019-07-25 16:24:03 +00:00
|
|
|
<button
|
2020-09-14 19:51:27 +00:00
|
|
|
type="button"
|
2018-07-09 21:29:13 +00:00
|
|
|
onClick={onGoBack}
|
2019-07-31 16:16:29 +00:00
|
|
|
className={classNames(
|
2021-03-01 20:08:37 +00:00
|
|
|
'module-ConversationHeader__back-icon',
|
|
|
|
showBackButton ? 'module-ConversationHeader__back-icon--show' : null
|
2019-07-25 16:24:03 +00:00
|
|
|
)}
|
|
|
|
disabled={!showBackButton}
|
2020-09-14 19:51:27 +00:00
|
|
|
aria-label={i18n('goBack')}
|
2018-07-09 21:29:13 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
private renderHeaderInfoTitle(): ReactNode {
|
|
|
|
const { name, title, type, i18n, isMe } = this.props;
|
2019-01-31 01:45:58 +00:00
|
|
|
|
|
|
|
if (isMe) {
|
|
|
|
return (
|
2021-03-01 20:08:37 +00:00
|
|
|
<div className="module-ConversationHeader__header__info__title">
|
2019-01-31 01:45:58 +00:00
|
|
|
{i18n('noteToSelf')}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2020-07-24 01:35:32 +00:00
|
|
|
const shouldShowIcon = Boolean(name && type === 'direct');
|
|
|
|
|
2018-07-09 21:29:13 +00:00
|
|
|
return (
|
2021-03-01 20:08:37 +00:00
|
|
|
<div className="module-ConversationHeader__header__info__title">
|
2020-07-24 01:35:32 +00:00
|
|
|
<Emojify text={title} />
|
|
|
|
{shouldShowIcon ? (
|
2021-03-01 20:08:37 +00:00
|
|
|
<InContactsIcon
|
|
|
|
className="module-ConversationHeader__header__info__title__in-contacts-icon"
|
|
|
|
i18n={i18n}
|
|
|
|
/>
|
2018-07-09 21:29:13 +00:00
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
private renderHeaderInfoSubtitle(): ReactNode {
|
|
|
|
const expirationNode = this.renderExpirationLength();
|
|
|
|
const verifiedNode = this.renderVerifiedIcon();
|
|
|
|
|
|
|
|
if (expirationNode || verifiedNode) {
|
|
|
|
return (
|
|
|
|
<div className="module-ConversationHeader__header__info__subtitle">
|
|
|
|
{expirationNode}
|
|
|
|
{verifiedNode}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private renderAvatar(): ReactNode {
|
2018-07-09 21:29:13 +00:00
|
|
|
const {
|
2021-04-30 19:40:25 +00:00
|
|
|
acceptedMessageRequest,
|
2018-07-09 21:29:13 +00:00
|
|
|
avatarPath,
|
|
|
|
color,
|
|
|
|
i18n,
|
2020-07-24 01:35:32 +00:00
|
|
|
type,
|
2019-01-31 01:45:58 +00:00
|
|
|
isMe,
|
2018-07-09 21:29:13 +00:00
|
|
|
name,
|
|
|
|
phoneNumber,
|
|
|
|
profileName,
|
2021-04-30 19:40:25 +00:00
|
|
|
sharedGroupNames,
|
2020-07-24 01:35:32 +00:00
|
|
|
title,
|
2021-04-30 19:40:25 +00:00
|
|
|
unblurredAvatarPath,
|
2018-07-09 21:29:13 +00:00
|
|
|
} = this.props;
|
|
|
|
|
|
|
|
return (
|
2021-03-01 20:08:37 +00:00
|
|
|
<span className="module-ConversationHeader__header__avatar">
|
2018-09-27 00:23:17 +00:00
|
|
|
<Avatar
|
2021-04-30 19:40:25 +00:00
|
|
|
acceptedMessageRequest={acceptedMessageRequest}
|
2018-09-27 00:23:17 +00:00
|
|
|
avatarPath={avatarPath}
|
|
|
|
color={color}
|
2020-07-24 01:35:32 +00:00
|
|
|
conversationType={type}
|
2018-09-27 00:23:17 +00:00
|
|
|
i18n={i18n}
|
2021-04-30 19:40:25 +00:00
|
|
|
isMe={isMe}
|
2019-01-31 01:45:58 +00:00
|
|
|
noteToSelf={isMe}
|
2020-07-24 01:35:32 +00:00
|
|
|
title={title}
|
2018-09-27 00:23:17 +00:00
|
|
|
name={name}
|
|
|
|
phoneNumber={phoneNumber}
|
|
|
|
profileName={profileName}
|
2021-04-30 19:40:25 +00:00
|
|
|
sharedGroupNames={sharedGroupNames}
|
2020-12-12 00:45:14 +00:00
|
|
|
size={AvatarSize.THIRTY_TWO}
|
2021-04-30 19:40:25 +00:00
|
|
|
unblurredAvatarPath={unblurredAvatarPath}
|
2018-09-27 00:23:17 +00:00
|
|
|
/>
|
|
|
|
</span>
|
2018-07-09 21:29:13 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
private renderExpirationLength(): ReactNode {
|
|
|
|
const { i18n, expireTimer } = this.props;
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2021-05-03 23:24:40 +00:00
|
|
|
if (!expireTimer) {
|
2018-07-09 21:29:13 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2021-03-01 20:08:37 +00:00
|
|
|
<div className="module-ConversationHeader__header__info__subtitle__expiration">
|
2021-05-03 23:24:40 +00:00
|
|
|
{expirationTimer.format(i18n, expireTimer)}
|
2018-07-09 21:29:13 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
private renderVerifiedIcon(): ReactNode {
|
|
|
|
const { i18n, isVerified } = this.props;
|
|
|
|
|
|
|
|
if (!isVerified) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="module-ConversationHeader__header__info__subtitle__verified">
|
|
|
|
{i18n('verified')}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private renderMoreButton(triggerId: string): ReactNode {
|
2020-09-14 19:51:27 +00:00
|
|
|
const { i18n, showBackButton } = this.props;
|
2018-07-09 21:29:13 +00:00
|
|
|
|
|
|
|
return (
|
2019-01-14 21:49:58 +00:00
|
|
|
<ContextMenuTrigger id={triggerId} ref={this.menuTriggerRef}>
|
2019-07-25 16:24:03 +00:00
|
|
|
<button
|
2020-09-14 19:51:27 +00:00
|
|
|
type="button"
|
2018-07-09 21:29:13 +00:00
|
|
|
onClick={this.showMenuBound}
|
2019-07-31 16:16:29 +00:00
|
|
|
className={classNames(
|
2021-03-03 01:16:04 +00:00
|
|
|
'module-ConversationHeader__button',
|
|
|
|
'module-ConversationHeader__button--more',
|
|
|
|
showBackButton ? null : 'module-ConversationHeader__button--show'
|
2019-07-25 16:24:03 +00:00
|
|
|
)}
|
|
|
|
disabled={showBackButton}
|
2020-09-14 19:51:27 +00:00
|
|
|
aria-label={i18n('moreInfo')}
|
2018-07-09 21:29:13 +00:00
|
|
|
/>
|
|
|
|
</ContextMenuTrigger>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
private renderSearchButton(): ReactNode {
|
2020-09-14 19:51:27 +00:00
|
|
|
const { i18n, onSearchInConversation, showBackButton } = this.props;
|
2019-08-09 23:12:29 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<button
|
2020-09-14 19:51:27 +00:00
|
|
|
type="button"
|
2019-08-09 23:12:29 +00:00
|
|
|
onClick={onSearchInConversation}
|
|
|
|
className={classNames(
|
2021-03-03 01:16:04 +00:00
|
|
|
'module-ConversationHeader__button',
|
|
|
|
'module-ConversationHeader__button--search',
|
|
|
|
showBackButton ? null : 'module-ConversationHeader__button--show'
|
2019-08-09 23:12:29 +00:00
|
|
|
)}
|
|
|
|
disabled={showBackButton}
|
2020-09-14 19:51:27 +00:00
|
|
|
aria-label={i18n('search')}
|
2019-08-09 23:12:29 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
private renderOutgoingCallButtons(): ReactNode {
|
2020-09-14 19:51:27 +00:00
|
|
|
const {
|
|
|
|
i18n,
|
|
|
|
onOutgoingAudioCallInConversation,
|
2020-10-30 17:52:21 +00:00
|
|
|
onOutgoingVideoCallInConversation,
|
2020-11-19 16:37:56 +00:00
|
|
|
outgoingCallButtonStyle,
|
2020-09-14 19:51:27 +00:00
|
|
|
showBackButton,
|
|
|
|
} = this.props;
|
2021-03-01 20:08:37 +00:00
|
|
|
const { isNarrow } = this.state;
|
2020-09-14 19:51:27 +00:00
|
|
|
|
2020-11-19 16:37:56 +00:00
|
|
|
const videoButton = (
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
onClick={onOutgoingVideoCallInConversation}
|
|
|
|
className={classNames(
|
2021-03-03 01:16:04 +00:00
|
|
|
'module-ConversationHeader__button',
|
|
|
|
'module-ConversationHeader__button--video',
|
|
|
|
showBackButton ? null : 'module-ConversationHeader__button--show'
|
2020-11-19 16:37:56 +00:00
|
|
|
)}
|
|
|
|
disabled={showBackButton}
|
|
|
|
aria-label={i18n('makeOutgoingVideoCall')}
|
|
|
|
/>
|
2020-06-04 18:16:19 +00:00
|
|
|
);
|
2020-11-19 16:37:56 +00:00
|
|
|
|
|
|
|
switch (outgoingCallButtonStyle) {
|
|
|
|
case OutgoingCallButtonStyle.None:
|
|
|
|
return null;
|
|
|
|
case OutgoingCallButtonStyle.JustVideo:
|
|
|
|
return videoButton;
|
|
|
|
case OutgoingCallButtonStyle.Both:
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{videoButton}
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
onClick={onOutgoingAudioCallInConversation}
|
|
|
|
className={classNames(
|
2021-03-03 01:16:04 +00:00
|
|
|
'module-ConversationHeader__button',
|
|
|
|
'module-ConversationHeader__button--audio',
|
2020-11-19 16:37:56 +00:00
|
|
|
showBackButton
|
|
|
|
? null
|
2021-03-03 01:16:04 +00:00
|
|
|
: 'module-ConversationHeader__button--show'
|
2020-11-19 16:37:56 +00:00
|
|
|
)}
|
|
|
|
disabled={showBackButton}
|
|
|
|
aria-label={i18n('makeOutgoingCall')}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
);
|
2020-11-20 17:19:28 +00:00
|
|
|
case OutgoingCallButtonStyle.Join:
|
|
|
|
return (
|
|
|
|
<button
|
2021-03-01 20:08:37 +00:00
|
|
|
aria-label={i18n('joinOngoingCall')}
|
2020-11-20 17:19:28 +00:00
|
|
|
type="button"
|
|
|
|
onClick={onOutgoingVideoCallInConversation}
|
|
|
|
className={classNames(
|
2021-03-03 01:16:04 +00:00
|
|
|
'module-ConversationHeader__button',
|
|
|
|
'module-ConversationHeader__button--join-call',
|
|
|
|
showBackButton ? null : 'module-ConversationHeader__button--show'
|
2020-11-20 17:19:28 +00:00
|
|
|
)}
|
|
|
|
disabled={showBackButton}
|
|
|
|
>
|
2021-03-01 20:08:37 +00:00
|
|
|
{isNarrow ? null : i18n('joinOngoingCall')}
|
2020-11-20 17:19:28 +00:00
|
|
|
</button>
|
|
|
|
);
|
2020-11-19 16:37:56 +00:00
|
|
|
default:
|
|
|
|
throw missingCaseError(outgoingCallButtonStyle);
|
|
|
|
}
|
2020-06-04 18:16:19 +00:00
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
private renderMenu(triggerId: string): ReactNode {
|
2018-07-09 21:29:13 +00:00
|
|
|
const {
|
|
|
|
i18n,
|
2020-10-28 21:54:33 +00:00
|
|
|
acceptedMessageRequest,
|
2020-10-30 17:52:21 +00:00
|
|
|
canChangeTimer,
|
|
|
|
isArchived,
|
2018-07-09 21:29:13 +00:00
|
|
|
isMe,
|
2020-10-02 18:30:43 +00:00
|
|
|
isPinned,
|
2020-07-24 01:35:32 +00:00
|
|
|
type,
|
2020-10-28 22:54:32 +00:00
|
|
|
markedUnread,
|
2020-10-30 17:52:21 +00:00
|
|
|
muteExpiresAt,
|
|
|
|
isMissingMandatoryProfileSharing,
|
|
|
|
left,
|
2021-01-29 21:19:24 +00:00
|
|
|
groupVersion,
|
2018-07-09 21:29:13 +00:00
|
|
|
onDeleteMessages,
|
|
|
|
onResetSession,
|
|
|
|
onSetDisappearingMessages,
|
2020-08-27 19:45:08 +00:00
|
|
|
onSetMuteNotifications,
|
2018-07-09 21:29:13 +00:00
|
|
|
onShowAllMedia,
|
2021-01-29 21:19:24 +00:00
|
|
|
onShowConversationDetails,
|
2018-07-09 21:29:13 +00:00
|
|
|
onShowGroupMembers,
|
|
|
|
onShowSafetyNumber,
|
2019-03-12 00:20:16 +00:00
|
|
|
onArchive,
|
2020-10-28 22:54:32 +00:00
|
|
|
onMarkUnread,
|
2020-10-02 18:30:43 +00:00
|
|
|
onSetPin,
|
2019-03-12 00:20:16 +00:00
|
|
|
onMoveToInbox,
|
2018-07-09 21:29:13 +00:00
|
|
|
} = this.props;
|
|
|
|
|
2021-04-01 21:02:22 +00:00
|
|
|
const muteOptions: Array<MuteOption> = [];
|
2020-10-30 17:52:21 +00:00
|
|
|
if (isMuted(muteExpiresAt)) {
|
|
|
|
const expires = moment(muteExpiresAt);
|
2021-04-09 16:19:38 +00:00
|
|
|
|
|
|
|
let muteExpirationLabel: string;
|
|
|
|
if (Number(muteExpiresAt) >= Number.MAX_SAFE_INTEGER) {
|
|
|
|
muteExpirationLabel = i18n('muteExpirationLabelAlways');
|
|
|
|
} else {
|
|
|
|
const muteExpirationUntil = moment().isSame(expires, 'day')
|
|
|
|
? expires.format('hh:mm A')
|
|
|
|
: expires.format('M/D/YY, hh:mm A');
|
|
|
|
|
|
|
|
muteExpirationLabel = i18n('muteExpirationLabel', [
|
|
|
|
muteExpirationUntil,
|
|
|
|
]);
|
|
|
|
}
|
2020-10-30 17:52:21 +00:00
|
|
|
|
2020-08-27 19:45:08 +00:00
|
|
|
muteOptions.push(
|
|
|
|
...[
|
|
|
|
{
|
2021-04-09 16:19:38 +00:00
|
|
|
name: muteExpirationLabel,
|
2020-08-27 19:45:08 +00:00
|
|
|
disabled: true,
|
|
|
|
value: 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: i18n('unmute'),
|
|
|
|
value: 0,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
muteOptions.push(...getMuteOptions(i18n));
|
|
|
|
|
2020-09-14 19:51:27 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
2018-08-01 18:59:49 +00:00
|
|
|
const disappearingTitle = i18n('disappearingMessages') as any;
|
2020-09-14 19:51:27 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
2020-08-27 19:45:08 +00:00
|
|
|
const muteTitle = i18n('muteNotificationsTitle') as any;
|
2020-07-24 01:35:32 +00:00
|
|
|
const isGroup = type === 'group';
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2020-10-30 17:52:21 +00:00
|
|
|
const disableTimerChanges = Boolean(
|
|
|
|
!canChangeTimer ||
|
|
|
|
!acceptedMessageRequest ||
|
|
|
|
left ||
|
|
|
|
isMissingMandatoryProfileSharing
|
|
|
|
);
|
|
|
|
|
2021-03-11 22:59:10 +00:00
|
|
|
const hasGV2AdminEnabled = isGroup && groupVersion === 2;
|
2021-01-29 21:19:24 +00:00
|
|
|
|
2018-07-09 21:29:13 +00:00
|
|
|
return (
|
|
|
|
<ContextMenu id={triggerId}>
|
2020-09-09 02:25:05 +00:00
|
|
|
{disableTimerChanges ? null : (
|
2020-03-26 21:47:35 +00:00
|
|
|
<SubMenu title={disappearingTitle}>
|
2021-05-03 23:24:40 +00:00
|
|
|
{expirationTimer.DEFAULT_DURATIONS_IN_SECONDS.map(
|
|
|
|
(seconds: number) => (
|
|
|
|
<MenuItem
|
|
|
|
key={seconds}
|
|
|
|
onClick={() => {
|
|
|
|
onSetDisappearingMessages(seconds);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{expirationTimer.format(i18n, seconds)}
|
|
|
|
</MenuItem>
|
|
|
|
)
|
|
|
|
)}
|
2020-03-26 21:47:35 +00:00
|
|
|
</SubMenu>
|
2020-09-09 02:25:05 +00:00
|
|
|
)}
|
2020-10-28 22:54:32 +00:00
|
|
|
<SubMenu title={muteTitle}>
|
|
|
|
{muteOptions.map(item => (
|
|
|
|
<MenuItem
|
|
|
|
key={item.name}
|
|
|
|
disabled={item.disabled}
|
|
|
|
onClick={() => {
|
|
|
|
onSetMuteNotifications(item.value);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{item.name}
|
|
|
|
</MenuItem>
|
|
|
|
))}
|
|
|
|
</SubMenu>
|
2021-01-29 21:19:24 +00:00
|
|
|
{hasGV2AdminEnabled ? (
|
|
|
|
<MenuItem onClick={onShowConversationDetails}>
|
|
|
|
{i18n('showConversationDetails')}
|
|
|
|
</MenuItem>
|
|
|
|
) : null}
|
|
|
|
{isGroup && !hasGV2AdminEnabled ? (
|
2018-07-09 21:29:13 +00:00
|
|
|
<MenuItem onClick={onShowGroupMembers}>
|
|
|
|
{i18n('showMembers')}
|
|
|
|
</MenuItem>
|
|
|
|
) : null}
|
2020-10-28 22:54:32 +00:00
|
|
|
<MenuItem onClick={onShowAllMedia}>{i18n('viewRecentMedia')}</MenuItem>
|
2018-07-09 21:29:13 +00:00
|
|
|
{!isGroup && !isMe ? (
|
|
|
|
<MenuItem onClick={onShowSafetyNumber}>
|
|
|
|
{i18n('showSafetyNumber')}
|
|
|
|
</MenuItem>
|
|
|
|
) : null}
|
2020-10-28 21:54:33 +00:00
|
|
|
{!isGroup && acceptedMessageRequest ? (
|
2018-07-09 21:29:13 +00:00
|
|
|
<MenuItem onClick={onResetSession}>{i18n('resetSession')}</MenuItem>
|
|
|
|
) : null}
|
2020-10-28 22:54:32 +00:00
|
|
|
<MenuItem divider />
|
|
|
|
{!markedUnread ? (
|
|
|
|
<MenuItem onClick={onMarkUnread}>{i18n('markUnread')}</MenuItem>
|
|
|
|
) : null}
|
2019-03-12 00:20:16 +00:00
|
|
|
{isArchived ? (
|
|
|
|
<MenuItem onClick={onMoveToInbox}>
|
|
|
|
{i18n('moveConversationToInbox')}
|
|
|
|
</MenuItem>
|
|
|
|
) : (
|
|
|
|
<MenuItem onClick={onArchive}>{i18n('archiveConversation')}</MenuItem>
|
|
|
|
)}
|
2018-07-09 21:29:13 +00:00
|
|
|
<MenuItem onClick={onDeleteMessages}>{i18n('deleteMessages')}</MenuItem>
|
2020-10-28 22:54:32 +00:00
|
|
|
{isPinned ? (
|
|
|
|
<MenuItem onClick={() => onSetPin(false)}>
|
|
|
|
{i18n('unpinConversation')}
|
|
|
|
</MenuItem>
|
|
|
|
) : (
|
|
|
|
<MenuItem onClick={() => onSetPin(true)}>
|
|
|
|
{i18n('pinConversation')}
|
|
|
|
</MenuItem>
|
|
|
|
)}
|
2018-07-09 21:29:13 +00:00
|
|
|
</ContextMenu>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
private renderHeader(): ReactNode {
|
2021-01-29 21:19:24 +00:00
|
|
|
const {
|
|
|
|
conversationTitle,
|
2021-02-01 22:57:42 +00:00
|
|
|
groupVersion,
|
2021-01-29 21:19:24 +00:00
|
|
|
id,
|
|
|
|
isMe,
|
|
|
|
onShowContactModal,
|
2021-02-01 22:57:42 +00:00
|
|
|
onShowConversationDetails,
|
2021-01-29 21:19:24 +00:00
|
|
|
type,
|
|
|
|
} = this.props;
|
|
|
|
|
2021-02-01 22:57:42 +00:00
|
|
|
if (conversationTitle !== undefined) {
|
2021-01-29 21:19:24 +00:00
|
|
|
return (
|
2021-03-01 20:08:37 +00:00
|
|
|
<div className="module-ConversationHeader__header">
|
|
|
|
<div className="module-ConversationHeader__header__info">
|
|
|
|
<div className="module-ConversationHeader__header__info__title">
|
|
|
|
{conversationTitle}
|
|
|
|
</div>
|
2021-01-29 21:19:24 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2021-01-28 00:18:50 +00:00
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
let onClick: undefined | (() => void);
|
|
|
|
switch (type) {
|
|
|
|
case 'direct':
|
|
|
|
onClick = isMe
|
|
|
|
? undefined
|
|
|
|
: () => {
|
|
|
|
onShowContactModal(id);
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case 'group': {
|
2021-03-11 22:59:10 +00:00
|
|
|
const hasGV2AdminEnabled = groupVersion === 2;
|
2021-03-01 20:08:37 +00:00
|
|
|
onClick = hasGV2AdminEnabled
|
|
|
|
? () => {
|
|
|
|
onShowConversationDetails();
|
|
|
|
}
|
|
|
|
: undefined;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
throw missingCaseError(type);
|
|
|
|
}
|
2021-02-01 22:57:42 +00:00
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
const contents = (
|
|
|
|
<>
|
|
|
|
{this.renderAvatar()}
|
|
|
|
<div className="module-ConversationHeader__header__info">
|
|
|
|
{this.renderHeaderInfoTitle()}
|
|
|
|
{this.renderHeaderInfoSubtitle()}
|
2021-02-01 22:57:42 +00:00
|
|
|
</div>
|
2021-03-01 20:08:37 +00:00
|
|
|
</>
|
|
|
|
);
|
2021-02-01 22:57:42 +00:00
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
if (onClick) {
|
2021-01-28 00:18:50 +00:00
|
|
|
return (
|
2021-03-01 20:08:37 +00:00
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
className="module-ConversationHeader__header module-ConversationHeader__header--clickable"
|
|
|
|
onClick={onClick}
|
|
|
|
>
|
|
|
|
{contents}
|
|
|
|
</button>
|
2021-01-28 00:18:50 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
return <div className="module-ConversationHeader__header">{contents}</div>;
|
2021-01-28 00:18:50 +00:00
|
|
|
}
|
|
|
|
|
2021-03-01 20:08:37 +00:00
|
|
|
public render(): ReactNode {
|
2018-07-09 21:29:13 +00:00
|
|
|
const { id } = this.props;
|
2021-03-01 20:08:37 +00:00
|
|
|
const { isNarrow } = this.state;
|
2019-01-14 21:49:58 +00:00
|
|
|
const triggerId = `conversation-${id}`;
|
2018-07-09 21:29:13 +00:00
|
|
|
|
|
|
|
return (
|
2021-03-01 20:08:37 +00:00
|
|
|
<Measure
|
|
|
|
bounds
|
|
|
|
onResize={({ bounds }) => {
|
2021-03-03 01:16:04 +00:00
|
|
|
if (!bounds || !bounds.width) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.setState({ isNarrow: bounds.width < 500 });
|
2021-03-01 20:08:37 +00:00
|
|
|
}}
|
|
|
|
>
|
|
|
|
{({ measureRef }) => (
|
|
|
|
<div
|
|
|
|
className={classNames('module-ConversationHeader', {
|
|
|
|
'module-ConversationHeader--narrow': isNarrow,
|
|
|
|
})}
|
|
|
|
ref={measureRef}
|
|
|
|
>
|
|
|
|
{this.renderBackButton()}
|
|
|
|
{this.renderHeader()}
|
|
|
|
{this.renderOutgoingCallButtons()}
|
|
|
|
{this.renderSearchButton()}
|
|
|
|
{this.renderMoreButton(triggerId)}
|
|
|
|
{this.renderMenu(triggerId)}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</Measure>
|
2018-07-09 21:29:13 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|