import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react'; import type { ElementRef } from '../../lib/teact/teact'; import type React from '../../lib/teact/teact'; import { memo, useEffect, useMemo, useRef, } from '../../lib/teact/teact'; import { getActions } from '../../global'; import type { ApiBotInlineMediaResult, ApiSticker } from '../../api/types'; import type { ObserveFn } from '../../hooks/useIntersectionObserver'; import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment'; import buildClassName from '../../util/buildClassName'; import { getServerTime } from '../../util/serverTime'; import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur'; import useDynamicColorListener from '../../hooks/stickers/useDynamicColorListener'; import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import { useIsIntersecting } from '../../hooks/useIntersectionObserver'; import useLastCallback from '../../hooks/useLastCallback'; import useOldLang from '../../hooks/useOldLang'; import Button from '../ui/Button'; import Menu from '../ui/Menu'; import MenuItem from '../ui/MenuItem'; import Icon from './icons/Icon'; import StickerView from './StickerView'; import './StickerButton.scss'; type OwnProps = { sticker: ApiSticker; size: number; noPlay?: boolean; title?: string; className?: string; noContextMenu?: boolean; isSavedMessages?: boolean; isStatusPicker?: boolean; canViewSet?: boolean; isSelected?: boolean; isCurrentUserPremium?: boolean; shouldIgnorePremium?: boolean; sharedCanvasRef?: ElementRef; withTranslucentThumb?: boolean; forcePlayback?: boolean; observeIntersection: ObserveFn; observeIntersectionForShowing?: ObserveFn; noShowPremium?: boolean; onClick?: (arg: OwnProps['clickArg'], isSilent?: boolean, shouldSchedule?: boolean) => void; clickArg: T; onFaveClick?: (sticker: ApiSticker) => void; onUnfaveClick?: (sticker: ApiSticker) => void; onRemoveRecentClick?: (sticker: ApiSticker) => void; onContextMenuOpen?: NoneToVoidFunction; onContextMenuClose?: NoneToVoidFunction; onContextMenuClick?: NoneToVoidFunction; isEffectEmoji?: boolean; }; const contentForStatusMenuContext = [ { title: 'SetTimeoutFor.Hours', value: 1, arg: 3600 }, { title: 'SetTimeoutFor.Hours', value: 2, arg: 7200 }, { title: 'SetTimeoutFor.Hours', value: 8, arg: 28800 }, { title: 'SetTimeoutFor.Days', value: 1, arg: 86400 }, { title: 'SetTimeoutFor.Days', value: 2, arg: 172800 }, ]; const StickerButton = ({ sticker, size, noPlay, title, className, noContextMenu, isSavedMessages, isStatusPicker, canViewSet, observeIntersection, observeIntersectionForShowing, isSelected, isCurrentUserPremium, shouldIgnorePremium, noShowPremium, sharedCanvasRef, withTranslucentThumb, forcePlayback, onClick, clickArg, onFaveClick, onUnfaveClick, onRemoveRecentClick, onContextMenuOpen, onContextMenuClose, onContextMenuClick, isEffectEmoji, }: OwnProps) => { const { openStickerSet, openPremiumModal, setEmojiStatus } = getActions(); const ref = useRef(); const menuRef = useRef(); const lang = useOldLang(); const hasCustomColor = sticker.shouldUseTextColor; const customColor = useDynamicColorListener(ref, undefined, !hasCustomColor); const { id, stickerSetInfo, } = sticker; const isPremium = !sticker.isFree || sticker.hasEffect; const isCustomEmoji = sticker.isCustomEmoji || isEffectEmoji; const isLocked = !isCurrentUserPremium && isPremium && !shouldIgnorePremium; const isIntersecting = useIsIntersecting(ref, observeIntersection); const shouldLoad = isIntersecting; const shouldPlay = isIntersecting && !noPlay; const isIntesectingForShowing = useIsIntersecting(ref, observeIntersectionForShowing); const { isContextMenuOpen, contextMenuAnchor, handleBeforeContextMenu, handleContextMenu, handleContextMenuClose, handleContextMenuHide, } = useContextMenuHandlers(ref); const shouldRenderContextMenu = Boolean(!noContextMenu && contextMenuAnchor); const getTriggerElement = useLastCallback(() => ref.current); const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar')); const getMenuElement = useLastCallback(() => { return isStatusPicker ? menuRef.current : ref.current!.querySelector('.sticker-context-menu .bubble'); }); const getLayout = useLastCallback(() => ({ withPortal: isStatusPicker, shouldAvoidNegativePosition: true })); useEffect(() => { if (isContextMenuOpen) { onContextMenuOpen?.(); } else { onContextMenuClose?.(); } }, [isContextMenuOpen, onContextMenuClose, onContextMenuOpen]); useEffect(() => { if (!isIntersecting) handleContextMenuClose(); }, [handleContextMenuClose, isIntersecting]); const handleClick = () => { if (isContextMenuOpen) return; if (isLocked) { if (isEffectEmoji) { openPremiumModal({ initialSection: 'effects' }); } else if (isCustomEmoji) { openPremiumModal({ initialSection: 'animated_emoji' }); } else { openPremiumModal({ initialSection: 'premium_stickers' }); } onContextMenuClose?.(); return; } onClick?.(clickArg); }; const handleMouseDown = (e: React.MouseEvent) => { preventMessageInputBlurWithBubbling(e); handleBeforeContextMenu(e); }; const handleRemoveClick = useLastCallback((e: ReactMouseEvent) => { e.stopPropagation(); e.preventDefault(); onRemoveRecentClick!(sticker); }); const handleContextRemoveRecent = useLastCallback(() => { onRemoveRecentClick!(sticker); }); const handleContextUnfave = useLastCallback(() => { onUnfaveClick!(sticker); }); const handleContextFave = useLastCallback(() => { onFaveClick!(sticker); }); const handleSendQuiet = useLastCallback(() => { onClick?.(clickArg, true); }); const handleSendScheduled = useLastCallback(() => { onClick?.(clickArg, undefined, true); }); const handleOpenSet = useLastCallback(() => { openStickerSet({ stickerSetInfo }); }); const handleEmojiStatusExpiresClick = useLastCallback((e: React.SyntheticEvent, duration = 0) => { e.preventDefault(); e.stopPropagation(); handleContextMenuClose(); onContextMenuClick?.(); setEmojiStatus({ emojiStatus: { type: 'regular', documentId: sticker.id, until: getServerTime() + duration }, }); }); const shouldShowCloseButton = !IS_TOUCH_ENV && onRemoveRecentClick; const fullClassName = buildClassName( 'StickerButton', onClick && 'interactive', isSelected && 'selected', isCustomEmoji && 'custom-emoji', isEffectEmoji && 'effect-emoji', className, ); const contextMenuItems = useMemo(() => { if (!shouldRenderContextMenu || noContextMenu || (isCustomEmoji && !isStatusPicker)) return []; const items: ReactNode[] = []; if (isCustomEmoji) { contentForStatusMenuContext.forEach((item) => { items.push( {lang(item.title, item.value, 'i')} , ); }); return items; } if (onUnfaveClick) { items.push( {lang('Stickers.RemoveFromFavorites')} , ); } if (onFaveClick) { items.push( {lang('Stickers.AddToFavorites')} , ); } if (!isLocked && onClick) { if (!isSavedMessages) { items.push({lang('SendWithoutSound')}); } items.push( {lang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')} , ); } if (canViewSet) { items.push( {lang('ViewPackPreview')} , ); } if (onRemoveRecentClick) { items.push( {lang('DeleteFromRecent')} , ); } return items; }, [ shouldRenderContextMenu, noContextMenu, isCustomEmoji, isStatusPicker, onUnfaveClick, onFaveClick, isLocked, onClick, canViewSet, onRemoveRecentClick, handleEmojiStatusExpiresClick, lang, handleContextUnfave, handleContextFave, isSavedMessages, handleSendScheduled, handleSendQuiet, handleOpenSet, handleContextRemoveRecent, ]); return (
{isIntesectingForShowing && ( )} {!noShowPremium && isLocked && (
)} {!noShowPremium && isPremium && !isLocked && (
)} {shouldShowCloseButton && (
); }; export default memo(StickerButton);