Add "AddToPlaylist" to AlbumContextMenu

This commit is contained in:
Deluan 2020-05-17 18:55:17 -04:00
parent 176bfe1506
commit 308163c2e0
10 changed files with 148 additions and 119 deletions

5
ui/package-lock.json generated
View File

@ -10530,6 +10530,11 @@
"object-visit": "^1.0.0"
}
},
"material-ui-nested-menu-item": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/material-ui-nested-menu-item/-/material-ui-nested-menu-item-1.0.2.tgz",
"integrity": "sha512-LZb8xI0FrAI/A3P2vT3CB9bmSoOFWOK0dikTc1t9VvEpp1a8hZkbVUz7VhETnoLUYu3NXCkgulmXcl3zitqI9A=="
},
"md5-hex": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz",

View File

@ -6,6 +6,7 @@
"deepmerge": "^4.2.2",
"jwt-decode": "^2.2.0",
"lodash.throttle": "^4.1.1",
"material-ui-nested-menu-item": "^1.0.2",
"md5-hex": "^3.0.1",
"prop-types": "^15.7.2",
"ra-data-json-server": "^3.4.1",

View File

@ -7,6 +7,8 @@ import { makeStyles } from '@material-ui/core/styles'
import { useDataProvider, useNotify, useTranslate } from 'react-admin'
import { useDispatch } from 'react-redux'
import { addTracks, playTracks, shuffleTracks } from '../audioplayer'
import NestedMenuItem from 'material-ui-nested-menu-item'
import { AddToPlaylistMenu } from '../common'
const useStyles = makeStyles({
icon: {
@ -96,6 +98,15 @@ const AlbumContextMenu = ({ record, color }) => {
{options[key].label}
</MenuItem>
))}
<NestedMenuItem
label={translate('resources.song.actions.addToPlaylist')}
parentMenuOpen={open}
>
<AddToPlaylistMenu
albumId={[record.id]}
onClose={() => setAnchorEl(null)}
/>
</NestedMenuItem>
</Menu>
</div>
)

View File

@ -67,7 +67,7 @@ const AlbumSongs = (props) => {
const isXsmall = useMediaQuery((theme) => theme.breakpoints.down('xs'))
const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('md'))
const controllerProps = useListController(props)
const { bulkActionButtons, albumId, expand, className } = props
const { bulkActionButtons, albumId, className } = props
const { data, ids, version, loaded } = controllerProps
let multiDisc = false
@ -116,7 +116,7 @@ const AlbumSongs = (props) => {
<DatagridLoading
classes={classes}
className={className}
expand={expand}
expand={null}
hasBulkActions={hasBulkActions}
nbChildren={3}
size={'small'}

View File

@ -0,0 +1,88 @@
import React from 'react'
import {
useDataProvider,
useGetList,
useNotify,
useTranslate,
} from 'react-admin'
import { MenuItem } from '@material-ui/core'
import PropTypes from 'prop-types'
export const addTracksToPlaylist = (dataProvider, selectedIds, playlistId) =>
dataProvider
.create('playlistTrack', {
data: { ids: selectedIds },
filter: { playlist_id: playlistId },
})
.then(() => selectedIds.length)
export const addAlbumToPlaylist = (dataProvider, albumId, playlistId) =>
dataProvider
.getList('albumSong', {
pagination: { page: 1, perPage: -1 },
sort: { field: 'discNumber asc, trackNumber asc', order: 'ASC' },
filter: { album_id: albumId },
})
.then((response) => response.data.map((song) => song.id))
.then((ids) => addTracksToPlaylist(dataProvider, ids, playlistId))
const AddToPlaylistMenu = ({ selectedIds, albumId, onClose }) => {
const translate = useTranslate()
const notify = useNotify()
const dataProvider = useDataProvider()
const { ids, data, loaded } = useGetList(
'playlist',
{ page: 1, perPage: -1 },
{ field: 'name', order: 'ASC' },
{}
)
if (!loaded) {
return <MenuItem>Loading...</MenuItem>
}
const handleItemClick = (e) => {
e.preventDefault()
const value = e.target.getAttribute('value')
if (value !== '') {
const add = albumId
? addAlbumToPlaylist(dataProvider, albumId, value)
: addTracksToPlaylist(dataProvider, selectedIds, value)
add
.then((len) => {
notify(
translate('message.songsAddedToPlaylist', {
smart_count: len,
})
)
})
.catch(() => {
notify('ra.page.error', 'warning')
})
}
e.stopPropagation()
onClose && onClose()
}
return (
<>
{ids.map((id) => (
<MenuItem value={id} key={id} onClick={handleItemClick}>
{data[id].name}
</MenuItem>
))}
</>
)
}
AddToPlaylistMenu.propTypes = {
selectedIds: PropTypes.arrayOf(PropTypes.any).isRequired,
albumId: PropTypes.string,
}
AddToPlaylistMenu.defaultProps = {
selectedIds: [],
}
export default AddToPlaylistMenu

View File

@ -1,77 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useGetList, useTranslate } from 'react-admin'
import { makeStyles } from '@material-ui/core/styles'
import Avatar from '@material-ui/core/Avatar'
import List from '@material-ui/core/List'
import ListItem from '@material-ui/core/ListItem'
import ListItemAvatar from '@material-ui/core/ListItemAvatar'
import ListItemText from '@material-ui/core/ListItemText'
import DialogTitle from '@material-ui/core/DialogTitle'
import Dialog from '@material-ui/core/Dialog'
import { blue } from '@material-ui/core/colors'
import PlaylistIcon from '../icons/Playlist'
const useStyles = makeStyles({
avatar: {
backgroundColor: blue[100],
color: blue[600],
},
})
function SelectPlaylistDialog(props) {
const classes = useStyles()
const translate = useTranslate()
const { onClose, selectedValue, open } = props
const { ids, data, loaded } = useGetList(
'playlist',
{ page: 1, perPage: -1 },
{ field: '', order: '' },
{}
)
if (!loaded) {
return <div />
}
const handleClose = () => {
onClose(selectedValue)
}
const handleListItemClick = (value) => {
onClose(value)
}
return (
<Dialog
onClose={handleClose}
aria-labelledby="select-playlist-dialog-title"
open={open}
scroll={'paper'}
>
<DialogTitle id="select-playlist-dialog-title">
{translate('resources.playlist.actions.selectPlaylist')}
</DialogTitle>
<List>
{ids.map((id) => (
<ListItem button onClick={() => handleListItemClick(id)} key={id}>
<ListItemAvatar>
<Avatar className={classes.avatar}>
<PlaylistIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={data[id].name} />
</ListItem>
))}
</List>
</Dialog>
)
}
SelectPlaylistDialog.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
selectedValue: PropTypes.string.isRequired,
}
export default SelectPlaylistDialog

