import type { FC } from '@teact'; import { memo, useEffect, useRef } from '@teact'; import { getActions, withGlobal } from '../../../global'; import type { ApiChatFolder, ApiChatlistExportedInvite } from '../../../api/types'; import type { GlobalState } from '../../../global/types'; import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer'; import type { AnimationLevel } from '../../../types'; import { ALL_FOLDER_ID } from '../../../config'; import { selectTabState } from '../../../global/selectors'; import { selectCurrentLimit } from '../../../global/selectors/limits'; import { selectSharedSettings } from '../../../global/selectors/sharedState'; import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment'; import buildClassName from '../../../util/buildClassName'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; import { captureEvents, SwipeDirection } from '../../../util/captureEvents'; import { resolveTransitionName } from '../../../util/resolveTransitionName'; import useDerivedState from '../../../hooks/useDerivedState'; import useFolderTabs from '../../../hooks/useFolderTabs'; import useHistoryBack from '../../../hooks/useHistoryBack'; import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import useShowTransition from '../../../hooks/useShowTransition'; import StoryRibbon from '../../story/StoryRibbon'; import TabList from '../../ui/TabList'; import Transition from '../../ui/Transition'; import ChatList from './ChatList'; type OwnProps = { foldersDispatch: FolderEditDispatch; shouldHideFolderTabs?: boolean; isForumPanelOpen?: boolean; isFoldersSidebarShown?: boolean; }; type StateProps = { chatFoldersById: Record; folderInvitesById: Record; orderedFolderIds?: number[]; activeChatFolder: number; currentUserId?: string; animationLevel: AnimationLevel; shouldSkipHistoryAnimations?: boolean; maxFolders: number; maxChatLists: number; maxFolderInvites: number; hasArchivedChats?: boolean; hasArchivedStories?: boolean; archiveSettings: GlobalState['archiveSettings']; isStoryRibbonShown?: boolean; }; const SAVED_MESSAGES_HOTKEY = '0'; const FIRST_FOLDER_INDEX = 0; const ChatFolders: FC = ({ foldersDispatch, chatFoldersById, orderedFolderIds, activeChatFolder, currentUserId, isForumPanelOpen, animationLevel, shouldSkipHistoryAnimations, maxFolders, maxChatLists, shouldHideFolderTabs, folderInvitesById, maxFolderInvites, hasArchivedChats, hasArchivedStories, archiveSettings, isStoryRibbonShown, isFoldersSidebarShown, }) => { const { loadChatFolders, setActiveChatFolder, openChat, } = getActions(); const transitionRef = useRef(); const lang = useLang(); useEffect(() => { loadChatFolders(); }, []); const { ref, shouldRender: shouldRenderStoryRibbon, getIsClosing: getIsStoryRibbonClosing, } = useShowTransition({ isOpen: isStoryRibbonShown, className: false, withShouldRender: true, }); const isStoryRibbonClosing = useDerivedState(getIsStoryRibbonClosing); const scrollToTop = useLastCallback(() => { const activeList = ref.current?.querySelector('#LeftColumn .chat-list.Transition_slide-active'); activeList?.scrollTo({ top: 0, behavior: 'smooth', }); }); const { displayedFolders, folderTabs } = useFolderTabs({ sidebarMode: false, orderedFolderIds, chatFoldersById, maxFolders, maxChatLists, folderInvitesById, maxFolderInvites, }); const allChatsFolderIndex = displayedFolders?.findIndex((folder) => folder.id === ALL_FOLDER_ID); const isInAllChatsFolder = allChatsFolderIndex === activeChatFolder; const isInFirstFolder = FIRST_FOLDER_INDEX === activeChatFolder; const handleSwitchTab = useLastCallback((index: number) => { setActiveChatFolder({ activeChatFolder: index }, { forceOnHeavyAnimation: true }); if (activeChatFolder === index) { scrollToTop(); } }); // Prevent `activeTab` pointing at non-existing folder after update useEffect(() => { if (!folderTabs?.length) { return; } if (activeChatFolder >= folderTabs.length) { setActiveChatFolder({ activeChatFolder: FIRST_FOLDER_INDEX }); } }, [activeChatFolder, folderTabs, setActiveChatFolder]); useEffect(() => { if (!IS_TOUCH_ENV || !folderTabs?.length || isForumPanelOpen) { return undefined; } return captureEvents(transitionRef.current!, { selectorToPreventScroll: '.chat-list', onSwipe: (e, direction) => { if (direction === SwipeDirection.Left) { setActiveChatFolder( { activeChatFolder: Math.min(activeChatFolder + 1, folderTabs.length - 1) }, { forceOnHeavyAnimation: true }, ); return true; } else if (direction === SwipeDirection.Right) { setActiveChatFolder({ activeChatFolder: Math.max(0, activeChatFolder - 1) }, { forceOnHeavyAnimation: true }); return true; } return false; }, }); }, [activeChatFolder, folderTabs, isForumPanelOpen, setActiveChatFolder]); const isNotInFirstFolderRef = useRef(); isNotInFirstFolderRef.current = !isInFirstFolder; useEffect(() => (isNotInFirstFolderRef.current ? captureEscKeyListener(() => { if (isNotInFirstFolderRef.current) { setActiveChatFolder({ activeChatFolder: FIRST_FOLDER_INDEX }); } }) : undefined), [activeChatFolder, setActiveChatFolder]); useHistoryBack({ isActive: !isInFirstFolder, onBack: () => setActiveChatFolder({ activeChatFolder: FIRST_FOLDER_INDEX }, { forceOnHeavyAnimation: true }), }); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.ctrlKey && e.shiftKey && e.code.startsWith('Digit') && folderTabs) { const [, digit] = e.code.match(/Digit(\d)/) || []; if (!digit) return; if (digit === SAVED_MESSAGES_HOTKEY) { openChat({ id: currentUserId, shouldReplaceHistory: true }); return; } const folder = Number(digit) - 1; if (folder > folderTabs.length - 1) return; setActiveChatFolder({ activeChatFolder: folder }, { forceOnHeavyAnimation: true }); e.preventDefault(); } }; document.addEventListener('keydown', handleKeyDown, true); return () => { document.removeEventListener('keydown', handleKeyDown, true); }; }, [currentUserId, folderTabs, openChat, setActiveChatFolder]); const { ref: placeholderRef, shouldRender: shouldRenderPlaceholder, } = useShowTransition({ isOpen: !orderedFolderIds && !isFoldersSidebarShown, noMountTransition: true, withShouldRender: true, }); function renderCurrentTab(isActive: boolean) { const activeFolder = Object.values(chatFoldersById) .find(({ id }) => id === folderTabs![activeChatFolder].id); const isFolder = activeFolder && !isInAllChatsFolder; return ( ); } const hasFolders = folderTabs && folderTabs.length > 1; const shouldRenderFolders = hasFolders && !isFoldersSidebarShown; return (
{shouldRenderStoryRibbon && } {shouldRenderFolders ? ( ) : shouldRenderPlaceholder ? (
) : undefined} {renderCurrentTab}
); }; export default memo(withGlobal( (global): Complete => { const { chatFolders: { byId: chatFoldersById, orderedIds: orderedFolderIds, invites: folderInvitesById, }, chats: { listIds: { archived, }, }, stories: { orderedPeerIds: { archived: archivedStories, }, }, currentUserId, archiveSettings, } = global; const { animationLevel } = selectSharedSettings(global); const { shouldSkipHistoryAnimations, activeChatFolder } = selectTabState(global); const { storyViewer: { isRibbonShown: isStoryRibbonShown } } = selectTabState(global); return { chatFoldersById, folderInvitesById, orderedFolderIds, activeChatFolder, currentUserId, animationLevel, shouldSkipHistoryAnimations, hasArchivedChats: Boolean(archived?.length), hasArchivedStories: Boolean(archivedStories?.length), maxFolders: selectCurrentLimit(global, 'dialogFilters'), maxFolderInvites: selectCurrentLimit(global, 'chatlistInvites'), maxChatLists: selectCurrentLimit(global, 'chatlistJoined'), archiveSettings, isStoryRibbonShown, }; }, )(ChatFolders));