// 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 (
);
});
const AutocompleteStickerIcon = observer(
({sticker, isInteracting}: {sticker: GuildSticker; isInteracting: boolean}) => {
const {shouldAnimate} = useStickerAnimation({isInteracting, isAnimated: sticker.animated});
return (
);
},
);
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"
/>
);
})}
>
)}
>
);
},
);