Files
psysonic/src/components/MobileMoreOverlay.tsx
T
cucadmuh ea6ac49885 feat(offline): Offline Library shows cached albums from all servers (#719)
* feat(offline): show cached albums from all servers in Offline Library

List every offline album regardless of active server; load cover art per
source server and switch before play/enqueue. Sidebar, mobile nav, and
disconnect auto-nav use any cached content; multi-server cards show a label.

* docs(changelog): link offline library PR #719

* chore(pr-719): address review — CHANGELOG in Added, helper tests

Move release note to ## Added per team changelog policy; cover
offlineAlbumCoverArt and ensureServerForOfflineAlbum in unit tests.
2026-05-15 17:20:19 +03:00

78 lines
3.1 KiB
TypeScript

import { createPortal } from 'react-dom';
import { NavLink } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Settings, HardDriveDownload } from 'lucide-react';
import { useSidebarStore } from '../store/sidebarStore';
import { useAuthStore } from '../store/authStore';
import { useOfflineStore } from '../store/offlineStore';
import { ALL_NAV_ITEMS } from '../config/navItems';
import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable';
import { hasAnyOfflineAlbums } from '../utils/offline/offlineLibraryHelpers';
const BOTTOM_NAV_ROUTES = new Set(['/', '/albums', '/now-playing']);
export default function MobileMoreOverlay({ onClose }: { onClose: () => void }) {
const { t } = useTranslation();
const sidebarItems = useSidebarStore(s => s.items);
const randomNavMode = useAuthStore(s => s.randomNavMode);
const offlineAlbums = useOfflineStore(s => s.albums);
const hasOfflineContent = hasAnyOfflineAlbums(offlineAlbums);
const luckyMixBase = useLuckyMixAvailable();
const luckyMixAvailable = luckyMixBase && randomNavMode === 'separate';
const items = sidebarItems
.filter(cfg => {
if (!cfg?.visible) return false;
const item = ALL_NAV_ITEMS[cfg.id];
if (!item) return false;
if (BOTTOM_NAV_ROUTES.has(item.to)) return false;
if (randomNavMode === 'hub' && (cfg.id === 'randomMix' || cfg.id === 'randomAlbums')) return false;
if (randomNavMode === 'separate' && cfg.id === 'randomPicker') return false;
if (cfg.id === 'luckyMix' && !luckyMixAvailable) return false;
return true;
})
.map(cfg => ALL_NAV_ITEMS[cfg.id]);
return createPortal(
<>
<div className="mobile-more-backdrop" onClick={onClose} />
<div className="mobile-more-sheet">
<div className="mobile-more-handle" />
<div className="mobile-more-grid">
{items.map(item => (
<NavLink
key={item.to}
to={item.to}
end={item.to === '/'}
className={({ isActive }) => `mobile-more-item${isActive ? ' active' : ''}`}
onClick={onClose}
>
<span className="mobile-more-icon"><item.icon size={24} /></span>
<span className="mobile-more-label">{t(item.labelKey)}</span>
</NavLink>
))}
{hasOfflineContent && (
<NavLink
to="/offline"
className={({ isActive }) => `mobile-more-item${isActive ? ' active' : ''}`}
onClick={onClose}
>
<span className="mobile-more-icon"><HardDriveDownload size={24} /></span>
<span className="mobile-more-label">{t('sidebar.offlineLibrary')}</span>
</NavLink>
)}
<NavLink
to="/settings"
className={({ isActive }) => `mobile-more-item${isActive ? ' active' : ''}`}
onClick={onClose}
>
<span className="mobile-more-icon"><Settings size={24} /></span>
<span className="mobile-more-label">{t('sidebar.settings')}</span>
</NavLink>
</div>
</div>
</>,
document.body
);
}