diff --git a/src/renderer/features/artists/components/album-artist-detail-content.tsx b/src/renderer/features/artists/components/album-artist-detail-content.tsx index ada04c4fb..f2d218c7b 100644 --- a/src/renderer/features/artists/components/album-artist-detail-content.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-content.tsx @@ -1,29 +1,43 @@ import { useQuery, useSuspenseQuery } from '@tanstack/react-query'; -import { Suspense, useMemo } from 'react'; +import { Suspense, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { createSearchParams, generatePath, Link, useParams } from 'react-router'; import styles from './album-artist-detail-content.module.css'; +import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder'; +import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize'; +import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns'; +import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list'; +import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; +import { ItemControls } from '/@/renderer/components/item-list/types'; import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel'; import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; import { AlbumArtistGridCarousel } from '/@/renderer/features/artists/components/album-artist-grid-carousel'; import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller'; import { usePlayer } from '/@/renderer/features/player/context/player-context'; +import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; import { DefaultPlayButton } from '/@/renderer/features/shared/components/play-button'; +import { searchLibraryItems } from '/@/renderer/features/shared/utils'; import { useContainerQuery } from '/@/renderer/hooks'; import { useGenreRoute } from '/@/renderer/hooks/use-genre-route'; import { AppRoute } from '/@/renderer/router/routes'; -import { ArtistItem, useCurrentServer } from '/@/renderer/store'; -import { useGeneralSettings, usePlayButtonBehavior } from '/@/renderer/store/settings.store'; +import { ArtistItem, useCurrentServer, usePlayerSong } from '/@/renderer/store'; +import { + useGeneralSettings, + usePlayButtonBehavior, + useSettingsStore, +} from '/@/renderer/store/settings.store'; import { sanitize } from '/@/renderer/utils/sanitize'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { Button } from '/@/shared/components/button/button'; import { Grid } from '/@/shared/components/grid/grid'; import { Group } from '/@/shared/components/group/group'; +import { Icon } from '/@/shared/components/icon/icon'; import { Spinner } from '/@/shared/components/spinner/spinner'; import { Spoiler } from '/@/shared/components/spoiler/spoiler'; import { Stack } from '/@/shared/components/stack/stack'; +import { TextInput } from '/@/shared/components/text-input/text-input'; import { TextTitle } from '/@/shared/components/text-title/text-title'; import { Text } from '/@/shared/components/text/text'; import { @@ -31,10 +45,11 @@ import { AlbumListSort, LibraryItem, ServerType, + Song, SortOrder, TopSongListResponse, } from '/@/shared/types/domain-types'; -import { Play } from '/@/shared/types/types'; +import { ItemListKey, ListDisplayType, Play } from '/@/shared/types/types'; interface AlbumArtistActionButtonsProps { albumCount: null | number | undefined; @@ -175,33 +190,175 @@ const AlbumArtistMetadataTopSongs = ({ topSongsQuery, }: AlbumArtistMetadataTopSongsProps) => { const { t } = useTranslation(); + const [searchTerm, setSearchTerm] = useState(''); + const [showAll, setShowAll] = useState(false); + const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.SONG]?.table); + const currentSong = usePlayerSong(); + const player = usePlayer(); + + const songs = useMemo(() => topSongsQuery?.data?.items || [], [topSongsQuery?.data?.items]); + + const columns = useMemo(() => { + return tableConfig?.columns || []; + }, [tableConfig?.columns]); + + const filteredSongs = useMemo(() => { + const filtered = searchLibraryItems(songs, searchTerm, LibraryItem.SONG); + // When searching, show all results. Otherwise, limit to 5 if not showing all + if (searchTerm.trim() || showAll) { + return filtered; + } + return filtered.slice(0, 5); + }, [songs, searchTerm, showAll]); + + const { handleColumnReordered } = useItemListColumnReorder({ + itemListKey: ItemListKey.SONG, + }); + + const { handleColumnResized } = useItemListColumnResize({ + itemListKey: ItemListKey.SONG, + }); + + const overrideControls: Partial = useMemo(() => { + return { + onDoubleClick: ({ index, internalState, item }) => { + if (!item) { + return; + } + + const items = internalState?.getData() as Song[]; + + if (index !== undefined) { + player.addToQueueByData(items, Play.NOW); + player.mediaPlayByIndex(index); + } + }, + }; + }, [player]); if (!topSongsQuery?.data?.items?.length) return null; + if (!tableConfig || columns.length === 0) { + return ( +
+ + + + {t('page.albumArtistDetail.topSongs', { + postProcess: 'sentenceCase', + })} + + + + +
+ ); + } + + const currentSongId = currentSong?.id; + return (
- - - - {t('page.albumArtistDetail.topSongs', { - postProcess: 'sentenceCase', - })} - - + + + + + {t('page.albumArtistDetail.topSongs', { + postProcess: 'sentenceCase', + })} + + + - + + } + onChange={(e) => setSearchTerm(e.target.value)} + placeholder={t('common.search', { postProcess: 'sentenceCase' })} + radius="xl" + rightSection={ + searchTerm ? ( + setSearchTerm('')} + size="sm" + variant="transparent" + /> + ) : null + } + styles={{ + input: { + background: 'transparent', + border: '1px solid rgba(255, 255, 255, 0.05)', + }, + }} + value={searchTerm} + /> + + + + {!searchTerm.trim() && songs.length > 5 && !showAll && ( + + + + )} +
); }; diff --git a/src/renderer/features/artists/components/album-artist-detail-top-songs-list-header.tsx b/src/renderer/features/artists/components/album-artist-detail-top-songs-list-header.tsx index 81690eb27..42f302722 100644 --- a/src/renderer/features/artists/components/album-artist-detail-top-songs-list-header.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-top-songs-list-header.tsx @@ -4,10 +4,10 @@ import { PageHeader } from '/@/renderer/components/page-header/page-header'; import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar'; import { Badge } from '/@/shared/components/badge/badge'; import { SpinnerIcon } from '/@/shared/components/spinner/spinner'; -import { LibraryItem, QueueSong } from '/@/shared/types/domain-types'; +import { LibraryItem, Song } from '/@/shared/types/domain-types'; interface AlbumArtistDetailTopSongsListHeaderProps { - data: QueueSong[]; + data: Song[]; itemCount?: number; title: string; } diff --git a/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx b/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx index 893e2e520..a8f08b69a 100644 --- a/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx +++ b/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx @@ -2,14 +2,23 @@ import { useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; import { useParams } from 'react-router'; +import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder'; +import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize'; +import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list'; +import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; +import { ItemControls } from '/@/renderer/components/item-list/types'; import { ListContext } from '/@/renderer/context/list-context'; import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; import { AlbumArtistDetailTopSongsListHeader } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-header'; +import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; +import { usePlayerSong } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store/auth.store'; -import { LibraryItem } from '/@/shared/types/domain-types'; +import { useSettingsStore } from '/@/renderer/store/settings.store'; +import { LibraryItem, Song } from '/@/shared/types/domain-types'; +import { ItemListKey, Play } from '/@/shared/types/types'; const AlbumArtistDetailTopSongsListRoute = () => { const { albumArtistId, artistId } = useParams() as { @@ -36,6 +45,40 @@ const AlbumArtistDetailTopSongsListRoute = () => { ); const itemCount = topSongsQuery?.data?.items?.length || 0; + const songs = useMemo(() => topSongsQuery?.data?.items || [], [topSongsQuery?.data?.items]); + + const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.SONG]?.table); + const currentSong = usePlayerSong(); + const player = usePlayer(); + + const columns = useMemo(() => { + return tableConfig?.columns || []; + }, [tableConfig?.columns]); + + const { handleColumnReordered } = useItemListColumnReorder({ + itemListKey: ItemListKey.SONG, + }); + + const { handleColumnResized } = useItemListColumnResize({ + itemListKey: ItemListKey.SONG, + }); + + const overrideControls: Partial = useMemo(() => { + return { + onDoubleClick: ({ index, internalState, item }) => { + if (!item) { + return; + } + + const items = internalState?.getData() as Song[]; + + if (index !== undefined) { + player.addToQueueByData(items, Play.NOW); + player.mediaPlayByIndex(index); + } + }, + }; + }, [player]); const providerValue = useMemo(() => { return { @@ -44,19 +87,53 @@ const AlbumArtistDetailTopSongsListRoute = () => { }; }, [routeId, pageKey]); + const currentSongId = currentSong?.id; + + if (!tableConfig || columns.length === 0) { + return ( + + + + + + + + ); + } + return ( - {/* */} +