mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
optimize artist page load speed
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 (
|
||||
<Suspense fallback={null}>
|
||||
{canStartQuery ? (
|
||||
<AlbumArtistMetadataTopSongsContent detailQuery={detailQuery} routeId={routeId} />
|
||||
) : null}
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
interface AlbumArtistMetadataExternalLinksProps {
|
||||
artistName?: string;
|
||||
externalLinks: boolean;
|
||||
@@ -543,7 +580,15 @@ const AlbumArtistMetadataSimilarArtists = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const AlbumArtistDetailContent = () => {
|
||||
interface AlbumArtistDetailContentProps {
|
||||
albumsQuery: UseSuspenseQueryResult<AlbumListResponse, Error>;
|
||||
detailQuery: UseSuspenseQueryResult<AlbumArtistDetailResponse, Error>;
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
</Grid.Col>
|
||||
)}
|
||||
<Grid.Col order={itemOrder.recentAlbums} span={12}>
|
||||
<ArtistAlbums />
|
||||
<ArtistAlbums albumsQuery={albumsQuery} />
|
||||
</Grid.Col>
|
||||
{enabledItem.similarArtists && (
|
||||
<Grid.Col order={itemOrder.similarArtists} span={12}>
|
||||
@@ -1020,10 +1058,13 @@ const releaseTypeToEnumMap: Record<string, ArtistReleaseTypeItem> = {
|
||||
spokenword: ArtistReleaseTypeItem.RELEASE_TYPE_SPOKENWORD,
|
||||
};
|
||||
|
||||
const ArtistAlbums = () => {
|
||||
interface ArtistAlbumsProps {
|
||||
albumsQuery: UseSuspenseQueryResult<AlbumListResponse, Error>;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
const headerRef = useRef<HTMLDivElement>(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 = () => {
|
||||
)}
|
||||
<LibraryContainer>
|
||||
<AlbumArtistDetailHeader ref={headerRef as React.Ref<HTMLDivElement>} />
|
||||
<AlbumArtistDetailContent />
|
||||
<AlbumArtistDetailContent albumsQuery={albumsQuery} detailQuery={detailQuery} />
|
||||
</LibraryContainer>
|
||||
</NativeScrollArea>
|
||||
</AnimatedPage>
|
||||
|
||||
Reference in New Issue
Block a user