import '@fontsource-variable/inter' import { useQueryClient } from '@tanstack/react-query' import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' import { useCallback, useEffect, useState } from 'react' import { Navigate, Route, BrowserRouter as Router, Routes } from 'react-router-dom' import './App.css' import { Dropdown } from './components/Dropdown' import { ErrorBoundary } from './components/ErrorBoundary' import { Main } from './components/Main' import './components/MediaList.css' import { Sidenav } from './components/Sidenav' import { AudioStorageContextProvider } from './context/AudioStorageContext/AudioStorageContextProvider' import { DownloadContextProvider } from './context/DownloadContext/DownloadContextProvider' import { useDropdownContext } from './context/DropdownContext/DropdownContext' import { DropdownContextProvider } from './context/DropdownContext/DropdownContextProvider' import { HistoryContextProvider } from './context/HistoryContext/HistoryContextProvider' import { JellyfinContextProvider } from './context/JellyfinContext/JellyfinContextProvider' import { PageTitleProvider } from './context/PageTitleContext/PageTitleProvider' import { PlaybackContextProvider } from './context/PlaybackContext/PlaybackContextProvider' import { ScrollContextProvider } from './context/ScrollContext/ScrollContextProvider' import { useSidenavContext } from './context/SidenavContext/SidenavContext' import { SidenavContextProvider } from './context/SidenavContext/SidenavContextProvider' import { ThemeContextProvider } from './context/ThemeContext/ThemeContextProvider' import { useDocumentTitle } from './hooks/useDocumentTitle' import { Album } from './pages/Album' import { AlbumArtists } from './pages/AlbumArtists' import { Albums } from './pages/Albums' import { Artist } from './pages/Artist' import { Artists } from './pages/Artists'; import { Discover } from './pages/Discover'; import { ArtistTracks } from './pages/ArtistTracks' import { Downloads } from './pages/Downloads' import { Favorites } from './pages/Favorites' import { FrequentlyPlayed } from './pages/FrequentlyPlayed' import { Genre } from './pages/Genre' import { Home } from './pages/Home' import { InstantMix } from './pages/InstantMix' import { Login } from './pages/Login' import { Lyrics } from './pages/Lyrics' import { NowPlaying } from './pages/NowPlaying' import { NowPlayingLyrics } from './pages/NowPlayingLyrics' import { Playlist } from './pages/Playlist' import { Queue } from './pages/Queue' import { RecentlyPlayed } from './pages/RecentlyPlayed' import { SearchResults } from './pages/SearchResults' import { SectionMore } from './pages/SectionMore' import { Settings } from './pages/Settings' import { Tracks } from './pages/Tracks' import { OnlineSearch } from './pages/OnlineSearch' import { persister, queryClient } from './queryClient' import { memoryManager } from './utils/memoryManager' export const App = () => { useEffect(() => { // Initialize memory manager const cleanup = memoryManager.onMemoryWarning(() => { console.log('Memory warning in App - cleaning up React Query cache'); queryClient.clear(); }); // Make query client available globally for memory manager (window as any).queryClient = queryClient; return cleanup; }, []); return ( ) } const RoutedApp = () => { const [auth, setAuth] = useState(() => { const savedAuth = localStorage.getItem('auth') return savedAuth ? JSON.parse(savedAuth) : null }) const [isLoggingOut, setIsLoggingOut] = useState(false) const queryClient = useQueryClient() const handleLogin = (authData: AuthData) => { setAuth(authData) localStorage.setItem('auth', JSON.stringify(authData)) } const handleLogout = async () => { setIsLoggingOut(true) localStorage.removeItem('repeatMode') setAuth(null) localStorage.removeItem('auth') setIsLoggingOut(false) queryClient.clear() await persister.removeClient() } useEffect(() => { if (!auth) { localStorage.removeItem('auth') } }, [auth]) useEffect(() => { const isWindows = /Win/.test(navigator.userAgent) const isChromium = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) const isEdge = /Edg/.test(navigator.userAgent) && /Microsoft Corporation/.test(navigator.vendor) const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream // eslint-disable-line @typescript-eslint/no-explicit-any const isAndroidPWA = /Android/.test(navigator.userAgent) && (window.matchMedia('(display-mode: standalone)').matches || (navigator as any).standalone) // eslint-disable-line @typescript-eslint/no-explicit-any if (isWindows && (isChromium || isEdge)) { document.getElementsByTagName('html')[0].classList.add('winOS') } if (isIOS) { document.getElementsByTagName('html')[0].classList.add('iOS') } // env safe area inset not supported or unreliable in android pwa? if (isAndroidPWA) { document.getElementsByTagName('html')[0].classList.add('safeAreaFallback') } // Needed for iOS, else you need to refresh page after changing orientation mode const updateOrientation = () => { const isIOSSafariNonPWA = isIOS && !window.matchMedia('(display-mode: standalone)').matches && !(navigator as any).standalone && // eslint-disable-line @typescript-eslint/no-explicit-any !window.matchMedia('(orientation: landscape)').matches const htmlElement = document.getElementsByTagName('html')[0] if (isIOS) { if (isIOSSafariNonPWA) { htmlElement.classList.add('safeAreaFallback') } else { htmlElement.classList.remove('safeAreaFallback') } } } updateOrientation() window.matchMedia('(orientation: landscape)').addEventListener('change', updateOrientation) return () => { window.matchMedia('(orientation: landscape)').removeEventListener('change', updateOrientation) } }, []) const actualApp = (
: } /> ) : ( ) } />
) return ( {actualApp} ) } interface AuthData { serverUrl: string token: string userId: string username: string } const MainLayout = ({ auth, handleLogout }: { auth: AuthData; handleLogout: () => void }) => { useDocumentTitle(); const memoSettings = useCallback(() => , [handleLogout]); const { showSidenav, toggleSidenav } = useSidenavContext() const dropdownContext = useDropdownContext() const { isOpen: isDropdownOpen } = dropdownContext const isTouchDevice = 'ontouchstart' in window return ( } /> } />
{/* Safe area spacer for iOS status bar */}
} /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } />
} /> ) }