@ -11,7 +11,6 @@ import ListViewType from '../viewtypes/ListViewType';
import useSearchQuery from '../../hooks/useSearchQuery' ;
import GenericPageHeader from '../layout/GenericPageHeader' ;
import GenericPage from '../layout/GenericPage' ;
import PageLoader from '../loader/PageLoader' ;
import { useAppDispatch , useAppSelector } from '../../redux/hooks' ;
import {
toggleSelected ,
@ -27,7 +26,6 @@ import {
StyledTag ,
} from '../shared/styled' ;
import { FilterButton , RefreshButton } from '../shared/ToolbarButtons' ;
import { setActive , setAdvancedFilters } from '../../redux/albumSlice' ;
import { setSearchQuery } from '../../redux/miscSlice' ;
import { apiController } from '../../api/controller' ;
import { Item , Server } from '../../types' ;
@ -35,6 +33,9 @@ import AdvancedFilters from './AdvancedFilters';
import useAdvancedFilter from '../../hooks/useAdvancedFilter' ;
import ColumnSort from '../shared/ColumnSort' ;
import useColumnSort from '../../hooks/useColumnSort' ;
import { setFilter , setPagination , setAdvancedFilters , setColumnSort } from '../../redux/viewSlice' ;
import useGridScroll from '../../hooks/useGridScroll' ;
import useListScroll from '../../hooks/useListScroll' ;
export const ALBUM_SORT_TYPES = [
{ label : i18next.t ( 'A-Z (Name)' ) , value : 'alphabeticalByName' , role : i18next.t ( 'Default' ) } ,
@ -51,43 +52,65 @@ const AlbumList = () => {
const history = useHistory ( ) ;
const queryClient = useQueryClient ( ) ;
const folder = useAppSelector ( ( state ) = > state . folder ) ;
const album = useAppSelector ( ( state ) = > state . album ) ;
const config = useAppSelector ( ( state ) = > state . config ) ;
const misc = useAppSelector ( ( state ) = > state . misc ) ;
const view = useAppSelector ( ( state ) = > state . view ) ;
const [ isRefreshing , setIsRefreshing ] = useState ( false ) ;
const [ sortTypes , setSortTypes ] = useState < any [ ] > ( [ ] ) ;
const [ viewType , setViewType ] = useState ( settings . getSync ( 'albumViewType' ) ) ;
const [ musicFolder , setMusicFolder ] = useState ( undefined ) ;
const [ musicFolder , setMusicFolder ] = useState ( { loaded : false , id : undefined } ) ;
const albumFilterPickerContainerRef = useRef ( null ) ;
const [ isRefresh , setIsRefresh ] = useState ( false ) ;
const [ currentQueryKey , setCurrentQueryKey ] = useState < any > ( [ 'albumList' ] ) ;
const gridRef = useRef < any > ( ) ;
const listRef = useRef < any > ( ) ;
const { gridScroll } = useGridScroll ( gridRef ) ;
const { listScroll } = useListScroll ( listRef ) ;
useEffect ( ( ) = > {
if ( folder . applied . albums ) {
setMusicFolder ( folder . musicFolder ) ;
setMusicFolder ( { loaded : true , id : folder.musicFolder } ) ;
} else {
setMusicFolder ( { loaded : true , id : undefined } ) ;
}
} , [ folder . applied . albums , folder . musicFolder ] ) ;
useEffect ( ( ) = > {
if ( config . serverType === Server . Subsonic || ! view . album . pagination . serverSide ) {
// Client-side paging won't require a separate key for the active page
setCurrentQueryKey ( [ 'albumList' , view . album . filter , musicFolder . id ] ) ;
} else {
setCurrentQueryKey ( [ 'albumList' , view . album . filter , view . album . pagination , musicFolder . id ] ) ;
}
} , [ folder ] ) ;
} , [ con fig . serverType , musicF older. id , view . album . filter , view . album . pagination ] ) ;
const { isLoading , isError , data : albums , error } : any = useQuery (
[ 'albumList' , album . active . filter , musicFolder ] ,
currentQueryKey ,
( ) = >
album . active . filter === 'random'
view . album . filter === 'random' ||
( view . album . pagination . recordsPerPage !== 0 && view . album . pagination . serverSide )
? apiController ( {
serverType : config.serverType ,
endpoint : 'getAlbums' ,
args :
config . serverType === Server . Subsonic
? {
type : 'random' ,
size : 1 00,
type : view . album . filter ,
size : 5 00,
offset : 0 ,
musicFolderId : musicFolder ,
recursive : view.album.filter !== 'random' ,
}
: {
type : 'random' ,
size : 100 ,
offset : 0 ,
type : view . album . filter ,
size :
view . album . pagination . recordsPerPage === 0
? 100
: view . album . pagination . recordsPerPage ,
offset :
( view . album . pagination . activePage - 1 ) * view . album . pagination . recordsPerPage ,
recursive : false ,
musicFolderId : musicFolder ,
musicFolderId : musicFolder.id ,
} ,
} )
: apiController ( {
@ -96,23 +119,31 @@ const AlbumList = () => {
args :
config . serverType === Server . Subsonic
? {
type : album . active . filter ,
type : view . album . filter ,
size : 500 ,
offset : 0 ,
musicFolderId : musicFolder ,
musicFolderId : musicFolder.id ,
recursive : true ,
}
: {
type : album . active . filter ,
type : view . album . filter ,
recursive : true ,
musicFolderId : musicFolder ,
musicFolderId : musicFolder.id ,
} ,
} ) ,
{
cacheTime : 3600000 , // Stay in cache for 1 hour
staleTime : Infinity , // Only allow manual refresh
cacheTime :
view . album . pagination . recordsPerPage !== 0 && config . serverType === Server . Jellyfin
? 600000
: Infinity ,
staleTime :
view . album . pagination . recordsPerPage !== 0 && config . serverType === Server . Jellyfin
? 600000
: Infinity ,
enabled : currentQueryKey !== [ 'albumList' ] && musicFolder . loaded ,
}
) ;
const { data : genres } : any = useQuery ( [ 'genreList' ] , async ( ) = > {
const res = await apiController ( {
serverType : config.serverType ,
@ -131,7 +162,7 @@ const AlbumList = () => {
} ) ;
} ) ;
const searchedData = useSearchQuery ( misc . searchQuery , albums , [
const searchedData = useSearchQuery ( misc . searchQuery , albums ? . data , [
'title' ,
'artist' ,
'genre' ,
@ -145,18 +176,36 @@ const AlbumList = () => {
byGenreData ,
byStarredData ,
byYearData ,
} = useAdvancedFilter ( albums , album . advancedFilters ) ;
} = useAdvancedFilter ( albums ? . data , view . album . advancedFilters ) ;
const { sortColumns , sortedData } = useColumnSort (
filteredData ,
Item . Album ,
album . advancedFilters . properties . sort
) ;
const { sortColumns , sortedData } = useColumnSort ( filteredData , Item . Album , view . album . sort ) ;
useEffect ( ( ) = > {
setSortTypes ( _ . compact ( _ . concat ( ALBUM_SORT_TYPES , genres ) ) ) ;
} , [ genres ] ) ;
useEffect ( ( ) = > {
if ( albums ? . data && sortedData ? . length ) {
const pages =
Math . floor (
( view . album . pagination . serverSide && config . serverType === Server . Jellyfin
? albums ? . totalRecordCount
: sortedData ? . length ) / view . album . pagination . recordsPerPage
) + 1 ;
if ( pages && view . album . pagination . pages !== pages ) {
dispatch (
setPagination ( {
listType : Item.Album ,
data : {
pages ,
} ,
} )
) ;
}
}
} , [ albums , config . serverType , dispatch , sortedData ? . length , view . album . pagination ] ) ;
let timeout : any = null ;
const handleRowClick = ( e : any , rowData : any , tableData : any ) = > {
if ( timeout === null ) {
@ -194,10 +243,10 @@ const AlbumList = () => {
endpoint : 'star' ,
args : { id : rowData.id , type : 'album' } ,
} ) ;
queryClient . setQueryData ( [ 'albumList' , album . active . filter , musicFolder ] , ( oldData : any ) = > {
const starredIndices = _ . keys ( _ . pickBy ( oldData , { id : rowData.id } ) ) ;
queryClient . setQueryData ( [ 'albumList' , view . album . filter , musicFolder . id ] , ( oldData : any ) = > {
const starredIndices = _ . keys ( _ . pickBy ( oldData . data , { id : rowData.id } ) ) ;
starredIndices . forEach ( ( index ) = > {
oldData [ index ] . starred = Date . now ( ) ;
oldData . data [ index ] . starred = Date . now ( ) ;
} ) ;
return oldData ;
@ -208,10 +257,10 @@ const AlbumList = () => {
endpoint : 'unstar' ,
args : { id : rowData.id , type : 'album' } ,
} ) ;
queryClient . setQueryData ( [ 'albumList' , album . active . filter , musicFolder ] , ( oldData : any ) = > {
const starredIndices = _ . keys ( _ . pickBy ( oldData , { id : rowData.id } ) ) ;
queryClient . setQueryData ( [ 'albumList' , view . album . filter , musicFolder . id ] , ( oldData : any ) = > {
const starredIndices = _ . keys ( _ . pickBy ( oldData . data , { id : rowData.id } ) ) ;
starredIndices . forEach ( ( index ) = > {
oldData [ index ] . starred = undefined ;
oldData . data [ index ] . starred = undefined ;
} ) ;
return oldData ;
@ -226,10 +275,10 @@ const AlbumList = () => {
args : { ids : [ rowData . id ] , rating : e } ,
} ) ;
queryClient . setQueryData ( [ 'albumList' , album . active . filter , musicFolder ] , ( oldData : any ) = > {
const ratedIndices = _ . keys ( _ . pickBy ( oldData , { id : rowData.id } ) ) ;
queryClient . setQueryData ( [ 'albumList' , view . album . filter , musicFolder . id ] , ( oldData : any ) = > {
const ratedIndices = _ . keys ( _ . pickBy ( oldData . data , { id : rowData.id } ) ) ;
ratedIndices . forEach ( ( index ) = > {
oldData [ index ] . userRating = e ;
oldData . data [ index ] . userRating = e ;
} ) ;
return oldData ;
@ -256,8 +305,8 @@ const AlbumList = () => {
container = { ( ) = > albumFilterPickerContainerRef . current }
size = "sm"
width = { 180 }
defaultValue = { album . active . filter }
value = { album . active . filter }
defaultValue = { view . album . filter }
value = { view . album . filter }
groupBy = "role"
data = { sortTypes || ALBUM_SORT_TYPES }
disabledItemValues = {
@ -266,17 +315,18 @@ const AlbumList = () => {
cleanable = { false }
placeholder = { t ( 'Sort Type' ) }
onChange = { async ( value : string ) = > {
setIsRefresh ( true ) ;
await queryClient . cancelQueries ( [
'albumList' ,
album . active . filter ,
musicFolder ,
view . album . filter ,
musicFolder . id ,
] ) ;
dispatch ( setSearchQuery ( '' ) ) ;
dispatch ( setActive ( { . . . album . active , filter : value } ) ) ;
dispatch ( setFilter ( { listType : Item.Album , data : value } ) ) ;
dispatch ( setPagination ( { listType : Item.Album , data : { activePage : 1 } } ) ) ;
localStorage . setItem ( 'scroll_grid_albumList' , '0' ) ;
localStorage . setItem ( 'scroll_list_albumList' , '0' ) ;
setIsRefresh ( false ) ;
gridScroll ( 0 ) ;
listScroll ( 0 ) ;
} }
/ >
< RefreshButton onClick = { handleRefresh } size = "sm" loading = { isRefreshing } / >
@ -293,8 +343,12 @@ const AlbumList = () => {
speaker = {
< StyledPopover width = "275px" opacity = { 0.97 } >
< Nav
activeKey = { album . advancedFilters . nav }
onSelect = { ( e ) = > dispatch ( setAdvancedFilters ( { filter : 'nav' , value : e } ) ) }
activeKey = { view . album . advancedFilters . nav }
onSelect = { ( e ) = >
dispatch (
setAdvancedFilters ( { listType : Item.Album , filter : 'nav' , value : e } )
)
}
justified
appearance = "tabs"
>
@ -302,7 +356,7 @@ const AlbumList = () => {
< StyledNavItem eventKey = "sort" > Sort < / StyledNavItem >
< / Nav >
< br / >
{ album . advancedFilters . nav === 'filters' && (
{ view . album . advancedFilters . nav === 'filters' && (
< AdvancedFilters
filteredData = { {
filteredData ,
@ -312,26 +366,26 @@ const AlbumList = () => {
byStarredData ,
byYearData ,
} }
originalData = { albums }
filter = { album . advancedFilters }
originalData = { albums ? . data }
filter = { view . album . advancedFilters }
setAdvancedFilters = { setAdvancedFilters }
/ >
) }
{ album . advancedFilters . nav === 'sort' && (
{ view . album . advancedFilters . nav === 'sort' && (
< ColumnSort
sortColumns = { sortColumns }
sortColumn = { album . advancedFilters . properties . sort . column }
sortType = { album . advancedFilters . properties . sort . type }
sortColumn = { view . album . sort . column }
sortType = { view . album . sort . type }
disabledItemValues = {
config . serverType === Server . Jellyfin ? [ 'playCount' , 'userRating' ] : [ ]
}
clearSortType = { ( ) = >
dispatch (
setAdvancedFilters ( {
filter : 'sort' ,
value : {
. . . album . advancedFilters . properties . sort ,
setColumnSort ( {
listType : Item.Album ,
data : {
. . . view . album . sort ,
column : undefined ,
} ,
} )
@ -339,17 +393,17 @@ const AlbumList = () => {
}
setSortType = { ( e : string ) = >
dispatch (
setAdvancedFilters ( {
filter : 'sort' ,
value : { . . . album . advancedFilters . properties . sort , type : e } ,
setColumnSort ( {
listType : Item.Album ,
data : { . . . view . album . sort , type : e } ,
} )
)
}
setSortColumn = { ( e : string ) = >
dispatch (
setAdvancedFilters ( {
filter : 'sort' ,
value : { . . . album . advancedFilters . properties . sort , column : e } ,
setColumnSort ( {
listType : Item.Album ,
data : { . . . view . album . sort , column : e } ,
} )
)
}
@ -361,7 +415,7 @@ const AlbumList = () => {
< FilterButton
size = "sm"
appearance = {
album . advancedFilters . enabled || album . advancedFilters . properties . sort . column
view . album . advancedFilters . enabled || view . album . sort . column
? 'primary'
: 'subtle'
}
@ -376,11 +430,21 @@ const AlbumList = () => {
/ >
}
>
{ isLoading && < PageLoader / > }
{ isError && < div > Error : { error } < / div > }
{ ! isLoading && ! is Error && sortedData ? . length > 0 && viewType === 'list' && (
{ ! isError && viewType === 'list' && (
< ListViewType
data = { misc . searchQuery !== '' ? searchedData : sortedData }
ref = { listRef }
data = {
misc . searchQuery !== ''
? searchedData
: ( config . serverType === Server . Subsonic || ! view . album . pagination . serverSide ) &&
view . album . pagination . recordsPerPage !== 0
? sortedData ? . slice (
( view . album . pagination . activePage - 1 ) * view . album . pagination . recordsPerPage ,
view . album . pagination . activePage * view . album . pagination . recordsPerPage
)
: sortedData
}
tableColumns = { config . lookAndFeel . listView . album . columns }
rowHeight = { config . lookAndFeel . listView . album . rowHeight }
fontSize = { config . lookAndFeel . listView . album . fontSize }
@ -401,16 +465,67 @@ const AlbumList = () => {
'deletePlaylist' ,
'viewInFolder' ,
] }
loading = { isLoading }
handleFavorite = { handleRowFavorite }
initialScrollOffset = { Number ( localStorage . getItem ( 'scroll_list_albumList' ) ) }
onScroll = { ( scrollIndex : number ) = > {
localStorage . setItem ( 'scroll_list_albumList' , String ( Math . abs ( scrollIndex ) ) ) ;
} }
paginationProps = {
view . album . pagination . recordsPerPage !== 0 && {
disabled : misc.searchQuery !== '' ? true : null ,
pages : view.album.pagination.pages ,
activePage : view.album.pagination.activePage ,
maxButtons : 3 ,
prev : true ,
next : true ,
ellipsis : true ,
boundaryLinks : true ,
startIndex :
view . album . pagination . recordsPerPage * ( view . album . pagination . activePage - 1 ) + 1 ,
endIndex : view.album.pagination.recordsPerPage * view . album . pagination . activePage ,
handleGoToButton : ( e : number ) = > {
localStorage . setItem ( 'scroll_list_albumList' , '0' ) ;
dispatch (
setPagination ( {
listType : Item.Album ,
data : {
activePage : e ,
} ,
} )
) ;
} ,
onSelect : async ( e : number ) = > {
localStorage . setItem ( 'scroll_list_albumList' , '0' ) ;
await queryClient . cancelQueries ( [ 'albumList' ] , { active : true } ) ;
dispatch (
setPagination ( {
listType : Item.Album ,
data : {
activePage : e ,
} ,
} )
) ;
listScroll ( 0 ) ;
} ,
}
}
/ >
) }
{ ! isLoading && ! isError && sortedData ? . length > 0 && viewType === 'grid' && (
{ ! isError && viewType === 'grid' && (
< GridViewType
data = { misc . searchQuery !== '' ? searchedData : sortedData }
gridRef = { gridRef }
data = {
misc . searchQuery !== ''
? searchedData
: ( config . serverType === Server . Subsonic || ! view . album . pagination . serverSide ) &&
view . album . pagination . recordsPerPage !== 0
? sortedData ? . slice (
( view . album . pagination . activePage - 1 ) * view . album . pagination . recordsPerPage ,
view . album . pagination . activePage * view . album . pagination . recordsPerPage
)
: sortedData
}
cardTitle = { {
prefix : '/library/album' ,
property : 'title' ,
@ -430,7 +545,46 @@ const AlbumList = () => {
onScroll = { ( scrollIndex : number ) = > {
localStorage . setItem ( 'scroll_grid_albumList' , String ( scrollIndex ) ) ;
} }
refresh = { isRefresh }
loading = { isLoading }
paginationProps = {
view . album . pagination . recordsPerPage !== 0 && {
disabled : misc.searchQuery !== '' ? true : null ,
pages : view.album.pagination.pages ,
activePage : view.album.pagination.activePage ,
maxButtons : 3 ,
prev : true ,
next : true ,
ellipsis : true ,
boundaryLinks : true ,
startIndex :
view . album . pagination . recordsPerPage * ( view . album . pagination . activePage - 1 ) + 1 ,
endIndex : view.album.pagination.recordsPerPage * view . album . pagination . activePage ,
handleGoToButton : ( e : number ) = > {
localStorage . setItem ( 'scroll_grid_albumList' , '0' ) ;
dispatch (
setPagination ( {
listType : Item.Album ,
data : {
activePage : e ,
} ,
} )
) ;
} ,
onSelect : async ( e : number ) = > {
localStorage . setItem ( 'scroll_grid_albumList' , '0' ) ;
await queryClient . cancelQueries ( [ 'albumList' ] ) ;
dispatch (
setPagination ( {
listType : Item.Album ,
data : {
activePage : e ,
} ,
} )
) ;
gridScroll ( 0 ) ;
} ,
}
}
/ >
) }
< / GenericPage >