Add 5-star rating system(#986)
* Added Star Rating functionality for Songs * Added Star Rating functionality for Artists * Added Star Rating functionality for AlbumListView * Added Star Rating functionality for AlbumDetails and improved typography for title * Added functionality to turn on/off Star Rating functionality for Songs * Added functionality to turn on/off Star Rating functionality for Artists * Added functionality to turn on/off Star Rating functionality for Albums * Added enableStarRating to server config * Resolved the bugs and improved the ratings functionality. * synced repo and removed duplicate key * changed the default rating size to small, and changed the color to match the theme. * Added translations for ratings, and Top Rated tab in side menu. * Changed rating translation to topRated in albumLists, and added has_rating filter to topRated. * Added empty stars icon to RatingField. * Added sortable=false in AlbumSongs and added sortByOrder=DESC in all List components. * Added translations for rating, for artists and albums, and removed the sortByOrder=DESC from SimpleLists.
This commit is contained in:
@@ -7,7 +7,8 @@ import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { sanitizeListRestProps } from 'ra-core'
|
||||
import { ArtistContextMenu } from './index'
|
||||
import { ArtistContextMenu, RatingField } from './index'
|
||||
import config from '../config'
|
||||
|
||||
const useStyles = makeStyles(
|
||||
{
|
||||
@@ -48,7 +49,17 @@ export const ArtistSimpleList = ({
|
||||
<ListItem className={classes.listItem} button={true}>
|
||||
<ListItemText
|
||||
primary={
|
||||
<div className={classes.title}>{data[id].name}</div>
|
||||
<>
|
||||
<div className={classes.title}>{data[id].name}</div>
|
||||
{config.enableStarRating && (
|
||||
<RatingField
|
||||
record={data[id]}
|
||||
source={'rating'}
|
||||
resource={'artist'}
|
||||
size={'small'}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ListItemSecondaryAction className={classes.rightIcon}>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Rating from '@material-ui/lab/Rating'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import StarBorderIcon from '@material-ui/icons/StarBorder'
|
||||
import clsx from 'clsx'
|
||||
import { useRating } from './useRating'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
rating: {
|
||||
color: (props) => props.color,
|
||||
visibility: (props) => (props.visible === false ? 'hidden' : 'inherit'),
|
||||
},
|
||||
show: {
|
||||
visibility: 'visible !important',
|
||||
},
|
||||
hide: {
|
||||
visibility: 'hidden',
|
||||
},
|
||||
})
|
||||
|
||||
export const RatingField = ({
|
||||
resource,
|
||||
record,
|
||||
visible,
|
||||
className,
|
||||
size,
|
||||
color,
|
||||
}) => {
|
||||
const [rate, rating] = useRating(resource, record)
|
||||
const classes = useStyles({ visible, rating: record.rating, color })
|
||||
|
||||
const stopPropagation = (e) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleRating = useCallback(
|
||||
(e, val) => {
|
||||
rate(val, e.target.name)
|
||||
},
|
||||
[rate]
|
||||
)
|
||||
|
||||
return (
|
||||
<span onClick={(e) => stopPropagation(e)}>
|
||||
<Rating
|
||||
name={record.id}
|
||||
className={clsx(
|
||||
className,
|
||||
classes.rating,
|
||||
rating > 0 ? classes.show : classes.hide
|
||||
)}
|
||||
value={rating}
|
||||
size={size}
|
||||
emptyIcon={<StarBorderIcon fontSize="inherit" />}
|
||||
onChange={(e, newValue) => handleRating(e, newValue)}
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
RatingField.propTypes = {
|
||||
resource: PropTypes.string.isRequired,
|
||||
record: PropTypes.object.isRequired,
|
||||
visible: PropTypes.bool,
|
||||
size: PropTypes.string,
|
||||
}
|
||||
|
||||
RatingField.defaultProps = {
|
||||
record: {},
|
||||
visible: true,
|
||||
size: 'small',
|
||||
color: 'inherit',
|
||||
}
|
||||
@@ -7,9 +7,10 @@ import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { sanitizeListRestProps } from 'ra-core'
|
||||
import { DurationField, SongContextMenu } from './index'
|
||||
import { DurationField, SongContextMenu, RatingField } from './index'
|
||||
import { setTrack } from '../actions'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import config from '../config'
|
||||
|
||||
const useStyles = makeStyles(
|
||||
{
|
||||
@@ -77,17 +78,27 @@ export const SongSimpleList = ({
|
||||
<div className={classes.title}>{data[id].title}</div>
|
||||
}
|
||||
secondary={
|
||||
<span className={classes.secondary}>
|
||||
<span className={classes.artist}>
|
||||
{data[id].artist}
|
||||
<>
|
||||
<span className={classes.secondary}>
|
||||
<span className={classes.artist}>
|
||||
{data[id].artist}
|
||||
</span>
|
||||
<span className={classes.timeStamp}>
|
||||
<DurationField
|
||||
record={data[id]}
|
||||
source={'duration'}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<span className={classes.timeStamp}>
|
||||
<DurationField
|
||||
{config.enableStarRating && (
|
||||
<RatingField
|
||||
record={data[id]}
|
||||
source={'duration'}
|
||||
source={'rating'}
|
||||
resource={'song'}
|
||||
size={'small'}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ListItemSecondaryAction className={classes.rightIcon}>
|
||||
|
||||
@@ -28,3 +28,5 @@ export * from './useTraceUpdate'
|
||||
export * from './Writable'
|
||||
export * from './SongSimpleList'
|
||||
export * from './ArtistSimpleList'
|
||||
export * from './RatingField'
|
||||
export * from './useRating'
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useState, useCallback, useEffect, useRef } from 'react'
|
||||
import { useDataProvider, useNotify } from 'react-admin'
|
||||
import subsonic from '../subsonic'
|
||||
|
||||
export const useRating = (resource, record) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const notify = useNotify()
|
||||
const dataProvider = useDataProvider()
|
||||
const mountedRef = useRef(false)
|
||||
const rating = record.rating
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true
|
||||
return () => {
|
||||
mountedRef.current = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
const refreshRating = useCallback(() => {
|
||||
dataProvider
|
||||
.getOne(resource, { id: record.id })
|
||||
.then(() => {
|
||||
if (mountedRef.current) {
|
||||
setLoading(false)
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log('Error encountered: ' + e)
|
||||
})
|
||||
}, [dataProvider, record, resource])
|
||||
|
||||
const rate = (val, id) => {
|
||||
setLoading(true)
|
||||
subsonic
|
||||
.setRating(id, val)
|
||||
.then(refreshRating)
|
||||
.catch((e) => {
|
||||
console.log('Error setting star rating: ', e)
|
||||
notify('ra.page.error', 'warning')
|
||||
if (mountedRef.current) {
|
||||
setLoading(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return [rate, rating, loading]
|
||||
}
|
||||
Reference in New Issue
Block a user