import type { ReactNode } from 'react'; import { Badge, Button } from '@olly/modern-sk'; import { useTranslation } from 'react-i18next'; import { Link, useNavigate } from 'react-router'; import { skipToken } from '@reduxjs/toolkit/query'; import { Icon } from '../common/Icon'; import { ArtTile } from '../common/ArtTile'; import { AvailabilityBadge } from './AvailabilityBadge'; import { MetadataStatusBadge } from './MetadataStatusBadge'; import { LoadingSkeleton } from '../common/LoadingSkeleton'; import { ErrorState } from '../common/ErrorState'; import { EmptyState } from '../common/EmptyState'; import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch'; import { closeTrackInfo } from '../../store/slices/ui'; import { play } from '../../store/slices/player'; import { addToQueue } from '../../store/slices/queue'; import { useGetTrackQuery, useGetAlbumQuery, } from '../../api/endpoints/library'; import { getTrackCoverUrl } from '../../api/endpoints/streaming'; import { formatDuration, formatFileSize, formatDateTime, } from '../../lib/format'; import type { Track } from '../../api/types'; /** * Right-side "Get Info"-style drawer for a single track. Rendered after the * QueuePanel in AppShell so it sits to the *right* of the queue when both are * open. Open state lives in `ui.trackInfoId`; it reads the live Track (and its * album) from the RTKQ cache so enrichment updates stay in sync. */ export function TrackInfoDrawer() { const trackId = useAppSelector((s) => s.ui.trackInfoId); const isOpen = trackId !== null; return ( ); } function TrackInfoContent({ trackId }: { trackId: string }) { const { t } = useTranslation(); const dispatch = useAppDispatch(); const navigate = useNavigate(); const token = useAppSelector((s) => s.auth.accessToken); const { data: track, isLoading, isError, refetch, } = useGetTrackQuery(trackId); // Album record fills in fields the lean TrackOut omits (year especially). const { data: album } = useGetAlbumQuery(track?.albumId ?? skipToken); const close = () => dispatch(closeTrackInfo()); return ( <>

{t('trackInfo.title')}

{isLoading ? ( ) : isError ? ( ) : !track ? ( ) : ( dispatch(play(track.id))} onQueue={() => dispatch( addToQueue({ trackId: track.id, title: track.title, artistName: track.artistName, albumTitle: track.albumTitle, durationMs: track.durationMs, albumArtUrl: track.albumArtUrl, }), ) } onEdit={() => { navigate(`/tracks/${track.id}/metadata`); }} /> )}
); } function TrackInfoBody({ track, albumYear, albumTrackCount, coverUrl, onPlay, onQueue, onEdit, }: { track: Track; albumYear?: number; albumTrackCount?: number; coverUrl?: string; onPlay: () => void; onQueue: () => void; onEdit: () => void; }) { const { t } = useTranslation(); const seedLabel = track.albumTitle || track.title; const year = track.year ?? albumYear; return ( <>
{coverUrl ? ( {track.albumTitle} ) : ( )}

{track.title}

{track.artistName} {track.albumId && ( {track.albumTitle} )}
{track.liked && ( {t('trackInfo.liked')} )}
{track.metadataStatus === 'failed' && track.metadataError && (

{track.metadataError}

)}
); } function InfoSection({ title, children, }: { title: string; children: ReactNode; }) { return (
{title} {children}
); } /** A label/value row; renders nothing when the value is empty (Finder-style). */ function InfoRow({ label, value, mono, }: { label: string; value?: string; mono?: boolean; }) { if (!value) return null; return (
{label} {value}
); }