From 308163c2e0b6759c5c89a8ac731db50ddb84ff5f Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 17 May 2020 18:55:17 -0400 Subject: [PATCH] Add "AddToPlaylist" to AlbumContextMenu --- ui/package-lock.json | 5 ++ ui/package.json | 1 + ui/src/album/AlbumContextMenu.js | 11 ++++ ui/src/album/AlbumSongs.js | 4 +- ui/src/common/AddToPlaylistMenu.js | 88 +++++++++++++++++++++++++++ ui/src/common/SelectPlaylistDialog.js | 77 ----------------------- ui/src/common/index.js | 4 +- ui/src/i18n/en.json | 3 +- ui/src/song/AddToPlaylistButton.js | 59 +++++++----------- ui/src/song/SongContextMenu.js | 15 ++++- 10 files changed, 148 insertions(+), 119 deletions(-) create mode 100644 ui/src/common/AddToPlaylistMenu.js delete mode 100644 ui/src/common/SelectPlaylistDialog.js diff --git a/ui/package-lock.json b/ui/package-lock.json index d465feeeb..ba02b2c41 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -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", diff --git a/ui/package.json b/ui/package.json index 2f4b08b84..382f45dae 100644 --- a/ui/package.json +++ b/ui/package.json @@ -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", diff --git a/ui/src/album/AlbumContextMenu.js b/ui/src/album/AlbumContextMenu.js index 93a6262ab..cc5e36d02 100644 --- a/ui/src/album/AlbumContextMenu.js +++ b/ui/src/album/AlbumContextMenu.js @@ -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} ))} + + setAnchorEl(null)} + /> + ) diff --git a/ui/src/album/AlbumSongs.js b/ui/src/album/AlbumSongs.js index cd5ebc9d6..cf4ce535d 100644 --- a/ui/src/album/AlbumSongs.js +++ b/ui/src/album/AlbumSongs.js @@ -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) => { + 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 Loading... + } + + 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) => ( + + {data[id].name} + + ))} + + ) +} + +AddToPlaylistMenu.propTypes = { + selectedIds: PropTypes.arrayOf(PropTypes.any).isRequired, + albumId: PropTypes.string, +} + +AddToPlaylistMenu.defaultProps = { + selectedIds: [], +} + +export default AddToPlaylistMenu diff --git a/ui/src/common/SelectPlaylistDialog.js b/ui/src/common/SelectPlaylistDialog.js deleted file mode 100644 index 225d33534..000000000 --- a/ui/src/common/SelectPlaylistDialog.js +++ /dev/null @@ -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
- } - - const handleClose = () => { - onClose(selectedValue) - } - - const handleListItemClick = (value) => { - onClose(value) - } - - return ( - - - {translate('resources.playlist.actions.selectPlaylist')} - - - {ids.map((id) => ( - handleListItemClick(id)} key={id}> - - - - - - - - ))} - - - ) -} - -SelectPlaylistDialog.propTypes = { - onClose: PropTypes.func.isRequired, - open: PropTypes.bool.isRequired, - selectedValue: PropTypes.string.isRequired, -} - -export default SelectPlaylistDialog diff --git a/ui/src/common/index.js b/ui/src/common/index.js index 4b08c6cb0..14f3399c2 100644 --- a/ui/src/common/index.js +++ b/ui/src/common/index.js @@ -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, } diff --git a/ui/src/i18n/en.json b/ui/src/i18n/en.json index c10cd895c..4802d8718 100644 --- a/ui/src/i18n/en.json +++ b/ui/src/i18n/en.json @@ -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", diff --git a/ui/src/song/AddToPlaylistButton.js b/ui/src/song/AddToPlaylistButton.js index a3e6b349e..2d7147cf4 100644 --- a/ui/src/song/AddToPlaylistButton.js +++ b/ui/src/song/AddToPlaylistButton.js @@ -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 ( <> - + > + + ) } diff --git a/ui/src/song/SongContextMenu.js b/ui/src/song/SongContextMenu.js index f0f8d0bb5..92822733e 100644 --- a/ui/src/song/SongContextMenu.js +++ b/ui/src/song/SongContextMenu.js @@ -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 ( <> @@ -46,7 +50,7 @@ export const SongContextMenu = ({ record }) => { {Object.keys(options).map((key) => ( @@ -54,6 +58,15 @@ export const SongContextMenu = ({ record }) => { {options[key].label} ))} + + setAnchorEl(null)} + /> + )