Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/common/src/utils/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export const SAVED_PAGE = '/favorites'
export const FAVORITES_PAGE = '/favorites'

export const LIBRARY_PAGE = '/library'
export const LIBRARY_TRACKS_PAGE = '/library/tracks'
export const LIBRARY_ALBUMS_PAGE = '/library/albums'
export const LIBRARY_PLAYLISTS_PAGE = '/library/playlists'
export const HISTORY_PAGE = '/history'
export const DASHBOARD_PAGE = '/dashboard'
export const AUDIO_PAGE = '/audio'
Expand Down
35 changes: 31 additions & 4 deletions packages/web/src/app/web-player/WebPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ const {
EXPLORE_PAGE,
SAVED_PAGE,
LIBRARY_PAGE,
LIBRARY_TRACKS_PAGE,
LIBRARY_ALBUMS_PAGE,
LIBRARY_PLAYLISTS_PAGE,
HISTORY_PAGE,
DASHBOARD_PAGE,
COIN_DETAIL_PAGE,
Expand Down Expand Up @@ -865,8 +868,20 @@ const WebPlayer = (props: WebPlayerProps) => {
/>
</>
)}
<Route path={SAVED_PAGE} element={<LibraryPage />} />
<Route path={LIBRARY_PAGE} element={<LibraryPage />} />
<Route
path={SAVED_PAGE}
element={<Navigate to={LIBRARY_TRACKS_PAGE} replace />}
/>
<Route
path={LIBRARY_PAGE}
element={<Navigate to={LIBRARY_TRACKS_PAGE} replace />}
/>
<Route path={LIBRARY_TRACKS_PAGE} element={<LibraryPage />} />
<Route path={LIBRARY_ALBUMS_PAGE} element={<LibraryPage />} />
<Route
path={LIBRARY_PLAYLISTS_PAGE}
element={<LibraryPage />}
/>
<Route path={HISTORY_PAGE} element={<HistoryPage />} />
{!isProduction ? (
<Route path={DEV_TOOLS_PAGE} element={<DevTools />} />
Expand Down Expand Up @@ -1271,8 +1286,20 @@ const WebPlayer = (props: WebPlayerProps) => {
path={UPLOAD_PAGE}
element={<UploadPage scrollToTop={scrollToTop} />}
/>
<Route path={SAVED_PAGE} element={<LibraryPage />} />
<Route path={LIBRARY_PAGE} element={<LibraryPage />} />
<Route
path={SAVED_PAGE}
element={<Navigate to={LIBRARY_TRACKS_PAGE} replace />}
/>
<Route
path={LIBRARY_PAGE}
element={<Navigate to={LIBRARY_TRACKS_PAGE} replace />}
/>
<Route path={LIBRARY_TRACKS_PAGE} element={<LibraryPage />} />
<Route path={LIBRARY_ALBUMS_PAGE} element={<LibraryPage />} />
<Route
path={LIBRARY_PLAYLISTS_PAGE}
element={<LibraryPage />}
/>
<Route path={HISTORY_PAGE} element={<HistoryPage />} />
{!isProduction ? (
<Route path={DEV_TOOLS_PAGE} element={<DevTools />} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ const LibraryPage = () => {

const { tabs, body } = useTabs({
isMobile: false,
selectedTabLabel: currentTab,
didChangeTabsFrom: (_, to) => {
onChangeTab(to as LibraryPageTabs)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ const LibraryPage = () => {
const { tabs, body } = useTabs({
tabs: tabHeaders,
elements,
selectedTabLabel: currentTab,
initialScrollOffset: SCROLL_HEIGHT,
onTabClick: handleTabClick
})
Expand Down
125 changes: 115 additions & 10 deletions packages/web/src/pages/library-page/hooks/useLibraryPage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'

import {
useCurrentAccount,
Expand All @@ -19,6 +19,7 @@ import {
libraryPageTracksLineupActions as tracksActions,
libraryPageActions as saveActions,
libraryPageSelectors,
LibraryCategory,
LibraryPageTabs,
queueSelectors,
tracksSocialActions as socialActions,
Expand All @@ -34,15 +35,29 @@ import { route } from '@audius/common/utils'
import { GetUserLibraryTracksSortMethodEnum } from '@audius/sdk'
import { debounce } from 'lodash'
import { useDispatch, useSelector } from 'react-redux'
import { useLocation, useNavigate, useSearchParams } from 'react-router'

import { TrackEvent, make } from 'common/store/analytics/actions'
import { push } from 'utils/navigation'

import {
getTabFromPathname,
getLibraryPath,
categoryFromFilterParam,
filterParamFromCategory,
LIBRARY_FILTER_PARAM,
LIBRARY_SEARCH_PARAM
} from '../lib/libraryUrl'

const { profilePage } = route
const { makeGetCurrent } = queueSelectors
const { getPlaying, getBuffering } = playerSelectors
const { getLibraryTracksLineup, hasReachedEnd, getTracksCategory } =
libraryPageSelectors
const {
getLibraryTracksLineup,
hasReachedEnd,
getTracksCategory,
getCategory
} = libraryPageSelectors
const { updatedPlaylistViewed } = playlistUpdatesActions

const { selectAllPlaylistUpdateIds } = playlistUpdatesSelectors
Expand Down Expand Up @@ -77,6 +92,11 @@ type LibraryPageState = {

export const useLibraryPage = () => {
const dispatch = useDispatch()
const location = useLocation()
const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams()
const lastCategoryUrlRef = useRef<string | null>(null)

const currentTrack = useCurrentTrack()
const tracks = useLineupTable(getLibraryTracksLineup)

Expand All @@ -101,16 +121,70 @@ export const useLibraryPage = () => {
}
})

const urlTab = getTabFromPathname(location.pathname)
const urlFilter = searchParams.get(LIBRARY_FILTER_PARAM)
const urlSearch = searchParams.get(LIBRARY_SEARCH_PARAM) ?? ''

const [state, setState] = useState<LibraryPageState>({
filterText: '',
filterText: urlSearch,
sortMethod: '',
sortDirection: '',
initialOrder: null,
allTracksFetched: false,
currentTab: ProfileTabs.TRACKS,
currentTab: urlTab,
shouldReturnToTrackPurchases: false
})

const selectedCategoryForUrlTab = useSelector(
(state: Parameters<typeof getCategory>[0]) =>
getCategory(state, { currentTab: urlTab })
)

// Sync from URL to state and Redux when location changes
useEffect(() => {
const tab = getTabFromPathname(location.pathname)
let category = categoryFromFilterParam(urlFilter)
if (
tab === LibraryPageTabs.PLAYLISTS &&
category === LibraryCategory.Purchase
) {
category = LibraryCategory.All
}
const search = urlSearch

lastCategoryUrlRef.current = filterParamFromCategory(category)
setState((prev) => ({
...prev,
currentTab: tab,
filterText: search
}))
dispatch(
saveActions.setSelectedCategory({
currentTab: tab,
category
})
)
}, [location.pathname, urlFilter, urlSearch, dispatch])

// When user changes category via menu (Redux updates), sync to URL
useEffect(() => {
const urlCategoryParam = filterParamFromCategory(selectedCategoryForUrlTab)
if (lastCategoryUrlRef.current === urlCategoryParam) return
lastCategoryUrlRef.current = urlCategoryParam
setSearchParams(
(prev) => {
const next = new URLSearchParams(prev)
if (urlCategoryParam === 'all') {
next.delete(LIBRARY_FILTER_PARAM)
} else {
next.set(LIBRARY_FILTER_PARAM, urlCategoryParam)
}
return next
},
{ replace: true }
)
}, [selectedCategoryForUrlTab, setSearchParams])

const fetchLibraryTracks = useCallback(
(
query?: string,
Expand Down Expand Up @@ -310,17 +384,42 @@ export const useLibraryPage = () => {
handleFetchSavedTracks()
}, [tracksCategory, handleFetchSavedTracks])

const updateSearchParam = useCallback(
(search: string) => {
setSearchParams(
(prev) => {
const next = new URLSearchParams(prev)
if (search.trim() === '') {
next.delete(LIBRARY_SEARCH_PARAM)
} else {
next.set(LIBRARY_SEARCH_PARAM, search)
}
return next
},
{ replace: true }
)
},
[setSearchParams]
)

const debouncedUpdateSearchParam = useMemo(
() => debounce(updateSearchParam, 300),
[updateSearchParam]
)

const onFilterChange = useCallback(
(e: any) => {
const value = e.target.value
const callBack = !state.allTracksFetched
? handleFetchSavedTracks
: undefined
setState((prev) => ({ ...prev, filterText: e.target.value }))
setState((prev) => ({ ...prev, filterText: value }))
debouncedUpdateSearchParam(value)
if (callBack) {
callBack()
}
},
[state.allTracksFetched, handleFetchSavedTracks]
[state.allTracksFetched, handleFetchSavedTracks, debouncedUpdateSearchParam]
)

const onSortChange = useCallback(
Expand Down Expand Up @@ -554,9 +653,15 @@ export const useLibraryPage = () => {
[formatMetadata, tracks.entries, state.initialOrder, updateLineupOrder]
)

const onChangeTab = useCallback((tab: LibraryPageTabs) => {
setState((prev) => ({ ...prev, currentTab: tab }))
}, [])
const onChangeTab = useCallback(
(tab: LibraryPageTabs) => {
setState((prev) => ({ ...prev, currentTab: tab }))
const path = getLibraryPath(tab)
const search = searchParams.toString()
navigate(search ? `${path}?${search}` : path)
},
[navigate, searchParams]
)

const isQueuedValue = isQueued()
const playingUid = getPlayingUid()
Expand Down
83 changes: 83 additions & 0 deletions packages/web/src/pages/library-page/lib/libraryUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
LibraryCategory,
LibraryPageTabs,
LibraryCategoryType
} from '@audius/common/store'
import { route } from '@audius/common/utils'

const { LIBRARY_ALBUMS_PAGE, LIBRARY_PLAYLISTS_PAGE, LIBRARY_TRACKS_PAGE } =
route

export const LIBRARY_FILTER_PARAM = 'filter'
export const LIBRARY_SEARCH_PARAM = 'search'

export const LIBRARY_TAB_PATHS = [
LIBRARY_TRACKS_PAGE,
LIBRARY_ALBUMS_PAGE,
LIBRARY_PLAYLISTS_PAGE
] as const

const PATH_TO_TAB: Record<string, LibraryPageTabs> = {
[LIBRARY_TRACKS_PAGE]: LibraryPageTabs.TRACKS,
[LIBRARY_ALBUMS_PAGE]: LibraryPageTabs.ALBUMS,
[LIBRARY_PLAYLISTS_PAGE]: LibraryPageTabs.PLAYLISTS
}

const TAB_TO_PATH: Record<LibraryPageTabs, string> = {
[LibraryPageTabs.TRACKS]: LIBRARY_TRACKS_PAGE,
[LibraryPageTabs.ALBUMS]: LIBRARY_ALBUMS_PAGE,
[LibraryPageTabs.PLAYLISTS]: LIBRARY_PLAYLISTS_PAGE
}

/** URL filter values: all, favorites, reposts, premium (premium = purchase) */
export const FILTER_URL_VALUES = [
'all',
'favorites',
'reposts',
'premium'
] as const
export type LibraryFilterParam = (typeof FILTER_URL_VALUES)[number]

const URL_FILTER_TO_CATEGORY: Record<string, LibraryCategoryType> = {
all: LibraryCategory.All,
favorites: LibraryCategory.Favorite,
reposts: LibraryCategory.Repost,
premium: LibraryCategory.Purchase
}

const CATEGORY_TO_URL_FILTER: Record<LibraryCategoryType, LibraryFilterParam> =
{
[LibraryCategory.All]: 'all',
[LibraryCategory.Favorite]: 'favorites',
[LibraryCategory.Repost]: 'reposts',
[LibraryCategory.Purchase]: 'premium'
}

export function getTabFromPathname(pathname: string): LibraryPageTabs {
const path = LIBRARY_TAB_PATHS.find((p) => pathname === p)
return path ? PATH_TO_TAB[path] : LibraryPageTabs.TRACKS
}

export function getLibraryPath(tab: LibraryPageTabs): string {
return TAB_TO_PATH[tab] ?? LIBRARY_TRACKS_PAGE
}

export function categoryFromFilterParam(
param: string | null
): LibraryCategoryType {
if (!param) return LibraryCategory.All
const category = URL_FILTER_TO_CATEGORY[param.toLowerCase()]
return category ?? LibraryCategory.All
}

export function filterParamFromCategory(
category: LibraryCategoryType
): LibraryFilterParam {
return CATEGORY_TO_URL_FILTER[category] ?? 'all'
}

export function isLibraryFilterParam(
value: string
): value is LibraryFilterParam {
return FILTER_URL_VALUES.includes(value as LibraryFilterParam)
}
5 changes: 4 additions & 1 deletion packages/web/src/ssr/library/+route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { makePageRoute } from 'ssr/util'

export default makePageRoute(['/library'], 'Library Page')
export default makePageRoute(
['/library', '/library/tracks', '/library/albums', '/library/playlists'],
'Library Page'
)