perf(app): fetch text attachment previews only when they scroll near (#1865)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:34:04 +02:00
1 parent 39e2c89d5c
commit d2d199e136
2 files changed
+13 -3

No files matched your search

@@ -48,9 +48,10 @@ interface AttachmentFileProps {
attachment: MessageAttachment; attachment: MessageAttachment;
isPreview?: boolean; isPreview?: boolean;
message?: Message; message?: Message;
spoilerHidden?: boolean;
} }
export const AttachmentFile = observer(({attachment, message, isPreview}: AttachmentFileProps) => { export const AttachmentFile = observer(({attachment, message, isPreview, spoilerHidden}: AttachmentFileProps) => {
const {i18n} = useLingui(); const {i18n} = useLingui();
const {enabled: isMobile} = MobileLayout; const {enabled: isMobile} = MobileLayout;
const isExpired = Boolean(attachment.expired); const isExpired = Boolean(attachment.expired);
@@ -247,6 +248,7 @@ export const AttachmentFile = observer(({attachment, message, isPreview}: Attach
{showTextPreview ? ( {showTextPreview ? (
<TextualAttachmentPreview <TextualAttachmentPreview
attachment={attachment} attachment={attachment}
spoilerHidden={spoilerHidden}
data-flx="channel.embeds.attachments.attachment-file.textual-attachment-preview" data-flx="channel.embeds.attachments.attachment-file.textual-attachment-preview"
/> />
) : ( ) : (
@@ -26,6 +26,7 @@ import {
} from '@app/features/channel/components/embeds/attachments/TextualAttachmentPreviewUtils'; } from '@app/features/channel/components/embeds/attachments/TextualAttachmentPreviewUtils';
import {TextualPreviewContextMenu} from '@app/features/channel/components/embeds/attachments/TextualPreviewContextMenu'; import {TextualPreviewContextMenu} from '@app/features/channel/components/embeds/attachments/TextualPreviewContextMenu';
import {useArboriumHighlightedHtml} from '@app/features/code_highlighting/utils/ArboriumHighlighting'; import {useArboriumHighlightedHtml} from '@app/features/code_highlighting/utils/ArboriumHighlighting';
import {useNearViewport} from '@app/features/messaging/hooks/useNearViewport';
import TextualPreview from '@app/features/messaging/state/TextualPreview'; import TextualPreview from '@app/features/messaging/state/TextualPreview';
import { import {
shouldPreviewAttachment, shouldPreviewAttachment,
@@ -93,9 +94,12 @@ async function readPreviewText(response: Response): Promise<string> {
export const TextualAttachmentPreview = observer(function TextualAttachmentPreview({ export const TextualAttachmentPreview = observer(function TextualAttachmentPreview({
attachment, attachment,
}: TextualAttachmentPreviewProps) { spoilerHidden = false,
}: TextualAttachmentPreviewProps & {spoilerHidden?: boolean}) {
const {i18n} = useLingui(); const {i18n} = useLingui();
const shouldShowPreview = shouldPreviewAttachment(attachment); const shouldShowPreview = shouldPreviewAttachment(attachment);
const {ref: visibilityRef, isNearViewport} = useNearViewport<HTMLDivElement>({rememberKey: attachment.url});
const shouldFetchPreview = shouldShowPreview && isNearViewport && !spoilerHidden;
const isCsvPreview = useMemo( const isCsvPreview = useMemo(
() => isCsvAttachment(attachment), () => isCsvAttachment(attachment),
[attachment.content_type, attachment.filename, attachment.title], [attachment.content_type, attachment.filename, attachment.title],
@@ -123,6 +127,9 @@ export const TextualAttachmentPreview = observer(function TextualAttachmentPrevi
setIsFullscreenOpen(false); setIsFullscreenOpen(false);
}, [attachment.id]); }, [attachment.id]);
useEffect(() => { useEffect(() => {
if (!shouldFetchPreview) {
return;
}
if (!attachment.url) { if (!attachment.url) {
setStatus('error'); setStatus('error');
setPreviewError({type: 'network'}); setPreviewError({type: 'network'});
@@ -164,7 +171,7 @@ export const TextualAttachmentPreview = observer(function TextualAttachmentPrevi
setPreviewError({type: 'network', message: error?.message ?? 'Failed to load preview'}); setPreviewError({type: 'network', message: error?.message ?? 'Failed to load preview'});
}); });
return () => controller.abort(); return () => controller.abort();
}, [attachment.id, attachment.size, attachment.url]); }, [attachment.id, attachment.size, attachment.url, shouldFetchPreview]);
const lineCount = useMemo(() => getLineCount(textContent), [textContent]); const lineCount = useMemo(() => getLineCount(textContent), [textContent]);
const csvRows = useMemo(() => (isCsvPreview ? parseCsvRows(textContent) : null), [isCsvPreview, textContent]); const csvRows = useMemo(() => (isCsvPreview ? parseCsvRows(textContent) : null), [isCsvPreview, textContent]);
const csvRowCount = csvRows?.length ?? 0; const csvRowCount = csvRows?.length ?? 0;
@@ -282,6 +289,7 @@ export const TextualAttachmentPreview = observer(function TextualAttachmentPrevi
return ( return (
<> <>
<div <div
ref={visibilityRef}
className={styles.textualPreview} className={styles.textualPreview}
data-flx="channel.embeds.attachments.textual-attachment-preview.textual-preview" data-flx="channel.embeds.attachments.textual-attachment-preview.textual-preview"
> >