perf(app): render guild stickers only near the modal viewport (#1964)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:54:19 +02:00
1 parent 987768e8dc
commit 022ccc794d
2 files changed
+26 -1

No files matched your search

@@ -16,10 +16,12 @@ import {getAcceptString, getAssetFormatErrorMessage} from '@app/features/express
import {isSvgFile} from '@app/features/expressions/utils/ImageUploadFileUtils'; import {isSvgFile} from '@app/features/expressions/utils/ImageUploadFileUtils';
import {CloneAllowedToggle} from '@app/features/guild/components/modals/guild_tabs/CloneAllowedToggle'; import {CloneAllowedToggle} from '@app/features/guild/components/modals/guild_tabs/CloneAllowedToggle';
import styles from '@app/features/guild/components/modals/guild_tabs/GuildStickersTab.module.css'; import styles from '@app/features/guild/components/modals/guild_tabs/GuildStickersTab.module.css';
import {resolveEnclosingScrollSurface} from '@app/features/guild/components/modals/guild_tabs/GuildStickersTabScrollSurface';
import {UploadDropZone} from '@app/features/guild/components/UploadDropZone'; import {UploadDropZone} from '@app/features/guild/components/UploadDropZone';
import {UploadSlotInfo} from '@app/features/guild/components/UploadSlotInfo'; import {UploadSlotInfo} from '@app/features/guild/components/UploadSlotInfo';
import Guilds from '@app/features/guild/state/Guilds'; import Guilds from '@app/features/guild/state/Guilds';
import {OKAY_DESCRIPTOR, TRY_AGAIN_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors'; import {OKAY_DESCRIPTOR, TRY_AGAIN_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {NearViewportSurfaceContext} from '@app/features/messaging/hooks/useNearViewport';
import {openFilePicker} from '@app/features/messaging/utils/FilePickerUtils'; import {openFilePicker} from '@app/features/messaging/utils/FilePickerUtils';
import {formatFileSize} from '@app/features/messaging/utils/FileUtils'; import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
import Permission from '@app/features/permissions/state/Permission'; import Permission from '@app/features/permissions/state/Permission';
@@ -42,7 +44,7 @@ import {clsx} from 'clsx';
import {matchSorter} from 'match-sorter'; import {matchSorter} from 'match-sorter';
import {observer} from 'mobx-react-lite'; import {observer} from 'mobx-react-lite';
import type React from 'react'; import type React from 'react';
import {useCallback, useEffect, useMemo, useState} from 'react'; import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
const SEARCH_STICKERS_DESCRIPTOR = msg({ const SEARCH_STICKERS_DESCRIPTOR = msg({
message: 'Search stickers', message: 'Search stickers',
@@ -92,6 +94,8 @@ const GuildStickersTab: React.FC<{guildId: string}> = observer(function GuildSti
const [stickers, setStickers] = useState<ReadonlyArray<GuildStickerWithUser>>([]); const [stickers, setStickers] = useState<ReadonlyArray<GuildStickerWithUser>>([]);
const [fetchStatus, setFetchStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle'); const [fetchStatus, setFetchStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle');
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const gridRef = useRef<HTMLDivElement | null>(null);
const resolveGridScrollSurface = useCallback(() => resolveEnclosingScrollSurface(gridRef.current), []);
const layoutState = EmojiStickerLayout; const layoutState = EmojiStickerLayout;
const viewMode = layoutState.getStickerViewMode(); const viewMode = layoutState.getStickerViewMode();
const guild = Guilds.getGuild(guildId); const guild = Guilds.getGuild(guildId);
@@ -309,7 +313,9 @@ const GuildStickersTab: React.FC<{guildId: string}> = observer(function GuildSti
/> />
)} )}
{fetchStatus === 'success' && filteredStickers.length > 0 && ( {fetchStatus === 'success' && filteredStickers.length > 0 && (
<NearViewportSurfaceContext.Provider value={resolveGridScrollSurface}>
<div <div
ref={gridRef}
className={clsx(styles.stickerGrid, viewMode === 'compact' ? styles.compactGrid : styles.cozyGrid)} className={clsx(styles.stickerGrid, viewMode === 'compact' ? styles.compactGrid : styles.cozyGrid)}
data-flx="guild.guild-tabs.guild-stickers-tab.sticker-grid" data-flx="guild.guild-tabs.guild-stickers-tab.sticker-grid"
> >
@@ -324,6 +330,7 @@ const GuildStickersTab: React.FC<{guildId: string}> = observer(function GuildSti
/> />
))} ))}
</div> </div>
</NearViewportSurfaceContext.Provider>
)} )}
{fetchStatus === 'error' && ( {fetchStatus === 'error' && (
<StatusSlate <StatusSlate
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
const SCROLLABLE_OVERFLOW_VALUES = new Set(['auto', 'scroll', 'overlay']);
export function resolveEnclosingScrollSurface(node: Element | null): Element | null {
let candidate = node?.parentElement ?? null;
while (candidate) {
const computedStyle = window.getComputedStyle(candidate);
if (
SCROLLABLE_OVERFLOW_VALUES.has(computedStyle.overflowY) ||
SCROLLABLE_OVERFLOW_VALUES.has(computedStyle.overflow)
) {
return candidate;
}
candidate = candidate.parentElement;
}
return null;
}