perf(app): preload reaction menu emoji at the size they render (#1882)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:37:38 +02:00
1 parent 76ce060d13
commit 19264d7f81
2 files changed
+40 -9

No files matched your search

@@ -4,15 +4,14 @@ import styles from '@app/features/channel/components/MessageActionBottomSheet.mo
import {useMessageActionMenuData} from '@app/features/channel/components/MessageActionMenu';
import {MessageReactionsSheet} from '@app/features/channel/components/MessageReactionsSheet';
import {
getQuickReactionEmojiSrc,
REACT_WITH_EMOJI_DESCRIPTOR,
renderQuickReactionEmoji,
useReactionMenuImagePreload,
} from '@app/features/channel/components/QuickReactionsRow';
import quickReactionStyles from '@app/features/channel/components/QuickReactionsRow.module.css';
import type {Channel} from '@app/features/channel/models/Channel';
import EmojiPicker from '@app/features/emoji/state/EmojiPicker';
import {ExpressionPickerSheet} from '@app/features/expressions/components/modals/ExpressionPickerSheet';
import {useExpressionImagesPreload} from '@app/features/expressions/utils/ExpressionImageCache';
import {COPY_LINK_DESCRIPTOR, OPEN_LINK_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {useMessageReactions as useMessageReactionsSnapshot} from '@app/features/messaging/hooks/useMessageReactionStore';
import type {Message} from '@app/features/messaging/models/MessagingMessage';
@@ -109,11 +108,7 @@ export const MessageActionBottomSheet: React.FC<MessageActionBottomSheetProps> =
},
];
}, [handleCopyLink, handleOpenLink, i18n.locale, linkUrl]);
const quickReactionImageUrls = useMemo(
() => quickReactionEmojis.map((emoji) => getQuickReactionEmojiSrc(emoji)),
[quickReactionEmojis],
);
useExpressionImagesPreload(quickReactionImageUrls);
useReactionMenuImagePreload(quickReactionEmojis);
const visibleGroups = useMemo(
() => [...linkGroups, ...groups].filter((group) => group.items.length > 0),
[groups, linkGroups],
@@ -1,12 +1,15 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {useShouldAnimate} from '@app/features/app/hooks/useShouldAnimate';
import styles from '@app/features/channel/components/QuickReactionsRow.module.css';
import type {FlatEmoji} from '@app/features/emoji/types/EmojiTypes';
import {buildCustomEmojiURL} from '@app/features/expressions/utils/CustomEmojiImageUrl';
import {useExpressionImagesPreload} from '@app/features/expressions/utils/ExpressionImageCache';
import {getEmojiDisplayData} from '@app/features/expressions/utils/SkinToneUtils';
import {ReactionImage} from '@app/features/messaging/components/ReactionImage';
import * as AvatarUtils from '@app/features/user/utils/AvatarUtils';
import {msg} from '@lingui/core/macro';
import type React from 'react';
import {useMemo} from 'react';
export const REACT_WITH_EMOJI_DESCRIPTOR = msg({
message: 'React with {emojiShortcode}',
@@ -16,7 +19,7 @@ export const REACT_WITH_EMOJI_DESCRIPTOR = msg({
export function getQuickReactionEmojiSrc(emoji: FlatEmoji): string {
const {url: displayUrl} = getEmojiDisplayData(emoji);
return emoji.id ? AvatarUtils.getEmojiURL({id: emoji.id, animated: false}) : (displayUrl ?? '');
return emoji.id ? buildCustomEmojiURL({id: emoji.id, animated: emoji.animated === true}) : (displayUrl ?? '');
}
export function renderQuickReactionEmoji(emoji: FlatEmoji): React.ReactNode {
@@ -25,9 +28,42 @@ export function renderQuickReactionEmoji(emoji: FlatEmoji): React.ReactNode {
<ReactionImage
src={emojiSrc}
alt={emoji.name}
aria-hidden={true}
draggable={false}
className={styles.emojiImg}
data-flx="channel.quick-reactions-row.render-quick-reaction-emoji.emoji-img"
/>
);
}
export function getReactionSubmenuEmojiSrc(emoji: FlatEmoji, animationAllowed: boolean): string {
const {url: displayUrl} = getEmojiDisplayData(emoji);
return emoji.id
? buildCustomEmojiURL({id: emoji.id, animated: emoji.animated === true && animationAllowed})
: (displayUrl ?? '');
}
export function useReactionSubmenuEmojiSrc(emoji: FlatEmoji): string {
const animationAllowed = useShouldAnimate({kind: 'emoji', isAnimated: emoji.animated === true});
return getReactionSubmenuEmojiSrc(emoji, animationAllowed);
}
const NO_SUBMENU_EMOJIS: ReadonlyArray<FlatEmoji> = [];
export function useReactionMenuImagePreload(
quickReactionEmojis: ReadonlyArray<FlatEmoji>,
submenuReactionEmojis: ReadonlyArray<FlatEmoji> = NO_SUBMENU_EMOJIS,
): void {
const animationAllowed = useShouldAnimate({kind: 'emoji', isAnimated: true});
const urls = useMemo(
() =>
Array.from(
new Set([
...quickReactionEmojis.map(getQuickReactionEmojiSrc),
...submenuReactionEmojis.map((emoji) => getReactionSubmenuEmojiSrc(emoji, animationAllowed)),
]),
).filter(Boolean),
[quickReactionEmojis, submenuReactionEmojis, animationAllowed],
);
useExpressionImagesPreload(urls);
}