Make spotify-ish more spotify-ish (#914)

* [Theme] Allow customising album and player parts

* [Theme] Allow customizing song lists view

* Make spotify-ish more spotify-ish

* Fix responsive issues in spotify-ish

* Spotify-ish login page

* Add back the previous "Spotify-ish" theme as "Green"

Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Samarjeet 2021-04-01 02:28:47 +05:30 committed by GitHub
parent 5128c049d7
commit ea65da484b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 573 additions and 160 deletions

View File

@ -22,65 +22,73 @@ import {
} from '../common'
import config from '../config'
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.down('xs')]: {
padding: '0.7em',
minWidth: '20em',
const useStyles = makeStyles(
(theme) => ({
root: {
[theme.breakpoints.down('xs')]: {
padding: '0.7em',
minWidth: '20em',
},
[theme.breakpoints.up('sm')]: {
padding: '1em',
minWidth: '32em',
},
},
[theme.breakpoints.up('sm')]: {
padding: '1em',
minWidth: '32em',
cardContents: {
display: 'flex',
},
},
cardContents: {
display: 'flex',
},
details: {
display: 'flex',
flexDirection: 'column',
},
content: {
flex: '2 0 auto',
},
coverParent: {
[theme.breakpoints.down('xs')]: {
height: '8em',
width: '8em',
minWidth: '8em',
details: {
display: 'flex',
flexDirection: 'column',
},
[theme.breakpoints.up('sm')]: {
height: '10em',
width: '10em',
minWidth: '10em',
content: {
flex: '2 0 auto',
},
[theme.breakpoints.up('lg')]: {
height: '15em',
width: '15em',
minWidth: '15em',
coverParent: {
[theme.breakpoints.down('xs')]: {
height: '8em',
width: '8em',
minWidth: '8em',
},
[theme.breakpoints.up('sm')]: {
height: '10em',
width: '10em',
minWidth: '10em',
},
[theme.breakpoints.up('lg')]: {
height: '15em',
width: '15em',
minWidth: '15em',
},
},
},
cover: {
objectFit: 'contain',
cursor: 'pointer',
display: 'block',
width: '100%',
height: '100%',
},
loveButton: {
top: theme.spacing(-0.2),
left: theme.spacing(0.5),
},
commentBlock: {
display: 'inline-block',
marginTop: '1em',
float: 'left',
wordBreak: 'break-all',
},
pointerCursor: {
cursor: 'pointer',
},
}))
cover: {
objectFit: 'contain',
cursor: 'pointer',
display: 'block',
width: '100%',
height: '100%',
},
loveButton: {
top: theme.spacing(-0.2),
left: theme.spacing(0.5),
},
commentBlock: {
display: 'inline-block',
marginTop: '1em',
float: 'left',
wordBreak: 'break-all',
},
pointerCursor: {
cursor: 'pointer',
},
recordName: {},
recordArtist: {},
recordMeta: {},
}),
{
name: 'NDAlbumDetails',
}
)
const AlbumComment = ({ record }) => {
const classes = useStyles()
@ -159,7 +167,7 @@ const AlbumDetails = ({ record }) => {
</div>
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="h5">
<Typography variant="h5" className={classes.recordName}>
{record.name}
{config.enableFavourites && (
<LoveButton
@ -172,11 +180,13 @@ const AlbumDetails = ({ record }) => {
/>
)}
</Typography>
<Typography component="h6">
<Typography component="h6" className={classes.recordArtist}>
<ArtistLinkField record={record} />
</Typography>
<Typography component="p">{genreYear(record)}</Typography>
<Typography component="p">
<Typography component="p" className={classes.recordMeta}>
{genreYear(record)}
</Typography>
<Typography component="p" className={classes.recordMeta}>
{record.songCount}{' '}
{translate('resources.song.name', {
smart_count: record.songCount,

View File

@ -19,59 +19,64 @@ import {
RangeField,
} from '../common'
const useStyles = makeStyles((theme) => ({
root: {
margin: '20px',
},
tileBar: {
transition: 'all 150ms ease-out',
opacity: 0,
textAlign: 'left',
marginBottom: '3px',
background:
'linear-gradient(to top, rgba(0,0,0,0.7) 0%,rgba(0,0,0,0.4) 70%,rgba(0,0,0,0) 100%)',
},
tileBarMobile: {
textAlign: 'left',
marginBottom: '3px',
background:
'linear-gradient(to top, rgba(0,0,0,0.7) 0%,rgba(0,0,0,0.4) 70%,rgba(0,0,0,0) 100%)',
},
albumArtistName: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
textAlign: 'left',
fontSize: '1em',
},
albumName: {
fontSize: '14px',
color: theme.palette.type === 'dark' ? '#eee' : 'black',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
albumSubtitle: {
fontSize: '12px',
color: theme.palette.type === 'dark' ? '#c5c5c5' : '#696969',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
link: {
position: 'relative',
display: 'block',
textDecoration: 'none',
'&:hover $tileBar': {
opacity: 1,
const useStyles = makeStyles(
(theme) => ({
root: {
margin: '20px',
},
},
albumLink: {
position: 'relative',
display: 'block',
textDecoration: 'none',
},
}))
tileBar: {
transition: 'all 150ms ease-out',
opacity: 0,
textAlign: 'left',
marginBottom: '3px',
background:
'linear-gradient(to top, rgba(0,0,0,0.7) 0%,rgba(0,0,0,0.4) 70%,rgba(0,0,0,0) 100%)',
},
tileBarMobile: {
textAlign: 'left',
marginBottom: '3px',
background:
'linear-gradient(to top, rgba(0,0,0,0.7) 0%,rgba(0,0,0,0.4) 70%,rgba(0,0,0,0) 100%)',
},
albumArtistName: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
textAlign: 'left',
fontSize: '1em',
},
albumName: {
fontSize: '14px',
color: theme.palette.type === 'dark' ? '#eee' : 'black',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
albumSubtitle: {
fontSize: '12px',
color: theme.palette.type === 'dark' ? '#c5c5c5' : '#696969',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
link: {
position: 'relative',
display: 'block',
textDecoration: 'none',
'&:hover $tileBar': {
opacity: 1,
},
},
albumLink: {
position: 'relative',
display: 'block',
textDecoration: 'none',
},
albumContainer: {},
albumPlayButton: {},
}),
{ name: 'NDAlbumGridView' }
)
const useCoverStyles = makeStyles({
cover: {
@ -112,7 +117,7 @@ const AlbumGridTile = ({ showArtist, record, basePath }) => {
const classes = useStyles()
return (
<div>
<div className={classes.albumContainer}>
<Link
className={classes.link}
to={linkToRecord(basePath, record.id, 'show')}
@ -120,7 +125,14 @@ const AlbumGridTile = ({ showArtist, record, basePath }) => {
<Cover album={record} />
<GridListTileBar
className={isDesktop ? classes.tileBar : classes.tileBarMobile}
subtitle={<PlayButton color={'white'} record={record} size="small" />}
subtitle={
<PlayButton
className={classes.albumPlayButton}
color={'white'}
record={record}
size="small"
/>
}
actionIcon={<AlbumContextMenu record={record} color={'white'} />}
/>
</Link>

View File

@ -5,13 +5,24 @@ import {
useShowContext,
useShowController,
} from 'react-admin'
import { makeStyles } from '@material-ui/core/styles'
import AlbumSongs from './AlbumSongs'
import AlbumDetails from './AlbumDetails'
import AlbumActions from './AlbumActions'
const useStyles = makeStyles(
(theme) => ({
albumActions: {},
}),
{
name: 'NDAlbumShow',
}
)
const AlbumShowLayout = (props) => {
const { loading, ...context } = useShowContext(props)
const { record } = context
const classes = useStyles()
return (
<>
@ -29,7 +40,9 @@ const AlbumShowLayout = (props) => {
<AlbumSongs
resource={'albumSong'}
exporter={false}
actions={<AlbumActions record={record} />}
actions={
<AlbumActions className={classes.albumActions} record={record} />
}
/>
</ReferenceManyField>
)}

View File

@ -26,18 +26,21 @@ import { sendNotification, baseUrl } from '../utils'
import { keyMap } from '../hotkeys'
import useCurrentTheme from '../themes/useCurrentTheme'
const useStyle = makeStyles((theme) => ({
audioTitle: {
textDecoration: 'none',
color: theme.palette.primary.dark,
'&.songTitle': {
fontWeight: 'bold',
const useStyle = makeStyles(
(theme) => ({
audioTitle: {
textDecoration: 'none',
color: theme.palette.primary.dark,
'&.songTitle': {
fontWeight: 'bold',
},
},
},
player: {
display: (props) => (props.visible ? 'block' : 'none'),
},
}))
player: {
display: (props) => (props.visible ? 'block' : 'none'),
},
}),
{ name: 'NDAudioPlayer' }
)
let audioInstance = null

View File

@ -4,36 +4,41 @@ import { makeStyles } from '@material-ui/core/styles'
import { useTranslate } from 'react-admin'
import { DurationField, SizeField } from '../common'
const useStyles = makeStyles((theme) => ({
container: {
[theme.breakpoints.down('xs')]: {
padding: '0.7em',
minWidth: '24em',
const useStyles = makeStyles(
(theme) => ({
container: {
[theme.breakpoints.down('xs')]: {
padding: '0.7em',
minWidth: '24em',
},
[theme.breakpoints.up('sm')]: {
padding: '1em',
minWidth: '32em',
},
},
[theme.breakpoints.up('sm')]: {
padding: '1em',
minWidth: '32em',
details: {
display: 'inline-block',
verticalAlign: 'top',
[theme.breakpoints.down('xs')]: {
width: '14em',
},
[theme.breakpoints.up('sm')]: {
width: '26em',
},
[theme.breakpoints.up('lg')]: {
width: '38em',
},
},
},
details: {
display: 'inline-block',
verticalAlign: 'top',
[theme.breakpoints.down('xs')]: {
width: '14em',
title: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
},
[theme.breakpoints.up('sm')]: {
width: '26em',
},
[theme.breakpoints.up('lg')]: {
width: '38em',
},
},
title: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
},
}))
}),
{
name: 'NDPlaylistDetails',
}
)
const PlaylistDetails = (props) => {
const { record = {} } = props

View File

@ -5,14 +5,26 @@ import {
useShowContext,
useShowController,
} from 'react-admin'
import { makeStyles } from '@material-ui/core/styles'
import PlaylistDetails from './PlaylistDetails'
import PlaylistSongs from './PlaylistSongs'
import PlaylistActions from './PlaylistActions'
import { Title, isReadOnly } from '../common'
const useStyles = makeStyles(
(theme) => ({
playlistActions: {},
}),
{
name: 'NDPlaylistShow',
}
)
const PlaylistShowLayout = (props) => {
const { loading, ...context } = useShowContext(props)
const { record } = context
const classes = useStyles()
return (
<>
{record && <PlaylistDetails {...context} />}
@ -30,7 +42,12 @@ const PlaylistShowLayout = (props) => {
{...props}
readOnly={isReadOnly(record.owner)}
title={<Title subTitle={record.name} />}
actions={<PlaylistActions record={record} />}
actions={
<PlaylistActions
className={classes.playlistActions}
record={record}
/>
}
resource={'playlistTrack'}
exporter={false}
perPage={0}

34
ui/src/themes/green.js Normal file
View File

@ -0,0 +1,34 @@
import green from '@material-ui/core/colors/green'
export default {
themeName: 'Green',
palette: {
primary: {
light: green['300'],
main: green['500'],
},
secondary: {
main: green['900'],
contrastText: '#fff',
},
type: 'dark',
},
overrides: {
MuiFormGroup: {
root: {
color: 'white',
},
},
NDLogin: {
systemNameLink: {
color: '#fff',
},
welcome: {
color: '#eee',
},
},
},
player: {
theme: 'dark',
},
}

View File

@ -1,5 +1,6 @@
import LightTheme from './light'
import DarkTheme from './dark'
import GreenTheme from './green'
import SpotifyTheme from './spotify'
export default { LightTheme, DarkTheme, SpotifyTheme }
export default { LightTheme, DarkTheme, GreenTheme, SpotifyTheme }

View File

@ -1,30 +1,348 @@
import green from '@material-ui/core/colors/green'
const spotifyGreen = {
300: '#62ec83',
500: '#1db954',
900: '#008827',
}
// For Album, Playlist
const musicListActions = {
padding: '1rem 0',
alignItems: 'center',
'@global': {
button: {
margin: 5,
border: '1px solid transparent',
backgroundColor: 'inherit',
color: '#b3b3b3',
'&:hover': {
border: '1px solid #b3b3b3',
backgroundColor: 'inherit !important',
},
},
'button:first-child': {
'@media screen and (max-width: 720px)': {
transform: 'scale(1.5)',
margin: '1rem',
'&:hover': {
transform: 'scale(1.6) !important',
},
},
transform: 'scale(2)',
margin: '1.5rem',
minWidth: 0,
padding: 5,
transition: 'transform .3s ease',
background: spotifyGreen['500'],
color: '#fff',
borderRadius: 500,
border: 0,
'&:hover': {
transform: 'scale(2.1)',
backgroundColor: `${spotifyGreen['500']} !important`,
border: 0,
},
},
'button:first-child>span:first-child': {
padding: 0,
},
'button:first-child>span:first-child>span': {
display: 'none',
},
'button>span:first-child>span, button:not(:first-child)>span:first-child>svg': {
color: '#b3b3b3',
},
},
}
export default {
themeName: 'Spotify-ish',
typography: {
fontFamily: "system-ui, 'Helvetica Neue', Helvetica, Arial",
h6: {
fontSize: '1rem', // AppBar title
},
},
palette: {
primary: {
light: green['300'],
main: green['500'],
light: spotifyGreen['300'],
main: spotifyGreen['500'],
},
secondary: {
main: green['900'],
main: '#fff',
contrastText: '#fff',
},
background: {
default: '#121212',
paper: 'inherit',
},
type: 'dark',
},
overrides: {
MuiPopover: {
paper: {
backgroundColor: '#121212',
},
},
MuiDialog: {
paper: {
backgroundColor: '#121212',
},
},
MuiFormGroup: {
root: {
color: 'white',
color: spotifyGreen['500'],
},
},
MuiMenuItem: {
root: {
fontSize: '0.875rem',
},
},
MuiDivider: {
root: {
margin: '.75rem 0',
},
},
MuiIconButton: {
label: {
// color: '#fff'
},
},
MuiButton: {
root: {
background: spotifyGreen['500'],
color: '#fff',
// margin: '5px',
border: '1px solid transparent',
borderRadius: 500,
'&:hover': {
background: `${spotifyGreen['900']} !important`,
},
},
textSecondary: {
border: '1px solid #b3b3b3',
background: '#000',
'&:hover': {
border: '1px solid #fff !important',
background: '#000 !important',
},
},
label: {
color: '#fff',
paddingRight: '1rem',
paddingLeft: '0.7rem',
},
},
MuiDrawer: {
root: {
background: '#000',
paddingTop: '10px',
},
},
MuiTableRow: {
root: {
padding: '10px 0',
transition: 'background-color .3s ease',
'&:hover': {
backgroundColor: '#1d1d1d !important',
},
'@global': {
'td:nth-child(4)': {
color: '#fff !important',
},
},
},
},
MuiTableCell: {
root: {
borderBottom: '1px solid #1d1d1d',
padding: '10px !important',
color: '#b3b3b3 !important',
},
head: {
borderBottom: '1px solid #282828',
fontSize: '0.75rem',
textTransform: 'uppercase',
letterSpacing: 1.2,
},
},
MuiAppBar: {
positionFixed: {
backgroundColor: '#000 !important',
boxShadow: 'none',
},
},
NDAlbumGridView: {
albumName: {
marginTop: '0.5rem',
fontWeight: 700,
textTransform: 'none',
color: '#fff',
},
albumSubtitle: {
color: '#b3b3b3',
},
albumContainer: {
backgroundColor: '#181818',
borderRadius: '.5rem',
padding: '.75rem',
transition: 'background-color .3s ease',
'&:hover': {
backgroundColor: '#282828',
},
},
albumPlayButton: {
backgroundColor: spotifyGreen['500'],
borderRadius: '50%',
boxShadow: '0 8px 8px rgb(0 0 0 / 30%)',
padding: '0.35rem',
transition: 'padding .3s ease',
'&:hover': {
background: `${spotifyGreen['500']} !important`,
padding: '0.45rem',
},
},
},
NDPlaylistDetails: {
container: {
background: 'linear-gradient(#1d1d1d, transparent)',
borderRadius: 0,
paddingTop: '2.5rem !important',
boxShadow: 'none',
},
title: {
fontSize: 'calc(1.5rem + 1.5vw);',
fontWeight: 700,
color: '#fff',
},
details: {
fontSize: '.875rem',
color: 'rgba(255,255,255, 0.8)',
},
},
NDAlbumDetails: {
root: {
background: 'linear-gradient(#1d1d1d, transparent)',
borderRadius: 0,
boxShadow: 'none',
},
cardContents: {
alignItems: 'center',
paddingTop: '1.5rem',
},
recordName: {
fontSize: 'calc(1rem + 1.5vw);',
fontWeight: 700,
},
recordArtist: {
fontSize: '.875rem',
fontWeight: 700,
},
recordMeta: {
fontSize: '.875rem',
color: 'rgba(255,255,255, 0.8)',
},
commentBlock: {
fontSize: '.875rem',
color: 'rgba(255,255,255, 0.8)',
},
},
NDAlbumShow: {
albumActions: musicListActions,
},
NDPlaylistShow: {
playlistActions: musicListActions,
},
NDAudioPlayer: {
audioTitle: {
color: '#fff',
fontSize: '0.875rem',
'&.songTitle': {
fontWeight: 400,
},
'&.songInfo': {
fontSize: '0.675rem',
color: '#b3b3b3',
},
},
player: {
border: '10px solid blue',
},
},
NDLogin: {
main: {
boxShadow: 'inset 0 0 0 2000px rgba(0, 0, 0, .8)',
},
systemNameLink: {
color: '#fff',
textDecoration: 'none',
},
welcome: {
color: '#eee',
systemName: {
marginTop: '0.5em',
// borderBottom: '1px solid #282828',
marginBottom: '1em',
},
icon: {
backgroundColor: 'inherit',
},
card: {
// background: 'none',
background: 'none',
boxShadow: 'none',
padding: '10px 0',
minWidth: 360,
},
avatar: {
marginBottom: 0,
},
},
RaLayout: {
content: {
padding: '0 !important',
background: 'linear-gradient(#171717, #121212)',
},
},
RaListToolbar: {
toolbar: {
padding: '0 .55rem !important',
},
},
RaSearchInput: {
input: {
// borderRadius: 500,
// width: '20rem',
paddingLeft: '.9rem',
border: 0,
// height: '2.5rem',
},
},
RaFilterButton: {
root: {
marginRight: '1rem',
},
},
RaPaginationActions: {
button: {
backgroundColor: 'inherit',
minWidth: 48,
margin: '0 4px',
border: '1px solid #282828',
'@global': {
'> .MuiButton-label': {
padding: 0,
},
},
},
actions: {
'@global': {
'.next-page': {
marginLeft: 8,
marginRight: 8,
},
'.previous-page': {
marginRight: 8,
},
},
},
},
},