import classNames from 'clsx'; import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import React, { useRef, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; // @ts-ignore import Overlay from 'react-overlays/lib/Overlay'; import { createSelector } from 'reselect'; import { useEmoji } from 'soapbox/actions/emojis'; import { getSettings, changeSetting } from 'soapbox/actions/settings'; import { IconButton } from 'soapbox/components/ui'; import { EmojiPicker as EmojiPickerAsync } from 'soapbox/features/ui/util/async-components'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import EmojiPickerMenu from './emoji-picker-menu'; import type { Emoji as EmojiType } from 'soapbox/components/autosuggest-emoji'; import type { RootState } from 'soapbox/store'; let EmojiPicker: any, Emoji: any; // load asynchronously const perLine = 8; const lines = 2; const DEFAULTS = [ '+1', 'grinning', 'kissing_heart', 'heart_eyes', 'laughing', 'stuck_out_tongue_winking_eye', 'sweat_smile', 'joy', 'yum', 'disappointed', 'thinking_face', 'weary', 'sob', 'sunglasses', 'heart', 'ok_hand', ]; const getFrequentlyUsedEmojis = createSelector([ (state: RootState) => state.settings.get('frequentlyUsedEmojis', ImmutableMap()), ], emojiCounters => { let emojis = emojiCounters .keySeq() .sort((a: number, b: number) => emojiCounters.get(a) - emojiCounters.get(b)) .reverse() .slice(0, perLine * lines) .toArray(); if (emojis.length < DEFAULTS.length) { const uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji)); emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length)); } return emojis; }); const getCustomEmojis = createSelector([ (state: RootState) => state.custom_emojis as ImmutableList>, ], emojis => emojis.filter((e) => e.get('visible_in_picker')).sort((a, b) => { const aShort = a.get('shortcode')!.toLowerCase(); const bShort = b.get('shortcode')!.toLowerCase(); if (aShort < bShort) { return -1; } else if (aShort > bShort) { return 1; } else { return 0; } }) as ImmutableList>); const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search…' }, emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emoji\'s found.' }, custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' }, recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' }, search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' }, people: { id: 'emoji_button.people', defaultMessage: 'People' }, nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' }, food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' }, activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' }, travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' }, objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' }, symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' }, flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' }, }); interface IEmojiPickerDropdown { onPickEmoji: (data: EmojiType) => void, button?: JSX.Element, } const EmojiPickerDropdown: React.FC = ({ onPickEmoji, button }) => { const intl = useIntl(); const dispatch = useAppDispatch(); const customEmojis = useAppSelector((state) => getCustomEmojis(state)); const skinTone = useAppSelector((state) => getSettings(state).get('skinTone') as number); const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state)); const [active, setActive] = useState(false); const [loading, setLoading] = useState(false); const [placement, setPlacement] = useState<'bottom' | 'top'>(); const target = useRef(null); const onSkinTone = (skinTone: number) => { dispatch(changeSetting(['skinTone'], skinTone)); }; const handlePickEmoji = (emoji: EmojiType) => { // eslint-disable-next-line react-hooks/rules-of-hooks dispatch(useEmoji(emoji)); if (onPickEmoji) { onPickEmoji(emoji); } }; const onShowDropdown: React.EventHandler = (e) => { e.stopPropagation(); setActive(true); if (!EmojiPicker) { setLoading(true); EmojiPickerAsync().then(EmojiMart => { EmojiPicker = EmojiMart.Picker; Emoji = EmojiMart.Emoji; setLoading(false); }).catch(() => { setLoading(false); }); } const { top } = (e.target as any).getBoundingClientRect(); setPlacement(top * 2 < innerHeight ? 'bottom' : 'top'); }; const onHideDropdown = () => { setActive(false); }; const onToggle: React.EventHandler = (e) => { if (!loading && (!(e as React.KeyboardEvent).key || (e as React.KeyboardEvent).key === 'Enter')) { if (active) { onHideDropdown(); } else { onShowDropdown(e); } } }; const handleKeyDown: React.KeyboardEventHandler = e => { if (e.key === 'Escape') { onHideDropdown(); } }; const title = intl.formatMessage(messages.emoji); return (
{button || }
); }; export { EmojiPicker, Emoji }; export default EmojiPickerDropdown;