import type { MouseEvent as ReactMouseEvent } from 'react'; import React, { memo, useCallback, useEffect, useRef, } from '../../lib/teact/teact'; import { getActions } from '../../global'; import type { ApiBotInlineMediaResult, ApiSticker } from '../../api/types'; import { ApiMediaFormat } from '../../api/types'; import buildClassName from '../../util/buildClassName'; import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur'; import safePlay from '../../util/safePlay'; import { IS_TOUCH_ENV, IS_WEBM_SUPPORTED } from '../../util/environment'; import type { ObserveFn } from '../../hooks/useIntersectionObserver'; import { useIsIntersecting } from '../../hooks/useIntersectionObserver'; import useMedia from '../../hooks/useMedia'; import useShowTransition from '../../hooks/useShowTransition'; import useFlag from '../../hooks/useFlag'; import useLang from '../../hooks/useLang'; import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import useContextMenuPosition from '../../hooks/useContextMenuPosition'; import AnimatedSticker from './AnimatedSticker'; import Button from '../ui/Button'; import Menu from '../ui/Menu'; import MenuItem from '../ui/MenuItem'; import './StickerButton.scss'; type OwnProps = { sticker: ApiSticker; size: number; noAnimate?: boolean; title?: string; className?: string; clickArg: T; noContextMenu?: boolean; isSavedMessages?: boolean; canViewSet?: boolean; observeIntersection: ObserveFn; onClick?: (arg: OwnProps['clickArg'], isSilent?: boolean, shouldSchedule?: boolean) => void; onFaveClick?: (sticker: ApiSticker) => void; onUnfaveClick?: (sticker: ApiSticker) => void; onRemoveRecentClick?: (sticker: ApiSticker) => void; }; const StickerButton = ({ sticker, size, noAnimate, title, className, clickArg, noContextMenu, isSavedMessages, canViewSet, observeIntersection, onClick, onFaveClick, onUnfaveClick, onRemoveRecentClick, }: OwnProps) => { const { openStickerSet } = getActions(); // eslint-disable-next-line no-null/no-null const ref = useRef(null); const lang = useLang(); const localMediaHash = `sticker${sticker.id}`; const stickerSelector = `sticker-button-${sticker.id}`; const isIntersecting = useIsIntersecting(ref, observeIntersection); const thumbDataUri = sticker.thumbnail ? sticker.thumbnail.dataUri : undefined; const previewBlobUrl = useMedia(`${localMediaHash}?size=m`, !isIntersecting, ApiMediaFormat.BlobUrl); const shouldPlay = isIntersecting && !noAnimate; const lottieData = useMedia(sticker.isLottie && localMediaHash, !shouldPlay, ApiMediaFormat.Lottie); const [isLottieLoaded, markLoaded, unmarkLoaded] = useFlag(Boolean(lottieData)); const canLottiePlay = isLottieLoaded && shouldPlay; const isVideo = sticker.isVideo && IS_WEBM_SUPPORTED; const videoBlobUrl = useMedia(isVideo && localMediaHash, !shouldPlay, ApiMediaFormat.BlobUrl); const canVideoPlay = Boolean(isVideo && videoBlobUrl && shouldPlay); const { transitionClassNames: previewTransitionClassNames } = useShowTransition( Boolean(previewBlobUrl || canLottiePlay), undefined, undefined, 'slow', ); const { isContextMenuOpen, contextMenuPosition, handleBeforeContextMenu, handleContextMenu, handleContextMenuClose, handleContextMenuHide, } = useContextMenuHandlers(ref); const getTriggerElement = useCallback(() => ref.current, []); const getRootElement = useCallback( () => ref.current!.closest('.custom-scroll, .no-scrollbar'), [], ); const getMenuElement = useCallback( () => ref.current!.querySelector('.sticker-context-menu .bubble'), [], ); const { positionX, positionY, transformOriginX, transformOriginY, style: menuStyle, } = useContextMenuPosition( contextMenuPosition, getTriggerElement, getRootElement, getMenuElement, ); // To avoid flickering useEffect(() => { if (!shouldPlay) { unmarkLoaded(); } }, [unmarkLoaded, shouldPlay]); useEffect(() => { if (!isVideo || !ref.current) return; const video = ref.current.querySelector('video'); if (!video) return; if (canVideoPlay) { safePlay(video); } else { video.pause(); } }, [isVideo, canVideoPlay]); useEffect(() => { if (!isIntersecting) handleContextMenuClose(); }, [handleContextMenuClose, isIntersecting]); const handleClick = () => { if (isContextMenuOpen) return; onClick?.(clickArg); }; const handleMouseDown = (e: React.MouseEvent) => { preventMessageInputBlurWithBubbling(e); handleBeforeContextMenu(e); }; const handleRemoveClick = useCallback((e: ReactMouseEvent) => { e.stopPropagation(); e.preventDefault(); onRemoveRecentClick!(sticker); }, [onRemoveRecentClick, sticker]); const handleContextRemoveRecent = useCallback(() => { onRemoveRecentClick!(sticker); }, [onRemoveRecentClick, sticker]); const handleContextUnfave = useCallback(() => { onUnfaveClick!(sticker); }, [onUnfaveClick, sticker]); const handleContextFave = useCallback(() => { onFaveClick!(sticker); }, [onFaveClick, sticker]); const handleSendQuiet = useCallback(() => { onClick?.(clickArg, true); }, [clickArg, onClick]); const handleSendScheduled = useCallback(() => { onClick?.(clickArg, undefined, true); }, [clickArg, onClick]); const handleOpenSet = useCallback(() => { openStickerSet({ sticker }); }, [openStickerSet, sticker]); const shouldShowCloseButton = !IS_TOUCH_ENV && onRemoveRecentClick; const fullClassName = buildClassName( 'StickerButton', onClick && 'interactive', stickerSelector, className, ); const style = (thumbDataUri && !canLottiePlay && !canVideoPlay) ? `background-image: url('${thumbDataUri}');` : ''; return (
{!canLottiePlay && !canVideoPlay && ( // eslint-disable-next-line jsx-a11y/alt-text )} {isVideo && (
); }; export default memo(StickerButton);