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:
Neil Chauhan
2021-04-08 01:32:52 +05:30
committed by GitHub
parent 840521ffe2
commit 48ae4f7479
17 changed files with 277 additions and 16 deletions
+13 -2
View File
@@ -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}>
+73
View File
@@ -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',
}
+20 -9
View File
@@ -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}>
+2
View File
@@ -28,3 +28,5 @@ export * from './useTraceUpdate'
export * from './Writable'
export * from './SongSimpleList'
export * from './ArtistSimpleList'
export * from './RatingField'
export * from './useRating'
+47
View File
@@ -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]
}