// Copyright 2018 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { ReactChild } from 'react'; import React, { forwardRef, useCallback, useState } from 'react'; import classNames from 'classnames'; import type { LocalizerType } from '../../types/Util'; import type { DirectionType, MessageStatusType } from './Message'; import type { PushPanelForConversationActionType } from '../../state/ducks/conversations'; import { missingCaseError } from '../../util/missingCaseError'; import { ExpireTimer } from './ExpireTimer'; import { MessageTimestamp } from './MessageTimestamp'; import { PanelType } from '../../types/Panels'; import { Spinner } from '../Spinner'; import { ConfirmationDialog } from '../ConfirmationDialog'; import { refMerger } from '../../util/refMerger'; import type { Size } from '../../hooks/useSizeObserver'; import { SizeObserver } from '../../hooks/useSizeObserver'; type PropsType = { deletedForEveryone?: boolean; direction: DirectionType; expirationLength?: number; expirationTimestamp?: number; hasText: boolean; i18n: LocalizerType; id: string; isEditedMessage?: boolean; isInline?: boolean; isOutlineOnlyBubble?: boolean; isShowingImage: boolean; isSticker?: boolean; isTapToViewExpired?: boolean; onWidthMeasured?: (width: number) => unknown; pushPanelForConversation: PushPanelForConversationActionType; retryMessageSend: (messageId: string) => unknown; showEditHistoryModal?: (id: string) => unknown; status?: MessageStatusType; textPending?: boolean; timestamp: number; }; enum ConfirmationType { EditError = 'EditError', } export const MessageMetadata = forwardRef>( function MessageMetadataInner( { deletedForEveryone, direction, expirationLength, expirationTimestamp, hasText, i18n, id, isEditedMessage, isOutlineOnlyBubble, isInline, isShowingImage, isSticker, isTapToViewExpired, onWidthMeasured, pushPanelForConversation, retryMessageSend, showEditHistoryModal, status, textPending, timestamp, }, ref ) { const [confirmationType, setConfirmationType] = useState< ConfirmationType | undefined >(); const withImageNoCaption = Boolean( !isSticker && !hasText && isShowingImage ); const metadataDirection = isSticker ? undefined : direction; let timestampNode: ReactChild; { const isError = status === 'error' && direction === 'outgoing'; const isPartiallySent = status === 'partial-sent' && direction === 'outgoing'; const isPaused = status === 'paused'; if (isError || isPartiallySent || isPaused) { let statusInfo: React.ReactChild; if (isError) { if (deletedForEveryone) { statusInfo = i18n('icu:deleteFailed'); } else if (isEditedMessage) { statusInfo = ( ); } else { statusInfo = i18n('icu:sendFailed'); } } else if (isPaused) { statusInfo = i18n('icu:sendPaused'); } else { statusInfo = ( ); } timestampNode = ( {statusInfo} ); } else { timestampNode = ( ); } } let confirmation: JSX.Element | undefined; if (confirmationType === undefined) { // no-op } else if (confirmationType === ConfirmationType.EditError) { confirmation = ( { retryMessageSend(id); setConfirmationType(undefined); }, style: 'negative', text: i18n('icu:ResendMessageEdit__button'), }, ]} i18n={i18n} onClose={() => { setConfirmationType(undefined); }} > {i18n('icu:ResendMessageEdit__body')} ); } else { throw missingCaseError(confirmationType); } const className = classNames( 'module-message__metadata', isInline && 'module-message__metadata--inline', withImageNoCaption && 'module-message__metadata--with-image-no-caption', isOutlineOnlyBubble && 'module-message__metadata--outline-only-bubble' ); const children = ( <> {isEditedMessage && showEditHistoryModal && ( )} {timestampNode} {expirationLength ? ( ) : null} {textPending ? (
) : null} {(!deletedForEveryone || status === 'sending') && !textPending && direction === 'outgoing' && status !== 'error' && status !== 'partial-sent' ? (
) : null} {confirmation} ); const onResize = useCallback( (size: Size) => { onWidthMeasured?.(size.width); }, [onWidthMeasured] ); if (onWidthMeasured) { return ( {measureRef => (
{children}
)}
); } return (
{children}
); } );