import type { FC } from '../../../lib/teact/teact'; import React, { useState, useEffect, memo, useRef, useMemo, useCallback, } from '../../../lib/teact/teact'; import { getGlobal, withGlobal } from '../../../global'; import type { ApiStickerSet, ApiSticker } from '../../../api/types'; import type { StickerSetOrRecent } from '../../../types'; import { CHAT_STICKER_SET_ID, FAVORITE_SYMBOL_SET_ID, PREMIUM_STICKER_SET_ID, RECENT_SYMBOL_SET_ID, SLIDE_TRANSITION_DURATION, STICKER_PICKER_MAX_SHARED_COVERS, STICKER_SIZE_PICKER_HEADER, } from '../../../config'; import { IS_TOUCH_ENV } from '../../../util/environment'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import fastSmoothScroll from '../../../util/fastSmoothScroll'; import buildClassName from '../../../util/buildClassName'; import fastSmoothScrollHorizontal from '../../../util/fastSmoothScrollHorizontal'; import { pickTruthy, unique } from '../../../util/iteratees'; import { selectIsAlwaysHighPriorityEmoji, selectIsChatWithSelf, selectIsCurrentUserPremium, } from '../../../global/selectors'; import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useLang from '../../../hooks/useLang'; import Loading from '../../ui/Loading'; import Button from '../../ui/Button'; import StickerButton from '../../common/StickerButton'; import StickerSet from './StickerSet'; import StickerSetCover from './StickerSetCover'; import './StickerPicker.scss'; type OwnProps = { chatId?: string; className?: string; loadAndPlay: boolean; isStatusPicker?: boolean; idPrefix?: string; withDefaultTopicIcons?: boolean; onCustomEmojiSelect: (sticker: ApiSticker) => void; onContextMenuOpen?: NoneToVoidFunction; onContextMenuClose?: NoneToVoidFunction; onContextMenuClick?: NoneToVoidFunction; }; type StateProps = { customEmojisById?: Record; recentCustomEmojiIds?: string[]; recentStatusEmojis?: ApiSticker[]; stickerSetsById: Record; addedCustomEmojiIds?: string[]; defaultTopicIconsId?: string; defaultStatusIconsId?: string; customEmojiFeaturedIds?: string[]; canAnimate?: boolean; isSavedMessages?: boolean; isCurrentUserPremium?: boolean; }; const SMOOTH_SCROLL_DISTANCE = 500; const HEADER_BUTTON_WIDTH = 52; // px (including margin) const STICKER_INTERSECTION_THROTTLE = 200; const DEFAULT_ID_PREFIX = 'custom-emoji-set'; const stickerSetIntersections: boolean[] = []; const CustomEmojiPicker: FC = ({ className, loadAndPlay, addedCustomEmojiIds, customEmojisById, recentCustomEmojiIds, recentStatusEmojis, stickerSetsById, idPrefix = DEFAULT_ID_PREFIX, customEmojiFeaturedIds, canAnimate, isStatusPicker, isSavedMessages, isCurrentUserPremium, withDefaultTopicIcons, defaultTopicIconsId, defaultStatusIconsId, onCustomEmojiSelect, onContextMenuOpen, onContextMenuClose, onContextMenuClick, }) => { // eslint-disable-next-line no-null/no-null const containerRef = useRef(null); // eslint-disable-next-line no-null/no-null const headerRef = useRef(null); // eslint-disable-next-line no-null/no-null const sharedCanvasRef = useRef(null); // eslint-disable-next-line no-null/no-null const sharedCanvasHqRef = useRef(null); const [activeSetIndex, setActiveSetIndex] = useState(0); const recentCustomEmojis = useMemo(() => { return isStatusPicker ? recentStatusEmojis : Object.values(pickTruthy(customEmojisById!, recentCustomEmojiIds!)); }, [customEmojisById, isStatusPicker, recentCustomEmojiIds, recentStatusEmojis]); const { observe: observeIntersection } = useIntersectionObserver({ rootRef: containerRef, throttleMs: STICKER_INTERSECTION_THROTTLE, }, (entries) => { entries.forEach((entry) => { const { id } = entry.target as HTMLDivElement; if (!id || !id.startsWith(idPrefix)) { return; } const index = Number(id.replace(`${idPrefix}-`, '')); stickerSetIntersections[index] = entry.isIntersecting; }); const intersectingWithIndexes = stickerSetIntersections .map((isIntersecting, index) => ({ index, isIntersecting })) .filter(({ isIntersecting }) => isIntersecting); if (!intersectingWithIndexes.length) { return; } setActiveSetIndex(intersectingWithIndexes[Math.floor(intersectingWithIndexes.length / 2)].index); }); const { observe: observeIntersectionForCovers } = useIntersectionObserver({ rootRef: headerRef }); const lang = useLang(); const areAddedLoaded = Boolean(addedCustomEmojiIds); const allSets = useMemo(() => { if (!addedCustomEmojiIds) { return MEMO_EMPTY_ARRAY; } const defaultSets: StickerSetOrRecent[] = []; if (isStatusPicker) { const defaultStatusIconsPack = stickerSetsById[defaultStatusIconsId!]; if (defaultStatusIconsPack.stickers?.length) { const stickers = (defaultStatusIconsPack.stickers || []).concat(recentCustomEmojis || []); defaultSets.push({ ...defaultStatusIconsPack, stickers, count: stickers.length, id: RECENT_SYMBOL_SET_ID, title: lang('RecentStickers'), }); } } else if (withDefaultTopicIcons) { const defaultTopicIconsPack = stickerSetsById[defaultTopicIconsId!]; if (defaultTopicIconsPack.stickers?.length) { defaultSets.push({ ...defaultTopicIconsPack, id: RECENT_SYMBOL_SET_ID, title: lang('RecentStickers'), }); } } else if (recentCustomEmojis?.length) { defaultSets.push({ id: RECENT_SYMBOL_SET_ID, accessHash: '0', title: lang('RecentStickers'), stickers: recentCustomEmojis, count: recentCustomEmojis.length, isEmoji: true as true, }); } const setIdsToDisplay = unique(addedCustomEmojiIds.concat(customEmojiFeaturedIds || [])); const setsToDisplay = Object.values(pickTruthy(stickerSetsById, setIdsToDisplay)); return [ ...defaultSets, ...setsToDisplay, ]; }, [ addedCustomEmojiIds, isStatusPicker, withDefaultTopicIcons, recentCustomEmojis, customEmojiFeaturedIds, stickerSetsById, defaultStatusIconsId, lang, defaultTopicIconsId, ]); const noPopulatedSets = useMemo(() => ( areAddedLoaded && allSets.filter((set) => set.stickers?.length).length === 0 ), [allSets, areAddedLoaded]); const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION); const shouldRenderContents = areAddedLoaded && canRenderContents && !noPopulatedSets; useHorizontalScroll(headerRef, !shouldRenderContents); // Scroll container and header when active set changes useEffect(() => { if (!areAddedLoaded) { return; } const header = headerRef.current; if (!header) { return; } const newLeft = activeSetIndex * HEADER_BUTTON_WIDTH - (header.offsetWidth / 2 - HEADER_BUTTON_WIDTH / 2); fastSmoothScrollHorizontal(header, newLeft); }, [areAddedLoaded, activeSetIndex]); const selectStickerSet = useCallback((index: number) => { setActiveSetIndex(index); const stickerSetEl = document.getElementById(`${idPrefix}-${index}`)!; fastSmoothScroll(containerRef.current!, stickerSetEl, 'start', undefined, SMOOTH_SCROLL_DISTANCE); }, [idPrefix]); const handleEmojiSelect = useCallback((emoji: ApiSticker) => { onCustomEmojiSelect(emoji); }, [onCustomEmojiSelect]); function renderCover(stickerSet: StickerSetOrRecent, index: number) { const firstSticker = stickerSet.stickers?.[0]; const buttonClassName = buildClassName( 'symbol-set-button sticker-set-button', index === activeSetIndex && 'activated', ); const withSharedCanvas = index < STICKER_PICKER_MAX_SHARED_COVERS; const isHq = selectIsAlwaysHighPriorityEmoji(getGlobal(), stickerSet as ApiStickerSet); if (stickerSet.id === RECENT_SYMBOL_SET_ID || stickerSet.id === FAVORITE_SYMBOL_SET_ID || stickerSet.id === CHAT_STICKER_SET_ID || stickerSet.id === PREMIUM_STICKER_SET_ID || stickerSet.hasThumbnail || !firstSticker ) { return ( ); } else { return ( ); } } const fullClassName = buildClassName('StickerPicker', 'CustomEmojiPicker', className); if (!shouldRenderContents) { return (
{noPopulatedSets ? (
{lang('NoStickers')}
) : ( )}
); } return (
{allSets.map(renderCover)}
{allSets.map((stickerSet, i) => ( = i - 1 && activeSetIndex <= i + 1} isSavedMessages={isSavedMessages} isStatusPicker={isStatusPicker} shouldHideRecentHeader={withDefaultTopicIcons || isStatusPicker} withDefaultTopicIcon={withDefaultTopicIcons && stickerSet.id === RECENT_SYMBOL_SET_ID} withDefaultStatusIcon={isStatusPicker && stickerSet.id === RECENT_SYMBOL_SET_ID} isCurrentUserPremium={isCurrentUserPremium} onStickerSelect={handleEmojiSelect} onContextMenuOpen={onContextMenuOpen} onContextMenuClose={onContextMenuClose} onContextMenuClick={onContextMenuClick} /> ))}
); }; export default memo(withGlobal( (global, { chatId, isStatusPicker }): StateProps => { const { stickers: { setsById: stickerSetsById, }, customEmojis: { byId: customEmojisById, featuredIds: customEmojiFeaturedIds, statusRecent: { emojis: recentStatusEmojis, }, }, recentCustomEmojis: recentCustomEmojiIds, } = global; const isSavedMessages = Boolean(chatId && selectIsChatWithSelf(global, chatId)); return { customEmojisById: !isStatusPicker ? customEmojisById : undefined, recentCustomEmojiIds: !isStatusPicker ? recentCustomEmojiIds : undefined, recentStatusEmojis: isStatusPicker ? recentStatusEmojis : undefined, stickerSetsById, addedCustomEmojiIds: global.customEmojis.added.setIds, canAnimate: global.settings.byKey.shouldLoopStickers, isSavedMessages, isCurrentUserPremium: selectIsCurrentUserPremium(global), customEmojiFeaturedIds, defaultTopicIconsId: global.defaultTopicIconsId, defaultStatusIconsId: global.defaultStatusIconsId, }; }, )(CustomEmojiPicker));