import '@fontsource-variable/inter'
import { QueryClientProvider, 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 { 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 { Settings } from './pages/Settings'
import { Tracks } from './pages/Tracks'
import { persister, queryClient } from './queryClient'
export const App = () => {
return (
{window.__NPM_LIFECYCLE_EVENT__ === 'dev:nocache' ? (
) : (
)}
)
}
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 { showSidenav, toggleSidenav } = useSidenavContext()
const dropdownContext = useDropdownContext()
const isDropdownOpen = dropdownContext?.isOpen || false
const isTouchDevice = dropdownContext?.isTouchDevice || false
const memoSettings = useCallback(() => {
return
}, [handleLogout])
return (
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
}
/>
} />
}
/>
} />
} />
} />
} />
} />
} />
} />
} />
} />
}
/>
)
}