feat(album): single cover on album detail, track-number rows
Album detail header falls back to a track's cover when the album record itself has none, and each track row hides its per-track art in favour of a large album-position number, since the header already shows the album's cover once.
This commit is contained in:
@@ -15,6 +15,10 @@ interface Props {
|
||||
track: Track;
|
||||
index?: number;
|
||||
showAlbum?: boolean;
|
||||
/** Hide cover art and show the track's album position instead — used on
|
||||
* the album detail page, where the album cover is already shown once in
|
||||
* the header and per-track art would be redundant. */
|
||||
hideArt?: boolean;
|
||||
onAddToPlaylist?: (track: Track) => void;
|
||||
onEditMetadata?: (track: Track) => void;
|
||||
onDelete?: (track: Track) => void;
|
||||
@@ -24,6 +28,7 @@ export function TrackRow({
|
||||
track,
|
||||
index,
|
||||
showAlbum = false,
|
||||
hideArt = false,
|
||||
onAddToPlaylist,
|
||||
onEditMetadata,
|
||||
onDelete,
|
||||
@@ -58,24 +63,43 @@ export function TrackRow({
|
||||
selected={isActive}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '2rem 2.5rem 1fr auto auto',
|
||||
gridTemplateColumns: hideArt
|
||||
? '2.5rem 1fr auto auto'
|
||||
: '2rem 2.5rem 1fr auto auto',
|
||||
gap: '0.75rem',
|
||||
alignItems: 'center',
|
||||
padding: '0.375rem 0.75rem',
|
||||
cursor: 'default',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '0.75rem',
|
||||
color: 'var(--color-text-3)',
|
||||
textAlign: 'right',
|
||||
}}
|
||||
>
|
||||
{isActive && isPlaying ? '▶' : index !== undefined ? index + 1 : ''}
|
||||
</span>
|
||||
{!hideArt && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: '0.75rem',
|
||||
color: 'var(--color-text-3)',
|
||||
textAlign: 'right',
|
||||
}}
|
||||
>
|
||||
{isActive && isPlaying ? '▶' : index !== undefined ? index + 1 : ''}
|
||||
</span>
|
||||
)}
|
||||
<div className="track-art">
|
||||
{artUrl ? (
|
||||
{hideArt ? (
|
||||
<div
|
||||
style={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '1.0625rem',
|
||||
fontWeight: 600,
|
||||
color: isActive ? 'var(--color-accent)' : 'var(--color-text-3)',
|
||||
}}
|
||||
>
|
||||
{track.trackNumber ?? (index !== undefined ? index + 1 : '')}
|
||||
</div>
|
||||
) : artUrl ? (
|
||||
<img
|
||||
src={artUrl}
|
||||
alt=""
|
||||
|
||||
@@ -9,16 +9,17 @@ import { TrackRow } from '../../components/track/TrackRow';
|
||||
import { LoadingSkeleton } from '../../components/common/LoadingSkeleton';
|
||||
import { ErrorState } from '../../components/common/ErrorState';
|
||||
import { EmptyState } from '../../components/common/EmptyState';
|
||||
import { useAppDispatch } from '../../hooks/useAppDispatch';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch';
|
||||
import { setQueue } from '../../store/slices/queue';
|
||||
import { formatDuration } from '../../lib/format';
|
||||
import { getCoverUrl } from '../../api/endpoints/streaming';
|
||||
import { getCoverUrl, getTrackCoverUrl } from '../../api/endpoints/streaming';
|
||||
|
||||
export function AlbumDetailPage() {
|
||||
const { t } = useTranslation();
|
||||
const { albumId } = useParams<{ albumId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const token = useAppSelector((s) => s.auth.accessToken);
|
||||
|
||||
const albumQuery = useGetAlbumQuery(albumId ?? '', { skip: !albumId });
|
||||
const tracksQuery = useGetAlbumTracksQuery(albumId ?? '', { skip: !albumId });
|
||||
@@ -42,7 +43,13 @@ export function AlbumDetailPage() {
|
||||
|
||||
const album = albumQuery.data;
|
||||
const tracks = tracksQuery.data ?? [];
|
||||
const artUrl = getCoverUrl(album?.artUrl);
|
||||
// The album record itself carries no cover; fall back to a track's cover.
|
||||
const coverTrack = tracks.find((t) => t.hasCover);
|
||||
const artUrl =
|
||||
getCoverUrl(album?.artUrl) ??
|
||||
(token && coverTrack
|
||||
? getTrackCoverUrl(coverTrack.id, token, true)
|
||||
: undefined);
|
||||
|
||||
const handlePlayAll = () => {
|
||||
if (!tracks.length || !album) return;
|
||||
@@ -178,7 +185,7 @@ export function AlbumDetailPage() {
|
||||
/>
|
||||
)}
|
||||
{tracks.map((track, i) => (
|
||||
<TrackRow key={track.id} track={track} index={i} />
|
||||
<TrackRow key={track.id} track={track} index={i} hideArt />
|
||||
))}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user