feat: option to display albums as a grid

This commit is contained in:
Deluan 2020-03-28 16:25:55 -04:00
parent fc0621646b
commit f1af646cee
7 changed files with 271 additions and 42 deletions

View File

@ -13,6 +13,7 @@ import album from './album'
import artist from './artist'
import { createMuiTheme } from '@material-ui/core/styles'
import { Player, playQueueReducer } from './audioplayer'
import { albumViewReducer } from './album/albumState'
const theme = createMuiTheme(DarkTheme)
@ -34,7 +35,10 @@ const App = () => {
return (
<Admin
theme={theme}
customReducers={{ queue: playQueueReducer }}
customReducers={{
queue: playQueueReducer,
albumView: albumViewReducer
}}
dataProvider={dataProvider}
authProvider={authProvider}
i18nProvider={i18nProvider}

View File

@ -0,0 +1,87 @@
import React from 'react'
import { GridList, GridListTile, GridListTileBar } 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'
const useStyles = makeStyles((theme) => ({
root: {
margin: '5px'
},
cover: {
display: 'inline-block',
maxWidth: '100%',
height: 'auto'
},
tileBar: {
textAlign: 'center',
background:
'linear-gradient(to top, rgba(0,0,0,0.8) 0%,rgba(0,0,0,0.4) 70%,rgba(0,0,0,0) 100%)'
},
albumArtistName: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
textAlign: 'center',
fontSize: '1em'
}
}))
const getColsForWidth = (width) => {
if (width === 'xs') return 2
if (width === 'sm') return 4
if (width === 'md') return 5
if (width === 'lg') return 6
return 7
}
const LoadedAlbumGrid = ({ ids, data, basePath, width }) => {
const classes = useStyles()
return (
<div className={classes.root}>
<GridList
cellHeight={'auto'}
cols={getColsForWidth(width)}
className={classes.gridList}
spacing={20}
>
{ids.map((id) => (
<GridListTile
component={Link}
key={id}
to={linkToRecord(basePath, data[id].id, 'show')}
>
<img
src={subsonicUrl(
'getCoverArt',
data[id].coverArtId || 'not_found',
{ size: 300 }
)}
alt={data[id].album}
className={classes.cover}
/>
<GridListTileBar
className={classes.tileBar}
title={data[id].name}
subtitle={
<>
<div className={classes.albumArtistName}>
{data[id].albumArtist}
</div>
</>
}
/>
</GridListTile>
))}
</GridList>
</div>
)
}
const AlbumGridView = ({ loading, ...props }) =>
loading ? <Loading /> : <LoadedAlbumGrid {...props} />
export default withWidth()(AlbumGridView)

View File

@ -1,23 +1,21 @@
import React from 'react'
import { useSelector } from 'react-redux'
import {
BooleanField,
Datagrid,
DateField,
AutocompleteInput,
Filter,
List,
NumberField,
FunctionField,
SearchInput,
NumberInput,
NullableBooleanInput,
Show,
SimpleShowLayout,
NumberInput,
ReferenceInput,
AutocompleteInput,
TextField
SearchInput,
Pagination
} from 'react-admin'
import { DurationField, Pagination, Title, RangeField } from '../common'
import { useMediaQuery } from '@material-ui/core'
import { Title } from '../common'
import { withWidth } from '@material-ui/core'
import AlbumListActions from './AlbumListActions'
import AlbumListView from './AlbumListView'
import AlbumGridView from './AlbumGridView'
import { ALBUM_LIST_MODE } from './albumState'
const AlbumFilter = (props) => (
<Filter {...props}>
@ -35,21 +33,27 @@ const AlbumFilter = (props) => (
</Filter>
)
const AlbumDetails = (props) => {
return (
<Show {...props} title=" ">
<SimpleShowLayout>
<TextField source="albumArtist" />
<TextField source="genre" />
<BooleanField source="compilation" />
<DateField source="updatedAt" showTime />
</SimpleShowLayout>
</Show>
)
const getPerPage = (width) => {
if (width === 'xs') return 12
if (width === 'sm') return 12
if (width === 'md') return 15
if (width === 'lg') return 18
return 21
}
const getPerPageOptions = (width) => {
const options = [3, 6, 12]
if (width === 'xs') return [12]
if (width === 'sm') return [12]
if (width === 'md') return options.map((v) => v * 5)
if (width === 'lg') return options.map((v) => v * 6)
return options.map((v) => v * 7)
}
const AlbumList = (props) => {
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
const { width } = props
const albumView = useSelector((state) => state.albumView)
return (
<List
{...props}
@ -57,21 +61,20 @@ const AlbumList = (props) => {
sort={{ field: 'name', order: 'ASC' }}
exporter={false}
bulkActionButtons={false}
actions={<AlbumListActions />}
filters={<AlbumFilter />}
perPage={15}
pagination={<Pagination />}
perPage={getPerPage(width)}
pagination={
<Pagination rowsPerPageOptions={getPerPageOptions(width)} {...props} />
}
>
<Datagrid expand={<AlbumDetails />} rowClick={'show'}>
<TextField source="name" />
<FunctionField
source="artist"
render={(r) => (r.albumArtist ? r.albumArtist : r.artist)}
/>
{isDesktop && <NumberField source="songCount" />}
<RangeField source={'year'} sortBy={'maxYear'} />
{isDesktop && <DurationField source="duration" />}
</Datagrid>
{albumView.mode === ALBUM_LIST_MODE ? (
<AlbumListView {...props} />
) : (
<AlbumGridView {...props} />
)}
</List>
)
}
export default AlbumList
export default withWidth()(AlbumList)

View File

@ -0,0 +1,69 @@
import React, { cloneElement } from 'react'
import { Button, sanitizeListRestProps, TopToolbar } from 'react-admin'
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'
const AlbumListActions = ({
currentSort,
className,
resource,
filters,
displayedFilters,
filterValues,
permanentFilter,
exporter,
basePath,
selectedIds,
onUnselectItems,
showFilter,
maxResults,
total,
fullWidth,
...rest
}) => {
const dispatch = useDispatch()
const albumView = useSelector((state) => state.albumView)
return (
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
{filters &&
cloneElement(filters, {
resource,
showFilter,
displayedFilters,
filterValues,
context: 'button'
})}
<ButtonGroup
variant="text"
color="primary"
aria-label="text primary button group"
>
<Button
size="small"
color={albumView.mode === ALBUM_LIST_MODE ? 'primary' : 'secondary'}
onClick={() => dispatch(selectViewMode(ALBUM_LIST_MODE))}
>
<ViewHeadlineIcon fontSize="inherit" />
</Button>
<Button
size="small"
color={albumView.mode === ALBUM_GRID_MODE ? 'primary' : 'secondary'}
onClick={() => dispatch(selectViewMode(ALBUM_GRID_MODE))}
>
<ViewModuleIcon fontSize="inherit" />
</Button>
</ButtonGroup>
</TopToolbar>
)
}
AlbumListActions.defaultProps = {
selectedIds: [],
onUnselectItems: () => null
}
export default AlbumListActions

View File

@ -0,0 +1,43 @@
import React from 'react'
import {
BooleanField,
Datagrid,
DateField,
NumberField,
FunctionField,
Show,
SimpleShowLayout,
TextField
} from 'react-admin'
import { DurationField, RangeField } from '../common'
import { useMediaQuery } from '@material-ui/core'
const AlbumDetails = (props) => {
return (
<Show {...props} title=" ">
<SimpleShowLayout>
<TextField source="albumArtist" />
<TextField source="genre" />
<BooleanField source="compilation" />
<DateField source="updatedAt" showTime />
</SimpleShowLayout>
</Show>
)
}
const AlbumListView = (props) => {
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
return (
<Datagrid {...props} expand={<AlbumDetails />} rowClick={'show'}>
<TextField source="name" />
<FunctionField
source="artist"
render={(r) => (r.albumArtist ? r.albumArtist : r.artist)}
/>
{isDesktop && <NumberField source="songCount" />}
<RangeField source={'year'} sortBy={'maxYear'} />
{isDesktop && <DurationField source="duration" />}
</Datagrid>
)
}
export default AlbumListView

View File

@ -0,0 +1,23 @@
const ALBUM_GRID_MODE = 'ALBUM_GRID_MODE'
const ALBUM_LIST_MODE = 'ALBUM_LIST_MODE'
const selectViewMode = (mode) => ({ type: mode })
const albumViewReducer = (
previousState = {
mode: localStorage.getItem('albumViewMode') || ALBUM_LIST_MODE
},
payload
) => {
const { type } = payload
switch (type) {
case ALBUM_GRID_MODE:
case ALBUM_LIST_MODE:
localStorage.setItem('albumViewMode', type)
return { mode: type }
default:
return previousState
}
}
export { ALBUM_LIST_MODE, ALBUM_GRID_MODE, albumViewReducer, selectViewMode }

View File

@ -1,12 +1,12 @@
import React, { forwardRef } from 'react';
import React, { forwardRef } from 'react'
import { AppBar as RAAppBar, UserMenu, MenuItemLink } from 'react-admin'
import InfoIcon from '@material-ui/icons/Info';
import InfoIcon from '@material-ui/icons/Info'
const ConfigurationMenu = forwardRef(({ onClick }, ref) => (
<MenuItemLink
ref={ref}
to=""
primaryText={"Version " + localStorage.getItem("version") }
primaryText={'Version ' + localStorage.getItem('version')}
leftIcon={<InfoIcon />}
onClick={onClick}
/>