TelegramPWA/src/components/middle/composer/SymbolMenuButton.tsx

214 lines
6.5 KiB
TypeScript

import React, {
memo, useCallback, useRef, useState,
} from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { IAnchorPosition } from '../../../types';
import type { ApiVideo, ApiSticker } from '../../../api/types';
import { EDITABLE_INPUT_CSS_SELECTOR, EDITABLE_INPUT_MODAL_CSS_SELECTOR } from '../../../config';
import buildClassName from '../../../util/buildClassName';
import useFlag from '../../../hooks/useFlag';
import useMenuPosition from '../../../hooks/useMenuPosition';
import Button from '../../ui/Button';
import Spinner from '../../ui/Spinner';
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
import SymbolMenu from './SymbolMenu.async';
const MOBILE_KEYBOARD_HIDE_DELAY_MS = 100;
type OwnProps = {
chatId: string;
threadId?: number;
isMobile?: boolean;
isReady?: boolean;
isSymbolMenuOpen?: boolean;
canSendGifs?: boolean;
canSendStickers?: boolean;
openSymbolMenu: VoidFunction;
closeSymbolMenu: VoidFunction;
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: VoidFunction;
onEmojiSelect: (emoji: string) => void;
closeBotCommandMenu?: VoidFunction;
closeSendAsMenu?: VoidFunction;
isSymbolMenuForced?: boolean;
isAttachmentModal?: boolean;
canSendPlainText?: boolean;
className?: string;
};
const SymbolMenuButton: FC<OwnProps> = ({
chatId,
threadId,
isMobile,
canSendGifs,
canSendStickers,
isReady,
isSymbolMenuOpen,
openSymbolMenu,
closeSymbolMenu,
onCustomEmojiSelect,
onStickerSelect,
onGifSelect,
isAttachmentModal,
canSendPlainText,
onRemoveSymbol,
onEmojiSelect,
closeBotCommandMenu,
closeSendAsMenu,
isSymbolMenuForced,
className,
}) => {
const {
setStickerSearchQuery,
setGifSearchQuery,
addRecentEmoji,
addRecentCustomEmoji,
} = getActions();
// eslint-disable-next-line no-null/no-null
const triggerRef = useRef<HTMLDivElement>(null);
const [isSymbolMenuLoaded, onSymbolMenuLoadingComplete] = useFlag();
const [contextMenuPosition, setContextMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
const symbolMenuButtonClassName = buildClassName(
'mobile-symbol-menu-button',
!isReady && 'not-ready',
isSymbolMenuLoaded
? (isSymbolMenuOpen && 'menu-opened')
: (isSymbolMenuOpen && 'is-loading'),
);
const handleActivateSymbolMenu = useCallback(() => {
closeBotCommandMenu?.();
closeSendAsMenu?.();
openSymbolMenu();
const triggerEl = triggerRef.current;
if (!triggerEl) return;
const { x, y } = triggerEl.getBoundingClientRect();
setContextMenuPosition({ x, y });
}, [closeBotCommandMenu, closeSendAsMenu, openSymbolMenu]);
const handleSearchOpen = useCallback((type: 'stickers' | 'gifs') => {
if (type === 'stickers') {
setStickerSearchQuery({ query: '' });
setGifSearchQuery({ query: undefined });
} else {
setGifSearchQuery({ query: '' });
setStickerSearchQuery({ query: undefined });
}
}, [setStickerSearchQuery, setGifSearchQuery]);
const handleSymbolMenuOpen = useCallback(() => {
const messageInput = document.querySelector<HTMLDivElement>(
isAttachmentModal ? EDITABLE_INPUT_MODAL_CSS_SELECTOR : EDITABLE_INPUT_CSS_SELECTOR,
);
if (!isMobile || messageInput !== document.activeElement) {
openSymbolMenu();
return;
}
messageInput?.blur();
setTimeout(() => {
closeBotCommandMenu?.();
openSymbolMenu();
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
}, [isAttachmentModal, isMobile, openSymbolMenu, closeBotCommandMenu]);
const getTriggerElement = useCallback(() => triggerRef.current, []);
const getRootElement = useCallback(
() => triggerRef.current?.closest('.custom-scroll, .no-scrollbar'),
[],
);
const getMenuElement = useCallback(
() => document.querySelector('#portals .SymbolMenu .bubble'),
[],
);
const getLayout = useCallback(() => ({
withPortal: true,
}), []);
const {
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
} = useMenuPosition(
contextMenuPosition,
getTriggerElement,
getRootElement,
getMenuElement,
getLayout,
);
return (
<>
{isMobile ? (
<Button
className={symbolMenuButtonClassName}
round
color="translucent"
onClick={isSymbolMenuOpen ? closeSymbolMenu : handleSymbolMenuOpen}
ariaLabel="Choose emoji, sticker or GIF"
>
<i className="icon icon-smile" />
<i className="icon icon-keyboard" />
{isSymbolMenuOpen && !isSymbolMenuLoaded && <Spinner color="gray" />}
</Button>
) : (
<ResponsiveHoverButton
className={buildClassName('symbol-menu-button', isSymbolMenuOpen && 'activated')}
round
color="translucent"
onActivate={handleActivateSymbolMenu}
ariaLabel="Choose emoji, sticker or GIF"
>
<div ref={triggerRef} className="symbol-menu-trigger" />
<i className="icon icon-smile" />
</ResponsiveHoverButton>
)}
<SymbolMenu
chatId={chatId}
threadId={threadId}
isOpen={isSymbolMenuOpen || Boolean(isSymbolMenuForced)}
canSendGifs={canSendGifs}
canSendStickers={canSendStickers}
onLoad={onSymbolMenuLoadingComplete}
onClose={closeSymbolMenu}
onEmojiSelect={onEmojiSelect}
onStickerSelect={onStickerSelect}
onCustomEmojiSelect={onCustomEmojiSelect}
onGifSelect={onGifSelect}
onRemoveSymbol={onRemoveSymbol}
onSearchOpen={handleSearchOpen}
addRecentEmoji={addRecentEmoji}
addRecentCustomEmoji={addRecentCustomEmoji}
isAttachmentModal={isAttachmentModal}
canSendPlainText={canSendPlainText}
className={className}
positionX={isAttachmentModal ? positionX : undefined}
positionY={isAttachmentModal ? positionY : undefined}
transformOriginX={isAttachmentModal ? transformOriginX : undefined}
transformOriginY={isAttachmentModal ? transformOriginY : undefined}
style={isAttachmentModal ? menuStyle : undefined}
/>
</>
);
};
export default memo(SymbolMenuButton);