import { ArrowLeftIcon, BookmarkFillIcon, ChevronDownIcon, HeartFillIcon } from '@primer/octicons-react' import { JSX, memo, useCallback, useEffect, useMemo, useRef } from 'react' import { Link, useLocation } from 'react-router-dom' import { useFilterContext } from '../context/FilterContext/FilterContext' import { FilterContextProvider } from '../context/FilterContext/FilterContextProvider' import { HeaderSearchBar } from './HeaderSearchBar' import { useHistoryContext } from '../context/HistoryContext/HistoryContext' import { usePageTitle } from '../context/PageTitleContext/PageTitleContext' import { usePlaybackContext } from '../context/PlaybackContext/PlaybackContext' import { useSidenavContext } from '../context/SidenavContext/SidenavContext' import { useDuration } from '../hooks/useDuration' import { getPageTitle } from '../utils/titleUtils' import { JellyImg } from './JellyImg' import { Squircle } from './Squircle' import { AlbumIcon, ArtistsIcon, ExpandIcon, PlaylistIcon, SortingIcon, TrackIcon, TracksIcon } from './SvgIcons' // import { SearchBar } from './SearchBar' export const Main = (props: Parameters[0]) => { return ( ) } export const MainContent = ({ content: Content, filterType, }: { content: () => JSX.Element filterType?: 'mediaItems' | 'favorites' | 'kind' | 'mediaItemsPlaylist' }) => { const playback = usePlaybackContext() const { pageTitle } = usePageTitle() const { goBack: previousPage } = useHistoryContext() const location = useLocation() const { toggleSidenav } = useSidenavContext() const { filter, setFilter } = useFilterContext() const { currentTrack } = usePlaybackContext() useEffect(() => { window.scrollTo({ top: 0, behavior: 'instant' }) }, [location.pathname]) const memoHeader = useMemo(() => { return (
{getPageTitle(pageTitle, location)}
{location.pathname.startsWith('/album/') && pageTitle && (
)} {pageTitle && (location.pathname.match(/^\/artist\/[^/]+\/tracks$/) ? (
) : location.pathname.startsWith('/artist/') ? (
) : null)} {location.pathname.startsWith('/genre/') && pageTitle && (
)} {location.pathname.startsWith('/playlist/') && pageTitle && (
)}
{/* Search Bar */}
{(filterType === 'mediaItems' || filterType === 'mediaItemsPlaylist') && (
{filter.sort !== 'Inherit' && (
{ setFilter(c => ({ ...c, order: c.order === 'Ascending' ? 'Descending' : 'Ascending', })) }} title={filter.order === 'Ascending' ? 'Ascending' : 'Descending'} >
)}
)} {(filterType === 'favorites' || filterType === 'kind') && ( <>
{filter.kind === 'Tracks' && ( )} {filter.kind === 'Albums' && ( )} {filter.kind === 'Artists' && ( )}
{filterType === 'favorites' && (
{ setFilter(c => ({ ...c, order: c.order === 'Ascending' ? 'Descending' : 'Ascending', })) }} title={filter.order === 'Ascending' ? 'Ascending' : 'Descending'} >
)} )} {/* */}
) }, [ filter.kind, filter.order, filter.sort, filterType, location, pageTitle, previousPage, setFilter, toggleSidenav, ]) const memoContent = useMemo(() => { return (
) }, [Content]) const memoFooter = useMemo(() => { return (
{currentTrack && ( )} {!currentTrack && (
)}
{playback.currentTrack?.Name ? ( {playback.currentTrack.Name} ) : ( 'No Track Played' )}
{playback.currentTrack?.UserData?.IsFavorite && ( )}
{playback.currentTrack && playback.currentTrack.ArtistItems && playback.currentTrack.ArtistItems.length > 0 ? (() => { const artistItems = playback.currentTrack.ArtistItems return artistItems.map((artist, index) => ( {artist.Name} {index < artistItems.length - 1 && ', '} )) })() : playback.currentTrack?.Artists?.join(', ') || playback.currentTrack?.AlbumArtist || 'No Artist'}
{playback.currentTrack?.Album ? ( {playback.currentTrack.Album} ) : (
No Album
)}
) }, [ currentTrack, playback.isPlaying, playback.nextTrack, playback.previousTrack, playback.repeat, playback.shuffle, playback.togglePlayPause, playback.toggleRepeat, playback.toggleShuffle, ]) return (
{memoHeader} {memoContent} {memoFooter}
) } export const Progressbar = () => { const playback = usePlaybackContext() const audio = playback.audioRef.current as HTMLAudioElement | undefined const progressRef = useRef(null) const trackRef = useRef(null) const bufferRef = useRef(false) const calcDuration = useCallback(() => { return audio?.duration || 0 }, [audio]) const calcProgress = useCallback(() => { if (!audio) return 0 const d = calcDuration() const t = audio.currentTime || 0 if (d > 0) { return (t / d) * 100 } return 0 }, [audio, calcDuration]) const restoreProgress = useCallback(() => { if (bufferRef.current) { return } progressRef.current?.style.setProperty('--transition-duration', `0s`) progressRef.current?.style.setProperty('--progress-width', `${calcProgress()}%`) if (playback.isPlaying && audio?.currentTime) { void progressRef.current?.offsetWidth // Trigger reflow progressRef.current?.style.setProperty('--transition-duration', `${calcDuration() - audio.currentTime}s`) progressRef.current?.style.setProperty('--progress-width', `100%`) } }, [audio?.currentTime, calcDuration, calcProgress, playback.isPlaying]) const calcBuffered = useCallback(() => { if (!audio) return 0 if (audio.buffered.length > 0) { const end = audio.buffered.end(audio.buffered.length - 1) return (end / (audio.duration || 1)) * 100 } return 0 }, [audio]) useEffect(() => { if (!audio) return progressRef.current?.style.setProperty('--progress-width', `${calcProgress()}%`) progressRef.current?.style.setProperty('--transition-duration', `${calcDuration()}s`) }, [audio, calcDuration, calcProgress]) useEffect(() => { if (!audio) return if (playback.isPlaying) { restoreProgress() } else { progressRef.current?.style.setProperty('--progress-width', `${calcProgress()}%`) progressRef.current?.style.setProperty('--transition-duration', `0s`) } }, [audio, audio?.src, calcProgress, playback.isPlaying, restoreProgress]) useEffect(() => { if (!audio) return const handleTimeUpdate = () => { if (!audio.duration) { return } restoreProgress() } const handleBuffered = () => { progressRef.current?.style.setProperty('--buffered-width', `${calcBuffered()}%`) } const handleEmptied = () => { progressRef.current?.style.setProperty('--buffered-width', `0%`) progressRef.current?.style.setProperty('--progress-width', `0%`) progressRef.current?.style.setProperty('--transition-duration', `0s`) void progressRef.current?.offsetWidth // Trigger reflow } const handleChange = () => { progressRef.current?.style.setProperty('--progress-width', `${calcProgress()}%`) progressRef.current?.style.setProperty('--transition-duration', `${calcDuration()}s`) } const handleWaiting = () => { bufferRef.current = true progressRef.current?.classList.add('loading') progressRef.current?.style.setProperty('--transition-duration', `0s`) progressRef.current?.style.setProperty('--progress-width', `${calcProgress()}%`) } const handlePlaying = () => { bufferRef.current = false progressRef.current?.classList.remove('loading') restoreProgress() } audio.addEventListener('timeupdate', handleTimeUpdate) audio.addEventListener('emptied', handleEmptied) audio.addEventListener('progress', handleBuffered) audio.addEventListener('loadedmetadata', handleChange) audio.addEventListener('waiting', handleWaiting) audio.addEventListener('playing', handlePlaying) return () => { audio.removeEventListener('timeupdate', handleTimeUpdate) audio.removeEventListener('emptied', handleEmptied) audio.removeEventListener('progress', handleBuffered) audio.removeEventListener('loadedmetadata', handleChange) audio.removeEventListener('waiting', handleWaiting) audio.removeEventListener('playing', handlePlaying) } }, [audio, calcBuffered, calcDuration, calcProgress, restoreProgress]) // better progress bar support iOS const handleTouchMove = (e: React.TouchEvent) => { e.preventDefault() if (!audio || !progressRef.current || !trackRef.current) return const rect = trackRef.current.getBoundingClientRect() const percent = Math.min(Math.max((e.touches[0].clientX - rect.left) / rect.width, 0), 1) const newTime = percent * calcDuration() audio.currentTime = newTime progressRef.current.value = newTime.toString() restoreProgress() } return (
{ if (!audio) return const t = parseFloat(e.currentTarget.value) audio.currentTime = t restoreProgress() }} />
) } const Duration = memo(() => { const playback = usePlaybackContext() const duration = useDuration() return (
{playback.formatTime(duration.progress)}
/
{playback.formatTime(duration.duration)}
) })