This commit is contained in:
jeffvli
2026-02-09 20:22:20 -08:00
parent b41a91c178
commit c7c72d27db
4 changed files with 34 additions and 20 deletions
@@ -15,7 +15,7 @@ import {
} from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath, Link } from 'react-router';
import { List, RowComponentProps, useDynamicRowHeight } from 'react-window-v2';
import { List, RowComponentProps, useDynamicRowHeight, useListRef } from 'react-window-v2';
import styles from './item-detail.module.css';
@@ -73,7 +73,9 @@ interface ItemDetailListProps {
itemCount?: number;
items?: unknown[];
onRangeChanged?: (range: { startIndex: number; stopIndex: number }) => Promise<void> | void;
onScrollEnd?: (rowIndex: number) => void;
rowHeight?: number;
scrollOffset?: number;
}
interface RowData {
@@ -815,6 +817,8 @@ const DetailListHeader = memo(
DetailListHeader.displayName = 'DetailListHeader';
const SCROLL_END_DEBOUNCE_MS = 150;
export const ItemDetailList = ({
currentPage,
data,
@@ -823,9 +827,13 @@ export const ItemDetailList = ({
itemCount: externalItemCount,
items,
onRangeChanged,
onScrollEnd,
}: ItemDetailListProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const listRef = useListRef(null);
const lastVisibleStartIndexRef = useRef(0);
const queryClient = useQueryClient();
const controls = useDefaultItemListControls();
const isMutatingCreateFavorite = useIsMutatingCreateFavorite();
const isMutatingDeleteFavorite = useIsMutatingDeleteFavorite();
@@ -909,6 +917,7 @@ export const ItemDetailList = ({
const handleRowsRendered = useCallback(
(range: { startIndex: number; stopIndex: number }) => {
lastVisibleStartIndexRef.current = range.startIndex;
const el = headerLeftRef.current;
if (el) {
const album = getItem?.(range.startIndex) as Album | undefined;
@@ -1017,8 +1026,27 @@ export const ItemDetailList = ({
target: container,
});
return () => osInstance()?.destroy();
}, [initialize, osInstance]);
let scrollEndTimeoutId: null | ReturnType<typeof setTimeout> = null;
const handleScroll = () => {
if (scrollEndTimeoutId) clearTimeout(scrollEndTimeoutId);
scrollEndTimeoutId = setTimeout(() => {
scrollEndTimeoutId = null;
onScrollEnd?.(lastVisibleStartIndexRef.current);
}, SCROLL_END_DEBOUNCE_MS);
};
if (onScrollEnd) {
viewport.addEventListener('scroll', handleScroll, { passive: true });
}
return () => {
if (onScrollEnd) {
viewport.removeEventListener('scroll', handleScroll);
if (scrollEndTimeoutId) clearTimeout(scrollEndTimeoutId);
}
osInstance()?.destroy();
};
}, [initialize, onScrollEnd, osInstance]);
return (
<div className={styles.wrapper}>
@@ -1033,6 +1061,7 @@ export const ItemDetailList = ({
)}
<div className={styles.container} ref={containerRef}>
<List
listRef={listRef}
onRowsRendered={throttledHandleRowsRendered}
rowComponent={
RowComponent as (props: RowComponentProps<RowData>) => ReactElement
@@ -2,7 +2,6 @@ import { UseSuspenseQueryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader';
import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist';
import { ItemDetailList } from '/@/renderer/components/item-list/item-detail-list/item-detail';
import { ItemListComponentProps } from '/@/renderer/components/item-list/types';
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
@@ -25,7 +24,6 @@ export const AlbumListInfiniteDetail = ({
sortBy: AlbumListSort.NAME,
sortOrder: SortOrder.ASC,
},
saveScrollOffset = true,
serverId,
}: AlbumListInfiniteDetailProps) => {
const listCountQuery = albumQueries.listCount({
@@ -45,10 +43,6 @@ export const AlbumListInfiniteDetail = ({
serverId,
});
const { handleOnScrollEnd, scrollOffset } = useItemListScrollPersist({
enabled: saveScrollOffset,
});
return (
<ItemDetailList
data={loadedItems}
@@ -2,7 +2,6 @@ import { UseSuspenseQueryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader';
import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist';
import { ItemDetailList } from '/@/renderer/components/item-list/item-detail-list/item-detail';
import { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination';
import { useItemListPagination } from '/@/renderer/components/item-list/item-list-pagination/use-item-list-pagination';
@@ -27,7 +26,6 @@ export const AlbumListPaginatedDetail = ({
sortBy: AlbumListSort.NAME,
sortOrder: SortOrder.ASC,
},
saveScrollOffset = true,
serverId,
}: AlbumListPaginatedDetailProps) => {
const listCountQuery = albumQueries.listCount({
@@ -50,10 +48,6 @@ export const AlbumListPaginatedDetail = ({
serverId,
});
const { handleOnScrollEnd, scrollOffset } = useItemListScrollPersist({
enabled: saveScrollOffset,
});
return (
<ItemListWithPagination
currentPage={currentPage}
@@ -531,7 +531,7 @@ export const applyFavoriteOptimisticUpdates = (
pendingUpdates.push({
previousData: data,
queryKey,
updater: (prev: { items: Song[] } | undefined) => {
updater: (prev: undefined | { items: Song[] }) => {
if (!prev) return prev;
const updatedItems = updateItemInArray(prev.items, itemIdSet, (item) =>
createFavoriteUpdater<Song>(item),
@@ -701,10 +701,7 @@ export const applyFavoriteOptimisticUpdatesDeferred = (
queryKeys.playlists.songList(variables.apiClientProps.serverId),
'playlist-song-list',
);
collectQueries(
queryKeys.songs.list(variables.apiClientProps.serverId),
'song-list',
);
collectQueries(queryKeys.songs.list(variables.apiClientProps.serverId), 'song-list');
collectQueries(
queryKeys.albumArtists.topSongs(variables.apiClientProps.serverId),
'top-songs',