// Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; import formatFileSize from 'filesize'; import type { LocalizerType } from '../types/Util'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import type { StorySendStateType, StoryViewType } from '../types/Stories'; import { Avatar, AvatarSize } from './Avatar'; import { ContactName } from './conversation/ContactName'; import { ContextMenu } from './ContextMenu'; import { Intl } from './Intl'; import { Modal } from './Modal'; import { SendStatus } from '../messages/MessageSendState'; import { Theme } from '../util/theme'; import { formatDateTimeLong } from '../util/timestamp'; import { DurationInSeconds } from '../util/durations'; import type { saveAttachment } from '../util/saveAttachment'; import type { AttachmentType } from '../types/Attachment'; import { ThemeType } from '../types/Util'; import { Time } from './Time'; import { groupBy } from '../util/mapUtil'; import { format as formatRelativeTime } from '../util/expirationTimer'; export type PropsType = { getPreferredBadge: PreferredBadgeSelectorType; i18n: LocalizerType; isInternalUser?: boolean; onClose: () => unknown; saveAttachment: typeof saveAttachment; sender: StoryViewType['sender']; sendState?: Array; attachment?: AttachmentType; expirationTimestamp: number | undefined; timestamp: number; }; const contactSortCollator = new window.Intl.Collator(); function getI18nKey(sendStatus: SendStatus | undefined): string { if (sendStatus === SendStatus.Failed) { return 'MessageDetailsHeader--Failed'; } if (sendStatus === SendStatus.Viewed) { return 'MessageDetailsHeader--Viewed'; } if (sendStatus === SendStatus.Read) { return 'MessageDetailsHeader--Read'; } if (sendStatus === SendStatus.Delivered) { return 'MessageDetailsHeader--Delivered'; } if (sendStatus === SendStatus.Sent) { return 'MessageDetailsHeader--Sent'; } if (sendStatus === SendStatus.Pending) { return 'MessageDetailsHeader--Pending'; } return 'from'; } export function StoryDetailsModal({ attachment, getPreferredBadge, i18n, isInternalUser, onClose, saveAttachment, sender, sendState, timestamp, expirationTimestamp, }: PropsType): JSX.Element { // the sender is included in the sendState data // but we don't want to show the sender in the "Sent To" list const actualRecipientsSendState = sendState?.filter( s => s.recipient.id !== sender.id ); const contactsBySendStatus = actualRecipientsSendState ? groupBy(actualRecipientsSendState, contact => contact.status) : undefined; let content: JSX.Element; if (contactsBySendStatus) { content = (
{[ SendStatus.Failed, SendStatus.Viewed, SendStatus.Read, SendStatus.Delivered, SendStatus.Sent, SendStatus.Pending, ].map(sendStatus => { const contacts = contactsBySendStatus.get(sendStatus); if (!contacts) { return null; } const i18nKey = getI18nKey(sendStatus); const sortedContacts = [...contacts].sort((a, b) => contactSortCollator.compare(a.recipient.title, b.recipient.title) ); return (
{i18n(i18nKey)}
{sortedContacts.map(status => { const contact = status.recipient; return (
{status.updatedAt && ( )}
); })}
); })}
); } else { content = (
{i18n('sent')}
); } const timeRemaining = expirationTimestamp ? DurationInSeconds.fromMillis(expirationTimestamp - Date.now()) : undefined; const menuOptions = [ { icon: 'StoryDetailsModal__copy-icon', label: i18n('StoryDetailsModal__copy-timestamp'), onClick: () => { window.navigator.clipboard.writeText(String(timestamp)); }, }, ]; if (isInternalUser && attachment) { menuOptions.push({ icon: 'StoryDetailsModal__download-icon', label: i18n('StoryDetailsModal__download-attachment'), onClick: () => { saveAttachment(attachment); }, }); } return (
{formatDateTimeLong(i18n, timestamp)} , ]} />
{attachment && (
{formatFileSize(attachment.size)} , ]} />
)} {timeRemaining && timeRemaining > 0 && (
{formatRelativeTime(i18n, timeRemaining, { largest: 2, })} , ]} />
)} } > {content}
); }