mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
fix(ui): split album and track artists (OpenSubsonic) (#696)
* fix(ui): split OpenSubsonic album and track artists in header and player Album detail header uses albumArtists from album or child songs; player bar, mobile player, and mini player use structured track artists with per-id links. Adds deriveAlbumHeaderArtistRefs helper and OpenArtistRefInline. Fixes #552 * docs: changelog and credits for OpenSubsonic artist links (PR #696)
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import type { SubsonicOpenArtistRef } from '../api/subsonicTypes';
|
||||
|
||||
interface Props {
|
||||
refs: SubsonicOpenArtistRef[];
|
||||
/** Used when `refs` is empty (callers should normally avoid that). */
|
||||
fallbackName: string;
|
||||
/** Invoked with Subsonic artist id when a ref has an id. */
|
||||
onGoArtist: (artistId: string) => void;
|
||||
/** Wrapper element: `span` (default) or `fragment` children only. */
|
||||
as?: 'span' | 'none';
|
||||
/** `button` for album header; `span` matches dense player / track rows. */
|
||||
linkTag?: 'button' | 'span';
|
||||
outerClassName?: string;
|
||||
linkClassName?: string;
|
||||
separatorClassName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders OpenSubsonic `artists` / `albumArtists` refs as ·-separated names with
|
||||
* per-artist navigation when `id` is present (same interaction model as album
|
||||
* track rows).
|
||||
*/
|
||||
export function OpenArtistRefInline({
|
||||
refs,
|
||||
fallbackName,
|
||||
onGoArtist,
|
||||
as = 'span',
|
||||
linkTag = 'button',
|
||||
outerClassName,
|
||||
linkClassName,
|
||||
separatorClassName = 'open-artist-ref-sep',
|
||||
}: Props) {
|
||||
const list = refs.length > 0 ? refs : [{ name: fallbackName }];
|
||||
const inner = (
|
||||
<>
|
||||
{list.map((a, i) => (
|
||||
<Fragment key={a.id ?? `n:${a.name ?? ''}:${i}`}>
|
||||
{i > 0 && <span className={separatorClassName} aria-hidden="true"> · </span>}
|
||||
{a.id ? (
|
||||
linkTag === 'span' ? (
|
||||
<span
|
||||
role="link"
|
||||
tabIndex={0}
|
||||
className={linkClassName}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onGoArtist(a.id!);
|
||||
}}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onGoArtist(a.id!);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{a.name ?? fallbackName}
|
||||
</span>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={linkClassName}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onGoArtist(a.id!);
|
||||
}}
|
||||
>
|
||||
{a.name ?? fallbackName}
|
||||
</button>
|
||||
)
|
||||
) : (
|
||||
<span>{a.name ?? fallbackName}</span>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
if (as === 'none') return inner;
|
||||
return <span className={outerClassName}>{inner}</span>;
|
||||
}
|
||||
Reference in New Issue
Block a user