mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
add personal/community toggle for artist top songs (#1372)
This commit is contained in:
@@ -1281,17 +1281,26 @@ export const JellyfinController: InternalControllerEndpoint = {
|
||||
throw new Error('No userId found');
|
||||
}
|
||||
|
||||
const type = query.type === 'personal' ? 'personal' : 'community';
|
||||
|
||||
const res = await jfApiClient(apiClientProps).getTopSongsList({
|
||||
params: {
|
||||
userId: apiClientProps.server?.userId,
|
||||
},
|
||||
query: {
|
||||
ArtistIds: query.artistId,
|
||||
Fields: 'Genres, DateCreated, MediaSources, ParentId, SortName',
|
||||
Fields:
|
||||
type === 'personal'
|
||||
? 'Genres, DateCreated, MediaSources, ParentId, SortName, UserData'
|
||||
: 'Genres, DateCreated, MediaSources, ParentId, SortName',
|
||||
|
||||
IncludeItemTypes: 'Audio',
|
||||
Limit: query.limit,
|
||||
Recursive: true,
|
||||
SortBy: 'CommunityRating,SortName',
|
||||
SortBy:
|
||||
type === 'personal'
|
||||
? JFSongListSort.PLAY_COUNT
|
||||
: JFSongListSort.COMMUNITY_RATING,
|
||||
SortOrder: 'Descending',
|
||||
UserId: apiClientProps.server?.userId,
|
||||
},
|
||||
@@ -1301,15 +1310,31 @@ export const JellyfinController: InternalControllerEndpoint = {
|
||||
throw new Error('Failed to get top song list');
|
||||
}
|
||||
|
||||
return {
|
||||
items: res.body.Items.map((item) =>
|
||||
jfNormalize.song(
|
||||
item,
|
||||
apiClientProps.server,
|
||||
args.context?.pathReplace,
|
||||
args.context?.pathReplaceWith,
|
||||
),
|
||||
const items = res.body.Items.map((item) =>
|
||||
jfNormalize.song(
|
||||
item,
|
||||
apiClientProps.server,
|
||||
args.context?.pathReplace,
|
||||
args.context?.pathReplaceWith,
|
||||
),
|
||||
);
|
||||
|
||||
if (type === 'personal') {
|
||||
const sorted = orderBy(
|
||||
items,
|
||||
['playCount', 'albumId', 'trackNumber'],
|
||||
['desc', 'asc', 'asc'],
|
||||
);
|
||||
|
||||
return {
|
||||
items: sorted,
|
||||
startIndex: 0,
|
||||
totalRecordCount: res.body.TotalRecordCount,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
startIndex: 0,
|
||||
totalRecordCount: res.body.TotalRecordCount,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { set } from 'idb-keyval';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
|
||||
import { ndApiClient } from '/@/renderer/api/navidrome/navidrome-api';
|
||||
import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api';
|
||||
@@ -17,7 +18,9 @@ import {
|
||||
PlaylistSongListArgs,
|
||||
PlaylistSongListResponse,
|
||||
ServerListItemWithCredential,
|
||||
SongListSort,
|
||||
songListSortMap,
|
||||
SortOrder,
|
||||
sortOrderMap,
|
||||
tagListSortMap,
|
||||
userListSortMap,
|
||||
@@ -807,7 +810,59 @@ export const NavidromeController: InternalControllerEndpoint = {
|
||||
tags,
|
||||
};
|
||||
},
|
||||
getTopSongs: SubsonicController.getTopSongs,
|
||||
getTopSongs: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const type = query.type === 'personal' ? 'personal' : 'community';
|
||||
|
||||
if (type === 'community') {
|
||||
const res = await ssApiClient(apiClientProps).getTopSongsList({
|
||||
query: {
|
||||
artist: query.artist,
|
||||
count: query.limit,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get top songs');
|
||||
}
|
||||
|
||||
return {
|
||||
items: (res.body.topSongs?.song || []).map((song) =>
|
||||
ssNormalize.song(
|
||||
song,
|
||||
apiClientProps.server,
|
||||
args.context?.pathReplace,
|
||||
args.context?.pathReplaceWith,
|
||||
),
|
||||
),
|
||||
startIndex: 0,
|
||||
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
||||
};
|
||||
}
|
||||
|
||||
const res = await NavidromeController.getSongList({
|
||||
apiClientProps,
|
||||
query: {
|
||||
artistIds: [query.artistId],
|
||||
sortBy: SongListSort.PLAY_COUNT,
|
||||
sortOrder: SortOrder.DESC,
|
||||
startIndex: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const songsWithPlayCount = orderBy(
|
||||
res.items.filter((song) => song.playCount > 0),
|
||||
['playCount', 'albumId', 'trackNumber'],
|
||||
['desc', 'asc', 'asc'],
|
||||
);
|
||||
|
||||
return {
|
||||
items: songsWithPlayCount,
|
||||
startIndex: 0,
|
||||
totalRecordCount: res.totalRecordCount,
|
||||
};
|
||||
},
|
||||
getUserInfo: SubsonicController.getUserInfo,
|
||||
getUserList: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
@@ -1794,29 +1794,54 @@ export const SubsonicController: InternalControllerEndpoint = {
|
||||
getTopSongs: async (args) => {
|
||||
const { apiClientProps, context, query } = args;
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getTopSongsList({
|
||||
query: {
|
||||
artist: query.artist,
|
||||
count: query.limit,
|
||||
},
|
||||
});
|
||||
const type = query.type === 'personal' ? 'personal' : 'community';
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get top songs');
|
||||
}
|
||||
if (type === 'community') {
|
||||
const res = await ssApiClient(apiClientProps).getTopSongsList({
|
||||
query: {
|
||||
artist: query.artist,
|
||||
count: query.limit,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
items:
|
||||
res.body.topSongs?.song?.map((song) =>
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get top songs');
|
||||
}
|
||||
|
||||
return {
|
||||
items: (res.body.topSongs?.song || []).map((song) =>
|
||||
ssNormalize.song(
|
||||
song,
|
||||
apiClientProps.server,
|
||||
context?.pathReplace,
|
||||
context?.pathReplaceWith,
|
||||
),
|
||||
) || [],
|
||||
),
|
||||
startIndex: 0,
|
||||
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
||||
};
|
||||
}
|
||||
|
||||
const res = await SubsonicController.getSongList({
|
||||
apiClientProps,
|
||||
query: {
|
||||
artistIds: [query.artistId],
|
||||
sortBy: SongListSort.PLAY_COUNT,
|
||||
sortOrder: SortOrder.DESC,
|
||||
startIndex: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const songsWithPlayCount = orderBy(
|
||||
res.items.filter((song) => song.playCount > 0),
|
||||
['playCount', 'albumId', 'trackNumber'],
|
||||
['desc', 'asc', 'asc'],
|
||||
);
|
||||
|
||||
return {
|
||||
items: songsWithPlayCount,
|
||||
startIndex: 0,
|
||||
totalRecordCount: res.body.topSongs?.song?.length || 0,
|
||||
totalRecordCount: res.totalRecordCount,
|
||||
};
|
||||
},
|
||||
getUserInfo: async (args) => {
|
||||
|
||||
@@ -66,6 +66,7 @@ import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
||||
import { Grid } from '/@/shared/components/grid/grid';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||
import { Spoiler } from '/@/shared/components/spoiler/spoiler';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
@@ -74,6 +75,7 @@ import { TextTitle } from '/@/shared/components/text-title/text-title';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { useDebouncedValue } from '/@/shared/hooks/use-debounced-value';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
@@ -236,6 +238,10 @@ const AlbumArtistMetadataTopSongsContent = ({
|
||||
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',
|
||||
});
|
||||
const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.SONG]?.table);
|
||||
const currentSong = usePlayerSong();
|
||||
const player = usePlayer();
|
||||
@@ -249,6 +255,7 @@ const AlbumArtistMetadataTopSongsContent = ({
|
||||
query: {
|
||||
artist: detailQuery.data?.name || '',
|
||||
artistId: routeId,
|
||||
type: topSongsQueryType,
|
||||
},
|
||||
serverId: serverId,
|
||||
}),
|
||||
@@ -316,15 +323,9 @@ const AlbumArtistMetadataTopSongsContent = ({
|
||||
onLongPress: () => handlePlay(LONG_PRESS_PLAY_BEHAVIOR[Play.LAST]),
|
||||
});
|
||||
|
||||
if (topSongsQuery.isLoading || !topSongsQuery.data) {
|
||||
return null;
|
||||
}
|
||||
const isLoading = topSongsQuery.isLoading || !topSongsQuery.data;
|
||||
|
||||
if (!topSongsQuery?.data?.items?.length) return null;
|
||||
|
||||
if (!tableConfig) {
|
||||
return null;
|
||||
}
|
||||
if (!isLoading && !tableConfig) return null;
|
||||
|
||||
const currentSongId = currentSong?.id;
|
||||
|
||||
@@ -338,7 +339,7 @@ const AlbumArtistMetadataTopSongsContent = ({
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</TextTitle>
|
||||
<Badge>{songs.length}</Badge>
|
||||
{!isLoading && <Badge>{songs.length}</Badge>}
|
||||
</Group>
|
||||
<div className={styles.albumSectionDividerContainer}>
|
||||
<div className={styles.albumSectionDivider} />
|
||||
@@ -365,6 +366,7 @@ const AlbumArtistMetadataTopSongsContent = ({
|
||||
variant="subtle"
|
||||
{...handlePlayNow.handlers}
|
||||
{...handlePlayNow.props}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
<PlayTooltip type={Play.NEXT}>
|
||||
@@ -375,6 +377,7 @@ const AlbumArtistMetadataTopSongsContent = ({
|
||||
variant="subtle"
|
||||
{...handlePlayNext.handlers}
|
||||
{...handlePlayNext.props}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
<PlayTooltip type={Play.LAST}>
|
||||
@@ -385,78 +388,108 @@ const AlbumArtistMetadataTopSongsContent = ({
|
||||
variant="subtle"
|
||||
{...handlePlayLast.handlers}
|
||||
{...handlePlayLast.props}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
</ActionIconGroup>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Group gap="sm" w="100%">
|
||||
<TextInput
|
||||
flex={1}
|
||||
leftSection={<Icon icon="search" />}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder={t('common.search', { postProcess: 'sentenceCase' })}
|
||||
radius="xl"
|
||||
rightSection={
|
||||
searchTerm ? (
|
||||
<ActionIcon
|
||||
icon="x"
|
||||
onClick={() => setSearchTerm('')}
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
styles={{
|
||||
input: {
|
||||
background: 'transparent',
|
||||
border: '1px solid rgba(255, 255, 255, 0.05)',
|
||||
},
|
||||
}}
|
||||
value={searchTerm}
|
||||
/>
|
||||
<ListConfigMenu
|
||||
displayTypes={[{ hidden: true, value: ListDisplayType.GRID }]}
|
||||
listKey={ItemListKey.SONG}
|
||||
optionsConfig={{
|
||||
table: {
|
||||
itemsPerPage: { hidden: true },
|
||||
pagination: { hidden: true },
|
||||
},
|
||||
}}
|
||||
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}
|
||||
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>
|
||||
{isLoading ? (
|
||||
<Group justify="center" py="md">
|
||||
<Spinner container />
|
||||
</Group>
|
||||
)}
|
||||
) : tableConfig ? (
|
||||
<>
|
||||
<Group gap="sm" w="100%">
|
||||
<TextInput
|
||||
flex={1}
|
||||
leftSection={<Icon icon="search" />}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder={t('common.search', { postProcess: 'sentenceCase' })}
|
||||
radius="xl"
|
||||
rightSection={
|
||||
searchTerm ? (
|
||||
<ActionIcon
|
||||
icon="x"
|
||||
onClick={() => setSearchTerm('')}
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
styles={{
|
||||
input: {
|
||||
background: 'transparent',
|
||||
border: '1px solid rgba(255, 255, 255, 0.05)',
|
||||
},
|
||||
}}
|
||||
value={searchTerm}
|
||||
/>
|
||||
<SegmentedControl
|
||||
data={[
|
||||
{
|
||||
label: t('page.albumArtistDetail.topSongsCommunity', {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
value: 'community',
|
||||
},
|
||||
{
|
||||
label: t('page.albumArtistDetail.topSongsPersonal', {
|
||||
postProcess: 'sentenceCase',
|
||||
}),
|
||||
value: 'personal',
|
||||
},
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setTopSongsQueryType(value as 'community' | 'personal')
|
||||
}
|
||||
size="xs"
|
||||
value={topSongsQueryType}
|
||||
/>
|
||||
<ListConfigMenu
|
||||
displayTypes={[{ hidden: true, value: ListDisplayType.GRID }]}
|
||||
listKey={ItemListKey.SONG}
|
||||
optionsConfig={{
|
||||
table: {
|
||||
itemsPerPage: { hidden: true },
|
||||
pagination: { hidden: true },
|
||||
},
|
||||
}}
|
||||
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}
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</Stack>
|
||||
</section>
|
||||
);
|
||||
@@ -569,15 +602,9 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
|
||||
onLongPress: () => handlePlay(LONG_PRESS_PLAY_BEHAVIOR[Play.LAST]),
|
||||
});
|
||||
|
||||
if (favoriteSongsQuery.isLoading || !favoriteSongsQuery.data) {
|
||||
return null;
|
||||
}
|
||||
const isLoading = favoriteSongsQuery.isLoading || !favoriteSongsQuery.data;
|
||||
|
||||
if (!favoriteSongsQuery?.data?.items?.length) return null;
|
||||
|
||||
if (!tableConfig) {
|
||||
return null;
|
||||
}
|
||||
if (!isLoading && !tableConfig) return null;
|
||||
|
||||
const currentSongId = currentSong?.id;
|
||||
|
||||
@@ -591,7 +618,7 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</TextTitle>
|
||||
<Badge>{favoriteSongsQuery.data?.items?.length}</Badge>
|
||||
{!isLoading && <Badge>{songs.length}</Badge>}
|
||||
</Group>
|
||||
<div className={styles.albumSectionDividerContainer}>
|
||||
<div className={styles.albumSectionDivider} />
|
||||
@@ -618,6 +645,7 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
|
||||
variant="subtle"
|
||||
{...handlePlayNow.handlers}
|
||||
{...handlePlayNow.props}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
<PlayTooltip type={Play.NEXT}>
|
||||
@@ -628,6 +656,7 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
|
||||
variant="subtle"
|
||||
{...handlePlayNext.handlers}
|
||||
{...handlePlayNext.props}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
<PlayTooltip type={Play.LAST}>
|
||||
@@ -638,78 +667,87 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
|
||||
variant="subtle"
|
||||
{...handlePlayLast.handlers}
|
||||
{...handlePlayLast.props}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
</ActionIconGroup>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Group gap="sm" w="100%">
|
||||
<TextInput
|
||||
flex={1}
|
||||
leftSection={<Icon icon="search" />}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder={t('common.search', { postProcess: 'sentenceCase' })}
|
||||
radius="xl"
|
||||
rightSection={
|
||||
searchTerm ? (
|
||||
<ActionIcon
|
||||
icon="x"
|
||||
onClick={() => setSearchTerm('')}
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
styles={{
|
||||
input: {
|
||||
background: 'transparent',
|
||||
border: '1px solid rgba(255, 255, 255, 0.05)',
|
||||
},
|
||||
}}
|
||||
value={searchTerm}
|
||||
/>
|
||||
<ListConfigMenu
|
||||
displayTypes={[{ hidden: true, value: ListDisplayType.GRID }]}
|
||||
listKey={ItemListKey.SONG}
|
||||
optionsConfig={{
|
||||
table: {
|
||||
itemsPerPage: { hidden: true },
|
||||
pagination: { hidden: true },
|
||||
},
|
||||
}}
|
||||
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}
|
||||
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>
|
||||
{isLoading ? (
|
||||
<Group justify="center" py="md">
|
||||
<Spinner />
|
||||
</Group>
|
||||
)}
|
||||
) : tableConfig ? (
|
||||
<>
|
||||
<Group gap="sm" w="100%">
|
||||
<TextInput
|
||||
flex={1}
|
||||
leftSection={<Icon icon="search" />}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder={t('common.search', { postProcess: 'sentenceCase' })}
|
||||
radius="xl"
|
||||
rightSection={
|
||||
searchTerm ? (
|
||||
<ActionIcon
|
||||
icon="x"
|
||||
onClick={() => setSearchTerm('')}
|
||||
size="sm"
|
||||
variant="transparent"
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
styles={{
|
||||
input: {
|
||||
background: 'transparent',
|
||||
border: '1px solid rgba(255, 255, 255, 0.05)',
|
||||
},
|
||||
}}
|
||||
value={searchTerm}
|
||||
/>
|
||||
<ListConfigMenu
|
||||
displayTypes={[{ hidden: true, value: ListDisplayType.GRID }]}
|
||||
listKey={ItemListKey.SONG}
|
||||
optionsConfig={{
|
||||
table: {
|
||||
itemsPerPage: { hidden: true },
|
||||
pagination: { hidden: true },
|
||||
},
|
||||
}}
|
||||
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}
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</Stack>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-e
|
||||
import { usePlayerSong } from '/@/renderer/store';
|
||||
import { useCurrentServer } from '/@/renderer/store/auth.store';
|
||||
import { useSettingsStore } from '/@/renderer/store/settings.store';
|
||||
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
||||
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
||||
import { ItemListKey, Play } from '/@/shared/types/types';
|
||||
|
||||
@@ -28,6 +29,11 @@ const AlbumArtistDetailTopSongsListRoute = () => {
|
||||
const server = useCurrentServer();
|
||||
const pageKey = LibraryItem.SONG;
|
||||
|
||||
const [topSongsQueryType] = useLocalStorage<'community' | 'personal'>({
|
||||
defaultValue: 'community',
|
||||
key: 'album-artist-top-songs-query-type',
|
||||
});
|
||||
|
||||
const detailQuery = useQuery(
|
||||
artistsQueries.albumArtistDetail({
|
||||
query: { id: routeId },
|
||||
@@ -38,7 +44,11 @@ const AlbumArtistDetailTopSongsListRoute = () => {
|
||||
const topSongsQuery = useQuery(
|
||||
artistsQueries.topSongs({
|
||||
options: { enabled: !!detailQuery?.data?.name },
|
||||
query: { artist: detailQuery?.data?.name || '', artistId: routeId },
|
||||
query: {
|
||||
artist: detailQuery?.data?.name || '',
|
||||
artistId: routeId,
|
||||
type: topSongsQueryType,
|
||||
},
|
||||
serverId: server?.id,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -41,6 +41,16 @@ export const useSendScrobble = (options?: MutationOptions) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['home', 'mostPlayed'],
|
||||
});
|
||||
|
||||
// Invalidate album artist top songs
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.albumArtists.topSongs(serverId),
|
||||
});
|
||||
|
||||
// Invalidate album artist favorite songs
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.albumArtists.favoriteSongs(serverId),
|
||||
});
|
||||
}
|
||||
},
|
||||
...options,
|
||||
|
||||
Reference in New Issue
Block a user