import React, { memo, useEffect, useLayoutEffect, useRef, useState, } from '../../../lib/teact/teact'; import { requestMutation } from '../../../lib/fasterdom/fasterdom'; import { withGlobal } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; import type { ApiSticker, ApiVideo } from '../../../api/types'; import type { GlobalActions } from '../../../global'; import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import buildClassName from '../../../util/buildClassName'; import { selectTabState, selectIsContextMenuTranslucent } from '../../../global/selectors'; import useLastCallback from '../../../hooks/useLastCallback'; import useShowTransition from '../../../hooks/useShowTransition'; import useMouseInside from '../../../hooks/useMouseInside'; import useLang from '../../../hooks/useLang'; import useAppLayout from '../../../hooks/useAppLayout'; import Button from '../../ui/Button'; import Menu from '../../ui/Menu'; import Transition from '../../ui/Transition'; import EmojiPicker from './EmojiPicker'; import CustomEmojiPicker from '../../common/CustomEmojiPicker'; import StickerPicker from './StickerPicker'; import GifPicker from './GifPicker'; import SymbolMenuFooter, { SYMBOL_MENU_TAB_TITLES, SymbolMenuTabs } from './SymbolMenuFooter'; import Portal from '../../ui/Portal'; import './SymbolMenu.scss'; const ANIMATION_DURATION = 350; const STICKERS_TAB_INDEX = 2; export type OwnProps = { chatId: string; threadId?: number; isOpen: boolean; canSendStickers?: boolean; canSendGifs?: boolean; isMessageComposer?: boolean; idPrefix: string; onLoad: () => void; onClose: () => void; onEmojiSelect: (emoji: string) => void; onCustomEmojiSelect: (emoji: ApiSticker) => void; onStickerSelect?: ( sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, shouldPreserveInput?: boolean, canUpdateStickerSetsOrder?: boolean, ) => void; onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void; onRemoveSymbol: () => void; onSearchOpen: (type: 'stickers' | 'gifs') => void; addRecentEmoji: GlobalActions['addRecentEmoji']; addRecentCustomEmoji: GlobalActions['addRecentCustomEmoji']; className?: string; isAttachmentModal?: boolean; canSendPlainText?: boolean; positionX?: 'left' | 'right'; positionY?: 'top' | 'bottom'; transformOriginX?: number; transformOriginY?: number; style?: string; }; type StateProps = { isLeftColumnShown: boolean; isBackgroundTranslucent?: boolean; }; let isActivated = false; const SymbolMenu: FC = ({ chatId, threadId, isOpen, canSendStickers, canSendGifs, isMessageComposer, isLeftColumnShown, idPrefix, isAttachmentModal, canSendPlainText, className, positionX, positionY, transformOriginX, transformOriginY, style, isBackgroundTranslucent, onLoad, onClose, onEmojiSelect, onCustomEmojiSelect, onStickerSelect, onGifSelect, onRemoveSymbol, onSearchOpen, addRecentEmoji, addRecentCustomEmoji, }) => { const [activeTab, setActiveTab] = useState(0); const [recentEmojis, setRecentEmojis] = useState([]); const [recentCustomEmojis, setRecentCustomEmojis] = useState([]); const { isMobile } = useAppLayout(); const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, isMobile); const { shouldRender, transitionClassNames } = useShowTransition(isOpen, onClose, false, false); const lang = useLang(); if (!isActivated && isOpen) { isActivated = true; } useEffect(() => { onLoad(); }, [onLoad]); // If we can't send plain text, we should always show the stickers tab useEffect(() => { if (canSendPlainText) return; setActiveTab(STICKERS_TAB_INDEX); }, [canSendPlainText]); useLayoutEffect(() => { if (!isMobile || !isOpen || isAttachmentModal) { return undefined; } document.body.classList.add('enable-symbol-menu-transforms'); document.body.classList.add('is-symbol-menu-open'); return () => { document.body.classList.remove('is-symbol-menu-open'); setTimeout(() => { requestMutation(() => { document.body.classList.remove('enable-symbol-menu-transforms'); }); }, ANIMATION_DURATION); }; }, [isAttachmentModal, isMobile, isOpen]); const recentEmojisRef = useRef(recentEmojis); recentEmojisRef.current = recentEmojis; useEffect(() => { if (!recentEmojisRef.current.length || isOpen) { return; } recentEmojisRef.current.forEach((name) => { addRecentEmoji({ emoji: name }); }); setRecentEmojis([]); }, [isOpen, addRecentEmoji]); const handleEmojiSelect = useLastCallback((emoji: string, name: string) => { setRecentEmojis((emojis) => [...emojis, name]); onEmojiSelect(emoji); }); const recentCustomEmojisRef = useRef(recentCustomEmojis); recentCustomEmojisRef.current = recentCustomEmojis; useEffect(() => { if (!recentCustomEmojisRef.current.length || isOpen) { return; } recentCustomEmojisRef.current.forEach((documentId) => { addRecentCustomEmoji({ documentId, }); }); setRecentEmojis([]); }, [isOpen, addRecentCustomEmoji]); const handleCustomEmojiSelect = useLastCallback((emoji: ApiSticker) => { setRecentCustomEmojis((ids) => [...ids, emoji.id]); onCustomEmojiSelect(emoji); }); const handleSearch = useLastCallback((type: 'stickers' | 'gifs') => { onClose(); onSearchOpen(type); }); const handleStickerSelect = useLastCallback(( sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, canUpdateStickerSetsOrder?: boolean, ) => { onStickerSelect?.(sticker, isSilent, shouldSchedule, true, canUpdateStickerSetsOrder); }); function renderContent(isActive: boolean, isFrom: boolean) { switch (activeTab) { case SymbolMenuTabs.Emoji: return ( ); case SymbolMenuTabs.CustomEmoji: return ( ); case SymbolMenuTabs.Stickers: return ( ); case SymbolMenuTabs.GIFs: return ( ); } return undefined; } function stopPropagation(event: any) { event.stopPropagation(); } const content = ( <>
{isActivated && ( {renderContent} )}
{isMobile && ( )} ); if (isMobile) { if (!shouldRender) { return undefined; } const mobileClassName = buildClassName( 'SymbolMenu mobile-menu', transitionClassNames, isLeftColumnShown && 'left-column-open', isAttachmentModal && 'in-attachment-modal', isMessageComposer && 'in-middle-column', ); if (isAttachmentModal) { return (
{content}
); } return (
{content}
); } return ( {content} ); }; export default memo(withGlobal( (global): StateProps => { return { isLeftColumnShown: selectTabState(global).isLeftColumnShown, isBackgroundTranslucent: selectIsContextMenuTranslucent(global), }; }, )(SymbolMenu));