View File

@ -11,7 +11,7 @@ import SizeField from './SizeField'
import DocLink from './DocLink'
import List from './List'
import SongDatagridRow from './SongDatagridRow'
import SelectPlaylistDialog from './SelectPlaylistDialog'
import AddToPlaylistMenu from './AddToPlaylistMenu'
export {
Title,
@ -29,5 +29,5 @@ export {
formatRange,
ArtistLinkField,
artistLink,
SelectPlaylistDialog,
AddToPlaylistMenu,
}

View File

@ -237,7 +237,8 @@
"transcodingDisabled": "Changing the transcoding configuration through the web interface is disabled for security reasons. If you would like to change (edit or add) transcoding options, restart the server with the %{config} configuration option.",
"transcodingEnabled": "Navidrome is currently running with %{config}, making it possible to run system commands from the transcoding settings using the web interface. We recommend to disable it for security reasons and only enable it when configuring Transcoding options.",
"discSubtitle": "%{subtitle} (disc %{number})",
"discWithoutSubtitle": "Disc %{number}"
"discWithoutSubtitle": "Disc %{number}",
"songsAddedToPlaylist": "Added 1 song to playlist |||| Added %{smart_count} songs to playlist"
},
"menu": {
"library": "Library",

View File

@ -1,59 +1,46 @@
import React, { useState } from 'react'
import {
Button,
useTranslate,
useUnselectAll,
useDataProvider,
useNotify,
} from 'react-admin'
import SelectPlaylistDialog from '../common/SelectPlaylistDialog'
import React from 'react'
import { Button, useTranslate, useUnselectAll } from 'react-admin'
import { Menu } from '@material-ui/core'
import PlaylistAddIcon from '@material-ui/icons/PlaylistAdd'
import { AddToPlaylistMenu } from '../common'
const AddToPlaylistButton = ({ resource, selectedIds }) => {
const [open, setOpen] = useState(false)
const [selectedValue, setSelectedValue] = useState('')
const [anchorEl, setAnchorEl] = React.useState(null)
const translate = useTranslate()
const unselectAll = useUnselectAll()
const notify = useNotify()
const dataProvider = useDataProvider()
const handleClickOpen = () => {
setOpen(true)
const handleClick = (event) => {
setAnchorEl(event.currentTarget)
}
const handleClose = (value) => {
if (value !== '') {
dataProvider
.create('playlistTrack', {
data: { ids: selectedIds },
filter: { playlist_id: value },
})
.then(() => {
notify(`Added ${selectedIds.length} songs to playlist`)
})
.catch(() => {
notify('ra.page.error', 'warning')
})
}
setOpen(false)
setSelectedValue(value)
const handleClose = () => {
setAnchorEl(null)
unselectAll(resource)
}
return (
<>
<Button
aria-controls="simple-menu"
aria-haspopup="true"
onClick={handleClick}
color="secondary"
onClick={handleClickOpen}
label={translate('resources.song.actions.addToPlaylist')}
>
<PlaylistAddIcon />
</Button>
<SelectPlaylistDialog
selectedValue={selectedValue}
open={open}
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
/>
>
<AddToPlaylistMenu
selectedIds={selectedIds}
menuOpen={Boolean(anchorEl)}
/>
</Menu>
</>
)
}

View File

@ -4,6 +4,8 @@ import { useTranslate } from 'react-admin'
import { IconButton, Menu, MenuItem } from '@material-ui/core'
import MoreVertIcon from '@material-ui/icons/MoreVert'
import { addTracks, setTrack } from '../audioplayer'
import { AddToPlaylistMenu } from '../common'
import NestedMenuItem from 'material-ui-nested-menu-item'
export const SongContextMenu = ({ record }) => {
const dispatch = useDispatch()
@ -38,6 +40,8 @@ export const SongContextMenu = ({ record }) => {
e.stopPropagation()
}
const open = Boolean(anchorEl)
return (
<>
<IconButton onClick={handleClick} size={'small'}>
@ -46,7 +50,7 @@ export const SongContextMenu = ({ record }) => {
<Menu
id={'menu' + record.id}
anchorEl={anchorEl}
open={Boolean(anchorEl)}
open={open}
onClose={handleClose}
>
{Object.keys(options).map((key) => (
@ -54,6 +58,15 @@ export const SongContextMenu = ({ record }) => {
{options[key].label}
</MenuItem>
))}
<NestedMenuItem
label={translate('resources.song.actions.addToPlaylist')}
parentMenuOpen={open}
>
<AddToPlaylistMenu
selectedIds={[record.id]}
onClose={() => setAnchorEl(null)}
/>
</NestedMenuItem>
</Menu>
</>
)