import type { FC } from '../../lib/teact/teact'; import React, { memo, useEffect, useMemo, useRef, useState, } from '../../lib/teact/teact'; import { getActions, getGlobal, withGlobal } from '../../global'; import type { ApiAvailableReaction, ApiMessage, ApiReaction } from '../../api/types'; import { LoadMoreDirection } from '../../types'; import { getReactionUniqueKey, isSameReaction } from '../../global/helpers'; import { selectChatMessage, selectTabState, } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; import { formatDateAtTime } from '../../util/dateFormat'; import { unique } from '../../util/iteratees'; import { formatIntegerCompact } from '../../util/textFormat'; import useFlag from '../../hooks/useFlag'; import useInfiniteScroll from '../../hooks/useInfiniteScroll'; import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; import Avatar from '../common/Avatar'; import FullNameTitle from '../common/FullNameTitle'; import PrivateChatInfo from '../common/PrivateChatInfo'; import ReactionStaticEmoji from '../common/ReactionStaticEmoji'; import Button from '../ui/Button'; import InfiniteScroll from '../ui/InfiniteScroll'; import ListItem from '../ui/ListItem'; import Loading from '../ui/Loading'; import Modal from '../ui/Modal'; import './ReactorListModal.scss'; const MIN_REACTIONS_COUNT_FOR_FILTERS = 10; export type OwnProps = { isOpen: boolean; }; export type StateProps = Pick & { chatId?: string; messageId?: number; availableReactions?: ApiAvailableReaction[]; }; const ReactorListModal: FC = ({ isOpen, reactors, reactions, chatId, messageId, seenByDates, availableReactions, }) => { const { loadReactors, closeReactorListModal, openChat, } = getActions(); // No need for expensive global updates on chats or users, so we avoid them const chatsById = getGlobal().chats.byId; const usersById = getGlobal().users.byId; const lang = useLang(); const [isClosing, startClosing, stopClosing] = useFlag(false); const [chosenTab, setChosenTab] = useState(undefined); const canShowFilters = reactors && reactions && reactors.count >= MIN_REACTIONS_COUNT_FOR_FILTERS && reactions.results.length > 1; const chatIdRef = useRef(); useEffect(() => { if (isOpen && !isClosing) { chatIdRef.current = undefined; } if (isClosing && !isOpen) { stopClosing(); setChosenTab(undefined); } }, [isClosing, isOpen, stopClosing]); const handleCloseAnimationEnd = useLastCallback(() => { if (chatIdRef.current) { openChat({ id: chatIdRef.current }); } closeReactorListModal(); }); const handleClose = useLastCallback(() => { startClosing(); }); const handleClick = useLastCallback((userId: string) => { chatIdRef.current = userId; handleClose(); }); const handleLoadMore = useLastCallback(() => { loadReactors({ chatId: chatId!, messageId: messageId!, }); }); const allReactions = useMemo(() => { const uniqueReactions: ApiReaction[] = []; reactors?.reactions?.forEach(({ reaction }) => { if (!uniqueReactions.some((r) => isSameReaction(r, reaction))) { uniqueReactions.push(reaction); } }); return uniqueReactions; }, [reactors]); const peerIds = useMemo(() => { if (chosenTab) { return reactors?.reactions .filter(({ reaction }) => isSameReaction(reaction, chosenTab)) .map(({ peerId }) => peerId); } const seenByUserIds = Object.keys(seenByDates || {}); return unique(reactors?.reactions.map(({ peerId }) => peerId).concat(seenByUserIds || []) || []); }, [chosenTab, reactors, seenByDates]); const [viewportIds, getMore] = useInfiniteScroll( handleLoadMore, peerIds, reactors && reactors.nextOffset === undefined, ); useEffect(() => { getMore?.({ direction: LoadMoreDirection.Backwards }); }, [getMore]); return ( {canShowFilters && (
{allReactions.map((reaction) => { const count = reactions?.results .find((reactionsCount) => isSameReaction(reactionsCount.reaction, reaction))?.count; return ( ); })}
)}
{viewportIds?.length ? ( {viewportIds?.flatMap( (peerId) => { const peer = usersById[peerId] || chatsById[peerId]; const peerReactions = reactors?.reactions.filter((reactor) => reactor.peerId === peerId); const items: React.ReactNode[] = []; const seenByUser = seenByDates?.[peerId]; peerReactions?.forEach((r) => { if (chosenTab && !isSameReaction(r.reaction, chosenTab)) return; items.push( handleClick(peerId)} >
{formatDateAtTime(lang, r.addedDate * 1000)}
{r.reaction && ( )}
, ); }); if (!chosenTab && !peerReactions?.length) { items.push( handleClick(peerId)} > , ); } return items; }, )}
) : }
); }; export default memo(withGlobal( (global): StateProps => { const { chatId, messageId } = selectTabState(global).reactorModal || {}; const message = chatId && messageId ? selectChatMessage(global, chatId, messageId) : undefined; return { chatId, messageId, reactions: message?.reactions, reactors: message?.reactors, seenByDates: message?.seenByDates, availableReactions: global.availableReactions, }; }, )(ReactorListModal));