aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-28 15:35:20 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-28 15:35:58 +0200
commiteba2e6b14484c124f3c87ff95cd7ee640833e5d3 (patch)
treeba109b719c879df3ea40090e4fd6ff80b403a3a5 /packages/meshbay-hub/src/meshbay_hub/static/group-page.js
parente6917349c47cc4b7e04e1ecfab27c5291a5fe522 (diff)
downloadmeshbay-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.js59
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>
`;
}