diff --git a/src/renderer/api/jellyfin/jellyfin-controller.ts b/src/renderer/api/jellyfin/jellyfin-controller.ts index ac136a63b..243bee5a1 100644 --- a/src/renderer/api/jellyfin/jellyfin-controller.ts +++ b/src/renderer/api/jellyfin/jellyfin-controller.ts @@ -218,24 +218,25 @@ export const JellyfinController: InternalControllerEndpoint = { throw new Error('No userId found'); } - const res = await jfApiClient(apiClientProps).getAlbumArtistDetail({ - params: { - id: query.id, - userId: apiClientProps.server?.userId, - }, - query: { - Fields: 'Genres, Overview', - }, - }); - - const similarArtistsRes = await jfApiClient(apiClientProps).getSimilarArtistList({ - params: { - id: query.id, - }, - query: { - Limit: 10, - }, - }); + const [res, similarArtistsRes] = await Promise.all([ + jfApiClient(apiClientProps).getAlbumArtistDetail({ + params: { + id: query.id, + userId: apiClientProps.server?.userId, + }, + query: { + Fields: 'Genres, Overview', + }, + }), + jfApiClient(apiClientProps).getSimilarArtistList({ + params: { + id: query.id, + }, + query: { + Limit: 10, + }, + }), + ]); if (res.status !== 200 || similarArtistsRes.status !== 200) { throw new Error('Failed to get album artist detail'); diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 833fa6536..80b82e73b 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -184,18 +184,19 @@ export const NavidromeController: InternalControllerEndpoint = { getAlbumArtistDetail: async (args) => { const { apiClientProps, query } = args; - const res = await ndApiClient(apiClientProps).getAlbumArtistDetail({ - params: { - id: query.id, - }, - }); - - const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({ - query: { - count: 10, - id: query.id, - }, - }); + const [res, artistInfoRes] = await Promise.all([ + ndApiClient(apiClientProps).getAlbumArtistDetail({ + params: { + id: query.id, + }, + }), + ssApiClient(apiClientProps).getArtistInfo({ + query: { + count: 10, + id: query.id, + }, + }), + ]); if (res.status !== 200) { throw new Error('Failed to get album artist detail'); diff --git a/src/renderer/api/subsonic/subsonic-controller.ts b/src/renderer/api/subsonic/subsonic-controller.ts index edb6bb92f..7edfa4c6a 100644 --- a/src/renderer/api/subsonic/subsonic-controller.ts +++ b/src/renderer/api/subsonic/subsonic-controller.ts @@ -256,17 +256,18 @@ export const SubsonicController: InternalControllerEndpoint = { getAlbumArtistDetail: async (args) => { const { apiClientProps, query } = args; - const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({ - query: { - id: query.id, - }, - }); - - const res = await ssApiClient(apiClientProps).getArtist({ - query: { - id: query.id, - }, - }); + const [artistInfoRes, res] = await Promise.all([ + ssApiClient(apiClientProps).getArtistInfo({ + query: { + id: query.id, + }, + }), + ssApiClient(apiClientProps).getArtist({ + query: { + id: query.id, + }, + }), + ]); if (res.status !== 200) { throw new Error('Failed to get album artist detail'); 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 44e62b894..354cf4448 100644 --- a/src/renderer/features/artists/components/album-artist-detail-content.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-content.tsx @@ -1,8 +1,14 @@ -import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; +import { + useQuery, + useQueryClient, + useSuspenseQuery, + UseSuspenseQueryResult, +} from '@tanstack/react-query'; import { LayoutGroup, motion } from 'motion/react'; +import { Suspense } from 'react'; import { useCallback, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { createSearchParams, generatePath, Link, useParams } from 'react-router'; +import { createSearchParams, generatePath, Link, useLocation, useParams } from 'react-router'; import styles from './album-artist-detail-content.module.css'; @@ -16,7 +22,6 @@ import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table- 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 { albumQueries } from '/@/renderer/features/albums/api/album-api'; import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; import { AlbumArtistGridCarousel } from '/@/renderer/features/artists/components/album-artist-grid-carousel'; import { useIsPlayerFetching, usePlayer } from '/@/renderer/features/player/context/player-context'; @@ -67,6 +72,7 @@ import { Album, AlbumArtist, AlbumArtistDetailResponse, + AlbumListResponse, AlbumListSort, LibraryItem, RelatedArtist, @@ -214,7 +220,7 @@ interface AlbumArtistMetadataTopSongsProps { routeId: string; } -const AlbumArtistMetadataTopSongs = ({ +const AlbumArtistMetadataTopSongsContent = ({ detailQuery, routeId, }: AlbumArtistMetadataTopSongsProps) => { @@ -226,15 +232,22 @@ const AlbumArtistMetadataTopSongs = ({ const currentSong = usePlayerSong(); const player = usePlayer(); const serverId = useCurrentServerId(); + const server = useCurrentServer(); - const topSongsQuery = useSuspenseQuery( - artistsQueries.topSongs({ - query: { artist: detailQuery.data?.name || '', artistId: routeId }, + const canStartQuery = server?.type === ServerType.JELLYFIN || !!detailQuery.data?.name; + + const topSongsQuery = useQuery({ + ...artistsQueries.topSongs({ + query: { + artist: detailQuery.data?.name || '', + artistId: routeId, + }, serverId: serverId, }), - ); + enabled: canStartQuery, + }); - const songs = useMemo(() => topSongsQuery?.data?.items || [], [topSongsQuery?.data?.items]); + const songs = useMemo(() => topSongsQuery.data?.items || [], [topSongsQuery.data?.items]); const columns = useMemo(() => { return tableConfig?.columns || []; @@ -274,6 +287,10 @@ const AlbumArtistMetadataTopSongs = ({ }; }, [player]); + if (topSongsQuery.isLoading || !topSongsQuery.data) { + return null; + } + if (!topSongsQuery?.data?.items?.length) return null; if (!tableConfig || columns.length === 0) { @@ -404,6 +421,26 @@ const AlbumArtistMetadataTopSongs = ({ ); }; +const AlbumArtistMetadataTopSongs = ({ + detailQuery, + routeId, +}: AlbumArtistMetadataTopSongsProps) => { + const server = useCurrentServer(); + + const location = useLocation(); + const artistName = location.state?.item?.name || detailQuery.data?.name; + + const canStartQuery = server?.type === ServerType.JELLYFIN || !!artistName; + + return ( + + {canStartQuery ? ( + + ) : null} + + ); +}; + interface AlbumArtistMetadataExternalLinksProps { artistName?: string; externalLinks: boolean; @@ -543,7 +580,15 @@ const AlbumArtistMetadataSimilarArtists = ({ ); }; -export const AlbumArtistDetailContent = () => { +interface AlbumArtistDetailContentProps { + albumsQuery: UseSuspenseQueryResult; + detailQuery: UseSuspenseQueryResult; +} + +export const AlbumArtistDetailContent = ({ + albumsQuery, + detailQuery, +}: AlbumArtistDetailContentProps) => { const { artistItems, artistRadioCount, externalLinks, lastFM, musicBrainz } = useGeneralSettings(); const { albumArtistId, artistId } = useParams() as { @@ -567,13 +612,6 @@ export const AlbumArtistDetailContent = () => { return [enabled, order]; }, [artistItems]); - const detailQuery = useSuspenseQuery( - artistsQueries.albumArtistDetail({ - query: { id: routeId }, - serverId: server?.id, - }), - ); - const artistDiscographyLink = useMemo( () => `${generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY, { @@ -662,7 +700,7 @@ export const AlbumArtistDetailContent = () => { )} - + {enabledItem.similarArtists && ( @@ -1020,10 +1058,13 @@ const releaseTypeToEnumMap: Record = { spokenword: ArtistReleaseTypeItem.RELEASE_TYPE_SPOKENWORD, }; -const ArtistAlbums = () => { +interface ArtistAlbumsProps { + albumsQuery: UseSuspenseQueryResult; +} + +const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => { const { t } = useTranslation(); const { artistReleaseTypeItems } = useGeneralSettings(); - const serverId = useCurrentServerId(); const [searchTerm, setSearchTerm] = useState(''); const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300); const albumArtistDetailSort = useAppStore((state) => state.albumArtistDetailSort); @@ -1038,19 +1079,6 @@ const ArtistAlbums = () => { }; const routeId = (artistId || albumArtistId) as string; - const albumsQuery = useSuspenseQuery( - albumQueries.list({ - query: { - artistIds: [routeId], - limit: -1, - sortBy: AlbumListSort.RELEASE_DATE, - sortOrder: SortOrder.DESC, - startIndex: 0, - }, - serverId, - }), - ); - const rows = useGridRows(LibraryItem.ALBUM, ItemListKey.ALBUM); const controls = useDefaultItemListControls(); diff --git a/src/renderer/features/artists/routes/album-artist-detail-route.tsx b/src/renderer/features/artists/routes/album-artist-detail-route.tsx index 1a3ff8193..bb83e6c88 100644 --- a/src/renderer/features/artists/routes/album-artist-detail-route.tsx +++ b/src/renderer/features/artists/routes/album-artist-detail-route.tsx @@ -1,9 +1,10 @@ -import { useSuspenseQuery } from '@tanstack/react-query'; +import { useSuspenseQueries } from '@tanstack/react-query'; import { Suspense, useRef } from 'react'; import { useParams } from 'react-router'; import { useItemImageUrl } from '/@/renderer/components/item-image/item-image'; import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area'; +import { albumQueries } from '/@/renderer/features/albums/api/album-api'; import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content'; import { AlbumArtistDetailHeader } from '/@/renderer/features/artists/components/album-artist-detail-header'; @@ -16,14 +17,15 @@ import { LibraryContainer } from '/@/renderer/features/shared/components/library import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar'; import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { useFastAverageColor, useWaitForColorCalculation } from '/@/renderer/hooks'; -import { useCurrentServer, useGeneralSettings } from '/@/renderer/store'; +import { useCurrentServer, useCurrentServerId, useGeneralSettings } from '/@/renderer/store'; import { Spinner } from '/@/shared/components/spinner/spinner'; -import { LibraryItem } from '/@/shared/types/domain-types'; +import { AlbumListSort, LibraryItem, SortOrder } from '/@/shared/types/domain-types'; const AlbumArtistDetailRouteContent = () => { const scrollAreaRef = useRef(null); const headerRef = useRef(null); const server = useCurrentServer(); + const serverId = useCurrentServerId(); const { artistBackground, artistBackgroundBlur } = useGeneralSettings(); const { albumArtistId, artistId } = useParams() as { @@ -33,9 +35,21 @@ const AlbumArtistDetailRouteContent = () => { const routeId = (artistId || albumArtistId) as string; - const detailQuery = useSuspenseQuery( - artistsQueries.albumArtistDetail({ query: { id: routeId }, serverId: server?.id }), - ); + const [detailQuery, albumsQuery] = useSuspenseQueries({ + queries: [ + artistsQueries.albumArtistDetail({ query: { id: routeId }, serverId: server?.id }), + albumQueries.list({ + query: { + artistIds: [routeId], + limit: -1, + sortBy: AlbumListSort.RELEASE_DATE, + sortOrder: SortOrder.DESC, + startIndex: 0, + }, + serverId, + }), + ], + }); const imageUrl = useItemImageUrl({ id: detailQuery.data?.imageId || undefined, @@ -106,7 +120,7 @@ const AlbumArtistDetailRouteContent = () => { )} } /> - +