From eba2e6b14484c124f3c87ff95cd7ee640833e5d3 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 28 Aug 2026 15:35:20 +0200 Subject: feat(hub): cross-group search with reuse of existing views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Search page fetches indexes from all groups, then renders consolidated entries through the existing FilesPanel, VideoApp, and MusicApp components — no reimplemented views. Groups appear as top-level directories in the file browser; video/music entries use a synthetic root with per-entry transport refs for thumbnails and metadata across groups. Music player lifted to app.js with getConnection(groupId) for cross-group playback. Connection pool (max 3, LRU eviction) manages lazy WebRTC connections. All 10 locales updated with search keys. Co-Authored-By: Claude Opus 4.6 --- .../src/meshbay_hub/static/music-app.js | 39 ++++++++++++++++------ 1 file changed, 29 insertions(+), 10 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 dd354d9..418da89 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/music-app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/music-app.js @@ -174,8 +174,21 @@ function groupMusicEntries(entries, audioRoot) { // -- album cover, MusicBrainz fetched lazily and only when actually needed -- +const _musicMetaRetryListeners = new Set(); +function bumpMusicMetaGeneration() { + for (const fn of _musicMetaRetryListeners) fn(); +} + function useMusicMeta(transportRef, fileId, active) { const [meta, setMeta] = useState(null); + const [retryToken, setRetryToken] = useState(0); + + useEffect(() => { + const listener = () => setRetryToken((n) => n + 1); + _musicMetaRetryListeners.add(listener); + return () => _musicMetaRetryListeners.delete(listener); + }, []); + useEffect(() => { if (!active || !fileId) return; let cancelled = false; @@ -188,7 +201,7 @@ function useMusicMeta(transportRef, fileId, active) { } catch { if (!cancelled) setMeta({ confidence: 0 }); } })(); return () => { cancelled = true; }; - }, [fileId, active]); + }, [fileId, active, retryToken]); return meta; } @@ -236,21 +249,24 @@ function DiscPlaceholder({ cls }) { function AlbumCard({ album, transportRef, gekRef, musicbrainzEnabled, onOpen }) { const repTrack = album.tracks.find((tr) => tr.thumb_hash) || album.tracks[0]; - // Only when nothing in the library already gives us a cover -- the common - // case (a well-tagged rip with embedded art) needs no network call at all. + const tRef = repTrack._tRef || transportRef; + const gRef = repTrack._gRef || gekRef; const needsLookup = musicbrainzEnabled && !repTrack.thumb_hash; - const meta = useMusicMeta(transportRef, repTrack.id, needsLookup); + const meta = useMusicMeta(tRef, repTrack.id, needsLookup); const coverHash = repTrack.thumb_hash || (meta && meta.cover_thumb_hash) || null; return html`
${coverHash ? html`<${MediaThumb} thumbHash=${coverHash} alt=${album.album} - cls="music-cover" transportRef=${transportRef} gekRef=${gekRef} />` + cls="music-cover" transportRef=${tRef} gekRef=${gRef} />` : html`<${DiscPlaceholder} cls="music-cover" />`}
${album.album}
${album.artist}
+ ${repTrack.groupName && html` +
${repTrack.groupName}
+ `}
`; @@ -260,8 +276,10 @@ function AlbumCard({ album, transportRef, gekRef, musicbrainzEnabled, onOpen }) function MusicDetailModal({ album, transportRef, gekRef, musicbrainzEnabled, onClose, onPlayQueue }) { const repTrack = album.tracks.find((tr) => tr.thumb_hash) || album.tracks[0]; + const tRef = repTrack._tRef || transportRef; + const gRef = repTrack._gRef || gekRef; const needsLookup = musicbrainzEnabled && !repTrack.thumb_hash; - const meta = useMusicMeta(transportRef, repTrack.id, needsLookup); + const meta = useMusicMeta(tRef, repTrack.id, needsLookup); const coverHash = repTrack.thumb_hash || (meta && meta.cover_thumb_hash) || null; return html` @@ -278,7 +296,7 @@ function MusicDetailModal({ album, transportRef, gekRef, musicbrainzEnabled, onC
${coverHash ? html`<${MediaThumb} thumbHash=${coverHash} alt=${album.album} - cls="music-detail-cover" transportRef=${transportRef} gekRef=${gekRef} />` + cls="music-detail-cover" transportRef=${tRef} gekRef=${gRef} />` : html`<${DiscPlaceholder} cls="music-detail-cover" />`}
${album.artist}
@@ -437,6 +455,7 @@ function FlatList({ tracks, artists, onPlayQueue }) { function MusicApp({ groupId, transportRef, gekRef, status, entries, audioRoot, musicbrainzConfig, onPlayQueue, + hideFilter, }) { const [mode, setMode] = useState(loadViewMode); const [filter, setFilter] = useState(''); @@ -487,11 +506,11 @@ function MusicApp({ onClick=${() => setModeAndSave('flat')}> ${t('music.mode_flat')} - `}
${empty && html`

${t('music.empty')}

`} ${!empty && needle && filteredArtists.length === 0 && filteredTracks.length === 0 && html` @@ -506,4 +525,4 @@ function MusicApp({ `; } -export { MusicApp }; +export { MusicApp, groupMusicEntries, bumpMusicMetaGeneration }; -- cgit v1.2.3