mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
feat(artist): group albums by release type on artist page (#471)
* feat(artist): group albums by release type on artist page Uses the releaseType field to group albums/releases into sections like Albums, Compilation, Live, etc. If there's no release type it falls back to normal view * feat(artist): i18n release-type group labels * fix(artist): deterministic release-type group order * refactor(artist): replace inline styles with CSS classes * i18n(artist): translate release-type labels in remaining 7 locales Sayykii's `releaseTypes` namespace was added to en.ts only. Fills in de, fr, nl, zh, nb, ru, es with the same 8 keys (album, ep, single, compilation, live, soundtrack, remix, other) so users on non-English UIs see translated section headers on the artist page instead of the raw title-cased fallback. * docs: changelog + contributors for PR #471 Adds the v1.46.0 "Added" entry and bumps Sayykii's contributors line for the artist-page release-type grouping. --------- Co-authored-by: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com>
This commit is contained in:
@@ -459,6 +459,37 @@ export default function ArtistDetail() {
|
||||
[coverId],
|
||||
);
|
||||
|
||||
const groupedAlbums = useMemo(() => {
|
||||
if (albums.length === 0) return [];
|
||||
const RELEASE_TYPE_ORDER = ['album', 'ep', 'single', 'compilation', 'live', 'soundtrack', 'remix', 'other'];
|
||||
const defaultKey = 'album';
|
||||
const titleCase = (s: string) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
|
||||
const translateType = (tag: string) =>
|
||||
t(`artistDetail.releaseTypes.${tag}`, { defaultValue: titleCase(tag) });
|
||||
|
||||
const groups = new Map<string, SubsonicAlbum[]>();
|
||||
for (const album of albums) {
|
||||
const key = album.releaseTypes?.length
|
||||
? album.releaseTypes.map(r => r.toLowerCase()).join(' · ')
|
||||
: defaultKey;
|
||||
if (!groups.has(key)) groups.set(key, []);
|
||||
groups.get(key)!.push(album);
|
||||
}
|
||||
|
||||
if (groups.size === 1 && groups.has(defaultKey)) {
|
||||
return [[translateType(defaultKey), albums] as const];
|
||||
}
|
||||
|
||||
const sortKey = (key: string) => {
|
||||
const idx = RELEASE_TYPE_ORDER.indexOf(key.split(' · ')[0]);
|
||||
return idx >= 0 ? idx : RELEASE_TYPE_ORDER.length;
|
||||
};
|
||||
|
||||
return [...groups.entries()]
|
||||
.sort((a, b) => sortKey(a[0]) - sortKey(b[0]) || a[0].localeCompare(b[0]))
|
||||
.map(([key, group]) => [key.split(' · ').map(translateType).join(' · '), group] as const);
|
||||
}, [albums, t]);
|
||||
|
||||
useEffect(() => {
|
||||
setHeaderCoverFailed(false);
|
||||
}, [coverId, coverRevision, id]);
|
||||
@@ -858,9 +889,21 @@ export default function ArtistDetail() {
|
||||
{t('artistDetail.albumsBy', { name: artist.name })}
|
||||
</h2>
|
||||
{albums.length > 0 ? (
|
||||
<div className="album-grid-wrap album-grid-wrap--artist">
|
||||
{albums.map((a, i) => <AlbumCard key={`${a.id}-${i}`} album={a} />)}
|
||||
</div>
|
||||
groupedAlbums.length === 1 ? (
|
||||
<div className="album-grid-wrap album-grid-wrap--artist">
|
||||
{albums.map((a, i) => <AlbumCard key={`${a.id}-${i}`} album={a} />)}
|
||||
</div>
|
||||
) : groupedAlbums.map(([label, group]) => (
|
||||
<div key={label} className="artist-release-group">
|
||||
<div className="artist-release-group__header">
|
||||
<h3>{label}</h3>
|
||||
<span className="artist-release-group__count">{group.length}</span>
|
||||
</div>
|
||||
<div className="album-grid-wrap album-grid-wrap--artist">
|
||||
{group.map((a, i) => <AlbumCard key={`${a.id}-${i}`} album={a} />)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p style={{ color: 'var(--text-muted)' }}>{t('artistDetail.noAlbums')}</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user