import React, { useCallback, useMemo, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { MenuItemLink, useDataProvider, useNotify, useQueryWithStore, useTranslate, } from 'react-admin' import { useHistory } from 'react-router-dom' import QueueMusicIcon from '@material-ui/icons/QueueMusic' import { Typography } from '@material-ui/core' import QueueMusicOutlinedIcon from '@material-ui/icons/QueueMusicOutlined' import FavoriteIcon from '@material-ui/icons/Favorite' import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder' import { BiListUl } from 'react-icons/bi' import { useDrop } from 'react-dnd' import SubMenu from './SubMenu' import { canChangeTracks, OverflowTooltip, useRefreshOnEvents } from '../common' import { DraggableTypes } from '../consts' import { setSidebarPlaylistsOnlyFavourites } from '../actions' import config from '../config' const PlaylistMenuItemLink = ({ pls, sidebarIsOpen }) => { const dataProvider = useDataProvider() const notify = useNotify() const [, dropRef] = useDrop(() => ({ accept: canChangeTracks(pls) ? DraggableTypes.ALL : [], drop: (item) => dataProvider .addToPlaylist(pls.id, item) .then((res) => { notify('message.songsAddedToPlaylist', 'info', { smart_count: res.data?.added, }) }) .catch(() => { notify('ra.page.error', 'warning') }), })) return ( {pls.name} } sidebarIsOpen={sidebarIsOpen} dense={false} /> ) } const PlaylistsSubMenu = ({ state, setState, sidebarIsOpen, dense }) => { const history = useHistory() const dispatch = useDispatch() const translate = useTranslate() const onlyFavourites = useSelector( (state) => state.settings.sidebarPlaylistsOnlyFavourites, ) // Ignore a persisted preference when the feature is off, so disabling it later // (with the toggle now hidden) doesn't strand the user on a filtered sidebar const showFavouritesOnly = config.enableFavourites && onlyFavourites const playlistData = useSelector( (state) => state.admin.resources.playlist?.data, ) // Fingerprint of local star state; changes only when a playlist is (un)starred, // so a local toggle refetches the sidebar without the SSE echo the actor never gets const starFingerprint = useMemo(() => { const data = playlistData || {} return Object.keys(data) .filter((id) => data[id]?.starred) .sort() .join(',') }, [playlistData]) const [refreshCount, setRefreshCount] = useState(0) // Only the favourites-only view depends on star state changing elsewhere; // when showing all playlists a star event from another client changes nothing // async because useRefreshOnEvents calls .catch() on the returned value const onRefresh = useCallback(async () => { if (showFavouritesOnly) setRefreshCount((count) => count + 1) }, [showFavouritesOnly]) useRefreshOnEvents({ events: ['playlist'], onRefresh }) // A changed payload signature makes useQueryWithStore refetch const { data, loaded } = useQueryWithStore({ type: 'getList', resource: 'playlist', payload: { pagination: { page: 0, perPage: config.maxSidebarPlaylists, }, sort: { field: 'name' }, ...(showFavouritesOnly && { filter: { starred: true }, starFingerprint, refresh: refreshCount, }), }, }) const handleToggle = (menu) => { setState((state) => ({ ...state, [menu]: !state[menu] })) } const renderPlaylistMenuItemLink = (pls) => ( ) const userId = localStorage.getItem('userId') const myPlaylists = [] const sharedPlaylists = [] if (loaded && data) { const allPlaylists = Object.keys(data).map((id) => data[id]) allPlaylists.forEach((pls) => { if (userId === pls.ownerId) { myPlaylists.push(pls) } else { sharedPlaylists.push(pls) } }) } const onPlaylistConfig = useCallback( () => history.push('/playlist'), [history], ) const handleToggleFavourites = useCallback(() => { dispatch(setSidebarPlaylistsOnlyFavourites(!onlyFavourites)) }, [dispatch, onlyFavourites]) return ( <> handleToggle('menuPlaylists')} isOpen={state.menuPlaylists} sidebarIsOpen={sidebarIsOpen} name={'menu.playlists'} icon={} dense={dense} actionIcon={} onAction={onPlaylistConfig} onSecondaryAction={ config.enableFavourites ? handleToggleFavourites : undefined } secondaryActionIcon={ onlyFavourites ? ( ) : ( ) } secondaryActionTitle={translate('menu.onlyFavourites')} secondaryActionActive={onlyFavourites} > {myPlaylists.map(renderPlaylistMenuItemLink)} {sharedPlaylists?.length > 0 && ( handleToggle('menuSharedPlaylists')} isOpen={state.menuSharedPlaylists} sidebarIsOpen={sidebarIsOpen} name={'menu.sharedPlaylists'} icon={} dense={dense} > {sharedPlaylists.map(renderPlaylistMenuItemLink)} )} ) } export default PlaylistsSubMenu