perf(app): load sheet and popout media against their own scrollers (#1900)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:42:09 +02:00
1 parent 94bbbd1021
commit bb34c73069
3 files changed
+21 -4

No files matched your search

@@ -13,6 +13,7 @@ import {isKeyboardActivationKey} from '@app/features/input/utils/KeyboardUtils';
import {ensureMembersForMessages} from '@app/features/messaging/commands/MessageCommands'; import {ensureMembersForMessages} from '@app/features/messaging/commands/MessageCommands';
import {useMessageListKeyboardNavigation} from '@app/features/messaging/hooks/useMessageListKeyboardNavigation'; import {useMessageListKeyboardNavigation} from '@app/features/messaging/hooks/useMessageListKeyboardNavigation';
import {useMessageSelectionCopyForMessages} from '@app/features/messaging/hooks/useMessageSelectionCopy'; import {useMessageSelectionCopyForMessages} from '@app/features/messaging/hooks/useMessageSelectionCopy';
import {NearViewportSurfaceContext} from '@app/features/messaging/hooks/useNearViewport';
import type {Message} from '@app/features/messaging/models/MessagingMessage'; import type {Message} from '@app/features/messaging/models/MessagingMessage';
import {focusChannelTextareaAfterNavigation} from '@app/features/messaging/utils/ChannelTextareaFocusUtils'; import {focusChannelTextareaAfterNavigation} from '@app/features/messaging/utils/ChannelTextareaFocusUtils';
import {goToMessage} from '@app/features/messaging/utils/MessageNavigator'; import {goToMessage} from '@app/features/messaging/utils/MessageNavigator';
@@ -79,6 +80,7 @@ export const ChannelPinsContent = observer(({channel, onJump}: ChannelPinsConten
const canUnpin = isDMChannel || Permission.can(Permissions.MANAGE_MESSAGES, channel); const canUnpin = isDMChannel || Permission.can(Permissions.MANAGE_MESSAGES, channel);
const mobileLayout = MobileLayout; const mobileLayout = MobileLayout;
const scrollerRef = useRef<ScrollerHandle | null>(null); const scrollerRef = useRef<ScrollerHandle | null>(null);
const resolvePinnedScrollSurface = useMemo(() => () => scrollerRef.current?.getViewportElement() ?? null, []);
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const [selectedMessage, setSelectedMessage] = useState<Message | null>(null); const [selectedMessage, setSelectedMessage] = useState<Message | null>(null);
const pinMessagesPermissionLabel = formatPermissionLabel(i18n, Permissions.PIN_MESSAGES); const pinMessagesPermissionLabel = formatPermissionLabel(i18n, Permissions.PIN_MESSAGES);
@@ -187,6 +189,7 @@ export const ChannelPinsContent = observer(({channel, onJump}: ChannelPinsConten
} }
return ( return (
<> <>
<NearViewportSurfaceContext.Provider value={resolvePinnedScrollSurface}>
<Scroller <Scroller
className={clsx(previewStyles.scroller, mobileLayout.enabled && previewStyles.scrollerMobile)} className={clsx(previewStyles.scroller, mobileLayout.enabled && previewStyles.scrollerMobile)}
key="channel-pins-scroller" key="channel-pins-scroller"
@@ -196,9 +199,14 @@ export const ChannelPinsContent = observer(({channel, onJump}: ChannelPinsConten
data-message-selection-root="true" data-message-selection-root="true"
data-flx="app.channel-pins-content.scroller" data-flx="app.channel-pins-content.scroller"
> >
{mobileLayout.enabled && <div className={previewStyles.topSpacer} data-flx="app.channel-pins-content.div--5" />} {mobileLayout.enabled && (
<div className={previewStyles.topSpacer} data-flx="app.channel-pins-content.div--5" />
)}
{pinnedPins.slice().map(({message}) => { {pinnedPins.slice().map(({message}) => {
const cardClasses = clsx(previewStyles.previewCard, mobileLayout.enabled && previewStyles.previewCardMobile); const cardClasses = clsx(
previewStyles.previewCard,
mobileLayout.enabled && previewStyles.previewCardMobile,
);
const messageChannel = const messageChannel =
Channels.getChannel(message.channelId) ?? (message.channelId === channel.id ? channel : null); Channels.getChannel(message.channelId) ?? (message.channelId === channel.id ? channel : null);
if (!messageChannel) return null; if (!messageChannel) return null;
@@ -288,6 +296,7 @@ export const ChannelPinsContent = observer(({channel, onJump}: ChannelPinsConten
</div> </div>
)} )}
</Scroller> </Scroller>
</NearViewportSurfaceContext.Provider>
{mobileLayout.enabled && selectedMessage && ( {mobileLayout.enabled && selectedMessage && (
<MenuBottomSheet <MenuBottomSheet
isOpen={menuOpen} isOpen={menuOpen}
@@ -27,6 +27,7 @@ import {isKeyboardActivationKey} from '@app/features/input/utils/KeyboardUtils';
import * as MessageCommands from '@app/features/messaging/commands/MessageCommands'; import * as MessageCommands from '@app/features/messaging/commands/MessageCommands';
import {useMessageListKeyboardNavigation} from '@app/features/messaging/hooks/useMessageListKeyboardNavigation'; import {useMessageListKeyboardNavigation} from '@app/features/messaging/hooks/useMessageListKeyboardNavigation';
import {useMessageSelectionCopyForMessages} from '@app/features/messaging/hooks/useMessageSelectionCopy'; import {useMessageSelectionCopyForMessages} from '@app/features/messaging/hooks/useMessageSelectionCopy';
import {NearViewportSurfaceContext} from '@app/features/messaging/hooks/useNearViewport';
import type {Message} from '@app/features/messaging/models/MessagingMessage'; import type {Message} from '@app/features/messaging/models/MessagingMessage';
import { import {
applyChannelSearchHighlight, applyChannelSearchHighlight,
@@ -67,7 +68,7 @@ import {MessagePreviewContext} from '@fluxer/constants/src/ChannelConstants';
import {Trans, useLingui} from '@lingui/react/macro'; import {Trans, useLingui} from '@lingui/react/macro';
import {HashIcon} from '@phosphor-icons/react'; import {HashIcon} from '@phosphor-icons/react';
import {observer} from 'mobx-react-lite'; import {observer} from 'mobx-react-lite';
import React, {useCallback, useEffect, useRef, useState} from 'react'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
const NEWEST_DESCRIPTOR = msg({ const NEWEST_DESCRIPTOR = msg({
message: 'Newest', message: 'Newest',
@@ -189,6 +190,7 @@ export const ChannelSearchBottomSheet: React.FC<ChannelSearchBottomSheetProps> =
const [selectedMessage, setSelectedMessage] = useState<Message | null>(null); const [selectedMessage, setSelectedMessage] = useState<Message | null>(null);
const [selectedMessageChannel, setSelectedMessageChannel] = useState<Channel | null>(null); const [selectedMessageChannel, setSelectedMessageChannel] = useState<Channel | null>(null);
const scrollerRef = useRef<ScrollerHandle | null>(null); const scrollerRef = useRef<ScrollerHandle | null>(null);
const resolveSearchSheetScrollSurface = useMemo(() => () => scrollerRef.current?.getViewportElement() ?? null, []);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [hasFilters, setHasFilters] = useState<Array<HasFilterType>>([]); const [hasFilters, setHasFilters] = useState<Array<HasFilterType>>([]);
const [fromUserIds, setFromUserIds] = useState<Array<string>>([]); const [fromUserIds, setFromUserIds] = useState<Array<string>>([]);
@@ -605,6 +607,7 @@ export const ChannelSearchBottomSheet: React.FC<ChannelSearchBottomSheetProps> =
value={collapsedMessageVisibility} value={collapsedMessageVisibility}
data-flx="channel.channel-search-bottom-sheet.render-content.collapsed-message-visibility-provider" data-flx="channel.channel-search-bottom-sheet.render-content.collapsed-message-visibility-provider"
> >
<NearViewportSurfaceContext.Provider value={resolveSearchSheetScrollSurface}>
<Scroller <Scroller
ref={scrollerRef} ref={scrollerRef}
className={styles.resultsScroller} className={styles.resultsScroller}
@@ -688,6 +691,7 @@ export const ChannelSearchBottomSheet: React.FC<ChannelSearchBottomSheetProps> =
); );
})} })}
</Scroller> </Scroller>
</NearViewportSurfaceContext.Provider>
</CollapsedMessageVisibilityProvider> </CollapsedMessageVisibilityProvider>
{totalPages > 1 && ( {totalPages > 1 && (
<div <div
@@ -10,6 +10,7 @@ import {ensureMembersForMessages} from '@app/features/messaging/commands/Message
import * as SavedMessageCommands from '@app/features/messaging/commands/SavedMessageCommands'; import * as SavedMessageCommands from '@app/features/messaging/commands/SavedMessageCommands';
import {useMessageListKeyboardNavigation} from '@app/features/messaging/hooks/useMessageListKeyboardNavigation'; import {useMessageListKeyboardNavigation} from '@app/features/messaging/hooks/useMessageListKeyboardNavigation';
import {useMessageSelectionCopyForMessages} from '@app/features/messaging/hooks/useMessageSelectionCopy'; import {useMessageSelectionCopyForMessages} from '@app/features/messaging/hooks/useMessageSelectionCopy';
import {NearViewportSurfaceContext} from '@app/features/messaging/hooks/useNearViewport';
import type {Message} from '@app/features/messaging/models/MessagingMessage'; import type {Message} from '@app/features/messaging/models/MessagingMessage';
import SavedMessages from '@app/features/messaging/state/SavedMessages'; import SavedMessages from '@app/features/messaging/state/SavedMessages';
import {focusChannelTextareaAfterNavigation} from '@app/features/messaging/utils/ChannelTextareaFocusUtils'; import {focusChannelTextareaAfterNavigation} from '@app/features/messaging/utils/ChannelTextareaFocusUtils';
@@ -23,7 +24,7 @@ import {msg} from '@lingui/core/macro';
import {Trans, useLingui} from '@lingui/react/macro'; import {Trans, useLingui} from '@lingui/react/macro';
import {ArrowSquareOutIcon, TrashIcon} from '@phosphor-icons/react'; import {ArrowSquareOutIcon, TrashIcon} from '@phosphor-icons/react';
import {observer} from 'mobx-react-lite'; import {observer} from 'mobx-react-lite';
import {useEffect, useRef, useState} from 'react'; import {useEffect, useMemo, useRef, useState} from 'react';
const JUMP_TO_MESSAGE_DESCRIPTOR = msg({ const JUMP_TO_MESSAGE_DESCRIPTOR = msg({
message: 'Jump to message', message: 'Jump to message',
@@ -44,6 +45,7 @@ export const BookmarksBottomSheet = observer(({isOpen, onClose}: BookmarksBottom
const {savedMessages, missingSavedMessages, fetched} = SavedMessages; const {savedMessages, missingSavedMessages, fetched} = SavedMessages;
const hasBookmarks = savedMessages.length > 0 || missingSavedMessages.length > 0; const hasBookmarks = savedMessages.length > 0 || missingSavedMessages.length > 0;
const scrollerRef = useRef<ScrollerHandle | null>(null); const scrollerRef = useRef<ScrollerHandle | null>(null);
const resolveBookmarksScrollSurface = useMemo(() => () => scrollerRef.current?.getViewportElement() ?? null, []);
const [selectedMessage, setSelectedMessage] = useState<Message | null>(null); const [selectedMessage, setSelectedMessage] = useState<Message | null>(null);
const [menuOpen, setMenuOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const onCopySelectedMessages = useMessageSelectionCopyForMessages<HTMLDivElement>(savedMessages); const onCopySelectedMessages = useMessageSelectionCopyForMessages<HTMLDivElement>(savedMessages);
@@ -122,6 +124,7 @@ export const BookmarksBottomSheet = observer(({isOpen, onClose}: BookmarksBottom
data-flx="channel.bookmarks-bottom-sheet.bottom-sheet" data-flx="channel.bookmarks-bottom-sheet.bottom-sheet"
> >
{hasBookmarks ? ( {hasBookmarks ? (
<NearViewportSurfaceContext.Provider value={resolveBookmarksScrollSurface}>
<Scroller <Scroller
className={styles.messageList} className={styles.messageList}
key="bookmarks-bottom-sheet-scroller" key="bookmarks-bottom-sheet-scroller"
@@ -155,6 +158,7 @@ export const BookmarksBottomSheet = observer(({isOpen, onClose}: BookmarksBottom
))} ))}
</div> </div>
</Scroller> </Scroller>
</NearViewportSurfaceContext.Provider>
) : ( ) : (
<div className={styles.emptyState} data-flx="channel.bookmarks-bottom-sheet.empty-state"> <div className={styles.emptyState} data-flx="channel.bookmarks-bottom-sheet.empty-state">
<div className={styles.emptyContent} data-flx="channel.bookmarks-bottom-sheet.empty-content"> <div className={styles.emptyContent} data-flx="channel.bookmarks-bottom-sheet.empty-content">