mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-24 21:07:41 +02:00
optimize artist page load speed
This commit is contained in:
@@ -218,7 +218,8 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
throw new Error('No userId found');
|
throw new Error('No userId found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await jfApiClient(apiClientProps).getAlbumArtistDetail({
|
const [res, similarArtistsRes] = await Promise.all([
|
||||||
|
jfApiClient(apiClientProps).getAlbumArtistDetail({
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
userId: apiClientProps.server?.userId,
|
userId: apiClientProps.server?.userId,
|
||||||
@@ -226,16 +227,16 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
query: {
|
query: {
|
||||||
Fields: 'Genres, Overview',
|
Fields: 'Genres, Overview',
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
|
jfApiClient(apiClientProps).getSimilarArtistList({
|
||||||
const similarArtistsRes = await jfApiClient(apiClientProps).getSimilarArtistList({
|
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
Limit: 10,
|
Limit: 10,
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
if (res.status !== 200 || similarArtistsRes.status !== 200) {
|
if (res.status !== 200 || similarArtistsRes.status !== 200) {
|
||||||
throw new Error('Failed to get album artist detail');
|
throw new Error('Failed to get album artist detail');
|
||||||
|
|||||||
@@ -184,18 +184,19 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
getAlbumArtistDetail: async (args) => {
|
getAlbumArtistDetail: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const res = await ndApiClient(apiClientProps).getAlbumArtistDetail({
|
const [res, artistInfoRes] = await Promise.all([
|
||||||
|
ndApiClient(apiClientProps).getAlbumArtistDetail({
|
||||||
params: {
|
params: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
|
ssApiClient(apiClientProps).getArtistInfo({
|
||||||
const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({
|
|
||||||
query: {
|
query: {
|
||||||
count: 10,
|
count: 10,
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error('Failed to get album artist detail');
|
throw new Error('Failed to get album artist detail');
|
||||||
|
|||||||
@@ -256,17 +256,18 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
getAlbumArtistDetail: async (args) => {
|
getAlbumArtistDetail: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({
|
const [artistInfoRes, res] = await Promise.all([
|
||||||
|
ssApiClient(apiClientProps).getArtistInfo({
|
||||||
query: {
|
query: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
|
ssApiClient(apiClientProps).getArtist({
|
||||||
const res = await ssApiClient(apiClientProps).getArtist({
|
|
||||||
query: {
|
query: {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error('Failed to get album artist detail');
|
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 { LayoutGroup, motion } from 'motion/react';
|
||||||
|
import { Suspense } from 'react';
|
||||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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';
|
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 { 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 { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
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 { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { AlbumArtistGridCarousel } from '/@/renderer/features/artists/components/album-artist-grid-carousel';
|
import { AlbumArtistGridCarousel } from '/@/renderer/features/artists/components/album-artist-grid-carousel';
|
||||||
import { useIsPlayerFetching, usePlayer } from '/@/renderer/features/player/context/player-context';
|
import { useIsPlayerFetching, usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
@@ -67,6 +72,7 @@ import {
|
|||||||
Album,
|
Album,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
AlbumArtistDetailResponse,
|
AlbumArtistDetailResponse,
|
||||||
|
AlbumListResponse,
|
||||||
AlbumListSort,
|
AlbumListSort,
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
RelatedArtist,
|
RelatedArtist,
|
||||||
@@ -214,7 +220,7 @@ interface AlbumArtistMetadataTopSongsProps {
|
|||||||
routeId: string;
|
routeId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlbumArtistMetadataTopSongs = ({
|
const AlbumArtistMetadataTopSongsContent = ({
|
||||||
detailQuery,
|
detailQuery,
|
||||||
routeId,
|
routeId,
|
||||||
}: AlbumArtistMetadataTopSongsProps) => {
|
}: AlbumArtistMetadataTopSongsProps) => {
|
||||||
@@ -226,15 +232,22 @@ const AlbumArtistMetadataTopSongs = ({
|
|||||||
const currentSong = usePlayerSong();
|
const currentSong = usePlayerSong();
|
||||||
const player = usePlayer();
|
const player = usePlayer();
|
||||||
const serverId = useCurrentServerId();
|
const serverId = useCurrentServerId();
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const topSongsQuery = useSuspenseQuery(
|
const canStartQuery = server?.type === ServerType.JELLYFIN || !!detailQuery.data?.name;
|
||||||
artistsQueries.topSongs({
|
|
||||||
query: { artist: detailQuery.data?.name || '', artistId: routeId },
|
const topSongsQuery = useQuery({
|
||||||
|
...artistsQueries.topSongs({
|
||||||
|
query: {
|
||||||
|
artist: detailQuery.data?.name || '',
|
||||||
|
artistId: routeId,
|
||||||
|
},
|
||||||
serverId: serverId,
|
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(() => {
|
const columns = useMemo(() => {
|
||||||
return tableConfig?.columns || [];
|
return tableConfig?.columns || [];
|
||||||
@@ -274,6 +287,10 @@ const AlbumArtistMetadataTopSongs = ({
|
|||||||
};
|
};
|
||||||
}, [player]);
|
}, [player]);
|
||||||
|
|
||||||
|
if (topSongsQuery.isLoading || !topSongsQuery.data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!topSongsQuery?.data?.items?.length) return null;
|
if (!topSongsQuery?.data?.items?.length) return null;
|
||||||
|
|
||||||
if (!tableConfig || columns.length === 0) {
|
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 {
|
interface AlbumArtistMetadataExternalLinksProps {
|
||||||
artistName?: string;
|
artistName?: string;
|
||||||
externalLinks: boolean;
|
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 } =
|
const { artistItems, artistRadioCount, externalLinks, lastFM, musicBrainz } =
|
||||||
useGeneralSettings();
|
useGeneralSettings();
|
||||||
const { albumArtistId, artistId } = useParams() as {
|
const { albumArtistId, artistId } = useParams() as {
|
||||||
@@ -567,13 +612,6 @@ export const AlbumArtistDetailContent = () => {
|
|||||||
return [enabled, order];
|
return [enabled, order];
|
||||||
}, [artistItems]);
|
}, [artistItems]);
|
||||||
|
|
||||||
const detailQuery = useSuspenseQuery(
|
|
||||||
artistsQueries.albumArtistDetail({
|
|
||||||
query: { id: routeId },
|
|
||||||
serverId: server?.id,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const artistDiscographyLink = useMemo(
|
const artistDiscographyLink = useMemo(
|
||||||
() =>
|
() =>
|
||||||
`${generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY, {
|
`${generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY, {
|
||||||
@@ -662,7 +700,7 @@ export const AlbumArtistDetailContent = () => {
|
|||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
)}
|
)}
|
||||||
<Grid.Col order={itemOrder.recentAlbums} span={12}>
|
<Grid.Col order={itemOrder.recentAlbums} span={12}>
|
||||||
<ArtistAlbums />
|
<ArtistAlbums albumsQuery={albumsQuery} />
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
{enabledItem.similarArtists && (
|
{enabledItem.similarArtists && (
|
||||||
<Grid.Col order={itemOrder.similarArtists} span={12}>
|
<Grid.Col order={itemOrder.similarArtists} span={12}>
|
||||||
@@ -1020,10 +1058,13 @@ const releaseTypeToEnumMap: Record<string, ArtistReleaseTypeItem> = {
|
|||||||
spokenword: ArtistReleaseTypeItem.RELEASE_TYPE_SPOKENWORD,
|
spokenword: ArtistReleaseTypeItem.RELEASE_TYPE_SPOKENWORD,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ArtistAlbums = () => {
|
interface ArtistAlbumsProps {
|
||||||
|
albumsQuery: UseSuspenseQueryResult<AlbumListResponse, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { artistReleaseTypeItems } = useGeneralSettings();
|
const { artistReleaseTypeItems } = useGeneralSettings();
|
||||||
const serverId = useCurrentServerId();
|
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300);
|
const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300);
|
||||||
const albumArtistDetailSort = useAppStore((state) => state.albumArtistDetailSort);
|
const albumArtistDetailSort = useAppStore((state) => state.albumArtistDetailSort);
|
||||||
@@ -1038,19 +1079,6 @@ const ArtistAlbums = () => {
|
|||||||
};
|
};
|
||||||
const routeId = (artistId || albumArtistId) as string;
|
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 rows = useGridRows(LibraryItem.ALBUM, ItemListKey.ALBUM);
|
||||||
const controls = useDefaultItemListControls();
|
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 { Suspense, useRef } from 'react';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||||
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
|
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 { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content';
|
import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content';
|
||||||
import { AlbumArtistDetailHeader } from '/@/renderer/features/artists/components/album-artist-detail-header';
|
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 { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar';
|
||||||
import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary';
|
import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary';
|
||||||
import { useFastAverageColor, useWaitForColorCalculation } from '/@/renderer/hooks';
|
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 { 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 AlbumArtistDetailRouteContent = () => {
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||||
const headerRef = useRef<HTMLDivElement>(null);
|
const headerRef = useRef<HTMLDivElement>(null);
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
const serverId = useCurrentServerId();
|
||||||
const { artistBackground, artistBackgroundBlur } = useGeneralSettings();
|
const { artistBackground, artistBackgroundBlur } = useGeneralSettings();
|
||||||
|
|
||||||
const { albumArtistId, artistId } = useParams() as {
|
const { albumArtistId, artistId } = useParams() as {
|
||||||
@@ -33,9 +35,21 @@ const AlbumArtistDetailRouteContent = () => {
|
|||||||
|
|
||||||
const routeId = (artistId || albumArtistId) as string;
|
const routeId = (artistId || albumArtistId) as string;
|
||||||
|
|
||||||
const detailQuery = useSuspenseQuery(
|
const [detailQuery, albumsQuery] = useSuspenseQueries({
|
||||||
|
queries: [
|
||||||
artistsQueries.albumArtistDetail({ query: { id: routeId }, serverId: server?.id }),
|
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({
|
const imageUrl = useItemImageUrl({
|
||||||
id: detailQuery.data?.imageId || undefined,
|
id: detailQuery.data?.imageId || undefined,
|
||||||
@@ -106,7 +120,7 @@ const AlbumArtistDetailRouteContent = () => {
|
|||||||
)}
|
)}
|
||||||
<LibraryContainer>
|
<LibraryContainer>
|
||||||
<AlbumArtistDetailHeader ref={headerRef as React.Ref<HTMLDivElement>} />
|
<AlbumArtistDetailHeader ref={headerRef as React.Ref<HTMLDivElement>} />
|
||||||
<AlbumArtistDetailContent />
|
<AlbumArtistDetailContent albumsQuery={albumsQuery} detailQuery={detailQuery} />
|
||||||
</LibraryContainer>
|
</LibraryContainer>
|
||||||
</NativeScrollArea>
|
</NativeScrollArea>
|
||||||
</AnimatedPage>
|
</AnimatedPage>
|
||||||
|
|||||||
Reference in New Issue
Block a user