From 19264d7f81abf41a9a78b4d31bf9ed973c71e289 Mon Sep 17 00:00:00 2001 From: Hampus Date: Mon, 24 Aug 2026 12:37:38 +0200 Subject: [PATCH] perf(app): preload reaction menu emoji at the size they render (#1882) --- .../components/MessageActionBottomSheet.tsx | 9 +---- .../channel/components/QuickReactionsRow.tsx | 40 ++++++++++++++++++- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/fluxer_app/src/features/channel/components/MessageActionBottomSheet.tsx b/fluxer_app/src/features/channel/components/MessageActionBottomSheet.tsx index fb485b2f2..780c67226 100644 --- a/fluxer_app/src/features/channel/components/MessageActionBottomSheet.tsx +++ b/fluxer_app/src/features/channel/components/MessageActionBottomSheet.tsx @@ -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 = }, ]; }, [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], diff --git a/fluxer_app/src/features/channel/components/QuickReactionsRow.tsx b/fluxer_app/src/features/channel/components/QuickReactionsRow.tsx index 3d94ea0ae..cd89cac0d 100644 --- a/fluxer_app/src/features/channel/components/QuickReactionsRow.tsx +++ b/fluxer_app/src/features/channel/components/QuickReactionsRow.tsx @@ -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 { ); } + +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 = []; + +export function useReactionMenuImagePreload( + quickReactionEmojis: ReadonlyArray, + submenuReactionEmojis: ReadonlyArray = 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); +}