diff --git a/src/components/ui/emoji-selector.tsx b/src/components/ui/emoji-selector.tsx index 6d4b716e0..8760c6eaf 100644 --- a/src/components/ui/emoji-selector.tsx +++ b/src/components/ui/emoji-selector.tsx @@ -8,12 +8,12 @@ import { closeModal, openModal } from 'soapbox/actions/modals.ts'; import EmojiComponent from 'soapbox/components/ui/emoji.tsx'; import HStack from 'soapbox/components/ui/hstack.tsx'; import IconButton from 'soapbox/components/ui/icon-button.tsx'; -import EmojiPickerDropdown, { getFrequentlyUsedEmojis } from 'soapbox/features/emoji/components/emoji-picker-dropdown.tsx'; +import EmojiPickerDropdown from 'soapbox/features/emoji/components/emoji-picker-dropdown.tsx'; import emojiData from 'soapbox/features/emoji/data.ts'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts'; -import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts'; import { useClickOutside } from 'soapbox/hooks/useClickOutside.ts'; import { useFeatures } from 'soapbox/hooks/useFeatures.ts'; +import { useFrequentlyUsedEmojis } from 'soapbox/hooks/useFrequentlyUsedEmojis.ts'; import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts'; import { userTouching } from 'soapbox/is-mobile.ts'; @@ -74,7 +74,7 @@ const EmojiSelector: React.FC = ({ }): JSX.Element => { const { allowedEmoji } = useSoapboxConfig(); const { customEmojiReacts } = useFeatures(); - const shortcodes = useAppSelector((state) => getFrequentlyUsedEmojis(state)); + const frequentlyUsedEmojis = useFrequentlyUsedEmojis(); const dispatch = useAppDispatch(); const [expanded, setExpanded] = useState(false); @@ -138,7 +138,8 @@ const EmojiSelector: React.FC = ({ onClose?.(); }); - const recentEmojis = shortcodes.reduce((results, shortcode) => { + /** Frequently used emojis converted from shortcodes to native. */ + const frequentNative = frequentlyUsedEmojis.reduce((results, shortcode) => { const emoji = emojiData.emojis[shortcode]?.skins[0]?.native; if (emoji) { results.push(emoji); @@ -146,7 +147,8 @@ const EmojiSelector: React.FC = ({ return results; }, []); - const emojis = new Set([...recentEmojis, ...allowedEmoji]); + /** Set of native emojis to display in the selector. */ + const emojis = new Set([...frequentNative, ...allowedEmoji]); return (
any) | null; } -const perLine = 8; -const lines = 2; - -export const getFrequentlyUsedEmojis = createSelector([ - (state: RootState) => state.settings.get('frequentlyUsedEmojis', ImmutableMap()), -], (emojiCounters: ImmutableMap) => { - return emojiCounters - .keySeq() - .sort((a, b) => emojiCounters.get(a)! - emojiCounters.get(b)!) - .reverse() - .slice(0, perLine * lines) - .toArray(); -}); - /** Filter custom emojis to only ones visible in the picker, and sort them alphabetically. */ function filterCustomEmojis(customEmojis: MastodonCustomEmoji[]) { return customEmojis.filter(e => e.visible_in_picker).sort((a, b) => { @@ -115,7 +98,7 @@ const EmojiPickerDropdown: React.FC = ({ const theme = useTheme(); const { customEmojis } = useCustomEmojis(); - const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state)); + const frequentlyUsedEmojis = useFrequentlyUsedEmojis(); const handlePick = (emoji: any) => { setVisible?.(false); @@ -206,7 +189,7 @@ const EmojiPickerDropdown: React.FC = ({ custom={withCustom ? [{ emojis: buildCustomEmojis(filterCustomEmojis(customEmojis)) }] : undefined} title={title} onEmojiSelect={handlePick} - recent={frequentlyUsedEmojis} + recent={frequentlyUsedEmojis.slice(0, 16)} perLine={8} skin={handleSkinTone} emojiSize={22} diff --git a/src/hooks/useFrequentlyUsedEmojis.ts b/src/hooks/useFrequentlyUsedEmojis.ts new file mode 100644 index 000000000..f8bd8a7bd --- /dev/null +++ b/src/hooks/useFrequentlyUsedEmojis.ts @@ -0,0 +1,15 @@ +import { useMemo } from 'react'; + +import { useSettings } from 'soapbox/hooks/useSettings.ts'; + +/** Return a sorted list of most used emoji **shortcodes** from settings. */ +export function useFrequentlyUsedEmojis(): string[] { + const { frequentlyUsedEmojis } = useSettings(); + + return useMemo(() => { + return Object.entries(frequentlyUsedEmojis) + .sort((a, b) => b[1] - a[1]) + .map(([emoji]) => emoji); + + }, [frequentlyUsedEmojis]); +} \ No newline at end of file diff --git a/src/schemas/soapbox/settings.ts b/src/schemas/soapbox/settings.ts index 9f70c2c21..9fdbb6f01 100644 --- a/src/schemas/soapbox/settings.ts +++ b/src/schemas/soapbox/settings.ts @@ -75,6 +75,10 @@ const settingsSchema = z.object({ }), /** Settings notifications that have been dismissed. See `useSettingsNotifications` hook. */ dismissedSettingsNotifications: z.array(z.string()).catch([]), + frequentlyUsedEmojis: z.record( + z.string(), + z.number().int().nonnegative(), + ).catch({}), }); type Settings = z.infer;