// SPDX-License-Identifier: AGPL-3.0-or-later import {useShouldAnimate} from '@app/features/app/hooks/useShouldAnimate'; import {type AutocompleteOption, isEmoji, isMeme, isSticker} from '@app/features/channel/components/Autocomplete'; import styles from '@app/features/channel/components/AutocompleteEmoji.module.css'; import {AutocompleteItem} from '@app/features/channel/components/AutocompleteItem'; import {AutocompleteMemePreview} from '@app/features/channel/components/AutocompleteMemePreview'; import * as EmojiPickerCommands from '@app/features/emoji/commands/EmojiPickerCommands'; import {useStickerAnimation} from '@app/features/emoji/hooks/useStickerAnimation'; import type {FlatEmoji} from '@app/features/emoji/types/EmojiTypes'; import type {GuildSticker} from '@app/features/expressions/models/GuildSticker'; import {getEmojiDisplayData} from '@app/features/expressions/utils/SkinToneUtils'; import Guilds from '@app/features/guild/state/Guilds'; import { EMOJIS_DESCRIPTOR, MEDIA_DESCRIPTOR, STICKERS_DESCRIPTOR, } from '@app/features/i18n/utils/CommonMessageDescriptors'; import {getEmojiRenderUrl} from '@app/features/messaging/utils/markdown/EmojiDetector'; import * as AvatarUtils from '@app/features/user/utils/AvatarUtils'; import {msg} from '@lingui/core/macro'; import {useLingui} from '@lingui/react/macro'; import {observer} from 'mobx-react-lite'; import type React from 'react'; const DEFAULT_EMOJI_DESCRIPTOR = msg({ message: 'Default emoji', comment: 'Short label in the channel and chat autocomplete emoji. Keep it concise.', }); const SectionHeading = observer(({children}: {children: React.ReactNode}) => (
{children}
)); const AutocompleteEmojiIcon = observer(({emoji}: {emoji: FlatEmoji}) => { const isAnimatable = Boolean(emoji.animated); const shouldAnimate = useShouldAnimate({ kind: 'emoji', isAnimated: isAnimatable, }); const {url: fallbackDisplayUrl} = getEmojiDisplayData(emoji); const displayUrl = getEmojiRenderUrl({ id: emoji.id, surrogateUrl: fallbackDisplayUrl ?? null, isAnimatable, animated: shouldAnimate, jumbo: false, }) ?? ''; return ( {emoji.name} ); }); const AutocompleteStickerIcon = observer( ({sticker, isInteracting}: {sticker: GuildSticker; isInteracting: boolean}) => { const {shouldAnimate} = useStickerAnimation({isInteracting, isAnimated: sticker.animated}); return (
{sticker.name}
); }, ); export const AutocompleteEmoji = observer( ({ onSelect, keyboardFocusIndex, hoverIndex, options, onMouseEnter, onMouseLeave, rowRefs, getOptionId, }: { onSelect: (option: AutocompleteOption) => void; keyboardFocusIndex: number; hoverIndex: number; options: Array; onMouseEnter: (index: number) => void; onMouseLeave: () => void; rowRefs?: React.MutableRefObject>; getOptionId?: (index: number) => string; }) => { const {i18n} = useLingui(); const emojis = options.filter(isEmoji); const stickers = options.filter(isSticker); const memes = options.filter(isMeme); const handleEmojiSelect = (option: AutocompleteOption) => { if (isEmoji(option)) EmojiPickerCommands.trackEmojiUsage(option.emoji); onSelect(option); }; return ( <> {emojis.length > 0 && ( <> {i18n._(EMOJIS_DESCRIPTOR)} {emojis.map((option, index) => { return ( } isKeyboardSelected={index === keyboardFocusIndex} isHovered={index === hoverIndex} onSelect={() => handleEmojiSelect(option)} onMouseEnter={() => onMouseEnter(index)} onMouseLeave={onMouseLeave} innerRef={ rowRefs ? (node) => { rowRefs.current[index] = node; } : undefined } data-flx="channel.autocomplete-emoji.autocomplete-item.emoji-select" /> ); })} {(stickers.length > 0 || memes.length > 0) && (
)} )} {stickers.length > 0 && ( <> {i18n._(STICKERS_DESCRIPTOR)} {stickers.map((option, index) => { const currentIndex = emojis.length + index; return ( 0 ? option.sticker.tags.join(', ') : option.sticker.description || undefined } icon={ } isKeyboardSelected={currentIndex === keyboardFocusIndex} isHovered={currentIndex === hoverIndex} onSelect={() => onSelect(option)} onMouseEnter={() => onMouseEnter(currentIndex)} onMouseLeave={onMouseLeave} innerRef={ rowRefs ? (node) => { rowRefs.current[currentIndex] = node; } : undefined } data-flx="channel.autocomplete-emoji.autocomplete-item.select" /> ); })} {memes.length > 0 && (
)} )} {memes.length > 0 && ( <> {i18n._(MEDIA_DESCRIPTOR)} {memes.map((option, index) => { const currentIndex = emojis.length + stickers.length + index; return ( 0 ? option.meme.tags.join(', ') : undefined} icon={
} isKeyboardSelected={currentIndex === keyboardFocusIndex} isHovered={currentIndex === hoverIndex} onSelect={() => onSelect(option)} onMouseEnter={() => onMouseEnter(currentIndex)} onMouseLeave={onMouseLeave} innerRef={ rowRefs ? (node) => { rowRefs.current[currentIndex] = node; } : undefined } data-flx="channel.autocomplete-emoji.autocomplete-item.select--2" /> ); })} )} ); }, );