feat(ui): add CoverArtAvatar component and integrate it into artist and playlist lists
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
@@ -22,6 +22,7 @@ import { useDrag } from 'react-dnd'
|
|||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import {
|
import {
|
||||||
ArtistContextMenu,
|
ArtistContextMenu,
|
||||||
|
CoverArtAvatar,
|
||||||
List,
|
List,
|
||||||
QuickFilter,
|
QuickFilter,
|
||||||
useGetHandleArtistClick,
|
useGetHandleArtistClick,
|
||||||
@@ -43,6 +44,10 @@ const useStyles = makeStyles({
|
|||||||
verticalAlign: 'text-top',
|
verticalAlign: 'text-top',
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
|
'& td': {
|
||||||
|
paddingTop: '4px !important',
|
||||||
|
paddingBottom: '4px !important',
|
||||||
|
},
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
'& $contextMenu': {
|
'& $contextMenu': {
|
||||||
visibility: 'visible',
|
visibility: 'visible',
|
||||||
@@ -170,6 +175,7 @@ const ArtistListView = ({ hasShow, hasEdit, hasList, width, ...rest }) => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ArtistDatagrid rowClick={handleArtistLink} classes={{ row: classes.row }}>
|
<ArtistDatagrid rowClick={handleArtistLink} classes={{ row: classes.row }}>
|
||||||
|
<CoverArtAvatar source="id" />
|
||||||
<TextField source="name" />
|
<TextField source="name" />
|
||||||
<FunctionField
|
<FunctionField
|
||||||
source="albumCount"
|
source="albumCount"
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import List from '@material-ui/core/List'
|
import List from '@material-ui/core/List'
|
||||||
import ListItem from '@material-ui/core/ListItem'
|
import ListItem from '@material-ui/core/ListItem'
|
||||||
|
import ListItemAvatar from '@material-ui/core/ListItemAvatar'
|
||||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||||
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
|
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
|
||||||
import ListItemText from '@material-ui/core/ListItemText'
|
import ListItemText from '@material-ui/core/ListItemText'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { sanitizeListRestProps } from 'react-admin'
|
import { sanitizeListRestProps } from 'react-admin'
|
||||||
import { ArtistContextMenu, RatingField } from '../common'
|
import { ArtistContextMenu, CoverArtAvatar, RatingField } from '../common'
|
||||||
import config from '../config'
|
import config from '../config'
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
@@ -47,7 +48,11 @@ const ArtistSimpleList = ({
|
|||||||
data[id] && (
|
data[id] && (
|
||||||
<span key={id} onClick={() => linkType(id)}>
|
<span key={id} onClick={() => linkType(id)}>
|
||||||
<ListItem className={classes.listItem} button={true}>
|
<ListItem className={classes.listItem} button={true}>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<CoverArtAvatar record={data[id]} />
|
||||||
|
</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
|
style={{ marginLeft: '8px' }}
|
||||||
primary={
|
primary={
|
||||||
<>
|
<>
|
||||||
<div className={classes.title}>{data[id].name}</div>
|
<div className={classes.title}>{data[id].name}</div>
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { useRecordContext } from 'react-admin'
|
||||||
|
import { Avatar } from '@material-ui/core'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import subsonic from '../subsonic'
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
avatar: {
|
||||||
|
width: '55px',
|
||||||
|
height: '55px',
|
||||||
|
},
|
||||||
|
square: {
|
||||||
|
borderRadius: '4px',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const CoverArtAvatar = ({
|
||||||
|
record: recordProp,
|
||||||
|
variant = 'circular',
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
const recordContext = useRecordContext()
|
||||||
|
const record = recordProp || recordContext
|
||||||
|
if (!record) return null
|
||||||
|
const square = variant !== 'circular'
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
src={subsonic.getCoverArtUrl(record, 80, square)}
|
||||||
|
variant={variant}
|
||||||
|
className={clsx(classes.avatar, square && classes.square)}
|
||||||
|
alt={record.name}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
CoverArtAvatar.defaultProps = { label: '', sortable: false }
|
||||||
@@ -44,3 +44,4 @@ export * from './ParticipantsInfo'
|
|||||||
export * from './OverflowTooltip'
|
export * from './OverflowTooltip'
|
||||||
export * from './useSearchRefocus'
|
export * from './useSearchRefocus'
|
||||||
export * from './ImageUploadOverlay'
|
export * from './ImageUploadOverlay'
|
||||||
|
export * from './CoverArtAvatar'
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import {
|
|||||||
usePermissions,
|
usePermissions,
|
||||||
} from 'react-admin'
|
} from 'react-admin'
|
||||||
import Switch from '@material-ui/core/Switch'
|
import Switch from '@material-ui/core/Switch'
|
||||||
import { Avatar } from '@material-ui/core'
|
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { useMediaQuery } from '@material-ui/core'
|
import { useMediaQuery } from '@material-ui/core'
|
||||||
import {
|
import {
|
||||||
|
CoverArtAvatar,
|
||||||
DurationField,
|
DurationField,
|
||||||
List,
|
List,
|
||||||
Writable,
|
Writable,
|
||||||
@@ -29,17 +29,11 @@ import {
|
|||||||
} from '../common'
|
} from '../common'
|
||||||
import PlaylistListActions from './PlaylistListActions'
|
import PlaylistListActions from './PlaylistListActions'
|
||||||
import ChangePublicStatusButton from './ChangePublicStatusButton'
|
import ChangePublicStatusButton from './ChangePublicStatusButton'
|
||||||
import subsonic from '../subsonic'
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
button: {
|
button: {
|
||||||
color: theme.palette.type === 'dark' ? 'white' : undefined,
|
color: theme.palette.type === 'dark' ? 'white' : undefined,
|
||||||
},
|
},
|
||||||
coverArt: {
|
|
||||||
width: '40px',
|
|
||||||
height: '40px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const PlaylistFilter = (props) => {
|
const PlaylistFilter = (props) => {
|
||||||
@@ -126,25 +120,6 @@ const ToggleAutoImport = ({ resource, source }) => {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const CoverArtField = () => {
|
|
||||||
const classes = useStyles()
|
|
||||||
const record = useRecordContext()
|
|
||||||
if (!record) return null
|
|
||||||
return (
|
|
||||||
<Avatar
|
|
||||||
src={subsonic.getCoverArtUrl(record, 80, true)}
|
|
||||||
variant="square"
|
|
||||||
className={classes.coverArt}
|
|
||||||
alt={record.name}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
CoverArtField.defaultProps = {
|
|
||||||
label: '',
|
|
||||||
sortable: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
const PlaylistListBulkActions = (props) => {
|
const PlaylistListBulkActions = (props) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
return (
|
return (
|
||||||
@@ -204,7 +179,7 @@ const PlaylistList = (props) => {
|
|||||||
bulkActionButtons={!isXsmall && <PlaylistListBulkActions />}
|
bulkActionButtons={!isXsmall && <PlaylistListBulkActions />}
|
||||||
>
|
>
|
||||||
<Datagrid rowClick="show" isRowSelectable={(r) => isWritable(r?.ownerId)}>
|
<Datagrid rowClick="show" isRowSelectable={(r) => isWritable(r?.ownerId)}>
|
||||||
<CoverArtField source="id" />
|
<CoverArtAvatar source="id" variant="square" />
|
||||||
<TextField source="name" />
|
<TextField source="name" />
|
||||||
{columns}
|
{columns}
|
||||||
<Writable>
|
<Writable>
|
||||||
|
|||||||
Reference in New Issue
Block a user