Use creatable autocomplete, to select or create a new playlist

This commit is contained in:
Deluan
2020-05-25 14:50:46 -04:00
committed by Deluan Quintão
parent 23bd5e1131
commit 6db63e4dfc
14 changed files with 287 additions and 319 deletions
+12
View File
@@ -1693,6 +1693,18 @@
"@babel/runtime": "^7.4.4" "@babel/runtime": "^7.4.4"
} }
}, },
"@material-ui/lab": {
"version": "4.0.0-alpha.54",
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.54.tgz",
"integrity": "sha512-BK/z+8xGPQoMtG6gWKyagCdYO1/2DzkBchvvXs2bbTVh3sbi/QQLIqWV6UA1KtMVydYVt22NwV3xltgPkaPKLg==",
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.9.6",
"clsx": "^1.0.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0"
}
},
"@material-ui/styles": { "@material-ui/styles": {
"version": "4.10.0", "version": "4.10.0",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz", "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz",
+1
View File
@@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@material-ui/lab": "^4.0.0-alpha.54",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
+6 -2
View File
@@ -17,9 +17,10 @@ import { albumViewReducer } from './album/albumState'
import config from './config' import config from './config'
import customRoutes from './routes' import customRoutes from './routes'
import themeReducer from './personal/themeReducer' import themeReducer from './personal/themeReducer'
import { newPlaylistDialogReducer } from './dialogs/dialogState' import { addToPlaylistDialogReducer } from './dialogs/dialogState'
import createAdminStore from './store/createAdminStore' import createAdminStore from './store/createAdminStore'
import { i18nProvider } from './i18n' import { i18nProvider } from './i18n'
import AddToPlaylistDialog from './dialogs/AddToPlaylistDialog'
const history = createHashHistory() const history = createHashHistory()
@@ -33,7 +34,7 @@ const App = () => (
queue: playQueueReducer, queue: playQueueReducer,
albumView: albumViewReducer, albumView: albumViewReducer,
theme: themeReducer, theme: themeReducer,
newPlaylistDialog: newPlaylistDialogReducer, addToPlaylistDialog: addToPlaylistDialogReducer,
}, },
})} })}
> >
@@ -77,7 +78,10 @@ const App = () => (
<Resource name="albumSong" />, <Resource name="albumSong" />,
<Resource name="translation" />, <Resource name="translation" />,
<Resource name="playlistTrack" />, <Resource name="playlistTrack" />,
// Detached components
<Player />, <Player />,
<AddToPlaylistDialog />,
]} ]}
</Admin> </Admin>
</Provider> </Provider>
+12 -16
View File
@@ -1,14 +1,13 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import Menu from '@material-ui/core/Menu' import Menu from '@material-ui/core/Menu'
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem'
import MoreVertIcon from '@material-ui/icons/MoreVert' import MoreVertIcon from '@material-ui/icons/MoreVert'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { useDataProvider, useNotify, useTranslate } from 'react-admin' import { useDataProvider, useNotify, useTranslate } from 'react-admin'
import { useDispatch } from 'react-redux'
import { addTracks, playTracks, shuffleTracks } from '../audioplayer' import { addTracks, playTracks, shuffleTracks } from '../audioplayer'
import NestedMenuItem from 'material-ui-nested-menu-item' import { openAddToPlaylist } from '../dialogs/dialogState'
import { AddToPlaylistMenu } from '../common'
const useStyles = makeStyles({ const useStyles = makeStyles({
icon: { icon: {
@@ -23,19 +22,23 @@ const AlbumContextMenu = ({ record, color }) => {
const translate = useTranslate() const translate = useTranslate()
const notify = useNotify() const notify = useNotify()
const [anchorEl, setAnchorEl] = useState(null) const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const options = { const options = {
play: { play: {
label: translate('resources.album.actions.playAll'), label: translate('resources.album.actions.playAll'),
action: (data) => playTracks(data), action: playTracks,
}, },
addToQueue: { addToQueue: {
label: translate('resources.album.actions.addToQueue'), label: translate('resources.album.actions.addToQueue'),
action: (data) => addTracks(data), action: addTracks,
}, },
shuffle: { shuffle: {
label: translate('resources.album.actions.shuffle'), label: translate('resources.album.actions.shuffle'),
action: (data) => shuffleTracks(data), action: shuffleTracks,
},
addToPlaylist: {
label: translate('resources.song.actions.addToPlaylist'),
action: () => openAddToPlaylist({ albumId: record.id }),
}, },
} }
@@ -74,6 +77,8 @@ const AlbumContextMenu = ({ record, color }) => {
e.stopPropagation() e.stopPropagation()
} }
const open = Boolean(anchorEl)
return ( return (
<div> <div>
<IconButton <IconButton
@@ -98,15 +103,6 @@ const AlbumContextMenu = ({ record, color }) => {
{options[key].label} {options[key].label}
</MenuItem> </MenuItem>
))} ))}
<NestedMenuItem
label={translate('resources.song.actions.addToPlaylist')}
parentMenuOpen={open}
>
<AddToPlaylistMenu
albumId={record.id}
onClose={() => setAnchorEl(null)}
/>
</NestedMenuItem>
</Menu> </Menu>
</div> </div>
) )
-117
View File
@@ -1,117 +0,0 @@
import React from 'react'
import { useDispatch } from 'react-redux'
import PropTypes from 'prop-types'
import {
useDataProvider,
useGetList,
useNotify,
useTranslate,
} from 'react-admin'
import { MenuItem, Divider } from '@material-ui/core'
import NewPlaylistIcon from '@material-ui/icons/Add'
import { openNewPlaylist } from '../dialogs/dialogState'
import NewPlaylistDialog from '../dialogs/NewPlaylist'
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 = React.forwardRef(
({ selectedIds, albumId, onClose, onItemAdded }, ref) => {
const notify = useNotify()
const dispatch = useDispatch()
const translate = useTranslate()
const dataProvider = useDataProvider()
const { ids, data, loaded } = useGetList(
'playlist',
{ page: 1, perPage: -1 },
{ field: 'name', order: 'ASC' },
{}
)
if (!loaded) {
return <MenuItem>Loading...</MenuItem>
}
const addToPlaylist = (playlistId) => {
const add = albumId
? addAlbumToPlaylist(dataProvider, albumId, playlistId)
: addTracksToPlaylist(dataProvider, selectedIds, playlistId)
add
.then((len) => {
notify('message.songsAddedToPlaylist', 'info', { smart_count: len })
onItemAdded(playlistId)
})
.catch(() => {
notify('ra.page.error', 'warning')
})
}
const handleItemClick = (e) => {
e.preventDefault()
const playlistId = e.target.getAttribute('value')
if (playlistId !== '') {
addToPlaylist(playlistId)
}
e.stopPropagation()
onClose(e)
}
const handleOpenDialog = (e) => {
e.preventDefault()
dispatch(openNewPlaylist(albumId, selectedIds))
e.stopPropagation()
onClose(e)
}
return (
<>
<Divider component="li" />
{ids.map((id) => (
<MenuItem value={id} key={id} onClick={handleItemClick}>
{data[id].name}
</MenuItem>
))}
<MenuItem
value="newPlaylist"
key="newPlaylist"
onClick={handleOpenDialog}
>
{<NewPlaylistIcon fontSize="small" />}&nbsp;
{translate('resources.playlist.actions.newPlaylist')}
</MenuItem>
<NewPlaylistDialog onSubmit={onItemAdded} />
</>
)
}
)
AddToPlaylistMenu.propTypes = {
selectedIds: PropTypes.arrayOf(PropTypes.any).isRequired,
albumId: PropTypes.string,
onClose: PropTypes.func,
onItemAdded: PropTypes.func,
}
AddToPlaylistMenu.defaultProps = {
selectedIds: [],
onClose: () => {},
onItemAdded: () => {},
}
export default AddToPlaylistMenu
+10 -12
View File
@@ -7,10 +7,9 @@ import { makeStyles } from '@material-ui/core/styles'
import MoreVertIcon from '@material-ui/icons/MoreVert' import MoreVertIcon from '@material-ui/icons/MoreVert'
import StarIcon from '@material-ui/icons/Star' import StarIcon from '@material-ui/icons/Star'
import StarBorderIcon from '@material-ui/icons/StarBorder' import StarBorderIcon from '@material-ui/icons/StarBorder'
import NestedMenuItem from 'material-ui-nested-menu-item'
import { addTracks, setTrack } from '../audioplayer' import { addTracks, setTrack } from '../audioplayer'
import AddToPlaylistMenu from './AddToPlaylistMenu'
import config from '../config' import config from '../config'
import { openAddToPlaylist } from '../dialogs/dialogState'
const useStyles = makeStyles({ const useStyles = makeStyles({
noWrap: { noWrap: {
@@ -41,6 +40,14 @@ const SongContextMenu = ({ record, showStar, onAddToPlaylist, visible }) => {
label: translate('resources.song.actions.addToQueue'), label: translate('resources.song.actions.addToQueue'),
action: (record) => addTracks({ [record.id]: record }), action: (record) => addTracks({ [record.id]: record }),
}, },
addToPlaylist: {
label: translate('resources.song.actions.addToPlaylist'),
action: (record) =>
openAddToPlaylist({
selectedIds: [record.mediaFileId || record.id],
onSuccess: (id) => onAddToPlaylist(id),
}),
},
} }
const handleClick = (e) => { const handleClick = (e) => {
@@ -122,16 +129,6 @@ const SongContextMenu = ({ record, showStar, onAddToPlaylist, visible }) => {
{options[key].label} {options[key].label}
</MenuItem> </MenuItem>
))} ))}
<NestedMenuItem
label={translate('resources.song.actions.addToPlaylist')}
parentMenuOpen={open}
>
<AddToPlaylistMenu
selectedIds={[record.mediaFileId || record.id]}
onClose={handleClose}
onItemAdded={onAddToPlaylist}
/>
</NestedMenuItem>
</Menu> </Menu>
</span> </span>
) )
@@ -145,6 +142,7 @@ SongContextMenu.propTypes = {
} }
SongContextMenu.defaultProps = { SongContextMenu.defaultProps = {
onAddToPlaylist: () => {},
visible: true, visible: true,
showStar: true, showStar: true,
addLabel: true, addLabel: true,
-2
View File
@@ -11,7 +11,6 @@ import SizeField from './SizeField'
import DocLink from './DocLink' import DocLink from './DocLink'
import List from './List' import List from './List'
import { SongDatagrid, SongDatagridRow } from './SongDatagrid' import { SongDatagrid, SongDatagridRow } from './SongDatagrid'
import AddToPlaylistMenu from './AddToPlaylistMenu'
import SongContextMenu from './SongContextMenu' import SongContextMenu from './SongContextMenu'
import QuickFilter from './QuickFilter' import QuickFilter from './QuickFilter'
@@ -32,7 +31,6 @@ export {
formatRange, formatRange,
ArtistLinkField, ArtistLinkField,
artistLink, artistLink,
AddToPlaylistMenu,
SongContextMenu, SongContextMenu,
QuickFilter, QuickFilter,
} }
+117
View File
@@ -0,0 +1,117 @@
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import {
useCreate,
useDataProvider,
useTranslate,
useNotify,
} from 'react-admin'
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
} from '@material-ui/core'
import { closeAddToPlaylist } from './dialogState'
import SelectPlaylistInput from './SelectPlaylistInput'
const AddToPlaylistDialog = () => {
const { open, albumId, selectedIds, onSuccess } = useSelector(
(state) => state.addToPlaylistDialog
)
const dispatch = useDispatch()
const translate = useTranslate()
const notify = useNotify()
const [value, setValue] = useState({})
const dataProvider = useDataProvider()
const [create] = useCreate(
'playlist',
{ name: value.name },
{
onSuccess: ({ data }) => {
setValue(data)
addToPlaylist(data.id)
},
onFailure: (error) => notify(`Error: ${error.message}`, 'warning'),
}
)
const addTracksToPlaylist = (selectedIds, playlistId) =>
dataProvider
.create('playlistTrack', {
data: { ids: selectedIds },
filter: { playlist_id: playlistId },
})
.then(() => selectedIds.length)
const addAlbumToPlaylist = (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(ids, playlistId))
const addToPlaylist = (playlistId) => {
const add = albumId
? addAlbumToPlaylist(albumId, playlistId)
: addTracksToPlaylist(selectedIds, playlistId)
add
.then((len) => {
notify('message.songsAddedToPlaylist', 'info', { smart_count: len })
onSuccess && onSuccess(value, len)
})
.catch(() => {
notify('ra.page.error', 'warning')
})
}
const handleSubmit = (e) => {
if (value.id) {
addToPlaylist(value.id)
} else {
create()
}
dispatch(closeAddToPlaylist())
e.stopPropagation()
}
const handleClickClose = (e) => {
dispatch(closeAddToPlaylist())
e.stopPropagation()
}
const handleChange = (pls) => {
setValue(pls)
}
return (
<Dialog
disableBackdropClick
open={open}
onClose={handleClickClose}
aria-labelledby="form-dialog-new-playlist"
>
<DialogTitle id="form-dialog-new-playlist">
{translate('resources.playlist.actions.selectPlaylist')}
</DialogTitle>
<DialogContent>
<SelectPlaylistInput onChange={handleChange} />
</DialogContent>
<DialogActions>
<Button onClick={handleClickClose} color="primary">
{translate('ra.action.cancel')}
</Button>
<Button onClick={handleSubmit} color="primary" disabled={!value.name}>
{translate('ra.action.add')}
</Button>
</DialogActions>
</Dialog>
)
}
export default AddToPlaylistDialog
-120
View File
@@ -1,120 +0,0 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import {
useCreate,
useDataProvider,
useTranslate,
useNotify,
} from 'react-admin'
import {
Button,
TextField,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
} from '@material-ui/core'
import PropTypes from 'prop-types'
import { closeNewPlaylist } from './dialogState'
import {
addAlbumToPlaylist,
addTracksToPlaylist,
} from '../common/AddToPlaylistMenu'
const NewPlaylistDialog = ({ onCancel, onSubmit }) => {
const { open, albumId, selectedIds } = useSelector(
(state) => state.newPlaylistDialog
)
const dispatch = useDispatch()
const translate = useTranslate()
const notify = useNotify()
const [value, setValue] = React.useState('')
const dataProvider = useDataProvider()
const [create] = useCreate(
'playlist',
{ name: value },
{
onSuccess: ({ data }) => {
addToPlaylist(data.id)
},
onFailure: (error) => notify(`Error: ${error.message}`, 'warning'),
}
)
const addToPlaylist = (playlistId) => {
const add = albumId
? addAlbumToPlaylist(dataProvider, albumId, playlistId)
: addTracksToPlaylist(dataProvider, selectedIds, playlistId)
add
.then((len) => {
notify('message.songsAddedToPlaylist', 'info', { smart_count: len })
onSubmit(value)
})
.catch(() => {
notify('ra.page.error', 'warning')
})
}
const handleSubmit = (e) => {
create()
dispatch(closeNewPlaylist())
e.stopPropagation()
}
const handleChange = (e) => {
setValue(e.target.value)
}
const handleClickClose = (e) => {
onCancel(e)
dispatch(closeNewPlaylist())
e.stopPropagation()
}
return (
<div>
<Dialog
disableBackdropClick
open={open}
onClose={handleClickClose}
aria-labelledby="form-dialog-new-playlist"
>
<DialogTitle id="form-dialog-new-playlist">
{translate('resources.playlist.actions.newPlaylist')}
</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
id="name"
label={translate('resources.playlist.fields.name')}
type="text"
fullWidth
onChange={handleChange}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClickClose} color="primary">
{translate('ra.action.cancel')}
</Button>
<Button onClick={handleSubmit} color="primary">
{translate('ra.action.create')}
</Button>
</DialogActions>
</Dialog>
</div>
)
}
NewPlaylistDialog.propTypes = {
onCancel: PropTypes.func,
onSubmit: PropTypes.func,
}
NewPlaylistDialog.defaultProps = {
onCancel: () => {},
onSubmit: () => {},
}
export default NewPlaylistDialog
+96
View File
@@ -0,0 +1,96 @@
/* eslint-disable no-use-before-define */
import React from 'react'
import TextField from '@material-ui/core/TextField'
import Autocomplete, {
createFilterOptions,
} from '@material-ui/lab/Autocomplete'
import { useGetList, useTranslate } from 'react-admin'
import PropTypes from 'prop-types'
const filter = createFilterOptions()
const SelectPlaylistInput = ({ onChange }) => {
const translate = useTranslate()
const { ids, data, loaded } = useGetList(
'playlist',
{ page: 1, perPage: -1 },
{ field: 'name', order: 'ASC' },
{}
)
if (!loaded) {
return null
}
const options = ids.map((id) => data[id])
const handleOnChange = (event, newValue) => {
if (newValue == null) {
onChange({})
} else if (typeof newValue === 'string') {
onChange({
name: newValue,
})
} else if (newValue && newValue.inputValue) {
// Create a new value from the user input
onChange({
name: newValue.inputValue,
})
} else {
onChange(newValue)
}
}
return (
<Autocomplete
onChange={handleOnChange}
filterOptions={(options, params) => {
const filtered = filter(options, params)
// Suggest the creation of a new value
if (params.inputValue !== '') {
filtered.push({
inputValue: params.inputValue,
name: `Add "${params.inputValue}"`,
})
}
return filtered
}}
clearOnBlur
handleHomeEndKeys
openOnFocus
selectOnFocus
id="select-playlist-input"
options={options}
getOptionLabel={(option) => {
// Value selected with enter, right from the input
if (typeof option === 'string') {
return option
}
// Add "xxx" option created dynamically
if (option.inputValue) {
return option.inputValue
}
// Regular option
return option.name
}}
renderOption={(option) => option.name}
style={{ width: 300 }}
freeSolo
renderInput={(params) => (
<TextField
autoFocus
{...params}
label={translate('resources.playlist.fields.name')}
/>
)}
/>
)
}
SelectPlaylistInput.propTypes = {
onChange: PropTypes.func.isRequired,
}
export default SelectPlaylistInput
+13 -11
View File
@@ -1,17 +1,18 @@
const NEW_PLAYLIST_OPEN = 'NEW_PLAYLIST_OPEN' const ADD_TO_PLAYLIST_OPEN = 'ADD_TO_PLAYLIST_OPEN'
const NEW_PLAYLIST_CLOSE = 'NEW_PLAYLIST_CLOSE' const ADD_TO_PLAYLIST_CLOSE = 'ADD_TO_PLAYLIST_CLOSE'
const openNewPlaylist = (albumId, selectedIds) => ({ const openAddToPlaylist = ({ albumId, selectedIds, onSuccess }) => ({
type: NEW_PLAYLIST_OPEN, type: ADD_TO_PLAYLIST_OPEN,
albumId, albumId,
selectedIds, selectedIds,
onSuccess,
}) })
const closeNewPlaylist = () => ({ const closeAddToPlaylist = () => ({
type: NEW_PLAYLIST_CLOSE, type: ADD_TO_PLAYLIST_CLOSE,
}) })
const newPlaylistDialogReducer = ( const addToPlaylistDialogReducer = (
previousState = { previousState = {
open: false, open: false,
}, },
@@ -19,18 +20,19 @@ const newPlaylistDialogReducer = (
) => { ) => {
const { type } = payload const { type } = payload
switch (type) { switch (type) {
case NEW_PLAYLIST_OPEN: case ADD_TO_PLAYLIST_OPEN:
return { return {
...previousState, ...previousState,
open: true, open: true,
albumId: payload.albumId, albumId: payload.albumId,
selectedIds: payload.selectedIds, selectedIds: payload.selectedIds,
onSuccess: payload.onSuccess,
} }
case NEW_PLAYLIST_CLOSE: case ADD_TO_PLAYLIST_CLOSE:
return { ...previousState, open: false } return { ...previousState, open: false, onSuccess: undefined }
default: default:
return previousState return previousState
} }
} }
export { openNewPlaylist, closeNewPlaylist, newPlaylistDialogReducer } export { openAddToPlaylist, closeAddToPlaylist, addToPlaylistDialogReducer }
+2 -2
View File
@@ -69,8 +69,8 @@
"createdAt": "Created at" "createdAt": "Created at"
}, },
"actions": { "actions": {
"selectPlaylist": "Add songs to playlist:", "selectPlaylist": "Select a playlist:",
"newPlaylist": "New playlist" "addToPlaylist": "New playlist"
} }
}, },
"user": { "user": {
+2 -2
View File
@@ -81,8 +81,8 @@ const PlaylistSongs = (props) => {
return <div /> return <div />
} }
const onAddToPlaylist = (playlistId) => { const onAddToPlaylist = (pls) => {
if (playlistId === props.id) { if (pls.id === props.id) {
refresh() refresh()
} }
} }
+16 -35
View File
@@ -1,49 +1,30 @@
import React from 'react' import React from 'react'
import { useDispatch } from 'react-redux'
import { Button, useTranslate, useUnselectAll } from 'react-admin' import { Button, useTranslate, useUnselectAll } from 'react-admin'
import { Menu } from '@material-ui/core'
import PlaylistAddIcon from '@material-ui/icons/PlaylistAdd' import PlaylistAddIcon from '@material-ui/icons/PlaylistAdd'
import { AddToPlaylistMenu } from '../common' import { openAddToPlaylist } from '../dialogs/dialogState'
const AddToPlaylistButton = ({ resource, selectedIds, onAddToPlaylist }) => { const AddToPlaylistButton = ({ resource, selectedIds, onAddToPlaylist }) => {
const [anchorEl, setAnchorEl] = React.useState(null)
const translate = useTranslate() const translate = useTranslate()
const dispatch = useDispatch()
const unselectAll = useUnselectAll() const unselectAll = useUnselectAll()
const handleClick = (event) => { const handleClick = () => {
setAnchorEl(event.currentTarget) dispatch(
} openAddToPlaylist({ selectedIds, onSuccess: () => unselectAll(resource) })
)
const handleClose = () => {
setAnchorEl(null)
unselectAll(resource)
} }
return ( return (
<> <Button
<Button aria-controls="simple-menu"
aria-controls="simple-menu" aria-haspopup="true"
aria-haspopup="true" onClick={handleClick}
onClick={handleClick} color="secondary"
color="secondary" label={translate('resources.song.actions.addToPlaylist')}
label={translate('resources.song.actions.addToPlaylist')} >
> <PlaylistAddIcon />
<PlaylistAddIcon /> </Button>
</Button>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<AddToPlaylistMenu
selectedIds={selectedIds}
menuOpen={Boolean(anchorEl)}
onClose={handleClose}
onItemAdded={onAddToPlaylist}
/>
</Menu>
</>
) )
} }