import React from 'react'; import { CustomEmoji } from '../../config/customEmojiConfig'; import { useSuggestedEmojisModeConfig } from '../../config/useConfig'; import defaultEmojiData from '../../data/emojis'; import { DataEmoji, DataEmojis, EmojiProperties, EmojiProperties as Keys, } from '../../dataUtils/DataTypes'; import { emojiByUnified } from '../../dataUtils/emojiSelectors'; import { activeVariationFromUnified, unifiedWithoutSkinTone, } from '../../dataUtils/emojiUtils'; import { getSuggested } from '../../dataUtils/suggested'; import { Categories, EmojiData, SkinTones } from '../../types/exposedTypes'; import { usePickerConfig } from './PickerConfigContext'; import { useUpdateSuggested } from './PickerContext'; export interface PickerDataContextValue { emojiData: EmojiData; allEmojis: DataEmojis; allEmojisByUnified: Record; searchIndex: Record>; emojiByUnified: (unified?: string) => DataEmoji | undefined; activeVariationFromUnified: (unified: string) => SkinTones | null; } const PickerDataContext = React.createContext({ emojiData: {} as EmojiData, allEmojis: [], allEmojisByUnified: {}, searchIndex: {}, emojiByUnified, activeVariationFromUnified: () => null, }); export function PickerDataProvider({ children, }: { children: React.ReactNode; }) { const { customEmojis, emojiData: genericEmojiData } = usePickerConfig(); const data = React.useMemo(() => { const emojiData = genericEmojiData || (defaultEmojiData as EmojiData); // Clone to avoid mutation of shared source const newData: EmojiData = JSON.parse(JSON.stringify(emojiData)); if (customEmojis && customEmojis.length > 0) { newData.emojis[Categories.CUSTOM] = customEmojis.map(customToRegularEmoji); } const emojis = newData.emojis || {}; const allEmojis: DataEmojis = Object.values(emojis).flat(); const allEmojisByUnified: Record = {}; const searchIndex: Record> = {}; allEmojis.forEach((emoji) => { const unified = emoji[Keys.unified]; allEmojisByUnified[unified] = emoji; if (emoji[Keys.variations]) { emoji[Keys.variations]?.forEach((variation) => { allEmojisByUnified[variation] = emoji; }); } // Index for search // Re-implement indexEmoji logic here to be local const joinedNameString = (emoji[Keys.name] || []) .join('') .toLowerCase() .split(''); joinedNameString.forEach((char: string) => { searchIndex[char] = searchIndex[char] ?? {}; searchIndex[char][unified] = emoji; }); }); return { emojiData: newData, allEmojis, allEmojisByUnified, searchIndex, }; }, [genericEmojiData, customEmojis]); const emojiByUnified = React.useCallback( (unified?: string): DataEmoji | undefined => { if (!unified) return undefined; const result = data.allEmojisByUnified[unified] ?? data.allEmojisByUnified[unifiedWithoutSkinTone(unified)]; return result; }, [data.allEmojisByUnified], ); return ( {children} ); } export function usePickerDataContext() { return React.useContext(PickerDataContext); } export function useGetEmojisByCategory() { const { emojiData, emojiByUnified } = usePickerDataContext(); const suggestedEmojisModeConfig = useSuggestedEmojisModeConfig(); const [suggestedUpdated] = useUpdateSuggested(); const suggested = React.useMemo(() => { const suggested = getSuggested(suggestedEmojisModeConfig) ?? []; return suggested .map((s) => { const emoji = emojiByUnified(s.unified); if (!emoji) return undefined; return { ...emoji, [Keys.unified]: s.unified, }; }) .filter(Boolean) as DataEmojis; // eslint-disable-next-line react-hooks/exhaustive-deps }, [suggestedUpdated, suggestedEmojisModeConfig, emojiByUnified]); return function getEmojisByCategory(category: Categories): DataEmojis { if (category === Categories.SUGGESTED) { return suggested; } return emojiData.emojis?.[category] ?? []; }; } function customToRegularEmoji(emoji: CustomEmoji): DataEmoji { return { [EmojiProperties.name]: emoji.names.map((name: string) => name.toLowerCase(), ), [EmojiProperties.unified]: emoji.id.toLowerCase(), [EmojiProperties.added_in]: '0', [EmojiProperties.imgUrl]: emoji.imgUrl, }; }