mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-23 07:45:45 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05f7ae0611 | |||
| b0b7ff6a92 |
@@ -43,6 +43,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
* **Settings → System → Logging** and **PsyLab → Logs** offer basic and verbose debug-depth levels while Debug logging is enabled. Verbose mode adds structured multi-server scope, reachability, music-folder and New Releases diagnostics, with credentials and sensitive URL data redacted.
|
||||
|
||||
### Album details — disc covers in the multi-disc separator
|
||||
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1336](https://github.com/Psychotoxical/psysonic/pull/1336)**
|
||||
|
||||
* Multi-disc albums show each disc's cover next to "CD N" instead of a generic disc icon. Releases with distinct per-disc artwork show each disc's own cover; other albums fall back to the shared album art.
|
||||
|
||||
## Changed
|
||||
|
||||
### Library index — designed for several live servers at once
|
||||
|
||||
@@ -15,6 +15,7 @@ import { TrackRow } from '@/features/album/components/TrackRow';
|
||||
import { AlbumTrackListMobile } from '@/features/album/components/AlbumTrackListMobile';
|
||||
import { TracklistColumnPicker } from '@/ui/TracklistColumnPicker';
|
||||
import { TracklistHeaderRow } from '@/features/album/components/TracklistHeaderRow';
|
||||
import { DiscHeaderCover } from '@/features/album/components/DiscHeaderCover';
|
||||
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||
import { ownedEntityKey, ownedOverrideValue } from '@/lib/util/ownedEntityKey';
|
||||
|
||||
@@ -167,7 +168,7 @@ export default function AlbumTrackList({
|
||||
<div key={discNum}>
|
||||
{isMultiDisc && (
|
||||
<div className="disc-header">
|
||||
<span className="disc-icon">💿</span>
|
||||
<DiscHeaderCover song={discs.get(discNum)![0]} />
|
||||
CD {discNum}
|
||||
{discTitleByNum.get(discNum) && (
|
||||
<span className="disc-subtitle">{discTitleByNum.get(discNum)}</span>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { songToTrack } from '@/lib/media/songToTrack';
|
||||
import { formatLongDuration } from '@/lib/format/formatDuration';
|
||||
import { ownedEntityKey } from '@/lib/util/ownedEntityKey';
|
||||
import { sameQueueTrack } from '@/features/playback';
|
||||
import { DiscHeaderCover } from '@/features/album/components/DiscHeaderCover';
|
||||
|
||||
interface Props {
|
||||
discNums: number[];
|
||||
@@ -49,7 +50,7 @@ export function AlbumTrackListMobile({
|
||||
<div key={discNum}>
|
||||
{isMultiDisc && (
|
||||
<div className="disc-header">
|
||||
<span className="disc-icon">💿</span> CD {discNum}
|
||||
<DiscHeaderCover song={discs.get(discNum)![0]} /> CD {discNum}
|
||||
{discTitleByNum.get(discNum) && (
|
||||
<span className="disc-subtitle">{discTitleByNum.get(discNum)}</span>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { CoverArtImage } from '@/cover/CoverArtImage';
|
||||
import { albumCoverRefForSong } from '@/cover/ref';
|
||||
import { coverServerScopeForServerId } from '@/cover/serverScope';
|
||||
|
||||
type DiscHeaderSong = Pick<SubsonicSong, 'id' | 'albumId' | 'coverArt' | 'discNumber' | 'serverId'>;
|
||||
|
||||
/**
|
||||
* Cover shown next to a multi-disc separator ("CD N"), resolved from the disc's
|
||||
* own first track rather than album-scoped.
|
||||
*
|
||||
* A disc's cover is that track's own art (`song.coverArt`). Servers surface
|
||||
* embedded per-file art as per-track `mf-*` ids, and the album-scoped heuristic
|
||||
* (`album_has_distinct_disc_covers`) deliberately rejects per-track ids to avoid a
|
||||
* per-song cache explosion — which routes every disc to the shared album slot, so
|
||||
* discs with genuinely different embedded art collide on the first disc's cover.
|
||||
* Forcing per-disc resolution here is safe: the separator renders at most one
|
||||
* cover per disc, so a dedicated per-track cache slot cannot explode. Genuine
|
||||
* per-disc `dc-*` art resolves the same way; single-cover albums simply reuse the
|
||||
* same bytes under a per-disc slot.
|
||||
*/
|
||||
export function DiscHeaderCover({ song }: { song: DiscHeaderSong }) {
|
||||
const coverRef = albumCoverRefForSong(song, true, coverServerScopeForServerId(song.serverId));
|
||||
if (!coverRef) return null;
|
||||
return (
|
||||
<CoverArtImage
|
||||
coverRef={coverRef}
|
||||
displayCssPx={40}
|
||||
surface="dense"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="track-row-cover-thumb"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -626,8 +626,25 @@ html[data-track-previews-randommix="off"] [data-preview-loc="randomMix"] .pl
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.disc-icon {
|
||||
font-size: 18px;
|
||||
/* Album / per-disc cover in the multi-disc separator (replaces the old CD glyph).
|
||||
Sized here because `.track-row-cover-thumb` only carries dimensions in
|
||||
track-row / queue contexts; box sets with distinct disc art resolve per disc
|
||||
via the library cover resolver. */
|
||||
.disc-header .track-row-cover-thumb {
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--radius-sm);
|
||||
object-fit: cover;
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.disc-header .track-row-cover-thumb--placeholder {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
/* Per-disc subtitle (OpenSubsonic `discTitles`), e.g. "Sessions" on a deluxe CD 3. */
|
||||
|
||||
Reference in New Issue
Block a user