From db8a48bba6e6dfd6b07c7b5ca9cc2cfcc9e36b0a Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 28 Jul 2020 08:49:28 -0400 Subject: [PATCH] Implement album lists --- persistence/album_repository.go | 15 ++++--- ui/src/App.js | 3 +- ui/src/album/AlbumList.js | 2 +- ui/src/i18n/en.json | 8 ++++ ui/src/layout/Menu.js | 70 +++++++++++++++++++++++++++++++-- 5 files changed, 86 insertions(+), 12 deletions(-) diff --git a/persistence/album_repository.go b/persistence/album_repository.go index 36e7f7a88..a7cd32f45 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -36,15 +36,20 @@ func NewAlbumRepository(ctx context.Context, o orm.Ormer) model.AlbumRepository "max_year": "max_year asc, name, order_album_name asc", } r.filterMappings = map[string]filterFunc{ - "name": fullTextFilter, - "compilation": booleanFilter, - "artist_id": artistFilter, - "year": yearFilter, + "name": fullTextFilter, + "compilation": booleanFilter, + "artist_id": artistFilter, + "year": yearFilter, + "recently_played": recentlyPlayedFilter, } return r } +func recentlyPlayedFilter(field string, value interface{}) Sqlizer { + return Gt{"play_count": 0} +} + func yearFilter(field string, value interface{}) Sqlizer { return Or{ And{ @@ -67,7 +72,7 @@ func artistFilter(field string, value interface{}) Sqlizer { } func (r *albumRepository) CountAll(options ...model.QueryOptions) (int64, error) { - return r.count(Select(), options...) + return r.count(r.selectAlbum(options...)) } func (r *albumRepository) Exists(id string) (bool, error) { diff --git a/ui/src/App.js b/ui/src/App.js index 18897f9e1..f8d759b34 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -21,7 +21,6 @@ import { addToPlaylistDialogReducer } from './dialogs/dialogState' import createAdminStore from './store/createAdminStore' import { i18nProvider } from './i18n' import config from './config' - const history = createHashHistory() if (config.gaTrackingId) { @@ -57,7 +56,7 @@ const App = () => ( logoutButton={Logout} > {(permissions) => [ - , + , , , { exporter={false} bulkActionButtons={false} actions={} - sort={{ field: 'created_at', order: 'DESC' }} filters={} + sort={{ field: 'name', order: 'ASC' }} perPage={perPage} pagination={} > diff --git a/ui/src/i18n/en.json b/ui/src/i18n/en.json index 0c51eb4d4..f7b1e6725 100644 --- a/ui/src/i18n/en.json +++ b/ui/src/i18n/en.json @@ -46,6 +46,13 @@ "playNext": "Play Next", "addToQueue": "Play Later", "shuffle": "Shuffle" + }, + "lists": { + "default": "All", + "random": "Random", + "recentlyAdded": "Recently Added", + "recentlyPlayed": "Recently Played", + "mostPlayed": "Most Played" } }, "artist": { @@ -248,6 +255,7 @@ "delete_user_content": "Are you sure you want to delete this user and all their data (including playlists and preferences)?" }, "menu": { + "albumList": "Albums", "library": "Library", "settings": "Settings", "version": "Version %{version}", diff --git a/ui/src/layout/Menu.js b/ui/src/layout/Menu.js index ac66d2221..ff2fa5f8f 100644 --- a/ui/src/layout/Menu.js +++ b/ui/src/layout/Menu.js @@ -6,9 +6,14 @@ import { withRouter } from 'react-router-dom' import LibraryMusicIcon from '@material-ui/icons/LibraryMusic' import SettingsIcon from '@material-ui/icons/Settings' import ViewListIcon from '@material-ui/icons/ViewList' +import AlbumIcon from '@material-ui/icons/Album' import SubMenu from './SubMenu' import inflection from 'inflection' import PersonalMenu from './PersonalMenu' +import ShuffleIcon from '@material-ui/icons/Shuffle' +import LibraryAddIcon from '@material-ui/icons/LibraryAdd' +import VideoLibraryIcon from '@material-ui/icons/VideoLibrary' +import RepeatIcon from '@material-ui/icons/Repeat' const translatedResourceName = (resource, translate) => translate(`resources.${resource.name}.name`, { @@ -22,6 +27,26 @@ const translatedResourceName = (resource, translate) => : inflection.humanize(inflection.pluralize(resource.name)), }) +const albumLists = [ + { type: '', icon: AlbumIcon, params: 'sort=name&order=ASC' }, + { type: 'random', icon: ShuffleIcon, params: 'sort=random' }, + { + type: 'recentlyAdded', + icon: LibraryAddIcon, + params: 'sort=created_at&order=DESC', + }, + { + type: 'recentlyPlayed', + icon: VideoLibraryIcon, + params: 'sort=play_date&order=DESC&filter={"recently_played":true}', + }, + { + type: 'mostPlayed', + icon: RepeatIcon, + params: 'sort=play_count&order=DESC&filter={"recently_played":true}', + }, +] + const Menu = ({ onMenuClick, dense, logout }) => { const isXsmall = useMediaQuery((theme) => theme.breakpoints.down('xs')) const open = useSelector((state) => state.admin.ui.sidebarOpen) @@ -30,6 +55,7 @@ const Menu = ({ onMenuClick, dense, logout }) => { // TODO State is not persisted in mobile when you close the sidebar menu. Move to redux? const [state, setState] = useState({ + menuAlbumList: true, menuLibrary: true, menuSettings: false, }) @@ -38,7 +64,7 @@ const Menu = ({ onMenuClick, dense, logout }) => { setState((state) => ({ ...state, [menu]: !state[menu] })) } - const renderMenuItemLink = (resource) => ( + const renderResourceMenuItemLink = (resource) => ( { /> ) + const renderAlbumMenuItemLink = ({ type, params, icon }) => { + const resource = resources.find((r) => r.name === 'album') + if (!resource) { + return null + } + + const albumListAddress = `/album/${type}?${params}` + + const name = translate(`resources.album.lists.${type || 'default'}`, { + _: translatedResourceName(resource, translate), + }) + + return ( + } + onClick={onMenuClick} + sidebarIsOpen={open} + dense={dense} + exact + /> + ) + } + const subItems = (subMenu) => (resource) => resource.hasList && resource.options && resource.options.subMenu === subMenu return (
+ handleToggle('menuAlbumList')} + isOpen={state.menuAlbumList} + sidebarIsOpen={open} + name="menu.albumList" + icon={} + dense={dense} + > + {albumLists.map((al) => renderAlbumMenuItemLink(al))} + handleToggle('menuLibrary')} isOpen={state.menuLibrary} @@ -65,7 +127,7 @@ const Menu = ({ onMenuClick, dense, logout }) => { icon={} dense={dense} > - {resources.filter(subItems('library')).map(renderMenuItemLink)} + {resources.filter(subItems('library')).map(renderResourceMenuItemLink)} handleToggle('menuSettings')} @@ -75,14 +137,14 @@ const Menu = ({ onMenuClick, dense, logout }) => { icon={} dense={dense} > - {resources.filter(subItems('settings')).map(renderMenuItemLink)} + {resources.filter(subItems('settings')).map(renderResourceMenuItemLink)} - {resources.filter(subItems(undefined)).map(renderMenuItemLink)} + {resources.filter(subItems(undefined)).map(renderResourceMenuItemLink)} {isXsmall && logout}
)