Option to toggle fields in songs, albums & artists (#923)

* Add toggleColumns

- Add logic for toggling columns
- Add MenuComponent + useSelectedFields hook

* Refactoring

* eslint-fixes

* Typo

* skip menu in albumGridView

* add omittedFields

* Add toggling for playlists and albumSong

* Refactoring

* defaultProps - fix

* Add toggling for PlaylistSongs

* remove accidental console log

* Refactoring for future compatibility

* Hide ToggleMenu in albumGridView

* Add TopBarComponent in ToggleFieldsMenu

* Add defaultOff for useSelectedFields

* Fix edge case

* eslint fix

* Refactoring

* Add propType for forwardRef

* Fix issues

* add translation for grid and table

* add translation for grid and table

* Ignore menuBtn for spotify-ish and Ligera themes

* hide bpm by default in playlistSongs

* Add memoization

* Default album view must be Grid

Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Aldrin Jenson
2021-05-24 20:39:06 +05:30
committed by GitHub
parent 6a17717e30
commit cf8ee251ee
22 changed files with 681 additions and 215 deletions
+48 -36
View File
@@ -14,8 +14,13 @@ import { RiPlayListAddFill, RiPlayList2Fill } from 'react-icons/ri'
import { playNext, addTracks, playTracks, shuffleTracks } from '../actions'
import subsonic from '../subsonic'
import { formatBytes } from '../utils'
import { useMediaQuery } from '@material-ui/core'
import { useMediaQuery, makeStyles } from '@material-ui/core'
import config from '../config'
import ToggleFieldsMenu from '../common/ToggleFieldsMenu'
const useStyles = makeStyles({
toolbar: { display: 'flex', justifyContent: 'space-between', width: '100%' },
})
const AlbumActions = ({
className,
@@ -27,7 +32,9 @@ const AlbumActions = ({
}) => {
const dispatch = useDispatch()
const translate = useTranslate()
const classes = useStyles()
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
const isNotSmall = useMediaQuery((theme) => theme.breakpoints.up('sm'))
const handlePlay = React.useCallback(() => {
dispatch(playTracks(data, ids))
@@ -51,41 +58,46 @@ const AlbumActions = ({
return (
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
<Button
onClick={handlePlay}
label={translate('resources.album.actions.playAll')}
>
<PlayArrowIcon />
</Button>
<Button
onClick={handleShuffle}
label={translate('resources.album.actions.shuffle')}
>
<ShuffleIcon />
</Button>
<Button
onClick={handlePlayNext}
label={translate('resources.album.actions.playNext')}
>
<RiPlayList2Fill />
</Button>
<Button
onClick={handlePlayLater}
label={translate('resources.album.actions.addToQueue')}
>
<RiPlayListAddFill />
</Button>
{config.enableDownloads && (
<Button
onClick={handleDownload}
label={
translate('resources.album.actions.download') +
(isDesktop ? ` (${formatBytes(record.size)})` : '')
}
>
<CloudDownloadOutlinedIcon />
</Button>
)}
<div className={classes.toolbar}>
<div>
<Button
onClick={handlePlay}
label={translate('resources.album.actions.playAll')}
>
<PlayArrowIcon />
</Button>
<Button
onClick={handleShuffle}
label={translate('resources.album.actions.shuffle')}
>
<ShuffleIcon />
</Button>
<Button
onClick={handlePlayNext}
label={translate('resources.album.actions.playNext')}
>
<RiPlayList2Fill />
</Button>
<Button
onClick={handlePlayLater}
label={translate('resources.album.actions.addToQueue')}
>
<RiPlayListAddFill />
</Button>
{config.enableDownloads && (
<Button
onClick={handleDownload}
label={
translate('resources.album.actions.download') +
(isDesktop ? ` (${formatBytes(record.size)})` : '')
}
>
<CloudDownloadOutlinedIcon />
</Button>
)}
</div>
<div>{isNotSmall && <ToggleFieldsMenu resource="albumSong" />}</div>
</div>
</TopToolbar>
)
}
+17 -1
View File
@@ -6,9 +6,9 @@ import {
Filter,
NullableBooleanInput,
NumberInput,
Pagination,
ReferenceInput,
SearchInput,
Pagination,
useTranslate,
} from 'react-admin'
import FavoriteIcon from '@material-ui/icons/Favorite'
@@ -20,6 +20,7 @@ import AlbumGridView from './AlbumGridView'
import { AddToPlaylistDialog } from '../dialogs'
import albumLists, { defaultAlbumList } from './albumLists'
import config from '../config'
import useSelectedFields from '../common/useSelectedFields'
const AlbumFilter = (props) => {
const translate = useTranslate()
@@ -70,6 +71,21 @@ const AlbumList = (props) => {
.replace(/^\/album/, '')
.replace(/^\//, '')
// Workaround to force album columns to appear the first time.
// See https://github.com/navidrome/navidrome/pull/923#issuecomment-833004842
// TODO: Find a better solution
useSelectedFields({
resource: 'album',
columns: {
artist: 'artist',
songCount: 'songCount',
playCount: 'playCount',
year: 'year',
duration: 'duration',
rating: 'rating',
},
})
// If it does not have filter/sort params (usually coming from Menu),
// reload with correct filter/sort params
if (!location.search) {
+69 -25
View File
@@ -1,10 +1,71 @@
import React, { cloneElement } from 'react'
import { Button, sanitizeListRestProps, TopToolbar } from 'react-admin'
import { ButtonGroup } from '@material-ui/core'
import {
Button,
sanitizeListRestProps,
TopToolbar,
useTranslate,
} from 'react-admin'
import {
ButtonGroup,
useMediaQuery,
Typography,
makeStyles,
} 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 { albumViewGrid, albumViewList } from '../actions'
import ToggleFieldsMenu from '../common/ToggleFieldsMenu'
const useStyles = makeStyles({
title: { margin: '1rem' },
buttonGroup: { width: '100%', justifyContent: 'center' },
leftButton: { paddingRight: '0.5rem' },
rightButton: { paddingLeft: '0.5rem' },
})
const AlbumViewToggler = React.forwardRef(
({ showTitle = true, disableElevation, fullWidth }, ref) => {
const dispatch = useDispatch()
const albumView = useSelector((state) => state.albumView)
const classes = useStyles()
const translate = useTranslate()
return (
<div ref={ref}>
{showTitle && (
<Typography className={classes.title}>
{translate('ra.toggleFieldsMenu.layout')}
</Typography>
)}
<ButtonGroup
variant="text"
color="primary"
aria-label="text primary button group"
className={classes.buttonGroup}
>
<Button
size="small"
className={classes.leftButton}
label={translate('ra.toggleFieldsMenu.grid')}
color={albumView.grid ? 'primary' : 'secondary'}
onClick={() => dispatch(albumViewGrid())}
>
<ViewModuleIcon fontSize="inherit" />
</Button>
<Button
size="small"
className={classes.rightButton}
label={translate('ra.toggleFieldsMenu.table')}
color={albumView.grid ? 'secondary' : 'primary'}
onClick={() => dispatch(albumViewList())}
>
<ViewHeadlineIcon fontSize="inherit" />
</Button>
</ButtonGroup>
</div>
)
}
)
const AlbumListActions = ({
currentSort,
@@ -24,9 +85,7 @@ const AlbumListActions = ({
fullWidth,
...rest
}) => {
const dispatch = useDispatch()
const albumView = useSelector((state) => state.albumView)
const isNotSmall = useMediaQuery((theme) => theme.breakpoints.up('sm'))
return (
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
{filters &&
@@ -37,26 +96,11 @@ const AlbumListActions = ({
filterValues,
context: 'button',
})}
<ButtonGroup
variant="text"
color="primary"
aria-label="text primary button group"
>
<Button
size="small"
color={albumView.grid ? 'primary' : 'secondary'}
onClick={() => dispatch(albumViewGrid())}
>
<ViewModuleIcon fontSize="inherit" />
</Button>
<Button
size="small"
color={albumView.grid ? 'secondary' : 'primary'}
onClick={() => dispatch(albumViewList())}
>
<ViewHeadlineIcon fontSize="inherit" />
</Button>
</ButtonGroup>
{isNotSmall ? (
<ToggleFieldsMenu resource="album" topbarComponent={AlbumViewToggler} />
) : (
<AlbumViewToggler showTitle={false} />
)}
</TopToolbar>
)
}
+33 -14
View File
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useMemo } from 'react'
import Paper from '@material-ui/core/Paper'
import Table from '@material-ui/core/Table'
import TableBody from '@material-ui/core/TableBody'
@@ -28,6 +28,7 @@ import {
RatingField,
} from '../common'
import config from '../config'
import useSelectedFields from '../common/useSelectedFields'
const useStyles = makeStyles({
columnIcon: {
@@ -109,6 +110,36 @@ const AlbumListView = ({
const classes = useStyles()
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
const isXsmall = useMediaQuery((theme) => theme.breakpoints.down('xs'))
const toggleableFields = useMemo(() => {
return {
artist: <ArtistLinkField source="artist" />,
songCount: isDesktop && (
<NumberField source="songCount" sortByOrder={'DESC'} />
),
playCount: isDesktop && (
<NumberField source="playCount" sortByOrder={'DESC'} />
),
year: (
<RangeField source={'year'} sortBy={'maxYear'} sortByOrder={'DESC'} />
),
duration: isDesktop && <DurationField source="duration" />,
rating: config.enableStarRating && (
<RatingField
source={'rating'}
resource={'album'}
sortByOrder={'DESC'}
className={classes.ratingField}
/>
),
}
}, [classes.ratingField, isDesktop])
const columns = useSelectedFields({
resource: 'album',
columns: toggleableFields,
})
return isXsmall ? (
<SimpleList
primaryText={(r) => r.name}
@@ -147,19 +178,7 @@ const AlbumListView = ({
{...rest}
>
<TextField source="name" />
<ArtistLinkField source="artist" />
{isDesktop && <NumberField source="songCount" sortByOrder={'DESC'} />}
{isDesktop && <NumberField source="playCount" sortByOrder={'DESC'} />}
<RangeField source={'year'} sortBy={'maxYear'} sortByOrder={'DESC'} />
{isDesktop && <DurationField source="duration" />}
{config.enableStarRating && (
<RatingField
source={'rating'}
resource={'album'}
sortByOrder={'DESC'}
className={classes.ratingField}
/>
)}
{columns}
<AlbumContextMenu
source={'starred'}
sortBy={'starred ASC, starredAt ASC'}
+3 -1
View File
@@ -12,7 +12,9 @@ import AlbumActions from './AlbumActions'
const useStyles = makeStyles(
(theme) => ({
albumActions: {},
albumActions: {
width: '100%',
},
}),
{
name: 'NDAlbumShow',
+43 -26
View File
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useMemo } from 'react'
import {
BulkActionsToolbar,
ListToolbar,
@@ -24,6 +24,7 @@ import {
} from '../common'
import { AddToPlaylistDialog } from '../dialogs'
import { QualityInfo } from '../common/QualityInfo'
import useSelectedFields from '../common/useSelectedFields'
import config from '../config'
const useStyles = makeStyles(
@@ -87,6 +88,46 @@ const AlbumSongs = (props) => {
const classes = useStyles({ isDesktop })
const dispatch = useDispatch()
const version = useVersion()
const toggleableFields = useMemo(() => {
return {
trackNumber: isDesktop && (
<TextField
source="trackNumber"
sortBy="discNumber asc, trackNumber asc"
label="#"
sortable={false}
/>
),
title: (
<SongTitleField
source="title"
sortable={false}
showTrackNumbers={!isDesktop}
/>
),
artist: isDesktop && <TextField source="artist" sortable={false} />,
duration: <DurationField source="duration" sortable={false} />,
quality: isDesktop && <QualityInfo source="quality" sortable={false} />,
bpm: isDesktop && <NumberField source="bpm" sortable={false} />,
rating: isDesktop && config.enableStarRating && (
<RatingField
source="rating"
resource={'albumSong'}
sortable={false}
className={classes.ratingField}
/>
),
}
}, [isDesktop, classes.ratingField])
const columns = useSelectedFields({
resource: 'albumSong',
columns: toggleableFields,
omittedColumns: ['title'],
defaultOff: ['bpm'],
})
return (
<>
<ListToolbar
@@ -113,31 +154,7 @@ const AlbumSongs = (props) => {
contextAlwaysVisible={!isDesktop}
classes={{ row: classes.row }}
>
{isDesktop && (
<TextField
source="trackNumber"
sortBy="discNumber asc, trackNumber asc"
label="#"
sortable={false}
/>
)}
<SongTitleField
source="title"
sortable={false}
showTrackNumbers={!isDesktop}
/>
{isDesktop && <TextField source="artist" sortable={false} />}
<DurationField source="duration" sortable={false} />
{isDesktop && <QualityInfo source="quality" sortable={false} />}
{isDesktop && <NumberField source="bpm" sortable={false} />}
{isDesktop && config.enableStarRating && (
<RatingField
source="rating"
resource={'albumSong'}
sortable={false}
className={classes.ratingField}
/>
)}
{columns}
<SongContextMenu
source={'starred'}
sortable={false}