mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
fix(tracks): RC3 layout regressions + multi-artist links (#976)
* fix(tracks): RC3 layout regressions + multi-artist links - Restore vertical rhythm on the Tracks hub: the header/hero/rails/browse sections sat two wrapper divs below .tracks-page so its flex gap never reached them and they collapsed together. New .tracks-hub-stack re-applies the gap, fixing the tagline-touching-hero and Random-Pick-overlapping-hero spacing (the rail's nav buttons no longer ride into the box above). - Stop the sticky browse-table header going transparent on hover: its base background became opaque but the :hover rule still forced transparent, so rows bled through. Header now keeps its card background on hover. - Widen the Duration column (56px -> 72px; 56 -> 64 on mobile) so the "DURATION" header no longer clips/overruns. - Split multi-artist tracks into individually clickable artist links in both the "Track of the moment" hero and the browse list rows (OpenSubsonic artists[] with single-artist fallback), matching the album tracklist. * docs(changelog): Tracks RC3 spacing, Duration, header hover, multi-artist (#976)
This commit is contained in:
committed by
GitHub
parent
908c349cfd
commit
88df194808
@@ -855,6 +855,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
* Hiding **Mainstage** from the sidebar no longer leaves the app opening on a blank page — it now starts on the first visible library entry instead.
|
||||
* When every Mainstage section is turned off, the page shows a short message with a shortcut into **Settings → Personalisation** rather than appearing empty.
|
||||
|
||||
### Tracks — spacing, Duration column, header hover, multi-artist links
|
||||
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), reported by zunoz on Discord, PR [#976](https://github.com/Psychotoxical/psysonic/pull/976)**
|
||||
|
||||
* The Tracks hub sections (tagline, **Track of the moment**, **Random Pick** rail, **Browse all tracks**) no longer bunch together — even vertical spacing is restored, so the rail's navigation buttons stop riding up into the card above.
|
||||
* The **Browse all tracks** table no longer clips the **Duration** column header.
|
||||
* The track-list column header keeps its background on hover instead of turning transparent and letting rows show through.
|
||||
* **Track of the moment** and the browse rows split multi-artist tracks into individually clickable artist links, matching the album track list.
|
||||
|
||||
## [1.46.0] - 2026-05-18
|
||||
|
||||
> **🙏 Special thanks to [@zz5zz](https://github.com/zz5zz)** for his tireless quirk-spotting and bug reports on the [Psysonic Discord](https://discord.gg/AMnDRErm4u) — several of the polish fixes in this release landed directly off the back of his messages.
|
||||
|
||||
@@ -39,6 +39,13 @@ function SongRow({ song, showBpm }: Props) {
|
||||
enqueue([songToTrack(song)]);
|
||||
};
|
||||
|
||||
// Split multi-artist tracks into individually clickable links (OpenSubsonic
|
||||
// `artists[]`), falling back to the single flat artist when absent — mirrors
|
||||
// the album tracklist so a "A · B" credit isn't one link to a single artist.
|
||||
const artistRefs = song.artists && song.artists.length > 0
|
||||
? song.artists
|
||||
: [{ id: song.artistId, name: song.artist }];
|
||||
|
||||
const bpmTooltip =
|
||||
song.localBpmSource === 'analysis'
|
||||
? t('search.bpmSourceAnalysis')
|
||||
@@ -93,13 +100,17 @@ function SongRow({ song, showBpm }: Props) {
|
||||
</button>
|
||||
</div>
|
||||
<div className="song-list-row-cell song-list-row-title truncate" title={song.title}>{song.title}</div>
|
||||
<div className="song-list-row-cell truncate">
|
||||
<span
|
||||
className={song.artistId ? 'track-artist-link' : ''}
|
||||
style={{ cursor: song.artistId ? 'pointer' : 'default' }}
|
||||
onClick={(e) => { if (song.artistId) { e.stopPropagation(); navigateToArtist(song.artistId); } }}
|
||||
title={song.artist}
|
||||
>{song.artist}</span>
|
||||
<div className="song-list-row-cell truncate" title={song.artist}>
|
||||
{artistRefs.map((a, i) => (
|
||||
<React.Fragment key={a.id ?? a.name ?? i}>
|
||||
{i > 0 && <span className="track-artist-sep"> · </span>}
|
||||
<span
|
||||
className={a.id ? 'track-artist-link' : ''}
|
||||
style={{ cursor: a.id ? 'pointer' : 'default' }}
|
||||
onClick={(e) => { if (a.id) { e.stopPropagation(); navigateToArtist(a.id!); } }}
|
||||
>{a.name ?? song.artist}</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
<div className="song-list-row-cell truncate">
|
||||
{song.albumId ? (
|
||||
|
||||
@@ -105,6 +105,14 @@ export default function TracksPageChrome({
|
||||
[random, hero],
|
||||
);
|
||||
|
||||
// Split a multi-artist feature into individually clickable links
|
||||
// (OpenSubsonic `artists[]`), falling back to the single flat artist.
|
||||
const heroArtistRefs = hero
|
||||
? (hero.artists && hero.artists.length > 0
|
||||
? hero.artists
|
||||
: [{ id: hero.artistId, name: hero.artist }])
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{!perfFlags.disableMainstageStickyHeader && (
|
||||
@@ -140,11 +148,16 @@ export default function TracksPageChrome({
|
||||
</span>
|
||||
<h2 className="tracks-hero-title" title={hero.title}>{hero.title}</h2>
|
||||
<p className="tracks-hero-meta">
|
||||
<span
|
||||
className={hero.artistId ? 'track-artist-link' : ''}
|
||||
style={{ cursor: hero.artistId ? 'pointer' : 'default' }}
|
||||
onClick={() => hero.artistId && navigateToArtist(hero.artistId)}
|
||||
>{hero.artist}</span>
|
||||
{heroArtistRefs.map((a, i) => (
|
||||
<React.Fragment key={a.id ?? a.name ?? i}>
|
||||
{i > 0 && <span className="track-artist-sep"> · </span>}
|
||||
<span
|
||||
className={a.id ? 'track-artist-link' : ''}
|
||||
style={{ cursor: a.id ? 'pointer' : 'default' }}
|
||||
onClick={() => a.id && navigateToArtist(a.id)}
|
||||
>{a.name ?? hero.artist}</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{hero.album && (
|
||||
<>
|
||||
<span className="tracks-hero-meta-dot">·</span>
|
||||
|
||||
@@ -852,7 +852,7 @@ export default function SearchBrowsePage() {
|
||||
data-advanced-search-root
|
||||
>
|
||||
<div style={{ visibility: isLeaveRestorePending ? 'hidden' : 'visible' }}>
|
||||
<div>
|
||||
<div className={showTracksChrome ? 'tracks-hub-stack' : undefined}>
|
||||
{showTracksChrome ? (
|
||||
<>
|
||||
<TracksPageChrome
|
||||
|
||||
@@ -9,6 +9,16 @@
|
||||
padding-bottom: var(--space-8, 2rem);
|
||||
}
|
||||
|
||||
/* The hub sections (header, hero, rails, browse list) live two wrapper divs
|
||||
deep inside .tracks-page, so the page-level flex gap never reached them and
|
||||
the sections collapsed together. This stack restores even vertical rhythm
|
||||
between them. */
|
||||
.tracks-hub-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6, 1.5rem);
|
||||
}
|
||||
|
||||
.tracks-header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
}
|
||||
|
||||
.song-list-row {
|
||||
grid-template-columns: 64px minmax(0, 1.5fr) minmax(0, 1fr) 56px;
|
||||
grid-template-columns: 64px minmax(0, 1.5fr) minmax(0, 1fr) 64px;
|
||||
}
|
||||
|
||||
.song-list-row--with-bpm {
|
||||
grid-template-columns: 64px minmax(0, 1.5fr) minmax(0, 1fr) 48px 56px;
|
||||
grid-template-columns: 64px minmax(0, 1.5fr) minmax(0, 1fr) 48px 64px;
|
||||
}
|
||||
|
||||
.song-list-row-cell:nth-child(4), /* album */
|
||||
|
||||
+6
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
.song-list-row {
|
||||
display: grid;
|
||||
grid-template-columns: 64px minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1fr) 110px 56px;
|
||||
grid-template-columns: 64px minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1fr) 110px 72px;
|
||||
gap: var(--space-3);
|
||||
align-items: center;
|
||||
height: 52px;
|
||||
@@ -43,8 +43,11 @@
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
/* Header is sticky and opaque — keep its card background on hover so the
|
||||
generic .song-list-row:hover rule doesn't turn it transparent (which let
|
||||
rows scrolling underneath bleed through). */
|
||||
.song-list-row--header:hover {
|
||||
background: transparent;
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
.song-list-row-cell {
|
||||
@@ -75,7 +78,7 @@
|
||||
}
|
||||
|
||||
.song-list-row--with-bpm {
|
||||
grid-template-columns: 64px minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1fr) 110px 48px 56px;
|
||||
grid-template-columns: 64px minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1fr) 110px 48px 72px;
|
||||
}
|
||||
|
||||
.song-list-row-bpm {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.virtual-song-list-title {
|
||||
|
||||
Reference in New Issue
Block a user