Revert "pl-fe: Improve virtualization behavior"

This reverts commit d2d1840119.
This commit is contained in:
marcin mikołajczak
2024-10-04 00:00:43 +02:00
parent d2d1840119
commit 6bb4dc20a4
13 changed files with 82 additions and 71 deletions

View File

@ -1,5 +1,5 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { useVirtualizer, type Virtualizer } from '@tanstack/react-virtual';
import { useVirtualizer, useWindowVirtualizer, type Virtualizer } from '@tanstack/react-virtual';
import clsx from 'clsx';
import React, { useEffect, useMemo } from 'react';
@ -8,7 +8,16 @@ import { useSettings } from 'pl-fe/hooks';
import LoadMore from './load-more';
import { Card, Spinner } from './ui';
interface IScrollableListBase {
type IScrollableListWindowScroll = {
/** Whether to use the window to scroll the content instead of the container. */
useWindowScroll?: true;
} | {
/** Whether to use the window to scroll the content instead of the container. */
useWindowScroll: false;
parentRef: React.RefObject<HTMLElement>;
};
interface IScrollableList {
/** Pagination callback when the end of the list is reached. */
onLoadMore?: () => void;
/** Whether the data is currently being fetched. */
@ -33,6 +42,8 @@ interface IScrollableListBase {
placeholderComponent?: React.ComponentType | React.NamedExoticComponent;
/** Number of placeholders to render while loading. */
placeholderCount?: number;
/** Extra class names on the parent element. */
className?: string;
/** Extra class names on the list element. */
listClassName?: string;
/** Class names on each item container. */
@ -41,6 +52,8 @@ interface IScrollableListBase {
loadMoreClassName?: string;
/** `id` attribute on the parent element. */
id?: string;
/** CSS styles on the parent element. */
style?: React.CSSProperties;
/** Initial item index to scroll to. */
initialIndex?: number;
/** Estimated size for items. */
@ -49,20 +62,7 @@ interface IScrollableListBase {
alignToBottom?: boolean;
}
interface IScrollableListWithContainer extends IScrollableListBase {
/** Extra class names on the container element. */
className?: string;
/** CSS styles on the container element. */
style?: React.CSSProperties;
}
interface IScrollableListWithoutContainer extends IScrollableListBase {
parentRef: React.RefObject<HTMLElement>;
}
type IScrollableList = IScrollableListWithContainer | IScrollableListWithoutContainer;
const ScrollableList = React.forwardRef<Virtualizer<any, any>, IScrollableList>(({
const ScrollableList = React.forwardRef<Virtualizer<any, any>, IScrollableList & IScrollableListWindowScroll>(({
prepend = null,
alwaysPrepend,
children,
@ -72,6 +72,7 @@ const ScrollableList = React.forwardRef<Virtualizer<any, any>, IScrollableList>(
showLoading,
onScroll,
onLoadMore,
className,
listClassName,
itemClassName,
loadMoreClassName,
@ -80,6 +81,7 @@ const ScrollableList = React.forwardRef<Virtualizer<any, any>, IScrollableList>(
placeholderComponent: Placeholder,
placeholderCount = 0,
initialIndex,
style = {},
estimatedSize = 300,
alignToBottom,
...props
@ -95,11 +97,15 @@ const ScrollableList = React.forwardRef<Virtualizer<any, any>, IScrollableList>(
const data = showPlaceholder ? Array(placeholderCount).fill('') : elements;
const virtualizer = useVirtualizer({
const virtualizer = props.useWindowScroll === false ? useVirtualizer({
count: data.length + (hasMore ? 1 : 0),
overscan: 2,
overscan: 3,
estimateSize: () => estimatedSize,
getScrollElement: () => props.parentRef.current || parentRef.current,
}) : useWindowVirtualizer({
count: data.length + (hasMore ? 1 : 0),
overscan: 3,
estimateSize: () => estimatedSize,
getScrollElement: () => ('parentRef' in props && props.parentRef.current) || parentRef.current,
});
useEffect(() => {
@ -164,51 +170,44 @@ const ScrollableList = React.forwardRef<Virtualizer<any, any>, IScrollableList>(
const virtualItems = virtualizer.getVirtualItems();
const body = (
<div
id={'parentRef' in props ? id : undefined}
className={listClassName}
style={{
height: !showLoading && data.length ? virtualizer.getTotalSize() : undefined,
width: '100%',
position: 'relative',
}}
>
{(!showLoading || showPlaceholder) && data.length ? (
<>
{prepend}
{virtualItems.map((item) => (
<div
className={item.index === data.length ? '' : itemClassName}
key={item.key as number}
data-index={item.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
width: '100%',
transform: `translateY(${item.start}px)`,
}}
>
{renderItem(item.index)}
</div>
))}
</>
) : renderEmpty()}
</div>
);
if ('parentRef' in props) return body;
return (
<div
ref={parentRef}
id={id}
className={clsx(props.className, 'w-full')}
style={props.style}
className={clsx(className, 'w-full')}
style={style}
>
{body}
<div
className={listClassName}
style={{
height: !showLoading && data.length ? virtualizer.getTotalSize() : undefined,
width: '100%',
position: 'relative',
}}
>
{(!showLoading || showPlaceholder) && data.length ? (
<>
{prepend}
{virtualItems.map((item) => (
<div
className={item.index === data.length ? '' : itemClassName}
key={item.key as number}
data-index={item.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
width: '100%',
transform: `translateY(${item.start}px)`,
}}
>
{renderItem(item.index)}
</div>
))}
</>
) : renderEmpty()}
</div>
</div>
);
});
export { type IScrollableList, type IScrollableListWithContainer, type IScrollableListWithoutContainer, ScrollableList as default };
export { type IScrollableList, ScrollableList as default };

View File

@ -14,9 +14,9 @@ import { usePlFeConfig } from 'pl-fe/hooks';
import { Stack, Text } from './ui';
import type { OrderedSet as ImmutableOrderedSet } from 'immutable';
import type { IScrollableListWithContainer } from 'pl-fe/components/scrollable-list';
import type { IScrollableList } from 'pl-fe/components/scrollable-list';
interface IStatusList extends Omit<IScrollableListWithContainer, 'onLoadMore' | 'children'> {
interface IStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children'> {
/** Unique key to preserve the scroll position when navigating back. */
scrollKey: string;
/** List of status IDs to display. */
@ -54,6 +54,7 @@ const StatusList: React.FC<IStatusList> = ({
isLoading,
isPartial,
showGroup = true,
className,
...other
}) => {
const plFeConfig = usePlFeConfig();
@ -219,6 +220,7 @@ const StatusList: React.FC<IStatusList> = ({
onLoadMore={handleLoadOlder}
placeholderComponent={() => <PlaceholderStatus variant={divideType === 'border' ? 'slim' : 'rounded'} />}
placeholderCount={20}
className={className}
listClassName={clsx('divide-y divide-solid divide-gray-200 dark:divide-gray-800', {
'divide-none': divideType !== 'border',
})}

View File

@ -59,6 +59,7 @@ const ChatList: React.FC<IChatList> = ({ onClickChat, parentRef, topOffset }) =>
hasMore={hasNextPage}
onLoadMore={handleLoadMore}
estimatedSize={64}
useWindowScroll={false}
parentRef={parentRef}
loadMoreClassName='mx-4 mb-4'
>

View File

@ -196,6 +196,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
isLoading={isFetching}
showLoading={isFetching && !isFetchingNextPage}
onLoadMore={handleStartReached}
useWindowScroll={false}
parentRef={parentRef}
>
{cachedChatMessages.map((chatMessage, index) => {

View File

@ -61,6 +61,7 @@ const Results = ({ accountSearchResult, onSelect, parentRef }: IResults) => {
isLoading={isFetching}
hasMore={hasNextPage}
onLoadMore={handleLoadMore}
useWindowScroll={false}
parentRef={parentRef}
>
{(accounts || []).map((chat) => renderAccount(chat))}

View File

@ -21,6 +21,7 @@ const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
<ScrollableList
isLoading={isFetching}
style={{ height: 320 }}
useWindowScroll={false}
parentRef={parentRef}
>
{data.map((suggestion) => (

View File

@ -78,14 +78,14 @@ const getDescendantsIds = createSelector([
interface IThread {
status: SelectedStatus;
withMedia?: boolean;
isModal?: boolean;
useWindowScroll?: boolean;
itemClassName?: string;
}
const Thread: React.FC<IThread> = ({
itemClassName,
status,
isModal = false,
useWindowScroll = true,
withMedia = true,
}) => {
const dispatch = useAppDispatch();
@ -114,7 +114,7 @@ const Thread: React.FC<IThread> = ({
});
let initialIndex = ancestorsIds.size;
if (isModal && initialIndex !== 0) initialIndex = ancestorsIds.size + 1;
if (!useWindowScroll && initialIndex !== 0) initialIndex = ancestorsIds.size + 1;
const node = useRef<HTMLDivElement>(null);
const statusRef = useRef<HTMLDivElement>(null);
@ -234,7 +234,7 @@ const Thread: React.FC<IThread> = ({
};
const _selectChild = (index: number) => {
if (isModal) index = index + 1;
if (!useWindowScroll) index = index + 1;
const selector = `[data-index="${index}"] .focusable`;
const element = node.current?.querySelector<HTMLDivElement>(selector);
@ -341,7 +341,7 @@ const Thread: React.FC<IThread> = ({
<StatusActionBar
status={status}
expandable={isModal}
expandable={!useWindowScroll}
space='lg'
withLabels
/>
@ -356,7 +356,7 @@ const Thread: React.FC<IThread> = ({
const children: JSX.Element[] = [];
if (isModal) {
if (!useWindowScroll) {
// Add padding to the top of the Thread (for Media Modal)
children.push(<div key='padding' className='h-4' />);
}
@ -376,8 +376,8 @@ const Thread: React.FC<IThread> = ({
space={2}
className={
clsx({
'h-full': isModal,
'mt-2': !isModal,
'h-full': !useWindowScroll,
'mt-2': useWindowScroll,
})
}
>
@ -391,7 +391,7 @@ const Thread: React.FC<IThread> = ({
ref={node}
className={
clsx('bg-white black:bg-black dark:bg-primary-900', {
'h-full overflow-auto': isModal,
'h-full overflow-auto': !useWindowScroll,
})
}
>
@ -403,9 +403,10 @@ const Thread: React.FC<IThread> = ({
itemClassName={itemClassName}
listClassName={
clsx({
'h-full': isModal,
'h-full': !useWindowScroll,
})
}
useWindowScroll={useWindowScroll}
parentRef={node}
>
{children}

View File

@ -38,6 +38,7 @@ const FamiliarFollowersModal: React.FC<BaseModalProps & FamiliarFollowersModalPr
itemClassName='pb-3'
style={{ height: 'calc(80vh - 88px)' }}
estimatedSize={42}
useWindowScroll={false}
parentRef={modalRef}
>
{familiarFollowerIds.map(id =>

View File

@ -54,6 +54,7 @@ const FavouritesModal: React.FC<BaseModalProps & FavouritesModalProps> = ({ onCl
onLoadMore={handleLoadMore}
hasMore={!!next}
estimatedSize={42}
useWindowScroll={false}
parentRef={modalRef}
>
{accountIds.map(id =>

View File

@ -337,7 +337,7 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
<Thread
status={status}
withMedia={false}
isModal
useWindowScroll={false}
itemClassName='px-4'
/>
</div>

View File

@ -46,6 +46,7 @@ const MentionsModal: React.FC<BaseModalProps & MentionsModalProps> = ({ onClose,
listClassName='max-w-full'
itemClassName='pb-3'
estimatedSize={42}
useWindowScroll={false}
parentRef={modalRef}
>
{accountIds.map(id =>

View File

@ -95,6 +95,7 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
itemClassName='pb-3'
style={{ height: 'calc(80vh - 88px)' }}
estimatedSize={42}
useWindowScroll={false}
parentRef={modalRef}
>
{accounts.map((account) =>

View File

@ -56,6 +56,7 @@ const ReblogsModal: React.FC<BaseModalProps & ReblogsModalProps> = ({ onClose, s
onLoadMore={handleLoadMore}
hasMore={!!next}
estimatedSize={42}
useWindowScroll={false}
parentRef={modalRef}
>
{accountIds.map((id) =>