111 lines
3.4 KiB
TypeScript
111 lines
3.4 KiB
TypeScript
import type { FC } from '../../../lib/teact/teact';
|
|
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
|
import { getActions, getGlobal } from '../../../global';
|
|
|
|
import type { ApiBotCommand } from '../../../api/types';
|
|
import type { Signal } from '../../../util/signals';
|
|
|
|
import buildClassName from '../../../util/buildClassName';
|
|
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
|
|
|
import useLastCallback from '../../../hooks/useLastCallback';
|
|
import usePrevious from '../../../hooks/usePrevious';
|
|
import useShowTransition from '../../../hooks/useShowTransition';
|
|
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
|
|
|
|
import BotCommand from './BotCommand';
|
|
|
|
import './BotCommandTooltip.scss';
|
|
|
|
export type OwnProps = {
|
|
isOpen: boolean;
|
|
withUsername?: boolean;
|
|
botCommands?: ApiBotCommand[];
|
|
getHtml: Signal<string>;
|
|
onClick: NoneToVoidFunction;
|
|
onClose: NoneToVoidFunction;
|
|
};
|
|
|
|
const BotCommandTooltip: FC<OwnProps> = ({
|
|
isOpen,
|
|
withUsername,
|
|
botCommands,
|
|
getHtml,
|
|
onClick,
|
|
onClose,
|
|
}) => {
|
|
const { sendBotCommand } = getActions();
|
|
|
|
// eslint-disable-next-line no-null/no-null
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, undefined, undefined, false);
|
|
|
|
const handleSendCommand = useLastCallback(({ botId, command }: ApiBotCommand) => {
|
|
// No need for expensive global updates on users and chats, so we avoid them
|
|
const usersById = getGlobal().users.byId;
|
|
const bot = usersById[botId];
|
|
|
|
sendBotCommand({
|
|
command: `/${command}${withUsername && bot ? `@${bot.usernames![0].username}` : ''}`,
|
|
});
|
|
onClick();
|
|
});
|
|
|
|
const handleSelect = useLastCallback((botCommand: ApiBotCommand) => {
|
|
// We need an additional check because tooltip is updated with throttling
|
|
if (!botCommand.command.startsWith(getHtml().slice(1))) {
|
|
return false;
|
|
}
|
|
|
|
handleSendCommand(botCommand);
|
|
return true;
|
|
});
|
|
|
|
const selectedCommandIndex = useKeyboardNavigation({
|
|
isActive: isOpen,
|
|
items: botCommands,
|
|
onSelect: handleSelect,
|
|
onClose,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (botCommands && !botCommands.length) {
|
|
onClose();
|
|
}
|
|
}, [botCommands, onClose]);
|
|
|
|
useEffect(() => {
|
|
setTooltipItemVisible('.chat-item-clickable', selectedCommandIndex, containerRef);
|
|
}, [selectedCommandIndex]);
|
|
|
|
const prevCommands = usePrevious(botCommands && botCommands.length ? botCommands : undefined, shouldRender);
|
|
const renderedCommands = botCommands && !botCommands.length ? prevCommands : botCommands;
|
|
|
|
if (!shouldRender || (renderedCommands && !renderedCommands.length)) {
|
|
return undefined;
|
|
}
|
|
|
|
const className = buildClassName(
|
|
'BotCommandTooltip composer-tooltip custom-scroll',
|
|
transitionClassNames,
|
|
);
|
|
|
|
return (
|
|
<div className={className} ref={containerRef}>
|
|
{renderedCommands && renderedCommands.map((chatBotCommand, index) => (
|
|
<BotCommand
|
|
key={`${chatBotCommand.botId}_${chatBotCommand.command}`}
|
|
botCommand={chatBotCommand}
|
|
// No need for expensive global updates on users and chats, so we avoid them
|
|
bot={getGlobal().users.byId[chatBotCommand.botId]}
|
|
withAvatar
|
|
onClick={handleSendCommand}
|
|
focus={selectedCommandIndex === index}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default memo(BotCommandTooltip);
|