// Copyright 2018-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { ReactNode } from 'react'; import React from 'react'; import Measure from 'react-measure'; import classNames from 'classnames'; import { ContextMenu, ContextMenuTrigger, MenuItem, SubMenu, } from 'react-contextmenu'; import { Emojify } from './Emojify'; import { DisappearingTimeDialog } from '../DisappearingTimeDialog'; import { Avatar, AvatarSize } from '../Avatar'; import { InContactsIcon } from '../InContactsIcon'; import type { LocalizerType, ThemeType } from '../../types/Util'; import type { ConversationType } from '../../state/ducks/conversations'; import type { BadgeType } from '../../badges/types'; import type { HasStories } from '../../types/Stories'; import type { ViewUserStoriesActionCreatorType } from '../../state/ducks/stories'; import { StoryViewModeType } from '../../types/Stories'; import { getMuteOptions } from '../../util/getMuteOptions'; import * as expirationTimer from '../../util/expirationTimer'; import { missingCaseError } from '../../util/missingCaseError'; import { isInSystemContacts } from '../../util/isInSystemContacts'; import { useStartCallShortcuts, useKeyboardShortcuts, } from '../../hooks/useKeyboardShortcuts'; export enum OutgoingCallButtonStyle { None, JustVideo, Both, Join, } export type PropsDataType = { badge?: BadgeType; conversationTitle?: string; hasStories?: HasStories; isMissingMandatoryProfileSharing?: boolean; outgoingCallButtonStyle: OutgoingCallButtonStyle; showBackButton?: boolean; isSMSOnly?: boolean; theme: ThemeType; } & Pick< ConversationType, | 'acceptedMessageRequest' | 'announcementsOnly' | 'areWeAdmin' | 'avatarPath' | 'canChangeTimer' | 'color' | 'expireTimer' | 'groupVersion' | 'id' | 'isArchived' | 'isMe' | 'isPinned' | 'isVerified' | 'left' | 'markedUnread' | 'muteExpiresAt' | 'name' | 'phoneNumber' | 'profileName' | 'sharedGroupNames' | 'title' | 'type' | 'unblurredAvatarPath' >; export type PropsActionsType = { onSetMuteNotifications: (seconds: number) => void; onSetDisappearingMessages: (seconds: number) => void; onDeleteMessages: () => void; onSearchInConversation: () => void; onOutgoingAudioCallInConversation: () => void; onOutgoingVideoCallInConversation: () => void; onSetPin: (value: boolean) => void; onShowConversationDetails: () => void; onShowAllMedia: () => void; onShowGroupMembers: () => void; onGoBack: () => void; onArchive: () => void; onMarkUnread: () => void; onMoveToInbox: () => void; viewUserStories: ViewUserStoriesActionCreatorType; }; export type PropsHousekeepingType = { i18n: LocalizerType; }; export type PropsType = PropsDataType & PropsActionsType & PropsHousekeepingType; enum ModalState { NothingOpen, CustomDisappearingTimeout, } type StateType = { isNarrow: boolean; modalState: ModalState; }; const TIMER_ITEM_CLASS = 'module-ConversationHeader__disappearing-timer__item'; export class ConversationHeader extends React.Component { private showMenuBound: (event: React.MouseEvent) => void; // Comes from a third-party dependency // eslint-disable-next-line @typescript-eslint/no-explicit-any private menuTriggerRef: React.RefObject; public headerRef: React.RefObject; public constructor(props: PropsType) { super(props); this.state = { isNarrow: false, modalState: ModalState.NothingOpen }; this.menuTriggerRef = React.createRef(); this.headerRef = React.createRef(); this.showMenuBound = this.showMenu.bind(this); } private showMenu(event: React.MouseEvent): void { if (this.menuTriggerRef.current) { this.menuTriggerRef.current.handleContextClick(event); } } private renderBackButton(): ReactNode { const { i18n, onGoBack, showBackButton } = this.props; return ( ); } return (
{avatar} {contents}
); } public override render(): ReactNode { const { announcementsOnly, areWeAdmin, expireTimer, i18n, id, isSMSOnly, onOutgoingAudioCallInConversation, onOutgoingVideoCallInConversation, onSetDisappearingMessages, outgoingCallButtonStyle, showBackButton, } = this.props; const { isNarrow, modalState } = this.state; const triggerId = `conversation-${id}`; let modalNode: ReactNode; if (modalState === ModalState.NothingOpen) { modalNode = undefined; } else if (modalState === ModalState.CustomDisappearingTimeout) { modalNode = ( { this.setState({ modalState: ModalState.NothingOpen }); onSetDisappearingMessages(value); }} onClose={() => this.setState({ modalState: ModalState.NothingOpen })} /> ); } else { throw missingCaseError(modalState); } return ( <> {modalNode} { if (!bounds || !bounds.width) { return; } this.setState({ isNarrow: bounds.width < 500 }); }} > {({ measureRef }) => (
{this.renderBackButton()} {this.renderHeader()} {!isSMSOnly && ( )} {this.renderSearchButton()} {this.renderMoreButton(triggerId)} {this.renderMenu(triggerId)}
)}
); } } function OutgoingCallButtons({ announcementsOnly, areWeAdmin, i18n, isNarrow, onOutgoingAudioCallInConversation, onOutgoingVideoCallInConversation, outgoingCallButtonStyle, showBackButton, }: { isNarrow: boolean } & Pick< PropsType, | 'announcementsOnly' | 'areWeAdmin' | 'i18n' | 'onOutgoingAudioCallInConversation' | 'onOutgoingVideoCallInConversation' | 'outgoingCallButtonStyle' | 'showBackButton' >): JSX.Element | null { const videoButton = ( ); default: throw missingCaseError(outgoingCallButtonStyle); } }