120 lines
3.2 KiB
TypeScript
120 lines
3.2 KiB
TypeScript
import { Row } from 'modern-sk';
|
|
import { TrackContextMenu } from './TrackContextMenu';
|
|
import { AvailabilityBadge } from './AvailabilityBadge';
|
|
import { formatDuration } from '../../lib/format';
|
|
import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch';
|
|
import { play } from '../../store/slices/player';
|
|
import type { Track } from '../../api/types';
|
|
import { getCoverUrl } from '../../api/endpoints/streaming';
|
|
|
|
interface Props {
|
|
track: Track;
|
|
index?: number;
|
|
showAlbum?: boolean;
|
|
onAddToPlaylist?: (track: Track) => void;
|
|
onEditMetadata?: (track: Track) => void;
|
|
onDelete?: (track: Track) => void;
|
|
}
|
|
|
|
export function TrackRow({
|
|
track,
|
|
index,
|
|
showAlbum = false,
|
|
onAddToPlaylist,
|
|
onEditMetadata,
|
|
onDelete,
|
|
}: Props) {
|
|
const dispatch = useAppDispatch();
|
|
const currentTrackId = useAppSelector((s) => s.player.currentTrackId);
|
|
const isPlaying = useAppSelector((s) => s.player.isPlaying);
|
|
const isActive = currentTrackId === track.id;
|
|
const artUrl = getCoverUrl(track.albumArtUrl);
|
|
|
|
return (
|
|
<Row
|
|
selected={isActive}
|
|
onDoubleClick={() => dispatch(play(track.id))}
|
|
style={{
|
|
display: 'grid',
|
|
gridTemplateColumns: '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>
|
|
{artUrl ? (
|
|
<img
|
|
src={artUrl}
|
|
alt=""
|
|
width={36}
|
|
height={36}
|
|
style={{ borderRadius: 4, objectFit: 'cover' }}
|
|
/>
|
|
) : (
|
|
<div
|
|
style={{
|
|
width: 36,
|
|
height: 36,
|
|
borderRadius: 4,
|
|
background: 'var(--color-surface-3)',
|
|
}}
|
|
/>
|
|
)}
|
|
<div style={{ minWidth: 0 }}>
|
|
<div
|
|
style={{
|
|
fontWeight: isActive ? 600 : 400,
|
|
color: isActive ? 'var(--color-accent)' : 'var(--color-text-1)',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
whiteSpace: 'nowrap',
|
|
}}
|
|
>
|
|
{track.title}
|
|
</div>
|
|
<div
|
|
style={{
|
|
fontSize: '0.8125rem',
|
|
color: 'var(--color-text-2)',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
whiteSpace: 'nowrap',
|
|
}}
|
|
>
|
|
{track.artistName}
|
|
{showAlbum && ` · ${track.albumTitle}`}
|
|
</div>
|
|
</div>
|
|
<AvailabilityBadge availability={track.availability} />
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
<span
|
|
style={{
|
|
fontSize: '0.8125rem',
|
|
color: 'var(--color-text-3)',
|
|
minWidth: '3rem',
|
|
textAlign: 'right',
|
|
}}
|
|
>
|
|
{formatDuration(track.durationMs)}
|
|
</span>
|
|
<TrackContextMenu
|
|
track={track}
|
|
onAddToPlaylist={onAddToPlaylist}
|
|
onEditMetadata={onEditMetadata}
|
|
onDelete={onDelete}
|
|
/>
|
|
</div>
|
|
</Row>
|
|
);
|
|
}
|