aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-15 17:01:06 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-15 17:01:06 +0200
commit459fc93e98f23e326c2fa77fe86ba74c2bae77f0 (patch)
treef506a127cc3e0779c4d8cf9f27fe659d81bb787e /packages/meshbay-hub/src/meshbay_hub/static/music-app.js
parentfce0942cb7d81dbdbae70e50b0164fead7e8bc49 (diff)
downloadmeshbay-459fc93e98f23e326c2fa77fe86ba74c2bae77f0.tar.gz
feat(hub): Videos and Music list their cards a page at a time
Previous/next arrows in the pinned toolbar, on group pages and in Search. Page size is an account preference (Settings → Defaults), 50 by default, 10 to 200 in steps of 10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.js44
1 files changed, 34 insertions, 10 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 f622b5d..60a7b00 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
@@ -6,6 +6,7 @@ import { Icon } from './icon.js';
import { MediaThumb, LazyTile } from './video-app.js';
import { formatTime } from './music-player.js';
import { SourceTag } from './group-name.js';
+import { usePager, Pager, pageSizeFrom } from './pager.js';
// -- Music --------------------------------------------------------------------
//
@@ -324,8 +325,16 @@ function MusicDetailModal({ album, transportRef, gekRef, musicbrainzEnabled, onC
// -- Mode A: album grid -----------------------------------------------------
-function AlbumGrid({ artists, transportRef, gekRef, musicbrainzEnabled, onPlayQueue }) {
+// `units` is one page of `{ artist, album }`. An artist whose albums straddle
+// two pages gets its heading on both.
+function AlbumGrid({ units, transportRef, gekRef, musicbrainzEnabled, onPlayQueue }) {
const [detail, setDetail] = useState(null); // the album object
+ const artists = [];
+ for (const u of units) {
+ const prev = artists[artists.length - 1];
+ if (prev && prev.artist === u.artist) prev.albums.push(u.album);
+ else artists.push({ artist: u.artist, albums: [u.album] });
+ }
return html`
${artists.map((a) => html`
@@ -435,12 +444,8 @@ function FlatArtistFolder({ artist, onPlayQueue }) {
`;
}
-function FlatList({ tracks, artists, onPlayQueue }) {
- const items = [
- ...tracks.map((tr) => ({ key: tr.display_title || tr.name, kind: 'track', track: tr })),
- ...artists.map((a) => ({ key: a.artist, kind: 'artist', artist: a })),
- ].sort((a, b) => a.key.localeCompare(b.key));
-
+// `items` is one page of rows, already sorted by MusicApp.
+function FlatList({ items, onPlayQueue }) {
return html`
<div class="video-flat-list">
${items.map((it) => it.kind === 'track'
@@ -456,7 +461,7 @@ function FlatList({ tracks, artists, onPlayQueue }) {
function MusicApp({
groupId, transportRef, gekRef, status, entries, availableEntries,
musicDirectories, musicbrainzConfig, onPlayQueue,
- hideFilter,
+ hideFilter, userPrefs, pageResetKey,
}) {
const [mode, setMode] = useState(loadViewMode);
const [filter, setFilter] = useState('');
@@ -488,6 +493,24 @@ function MusicApp({
const filteredTracks = useMemo(() => (!needle ? tracks : tracks.filter(
(tr) => (tr.display_title || tr.name).toLowerCase().includes(needle))), [tracks, needle]);
+ // What this mode draws, in drawing order: albums under their artists in the
+ // grid, and in the flat list loose tracks and artist folders sorted together.
+ const units = useMemo(() => {
+ if (mode === 'grid') {
+ return filteredArtists.flatMap((a) => a.albums.map((album) => ({ artist: a.artist, album })));
+ }
+ return [
+ ...filteredTracks.map((tr) => ({ key: tr.display_title || tr.name, kind: 'track', track: tr })),
+ ...filteredArtists.map((a) => ({ key: a.artist, kind: 'artist', artist: a })),
+ ].sort((a, b) => a.key.localeCompare(b.key));
+ }, [mode, filteredArtists, filteredTracks]);
+
+ const pager = usePager(units.length, pageSizeFrom(userPrefs),
+ `${groupId}|${mode}|${needle}|${pageResetKey || ''}`);
+ const pageUnits = useMemo(() => {
+ return units.slice(pager.start, pager.end);
+ }, [units, pager.start, pager.end]);
+
const empty = albums.length === 0 && tracks.length === 0;
return html`
@@ -510,6 +533,7 @@ function MusicApp({
onClick=${() => setModeAndSave('flat')}>
${t('music.mode_flat')}
</button>
+ <${Pager} pager=${pager} />
${!hideFilter && html`<div class="tb-search">
<${Icon} name="search" />
<input type="text" placeholder="${t('group.filter')}"
@@ -521,9 +545,9 @@ function MusicApp({
<p class="page-message">${t('group.empty_filter')}</p>
`}
${!empty && mode === 'grid'
- ? html`<${AlbumGrid} artists=${filteredArtists} transportRef=${transportRef} gekRef=${gekRef}
+ ? html`<${AlbumGrid} units=${pageUnits} transportRef=${transportRef} gekRef=${gekRef}
musicbrainzEnabled=${musicbrainzEnabled} onPlayQueue=${onPlayQueue} />`
- : !empty && html`<${FlatList} tracks=${filteredTracks} artists=${filteredArtists}
+ : !empty && html`<${FlatList} items=${pageUnits}
onPlayQueue=${onPlayQueue} />`}
`}
`;