add client side sort to album song list

This commit is contained in:
jeffvli
2025-12-07 19:15:34 -08:00
parent 3c8054c93b
commit bd8503b25d
3 changed files with 104 additions and 3 deletions
@@ -15,6 +15,8 @@ import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel'; import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel';
import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
import { ListSortByDropdownControlled } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
import { ListSortOrderToggleButtonControlled } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
import { searchLibraryItems } from '/@/renderer/features/shared/utils'; import { searchLibraryItems } from '/@/renderer/features/shared/utils';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
@@ -28,6 +30,7 @@ import {
} from '/@/renderer/utils'; } from '/@/renderer/utils';
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify'; import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';
import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types'; import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types';
import { sortSongList } from '/@/shared/api/utils';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Checkbox } from '/@/shared/components/checkbox/checkbox'; import { Checkbox } from '/@/shared/components/checkbox/checkbox';
import { Flex } from '/@/shared/components/flex/flex'; import { Flex } from '/@/shared/components/flex/flex';
@@ -46,6 +49,7 @@ import {
ExplicitStatus, ExplicitStatus,
LibraryItem, LibraryItem,
Song, Song,
SongListSort,
SortOrder, SortOrder,
} from '/@/shared/types/domain-types'; } from '/@/shared/types/domain-types';
import { ItemListKey, ListDisplayType, Play } from '/@/shared/types/types'; import { ItemListKey, ListDisplayType, Play } from '/@/shared/types/types';
@@ -426,13 +430,20 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
const currentSong = usePlayerSong(); const currentSong = usePlayerSong();
const [sortBy, setSortBy] = useState<SongListSort>(SongListSort.ID);
const [sortOrder, setSortOrder] = useState<SortOrder>(SortOrder.ASC);
const columns = useMemo(() => { const columns = useMemo(() => {
return tableConfig?.columns || []; return tableConfig?.columns || [];
}, [tableConfig?.columns]); }, [tableConfig?.columns]);
const filteredSongs = useMemo(() => { const filteredSongs = useMemo(() => {
return searchLibraryItems(songs, searchTerm, LibraryItem.SONG); return sortSongList(
}, [songs, searchTerm]); searchLibraryItems(songs, searchTerm, LibraryItem.SONG),
sortBy,
sortOrder,
);
}, [songs, searchTerm, sortBy, sortOrder]);
const { handleColumnReordered } = useItemListColumnReorder({ const { handleColumnReordered } = useItemListColumnReorder({
itemListKey: ItemListKey.ALBUM_DETAIL, itemListKey: ItemListKey.ALBUM_DETAIL,
@@ -484,6 +495,11 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
return undefined; return undefined;
} }
// Remove groups when sorting
if (sortBy !== SongListSort.ID) {
return undefined;
}
if (discGroups.length <= 1) { if (discGroups.length <= 1) {
return undefined; return undefined;
} }
@@ -561,7 +577,7 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
}, },
rowHeight: 40, rowHeight: 40,
})); }));
}, [discGroups, t, searchTerm]); }, [searchTerm, sortBy, discGroups, t]);
const player = usePlayer(); const player = usePlayer();
@@ -630,6 +646,15 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
}} }}
value={searchTerm} value={searchTerm}
/> />
<ListSortByDropdownControlled
itemType={LibraryItem.PLAYLIST_SONG}
setSortBy={(value) => setSortBy(value as SongListSort)}
sortBy={sortBy}
/>
<ListSortOrderToggleButtonControlled
setSortOrder={(value) => setSortOrder(value as SortOrder)}
sortOrder={sortOrder}
/>
<ListConfigMenu <ListConfigMenu
displayTypes={[{ hidden: true, value: ListDisplayType.GRID }]} displayTypes={[{ hidden: true, value: ListDisplayType.GRID }]}
listKey={ItemListKey.ALBUM_DETAIL} listKey={ItemListKey.ALBUM_DETAIL}
@@ -1,3 +1,5 @@
import { Dispatch, SetStateAction } from 'react';
import i18n from '/@/i18n/i18n'; import i18n from '/@/i18n/i18n';
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter'; import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
@@ -19,6 +21,7 @@ import { ItemListKey } from '/@/shared/types/types';
interface ListSortByDropdownProps { interface ListSortByDropdownProps {
defaultSortByValue: string; defaultSortByValue: string;
disabled?: boolean; disabled?: boolean;
includeId?: boolean;
itemType: LibraryItem; itemType: LibraryItem;
listKey: ItemListKey; listKey: ItemListKey;
onChange?: (value: string) => void; onChange?: (value: string) => void;
@@ -72,6 +75,57 @@ export const ListSortByDropdown = ({
); );
}; };
interface ListSortByDropdownControlledProps {
disabled?: boolean;
itemType: LibraryItem;
setSortBy: Dispatch<SetStateAction<string>>;
sortBy: string;
target?: React.ReactNode;
}
export const ListSortByDropdownControlled = ({
disabled,
itemType,
setSortBy,
sortBy,
target,
}: ListSortByDropdownControlledProps) => {
const server = useCurrentServer();
const sortByLabel =
(itemType && FILTERS[itemType][server.type].find((f) => f.value === sortBy)?.name) || '—';
const handleSortByChange = (sortBy: string) => {
setSortBy(sortBy);
};
return (
<DropdownMenu disabled={disabled} position="bottom-start">
<DropdownMenu.Target>
{target ? (
target
) : (
<Button disabled={disabled} variant="subtle">
{sortByLabel}
</Button>
)}
</DropdownMenu.Target>
<DropdownMenu.Dropdown>
{FILTERS[itemType][server.type].map((f) => (
<DropdownMenu.Item
isSelected={f.value === sortBy}
key={`filter-${f.name}`}
onClick={() => handleSortByChange(f.value)}
value={f.value}
>
{f.name}
</DropdownMenu.Item>
))}
</DropdownMenu.Dropdown>
</DropdownMenu>
);
};
const CLIENT_SIDE_SONG_FILTERS = [ const CLIENT_SIDE_SONG_FILTERS = [
{ {
defaultOrder: SortOrder.ASC, defaultOrder: SortOrder.ASC,
@@ -29,3 +29,25 @@ export const ListSortOrderToggleButton = ({
/> />
); );
}; };
interface ListSortOrderToggleButtonControlledProps {
disabled?: boolean;
setSortOrder: (sortOrder: SortOrder) => void;
sortOrder: SortOrder;
}
export const ListSortOrderToggleButtonControlled = ({
disabled,
setSortOrder,
sortOrder,
}: ListSortOrderToggleButtonControlledProps) => {
return (
<OrderToggleButton
disabled={disabled}
onToggle={() =>
setSortOrder(sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC)
}
sortOrder={sortOrder as SortOrder}
/>
);
};