import type { FC } from '../../lib/teact/teact'; import React, { memo, useEffect, useMemo, useState, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { ApiStory, ApiTypeStoryView } from '../../api/types'; import { STORY_MIN_REACTIONS_SORT, STORY_VIEWS_MIN_CONTACTS_FILTER, STORY_VIEWS_MIN_SEARCH, } from '../../config'; import { selectIsCurrentUserPremium, selectPeerStory, selectTabState, } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; import { getServerTime } from '../../util/serverTime'; import renderText from '../common/helpers/renderText'; import useDebouncedCallback from '../../hooks/useDebouncedCallback'; import useFlag from '../../hooks/useFlag'; import useLastCallback from '../../hooks/useLastCallback'; import useOldLang from '../../hooks/useOldLang'; import useScrolledState from '../../hooks/useScrolledState'; import Button from '../ui/Button'; import DropdownMenu from '../ui/DropdownMenu'; import InfiniteScroll from '../ui/InfiniteScroll'; import ListItem from '../ui/ListItem'; import MenuItem from '../ui/MenuItem'; import Modal from '../ui/Modal'; import PlaceholderChatInfo from '../ui/placeholder/PlaceholderChatInfo'; import SearchInput from '../ui/SearchInput'; import StoryView from './StoryView'; import styles from './StoryViewModal.module.scss'; interface StateProps { story?: ApiStory; isLoading?: boolean; views?: ApiTypeStoryView[]; nextOffset?: string; viewersExpirePeriod: number; isCurrentUserPremium?: boolean; } const REFETCH_DEBOUNCE = 250; function StoryViewModal({ story, viewersExpirePeriod, views, nextOffset, isLoading, isCurrentUserPremium, }: StateProps) { const { loadStoryViewList, closeStoryViewModal, clearStoryViews, } = getActions(); const [areJustContacts, markJustContacts, unmarkJustContacts] = useFlag(false); const [areReactionsFirst, markReactionsFirst, unmarkReactionsFirst] = useFlag(true); const [query, setQuery] = useState(''); const lang = useOldLang(); const isOpen = Boolean(story); const isExpired = Boolean(story?.date) && (story!.date + viewersExpirePeriod) < getServerTime(); const { viewsCount = 0, reactionsCount = 0 } = story?.views || {}; const shouldShowJustContacts = story?.isPublic && viewsCount > STORY_VIEWS_MIN_CONTACTS_FILTER; const shouldShowSortByReactions = reactionsCount > STORY_MIN_REACTIONS_SORT; const shouldShowSearch = viewsCount > STORY_VIEWS_MIN_SEARCH; const hasHeader = shouldShowJustContacts || shouldShowSortByReactions || shouldShowSearch; useEffect(() => { if (!isOpen) { setQuery(''); unmarkJustContacts(); markReactionsFirst(); } }, [isOpen]); const refetchViews = useDebouncedCallback(() => { clearStoryViews({ isLoading: true }); }, [], REFETCH_DEBOUNCE, true); useEffect(() => { refetchViews(); }, [areJustContacts, areReactionsFirst, query, refetchViews]); const sortedViews = useMemo(() => { return views?.sort(prepareComparator(areReactionsFirst)); }, [areReactionsFirst, views]); const placeholderCount = !sortedViews?.length ? Math.min(viewsCount, 8) : 1; const notAllAvailable = Boolean(sortedViews?.length) && sortedViews!.length < viewsCount && isExpired; const handleLoadMore = useLastCallback(() => { if (!story?.id || nextOffset === undefined) return; loadStoryViewList({ peerId: story.peerId, storyId: story.id, offset: nextOffset, areReactionsFirst: areReactionsFirst || undefined, areJustContacts: areJustContacts || undefined, query, }); }); const { handleScroll, isAtBeginning } = useScrolledState(); const handleClose = useLastCallback(() => { closeStoryViewModal(); }); const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => { return ({ onTrigger, isOpen: isMenuOpen }) => ( ); }, [areReactionsFirst, lang]); return ( {hasHeader && (
{shouldShowJustContacts && (
)} {shouldShowSortByReactions && ( {lang('SortByReactions')} {areReactionsFirst && ( )} {lang('SortByTime')} {!areReactionsFirst && ( )} )} {shouldShowSearch && ( )}
)}
{isExpired && !isLoading && !query && Boolean(!sortedViews?.length) && (
{renderText( lang(isCurrentUserPremium ? 'ServerErrorViewers' : 'ExpiredViewsStub'), ['simple_markdown', 'emoji'], )}
)} {!isLoading && Boolean(query.length) && !sortedViews?.length && (
{lang('Story.ViewList.EmptyTextSearch')}
)} {sortedViews?.map((view) => { const additionalKeyId = view.type === 'forward' ? view.messageId : view.type === 'repost' ? view.storyId : 'user'; return ( ); })} {isLoading && Array.from({ length: placeholderCount }).map((_, i) => ( ))} {notAllAvailable && (
{lang('Story.ViewList.NotFullyRecorded')}
)}
); } function prepareComparator(areReactionsFirst?: boolean) { return (a: ApiTypeStoryView, b: ApiTypeStoryView) => { if (areReactionsFirst) { const reactionA = a.type === 'user' && a.reaction; const reactionB = b.type === 'user' && b.reaction; if (reactionA && !reactionB) { return -1; } if (!reactionA && reactionB) { return 1; } } return b.date - a.date; }; } export default memo(withGlobal((global) => { const { appConfig } = global; const { storyViewer: { viewModal } } = selectTabState(global); const { storyId, views, nextOffset, isLoading, } = viewModal || {}; const story = storyId ? selectPeerStory(global, global.currentUserId!, storyId) : undefined; return { storyId, views, viewersExpirePeriod: appConfig!.storyExpirePeriod + appConfig!.storyViewersExpirePeriod, story: story && 'content' in story ? story : undefined, nextOffset, isLoading, availableReactions: global.reactions.availableReactions, isCurrentUserPremium: selectIsCurrentUserPremium(global), }; })(StoryViewModal));