mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-08 13:00:13 +02:00
add new table to album detail
This commit is contained in:
@@ -5,23 +5,35 @@ import { generatePath, Link, useParams } from 'react-router';
|
||||
|
||||
import styles from './album-detail-content.module.css';
|
||||
|
||||
import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder';
|
||||
import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize';
|
||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||
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 { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||
import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel';
|
||||
import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
|
||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||
import { PlayButton } from '/@/renderer/features/shared/components/play-button';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { useGeneralSettings, usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||
import {
|
||||
useGeneralSettings,
|
||||
usePlayButtonBehavior,
|
||||
useSettingsStore,
|
||||
} from '/@/renderer/store/settings.store';
|
||||
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Button } from '/@/shared/components/button/button';
|
||||
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||
import { Spoiler } from '/@/shared/components/spoiler/spoiler';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { AlbumListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { AlbumListSort, LibraryItem, Song, SortOrder } from '/@/shared/types/domain-types';
|
||||
import { ItemListKey, ListDisplayType, Play } from '/@/shared/types/types';
|
||||
|
||||
interface AlbumDetailContentProps {
|
||||
background?: string;
|
||||
@@ -35,53 +47,46 @@ export const AlbumDetailContent = ({ background }: AlbumDetailContentProps) => {
|
||||
albumQueries.detail({ query: { id: albumId }, serverId: server.id }),
|
||||
);
|
||||
|
||||
const { data: detail } = useQuery(
|
||||
albumQueries.detail({ query: { id: albumId }, serverId: server.id }),
|
||||
);
|
||||
|
||||
const { ref, ...cq } = useContainerQuery();
|
||||
const { externalLinks, lastFM, musicBrainz } = useGeneralSettings();
|
||||
const genreRoute = useGenreRoute();
|
||||
|
||||
const carousels = useMemo(
|
||||
() => [
|
||||
{
|
||||
excludeIds: detail?.id ? [detail.id] : undefined,
|
||||
isHidden: !detail?.albumArtists?.[0]?.id,
|
||||
query: {
|
||||
_custom: {
|
||||
jellyfin: {
|
||||
ExcludeItemIds: detail?.id,
|
||||
},
|
||||
const carousels = [
|
||||
{
|
||||
excludeIds: detailQuery?.data?.id ? [detailQuery.data.id] : undefined,
|
||||
isHidden: !detailQuery?.data?.albumArtists?.[0]?.id,
|
||||
query: {
|
||||
_custom: {
|
||||
jellyfin: {
|
||||
ExcludeItemIds: detailQuery?.data?.id,
|
||||
},
|
||||
artistIds: detail?.albumArtists.length
|
||||
? [detail.albumArtists[0].id]
|
||||
: undefined,
|
||||
},
|
||||
sortBy: AlbumListSort.YEAR,
|
||||
sortOrder: SortOrder.DESC,
|
||||
title: t('page.albumDetail.moreFromArtist', { postProcess: 'sentenceCase' }),
|
||||
uniqueId: 'moreFromArtist',
|
||||
artistIds: detailQuery?.data?.albumArtists.length
|
||||
? [detailQuery?.data?.albumArtists[0].id]
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
excludeIds: detail?.id ? [detail.id] : undefined,
|
||||
isHidden: !detailQuery?.data?.genres?.[0],
|
||||
query: {
|
||||
genres: detailQuery.data?.genres.length
|
||||
? [detailQuery.data.genres[0].id]
|
||||
: undefined,
|
||||
},
|
||||
sortBy: AlbumListSort.RANDOM,
|
||||
sortOrder: SortOrder.ASC,
|
||||
title: `${t('page.albumDetail.moreFromGeneric', {
|
||||
item: '',
|
||||
postProcess: 'sentenceCase',
|
||||
})} ${detailQuery?.data?.genres?.[0]?.name}`,
|
||||
uniqueId: 'relatedGenres',
|
||||
sortBy: AlbumListSort.YEAR,
|
||||
sortOrder: SortOrder.DESC,
|
||||
title: t('page.albumDetail.moreFromArtist', { postProcess: 'sentenceCase' }),
|
||||
uniqueId: 'moreFromArtist',
|
||||
},
|
||||
{
|
||||
excludeIds: detailQuery?.data?.id ? [detailQuery.data.id] : undefined,
|
||||
isHidden: !detailQuery?.data?.genres?.[0],
|
||||
query: {
|
||||
genres: detailQuery?.data?.genres.length
|
||||
? [detailQuery?.data?.genres[0].id]
|
||||
: undefined,
|
||||
},
|
||||
],
|
||||
[detail?.id, detail?.albumArtists, detailQuery?.data?.genres, t],
|
||||
);
|
||||
sortBy: AlbumListSort.RANDOM,
|
||||
sortOrder: SortOrder.ASC,
|
||||
title: `${t('page.albumDetail.moreFromGeneric', {
|
||||
item: '',
|
||||
postProcess: 'sentenceCase',
|
||||
})} ${detailQuery?.data?.genres?.[0]?.name}`,
|
||||
uniqueId: 'relatedGenres',
|
||||
},
|
||||
];
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
|
||||
const handlePlay = async (playType?: Play) => {};
|
||||
@@ -125,6 +130,17 @@ export const AlbumDetailContent = ({ background }: AlbumDetailContentProps) => {
|
||||
/>
|
||||
</Group>
|
||||
</Group>
|
||||
<ListConfigMenu
|
||||
displayTypes={[{ hidden: true, value: ListDisplayType.GRID }]}
|
||||
listKey={ItemListKey.ALBUM_DETAIL}
|
||||
optionsConfig={{
|
||||
table: {
|
||||
itemsPerPage: { hidden: true },
|
||||
pagination: { hidden: true },
|
||||
},
|
||||
}}
|
||||
tableColumnsData={SONG_TABLE_COLUMNS}
|
||||
/>
|
||||
</Group>
|
||||
</section>
|
||||
{showGenres && (
|
||||
@@ -198,6 +214,12 @@ export const AlbumDetailContent = ({ background }: AlbumDetailContentProps) => {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{detailQuery?.data?.songs && detailQuery.data.songs.length > 0 && (
|
||||
<section>
|
||||
<AlbumDetailSongsTable songs={detailQuery.data.songs} />
|
||||
</section>
|
||||
)}
|
||||
|
||||
<Stack gap="lg" mt="3rem">
|
||||
{cq.height || cq.width ? (
|
||||
<>
|
||||
@@ -225,3 +247,203 @@ export const AlbumDetailContent = ({ background }: AlbumDetailContentProps) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface AlbumDetailSongsTableProps {
|
||||
songs: Song[];
|
||||
}
|
||||
|
||||
const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
|
||||
const { t } = useTranslation();
|
||||
const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.ALBUM_DETAIL]?.table);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return tableConfig?.columns || [];
|
||||
}, [tableConfig?.columns]);
|
||||
|
||||
const { handleColumnReordered } = useItemListColumnReorder({
|
||||
itemListKey: ItemListKey.ALBUM_DETAIL,
|
||||
});
|
||||
|
||||
const { handleColumnResized } = useItemListColumnResize({
|
||||
itemListKey: ItemListKey.ALBUM_DETAIL,
|
||||
});
|
||||
|
||||
const discGroups = useMemo(() => {
|
||||
if (songs.length === 0) return [];
|
||||
|
||||
const groups: Array<{
|
||||
discNumber: number;
|
||||
discSubtitle: null | string;
|
||||
itemCount: number;
|
||||
}> = [];
|
||||
let lastDiscNumber = -1;
|
||||
let currentGroupStartIndex = 0;
|
||||
|
||||
songs.forEach((song, index) => {
|
||||
if (song.discNumber !== lastDiscNumber) {
|
||||
// If we have a previous group, calculate its item count
|
||||
if (groups.length > 0) {
|
||||
groups[groups.length - 1].itemCount = index - currentGroupStartIndex;
|
||||
}
|
||||
// Start a new group
|
||||
groups.push({
|
||||
discNumber: song.discNumber,
|
||||
discSubtitle: song.discSubtitle,
|
||||
itemCount: 0, // Will be calculated when we encounter the next group or end
|
||||
});
|
||||
currentGroupStartIndex = index;
|
||||
lastDiscNumber = song.discNumber;
|
||||
}
|
||||
});
|
||||
|
||||
// Set item count for the last group
|
||||
if (groups.length > 0) {
|
||||
groups[groups.length - 1].itemCount = songs.length - currentGroupStartIndex;
|
||||
}
|
||||
|
||||
return groups;
|
||||
}, [songs]);
|
||||
|
||||
// const maxHeight = useMemo(() => {
|
||||
// if (!tableConfig) return undefined;
|
||||
|
||||
// const headerHeight = 40;
|
||||
// const rowHeights = {
|
||||
// compact: 40,
|
||||
// default: 64,
|
||||
// large: 88,
|
||||
// };
|
||||
// const rowHeight = rowHeights[tableConfig.size || 'default'];
|
||||
// const maxRows = 20;
|
||||
|
||||
// return headerHeight + maxRows * rowHeight;
|
||||
// }, [tableConfig]);
|
||||
|
||||
// Uncomment to enable static table height
|
||||
// const containerHeight = useMemo(() => {
|
||||
// if (!tableConfig || !maxHeight) return undefined;
|
||||
|
||||
// const headerHeight = 40;
|
||||
// const rowHeights = {
|
||||
// compact: 40,
|
||||
// default: 64,
|
||||
// large: 88,
|
||||
// };
|
||||
// const rowHeight = rowHeights[tableConfig.size || 'default'];
|
||||
// const actualRows = Math.min(songs.length, 20);
|
||||
|
||||
// return Math.min(headerHeight + actualRows * rowHeight, maxHeight);
|
||||
// }, [tableConfig, maxHeight, songs.length]);
|
||||
|
||||
const groups = useMemo(() => {
|
||||
if (discGroups.length <= 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return discGroups.map((discGroup) => ({
|
||||
itemCount: discGroup.itemCount,
|
||||
render: ({
|
||||
data,
|
||||
internalState,
|
||||
startDataIndex,
|
||||
}: {
|
||||
data: unknown[];
|
||||
groupIndex: number;
|
||||
index: number;
|
||||
internalState: any;
|
||||
startDataIndex: number;
|
||||
}) => {
|
||||
const groupItems = data.slice(startDataIndex, startDataIndex + discGroup.itemCount);
|
||||
|
||||
const selectedCount = groupItems.filter((item) => {
|
||||
if (!item || typeof item !== 'object' || !('id' in item)) return false;
|
||||
const rowId = internalState.extractRowId(item);
|
||||
return rowId ? internalState.isSelected(rowId) : false;
|
||||
}).length;
|
||||
|
||||
const isAllSelected = selectedCount === groupItems.length;
|
||||
const isSomeSelected = selectedCount > 0 && selectedCount < groupItems.length;
|
||||
|
||||
const handleCheckboxChange = () => {
|
||||
const selectableItems = groupItems;
|
||||
|
||||
if (isAllSelected) {
|
||||
// Deselect all items in the group
|
||||
const currentlySelected = internalState.getSelected();
|
||||
const groupItemIds = new Set(
|
||||
selectableItems
|
||||
.map((item) => internalState.extractRowId(item))
|
||||
.filter(Boolean),
|
||||
);
|
||||
const itemsToKeep = currentlySelected.filter(
|
||||
(item) => !groupItemIds.has(internalState.extractRowId(item) || ''),
|
||||
);
|
||||
internalState.setSelected(itemsToKeep);
|
||||
} else {
|
||||
// Select all items in the group (add to existing selection)
|
||||
const currentlySelected = internalState.getSelected();
|
||||
const selectedIds = new Set(
|
||||
currentlySelected
|
||||
.map((item) => internalState.extractRowId(item))
|
||||
.filter(Boolean),
|
||||
);
|
||||
const itemsToAdd = selectableItems.filter(
|
||||
(item) => !selectedIds.has(internalState.extractRowId(item) || ''),
|
||||
);
|
||||
internalState.setSelected([...currentlySelected, ...itemsToAdd]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Group
|
||||
align="center"
|
||||
h="100%"
|
||||
px="md"
|
||||
style={{ background: 'var(--theme-colors-background)' }}
|
||||
w="100%"
|
||||
>
|
||||
<Checkbox
|
||||
checked={isAllSelected}
|
||||
indeterminate={isSomeSelected}
|
||||
onChange={handleCheckboxChange}
|
||||
size="xs"
|
||||
/>
|
||||
<Text size="sm">
|
||||
{t('common.disc', { postProcess: 'sentenceCase' })}{' '}
|
||||
{discGroup.discNumber}
|
||||
{discGroup.discSubtitle && ` - ${discGroup.discSubtitle}`}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
},
|
||||
rowHeight: 40,
|
||||
}));
|
||||
}, [discGroups, t]);
|
||||
|
||||
if (!tableConfig || columns.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemTableList
|
||||
autoFitColumns={tableConfig.autoFitColumns}
|
||||
CellComponent={ItemTableListColumn}
|
||||
columns={columns}
|
||||
data={songs}
|
||||
enableAlternateRowColors={tableConfig.enableAlternateRowColors}
|
||||
enableDrag
|
||||
enableExpansion={false}
|
||||
enableHeader
|
||||
enableHorizontalBorders={tableConfig.enableHorizontalBorders}
|
||||
enableRowHoverHighlight={tableConfig.enableRowHoverHighlight}
|
||||
enableSelection
|
||||
enableStickyHeader
|
||||
enableVerticalBorders={tableConfig.enableVerticalBorders}
|
||||
groups={groups}
|
||||
itemType={LibraryItem.SONG}
|
||||
onColumnReordered={handleColumnReordered}
|
||||
onColumnResized={handleColumnResized}
|
||||
size={tableConfig.size}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { generatePath, Link, useParams } from 'react-router';
|
||||
|
||||
@@ -25,139 +25,142 @@ interface AlbumDetailHeaderProps {
|
||||
};
|
||||
}
|
||||
|
||||
export const AlbumDetailHeader = ({ background }: AlbumDetailHeaderProps) => {
|
||||
const { albumId } = useParams() as { albumId: string };
|
||||
const server = useCurrentServer();
|
||||
const detailQuery = useQuery(
|
||||
albumQueries.detail({ query: { id: albumId }, serverId: server?.id }),
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
export const AlbumDetailHeader = forwardRef<HTMLDivElement, AlbumDetailHeaderProps>(
|
||||
({ background }, ref) => {
|
||||
const { albumId } = useParams() as { albumId: string };
|
||||
const server = useCurrentServer();
|
||||
const detailQuery = useQuery(
|
||||
albumQueries.detail({ query: { id: albumId }, serverId: server?.id }),
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const showRating =
|
||||
detailQuery?.data?._serverType === ServerType.NAVIDROME ||
|
||||
detailQuery?.data?._serverType === ServerType.SUBSONIC;
|
||||
const showRating =
|
||||
detailQuery?.data?._serverType === ServerType.NAVIDROME ||
|
||||
detailQuery?.data?._serverType === ServerType.SUBSONIC;
|
||||
|
||||
const originalDifferentFromRelease =
|
||||
detailQuery.data?.originalDate &&
|
||||
detailQuery.data.originalDate !== detailQuery.data.releaseDate;
|
||||
const originalDifferentFromRelease =
|
||||
detailQuery.data?.originalDate &&
|
||||
detailQuery.data.originalDate !== detailQuery.data.releaseDate;
|
||||
|
||||
const releasePrefix = originalDifferentFromRelease
|
||||
? t('page.albumDetail.released', { postProcess: 'sentenceCase' })
|
||||
: '♫';
|
||||
const releasePrefix = originalDifferentFromRelease
|
||||
? t('page.albumDetail.released', { postProcess: 'sentenceCase' })
|
||||
: '♫';
|
||||
|
||||
const releaseTypes = useMemo(
|
||||
() =>
|
||||
normalizeReleaseTypes(detailQuery.data?.releaseTypes ?? [], t).map((type) => ({
|
||||
id: type,
|
||||
value: titleCase(type),
|
||||
})) || [],
|
||||
[detailQuery.data?.releaseTypes, t],
|
||||
);
|
||||
const releaseTypes = useMemo(
|
||||
() =>
|
||||
normalizeReleaseTypes(detailQuery.data?.releaseTypes ?? [], t).map((type) => ({
|
||||
id: type,
|
||||
value: titleCase(type),
|
||||
})) || [],
|
||||
[detailQuery.data?.releaseTypes, t],
|
||||
);
|
||||
|
||||
const metadataItems = releaseTypes.concat([
|
||||
{
|
||||
id: 'releaseDate',
|
||||
value:
|
||||
detailQuery?.data?.releaseDate &&
|
||||
`${releasePrefix} ${formatDateAbsoluteUTC(detailQuery?.data?.releaseDate)}`,
|
||||
},
|
||||
{
|
||||
id: 'songCount',
|
||||
value: t('entity.trackWithCount', {
|
||||
count: detailQuery?.data?.songCount as number,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'duration',
|
||||
value: detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
|
||||
},
|
||||
{
|
||||
id: 'playCount',
|
||||
value:
|
||||
typeof detailQuery?.data?.playCount === 'number' &&
|
||||
t('entity.play', {
|
||||
count: detailQuery?.data?.playCount,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'version',
|
||||
value: detailQuery.data?.version,
|
||||
},
|
||||
]);
|
||||
|
||||
if (originalDifferentFromRelease) {
|
||||
const formatted = `♫ ${formatDateAbsoluteUTC(detailQuery!.data!.originalDate)}`;
|
||||
metadataItems.splice(0, 0, {
|
||||
id: 'originalDate',
|
||||
value: formatted,
|
||||
});
|
||||
}
|
||||
|
||||
const updateRatingMutation = useSetRating({});
|
||||
|
||||
const handleUpdateRating = (rating: number) => {
|
||||
if (!detailQuery?.data) return;
|
||||
|
||||
updateRatingMutation.mutate({
|
||||
apiClientProps: { serverId: detailQuery.data._serverId },
|
||||
query: {
|
||||
id: [detailQuery.data.id],
|
||||
rating,
|
||||
type: LibraryItem.ALBUM,
|
||||
const metadataItems = releaseTypes.concat([
|
||||
{
|
||||
id: 'releaseDate',
|
||||
value:
|
||||
detailQuery?.data?.releaseDate &&
|
||||
`${releasePrefix} ${formatDateAbsoluteUTC(detailQuery?.data?.releaseDate)}`,
|
||||
},
|
||||
});
|
||||
};
|
||||
{
|
||||
id: 'songCount',
|
||||
value: t('entity.trackWithCount', {
|
||||
count: detailQuery?.data?.songCount as number,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'duration',
|
||||
value:
|
||||
detailQuery?.data?.duration && formatDurationString(detailQuery.data.duration),
|
||||
},
|
||||
{
|
||||
id: 'playCount',
|
||||
value:
|
||||
typeof detailQuery?.data?.playCount === 'number' &&
|
||||
t('entity.play', {
|
||||
count: detailQuery?.data?.playCount,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'version',
|
||||
value: detailQuery.data?.version,
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<LibraryHeader
|
||||
imageUrl={detailQuery?.data?.imageUrl}
|
||||
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
|
||||
title={detailQuery?.data?.name || ''}
|
||||
{...background}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
<Pill.Group>
|
||||
{metadataItems.map(
|
||||
(item, index) =>
|
||||
item.value && (
|
||||
<Pill key={`item-${item.id}-${index}`}>{item.value}</Pill>
|
||||
),
|
||||
if (originalDifferentFromRelease) {
|
||||
const formatted = `♫ ${formatDateAbsoluteUTC(detailQuery!.data!.originalDate)}`;
|
||||
metadataItems.splice(0, 0, {
|
||||
id: 'originalDate',
|
||||
value: formatted,
|
||||
});
|
||||
}
|
||||
|
||||
const updateRatingMutation = useSetRating({});
|
||||
|
||||
const handleUpdateRating = (rating: number) => {
|
||||
if (!detailQuery?.data) return;
|
||||
|
||||
updateRatingMutation.mutate({
|
||||
apiClientProps: { serverId: detailQuery.data._serverId },
|
||||
query: {
|
||||
id: [detailQuery.data.id],
|
||||
rating,
|
||||
type: LibraryItem.ALBUM,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack ref={ref}>
|
||||
<LibraryHeader
|
||||
imageUrl={detailQuery?.data?.imageUrl}
|
||||
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
|
||||
title={detailQuery?.data?.name || ''}
|
||||
{...background}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
<Pill.Group>
|
||||
{metadataItems.map(
|
||||
(item, index) =>
|
||||
item.value && (
|
||||
<Pill key={`item-${item.id}-${index}`}>{item.value}</Pill>
|
||||
),
|
||||
)}
|
||||
</Pill.Group>
|
||||
{showRating && (
|
||||
<Rating
|
||||
onChange={handleUpdateRating}
|
||||
readOnly={detailQuery?.isFetching || updateRatingMutation.isPending}
|
||||
value={detailQuery?.data?.userRating || 0}
|
||||
/>
|
||||
)}
|
||||
</Pill.Group>
|
||||
{showRating && (
|
||||
<Rating
|
||||
onChange={handleUpdateRating}
|
||||
readOnly={detailQuery?.isFetching || updateRatingMutation.isPending}
|
||||
value={detailQuery?.data?.userRating || 0}
|
||||
/>
|
||||
)}
|
||||
<Group
|
||||
gap="md"
|
||||
mah="4rem"
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
WebkitLineClamp: 2,
|
||||
}}
|
||||
>
|
||||
{detailQuery?.data?.albumArtists.map((artist) => (
|
||||
<Text
|
||||
component={Link}
|
||||
fw={600}
|
||||
isLink
|
||||
key={`artist-${artist.id}`}
|
||||
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||
albumArtistId: artist.id,
|
||||
})}
|
||||
variant="subtle"
|
||||
>
|
||||
{artist.name}
|
||||
</Text>
|
||||
))}
|
||||
</Group>
|
||||
</Stack>
|
||||
</LibraryHeader>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
<Group
|
||||
gap="md"
|
||||
mah="4rem"
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
WebkitLineClamp: 2,
|
||||
}}
|
||||
>
|
||||
{detailQuery?.data?.albumArtists.map((artist) => (
|
||||
<Text
|
||||
component={Link}
|
||||
fw={600}
|
||||
isLink
|
||||
key={`artist-${artist.id}`}
|
||||
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||
albumArtistId: artist.id,
|
||||
})}
|
||||
variant="subtle"
|
||||
>
|
||||
{artist.name}
|
||||
</Text>
|
||||
))}
|
||||
</Group>
|
||||
</Stack>
|
||||
</LibraryHeader>
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user