Emoji loading performance (#38784)

This commit is contained in:
Echo 2026-04-23 09:48:00 +02:00 committed by GitHub
parent fdb2563abf
commit 5bc69ea668
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 67 additions and 23 deletions

View File

@ -17,6 +17,7 @@ import { ToggleField } from '@/mastodon/components/form_fields';
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import { useCustomEmojis } from '@/mastodon/hooks/useCustomEmojis';
import { autoPlayGif } from '@/mastodon/initial_state';
import {
fetchProfile,
@ -175,7 +176,7 @@ export const AccountEdit: FC = () => {
}, [dispatch, profile?.bot]);
// Normally we would use the account emoji, but we want all custom emojis to be available to render after editing.
const emojis = useAppSelector((state) => state.custom_emojis);
const emojis = useCustomEmojis();
const htmlHandlers = useElementHandledLink({
hashtagAccountId: profile?.id,
});

View File

@ -1,4 +1,10 @@
import { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
import {
forwardRef,
useCallback,
useImperativeHandle,
useMemo,
useState,
} from 'react';
import type { FC, FocusEventHandler } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@ -9,6 +15,7 @@ import { closeModal } from '@/mastodon/actions/modal';
import { Button } from '@/mastodon/components/button';
import type { FieldStatus } from '@/mastodon/components/form_fields';
import { EmojiTextInputField } from '@/mastodon/components/form_fields';
import { useCustomEmojis } from '@/mastodon/hooks/useCustomEmojis';
import {
removeField,
selectFieldById,
@ -104,11 +111,6 @@ const selectFieldLimits = createAppSelector(
const RECOMMENDED_LIMIT = 40;
const selectEmojiCodes = createAppSelector(
[(state) => state.custom_emojis],
(emojis) => emojis.map((emoji) => emoji.get('shortcode')).toArray(),
);
interface ConfirmationMessage {
message: string;
confirm: string;
@ -143,7 +145,11 @@ export const EditFieldModal = forwardRef<
value?: FieldStatus;
}>({});
const customEmojiCodes = useAppSelector(selectEmojiCodes);
const customEmojis = useCustomEmojis();
const customEmojiCodes = useMemo(
() => Object.keys(customEmojis ?? {}),
[customEmojis],
);
const checkField = useCallback(
(value: string): FieldStatus | null => {
if (!value.trim()) {

View File

@ -35,6 +35,7 @@ import { CSS } from '@dnd-kit/utilities';
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
import { normalizeKey } from '@/mastodon/components/hotkeys/utils';
import { Icon } from '@/mastodon/components/icon';
import { useCustomEmojis } from '@/mastodon/hooks/useCustomEmojis';
import type { FieldData } from '@/mastodon/reducers/slices/profile_edit';
import {
patchProfile,
@ -217,7 +218,7 @@ export const ReorderFieldsModal: FC<DialogModalProps> = ({ onClose }) => {
void dispatch(patchProfile({ fields_attributes: newFields })).then(onClose);
}, [dispatch, fieldKeys, fields, onClose]);
const emojis = useAppSelector((state) => state.custom_emojis);
const emojis = useCustomEmojis();
return (
// Add a wrapper here in the capture phase, so that it can be intercepted before the window listener in ModalRoot.

View File

@ -267,6 +267,11 @@ export async function searchCustomEmojisByShortcodes(shortcodes: string[]) {
return results.filter((emoji) => shortcodes.includes(emoji.shortcode));
}
export async function loadAllCustomEmoji() {
const db = await loadDB();
return db.getAll('custom');
}
export async function loadLegacyShortcodesByShortcode(shortcode: string) {
const db = await loadDB();
return db.getFromIndex(

View File

@ -185,21 +185,16 @@ export function cleanExtraEmojis(extraEmojis?: CustomEmojiMapArg | null) {
if (!extraEmojis) {
return null;
}
if (Array.isArray(extraEmojis)) {
return extraEmojis.reduce<ExtraCustomEmojiMap>(
(acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }),
{},
);
if (!Array.isArray(extraEmojis) && !isList(extraEmojis)) {
return extraEmojis;
}
if (isList(extraEmojis)) {
return extraEmojis
.toJS()
.reduce<ExtraCustomEmojiMap>(
(acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }),
{},
);
const emojis: ExtraCustomEmojiMap = {};
const emojiArray = isList(extraEmojis) ? extraEmojis.toJS() : extraEmojis;
for (const emoji of emojiArray) {
emojis[emoji.shortcode] = emoji;
}
return extraEmojis;
return emojis;
}
/**

View File

@ -7,6 +7,7 @@ import elephantUIPlane from '@/images/elephant_ui_plane.svg';
import type { RenderSlideFn } from '@/mastodon/components/carousel';
import { Carousel } from '@/mastodon/components/carousel';
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
import { useCustomEmojis } from '@/mastodon/hooks/useCustomEmojis';
import { mascot } from '@/mastodon/initial_state';
import { createAppSelector, useAppSelector } from '@/mastodon/store';
@ -23,7 +24,7 @@ const announcementSelector = createAppSelector(
export const Announcements: FC = () => {
const announcements = useAppSelector(announcementSelector);
const emojis = useAppSelector((state) => state.custom_emojis);
const emojis = useCustomEmojis();
const renderSlide: RenderSlideFn<{
id: string;

View File

@ -0,0 +1,35 @@
import { useEffect, useState } from 'react';
import type { ExtraCustomEmojiMap } from '../features/emoji/types';
let emojis: ExtraCustomEmojiMap | null = null;
export function useCustomEmojis() {
const [, setLoaded] = useState(emojis !== null);
useEffect(() => {
if (!emojis) {
void loadEmojisIntoCache().then(() => {
setLoaded(true);
});
}
}, []);
return emojis;
}
async function loadEmojisIntoCache() {
const { loadAllCustomEmoji } = await import('../features/emoji/database');
const emojisRaw = await loadAllCustomEmoji();
if (emojisRaw.length === 0) {
return;
}
emojis = {};
for (const emoji of emojisRaw) {
emojis[emoji.shortcode] = {
url: emoji.url,
shortcode: emoji.shortcode,
static_url: emoji.static_url,
};
}
}