/* Copyright 2024 New Vector Ltd. Copyright 2022 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import React, { PropsWithChildren } from "react"; import { User } from "matrix-js-sdk/src/matrix"; import { Tooltip } from "@vector-im/compound-web"; import ReadReceiptMarker, { IReadReceiptPosition } from "./ReadReceiptMarker"; import { IReadReceiptProps } from "./EventTile"; import AccessibleButton from "../elements/AccessibleButton"; import MemberAvatar from "../avatars/MemberAvatar"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import { formatDate } from "../../../DateUtils"; import { Action } from "../../../dispatcher/actions"; import dis from "../../../dispatcher/dispatcher"; import ContextMenu, { aboveLeftOf, MenuItem, useContextMenu } from "../../structures/ContextMenu"; import { _t } from "../../../languageHandler"; import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex"; import { formatList } from "../../../utils/FormattingUtils"; // #20547 Design specified that we should show the three latest read receipts const MAX_READ_AVATARS_PLUS_N = 3; // #21935 If we’ve got just 4, don’t show +1, just show all of them const MAX_READ_AVATARS = MAX_READ_AVATARS_PLUS_N + 1; const READ_AVATAR_OFFSET = 10; export const READ_AVATAR_SIZE = 16; interface Props { readReceipts: IReadReceiptProps[]; readReceiptMap: { [userId: string]: IReadReceiptPosition }; checkUnmounting?: () => boolean; suppressAnimation: boolean; isTwelveHour?: boolean; } interface IAvatarPosition { hidden: boolean; position: number; } export function determineAvatarPosition(index: number, max: number): IAvatarPosition { if (index < max) { return { hidden: false, position: index, }; } else { return { hidden: true, position: 0, }; } } export function readReceiptTooltip(members: string[], maxAvatars: number): string | undefined { return formatList(members, maxAvatars); } export function ReadReceiptGroup({ readReceipts, readReceiptMap, checkUnmounting, suppressAnimation, isTwelveHour, }: Props): JSX.Element { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); // If we are above MAX_READ_AVATARS, we’ll have to remove a few to have space for the +n count. const hasMore = readReceipts.length > MAX_READ_AVATARS; const maxAvatars = hasMore ? MAX_READ_AVATARS_PLUS_N : MAX_READ_AVATARS; const tooltipMembers: string[] = readReceipts.map((it) => it.roomMember?.name ?? it.userId); const tooltipText = readReceiptTooltip(tooltipMembers, maxAvatars); // return early if there are no read receipts if (readReceipts.length === 0) { // We currently must include `mx_ReadReceiptGroup_container` in // the DOM of all events, as it is the positioned parent of the // animated read receipts. We can't let it unmount when a receipt // moves events, so for now we mount it for all events. Without // it, the animation will start from the top of the timeline // (because it lost its container). // See also https://github.com/vector-im/element-web/issues/17561 return (