Message: Fix parallel emoji interactions (#1678)

This commit is contained in:
Alexander Zinchuk 2022-02-20 13:39:33 +02:00
parent e33e22c982
commit 74d348b00a
10 changed files with 122 additions and 96 deletions

View File

@ -27,7 +27,7 @@ type OwnProps = {
forceLoadPreview?: boolean; forceLoadPreview?: boolean;
messageId?: number; messageId?: number;
chatId?: string; chatId?: string;
activeEmojiInteraction?: ActiveEmojiInteraction; activeEmojiInteractions?: ActiveEmojiInteraction[];
}; };
const QUALITY = 1; const QUALITY = 1;
@ -43,7 +43,7 @@ const AnimatedEmoji: FC<OwnProps> = ({
forceLoadPreview, forceLoadPreview,
messageId, messageId,
chatId, chatId,
activeEmojiInteraction, activeEmojiInteractions,
}) => { }) => {
const { const {
markAnimationLoaded, markAnimationLoaded,
@ -53,7 +53,7 @@ const AnimatedEmoji: FC<OwnProps> = ({
style, style,
handleClick, handleClick,
playKey, playKey,
} = useAnimatedEmoji(size, chatId, messageId, soundId, activeEmojiInteraction, isOwn, undefined, effect?.emoji); } = useAnimatedEmoji(size, chatId, messageId, soundId, activeEmojiInteractions, isOwn, undefined, effect?.emoji);
const localMediaHash = `sticker${sticker.id}`; const localMediaHash = `sticker${sticker.id}`;

View File

@ -23,7 +23,7 @@ type OwnProps = {
forceLoadPreview?: boolean; forceLoadPreview?: boolean;
messageId?: number; messageId?: number;
chatId?: string; chatId?: string;
activeEmojiInteraction?: ActiveEmojiInteraction; activeEmojiInteractions?: ActiveEmojiInteraction[];
}; };
const LocalAnimatedEmoji: FC<OwnProps> = ({ const LocalAnimatedEmoji: FC<OwnProps> = ({
@ -35,7 +35,7 @@ const LocalAnimatedEmoji: FC<OwnProps> = ({
observeIntersection, observeIntersection,
messageId, messageId,
chatId, chatId,
activeEmojiInteraction, activeEmojiInteractions,
}) => { }) => {
const { const {
playKey, playKey,
@ -44,7 +44,7 @@ const LocalAnimatedEmoji: FC<OwnProps> = ({
width, width,
handleClick, handleClick,
markAnimationLoaded, markAnimationLoaded,
} = useAnimatedEmoji(size, chatId, messageId, soundId, activeEmojiInteraction, isOwn, localEffect); } = useAnimatedEmoji(size, chatId, messageId, soundId, activeEmojiInteractions, isOwn, localEffect);
const id = `local_emoji_${localSticker}`; const id = `local_emoji_${localSticker}`;
const isIntersecting = useIsIntersecting(ref, observeIntersection); const isIntersecting = useIsIntersecting(ref, observeIntersection);

View File

@ -22,7 +22,7 @@ export default function useAnimatedEmoji(
chatId?: string, chatId?: string,
messageId?: number, messageId?: number,
soundId?: string, soundId?: string,
activeEmojiInteraction?: ActiveEmojiInteraction, activeEmojiInteractions?: ActiveEmojiInteraction[],
isOwn?: boolean, isOwn?: boolean,
localEffect?: string, localEffect?: string,
emoji?: string, emoji?: string,
@ -120,16 +120,21 @@ export default function useAnimatedEmoji(
useEffect(() => { useEffect(() => {
const container = ref.current; const container = ref.current;
if (!container || !activeEmojiInteraction) return; if (!container || !activeEmojiInteractions) return;
const { activeEmojiInteractions.forEach(({
messageId: selectedMessageId, endX, endY, id,
} = activeEmojiInteraction; startSize,
messageId: interactionMessageId,
}) => {
if (startSize || messageId !== interactionMessageId) {
return;
}
if (!endX && !endY && selectedMessageId === messageId) {
const { x, y } = container.getBoundingClientRect(); const { x, y } = container.getBoundingClientRect();
sendWatchingEmojiInteraction({ sendWatchingEmojiInteraction({
id,
chatId, chatId,
emoticon: localEffect ? selectLocalAnimatedEmojiEffectByName(localEffect) : emoji, emoticon: localEffect ? selectLocalAnimatedEmojiEffectByName(localEffect) : emoji,
startSize: width, startSize: width,
@ -138,9 +143,9 @@ export default function useAnimatedEmoji(
isReversed: !isOwn, isReversed: !isOwn,
}); });
play(); play();
} });
}, [ }, [
activeEmojiInteraction, chatId, emoji, isOwn, localEffect, messageId, play, sendWatchingEmojiInteraction, width, activeEmojiInteractions, chatId, emoji, isOwn, localEffect, messageId, play, sendWatchingEmojiInteraction, width,
]); ]);
return { return {

View File

@ -5,8 +5,9 @@ import { Bundles } from '../../util/moduleLoader';
import useModuleLoader from '../../hooks/useModuleLoader'; import useModuleLoader from '../../hooks/useModuleLoader';
const EmojiInteractionAnimationAsync: FC<OwnProps> = (props) => { const EmojiInteractionAnimationAsync: FC<OwnProps> = (props) => {
const { emojiInteraction } = props; const { activeEmojiInteraction } = props;
const EmojiInteractionAnimation = useModuleLoader(Bundles.Extra, 'EmojiInteractionAnimation', !emojiInteraction); const EmojiInteractionAnimation = useModuleLoader(Bundles.Extra, 'EmojiInteractionAnimation',
!activeEmojiInteraction);
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
return EmojiInteractionAnimation ? <EmojiInteractionAnimation {...props} /> : undefined; return EmojiInteractionAnimation ? <EmojiInteractionAnimation {...props} /> : undefined;

View File

@ -1,5 +1,5 @@
import React, { import React, {
FC, memo, useCallback, useEffect, useLayoutEffect, useState, FC, memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getDispatch, withGlobal } from '../../lib/teact/teactn'; import { getDispatch, withGlobal } from '../../lib/teact/teactn';
@ -21,13 +21,12 @@ import AnimatedSticker from '../common/AnimatedSticker';
import './EmojiInteractionAnimation.scss'; import './EmojiInteractionAnimation.scss';
export type OwnProps = { export type OwnProps = {
emojiInteraction: ActiveEmojiInteraction; activeEmojiInteraction: ActiveEmojiInteraction;
}; };
type StateProps = { type StateProps = {
effectAnimationId?: string; effectAnimationId?: string;
localEffectAnimation?: string; localEffectAnimation?: string;
isReversed?: boolean;
}; };
const HIDE_ANIMATION_DURATION = 250; const HIDE_ANIMATION_DURATION = 250;
@ -35,41 +34,50 @@ const PLAYING_DURATION = 3000;
const EFFECT_SIZE = 240; const EFFECT_SIZE = 240;
const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
emojiInteraction,
effectAnimationId, effectAnimationId,
localEffectAnimation, localEffectAnimation,
isReversed, activeEmojiInteraction,
}) => { }) => {
const { stopActiveEmojiInteraction } = getDispatch(); const { stopActiveEmojiInteraction } = getDispatch();
const [isHiding, startHiding] = useFlag(false); const [isHiding, startHiding] = useFlag(false);
const [isPlaying, startPlaying] = useFlag(false); const [isPlaying, startPlaying] = useFlag(false);
const timeoutRef = useRef<NodeJS.Timeout>();
const stop = useCallback(() => { const stop = useCallback(() => {
startHiding(); startHiding();
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setTimeout(() => { setTimeout(() => {
stopActiveEmojiInteraction(); stopActiveEmojiInteraction({ id: activeEmojiInteraction.id });
}, HIDE_ANIMATION_DURATION); }, HIDE_ANIMATION_DURATION);
}, [startHiding, stopActiveEmojiInteraction]); }, [activeEmojiInteraction.id, startHiding, stopActiveEmojiInteraction]);
const handleCancelAnimation = useCallback((e: UIEvent) => {
if (!(e.target as HTMLElement)?.closest('.AnimatedEmoji')) {
stop();
}
}, [stop]);
useEffect(() => { useEffect(() => {
document.addEventListener('touchstart', stop); document.addEventListener('touchstart', handleCancelAnimation);
document.addEventListener('touchmove', stop); document.addEventListener('touchmove', handleCancelAnimation);
document.addEventListener('mousedown', stop); document.addEventListener('mousedown', handleCancelAnimation);
document.addEventListener('wheel', stop); document.addEventListener('wheel', handleCancelAnimation);
return () => { return () => {
document.removeEventListener('touchstart', stop); document.removeEventListener('touchstart', handleCancelAnimation);
document.removeEventListener('touchmove', stop); document.removeEventListener('touchmove', handleCancelAnimation);
document.removeEventListener('mousedown', stop); document.removeEventListener('mousedown', handleCancelAnimation);
document.removeEventListener('wheel', stop); document.removeEventListener('wheel', handleCancelAnimation);
}; };
}, [stop]); }, [handleCancelAnimation]);
useLayoutEffect(() => { useLayoutEffect(() => {
const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent(); const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent();
setTimeout(() => { timeoutRef.current = setTimeout(() => {
stop(); stop();
dispatchHeavyAnimationStop(); dispatchHeavyAnimationStop();
}, PLAYING_DURATION); }, PLAYING_DURATION);
@ -86,16 +94,24 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
} }
}, [localEffectAnimation]); }, [localEffectAnimation]);
const scale = (emojiInteraction.startSize || 0) / EFFECT_SIZE; if (!activeEmojiInteraction.startSize) {
return undefined;
}
const scale = (activeEmojiInteraction.startSize || 0) / EFFECT_SIZE;
return ( return (
<div <div
className={buildClassName( className={buildClassName(
'EmojiInteractionAnimation', isHiding && 'hiding', isPlaying && 'playing', isReversed && 'reversed', 'EmojiInteractionAnimation',
isHiding && 'hiding',
isPlaying && 'playing',
activeEmojiInteraction.isReversed && 'reversed',
)} )}
style={`--scale: ${scale}; --start-x: ${emojiInteraction.x}px; --start-y: ${emojiInteraction.y}px;`} style={`--scale: ${scale}; --start-x: ${activeEmojiInteraction.x}px; --start-y: ${activeEmojiInteraction.y}px;`}
> >
<AnimatedSticker <AnimatedSticker
key={`effect_${effectAnimationId}`}
id={`effect_${effectAnimationId}`} id={`effect_${effectAnimationId}`}
size={EFFECT_SIZE} size={EFFECT_SIZE}
animationData={localEffectAnimationData || effectAnimationData} animationData={localEffectAnimationData || effectAnimationData}
@ -110,15 +126,14 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
}; };
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { emojiInteraction }): StateProps => { (global, { activeEmojiInteraction }): StateProps => {
const animatedEffect = emojiInteraction.animatedEffect !== undefined const animatedEffect = activeEmojiInteraction.animatedEffect !== undefined
&& selectAnimatedEmojiEffect(global, emojiInteraction.animatedEffect); && selectAnimatedEmojiEffect(global, activeEmojiInteraction.animatedEffect);
return { return {
effectAnimationId: animatedEffect ? animatedEffect.id : undefined, effectAnimationId: animatedEffect ? animatedEffect.id : undefined,
localEffectAnimation: !animatedEffect && emojiInteraction.animatedEffect localEffectAnimation: !animatedEffect && activeEmojiInteraction.animatedEffect
&& Object.keys(ANIMATED_STICKERS_PATHS).includes(emojiInteraction.animatedEffect) && Object.keys(ANIMATED_STICKERS_PATHS).includes(activeEmojiInteraction.animatedEffect)
? emojiInteraction.animatedEffect : undefined, ? activeEmojiInteraction.animatedEffect : undefined,
isReversed: emojiInteraction.isReversed,
}; };
}, },
)(EmojiInteractionAnimation)); )(EmojiInteractionAnimation));

View File

@ -109,7 +109,7 @@ type StateProps = {
canSubscribe?: boolean; canSubscribe?: boolean;
canStartBot?: boolean; canStartBot?: boolean;
canRestartBot?: boolean; canRestartBot?: boolean;
activeEmojiInteraction?: ActiveEmojiInteraction; activeEmojiInteractions?: ActiveEmojiInteraction[];
}; };
const CLOSE_ANIMATION_DURATION = IS_SINGLE_COLUMN_LAYOUT ? 450 + ANIMATION_END_DELAY : undefined; const CLOSE_ANIMATION_DURATION = IS_SINGLE_COLUMN_LAYOUT ? 450 + ANIMATION_END_DELAY : undefined;
@ -150,7 +150,7 @@ const MiddleColumn: FC<StateProps> = ({
canSubscribe, canSubscribe,
canStartBot, canStartBot,
canRestartBot, canRestartBot,
activeEmojiInteraction, activeEmojiInteractions,
}) => { }) => {
const { const {
openChat, openChat,
@ -522,9 +522,15 @@ const MiddleColumn: FC<StateProps> = ({
onUnpin={handleUnpinAllMessages} onUnpin={handleUnpinAllMessages}
/> />
)} )}
{activeEmojiInteraction && ( <div teactFastList>
<EmojiInteractionAnimation emojiInteraction={activeEmojiInteraction} /> {activeEmojiInteractions?.map((activeEmojiInteraction, i) => (
)} <EmojiInteractionAnimation
teactOrderKey={i}
key={activeEmojiInteraction.id}
activeEmojiInteraction={activeEmojiInteraction}
/>
))}
</div>
</div> </div>
); );
}; };
@ -538,7 +544,7 @@ export default memo(withGlobal(
const { messageLists } = global.messages; const { messageLists } = global.messages;
const currentMessageList = selectCurrentMessageList(global); const currentMessageList = selectCurrentMessageList(global);
const { isLeftColumnShown, chats: { listIds }, activeEmojiInteraction } = global; const { isLeftColumnShown, chats: { listIds }, activeEmojiInteractions } = global;
const state: StateProps = { const state: StateProps = {
theme, theme,
@ -556,7 +562,7 @@ export default memo(withGlobal(
isReactorListModalOpen: Boolean(global.reactorModal), isReactorListModalOpen: Boolean(global.reactorModal),
animationLevel: global.settings.byKey.animationLevel, animationLevel: global.settings.byKey.animationLevel,
currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1), currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1),
activeEmojiInteraction, activeEmojiInteractions,
}; };
if (!currentMessageList || !listIds.active) { if (!currentMessageList || !listIds.active) {

View File

@ -185,7 +185,7 @@ type StateProps = {
availableReactions?: ApiAvailableReaction[]; availableReactions?: ApiAvailableReaction[];
defaultReaction?: string; defaultReaction?: string;
activeReaction?: ActiveReaction; activeReaction?: ActiveReaction;
activeEmojiInteraction?: ActiveEmojiInteraction; activeEmojiInteractions?: ActiveEmojiInteraction[];
}; };
type MetaPosition = type MetaPosition =
@ -261,7 +261,7 @@ const Message: FC<OwnProps & StateProps> = ({
availableReactions, availableReactions,
defaultReaction, defaultReaction,
activeReaction, activeReaction,
activeEmojiInteraction, activeEmojiInteractions,
messageListType, messageListType,
isPinnedList, isPinnedList,
isDownloading, isDownloading,
@ -637,7 +637,7 @@ const Message: FC<OwnProps & StateProps> = ({
forceLoadPreview={isLocal} forceLoadPreview={isLocal}
messageId={messageId} messageId={messageId}
chatId={chatId} chatId={chatId}
activeEmojiInteraction={activeEmojiInteraction} activeEmojiInteractions={activeEmojiInteractions}
/> />
)} )}
{localSticker && ( {localSticker && (
@ -652,7 +652,7 @@ const Message: FC<OwnProps & StateProps> = ({
forceLoadPreview={isLocal} forceLoadPreview={isLocal}
messageId={messageId} messageId={messageId}
chatId={chatId} chatId={chatId}
activeEmojiInteraction={activeEmojiInteraction} activeEmojiInteractions={activeEmojiInteractions}
/> />
)} )}
{isAlbum && ( {isAlbum && (
@ -1055,7 +1055,7 @@ export default memo(withGlobal<OwnProps>(
availableReactions: global.availableReactions, availableReactions: global.availableReactions,
defaultReaction: isMessageLocal(message) ? undefined : selectDefaultReaction(global, chatId), defaultReaction: isMessageLocal(message) ? undefined : selectDefaultReaction(global, chatId),
activeReaction: reactionMessage && global.activeReactions[reactionMessage.id], activeReaction: reactionMessage && global.activeReactions[reactionMessage.id],
activeEmojiInteraction: global.activeEmojiInteraction, activeEmojiInteractions: global.activeEmojiInteractions,
...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }), ...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }),
...(typeof uploadProgress === 'number' && { uploadProgress }), ...(typeof uploadProgress === 'number' && { uploadProgress }),
...(isFocused && { focusDirection, noFocusHighlight, isResizingContainer }), ...(isFocused && { focusDirection, noFocusHighlight, isResizingContainer }),

View File

@ -65,13 +65,11 @@ export interface MessageList {
} }
export interface ActiveEmojiInteraction { export interface ActiveEmojiInteraction {
id: number;
x: number; x: number;
y: number; y: number;
messageId?: number; messageId?: number;
endX?: number;
endY?: number;
startSize?: number; startSize?: number;
reaction?: string;
animatedEffect?: string; animatedEffect?: string;
isReversed?: boolean; isReversed?: boolean;
} }
@ -332,7 +330,7 @@ export type GlobalState = {
}; };
availableReactions?: ApiAvailableReaction[]; availableReactions?: ApiAvailableReaction[];
activeEmojiInteraction?: ActiveEmojiInteraction; activeEmojiInteractions?: ActiveEmojiInteraction[];
activeReactions: Record<number, ActiveReaction>; activeReactions: Record<number, ActiveReaction>;
localTextSearch: { localTextSearch: {

View File

@ -15,6 +15,10 @@ import { buildCollectionByKey, omit } from '../../../util/iteratees';
import { ANIMATION_LEVEL_MAX } from '../../../config'; import { ANIMATION_LEVEL_MAX } from '../../../config';
import { isMessageLocal } from '../../helpers'; import { isMessageLocal } from '../../helpers';
const INTERACTION_RANDOM_OFFSET = 40;
let interactionLocalId = 0;
addReducer('loadAvailableReactions', () => { addReducer('loadAvailableReactions', () => {
(async () => { (async () => {
const result = await callApi('getAvailableReactions'); const result = await callApi('getAvailableReactions');
@ -45,28 +49,31 @@ addReducer('interactWithAnimatedEmoji', (global, actions, payload) => {
emoji, x, y, localEffect, startSize, isReversed, emoji, x, y, localEffect, startSize, isReversed,
} = payload!; } = payload!;
const activeEmojiInteraction = {
id: interactionLocalId++,
animatedEffect: emoji || localEffect,
x: subtractXForEmojiInteraction(global, x) + Math.random()
* INTERACTION_RANDOM_OFFSET - INTERACTION_RANDOM_OFFSET / 2,
y: y + Math.random() * INTERACTION_RANDOM_OFFSET - INTERACTION_RANDOM_OFFSET / 2,
startSize,
isReversed,
};
return { return {
...global, ...global,
activeEmojiInteraction: { activeEmojiInteractions: [...(global.activeEmojiInteractions || []), activeEmojiInteraction],
animatedEffect: emoji || localEffect,
x: subtractXForEmojiInteraction(global, x),
y,
startSize,
isReversed,
},
}; };
}); });
addReducer('sendEmojiInteraction', (global, actions, payload) => { addReducer('sendEmojiInteraction', (global, actions, payload) => {
const { const {
messageId, chatId, emoji, interactions, localEffect, messageId, chatId, emoji, interactions, localEffect,
x, y, startX, startY, startSize,
} = payload!; } = payload!;
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
if (!chat || (!emoji && !localEffect) || chatId === global.currentUserId) { if (!chat || (!emoji && !localEffect) || chatId === global.currentUserId) {
return undefined; return;
} }
void callApi('sendEmojiInteraction', { void callApi('sendEmojiInteraction', {
@ -75,20 +82,6 @@ addReducer('sendEmojiInteraction', (global, actions, payload) => {
emoticon: emoji || selectLocalAnimatedEmojiEffectByName(localEffect), emoticon: emoji || selectLocalAnimatedEmojiEffectByName(localEffect),
timestamps: interactions, timestamps: interactions,
}); });
if (!global.activeEmojiInteraction) return undefined;
return {
...global,
activeEmojiInteraction: {
...global.activeEmojiInteraction,
endX: subtractXForEmojiInteraction(global, x),
endY: y,
...(startX && { x: subtractXForEmojiInteraction(global, startX) }),
...(startY && { y: startY }),
...(startSize && { startSize }),
},
};
}); });
addReducer('sendDefaultReaction', (global, actions, payload) => { addReducer('sendDefaultReaction', (global, actions, payload) => {
@ -224,10 +217,12 @@ addReducer('setDefaultReaction', (global, actions, payload) => {
})(); })();
}); });
addReducer('stopActiveEmojiInteraction', (global) => { addReducer('stopActiveEmojiInteraction', (global, actions, payload) => {
const { id } = payload;
return { return {
...global, ...global,
activeEmojiInteraction: undefined, activeEmojiInteractions: global.activeEmojiInteractions?.filter((l) => l.id !== id),
}; };
}); });
@ -287,12 +282,12 @@ addReducer('loadMessageReactions', (global, actions, payload) => {
addReducer('sendWatchingEmojiInteraction', (global, actions, payload) => { addReducer('sendWatchingEmojiInteraction', (global, actions, payload) => {
const { const {
chatId, emoticon, x, y, startSize, isReversed, chatId, emoticon, x, y, startSize, isReversed, id,
} = payload; } = payload;
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
if (!chat || !global.activeEmojiInteraction || chatId === global.currentUserId) { if (!chat || !global.activeEmojiInteractions?.some((l) => l.id === id) || chatId === global.currentUserId) {
return undefined; return undefined;
} }
@ -300,12 +295,17 @@ addReducer('sendWatchingEmojiInteraction', (global, actions, payload) => {
return { return {
...global, ...global,
activeEmojiInteraction: { activeEmojiInteractions: global.activeEmojiInteractions.map((activeEmojiInteraction) => {
...global.activeEmojiInteraction, if (activeEmojiInteraction.id === id) {
x: subtractXForEmojiInteraction(global, x), return {
y, ...activeEmojiInteraction,
startSize, x: subtractXForEmojiInteraction(global, x),
isReversed, y,
}, startSize,
isReversed,
};
}
return activeEmojiInteraction;
}),
}; };
}); });

View File

@ -118,7 +118,7 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
case 'updateStartEmojiInteraction': { case 'updateStartEmojiInteraction': {
const { chatId: currentChatId } = selectCurrentMessageList(global) || {}; const { chatId: currentChatId } = selectCurrentMessageList(global) || {};
if (global.activeEmojiInteraction || currentChatId !== update.id) return; if (currentChatId !== update.id) return;
const message = selectChatMessage(global, currentChatId, update.messageId); const message = selectChatMessage(global, currentChatId, update.messageId);
if (!message) return; if (!message) return;
@ -130,10 +130,11 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
global = { global = {
...global, ...global,
activeEmojiInteraction: { activeEmojiInteractions: [...(global.activeEmojiInteractions || []), {
id: global.activeEmojiInteractions?.length || 0,
animatedEffect: localEmoji ? selectLocalAnimatedEmojiEffect(localEmoji) : update.emoji, animatedEffect: localEmoji ? selectLocalAnimatedEmojiEffect(localEmoji) : update.emoji,
messageId: update.messageId, messageId: update.messageId,
} as ActiveEmojiInteraction, } as ActiveEmojiInteraction],
}; };
setGlobal(global); setGlobal(global);