initial state: muzick music player + recommendation engine
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import type { TrackArtist } from '../types';
|
||||
|
||||
interface ArtistLinksProps {
|
||||
/** Ordered artists (first = main, rest = featured). */
|
||||
artists?: TrackArtist[] | null;
|
||||
/** Shown when there are no structured artists (plain text, not a link). */
|
||||
fallback?: string;
|
||||
className?: string;
|
||||
/** Stop row/card click handlers from firing when an artist link is clicked. */
|
||||
stopPropagation?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicates artists by ID, preferring `main` over `featured` when the
|
||||
* same artist has both roles (can happen because track_artists has a composite
|
||||
* PK of track_id + artist_id + role).
|
||||
*/
|
||||
function deduplicateArtists(artists: TrackArtist[]): TrackArtist[] {
|
||||
const map = new Map<string, TrackArtist>();
|
||||
for (const a of artists) {
|
||||
const existing = map.get(a.id);
|
||||
if (!existing || (existing.role === 'featured' && a.role === 'main')) {
|
||||
map.set(a.id, a);
|
||||
}
|
||||
}
|
||||
// Preserve original order, skipping duplicates.
|
||||
const seen = new Set<string>();
|
||||
return artists.filter((a) => {
|
||||
if (seen.has(a.id)) return false;
|
||||
seen.add(a.id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a track/album's artists as clickable links — main artist(s) then
|
||||
* "feat." guests. Single source of truth used by TrackRow, the playback bar,
|
||||
* the now-playing panel and album pages so artist navigation looks and behaves
|
||||
* the same everywhere.
|
||||
*
|
||||
* Artists are deduplicated by id — if the same artist appears as both main
|
||||
* and featured, only the main entry is shown.
|
||||
*/
|
||||
export function ArtistLinks({ artists, fallback, className = '', stopPropagation }: ArtistLinksProps) {
|
||||
if (!artists || artists.length === 0) {
|
||||
return <span className={className}>{fallback || 'Unknown artist'}</span>;
|
||||
}
|
||||
const unique = deduplicateArtists(artists);
|
||||
return (
|
||||
<span className={className}>
|
||||
{unique.map((a, i) => (
|
||||
<span key={a.id}>
|
||||
{i > 0 && <span className="opacity-60">{a.role === 'featured' && unique[i - 1].role !== 'featured' ? ' feat. ' : ', '}</span>}
|
||||
<Link
|
||||
to="/artists/$artistId"
|
||||
params={{ artistId: a.id }}
|
||||
onClick={stopPropagation ? (e) => e.stopPropagation() : undefined}
|
||||
className={`hover:text-text hover:underline ${a.role === 'featured' ? 'opacity-75' : ''}`}
|
||||
>
|
||||
{a.name}
|
||||
</Link>
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user