perf(app): render member list rows in scroll chunks (#1895)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:41:14 +02:00
1 parent b81bfc80fd
commit c54d7bcc88
5 files changed
+146 -228

No files matched your search

@@ -1,13 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import sharedStyles from '@app/features/app/components/shared/custom_status_display/CustomStatusDisplay.module.css';
import {buildCustomEmojiURL} from '@app/features/expressions/utils/CustomEmojiImageUrl';
import {getEmojiURL as getUnicodeEmojiURL} from '@app/features/expressions/utils/EmojiUtils';
import {usePresenceCustomStatus} from '@app/features/presence/hooks/usePresenceCustomStatus';
import {useTextOverflow} from '@app/features/ui/hooks/useTextOverflow';
import {Tooltip} from '@app/features/ui/tooltip/Tooltip';
import type {CustomStatus} from '@app/features/user/state/CustomStatus';
import {getCustomStatusText, isCustomStatusExpired, normalizeCustomStatus} from '@app/features/user/state/CustomStatus';
import * as AvatarUtils from '@app/features/user/utils/AvatarUtils';
import clsx from 'clsx';
import {type ReactNode, useEffect, useMemo, useRef, useState} from 'react';
@@ -16,7 +16,6 @@ interface CompactMemberCustomStatusProps {
customStatus?: CustomStatus | null;
userId?: string;
showText?: boolean;
deferMediaLoad?: boolean;
}
function sanitizeText(text: string): string {
@@ -45,33 +44,14 @@ export function hasVisibleCompactMemberCustomStatus(status: CustomStatus | null
return sanitizeText(normalized.text ?? '').length > 0;
}
const loadedStatusMediaUrls = new Set<string>();
function markStatusMediaLoaded(url: string): void {
loadedStatusMediaUrls.add(url);
}
function renderEmoji(status: CustomStatus, deferMediaLoad: boolean): ReactNode {
function renderEmoji(status: CustomStatus): ReactNode {
if (status.emojiId) {
const emojiUrl = AvatarUtils.getEmojiURL({id: status.emojiId, animated: false});
if (deferMediaLoad && !loadedStatusMediaUrls.has(emojiUrl)) {
return (
<span
className={sharedStyles.statusEmojiWrapper}
aria-hidden={true}
data-flx="channel.compact-member-custom-status.emoji-placeholder"
/>
);
}
return (
<img
src={emojiUrl}
src={buildCustomEmojiURL({id: status.emojiId, animated: false})}
alt={status.emojiName ?? undefined}
draggable={false}
className={sharedStyles.statusEmoji}
loading="lazy"
decoding="async"
onLoad={() => markStatusMediaLoaded(emojiUrl)}
data-flx="channel.compact-member-custom-status.emoji"
/>
);
@@ -83,24 +63,12 @@ function renderEmoji(status: CustomStatus, deferMediaLoad: boolean): ReactNode {
if (!twemojiUrl) {
return null;
}
if (deferMediaLoad && !loadedStatusMediaUrls.has(twemojiUrl)) {
return (
<span
className={sharedStyles.statusEmojiWrapper}
aria-hidden={true}
data-flx="channel.compact-member-custom-status.unicode-emoji-placeholder"
/>
);
}
return (
<img
src={twemojiUrl}
alt={status.emojiName}
draggable={false}
className={sharedStyles.statusEmoji}
loading="lazy"
decoding="async"
onLoad={() => markStatusMediaLoaded(twemojiUrl)}
data-flx="channel.compact-member-custom-status.unicode-emoji"
/>
);
@@ -111,7 +79,6 @@ export function CompactMemberCustomStatus({
customStatus,
userId,
showText = true,
deferMediaLoad = false,
}: CompactMemberCustomStatusProps) {
const containerRef = useRef<HTMLDivElement>(null);
const shouldFetchFromPresence = customStatus === undefined && userId !== undefined;
@@ -143,7 +110,7 @@ export function CompactMemberCustomStatus({
return null;
}
const text = normalized.text ? sanitizeText(normalized.text) : null;
const emoji = renderEmoji(normalized, deferMediaLoad);
const emoji = renderEmoji(normalized);
if (!emoji && (!showText || !text)) {
return null;
}