mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
ea6ac49885
* 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.
78 lines
3.1 KiB
TypeScript
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
|
|
);
|
|
}
|