Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
151 lines
4.7 KiB
TypeScript
151 lines
4.7 KiB
TypeScript
import { useDebouncedState, useStableCallback, useLocalStorage } from '@rocket.chat/fuselage-hooks';
|
|
import type { ReactNode, ContextType } from 'react';
|
|
import { useState, useCallback, useMemo, useSyncExternalStore } from 'react';
|
|
|
|
import { useUpdateCustomEmoji } from './useUpdateCustomEmoji';
|
|
import { EmojiPickerContext } from '../../contexts/EmojiPickerContext';
|
|
import { emoji, getFrequentEmoji, createEmojiListByCategorySubscription } from '../../lib/emoji';
|
|
import EmojiPicker from '../../views/composer/EmojiPicker';
|
|
|
|
const DEFAULT_ITEMS_LIMIT = 90;
|
|
|
|
// limit recent emojis to 27 (3 rows of 9)
|
|
const RECENT_EMOJIS_LIMIT = 27;
|
|
|
|
export type EmojiPickerProviderProps = { children: ReactNode };
|
|
|
|
const EmojiPickerProvider = ({ children }: EmojiPickerProviderProps) => {
|
|
const [emojiPicker, setEmojiPicker] = useState<ReactNode>(null);
|
|
const [emojiToPreview, setEmojiToPreview] = useDebouncedState<{ emoji: string; name: string } | null>(null, 100);
|
|
const [recentEmojis, setRecentEmojis] = useLocalStorage<string[]>('emoji.recent', []);
|
|
const [frequentEmojis, setFrequentEmojis] = useLocalStorage<[string, number][]>('emoji.frequent', []);
|
|
|
|
const [actualTone, setActualTone] = useLocalStorage('emoji.tone', 0);
|
|
const [currentCategory, setCurrentCategory] = useState('recent');
|
|
|
|
const [customItemsLimit, setCustomItemsLimit] = useState(DEFAULT_ITEMS_LIMIT);
|
|
|
|
const [quickReactions, _setQuickReactions] = useState<{ emoji: string; image: string }[]>(() =>
|
|
getFrequentEmoji(frequentEmojis.map(([emoji]) => emoji)),
|
|
);
|
|
|
|
const setQuickReactions = useStableCallback(() => _setQuickReactions(getFrequentEmoji(frequentEmojis.map(([emoji]) => emoji))));
|
|
const [sub, getSnapshot] = useMemo(() => {
|
|
return createEmojiListByCategorySubscription(customItemsLimit, actualTone, recentEmojis, setRecentEmojis, setQuickReactions);
|
|
}, [customItemsLimit, actualTone, recentEmojis, setRecentEmojis, setQuickReactions]);
|
|
|
|
const [emojiListByCategory, categoriesIndexes] = useSyncExternalStore(sub, getSnapshot);
|
|
|
|
useUpdateCustomEmoji();
|
|
|
|
const addFrequentEmojis = useCallback(
|
|
(emoji: string) => {
|
|
const empty: [string, number][] = frequentEmojis.some(([emojiName]) => emojiName === emoji) ? [] : [[emoji, 0]];
|
|
|
|
const sortedFrequent = [...empty, ...frequentEmojis]
|
|
.map(([emojiName, count]) => {
|
|
return emojiName === emoji
|
|
? ([emojiName, Math.min(count + 5, 100)] as [string, number])
|
|
: ([emojiName, Math.max(count - 1, 0)] as [string, number]);
|
|
})
|
|
.sort(([, frequentA], [, frequentB]) => frequentB - frequentA);
|
|
|
|
setFrequentEmojis(sortedFrequent);
|
|
_setQuickReactions(getFrequentEmoji(sortedFrequent.map(([emoji]) => emoji)));
|
|
},
|
|
[frequentEmojis, setFrequentEmojis],
|
|
);
|
|
|
|
const addRecentEmoji = useCallback(
|
|
(_emoji: string) => {
|
|
addFrequentEmojis(_emoji);
|
|
|
|
const recent = recentEmojis || [];
|
|
const pos = recent.indexOf(_emoji);
|
|
|
|
if (pos !== -1) {
|
|
recent.splice(pos, 1);
|
|
}
|
|
|
|
recent.unshift(_emoji);
|
|
|
|
recent.splice(RECENT_EMOJIS_LIMIT);
|
|
|
|
// If this value is not cloned, the recent list will not be updated
|
|
setRecentEmojis([...recent]);
|
|
emoji.packages.base.emojisByCategory.recent = recent;
|
|
},
|
|
[recentEmojis, setRecentEmojis, addFrequentEmojis],
|
|
);
|
|
|
|
const open = useCallback((ref: Element, callback: (emoji: string) => void) => {
|
|
return setEmojiPicker(<EmojiPicker reference={ref} onClose={() => setEmojiPicker(null)} onPickEmoji={(emoji) => callback(emoji)} />);
|
|
}, []);
|
|
|
|
const close = useCallback(() => setEmojiPicker(null), []);
|
|
|
|
const handlePreview = useCallback(
|
|
(emoji: string, name: string) =>
|
|
setEmojiToPreview((preview) => {
|
|
if (preview?.emoji === emoji && preview?.name === name) {
|
|
return preview;
|
|
}
|
|
return { emoji, name };
|
|
}),
|
|
[setEmojiToPreview],
|
|
);
|
|
|
|
const handleRemovePreview = useCallback(() => setEmojiToPreview(null), [setEmojiToPreview]);
|
|
|
|
const contextValue = useMemo(
|
|
(): ContextType<typeof EmojiPickerContext> => ({
|
|
isOpen: emojiPicker !== null,
|
|
close,
|
|
open,
|
|
emojiToPreview,
|
|
handlePreview,
|
|
handleRemovePreview,
|
|
addRecentEmoji,
|
|
emojiListByCategory,
|
|
recentEmojis,
|
|
setRecentEmojis,
|
|
actualTone,
|
|
currentCategory,
|
|
setCurrentCategory,
|
|
categoriesIndexes,
|
|
customItemsLimit,
|
|
setCustomItemsLimit,
|
|
setActualTone,
|
|
quickReactions,
|
|
}),
|
|
[
|
|
emojiPicker,
|
|
open,
|
|
close,
|
|
emojiToPreview,
|
|
addRecentEmoji,
|
|
emojiListByCategory,
|
|
categoriesIndexes,
|
|
recentEmojis,
|
|
setRecentEmojis,
|
|
actualTone,
|
|
currentCategory,
|
|
setCurrentCategory,
|
|
customItemsLimit,
|
|
setActualTone,
|
|
quickReactions,
|
|
handlePreview,
|
|
handleRemovePreview,
|
|
],
|
|
);
|
|
|
|
return (
|
|
<EmojiPickerContext.Provider value={contextValue}>
|
|
{children}
|
|
{emojiPicker}
|
|
</EmojiPickerContext.Provider>
|
|
);
|
|
};
|
|
|
|
export default EmojiPickerProvider;
|