mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
Merge 8fbe8e2864e4e160bf0aedd743fb354cc8fc9456 into 600ea5482c36d3705fbca1d9ab749d9bdafd6f80
This commit is contained in:
commit
e1423dec2a
@ -5,10 +5,11 @@ import { GlobalHotKeys } from 'react-hotkeys'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import { useMediaQuery } from '@material-ui/core'
|
||||
import { RiSaveLine } from 'react-icons/ri'
|
||||
import { LoveButton, useToggleLove } from '../common'
|
||||
import { LoveButton, RatingField, useToggleLove } from '../common'
|
||||
import { openSaveQueueDialog } from '../actions'
|
||||
import { keyMap } from '../hotkeys'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import config from '../config'
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
toolbar: {
|
||||
@ -53,6 +54,10 @@ const useStyles = makeStyles((theme) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
rating: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}))
|
||||
|
||||
const PlayerToolbar = ({ id, isRadio }) => {
|
||||
@ -99,16 +104,34 @@ const PlayerToolbar = ({ id, isRadio }) => {
|
||||
/>
|
||||
)
|
||||
|
||||
// Inline star rating for the currently-playing track. Only shown when star
|
||||
// rating is enabled and we have a real (non-radio) song loaded. `alwaysVisible`
|
||||
// keeps the empty stars on screen so an unrated track can be rated in place.
|
||||
const ratingControl =
|
||||
config.enableStarRating && !isRadio && !!id && data ? (
|
||||
<RatingField
|
||||
record={data}
|
||||
resource={'song'}
|
||||
size={'small'}
|
||||
alwaysVisible
|
||||
className={classes.rating}
|
||||
/>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<>
|
||||
<GlobalHotKeys keyMap={keyMap} handlers={handlers} allowChanges />
|
||||
{isDesktop ? (
|
||||
<li className={`${listItemClass} item`}>
|
||||
{ratingControl}
|
||||
{saveQueueButton}
|
||||
{loveButton}
|
||||
</li>
|
||||
) : (
|
||||
<>
|
||||
{ratingControl && (
|
||||
<li className={`${listItemClass} item`}>{ratingControl}</li>
|
||||
)}
|
||||
<li className={`${listItemClass} item`}>{saveQueueButton}</li>
|
||||
<li className={`${listItemClass} item`}>{loveButton}</li>
|
||||
</>
|
||||
|
||||
@ -30,6 +30,11 @@ vi.mock('../common', () => ({
|
||||
Love
|
||||
</button>
|
||||
),
|
||||
RatingField: ({ className }) => (
|
||||
<span data-testid="rating-field" className={className}>
|
||||
Rating
|
||||
</span>
|
||||
),
|
||||
useToggleLove: vi.fn(),
|
||||
}))
|
||||
|
||||
@ -61,14 +66,15 @@ describe('<PlayerToolbar />', () => {
|
||||
useMediaQuery.mockReturnValue(true) // isDesktop = true
|
||||
})
|
||||
|
||||
it('renders desktop toolbar with both buttons', () => {
|
||||
it('renders desktop toolbar with rating and buttons', () => {
|
||||
render(<PlayerToolbar id="song-1" />)
|
||||
|
||||
// Both buttons should be in a single list item
|
||||
// Rating + both buttons should be in a single list item
|
||||
const listItems = screen.getAllByRole('listitem')
|
||||
expect(listItems).toHaveLength(1)
|
||||
|
||||
// Verify both buttons are rendered
|
||||
// Verify rating and both buttons are rendered
|
||||
expect(screen.getByTestId('rating-field')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('save-queue-button')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('love-button')).toBeInTheDocument()
|
||||
|
||||
@ -76,6 +82,13 @@ describe('<PlayerToolbar />', () => {
|
||||
expect(listItems[0].className).toContain('toolbar')
|
||||
})
|
||||
|
||||
it('hides the rating for radio streams', () => {
|
||||
render(<PlayerToolbar id="song-1" isRadio={true} />)
|
||||
|
||||
expect(screen.queryByTestId('rating-field')).not.toBeInTheDocument()
|
||||
expect(screen.getByTestId('love-button')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('disables save queue button when isRadio is true', () => {
|
||||
render(<PlayerToolbar id="song-1" isRadio={true} />)
|
||||
|
||||
@ -109,20 +122,22 @@ describe('<PlayerToolbar />', () => {
|
||||
useMediaQuery.mockReturnValue(false) // isDesktop = false
|
||||
})
|
||||
|
||||
it('renders mobile toolbar with buttons in separate list items', () => {
|
||||
it('renders mobile toolbar with rating and buttons in separate list items', () => {
|
||||
render(<PlayerToolbar id="song-1" />)
|
||||
|
||||
// Each button should be in its own list item
|
||||
// Rating + each button should be in its own list item
|
||||
const listItems = screen.getAllByRole('listitem')
|
||||
expect(listItems).toHaveLength(2)
|
||||
expect(listItems).toHaveLength(3)
|
||||
|
||||
// Verify both buttons are rendered
|
||||
// Verify rating and both buttons are rendered
|
||||
expect(screen.getByTestId('rating-field')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('save-queue-button')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('love-button')).toBeInTheDocument()
|
||||
|
||||
// Verify mobile classes are applied
|
||||
expect(listItems[0].className).toContain('mobileListItem')
|
||||
expect(listItems[1].className).toContain('mobileListItem')
|
||||
expect(listItems[2].className).toContain('mobileListItem')
|
||||
})
|
||||
|
||||
it('disables save queue button when isRadio is true', () => {
|
||||
|
||||
@ -27,6 +27,7 @@ export const RatingField = ({
|
||||
className,
|
||||
size,
|
||||
color,
|
||||
alwaysVisible,
|
||||
...rest
|
||||
}) => {
|
||||
const record = useRecordContext(rest) || {}
|
||||
@ -59,7 +60,7 @@ export const RatingField = ({
|
||||
className={clsx(
|
||||
className,
|
||||
classes.rating,
|
||||
rating > 0 ? classes.show : classes.hide,
|
||||
rating > 0 || alwaysVisible ? classes.show : classes.hide,
|
||||
)}
|
||||
value={rating}
|
||||
size={size}
|
||||
@ -75,10 +76,15 @@ RatingField.propTypes = {
|
||||
record: PropTypes.object,
|
||||
visible: PropTypes.bool,
|
||||
size: PropTypes.string,
|
||||
// When true, the empty stars are always shown (not just on hover / when
|
||||
// already rated). Used by the player toolbar so the current track can be
|
||||
// rated directly.
|
||||
alwaysVisible: PropTypes.bool,
|
||||
}
|
||||
|
||||
RatingField.defaultProps = {
|
||||
visible: true,
|
||||
size: 'small',
|
||||
color: 'inherit',
|
||||
alwaysVisible: false,
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user