feat: optimized for small screens (only)

This commit is contained in:
Deluan 2020-02-07 12:36:26 -05:00
parent 81e1a7088f
commit 2ca98d8e81
7 changed files with 165 additions and 116 deletions

View File

@ -0,0 +1,46 @@
import { Loading, useGetOne } from 'react-admin'
import { Card, CardContent, CardMedia, Typography } from '@material-ui/core'
import { subsonicUrl } from '../subsonic'
import React from 'react'
export const AlbumDetails = ({ id, children, classes }) => {
const { data, loading, error } = useGetOne('album', id)
if (loading) {
return <Loading />
}
if (error) {
return <p>ERROR: {error}</p>
}
const genreYear = (data) => {
let genreDateLine = []
if (data.genre) {
genreDateLine.push(data.genre)
}
if (data.year) {
genreDateLine.push(data.year)
}
return genreDateLine.join(' - ')
}
return (
<Card className={classes.container}>
<CardMedia
image={subsonicUrl('getCoverArt', data.coverArtId || 'not_found')}
className={classes.albumCover}
/>
<CardContent className={classes.albumDetails}>
<Typography variant="h5" className={classes.albumTitle}>
{data.name}
</Typography>
<Typography component="h6">
{data.albumArtist || data.artist}
</Typography>
<Typography variant="h7">{genreYear(data)}</Typography>
</CardContent>
{children}
</Card>
)
}

View File

@ -6,6 +6,7 @@ import {
Filter, Filter,
List, List,
NumberField, NumberField,
FunctionField,
SearchInput, SearchInput,
TextInput, TextInput,
Show, Show,
@ -13,6 +14,7 @@ import {
TextField TextField
} from 'react-admin' } from 'react-admin'
import { DurationField, Pagination, Title } from '../common' import { DurationField, Pagination, Title } from '../common'
import { useMediaQuery } from '@material-ui/core'
const AlbumFilter = (props) => ( const AlbumFilter = (props) => (
<Filter {...props}> <Filter {...props}>
@ -34,33 +36,30 @@ const AlbumDetails = (props) => {
) )
} }
// const albumRowClick = (id, basePath, record) => { const AlbumList = (props) => {
// const filter = { album: record.name, album_id: id } const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
// if (!record.compilation) { return (
// filter.artist = record.artist <List
// } {...props}
// return `/song?filter=${JSON.stringify(filter)}&order=ASC&sort=trackNumber` title={<Title subTitle={'Albums'} />}
// } sort={{ field: 'name', order: 'ASC' }}
exporter={false}
const AlbumList = (props) => ( bulkActionButtons={false}
<List filters={<AlbumFilter />}
{...props} perPage={15}
title={<Title subTitle={'Albums'} />} pagination={<Pagination />}
sort={{ field: 'name', order: 'ASC' }} >
exporter={false} <Datagrid expand={<AlbumDetails />} rowClick={'show'}>
bulkActionButtons={false} <TextField source="name" />
filters={<AlbumFilter />} <FunctionField
perPage={15} source="artist"
pagination={<Pagination />} render={(r) => (r.albumArtist ? r.albumArtist : r.artist)}
> />
<Datagrid expand={<AlbumDetails />} rowClick={'show'}> {isDesktop && <NumberField source="songCount" />}
<TextField source="name" /> <TextField source="year" />
<TextField source="artist" /> {isDesktop && <DurationField source="duration" />}
<NumberField source="songCount" /> </Datagrid>
<TextField source="year" /> </List>
<DurationField source="duration" /> )
</Datagrid> }
</List>
)
export default AlbumList export default AlbumList

View File

