mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
decouple AlbumArtistInfo from AlbumArtistDetail (#1809)
This commit is contained in:
@@ -200,6 +200,18 @@ export const controller: GeneralController = {
|
|||||||
server.type,
|
server.type,
|
||||||
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
|
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
|
||||||
},
|
},
|
||||||
|
getAlbumArtistInfo(args) {
|
||||||
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn = apiController('getAlbumArtistInfo', server.type);
|
||||||
|
return fn
|
||||||
|
? fn(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }))
|
||||||
|
: Promise.resolve(null);
|
||||||
|
},
|
||||||
getAlbumArtistList(args) {
|
getAlbumArtistList(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
|
|
||||||
|
|||||||
@@ -258,34 +258,54 @@ export const JellyfinController: InternalControllerEndpoint = {
|
|||||||
throw new Error('No userId found');
|
throw new Error('No userId found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const [res, similarArtistsRes] = await Promise.all([
|
const res = await jfApiClient(apiClientProps).getAlbumArtistDetail({
|
||||||
jfApiClient(apiClientProps).getAlbumArtistDetail({
|
params: {
|
||||||
params: {
|
id: query.id,
|
||||||
id: query.id,
|
userId: apiClientProps.server?.userId,
|
||||||
userId: apiClientProps.server?.userId,
|
},
|
||||||
},
|
query: {
|
||||||
query: {
|
Fields: ['Genres', 'Overview', 'SortName'],
|
||||||
Fields: JF_FIELDS.ALBUM_ARTIST_DETAIL,
|
},
|
||||||
},
|
});
|
||||||
}),
|
|
||||||
jfApiClient(apiClientProps).getSimilarArtistList({
|
|
||||||
params: {
|
|
||||||
id: query.id,
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
Limit: 10,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (res.status !== 200 || similarArtistsRes.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error('Failed to get album artist detail');
|
throw new Error('Failed to get album artist detail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return jfNormalize.albumArtist(
|
return jfNormalize.albumArtist(res.body, apiClientProps.server);
|
||||||
{ ...res.body, similarArtists: similarArtistsRes.body },
|
},
|
||||||
apiClientProps.server,
|
getAlbumArtistInfo: async (args) => {
|
||||||
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
|
const similarArtistsRes = await jfApiClient(apiClientProps).getSimilarArtistList({
|
||||||
|
params: {
|
||||||
|
id: query.id,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
Limit: query.limit ?? 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (similarArtistsRes.status !== 200) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = similarArtistsRes.body?.Items?.filter(
|
||||||
|
(entry) => entry.Name !== 'Various Artists',
|
||||||
);
|
);
|
||||||
|
const similarArtists =
|
||||||
|
items?.map((entry) => ({
|
||||||
|
id: entry.Id,
|
||||||
|
imageId: entry.ImageTags?.Primary ? entry.Id : null,
|
||||||
|
imageUrl: null,
|
||||||
|
name: entry.Name,
|
||||||
|
userFavorite: entry.UserData?.IsFavorite || false,
|
||||||
|
userRating: null,
|
||||||
|
})) ?? null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
similarArtists,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
getAlbumArtistList: async (args) => {
|
getAlbumArtistList: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|||||||
@@ -190,19 +190,11 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
getAlbumArtistDetail: async (args) => {
|
getAlbumArtistDetail: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const [res, artistInfoRes] = await Promise.all([
|
const res = await ndApiClient(apiClientProps).getAlbumArtistDetail({
|
||||||
ndApiClient(apiClientProps).getAlbumArtistDetail({
|
params: {
|
||||||
params: {
|
id: query.id,
|
||||||
id: query.id,
|
},
|
||||||
},
|
});
|
||||||
}),
|
|
||||||
ssApiClient(apiClientProps).getArtistInfo({
|
|
||||||
query: {
|
|
||||||
count: 10,
|
|
||||||
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');
|
||||||
@@ -212,22 +204,42 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
throw new Error('Server is required');
|
throw new Error('Server is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer images from getArtistInfo first (which should be proxied)
|
return ndNormalize.albumArtist(res.body.data, apiClientProps.server);
|
||||||
// Prioritize large > medium > small
|
},
|
||||||
return ndNormalize.albumArtist(
|
getAlbumArtistInfo: async (args) => {
|
||||||
{
|
const { apiClientProps, query } = args;
|
||||||
...res.body.data,
|
|
||||||
...(artistInfoRes.status === 200 && {
|
const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({
|
||||||
largeImageUrl:
|
query: {
|
||||||
artistInfoRes.body.artistInfo.largeImageUrl ||
|
id: query.id,
|
||||||
artistInfoRes.body.artistInfo.mediumImageUrl ||
|
...(query.limit != null && { count: query.limit }),
|
||||||
artistInfoRes.body.artistInfo.smallImageUrl ||
|
|
||||||
res.body.data.largeImageUrl,
|
|
||||||
similarArtists: artistInfoRes.body.artistInfo.similarArtist,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
apiClientProps.server,
|
});
|
||||||
);
|
|
||||||
|
if (artistInfoRes.status !== 200) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const artistInfo = artistInfoRes.body.artistInfo;
|
||||||
|
const imageUrl =
|
||||||
|
artistInfo?.largeImageUrl ||
|
||||||
|
artistInfo?.mediumImageUrl ||
|
||||||
|
artistInfo?.smallImageUrl ||
|
||||||
|
null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
biography: artistInfo?.biography || null,
|
||||||
|
imageUrl,
|
||||||
|
similarArtists:
|
||||||
|
artistInfo?.similarArtist?.map((artist) => ({
|
||||||
|
id: artist.id,
|
||||||
|
imageId: null,
|
||||||
|
imageUrl: artist?.artistImageUrl?.replace(/\?size=\d+/, '') ?? null,
|
||||||
|
name: artist.name,
|
||||||
|
userFavorite: Boolean(artist.starred) || false,
|
||||||
|
userRating: artist.userRating ?? null,
|
||||||
|
})) ?? null,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
getAlbumArtistList: async (args) => {
|
getAlbumArtistList: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
AlbumArtistDetailQuery,
|
AlbumArtistDetailQuery,
|
||||||
|
AlbumArtistInfoQuery,
|
||||||
AlbumArtistListQuery,
|
AlbumArtistListQuery,
|
||||||
AlbumDetailQuery,
|
AlbumDetailQuery,
|
||||||
AlbumListQuery,
|
AlbumListQuery,
|
||||||
@@ -93,6 +94,13 @@ export const queryKeys: Record<
|
|||||||
|
|
||||||
return [serverId, 'albumArtists', 'infiniteList'] as const;
|
return [serverId, 'albumArtists', 'infiniteList'] as const;
|
||||||
},
|
},
|
||||||
|
info: (serverId: string, query?: AlbumArtistInfoQuery) => {
|
||||||
|
if (query) {
|
||||||
|
return [serverId, 'albumArtists', 'info', query] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [serverId, 'albumArtists', 'info'] as const;
|
||||||
|
},
|
||||||
list: (serverId: string, query?: AlbumArtistListQuery) => {
|
list: (serverId: string, query?: AlbumArtistListQuery) => {
|
||||||
const { filter, pagination } = splitPaginatedQuery(query);
|
const { filter, pagination } = splitPaginatedQuery(query);
|
||||||
if (query && pagination) {
|
if (query && pagination) {
|
||||||
|
|||||||
@@ -258,18 +258,11 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
getAlbumArtistDetail: async (args) => {
|
getAlbumArtistDetail: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const [artistInfoRes, res] = await Promise.all([
|
const res = await ssApiClient(apiClientProps).getArtist({
|
||||||
ssApiClient(apiClientProps).getArtistInfo({
|
query: {
|
||||||
query: {
|
id: query.id,
|
||||||
id: query.id,
|
},
|
||||||
},
|
});
|
||||||
}),
|
|
||||||
ssApiClient(apiClientProps).getArtist({
|
|
||||||
query: {
|
|
||||||
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');
|
||||||
@@ -277,11 +270,6 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
|
|
||||||
const artist = res.body.artist;
|
const artist = res.body.artist;
|
||||||
|
|
||||||
let artistInfo;
|
|
||||||
if (artistInfoRes.status === 200) {
|
|
||||||
artistInfo = artistInfoRes.body.artistInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...ssNormalize.albumArtist(artist, apiClientProps.server),
|
...ssNormalize.albumArtist(artist, apiClientProps.server),
|
||||||
albums: artist.album?.map((album) =>
|
albums: artist.album?.map((album) =>
|
||||||
@@ -292,10 +280,36 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
args.context?.pathReplaceWith,
|
args.context?.pathReplaceWith,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
similarArtists: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getAlbumArtistInfo: async (args) => {
|
||||||
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
|
const artistInfoRes = await ssApiClient(apiClientProps).getArtistInfo({
|
||||||
|
query: {
|
||||||
|
id: query.id,
|
||||||
|
...(query.limit != null && { count: query.limit }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (artistInfoRes.status !== 200) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const artistInfo = artistInfoRes.body.artistInfo;
|
||||||
|
|
||||||
|
return {
|
||||||
|
biography: artistInfo?.biography || null,
|
||||||
similarArtists:
|
similarArtists:
|
||||||
artistInfo?.similarArtist?.map((artist) =>
|
artistInfo?.similarArtist?.map((artist) => ({
|
||||||
ssNormalize.albumArtist(artist, apiClientProps.server),
|
id: artist.id,
|
||||||
) || null,
|
imageId: null,
|
||||||
|
imageUrl: null,
|
||||||
|
name: artist.name,
|
||||||
|
userFavorite: Boolean(artist.starred) || false,
|
||||||
|
userRating: artist.userRating ?? null,
|
||||||
|
})) ?? null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getAlbumArtistList: async (args) => {
|
getAlbumArtistList: async (args) => {
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ interface GridCarouselSkeletonProps {
|
|||||||
containerQuery?: ReturnType<typeof useGridCarouselContainerQuery>;
|
containerQuery?: ReturnType<typeof useGridCarouselContainerQuery>;
|
||||||
enableRefresh?: boolean;
|
enableRefresh?: boolean;
|
||||||
placeholderItemType: LibraryItem;
|
placeholderItemType: LibraryItem;
|
||||||
|
placeholderRound?: boolean;
|
||||||
placeholderRows: DataRow[];
|
placeholderRows: DataRow[];
|
||||||
rowCount?: number;
|
rowCount?: number;
|
||||||
title?: ReactNode | string;
|
title?: ReactNode | string;
|
||||||
@@ -375,12 +376,15 @@ const GridCarouselSkeleton = (props: GridCarouselSkeletonProps) => {
|
|||||||
containerQuery: providedContainerQuery,
|
containerQuery: providedContainerQuery,
|
||||||
enableRefresh = false,
|
enableRefresh = false,
|
||||||
placeholderItemType,
|
placeholderItemType,
|
||||||
|
placeholderRound = false,
|
||||||
placeholderRows,
|
placeholderRows,
|
||||||
rowCount = 1,
|
rowCount = 1,
|
||||||
title,
|
title,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { ...cq } = providedContainerQuery;
|
const defaultContainerQuery = useGridCarouselContainerQuery();
|
||||||
|
const containerQuery = providedContainerQuery ?? defaultContainerQuery;
|
||||||
|
const { ...cq } = containerQuery;
|
||||||
|
|
||||||
const cardsToShow = cq.isCalculated
|
const cardsToShow = cq.isCalculated
|
||||||
? getCardsToShow({
|
? getCardsToShow({
|
||||||
@@ -399,6 +403,7 @@ const GridCarouselSkeleton = (props: GridCarouselSkeletonProps) => {
|
|||||||
content: (
|
content: (
|
||||||
<MemoizedItemCard
|
<MemoizedItemCard
|
||||||
data={undefined}
|
data={undefined}
|
||||||
|
isRound={placeholderRound}
|
||||||
itemType={placeholderItemType}
|
itemType={placeholderItemType}
|
||||||
rows={placeholderRows}
|
rows={placeholderRows}
|
||||||
type="poster"
|
type="poster"
|
||||||
@@ -406,12 +411,12 @@ const GridCarouselSkeleton = (props: GridCarouselSkeletonProps) => {
|
|||||||
),
|
),
|
||||||
id: `skeleton-${index}`,
|
id: `skeleton-${index}`,
|
||||||
}));
|
}));
|
||||||
}, [cardsToShow, rowCount, placeholderItemType, placeholderRows]);
|
}, [cardsToShow, placeholderRound, rowCount, placeholderItemType, placeholderRows]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridCarousel
|
<GridCarousel
|
||||||
cards={placeholderCards}
|
cards={placeholderCards}
|
||||||
containerQuery={providedContainerQuery}
|
containerQuery={containerQuery}
|
||||||
enableRefresh={enableRefresh}
|
enableRefresh={enableRefresh}
|
||||||
hasNextPage={false}
|
hasNextPage={false}
|
||||||
isFetchingNextPage={false}
|
isFetchingNextPage={false}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { getOptimizedListCount } from '/@/renderer/api/utils-list-count';
|
|||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
import {
|
import {
|
||||||
AlbumArtistDetailQuery,
|
AlbumArtistDetailQuery,
|
||||||
|
AlbumArtistInfoQuery,
|
||||||
AlbumArtistListQuery,
|
AlbumArtistListQuery,
|
||||||
ArtistListQuery,
|
ArtistListQuery,
|
||||||
ListCountQuery,
|
ListCountQuery,
|
||||||
@@ -28,6 +29,20 @@ export const artistsQueries = {
|
|||||||
...args.options,
|
...args.options,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
albumArtistInfo: (args: QueryHookArgs<AlbumArtistInfoQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
return (
|
||||||
|
api.controller.getAlbumArtistInfo?.({
|
||||||
|
apiClientProps: { serverId: args.serverId, signal },
|
||||||
|
query: args.query,
|
||||||
|
}) ?? Promise.resolve(null)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.albumArtists.info(args.serverId, args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
albumArtistList: (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
albumArtistList: (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
||||||
return queryOptions({
|
return queryOptions({
|
||||||
queryFn: ({ signal }) => {
|
queryFn: ({ signal }) => {
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import {
|
|||||||
UseSuspenseQueryResult,
|
UseSuspenseQueryResult,
|
||||||
} from '@tanstack/react-query';
|
} from '@tanstack/react-query';
|
||||||
import { LayoutGroup, motion } from 'motion/react';
|
import { LayoutGroup, motion } from 'motion/react';
|
||||||
import { Suspense } from 'react';
|
import { Suspense, 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, useLocation, useParams } from 'react-router';
|
import { createSearchParams, generatePath, Link, useLocation, useParams } from 'react-router';
|
||||||
|
|
||||||
@@ -70,6 +69,7 @@ import { Grid } from '/@/shared/components/grid/grid';
|
|||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { Icon } from '/@/shared/components/icon/icon';
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
||||||
|
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
import { Spoiler } from '/@/shared/components/spoiler/spoiler';
|
import { Spoiler } from '/@/shared/components/spoiler/spoiler';
|
||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
@@ -201,18 +201,57 @@ const AlbumArtistMetadataGenres = ({ genres }: AlbumArtistMetadataGenresProps) =
|
|||||||
|
|
||||||
interface AlbumArtistMetadataBiographyProps {
|
interface AlbumArtistMetadataBiographyProps {
|
||||||
artistName?: string;
|
artistName?: string;
|
||||||
biography: null | string | undefined;
|
routeId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlbumArtistMetadataBiography = ({
|
const AlbumArtistMetadataBiography = ({
|
||||||
artistName,
|
artistName,
|
||||||
biography,
|
routeId,
|
||||||
}: AlbumArtistMetadataBiographyProps) => {
|
}: AlbumArtistMetadataBiographyProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
if (!biography) return null;
|
const artistInfoQuery = useQuery({
|
||||||
|
...artistsQueries.albumArtistInfo({
|
||||||
|
query: { id: routeId, limit: 10 },
|
||||||
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
enabled: Boolean(server?.id && routeId),
|
||||||
|
});
|
||||||
|
|
||||||
const sanitizedBiography = sanitize(biography);
|
const detailQuery = useQuery({
|
||||||
|
...artistsQueries.albumArtistDetail({
|
||||||
|
query: { id: routeId },
|
||||||
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
enabled: Boolean(server?.id && routeId),
|
||||||
|
});
|
||||||
|
|
||||||
|
const biography = artistInfoQuery.data?.biography || detailQuery.data?.biography;
|
||||||
|
const isLoading = !biography && (artistInfoQuery.isLoading || detailQuery.isLoading);
|
||||||
|
|
||||||
|
const sanitizedBiography = biography ? sanitize(biography) : '';
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<section style={{ maxWidth: '1280px' }}>
|
||||||
|
<TextTitle fw={700} order={3}>
|
||||||
|
{t('page.albumArtistDetail.about', {
|
||||||
|
artist: artistName,
|
||||||
|
})}
|
||||||
|
</TextTitle>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Skeleton enableAnimation height="1rem" width="100%" />
|
||||||
|
<Skeleton enableAnimation height="1rem" width="98%" />
|
||||||
|
<Skeleton enableAnimation height="1rem" width="60%" />
|
||||||
|
</Stack>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!biography) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section style={{ maxWidth: '1280px' }}>
|
<section style={{ maxWidth: '1280px' }}>
|
||||||
@@ -850,20 +889,25 @@ const AlbumArtistMetadataExternalLinks = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface AlbumArtistMetadataSimilarArtistsProps {
|
interface AlbumArtistMetadataSimilarArtistsProps {
|
||||||
detailQuery: ReturnType<typeof useSuspenseQuery<AlbumArtistDetailResponse>>;
|
|
||||||
routeId: string;
|
routeId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlbumArtistMetadataSimilarArtists = ({
|
const AlbumArtistMetadataSimilarArtists = ({ routeId }: AlbumArtistMetadataSimilarArtistsProps) => {
|
||||||
detailQuery,
|
|
||||||
routeId,
|
|
||||||
}: AlbumArtistMetadataSimilarArtistsProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const serverId = useCurrentServerId();
|
const serverId = useCurrentServerId();
|
||||||
|
|
||||||
|
const artistInfoQuery = useQuery({
|
||||||
|
...artistsQueries.albumArtistInfo({
|
||||||
|
query: { id: routeId, limit: 10 },
|
||||||
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
enabled: Boolean(server?.id && routeId),
|
||||||
|
});
|
||||||
|
|
||||||
|
const relatedArtists = artistInfoQuery.data?.similarArtists ?? null;
|
||||||
|
|
||||||
const similarArtists = useMemo(() => {
|
const similarArtists = useMemo(() => {
|
||||||
const relatedArtists = detailQuery.data?.similarArtists;
|
|
||||||
if (!relatedArtists || relatedArtists.length === 0) {
|
if (!relatedArtists || relatedArtists.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -890,7 +934,7 @@ const AlbumArtistMetadataSimilarArtists = ({
|
|||||||
userRating: relatedArtist.userRating,
|
userRating: relatedArtist.userRating,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}, [detailQuery.data?.similarArtists, server?.type, serverId]);
|
}, [relatedArtists, server?.type, serverId]);
|
||||||
|
|
||||||
const carouselTitle = useMemo(
|
const carouselTitle = useMemo(
|
||||||
() => (
|
() => (
|
||||||
@@ -908,7 +952,7 @@ const AlbumArtistMetadataSimilarArtists = ({
|
|||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (similarArtists.length === 0) {
|
if (!artistInfoQuery.isLoading && similarArtists.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -916,6 +960,7 @@ const AlbumArtistMetadataSimilarArtists = ({
|
|||||||
<AlbumArtistGridCarousel
|
<AlbumArtistGridCarousel
|
||||||
data={similarArtists}
|
data={similarArtists}
|
||||||
excludeIds={[routeId]}
|
excludeIds={[routeId]}
|
||||||
|
isLoading={artistInfoQuery.isLoading}
|
||||||
rowCount={1}
|
rowCount={1}
|
||||||
title={carouselTitle}
|
title={carouselTitle}
|
||||||
/>
|
/>
|
||||||
@@ -977,8 +1022,6 @@ export const AlbumArtistDetailContent = ({
|
|||||||
[routeId, detailQuery.data?.name],
|
[routeId, detailQuery.data?.name],
|
||||||
);
|
);
|
||||||
|
|
||||||
const biography =
|
|
||||||
detailQuery.data?.biography && enabledItem.biography ? detailQuery.data.biography : null;
|
|
||||||
const showGenres = detailQuery.data?.genres ? detailQuery.data.genres.length !== 0 : false;
|
const showGenres = detailQuery.data?.genres ? detailQuery.data.genres.length !== 0 : false;
|
||||||
const mbzId = detailQuery.data?.mbz;
|
const mbzId = detailQuery.data?.mbz;
|
||||||
|
|
||||||
@@ -1034,11 +1077,11 @@ export const AlbumArtistDetailContent = ({
|
|||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
)}
|
)}
|
||||||
{biography && (
|
{enabledItem.biography && (
|
||||||
<Grid.Col order={itemOrder.biography} span={12}>
|
<Grid.Col order={itemOrder.biography} span={12}>
|
||||||
<AlbumArtistMetadataBiography
|
<AlbumArtistMetadataBiography
|
||||||
artistName={detailQuery.data?.name}
|
artistName={detailQuery.data?.name}
|
||||||
biography={biography}
|
routeId={routeId}
|
||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
)}
|
)}
|
||||||
@@ -1047,10 +1090,7 @@ export const AlbumArtistDetailContent = ({
|
|||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
{enabledItem.similarArtists && (
|
{enabledItem.similarArtists && (
|
||||||
<Grid.Col order={itemOrder.similarArtists} span={12}>
|
<Grid.Col order={itemOrder.similarArtists} span={12}>
|
||||||
<AlbumArtistMetadataSimilarArtists
|
<AlbumArtistMetadataSimilarArtists routeId={routeId} />
|
||||||
detailQuery={detailQuery}
|
|
||||||
routeId={routeId}
|
|
||||||
/>
|
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
)}
|
)}
|
||||||
{enabledItem.topSongs && (
|
{enabledItem.topSongs && (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { forwardRef, Fragment, Ref, useCallback, useMemo } from 'react';
|
import { forwardRef, Fragment, Ref, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
@@ -69,9 +69,9 @@ export const AlbumArtistDetailHeader = forwardRef((_props, ref: Ref<HTMLDivEleme
|
|||||||
];
|
];
|
||||||
|
|
||||||
const { addToQueueByFetch } = usePlayer();
|
const { addToQueueByFetch } = usePlayer();
|
||||||
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
const setFavorite = useSetFavorite();
|
const setFavorite = useSetFavorite();
|
||||||
const setRating = useSetRating();
|
const setRating = useSetRating();
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
|
||||||
|
|
||||||
const handlePlay = useCallback(
|
const handlePlay = useCallback(
|
||||||
(type?: Play) => {
|
(type?: Play) => {
|
||||||
@@ -137,11 +137,19 @@ export const AlbumArtistDetailHeader = forwardRef((_props, ref: Ref<HTMLDivEleme
|
|||||||
type: 'itemCard',
|
type: 'itemCard',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const artistInfoQuery = useQuery({
|
||||||
|
...artistsQueries.albumArtistInfo({
|
||||||
|
query: { id: routeId, limit: 10 },
|
||||||
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
enabled: Boolean(server?.id && routeId),
|
||||||
|
});
|
||||||
|
|
||||||
const showRating = showRatings && detailQuery?.data?._serverType === ServerType.NAVIDROME;
|
const showRating = showRatings && detailQuery?.data?._serverType === ServerType.NAVIDROME;
|
||||||
|
|
||||||
const selectedImageUrl = useMemo(() => {
|
const selectedImageUrl = useMemo(() => {
|
||||||
return detailQuery.data?.imageUrl || imageUrl;
|
return detailQuery.data?.imageUrl || artistInfoQuery.data?.imageUrl || imageUrl;
|
||||||
}, [detailQuery.data?.imageUrl, imageUrl]);
|
}, [artistInfoQuery.data?.imageUrl, detailQuery.data?.imageUrl, imageUrl]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LibraryHeader
|
<LibraryHeader
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { GridCarousel } from '/@/renderer/components/grid-carousel/grid-carousel-v2';
|
import {
|
||||||
|
GridCarousel,
|
||||||
|
GridCarouselSkeletonFallback,
|
||||||
|
} from '/@/renderer/components/grid-carousel/grid-carousel-v2';
|
||||||
import { MemoizedItemCard } from '/@/renderer/components/item-card/item-card';
|
import { MemoizedItemCard } from '/@/renderer/components/item-card/item-card';
|
||||||
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
||||||
import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows';
|
import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows';
|
||||||
@@ -10,12 +13,13 @@ import { ItemListKey } from '/@/shared/types/types';
|
|||||||
interface AlbumArtistGridCarouselProps {
|
interface AlbumArtistGridCarouselProps {
|
||||||
data: AlbumArtist[];
|
data: AlbumArtist[];
|
||||||
excludeIds?: string[];
|
excludeIds?: string[];
|
||||||
|
isLoading?: boolean;
|
||||||
rowCount?: number;
|
rowCount?: number;
|
||||||
title: React.ReactNode | string;
|
title: React.ReactNode | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AlbumArtistGridCarousel(props: AlbumArtistGridCarouselProps) {
|
export function AlbumArtistGridCarousel(props: AlbumArtistGridCarouselProps) {
|
||||||
const { data, excludeIds, rowCount = 1, title } = props;
|
const { data, excludeIds, isLoading = false, rowCount = 1, title } = props;
|
||||||
const rows = useGridRows(LibraryItem.ALBUM_ARTIST, ItemListKey.ALBUM_ARTIST);
|
const rows = useGridRows(LibraryItem.ALBUM_ARTIST, ItemListKey.ALBUM_ARTIST);
|
||||||
const controls = useDefaultItemListControls();
|
const controls = useDefaultItemListControls();
|
||||||
|
|
||||||
@@ -41,6 +45,18 @@ export function AlbumArtistGridCarousel(props: AlbumArtistGridCarouselProps) {
|
|||||||
}));
|
}));
|
||||||
}, [data, excludeIds, controls, rows]);
|
}, [data, excludeIds, controls, rows]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<GridCarouselSkeletonFallback
|
||||||
|
placeholderItemType={LibraryItem.ALBUM_ARTIST}
|
||||||
|
placeholderRound
|
||||||
|
placeholderRows={rows}
|
||||||
|
rowCount={rowCount}
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const handleNextPage = () => {};
|
const handleNextPage = () => {};
|
||||||
const handlePrevPage = () => {};
|
const handlePrevPage = () => {};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useSuspenseQueries } from '@tanstack/react-query';
|
import { useQuery, useQueryClient, useSuspenseQueries } from '@tanstack/react-query';
|
||||||
import { Suspense, useRef } from 'react';
|
import { Suspense, useEffect, useRef } from 'react';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
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 { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
@@ -19,7 +20,12 @@ import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-e
|
|||||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||||
import { useArtistBackground, useCurrentServer, useCurrentServerId } from '/@/renderer/store';
|
import { useArtistBackground, useCurrentServer, useCurrentServerId } from '/@/renderer/store';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
import { AlbumListSort, LibraryItem, SortOrder } from '/@/shared/types/domain-types';
|
import {
|
||||||
|
AlbumArtistDetailResponse,
|
||||||
|
AlbumListSort,
|
||||||
|
LibraryItem,
|
||||||
|
SortOrder,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
const AlbumArtistDetailRouteContent = () => {
|
const AlbumArtistDetailRouteContent = () => {
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -27,6 +33,7 @@ const AlbumArtistDetailRouteContent = () => {
|
|||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const serverId = useCurrentServerId();
|
const serverId = useCurrentServerId();
|
||||||
const { artistBackground, artistBackgroundBlur } = useArtistBackground();
|
const { artistBackground, artistBackgroundBlur } = useArtistBackground();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { albumArtistId, artistId } = useParams() as {
|
const { albumArtistId, artistId } = useParams() as {
|
||||||
albumArtistId?: string;
|
albumArtistId?: string;
|
||||||
@@ -35,6 +42,26 @@ const AlbumArtistDetailRouteContent = () => {
|
|||||||
|
|
||||||
const routeId = (artistId || albumArtistId) as string;
|
const routeId = (artistId || albumArtistId) as string;
|
||||||
|
|
||||||
|
const artistInfoQuery = useQuery({
|
||||||
|
...artistsQueries.albumArtistInfo({
|
||||||
|
query: { id: routeId, limit: 10 },
|
||||||
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
enabled: Boolean(server?.id && routeId),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = artistInfoQuery.data;
|
||||||
|
if (!data?.imageUrl || !server?.id || !routeId) return;
|
||||||
|
queryClient.setQueryData(
|
||||||
|
queryKeys.albumArtists.detail(server.id, { id: routeId }),
|
||||||
|
(prev: AlbumArtistDetailResponse | undefined) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
return { ...prev, imageUrl: data.imageUrl ?? prev.imageUrl };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, [artistInfoQuery.data, queryClient, routeId, server?.id]);
|
||||||
|
|
||||||
const [detailQuery, albumsQuery] = useSuspenseQueries({
|
const [detailQuery, albumsQuery] = useSuspenseQueries({
|
||||||
queries: [
|
queries: [
|
||||||
artistsQueries.albumArtistDetail({ query: { id: routeId }, serverId: server?.id }),
|
artistsQueries.albumArtistDetail({ query: { id: routeId }, serverId: server?.id }),
|
||||||
@@ -60,13 +87,14 @@ const AlbumArtistDetailRouteContent = () => {
|
|||||||
|
|
||||||
const libraryBackgroundImageUrl = useItemImageUrl({
|
const libraryBackgroundImageUrl = useItemImageUrl({
|
||||||
id: detailQuery.data?.imageId || undefined,
|
id: detailQuery.data?.imageId || undefined,
|
||||||
|
imageUrl: detailQuery.data?.imageUrl,
|
||||||
itemType: LibraryItem.ALBUM_ARTIST,
|
itemType: LibraryItem.ALBUM_ARTIST,
|
||||||
type: 'itemCard',
|
type: 'itemCard',
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedImageUrl = imageUrl || detailQuery.data?.imageUrl;
|
const selectedImageUrl = imageUrl || detailQuery.data?.imageUrl;
|
||||||
|
|
||||||
const { background: backgroundColor, isLoading: isColorLoading } = useFastAverageColor({
|
const { background: backgroundColor } = useFastAverageColor({
|
||||||
id: artistId,
|
id: artistId,
|
||||||
src: selectedImageUrl,
|
src: selectedImageUrl,
|
||||||
srcLoaded: true,
|
srcLoaded: true,
|
||||||
@@ -76,9 +104,9 @@ const AlbumArtistDetailRouteContent = () => {
|
|||||||
|
|
||||||
const showBlurredImage = artistBackground;
|
const showBlurredImage = artistBackground;
|
||||||
|
|
||||||
if (isColorLoading) {
|
// if (isColorLoading) {
|
||||||
return <Spinner container />;
|
// return <Spinner container />;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPage key={`album-artist-detail-${routeId}`}>
|
<AnimatedPage key={`album-artist-detail-${routeId}`}>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Album,
|
Album,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
AlbumArtistDetailResponse,
|
AlbumArtistDetailResponse,
|
||||||
|
AlbumArtistInfoResponse,
|
||||||
AlbumArtistListResponse,
|
AlbumArtistListResponse,
|
||||||
AlbumDetailResponse,
|
AlbumDetailResponse,
|
||||||
AlbumListResponse,
|
AlbumListResponse,
|
||||||
@@ -240,29 +241,44 @@ export const applyFavoriteOptimisticUpdates = (
|
|||||||
return { ...prev, userFavorite: isFavorite };
|
return { ...prev, userFavorite: isFavorite };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev.similarArtists && prev.similarArtists.length > 0) {
|
|
||||||
const hasMatchingSimilarArtist = prev.similarArtists.some(
|
|
||||||
(artist) => itemIdSet.has(artist.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasMatchingSimilarArtist) {
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
similarArtists: prev.similarArtists.map((artist) =>
|
|
||||||
itemIdSet.has(artist.id)
|
|
||||||
? { ...artist, userFavorite: isFavorite }
|
|
||||||
: artist,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return prev;
|
return prev;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const infoQueryKey = queryKeys.albumArtists.info(variables.apiClientProps.serverId);
|
||||||
|
const infoQueries = queryClient.getQueriesData({
|
||||||
|
exact: false,
|
||||||
|
queryKey: infoQueryKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
infoQueries.forEach(([queryKey, data]) => {
|
||||||
|
if (data) {
|
||||||
|
pendingUpdates.push({
|
||||||
|
previousData: data,
|
||||||
|
queryKey,
|
||||||
|
updater: (prev: AlbumArtistInfoResponse | null | undefined) => {
|
||||||
|
if (!prev?.similarArtists?.length) return prev;
|
||||||
|
|
||||||
|
const hasMatching = prev.similarArtists.some((artist) =>
|
||||||
|
itemIdSet.has(artist.id),
|
||||||
|
);
|
||||||
|
if (!hasMatching) return prev;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
similarArtists: prev.similarArtists.map((artist) =>
|
||||||
|
itemIdSet.has(artist.id)
|
||||||
|
? { ...artist, userFavorite: isFavorite }
|
||||||
|
: artist,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const listQueryKey = queryKeys.albumArtists.list(variables.apiClientProps.serverId);
|
const listQueryKey = queryKeys.albumArtists.list(variables.apiClientProps.serverId);
|
||||||
const listQueries = queryClient.getQueriesData({
|
const listQueries = queryClient.getQueriesData({
|
||||||
exact: false,
|
exact: false,
|
||||||
@@ -654,6 +670,10 @@ export const applyFavoriteOptimisticUpdatesDeferred = (
|
|||||||
queryKeys.albumArtists.detail(variables.apiClientProps.serverId),
|
queryKeys.albumArtists.detail(variables.apiClientProps.serverId),
|
||||||
'album-artist-detail',
|
'album-artist-detail',
|
||||||
);
|
);
|
||||||
|
collectQueries(
|
||||||
|
queryKeys.albumArtists.info(variables.apiClientProps.serverId),
|
||||||
|
'album-artist-info',
|
||||||
|
);
|
||||||
collectQueries(
|
collectQueries(
|
||||||
queryKeys.albumArtists.list(variables.apiClientProps.serverId),
|
queryKeys.albumArtists.list(variables.apiClientProps.serverId),
|
||||||
'album-artist-list',
|
'album-artist-list',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Album,
|
Album,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
AlbumArtistDetailResponse,
|
AlbumArtistDetailResponse,
|
||||||
|
AlbumArtistInfoResponse,
|
||||||
AlbumArtistListResponse,
|
AlbumArtistListResponse,
|
||||||
AlbumDetailResponse,
|
AlbumDetailResponse,
|
||||||
AlbumListResponse,
|
AlbumListResponse,
|
||||||
@@ -235,29 +236,44 @@ export const applyRatingOptimisticUpdates = (
|
|||||||
return { ...prev, userRating: rating };
|
return { ...prev, userRating: rating };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev.similarArtists && prev.similarArtists.length > 0) {
|
|
||||||
const hasMatchingSimilarArtist = prev.similarArtists.some(
|
|
||||||
(artist) => itemIdSet.has(artist.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasMatchingSimilarArtist) {
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
similarArtists: prev.similarArtists.map((artist) =>
|
|
||||||
itemIdSet.has(artist.id)
|
|
||||||
? { ...artist, userRating: rating }
|
|
||||||
: artist,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return prev;
|
return prev;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const infoQueryKey = queryKeys.albumArtists.info(variables.apiClientProps.serverId);
|
||||||
|
const infoQueries = queryClient.getQueriesData({
|
||||||
|
exact: false,
|
||||||
|
queryKey: infoQueryKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
infoQueries.forEach(([queryKey, data]) => {
|
||||||
|
if (data) {
|
||||||
|
pendingUpdates.push({
|
||||||
|
previousData: data,
|
||||||
|
queryKey,
|
||||||
|
updater: (prev: AlbumArtistInfoResponse | null | undefined) => {
|
||||||
|
if (!prev?.similarArtists?.length) return prev;
|
||||||
|
|
||||||
|
const hasMatching = prev.similarArtists.some((artist) =>
|
||||||
|
itemIdSet.has(artist.id),
|
||||||
|
);
|
||||||
|
if (!hasMatching) return prev;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
similarArtists: prev.similarArtists.map((artist) =>
|
||||||
|
itemIdSet.has(artist.id)
|
||||||
|
? { ...artist, userRating: rating }
|
||||||
|
: artist,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const listQueryKey = queryKeys.albumArtists.list(variables.apiClientProps.serverId);
|
const listQueryKey = queryKeys.albumArtists.list(variables.apiClientProps.serverId);
|
||||||
const listQueries = queryClient.getQueriesData({
|
const listQueries = queryClient.getQueriesData({
|
||||||
exact: false,
|
exact: false,
|
||||||
@@ -626,6 +642,10 @@ export const applyRatingOptimisticUpdatesDeferred = (
|
|||||||
queryKeys.albumArtists.detail(variables.apiClientProps.serverId),
|
queryKeys.albumArtists.detail(variables.apiClientProps.serverId),
|
||||||
'album-artist-detail',
|
'album-artist-detail',
|
||||||
);
|
);
|
||||||
|
collectQueries(
|
||||||
|
queryKeys.albumArtists.info(variables.apiClientProps.serverId),
|
||||||
|
'album-artist-info',
|
||||||
|
);
|
||||||
collectQueries(
|
collectQueries(
|
||||||
queryKeys.albumArtists.list(variables.apiClientProps.serverId),
|
queryKeys.albumArtists.list(variables.apiClientProps.serverId),
|
||||||
'album-artist-list',
|
'album-artist-list',
|
||||||
@@ -695,19 +715,6 @@ export const applyRatingOptimisticUpdatesDeferred = (
|
|||||||
if (itemIdSet.has(prev.id)) {
|
if (itemIdSet.has(prev.id)) {
|
||||||
return { ...prev, userRating: rating };
|
return { ...prev, userRating: rating };
|
||||||
}
|
}
|
||||||
if (prev.similarArtists) {
|
|
||||||
const hasMatch = prev.similarArtists.some((a: any) =>
|
|
||||||
itemIdSet.has(a.id),
|
|
||||||
);
|
|
||||||
if (hasMatch) {
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
similarArtists: prev.similarArtists.map((a: any) =>
|
|
||||||
itemIdSet.has(a.id) ? { ...a, userRating: rating } : a,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
case 'album-artist-infinite-list':
|
case 'album-artist-infinite-list':
|
||||||
@@ -732,6 +739,17 @@ export const applyRatingOptimisticUpdatesDeferred = (
|
|||||||
}
|
}
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
case 'album-artist-info': {
|
||||||
|
if (!prev?.similarArtists?.length) return prev;
|
||||||
|
const hasMatch = prev.similarArtists.some((a: any) => itemIdSet.has(a.id));
|
||||||
|
if (!hasMatch) return prev;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
similarArtists: prev.similarArtists.map((a: any) =>
|
||||||
|
itemIdSet.has(a.id) ? { ...a, userRating: rating } : a,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
case 'album-artist-list':
|
case 'album-artist-list':
|
||||||
case 'album-list':
|
case 'album-list':
|
||||||
case 'artist-list':
|
case 'artist-list':
|
||||||
|
|||||||
@@ -820,6 +820,16 @@ export type AlbumArtistDetailQuery = { id: string };
|
|||||||
|
|
||||||
export type AlbumArtistDetailResponse = AlbumArtist | null;
|
export type AlbumArtistDetailResponse = AlbumArtist | null;
|
||||||
|
|
||||||
|
export type AlbumArtistInfoArgs = BaseEndpointArgs & { query: AlbumArtistInfoQuery };
|
||||||
|
|
||||||
|
export type AlbumArtistInfoQuery = { id: string; limit?: number };
|
||||||
|
|
||||||
|
export type AlbumArtistInfoResponse = {
|
||||||
|
biography?: null | string;
|
||||||
|
imageUrl?: null | string;
|
||||||
|
similarArtists: null | RelatedArtist[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ArtistListArgs = BaseEndpointArgs & { query: ArtistListQuery };
|
export type ArtistListArgs = BaseEndpointArgs & { query: ArtistListQuery };
|
||||||
|
|
||||||
export type ArtistListCountArgs = BaseEndpointArgs & { query: ListCountQuery<ArtistListQuery> };
|
export type ArtistListCountArgs = BaseEndpointArgs & { query: ListCountQuery<ArtistListQuery> };
|
||||||
@@ -1373,6 +1383,7 @@ export type ControllerEndpoint = {
|
|||||||
) => Promise<DeleteInternetRadioStationResponse>;
|
) => Promise<DeleteInternetRadioStationResponse>;
|
||||||
deletePlaylist: (args: DeletePlaylistArgs) => Promise<DeletePlaylistResponse>;
|
deletePlaylist: (args: DeletePlaylistArgs) => Promise<DeletePlaylistResponse>;
|
||||||
getAlbumArtistDetail: (args: AlbumArtistDetailArgs) => Promise<AlbumArtistDetailResponse>;
|
getAlbumArtistDetail: (args: AlbumArtistDetailArgs) => Promise<AlbumArtistDetailResponse>;
|
||||||
|
getAlbumArtistInfo?: (args: AlbumArtistInfoArgs) => Promise<AlbumArtistInfoResponse | null>;
|
||||||
getAlbumArtistList: (args: AlbumArtistListArgs) => Promise<AlbumArtistListResponse>;
|
getAlbumArtistList: (args: AlbumArtistListArgs) => Promise<AlbumArtistListResponse>;
|
||||||
getAlbumArtistListCount: (args: AlbumArtistListCountArgs) => Promise<number>;
|
getAlbumArtistListCount: (args: AlbumArtistListCountArgs) => Promise<number>;
|
||||||
getAlbumDetail: (args: AlbumDetailArgs) => Promise<AlbumDetailResponse>;
|
getAlbumDetail: (args: AlbumDetailArgs) => Promise<AlbumDetailResponse>;
|
||||||
@@ -1491,6 +1502,9 @@ export type InternalControllerEndpoint = {
|
|||||||
getAlbumArtistDetail: (
|
getAlbumArtistDetail: (
|
||||||
args: ReplaceApiClientProps<AlbumArtistDetailArgs>,
|
args: ReplaceApiClientProps<AlbumArtistDetailArgs>,
|
||||||
) => Promise<AlbumArtistDetailResponse>;
|
) => Promise<AlbumArtistDetailResponse>;
|
||||||
|
getAlbumArtistInfo?: (
|
||||||
|
args: ReplaceApiClientProps<AlbumArtistInfoArgs>,
|
||||||
|
) => Promise<AlbumArtistInfoResponse | null>;
|
||||||
getAlbumArtistList: (
|
getAlbumArtistList: (
|
||||||
args: ReplaceApiClientProps<AlbumArtistListArgs>,
|
args: ReplaceApiClientProps<AlbumArtistListArgs>,
|
||||||
) => Promise<AlbumArtistListResponse>;
|
) => Promise<AlbumArtistListResponse>;
|
||||||
|
|||||||
Reference in New Issue
Block a user