/* * Wire * Copyright (C) 2022 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * */ import {FC, useMemo, useState} from 'react'; import cx from 'classnames'; import {FadingScrollbar} from 'Components/FadingScrollbar'; import * as Icon from 'Components/Icon'; import {UserList} from 'Components/UserList'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {t} from 'Util/LocalizerUtil'; import {formatLocale} from 'Util/TimeUtil'; import {UsersReactions} from './UserReactions'; import {ConversationRepository} from '../../../conversation/ConversationRepository'; import {Conversation} from '../../../entity/Conversation'; import {ContentMessage} from '../../../entity/message/ContentMessage'; import {User} from '../../../entity/User'; import {SuperType} from '../../../message/SuperType'; import {UserRepository} from '../../../user/UserRepository'; import {PanelHeader} from '../PanelHeader'; import {PanelEntity, PanelState} from '../RightSidebar'; const MESSAGE_STATES = { REACTIONS: 'reactions', NO_REACTIONS: 'no-reactions', NO_RECEIPTS: 'no-receipts', RECEIPTS: 'receipts', RECEIPTS_OFF: 'receipts-off', }; const formatUserCount = (users: User[]): string => (users.length ? ` (${users.length})` : ''); const sortUsers = (userA: User, userB: User): number => userA.name().localeCompare(userB.name(), undefined, {sensitivity: 'base'}); const formatTime = (time: string | number | Date) => formatLocale(time, 'P, p'); interface MessageDetailsProps { activeConversation: Conversation; onClose: () => void; conversationRepository: ConversationRepository; messageEntity: ContentMessage; userRepository: UserRepository; showReactions?: boolean; selfUser: User; togglePanel: (state: PanelState, entity: PanelEntity, addMode?: boolean) => void; } const MessageDetails: FC = ({ activeConversation, conversationRepository, messageEntity, showReactions = false, userRepository, selfUser, onClose, togglePanel, }) => { const [isReceiptsOpen, setIsReceiptsOpen] = useState(!showReactions); const { timestamp, user: messageSender, reactions, readReceipts, edited_timestamp: editedTimestamp, } = useKoSubscribableChildren(messageEntity, ['timestamp', 'user', 'reactions', 'readReceipts', 'edited_timestamp']); const totalNbReactions = reactions.reduce((acc, [, users]) => acc + users.length, 0); const teamId = activeConversation.teamId; const supportsReceipts = messageSender.isMe && teamId; const receiptUsers = userRepository .findUsersByIds(readReceipts.map(({userId, domain}) => ({domain: domain || '', id: userId}))) .sort(sortUsers); const supportsReactions = useMemo(() => { const isPing = messageEntity.super_type === SuperType.PING; const isEphemeral = messageEntity?.isEphemeral(); return !isPing && !isEphemeral; }, [messageEntity]); const messageState = useMemo(() => { if (supportsReceipts && isReceiptsOpen) { if (!messageEntity.expectsReadConfirmation) { return MESSAGE_STATES.RECEIPTS_OFF; } return receiptUsers.length ? MESSAGE_STATES.RECEIPTS : MESSAGE_STATES.NO_RECEIPTS; } return reactions.length > 0 ? MESSAGE_STATES.REACTIONS : MESSAGE_STATES.NO_REACTIONS; }, [supportsReceipts, isReceiptsOpen, reactions.length, messageEntity.expectsReadConfirmation, receiptUsers.length]); const receiptTimes = useMemo(() => { return readReceipts.reduce>((times, {userId, time}) => { times[userId] = formatTime(time); return times; }, {}); }, [readReceipts]); const sentFooter = timestamp ? formatTime(timestamp) : ''; const receiptsTitle = t( 'messageDetailsTitleReceipts', messageEntity?.expectsReadConfirmation ? formatUserCount(receiptUsers) : '', ); const reactionsTitle = t('messageDetailsTitleReactions', totalNbReactions > 0 ? ` (${totalNbReactions})` : ''); const panelTitle = useMemo(() => { if (!supportsReceipts) { return reactionsTitle; } if (!supportsReactions) { return receiptsTitle; } return t('messageDetailsTitle'); }, [supportsReceipts, supportsReactions, reactionsTitle, receiptsTitle]); const showTabs = supportsReceipts && supportsReactions; const editedFooter = editedTimestamp ? formatTime(editedTimestamp) : ''; const onReceipts = () => setIsReceiptsOpen(true); const onReactions = () => setIsReceiptsOpen(false); const onParticipantClick = (userEntity: User) => togglePanel(PanelState.GROUP_PARTICIPANT_USER, userEntity); return (
{showTabs && (
)} {messageState === MESSAGE_STATES.RECEIPTS && (
)} {messageState === MESSAGE_STATES.REACTIONS && ( userRepository.findUsersByIds(ids)} onParticipantClick={onParticipantClick} /> )} {messageState === MESSAGE_STATES.NO_RECEIPTS && (

{t('messageDetailsNoReceipts')}

)} {messageState === MESSAGE_STATES.NO_REACTIONS && (

{t('messageDetailsNoReactions')}

)} {messageState === MESSAGE_STATES.RECEIPTS_OFF && (

{t('messageDetailsReceiptsOff')}

)}

{t('messageDetailsSent', sentFooter)}

{editedFooter && (

{t('messageDetailsEdited', editedFooter)}

)}
); }; export {MessageDetails};