From df7eb105dda550989dccd3ba2bf22a50e919b19b Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 24 Aug 2026 22:44:27 +0200 Subject: feat(hub): audio_root wiring, mutually-exclusive players, Settings rework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five related pieces of polish against the Music app and Settings, all from the same conversation: - Music app now requires audio_root, same as Videos requires video_root: an empty-state message until one is set, and grouping filtered to only what's under it (underAudioRoot, mirroring video-app.js's underVideoRoot). Wires the new audio_root/audio_root_ack pair through transport.js and group-page.js state the same way video_root already flows. - Starting one player now stops the other — opening a film closes the music queue, starting a track closes the video modal. Both used to run at once, found live. - Group Settings reworked: every section but a bare form (invite, pair-operator, approve-device) is now collapsible (CollapsibleSection); the uploads on/off button is a real toggle switch (ToggleSwitch, reused for TMDB/MusicBrainz's enabled switches too, each now with an icon + status badge in its header instead of a plain checkbox row); and shared directories, the Videos root picker, and the new Music root picker are merged into one "Directories" section (RootFolderRow) instead of three separate ones scattered down the page — the root pickers only show once their app is actually enabled. --- .../src/meshbay_hub/static/music-app.js | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/music-app.js') 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 cb14fb4..298e259 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/music-app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/music-app.js @@ -46,12 +46,24 @@ function foldKey(s) { .replace(/\s*&\s*/g, ' and ').replace(/\s+/g, ' ').trim(); } -function groupMusicEntries(entries) { +// Same shape as video-app.js's underVideoRoot: an unset root means "show +// nothing" (docs/musicbay.md's amended §2.1 — the node itself runs no +// tag/cover enrichment for this group before a root is chosen either, +// daemon.py's _enrich_new_audio_entries), not "the whole shared tree" — +// falling back to that would just show files nothing has enriched. +function underAudioRoot(entry, audioRoot) { + if (!audioRoot) return false; + const p = entry.path || ''; + return p === audioRoot || p.startsWith(audioRoot + '/'); +} + +function groupMusicEntries(entries, audioRoot) { const tracks = []; // no artist at all, even after the folder fallback -- rare, but real const byArtistKey = new Map(); // foldKey(artist) -> { artist, albumsByKey: Map, loose: [] } for (const e of entries) { if (e.type !== 'audio') continue; + if (!underAudioRoot(e, audioRoot)) continue; const artistRaw = (e.artist || '').trim(); if (!artistRaw) { tracks.push(e); continue; } const artistKey = foldKey(artistRaw); @@ -381,7 +393,7 @@ function FlatList({ tracks, artists, onPlayQueue }) { // -- shell -------------------------------------------------------------------- function MusicApp({ - groupId, transportRef, gekRef, status, entries, musicbrainzConfig, onPlayQueue, + groupId, transportRef, gekRef, status, entries, audioRoot, musicbrainzConfig, onPlayQueue, }) { const [mode, setMode] = useState(loadViewMode); const [filter, setFilter] = useState(''); @@ -392,7 +404,8 @@ function MusicApp({ const setModeAndSave = (m) => { setMode(m); saveViewMode(m); }; - const { tracks, artists, albums } = useMemo(() => groupMusicEntries(entries), [entries]); + const { tracks, artists, albums } = useMemo( + () => groupMusicEntries(entries, audioRoot), [entries, audioRoot]); const needle = filter.trim().toLowerCase(); const filteredArtists = useMemo(() => { @@ -418,7 +431,10 @@ function MusicApp({ ${status === 'offline' && html`

${t('group.offline_title')} ${t('group.offline_hint')}

`} - ${status === 'connected' && html` + ${status === 'connected' && !audioRoot && html` +

${t('music.no_root_configured')}

+ `} + ${status === 'connected' && audioRoot && html`