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

This commit is contained in:
Hampus
2026-08-24 12:54:19 +02:00
committed by GitHub
parent 987768e8dc
commit 022ccc794d
2 changed files with 41 additions and 16 deletions
@@ -16,10 +16,12 @@ import {getAcceptString, getAssetFormatErrorMessage} from '@app/features/express
import {isSvgFile} from '@app/features/expressions/utils/ImageUploadFileUtils';
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 {resolveEnclosingScrollSurface} from '@app/features/guild/components/modals/guild_tabs/GuildStickersTabScrollSurface';
import {UploadDropZone} from '@app/features/guild/components/UploadDropZone';
import {UploadSlotInfo} from '@app/features/guild/components/UploadSlotInfo';
import Guilds from '@app/features/guild/state/Guilds';
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 {formatFileSize} from '@app/features/messaging/utils/FileUtils';
import Permission from '@app/features/permissions/state/Permission';
@@ -42,7 +44,7 @@ import {clsx} from 'clsx';
import {matchSorter} from 'match-sorter';
import {observer} from 'mobx-react-lite';
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({
message: 'Search stickers',
@@ -92,6 +94,8 @@ const GuildStickersTab: React.FC<{guildId: string}> = observer(function GuildSti
const [stickers, setStickers] = useState<ReadonlyArray<GuildStickerWithUser>>([]);
const [fetchStatus, setFetchStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle');
const [searchQuery, setSearchQuery] = useState('');
const gridRef = useRef<HTMLDivElement | null>(null);
const resolveGridScrollSurface = useCallback(() => resolveEnclosingScrollSurface(gridRef.current), []);
const layoutState = EmojiStickerLayout;
const viewMode = layoutState.getStickerViewMode();
const guild = Guilds.getGuild(guildId);
@@ -309,21 +313,24 @@ const GuildStickersTab: React.FC<{guildId: string}> = observer(function GuildSti
/>
)}
{fetchStatus === 'success' && filteredStickers.length > 0 && (
<div
className={clsx(styles.stickerGrid, viewMode === 'compact' ? styles.compactGrid : styles.cozyGrid)}
data-flx="guild.guild-tabs.guild-stickers-tab.sticker-grid"
>
{filteredStickers.map((sticker) => (
<StickerGridItem
key={sticker.id}
guildId={guildId}
sticker={sticker}
canModify={canModifySticker(sticker)}
onUpdate={fetchStickers}
data-flx="guild.guild-tabs.guild-stickers-tab.sticker-grid-item"
/>
))}
</div>
<NearViewportSurfaceContext.Provider value={resolveGridScrollSurface}>
<div
ref={gridRef}
className={clsx(styles.stickerGrid, viewMode === 'compact' ? styles.compactGrid : styles.cozyGrid)}
data-flx="guild.guild-tabs.guild-stickers-tab.sticker-grid"
>
{filteredStickers.map((sticker) => (
<StickerGridItem
key={sticker.id}
guildId={guildId}
sticker={sticker}
canModify={canModifySticker(sticker)}
onUpdate={fetchStickers}
data-flx="guild.guild-tabs.guild-stickers-tab.sticker-grid-item"
/>
))}
</div>
</NearViewportSurfaceContext.Provider>
)}
{fetchStatus === 'error' && (
<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;
}