@ -1,91 +1,42 @@
import React from 'react' import React from 'react'
import { Show, SimpleList, useGetList, useGetOne, Loading } from 'react-admin' import { Show } from 'react-admin'
import { PlayButton, Title } from '../common' import { Title } from '../common'
import { addTrack } from '../player'
import { DurationField } from '../common'
import AddIcon from '@material-ui/icons/Add'
import { Typography, Paper } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { AlbumSongList } from './AlbumSongList'
import { AlbumDetails } from './AlbumDetails'
const AlbumTitle = ({ record }) => { const AlbumTitle = ({ record }) => {
return <Title subTitle={`Album: ${record ? record.name : ''}`} /> return <Title subTitle={record ? record.name : ''} />
} }
const useStyles = makeStyles({ const useStyles = makeStyles({
container: { minWidth: '35em', padding: '1em' }, container: { minWidth: '24em', padding: '1em' },
rightAlignedCell: { textAlign: 'right' }, rightAlignedCell: { textAlign: 'right' },
boldCell: { fontWeight: 'bold' } boldCell: { fontWeight: 'bold' },
albumCover: {
display: 'inline-block',
height: '8em',
width: '8em'
},
albumDetails: {
display: 'inline-block',
verticalAlign: 'top',
width: '14em'
},
albumTitle: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
}) })
const AlbumDetail = (props) => {
const classes = useStyles()
const { data, loading, error } = useGetOne('album', props.id)
if (loading) {
return <Loading />
}
if (error) {
return <p>ERROR: {error}</p>
}
let genreDate = []
if (data.genre) {
genreDate.push(data.genre)
}
if (data.year) {
genreDate.push(data.year)
}
return (
<Paper className={classes.container} elevation={2}>
<Typography variant="h5">{data.name}</Typography>
<Typography variant="h6">{data.albumArtist || data.artist}</Typography>
<Typography variant="h7">{genreDate.join(' - ')}</Typography>
<Typography variant="body2"></Typography>
</Paper>
)
}
const AlbumSongs = (props) => {
const { record } = props
const { data, total, loading, error } = useGetList(
'song',
{ page: 0, perPage: 100 },
{ field: 'album', order: 'ASC' },
{ album_id: record.id }
)
if (error) {
return <p>ERROR: {error}</p>
}
return (
<SimpleList
data={data}
ids={Object.keys(data)}
loading={loading}
total={total}
primaryText={(r) => (
<>
<PlayButton record={r} />
<PlayButton record={r} action={addTrack} icon={<AddIcon />} />
{r.trackNumber + '. ' + r.title}
</>
)}
secondaryText={(r) =>
r.albumArtist && r.artist !== r.albumArtist ? r.artist : ''
}
tertiaryText={(r) => <DurationField record={r} source={'duration'} />}
linkType={false}
/>
)
}
const AlbumShow = (props) => { const AlbumShow = (props) => {
const classes = useStyles()
return ( return (
<> <>
<AlbumDetail {...props} /> <AlbumDetails classes={classes} {...props} />
<Show title={<AlbumTitle />} {...props}> <Show title={<AlbumTitle />} {...props}>
<AlbumSongs {...props} /> <AlbumSongList {...props} />
</Show> </Show>
</> </>
) )

View File

@ -0,0 +1,45 @@
import React from 'react'
import { SimpleList, useGetList } from 'react-admin'
import { DurationField, PlayButton } from '../common'
import { addTrack } from '../player'
import AddIcon from '@material-ui/icons/Add'
export const AlbumSongList = (props) => {
const { record } = props
const { data, total, loading, error } = useGetList(
'song',
{ page: 0, perPage: 100 },
{ field: 'album', order: 'ASC' },
{ album_id: record.id }
)
if (error) {
return <p>ERROR: {error}</p>
}
const trackName = (r) => {
const name = r.title
if (r.trackNumber) {
return r.trackNumber + '. ' + name
}
return name
}
return (
<SimpleList
data={data}
ids={Object.keys(data)}
loading={loading}
total={total}
primaryText={(r) => (
<>
<PlayButton record={r} />
<PlayButton record={r} action={addTrack} icon={<AddIcon />} />
{trackName(r)}
</>
)}
secondaryText={(r) =>
r.albumArtist && r.artist !== r.albumArtist ? r.artist : ''
}
tertiaryText={(r) => <DurationField record={r} source={'duration'} />}
linkType={false}
/>
)
}

View File

@ -1,7 +1,13 @@
import React from 'react' import React from 'react'
import { useMediaQuery } from '@material-ui/core'
const Title = ({ subTitle }) => { const Title = ({ subTitle }) => {
return <span>Navidrome {subTitle ? ` - ${subTitle}` : ''}</span> const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
if (isDesktop) {
return <span>Navidrome {subTitle ? ` - ${subTitle}` : ''}</span>
}
return <span>{subTitle ? subTitle : 'Navidrome'}</span>
} }
export default Title export default Title

View File

@ -1,22 +1,11 @@
import 'react-jinke-music-player/assets/index.css' import 'react-jinke-music-player/assets/index.css'
import { subsonicUrl } from '../subsonic'
const PLAYER_ADD_TRACK = 'PLAYER_ADD_TRACK' const PLAYER_ADD_TRACK = 'PLAYER_ADD_TRACK'
const PLAYER_SET_TRACK = 'PLAYER_SET_TRACK' const PLAYER_SET_TRACK = 'PLAYER_SET_TRACK'
const PLAYER_SYNC_QUEUE = 'PLAYER_SYNC_QUEUE' const PLAYER_SYNC_QUEUE = 'PLAYER_SYNC_QUEUE'
const PLAYER_SCROBBLE = 'PLAYER_SCROBBLE' const PLAYER_SCROBBLE = 'PLAYER_SCROBBLE'
const subsonicUrl = (command, id, options) => {
const username = localStorage.getItem('username')
const token = localStorage.getItem('subsonic-token')
const salt = localStorage.getItem('subsonic-salt')
const timeStamp = new Date().getTime()
const url = `rest/${command}?u=${username}&f=json&v=1.8.0&c=NavidromeUI&t=${token}&s=${salt}&id=${id}&_=${timeStamp}`
if (options) {
return url + '&' + options
}
return url
}
const mapToAudioLists = (item) => ({ const mapToAudioLists = (item) => ({
id: item.id, id: item.id,
name: item.title, name: item.title,

13
ui/src/subsonic/index.js Normal file
View File

@ -0,0 +1,13 @@
const subsonicUrl = (command, id, options) => {
const username = localStorage.getItem('username')
const token = localStorage.getItem('subsonic-token')
const salt = localStorage.getItem('subsonic-salt')
const timeStamp = new Date().getTime()
const url = `rest/${command}?u=${username}&f=json&v=1.8.0&c=NavidromeUI&t=${token}&s=${salt}&id=${id}&_=${timeStamp}`
if (options) {
return url + '&' + options
}
return url
}
export { subsonicUrl }