import type { FC } from '../../../lib/teact/teact'; import React, { memo, useEffect, useMemo, useRef, useState, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import type { ApiChat } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types'; import { GENERAL_TOPIC_ID, TOPIC_HEIGHT_PX, TOPIC_LIST_SENSITIVE_AREA, TOPICS_SLICE, } from '../../../config'; import { requestNextMutation } from '../../../lib/fasterdom/fasterdom'; import { getOrderedTopics } from '../../../global/helpers'; import { selectCanAnimateInterface, selectChat, selectCurrentMessageList, selectIsForumPanelOpen, selectTabState, } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; import { captureEvents, SwipeDirection } from '../../../util/captureEvents'; import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import useAppLayout from '../../../hooks/useAppLayout'; import { dispatchHeavyAnimationEvent } from '../../../hooks/useHeavyAnimationCheck'; import useHistoryBack from '../../../hooks/useHistoryBack'; import useInfiniteScroll from '../../../hooks/useInfiniteScroll'; import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver'; import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import usePrevious from '../../../hooks/usePrevious'; import useOrderDiff from './hooks/useOrderDiff'; import GroupCallTopPane from '../../calls/group/GroupCallTopPane'; import GroupChatInfo from '../../common/GroupChatInfo'; import HeaderActions from '../../middle/HeaderActions'; import Button from '../../ui/Button'; import InfiniteScroll from '../../ui/InfiniteScroll'; import Loading from '../../ui/Loading'; import EmptyForum from './EmptyForum'; import Topic from './Topic'; import styles from './ForumPanel.module.scss'; type OwnProps = { isOpen?: boolean; isHidden?: boolean; onTopicSearch?: NoneToVoidFunction; onCloseAnimationEnd?: VoidFunction; onOpenAnimationStart?: VoidFunction; }; type StateProps = { chat?: ApiChat; currentTopicId?: number; withInterfaceAnimations?: boolean; }; const INTERSECTION_THROTTLE = 200; const ForumPanel: FC = ({ chat, currentTopicId, isOpen, isHidden, onTopicSearch, onCloseAnimationEnd, onOpenAnimationStart, withInterfaceAnimations, }) => { const { closeForumPanel, openChatWithInfo, loadTopics, } = getActions(); // eslint-disable-next-line no-null/no-null const ref = useRef(null); // eslint-disable-next-line no-null/no-null const containerRef = useRef(null); // eslint-disable-next-line no-null/no-null const scrollTopHandlerRef = useRef(null); const { isMobile } = useAppLayout(); useEffect(() => { if (chat && !chat.topics) { loadTopics({ chatId: chat.id }); } }, [chat, loadTopics]); const [isScrolled, setIsScrolled] = useState(false); const lang = useLang(); const handleClose = useLastCallback(() => { closeForumPanel(); }); useEffect(() => { if (!withInterfaceAnimations && !isOpen) { onCloseAnimationEnd?.(); } }, [withInterfaceAnimations, isOpen, onCloseAnimationEnd]); const handleToggleChatInfo = useLastCallback(() => { if (!chat) return; openChatWithInfo({ id: chat.id, shouldReplaceHistory: true }); }); const { observe } = useIntersectionObserver({ rootRef: containerRef, throttleMs: INTERSECTION_THROTTLE, }); useOnIntersect(scrollTopHandlerRef, observe, ({ isIntersecting }) => { setIsScrolled(!isIntersecting); }); const orderedIds = useMemo(() => { return chat?.topics ? getOrderedTopics(Object.values(chat.topics), chat.orderedPinnedTopicIds).map(({ id }) => id) : []; }, [chat]); const { orderDiffById, getAnimationType } = useOrderDiff(orderedIds, chat?.id); const [viewportIds, getMore] = useInfiniteScroll(() => { if (!chat) return; loadTopics({ chatId: chat.id }); }, orderedIds, !chat?.topicsCount || orderedIds.length >= chat.topicsCount, TOPICS_SLICE); const shouldRenderRef = useRef(false); const isVisible = isOpen && !isHidden; const prevIsVisible = usePrevious(isVisible); if (prevIsVisible !== isVisible) { shouldRenderRef.current = false; } useHistoryBack({ isActive: isVisible, onBack: handleClose, }); useEffect(() => (isVisible ? captureEscKeyListener(handleClose) : undefined), [handleClose, isVisible]); useEffect(() => { if (prevIsVisible !== isVisible) { // For performance reasons, we delay animation of the topic list panel to the next animation frame requestNextMutation(() => { if (!ref.current) return; const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent(); waitForTransitionEnd(ref.current, dispatchHeavyAnimationStop); onOpenAnimationStart?.(); if (isVisible) { shouldRenderRef.current = true; ref.current!.style.transform = 'none'; } else { shouldRenderRef.current = false; ref.current!.style.transform = ''; } }); } }, [isVisible, onOpenAnimationStart, prevIsVisible]); useEffect(() => { if (!IS_TOUCH_ENV) { return undefined; } return captureEvents(ref.current!, { selectorToPreventScroll: '.chat-list', onSwipe: ((e, direction) => { const closeDirection = lang.isRtl ? SwipeDirection.Left : SwipeDirection.Right; if (direction === closeDirection) { closeForumPanel(); return true; } return false; }), }); }, [closeForumPanel, lang.isRtl]); function renderTopics() { const viewportOffset = orderedIds!.indexOf(viewportIds![0]); return viewportIds?.map((id, i) => ( )); } const isLoading = chat?.topics === undefined; return (
{chat && ( )} {chat && ( )}
{chat && }
} > {Boolean(viewportIds?.length) && ( renderTopics() )} {isLoading && !viewportIds?.length && ( )} {!isLoading && viewportIds?.length === 1 && viewportIds[0] === GENERAL_TOPIC_ID && ( )}
); }; export default memo(withGlobal( (global): StateProps => { const chatId = selectTabState(global).forumPanelChatId; const chat = chatId ? selectChat(global, chatId) : undefined; const { chatId: currentChatId, threadId: currentThreadId, } = selectCurrentMessageList(global) || {}; return { chat, currentTopicId: chatId === currentChatId ? Number(currentThreadId) : undefined, withInterfaceAnimations: selectCanAnimateInterface(global), }; }, (global) => selectIsForumPanelOpen(global), )(ForumPanel));