diff --git a/persistence/album_repository.go b/persistence/album_repository.go index 8259533fd..6a131e4b8 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -24,6 +24,7 @@ func NewAlbumRepository(ctx context.Context, o orm.Ormer) model.AlbumRepository r.tableName = "album" r.sortMappings = map[string]string{ "artist": "compilation asc, album_artist asc, name asc", + "random": "RANDOM()", } r.filterMappings = map[string]filterFunc{ "name": fullTextFilter, diff --git a/ui/src/album/AlbumGridView.js b/ui/src/album/AlbumGridView.js index a52984652..450883892 100644 --- a/ui/src/album/AlbumGridView.js +++ b/ui/src/album/AlbumGridView.js @@ -1,11 +1,31 @@ import React from 'react' -import { GridList, GridListTile, GridListTileBar } from '@material-ui/core' +import { useDispatch, useSelector } from 'react-redux' +import { + GridList, + GridListTile, + GridListTileBar, + Tabs, + Tab +} from '@material-ui/core' import { makeStyles } from '@material-ui/core/styles' import withWidth from '@material-ui/core/withWidth' import { Link } from 'react-router-dom' import { linkToRecord } from 'ra-core' import { Loading } from 'react-admin' import { subsonicUrl } from '../subsonic' +import AllInclusiveIcon from '@material-ui/icons/AllInclusive' +import ShuffleIcon from '@material-ui/icons/Shuffle' +import StarIcon from '@material-ui/icons/Star' +import LibraryAddIcon from '@material-ui/icons/LibraryAdd' +import VideoLibraryIcon from '@material-ui/icons/VideoLibrary' +import { + ALBUM_LIST_ALL, + ALBUM_LIST_NEWEST, + ALBUM_LIST_RANDOM, + ALBUM_LIST_RECENT, + ALBUM_LIST_STARRED, + selectAlbumList +} from './albumState' const useStyles = makeStyles((theme) => ({ root: { @@ -38,10 +58,39 @@ const getColsForWidth = (width) => { return 7 } +const tabOrder = [ + ALBUM_LIST_ALL, + ALBUM_LIST_RANDOM, + ALBUM_LIST_NEWEST, + ALBUM_LIST_RECENT, + ALBUM_LIST_STARRED +] + const LoadedAlbumGrid = ({ ids, data, basePath, width }) => { const classes = useStyles() + const dispatch = useDispatch() + const albumView = useSelector((state) => state.albumView) + const tabSelected = tabOrder.indexOf(albumView.list) + + const handleChange = (event, newValue) => { + dispatch(selectAlbumList(tabOrder[newValue])) + } + return ( <div className={classes.root}> + <Tabs + value={tabSelected} + indicatorColor="primary" + textColor="primary" + aria-label="disabled tabs example" + onChange={handleChange} + > + <Tab label="All" icon={<AllInclusiveIcon />} /> + <Tab label="Random" icon={<ShuffleIcon />} /> + <Tab label="Newest" icon={<LibraryAddIcon />} /> + <Tab label="Recently Played" icon={<VideoLibraryIcon />} /> + <Tab label="Starred" icon={<StarIcon />} disabled={true} /> + </Tabs> <GridList cellHeight={'auto'} cols={getColsForWidth(width)} diff --git a/ui/src/album/AlbumList.js b/ui/src/album/AlbumList.js index f081bce58..dcc63a943 100644 --- a/ui/src/album/AlbumList.js +++ b/ui/src/album/AlbumList.js @@ -15,7 +15,7 @@ import { withWidth } from '@material-ui/core' import AlbumListActions from './AlbumListActions' import AlbumListView from './AlbumListView' import AlbumGridView from './AlbumGridView' -import { ALBUM_LIST_MODE } from './albumState' +import { ALBUM_MODE_LIST } from './albumState' const AlbumFilter = (props) => ( <Filter {...props}> @@ -58,17 +58,15 @@ const AlbumList = (props) => { <List {...props} title={<Title subTitle={'Albums'} />} - sort={{ field: 'name', order: 'ASC' }} + sort={albumView.params.sort} exporter={false} bulkActionButtons={false} actions={<AlbumListActions />} filters={<AlbumFilter />} perPage={getPerPage(width)} - pagination={ - <Pagination rowsPerPageOptions={getPerPageOptions(width)} {...props} /> - } + pagination={<Pagination rowsPerPageOptions={getPerPageOptions(width)} />} > - {albumView.mode === ALBUM_LIST_MODE ? ( + {albumView.mode === ALBUM_MODE_LIST ? ( <AlbumListView {...props} /> ) : ( <AlbumGridView {...props} /> diff --git a/ui/src/album/AlbumListActions.js b/ui/src/album/AlbumListActions.js index 739706d56..1dbbcb336 100644 --- a/ui/src/album/AlbumListActions.js +++ b/ui/src/album/AlbumListActions.js @@ -4,7 +4,7 @@ import { ButtonGroup } from '@material-ui/core' import ViewHeadlineIcon from '@material-ui/icons/ViewHeadline' import ViewModuleIcon from '@material-ui/icons/ViewModule' import { useDispatch, useSelector } from 'react-redux' -import { ALBUM_GRID_MODE, ALBUM_LIST_MODE, selectViewMode } from './albumState' +import { ALBUM_MODE_GRID, ALBUM_MODE_LIST, selectViewMode } from './albumState' const AlbumListActions = ({ currentSort, @@ -44,15 +44,15 @@ const AlbumListActions = ({ > <Button size="small" - color={albumView.mode === ALBUM_LIST_MODE ? 'primary' : 'secondary'} - onClick={() => dispatch(selectViewMode(ALBUM_LIST_MODE))} + color={albumView.mode === ALBUM_MODE_LIST ? 'primary' : 'secondary'} + onClick={() => dispatch(selectViewMode(ALBUM_MODE_LIST))} > <ViewHeadlineIcon fontSize="inherit" /> </Button> <Button size="small" - color={albumView.mode === ALBUM_GRID_MODE ? 'primary' : 'secondary'} - onClick={() => dispatch(selectViewMode(ALBUM_GRID_MODE))} + color={albumView.mode === ALBUM_MODE_GRID ? 'primary' : 'secondary'} + onClick={() => dispatch(selectViewMode(ALBUM_MODE_GRID))} > <ViewModuleIcon fontSize="inherit" /> </Button> diff --git a/ui/src/album/albumState.js b/ui/src/album/albumState.js index f2ed109a3..0eb8e8067 100644 --- a/ui/src/album/albumState.js +++ b/ui/src/album/albumState.js @@ -1,23 +1,60 @@ -const ALBUM_GRID_MODE = 'ALBUM_GRID_MODE' -const ALBUM_LIST_MODE = 'ALBUM_LIST_MODE' - +const ALBUM_MODE_GRID = 'ALBUM_GRID_MODE' +const ALBUM_MODE_LIST = 'ALBUM_LIST_MODE' const selectViewMode = (mode) => ({ type: mode }) +const ALBUM_LIST_ALL = 'ALBUM_LIST_ALL' +const ALBUM_LIST_RANDOM = 'ALBUM_LIST_RANDOM' +const ALBUM_LIST_NEWEST = 'ALBUM_LIST_NEWEST' +const ALBUM_LIST_RECENT = 'ALBUM_LIST_RECENT' +const ALBUM_LIST_STARRED = 'ALBUM_LIST_STARRED' + +const albumListParams = { + ALBUM_LIST_ALL: { sort: { field: 'name', order: 'ASC' } }, + ALBUM_LIST_RANDOM: { sort: { field: 'random' } }, + ALBUM_LIST_NEWEST: { sort: { field: 'created_at', order: 'DESC' } }, + ALBUM_LIST_RECENT: { + sort: { field: 'starred_at', order: 'DESC' }, + filter: { starred: true } + } +} + +const selectAlbumList = (mode) => ({ type: mode }) + const albumViewReducer = ( previousState = { - mode: localStorage.getItem('albumViewMode') || ALBUM_LIST_MODE + mode: localStorage.getItem('albumViewMode') || ALBUM_MODE_LIST, + list: localStorage.getItem('albumListType') || ALBUM_LIST_ALL, + params: { sort: {}, filter: {} } }, payload ) => { const { type } = payload switch (type) { - case ALBUM_GRID_MODE: - case ALBUM_LIST_MODE: + case ALBUM_MODE_GRID: + case ALBUM_MODE_LIST: localStorage.setItem('albumViewMode', type) - return { mode: type } + return { ...previousState, mode: type } + case ALBUM_LIST_ALL: + case ALBUM_LIST_RANDOM: + case ALBUM_LIST_NEWEST: + case ALBUM_LIST_RECENT: + case ALBUM_LIST_STARRED: + localStorage.setItem('albumListType', type) + return { ...previousState, list: type, params: albumListParams[type] } default: return previousState } } -export { ALBUM_LIST_MODE, ALBUM_GRID_MODE, albumViewReducer, selectViewMode } +export { + ALBUM_MODE_LIST, + ALBUM_MODE_GRID, + ALBUM_LIST_ALL, + ALBUM_LIST_RANDOM, + ALBUM_LIST_NEWEST, + ALBUM_LIST_RECENT, + ALBUM_LIST_STARRED, + albumViewReducer, + selectViewMode, + selectAlbumList +}