diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-24 19:46:06 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-24 19:46:06 +0200 |
| commit | 62253b9592a83ce152d0c64471e20514218fb132 (patch) | |
| tree | d41b84661da3252045937f5a90dc45ca7b029edc /packages/meshbay-hub/src/meshbay_hub/static/music-app.js | |
| parent | d053d083627f1f9f010752f8ad67941e22d49f27 (diff) | |
| download | meshbay-62253b9592a83ce152d0c64471e20514218fb132.tar.gz | |
feat(hub): consolidate loose tracks, "&"/"and" fold, player close/queue
Album-grid readability, part two:
- groupMusicEntries (music-app.js): an album bucket left with exactly
one track - a real album tag, but only one song from it, not the
whole release - clutters the grid the same way an untagged loose
track does. Both kinds now fold into one "<artist> - Various" tile
per artist, unless there is only one leftover track overall, where
relabeling buys nothing and the track keeps its own name (or the
generic placeholder, if it never had one).
- foldKey also normalizes "&" vs "and" ("Artist & The Band" / "Artist
and The Band" is one act, tagged both ways across different rips of
the same catalogue) alongside the existing case/whitespace fold.
- music-player.js: a close button pauses and tears the player down;
an unmount cleanup effect (pause, revoke every cached blob URL)
fires either way, whether that's the close button or the shell
tearing the bar down on its own. A "current queue" button opens an
overlay listing the whole playing queue with the current track
highlighted, click any to jump to it - works identically regardless
of how the queue was built (an album, the consolidated misc bucket,
a single standalone track), since it only ever reads the player's
own live tracks/order/pos.
- group-page.js: this component is not remounted when switching to a
*different* group on the same /group/:id route (only the groupId
prop changes) - so without an explicit reset, music from one group
would carry into the next one opened. Resets musicQueue to null on
groupId change; a tab switch inside one group still leaves it alone.
- Scrubbed real artist/band names that had leaked into code comments
and test fixtures (enrich_audio.py's docstrings, several
test_enrich_audio.py assertions, a music-app.js comment) - replaced
with generic placeholders, no behavioural change.
- i18n: music.various, music.player_close, music.player_queue,
music.queue_title added across all ten locales.
Client-side only except none of this touches the node at all. npm run
sync-ui re-run. Full suite: 1129 passed, no regressions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KBi7ALLGfwcjBXt57yNMcy
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/music-app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/music-app.js | 56 |
1 files changed, 41 insertions, 15 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/music-app.js b/packages/meshbay-hub/src/meshbay_hub/static/music-app.js index 87e1319..7e16b53 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/music-app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/music-app.js @@ -36,13 +36,15 @@ function saveViewMode(mode) { // -- grouping ------------------------------------------------------------- -// Two tags differing only in case ("Racing With The Sun" vs "Racing with -// the sun" -- found live, a single mistagged track split one real album -// into two cards) are the same artist/album for grouping purposes. Folded -// for the *key* only; the first-seen spelling is kept as the display -// string, so this never rewrites anyone's tags. +// Two tags differing only in case (found live: a single mistagged track +// split one real album into two cards) are the same artist/album for +// grouping purposes. Same for an "&" vs "and" spelling of the same act, +// tagged both ways across different rips of the same catalogue. Folded for +// the *key* only; the first-seen spelling is kept as the display string, so +// this never rewrites anyone's tags. function foldKey(s) { - return (s || '').trim().replace(/\s+/g, ' ').toLowerCase(); + return (s || '').trim().replace(/\s+/g, ' ').toLowerCase() + .replace(/\s*&\s*/g, ' and ').replace(/\s+/g, ' ').trim(); } function groupMusicEntries(entries) { @@ -74,18 +76,42 @@ function groupMusicEntries(entries) { tracks.sort(byTitle); const artists = [...byArtistKey.values()].map(({ artist, albumsByKey, loose }) => { - const albums = [...albumsByKey.values()].sort((a, b) => a.album.localeCompare(b.album)); - for (const album of albums) { + const sortedAlbums = [...albumsByKey.values()].sort((a, b) => a.album.localeCompare(b.album)); + + // A "singleton" -- an album bucket down to exactly one track, because + // that's a real album tag but this person only has one song from it, + // not the whole release -- clutters the grid exactly the way an + // untagged loose track does. Found live: one artist's folder listing a + // dozen near-empty one-track album cards alongside the genuine + // multi-track albums. Both kinds fold into a single "<artist> - + // Various" tile, unless there is only one leftover track overall, where + // relabeling away a real album name (or minting "Various" for one + // file) buys nothing. + const realAlbums = []; + const misc = [...loose]; + const miscSourceAlbums = []; + for (const album of sortedAlbums) { + if (album.tracks.length > 1) { realAlbums.push(album); continue; } + misc.push(...album.tracks); + miscSourceAlbums.push(album); + } + for (const album of realAlbums) { album.tracks.sort((a, b) => (trackNo(a) - trackNo(b)) || byTitle(a, b)); } - if (loose.length) { - // Appended after every real album, never sorted in among them by - // whatever the placeholder text happens to alphabetize to -- it - // isn't a release, and translating "Unknown album" must not move it. - loose.sort((a, b) => (trackNo(a) - trackNo(b)) || byTitle(a, b)); - albums.push({ artist, album: t('music.unknown_album'), isUnknown: true, tracks: loose }); + + if (misc.length === 1) { + // Keep the one leftover's own real album name if it had one; the + // generic placeholder only for a single untagged track with nothing + // else to call it. + realAlbums.push(miscSourceAlbums[0] + || { artist, album: t('music.unknown_album'), isUnknown: true, tracks: misc }); + } else if (misc.length > 1) { + misc.sort((a, b) => (trackNo(a) - trackNo(b)) || byTitle(a, b)); + realAlbums.push({ + artist, album: `${artist} - ${t('music.various')}`, isUnknown: true, tracks: misc, + }); } - return { artist, albums }; + return { artist, albums: realAlbums }; }).sort((a, b) => a.artist.localeCompare(b.artist)); const albums = artists.flatMap((a) => a.albums); |