import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { useDispatch } from 'react-redux'
import {
useNotify,
usePermissions,
useTranslate,
useDataProvider,
} from 'react-admin'
import { IconButton, Menu, MenuItem } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles'
import MoreVertIcon from '@material-ui/icons/MoreVert'
import { MdQuestionMark } from 'react-icons/md'
import clsx from 'clsx'
import {
playNext,
addTracks,
setTrack,
openAddToPlaylist,
openExtendedInfoDialog,
openDownloadMenu,
DOWNLOAD_MENU_SONG,
openShareMenu,
} from '../actions'
import { LoveButton } from './LoveButton'
import config from '../config'
import { playSimilar } from './playbackActions.js'
import { formatBytes } from '../utils'
import { useRedirect } from 'react-admin'
const useStyles = makeStyles({
noWrap: {
whiteSpace: 'nowrap',
},
})
const MoreButton = ({ record, onClick, info }) => {
const handleClick = record.missing
? (e) => {
info.action(record)
e.stopPropagation()
}
: onClick
return (
{record?.missing ? (
) : (
)}
)
}
export const SongContextMenu = ({
resource,
record,
showLove,
onAddToPlaylist,
className,
}) => {
const classes = useStyles()
const dispatch = useDispatch()
const translate = useTranslate()
const notify = useNotify()
const dataProvider = useDataProvider()
const [anchorEl, setAnchorEl] = useState(null)
const [playlistAnchorEl, setPlaylistAnchorEl] = useState(null)
const [playlists, setPlaylists] = useState([])
const [playlistsLoaded, setPlaylistsLoaded] = useState(false)
const { permissions } = usePermissions()
const redirect = useRedirect()
const options = {
playNow: {
enabled: true,
label: translate('resources.song.actions.playNow'),
action: (record) => dispatch(setTrack(record)),
},
playNext: {
enabled: true,
label: translate('resources.song.actions.playNext'),
action: (record) => dispatch(playNext({ [record.id]: record })),
},
addToQueue: {
enabled: true,
label: translate('resources.song.actions.addToQueue'),
action: (record) => dispatch(addTracks({ [record.id]: record })),
},
instantMix: {
enabled: config.enableExternalServices,
label: translate('resources.song.actions.instantMix'),
action: async (record) => {
notify('message.startingInstantMix', { type: 'info' })
try {
const id = record.mediaFileId || record.id
await playSimilar(dispatch, notify, id, {
seedRecord: record,
shuffle: true,
})
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error starting instant mix:', e)
notify('ra.page.error', { type: 'warning' })
}
},
},
addToPlaylist: {
enabled: true,
label: translate('resources.song.actions.addToPlaylist'),
action: (record) =>
dispatch(
openAddToPlaylist({
selectedIds: [record.mediaFileId || record.id],
onSuccess: (id) => onAddToPlaylist(id),
}),
),
},
showInPlaylist: {
enabled: true,
label:
translate('resources.song.actions.showInPlaylist') +
(playlists.length > 0 ? ' ►' : ''),
action: (record, e) => {
setPlaylistAnchorEl(e.currentTarget)
},
},
share: {
enabled: config.enableSharing,
label: translate('ra.action.share'),
action: (record) =>
dispatch(
openShareMenu(
[record.mediaFileId || record.id],
'song',
record.title,
),
),
},
download: {
enabled: config.enableDownloads,
label: `${translate('ra.action.download')} (${formatBytes(record.size)})`,
action: (record) =>
dispatch(openDownloadMenu(record, DOWNLOAD_MENU_SONG)),
},
info: {
enabled: true,
label: translate('resources.song.actions.info'),
action: async (record) => {
let fullRecord = record
if (permissions === 'admin' && !record.missing) {
try {
let id = record.mediaFileId ?? record.id
const data = await dataProvider.inspect(id)
fullRecord = { ...record, rawTags: data.data.rawTags }
} catch (error) {
notify(
translate('ra.notification.http_error') + ': ' + error.message,
{
type: 'warning',
multiLine: true,
duration: 0,
},
)
}
}
dispatch(openExtendedInfoDialog(fullRecord))
},
},
}
const handleClick = (e) => {
setAnchorEl(e.currentTarget)
if (!playlistsLoaded) {
const id = record.mediaFileId || record.id
dataProvider
.getPlaylists(id)
.then((res) => {
setPlaylists(res.data)
setPlaylistsLoaded(true)
})
.catch((error) => {
// eslint-disable-next-line no-console
console.error('Failed to fetch playlists:', error)
setPlaylists([])
setPlaylistsLoaded(true)
})
}
e.stopPropagation()
}
const handleClose = (e) => {
setAnchorEl(null)
e.stopPropagation()
}
const handleItemClick = (e) => {
e.preventDefault()
const key = e.target.getAttribute('value')
const action = options[key].action
if (key === 'showInPlaylist') {
// For showInPlaylist, we keep the main menu open and show submenu
action(record, e)
} else {
// For other actions, close the main menu
setAnchorEl(null)
action(record)
}
e.stopPropagation()
}
const handlePlaylistClose = (e) => {
setPlaylistAnchorEl(null)
if (e) {
e.stopPropagation()
}
}
const handleMainMenuClose = (e) => {
setAnchorEl(null)
setPlaylistAnchorEl(null) // Close both menus
e.stopPropagation()
}
const handlePlaylistClick = (id, e) => {
e.stopPropagation()
redirect(`/playlist/${id}/show`)
handlePlaylistClose()
}
const open = Boolean(anchorEl)
if (!record) {
return null
}
const present = !record.missing
return (
)
}
SongContextMenu.propTypes = {
resource: PropTypes.string.isRequired,
record: PropTypes.object.isRequired,
onAddToPlaylist: PropTypes.func,
showLove: PropTypes.bool,
}
SongContextMenu.defaultProps = {
onAddToPlaylist: () => {},
record: {},
resource: 'song',
showLove: true,
addLabel: true,
}