diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-28 15:35:20 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-28 15:35:58 +0200 |
| commit | eba2e6b14484c124f3c87ff95cd7ee640833e5d3 (patch) | |
| tree | ba109b719c879df3ea40090e4fd6ff80b403a3a5 /packages/meshbay-hub/src/meshbay_hub/static/group-page.js | |
| parent | e6917349c47cc4b7e04e1ecfab27c5291a5fe522 (diff) | |
| download | meshbay-eba2e6b14484c124f3c87ff95cd7ee640833e5d3.tar.gz | |
feat(hub): cross-group search with reuse of existing views
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 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-page.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/group-page.js | 59 |
1 files changed, 19 insertions, 40 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js index 4ebe5a7..5a2a79b 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js @@ -12,7 +12,6 @@ import { visibleApps } from './apps.js'; import { GroupName } from './group-name.js'; import { FilePreview } from './files-app.js'; import { VideoPlayer } from './video-player.js'; -import { MusicPlayerBar } from './music-player.js'; import { GroupSettingsPanel } from './group-settings.js'; /** @@ -23,7 +22,8 @@ import { GroupSettingsPanel } from './group-settings.js'; * an operator with no way to re-enable anything). */ function GroupPage({ groupId, group, token, username, userId, userPrefs, - onRefreshAuth, onJoined, onGroupUpdated, onPresence, onLeft }) { + onRefreshAuth, onJoined, onGroupUpdated, onPresence, onLeft, + onPlayQueue: parentOnPlayQueue, onStopMusic }) { const [status, setStatus] = useState('idle'); const [entries, setEntries] = useState([]); @@ -97,26 +97,13 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, // MusicBrainz on/off (per-group) + whether a contact string is configured // (node-wide) — docs/musicbay.md §3.2, same shape as tmdbConfig above. const [musicbrainzConfig, setMusicbrainzConfig] = useState(null); - // What music-app.js hands over when a track/album is clicked — owned here - // (not by music-app.js) so playback survives switching tabs, the same - // reasoning the video/preview modals are shell-owned. `nonce` makes - // "play this same album again from track 0" a distinct value every time, - // so MusicPlayerBar's queue-init effect always re-runs. - const [musicQueue, setMusicQueue] = useState(null); - // Starting one player stops the other — both would otherwise keep playing - // at once, found live: opening a film while a track was going left both - // audio tracks running together. onPlayQueue closes the video modal for - // the same reason `entry.type === 'video'` below stops the music queue. const onPlayQueue = useCallback((tracks, startIndex) => { setVideoEntry(null); - setMusicQueue({ tracks, startIndex, nonce: Date.now() }); - }, []); - // This component instance is not remounted when switching to a *different* - // group on the same /group/:id route (only groupId as a prop changes, see - // the connect effect's own comment below) — so leaving music playing here - // would carry it into whatever group is opened next. Tab switches inside - // one group must not stop it; leaving the group itself must. - useEffect(() => { setMusicQueue(null); }, [groupId]); + if (parentOnPlayQueue) { + const annotated = tracks.map(tr => tr.groupId ? tr : { ...tr, groupId }); + parentOnPlayQueue(annotated, startIndex, { transportRef, gekRef, groupId }); + } + }, [parentOnPlayQueue, groupId]); // Paired ≠ operator account. `is_node_admin` says the hub account owning this // node is the one connecting; this says the node pinned *this browser's* key // as an operator key. Only the second one lets you sign an invite, and only @@ -162,8 +149,9 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, if (indexMsg.dirs) setNodeDirs(indexMsg.dirs); if (indexMsg.roots) setNodeRoots(indexMsg.roots); cacheGroupIndex(groupId, group ? group.name : groupId, - group ? group.owner_username : null, fresh); - }, [groupId, group]); + group ? group.owner_username : null, fresh, + { videoRoot, audioRoot, photoRoots }); + }, [groupId, group, videoRoot, audioRoot, photoRoots]); // additions/deletions/updates (daemon.py _broadcast_index_change, once // there is a previous snapshot to diff against) — applied on top of @@ -184,10 +172,11 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, const additions = (deltaMsg.additions || []).filter((e) => !keptIds.has(e.id)); const fresh = updated.concat(additions); cacheGroupIndex(groupId, group ? group.name : groupId, - group ? group.owner_username : null, fresh); + group ? group.owner_username : null, fresh, + { videoRoot, audioRoot, photoRoots }); return fresh; }); - }, [groupId, group]); + }, [groupId, group, videoRoot, audioRoot, photoRoots]); useEffect(() => { let cancelled = false; @@ -464,9 +453,11 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, // already playing (from Music, or a previous Files click) rather than // merging with it, so there is nothing to special-case here. const onPreview = useCallback((entry) => { - // Opening a film stops whatever the Music player was doing — see the - // matching setVideoEntry(null) in onPlayQueue above for the reverse case. - if (entry.type === 'video') { setMusicQueue(null); setVideoEntry(entry); return; } + if (entry.type === 'video') { + if (onStopMusic) onStopMusic(); + setVideoEntry(entry); + return; + } if (entry.type === 'audio') { const siblings = entries .filter((e) => e.type === 'audio' && e.path === entry.path) @@ -476,7 +467,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, return; } setPreviewEntry(entry); - }, [entries, onPlayQueue]); + }, [entries, onPlayQueue, onStopMusic]); const apps = visibleApps(enabledApps); const commonProps = { @@ -647,18 +638,6 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, onClose=${() => setVideoEntry(null)} onDownload=${() => downloadFileForModal(videoEntry)} /> `} - ${/* Outside the tab-switched area on purpose (docs/musicbay.md §2.3): - once something has been played this session, the bar stays - mounted and keeps playing regardless of which tab is active — - switching to Chat or Files must not stop the music. Renders - nothing of its own until onPlayQueue has been called once. - Its own close button, and the effect above on leaving the - group, both go through the same setMusicQueue(null) — the - bar's own unmount cleanup is what actually stops playback. */ - musicQueue && html` - <${MusicPlayerBar} transportRef=${transportRef} gekRef=${gekRef} queue=${musicQueue} - userPrefs=${userPrefs} onClose=${() => setMusicQueue(null)} /> - `} </div> `; } |