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
+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 NEW_PLAYLIST_CLOSE = 'NEW_PLAYLIST_CLOSE'
const ADD_TO_PLAYLIST_OPEN = 'ADD_TO_PLAYLIST_OPEN'
const ADD_TO_PLAYLIST_CLOSE = 'ADD_TO_PLAYLIST_CLOSE'
const openNewPlaylist = (albumId, selectedIds) => ({
type: NEW_PLAYLIST_OPEN,
const openAddToPlaylist = ({ albumId, selectedIds, onSuccess }) => ({
type: ADD_TO_PLAYLIST_OPEN,
albumId,
selectedIds,
onSuccess,
})
const closeNewPlaylist = () => ({
type: NEW_PLAYLIST_CLOSE,
const closeAddToPlaylist = () => ({
type: ADD_TO_PLAYLIST_CLOSE,
})
const newPlaylistDialogReducer = (
const addToPlaylistDialogReducer = (
previousState = {
open: false,
},
@@ -19,18 +20,19 @@ const newPlaylistDialogReducer = (
) => {
const { type } = payload
switch (type) {
case NEW_PLAYLIST_OPEN:
case ADD_TO_PLAYLIST_OPEN:
return {
...previousState,
open: true,
albumId: payload.albumId,
selectedIds: payload.selectedIds,
onSuccess: payload.onSuccess,
}
case NEW_PLAYLIST_CLOSE:
return { ...previousState, open: false }
case ADD_TO_PLAYLIST_CLOSE:
return { ...previousState, open: false, onSuccess: undefined }
default:
return previousState
}
}
export { openNewPlaylist, closeNewPlaylist, newPlaylistDialogReducer }
export { openAddToPlaylist, closeAddToPlaylist, addToPlaylistDialogReducer }