diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/search-page.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/search-page.js | 70 |
1 files changed, 59 insertions, 11 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/search-page.js b/packages/meshbay-hub/src/meshbay_hub/static/search-page.js index 032d26a..5ac3ab6 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/search-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/search-page.js @@ -10,6 +10,7 @@ import { import { FilesPanel, FilePreview } from './files-app.js'; import { VideoApp, bumpMediaMetaGeneration, bumpThumbGeneration } from './video-app.js'; import { MusicApp, bumpMusicMetaGeneration } from './music-app.js'; +import { PhotosApp } from './photos-app.js'; import { VideoPlayer } from './video-player.js'; import { transfers } from './transfers.js'; @@ -18,6 +19,7 @@ const MAX_POOL_SIZE = 3; const DEBOUNCE_MS = 200; const SEARCH_VIDEO_ROOT = '__search__'; const SEARCH_AUDIO_ROOT = '__search__'; +const SEARCH_PHOTO_ROOTS = ['__search_photos__']; // -- Connection pool ---------------------------------------------------------- @@ -235,18 +237,15 @@ function SearchPage({ token, username, userId, groups, onPlayQueue, userPrefs }) return entry; }, [token, username, userId]); - // Eagerly connect to groups when entering Videos or Music view + // Pre-connect to all groups as soon as indexing finishes so thumbnails + // start loading before the user switches views. The pool evicts old + // connections but _thumbBlobCache keeps fetched thumbnails across evictions. useEffect(() => { - if (viewMode !== 'videos' && viewMode !== 'music') return; + if (fetching || indexedGroups.size === 0) return; let cancelled = false; (async () => { - const groupIds = [...indexedGroups.keys()].filter((gid) => { - const data = indexedGroups.get(gid); - if (viewMode === 'videos') return !!data.roots.videoRoot; - return !!data.roots.audioRoot; - }); - + const groupIds = [...indexedGroups.keys()]; for (let i = 0; i < groupIds.length; i += BATCH_SIZE) { if (cancelled) break; const batch = groupIds.slice(i, i + BATCH_SIZE); @@ -257,7 +256,7 @@ function SearchPage({ token, username, userId, groups, onPlayQueue, userPrefs }) })(); return () => { cancelled = true; }; - }, [viewMode, indexedGroups, connectGroup]); + }, [fetching, indexedGroups, connectGroup]); // -- Entry preparation -- @@ -364,6 +363,32 @@ function SearchPage({ token, username, userId, groups, onPlayQueue, userPrefs }) return result; }, [indexedGroups, q, matchesQuery, connectionGen]); + // Photos view: pre-filtered by photoRoots, path-prefixed + const photoEntries = useMemo(() => { + const result = []; + for (const [groupId, data] of indexedGroups) { + const roots = data.roots.photoRoots; + if (!roots || !roots.length) continue; + const conn = groupConns.current.get(groupId); + for (const e of data.entries) { + if (e.type !== 'image') continue; + const p = e.path || ''; + if (!roots.some((r) => p === r || p.startsWith(r + '/'))) continue; + if (q && !matchesQuery(e)) continue; + result.push({ + ...e, + path: '__search_photos__/' + e.path, + groupId, + groupName: data.groupName, + groupOwner: data.groupOwner, + _tRef: conn ? conn.tRef : null, + _gRef: conn ? conn.gRef : null, + }); + } + } + return result; + }, [indexedGroups, q, matchesQuery, connectionGen]); + // -- Callbacks -- const onPreview = useCallback(async (entry) => { @@ -417,7 +442,12 @@ function SearchPage({ token, username, userId, groups, onPlayQueue, userPrefs }) await downloadEntry(transfers, transport, modalGekRef.current, entry); }, []); - // No-op setters for FilesPanel (read-only mode) + const getTransport = useCallback(async (entry) => { + const conn = await connectGroup(entry.groupId); + return { transport: conn.transport, gek: conn.gek }; + }, [connectGroup]); + + // No-op setters for FilesPanel const noop = useCallback(() => {}, []); // -- Render -- @@ -456,6 +486,10 @@ function SearchPage({ token, username, userId, groups, onPlayQueue, userPrefs }) onClick=${() => setViewMode('music')} title=${t('search.view_music')}> <${Icon} name="music" /></button> + <button class=${viewMode === 'photos' ? 'active' : ''} + onClick=${() => setViewMode('photos')} + title=${t('search.view_photos')}> + <${Icon} name="image" /></button> </div> `} </div> @@ -497,7 +531,8 @@ function SearchPage({ token, username, userId, groups, onPlayQueue, userPrefs }) setError=${noop} onPreview=${onPreview} showGroup=${true} - readOnly=${true} /> + readOnly=${true} + getTransport=${getTransport} /> `} ${viewMode === 'videos' && hasResults && html` @@ -527,6 +562,19 @@ function SearchPage({ token, username, userId, groups, onPlayQueue, userPrefs }) hideFilter=${true} /> `} + ${viewMode === 'photos' && hasResults && html` + <${PhotosApp} + groupId="search" + transportRef=${defaultTRef} + gekRef=${defaultGRef} + status="connected" + entries=${photoEntries} + photoRoots=${SEARCH_PHOTO_ROOTS} + setError=${noop} + hideFilter=${true} + readOnly=${true} /> + `} + ${!hasResults && !fetching && html` <p class="page-message">${t('search.hint')}</p> `} |