improve album artist favorite performance and search (#1709)

* improve album artist favorite performance and search

* adjust top songs / favorite songs sections

---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
This commit is contained in:
Kendall Garner
2026-02-14 04:13:28 +00:00
committed by GitHub
parent 81189db1e1
commit 77fef33cbf
2 changed files with 100 additions and 88 deletions
@@ -228,6 +228,39 @@ const AlbumArtistMetadataBiography = ({
);
};
const TABLE_ROW_HEIGHT = {
compact: 40,
default: 64,
large: 88,
} as const;
const TABLE_HEADER_HEIGHT = 40;
interface SongTableListContainerProps {
children: React.ReactNode;
enableHeader?: boolean;
itemCount: number;
maxRows?: number;
tableSize?: 'compact' | 'default' | 'large';
}
function getTableRowHeight(size: 'compact' | 'default' | 'large' | undefined): number {
return size ? TABLE_ROW_HEIGHT[size] : TABLE_ROW_HEIGHT.default;
}
const SongTableListContainer = ({
children,
enableHeader = true,
itemCount,
maxRows = 5,
tableSize = 'default',
}: SongTableListContainerProps) => {
const rowHeight = getTableRowHeight(tableSize);
const headerOffset = enableHeader ? TABLE_HEADER_HEIGHT : 0;
const height = headerOffset + rowHeight * Math.min(itemCount, maxRows);
return <div style={{ height }}>{children}</div>;
};
interface AlbumArtistMetadataTopSongsProps {
detailQuery: ReturnType<typeof useSuspenseQuery<AlbumArtistDetailResponse>>;
routeId: string;
@@ -240,7 +273,6 @@ const AlbumArtistMetadataTopSongsContent = ({
const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState('');
const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300);
const [showAll, setShowAll] = useState(false);
const [topSongsQueryType, setTopSongsQueryType] = useLocalStorage<'community' | 'personal'>({
defaultValue: 'community',
key: 'album-artist-top-songs-query-type',
@@ -272,13 +304,8 @@ const AlbumArtistMetadataTopSongsContent = ({
}, [tableConfig?.columns]);
const filteredSongs = useMemo(() => {
const filtered = searchLibraryItems(songs, debouncedSearchTerm, LibraryItem.SONG);
// When searching, show all results. Otherwise, limit to 5 if not showing all
if (debouncedSearchTerm?.trim() || showAll) {
return filtered;
}
return filtered.slice(0, 5);
}, [songs, debouncedSearchTerm, showAll]);
return searchLibraryItems(songs, debouncedSearchTerm, LibraryItem.SONG);
}, [songs, debouncedSearchTerm]);
const { handleColumnReordered } = useItemListColumnReorder({
itemListKey: ItemListKey.SONG,
@@ -465,35 +492,35 @@ const AlbumArtistMetadataTopSongsContent = ({
tableColumnsData={SONG_TABLE_COLUMNS}
/>
</Group>
<ItemTableList
activeRowId={currentSongId}
autoFitColumns={tableConfig.autoFitColumns}
CellComponent={ItemTableListColumn}
columns={columns}
data={filteredSongs}
enableAlternateRowColors={tableConfig.enableAlternateRowColors}
enableDrag
enableDragScroll={false}
enableExpansion={false}
<SongTableListContainer
enableHeader={tableConfig.enableHeader}
enableHorizontalBorders={tableConfig.enableHorizontalBorders}
enableRowHoverHighlight={tableConfig.enableRowHoverHighlight}
enableSelection
enableSelectionDialog={false}
enableVerticalBorders={tableConfig.enableVerticalBorders}
itemType={LibraryItem.SONG}
onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized}
overrideControls={overrideControls}
size={tableConfig.size}
/>
{!searchTerm.trim() && songs.length > 5 && !showAll && (
<Group justify="center" w="100%">
<Button onClick={() => setShowAll(true)} variant="subtle">
{t('action.viewMore', { postProcess: 'sentenceCase' })}
</Button>
</Group>
)}
itemCount={filteredSongs.length}
maxRows={5}
tableSize={tableConfig.size}
>
<ItemTableList
activeRowId={currentSongId}
autoFitColumns={tableConfig.autoFitColumns}
CellComponent={ItemTableListColumn}
columns={columns}
data={filteredSongs}
enableAlternateRowColors={tableConfig.enableAlternateRowColors}
enableDrag
enableDragScroll={false}
enableExpansion={false}
enableHeader={tableConfig.enableHeader}
enableHorizontalBorders={tableConfig.enableHorizontalBorders}
enableRowHoverHighlight={tableConfig.enableRowHoverHighlight}
enableSelection
enableSelectionDialog={false}
enableVerticalBorders={tableConfig.enableVerticalBorders}
itemType={LibraryItem.SONG}
onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized}
overrideControls={overrideControls}
size={tableConfig.size}
/>
</SongTableListContainer>
</>
) : null}
</Stack>
@@ -529,7 +556,6 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState('');
const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300);
const [showAll, setShowAll] = useState(false);
const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.SONG]?.table);
const currentSong = usePlayerSong();
const player = usePlayer();
@@ -554,13 +580,8 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
}, [tableConfig?.columns]);
const filteredSongs = useMemo(() => {
const filtered = searchLibraryItems(songs, debouncedSearchTerm, LibraryItem.SONG);
// When searching, show all results. Otherwise, limit to 5 if not showing all
if (debouncedSearchTerm?.trim() || showAll) {
return filtered;
}
return filtered.slice(0, 5);
}, [songs, debouncedSearchTerm, showAll]);
return searchLibraryItems(songs, debouncedSearchTerm, LibraryItem.SONG);
}, [songs, debouncedSearchTerm]);
const { handleColumnReordered } = useItemListColumnReorder({
itemListKey: ItemListKey.SONG,
@@ -726,35 +747,35 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
tableColumnsData={SONG_TABLE_COLUMNS}
/>
</Group>
<ItemTableList
activeRowId={currentSongId}
autoFitColumns={tableConfig.autoFitColumns}
CellComponent={ItemTableListColumn}
columns={columns}
data={filteredSongs}
enableAlternateRowColors={tableConfig.enableAlternateRowColors}
enableDrag
enableDragScroll={false}
enableExpansion={false}
<SongTableListContainer
enableHeader={tableConfig.enableHeader}
enableHorizontalBorders={tableConfig.enableHorizontalBorders}
enableRowHoverHighlight={tableConfig.enableRowHoverHighlight}
enableSelection
enableSelectionDialog={false}
enableVerticalBorders={tableConfig.enableVerticalBorders}
itemType={LibraryItem.SONG}
onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized}
overrideControls={overrideControls}
size={tableConfig.size}
/>
{!searchTerm.trim() && songs.length > 5 && !showAll && (
<Group justify="center" w="100%">
<Button onClick={() => setShowAll(true)} variant="subtle">
{t('action.viewMore', { postProcess: 'sentenceCase' })}
</Button>
</Group>
)}
itemCount={filteredSongs.length}
maxRows={5}
tableSize={tableConfig.size}
>
<ItemTableList
activeRowId={currentSongId}
autoFitColumns={tableConfig.autoFitColumns}
CellComponent={ItemTableListColumn}
columns={columns}
data={filteredSongs}
enableAlternateRowColors={tableConfig.enableAlternateRowColors}
enableDrag
enableDragScroll={false}
enableExpansion={false}
enableHeader={tableConfig.enableHeader}
enableHorizontalBorders={tableConfig.enableHorizontalBorders}
enableRowHoverHighlight={tableConfig.enableRowHoverHighlight}
enableSelection
enableSelectionDialog={false}
enableVerticalBorders={tableConfig.enableVerticalBorders}
itemType={LibraryItem.SONG}
onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized}
overrideControls={overrideControls}
size={tableConfig.size}
/>
</SongTableListContainer>
</>
) : null}
</Stack>
+7 -16
View File
@@ -128,23 +128,12 @@ export const createFuseForLibraryItem = <T extends FuseSearchableItem>(
});
}
const sampleItem = items[0];
const stringKeys = Object.keys(sampleItem).filter(
(key) =>
typeof sampleItem[key as keyof T] === 'string' &&
!key.startsWith('_') &&
key !== 'id' &&
key !== 'albumId' &&
key !== 'streamUrl' &&
key !== 'serverId' &&
key !== 'ownerId',
) as string[];
const stringKeys: string[] = [];
const nestedKeys: Array<{ getFn: (item: T) => string; name: string }> = [];
switch (itemType) {
case LibraryItem.ALBUM: {
stringKeys.push('name', 'releaseType');
nestedKeys.push(
{
getFn: (item) => {
@@ -172,6 +161,7 @@ export const createFuseForLibraryItem = <T extends FuseSearchableItem>(
}
case LibraryItem.ALBUM_ARTIST: {
stringKeys.push('name');
nestedKeys.push({
getFn: (item) => {
const aa = item as AlbumArtist;
@@ -185,9 +175,10 @@ export const createFuseForLibraryItem = <T extends FuseSearchableItem>(
case LibraryItem.ARTIST:
case LibraryItem.GENRE:
case LibraryItem.RADIO_STATION:
stringKeys.push('name');
break;
case LibraryItem.PLAYLIST: {
stringKeys.push('name');
nestedKeys.push({
getFn: (item) => {
const p = item as Playlist;
@@ -200,7 +191,8 @@ export const createFuseForLibraryItem = <T extends FuseSearchableItem>(
case LibraryItem.PLAYLIST_SONG:
case LibraryItem.QUEUE_SONG:
case LibraryItem.SONG: {
case LibraryItem.SONG:
stringKeys.push('album', 'name');
nestedKeys.push(
{
getFn: (item) => {
@@ -218,7 +210,6 @@ export const createFuseForLibraryItem = <T extends FuseSearchableItem>(
},
);
break;
}
}
return new Fuse(items, {