import React, { PropTypes } from 'react' import { hashHistory } from 'react-router' import Link from './Link' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import TrackList from './TrackList' import Icon from './Icon' import * as helpers from '../helpers' import * as coreActions from '../services/core/actions' import * as uiActions from '../services/ui/actions' import * as pusherActions from '../services/pusher/actions' import * as mopidyActions from '../services/mopidy/actions' import * as lastfmActions from '../services/lastfm/actions' import * as spotifyActions from '../services/spotify/actions' class ContextMenu extends React.Component{ constructor(props){ super(props) this.state = { submenu_expanded: false } this.handleScroll = this.handleScroll.bind(this); this.handleMouseDown = this.handleMouseDown.bind(this); this.handleTouchStart = this.handleTouchStart.bind(this); } componentDidMount(){ window.addEventListener("scroll", this.handleScroll, false); window.addEventListener("mousedown", this.handleMouseDown, false); window.addEventListener("touchstart", this.handleTouchStart, false); } componentWillUnmount(){ window.removeEventListener("scroll", this.handleScroll, false); window.removeEventListener("mousedown", this.handleMouseDown, false); window.removeEventListener("touchstart", this.handleTouchStart, false); } componentWillReceiveProps(nextProps){ // if we've been given a menu object (ie activated) when we didn't have one prior if (nextProps.menu && !this.props.menu){ this.setState({ submenu_expanded: false }); var context = this.getContext(nextProps); // if we're able to be in the library, run a check if (nextProps.spotify_authorized && context.source == 'spotify'){ switch (nextProps.menu.context){ case 'artist': case 'album': case 'playlist': this.props.spotifyActions.following(nextProps.menu.items[0].uri) break } } // if we're able to be in the LastFM library, run a check if (nextProps.lastfm_authorized && context.is_track && context.items_count == 1){ if (nextProps.menu.items[0].uri && this.props.tracks[nextProps.menu.items[0].uri] !== undefined && this.props.tracks[nextProps.menu.items[0].uri].userloved === undefined){ this.props.lastfmActions.getTrack(nextProps.menu.items[0].uri); } } } } handleScroll(e){ if (this.props.menu){ this.props.uiActions.hideContextMenu(); } } handleMouseDown(e){ // if we click (touch or mouse) outside of the context menu or context menu trigger, kill it if ($(e.target).closest('.context-menu').length <= 0 && $(e.target).closest('.context-menu-trigger').length <= 0){ this.props.uiActions.hideContextMenu(); } } handleTouchStart(e){ // if we click (touch or mouse) outside of the context menu or context menu trigger, kill it if ($(e.target).closest('.context-menu').length <= 0 && $(e.target).closest('.context-menu-trigger').length <= 0){ this.props.uiActions.hideContextMenu(); } } getContext(props = this.props){ var context = { name: null, nice_name: 'Unknown', is_track: false } if (props.menu && props.menu.context){ context.name = props.menu.context; context.nice_name = props.menu.context; // handle ugly labels switch (props.menu.context){ case 'playlist': case 'editable-playlist': context.nice_name = 'playlist'; break; case 'track': case 'queue-track': case 'playlist-track': case 'editable-playlist-track': context.nice_name = 'track'; context.is_track = true; break; } // Consider the object(s) themselves // We can only really accommodate the first item. The only instances where // there is multiple is tracklists, when they're all of the same source (except search?) if (props.menu.items && props.menu.items.length > 0){ var item = props.menu.items[0] context.item = item; context.items_count = props.menu.items.length; context.source = helpers.uriSource(item.uri); context.type = helpers.uriType(item.uri); context.in_library = this.inLibrary(item); context.is_loved = this.isLoved(item); } } return context } inLibrary(item = null){ if (!item){ return false; } switch (helpers.uriType(item.uri)){ case 'artist': return (this.props.spotify_library_artists && this.props.spotify_library_artists.indexOf(item.uri) > -1); break; case 'album': return (this.props.spotify_library_albums && this.props.spotify_library_albums.indexOf(item.uri) > -1); break; case 'playlist': return (this.props.spotify_library_playlists && this.props.spotify_library_playlists.indexOf(item.uri) > -1); break; } return false; } /** * TODO: Currently the select track keys are the only details available. We need * the actual track object reference (including name and artists) to getTrack from LastFM **/ isLoved(item = null){ if (!item){ return false } if (this.props.tracks[item.uri] === undefined){ return false; } var track = this.props.tracks[item.uri]; return (track.userloved !== undefined && track.userloved == "1"); } canBeInLibrary(){ if (!this.props.spotify_authorized){ return false } return (helpers.uriSource(this.props.menu.items[0].uri) == 'spotify') } toggleInLibrary(e, in_library){ this.props.uiActions.hideContextMenu() if (in_library){ this.props.spotifyActions.following(this.props.menu.items[0].uri, 'DELETE') } else { this.props.spotifyActions.following(this.props.menu.items[0].uri, 'PUT') } } playQueueItem(e){ this.props.uiActions.hideContextMenu() var tracks = this.props.menu.items; this.props.mopidyActions.changeTrack(tracks[0].tlid); } removeFromQueue(e){ this.props.uiActions.hideContextMenu() var tracks = this.props.menu.items; var tracks_tlids = []; for(var i = 0; i < tracks.length; i++){ tracks_tlids.push(tracks[i].tlid ); } this.props.mopidyActions.removeTracks(tracks_tlids); } playURIs(e){ this.props.uiActions.hideContextMenu(); this.props.mopidyActions.playURIs(this.props.menu.uris, this.props.menu.tracklist_uri); } playPlaylist(e){ this.props.uiActions.hideContextMenu(); this.props.mopidyActions.playPlaylist(this.props.menu.uris[0]); } shufflePlayPlaylist(e){ this.props.uiActions.hideContextMenu(); this.props.mopidyActions.playPlaylist(this.props.menu.uris[0], true); } playArtistTopTracks(e){ this.props.uiActions.hideContextMenu(); this.props.spotifyActions.playArtistTopTracks(this.props.menu.uris[0]); } addToQueue(e, next = false){ this.props.uiActions.hideContextMenu(); this.props.mopidyActions.enqueueURIs(this.props.menu.uris, this.props.menu.tracklist_uri, next); } addTracksToPlaylist(e, playlist_uri){ this.props.uiActions.hideContextMenu(); this.props.coreActions.addTracksToPlaylist(playlist_uri, this.props.menu.uris); } toggleLoved(e, is_loved){ this.props.uiActions.hideContextMenu(); if (is_loved){ this.props.lastfmActions.unloveTrack(this.props.menu.items[0].uri); } else { this.props.lastfmActions.loveTrack(this.props.menu.items[0].uri); } } unloveTrack(e){ this.props.uiActions.hideContextMenu(); this.props.lastfmActions.unloveTrack(this.props.menu.items[0]); } removeFromPlaylist(e){ this.props.uiActions.hideContextMenu(); this.props.coreActions.removeTracksFromPlaylist(this.props.menu.tracklist_uri, this.props.menu.indexes); } deletePlaylist(e){ this.props.uiActions.hideContextMenu(); this.props.coreActions.deletePlaylist(this.props.menu.uris[0]); } startRadio(e){ this.props.uiActions.hideContextMenu(); this.props.pusherActions.startRadio(this.props.menu.uris); } goToRecommendations(e){ this.props.uiActions.hideContextMenu(); var uris_string = helpers.arrayOf('uri',this.props.menu.items).join(','); hashHistory.push(global.baseURL +'discover/recommendations/'+ uris_string ); } goToArtist(e){ if (!this.props.menu.items || this.props.menu.items.length <= 0 || !this.props.menu.items[0].artists_uris || this.props.menu.items[0].artists_uris.length <= 0){ return null; } else { this.props.uiActions.hideContextMenu(); // note: we can only go to one artist (even if this item has multiple artists, just go to the first one) hashHistory.push(global.baseURL +'artist/'+ this.props.menu.items[0].artists_uris[0]); } } goToAlbum(e){ if (!this.props.menu.items || this.props.menu.items.length <= 0 || !this.props.menu.items[0].album_uri){ return null; } else { this.props.uiActions.hideContextMenu(); hashHistory.push(global.baseURL +'album/'+ this.props.menu.items[0].album_uri); } } goToUser(e){ if (!this.props.menu.items || this.props.menu.items.length <= 0 || !this.props.menu.items[0].user_uri){ return null; } else { this.props.uiActions.hideContextMenu(); hashHistory.push(global.baseURL +'user/'+ this.props.menu.items[0].user_uri); } } goToTrack(e){ if (!this.props.menu.items || this.props.menu.items.length <= 0){ return null; } else { this.props.uiActions.hideContextMenu(); hashHistory.push(global.baseURL +'track/'+ encodeURIComponent(this.props.menu.items[0].uri)); } } copyURIs(e){ var temp = $(""); $("body").append(temp); temp.val(this.props.menu.uris.join(',')).select(); document.execCommand("copy"); temp.remove(); this.props.uiActions.createNotification({content: "Copied "+this.props.menu.uris.length+" URIs"}); this.props.uiActions.hideContextMenu() } closeAndDeselectTracks(e){ this.props.uiActions.hideContextMenu(); // TODO } renderPlaylistSubmenu(){ var playlists = [] for (var uri in this.props.playlists){ if (this.props.playlists[uri].can_edit) playlists.push(this.props.playlists[uri]) } playlists = helpers.sortItems(playlists, 'name') var loader = null if (this.props.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR && this.props.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR.status == 'running'){ loader = (
) } var list = No writable playlists if (playlists.length > 0){ list = playlists.map(playlist => { return ( this.addTracksToPlaylist(e,playlist.uri) }> { playlist.name } ) }) } return (
this.setState({submenu_expanded: false})}> Back {list} {loader}
) } renderTitle(){ var context = this.getContext() if (context.name == 'custom'){ return (
{this.props.menu.title}
) } switch (context.type){ case 'artist': case 'album': case 'playlist': var style = null; return ( {style ?
: null}
{context.source}   {context.nice_name}
{context.item.name}
) break default: return (
{context.source}   {context.nice_name}s
{context.items_count} items
) break } } setPlaylistSubmenu(expanded = !this.state.submenu_expanded){ this.setState({submenu_expanded: expanded}) if (expanded){ if (!this.props.spotify_library_playlists_loaded_all){ this.props.spotifyActions.getLibraryPlaylists() } if (!this.props.mopidy_library_playlists_loaded_all){ this.props.mopidyActions.getLibraryPlaylists() } } } renderItems(){ var context = this.getContext() var play_uris = ( this.playURIs(e)}> Play ) var play_playlist = ( this.playPlaylist(e)}> Play ) var shuffle_play_playlist = ( this.shufflePlayPlaylist(e)}> Shuffle play ) var play_queue_item = ( this.playQueueItem(e)}> Play ) var play_uris_next = ( this.addToQueue(e, true)}> Play next ) var play_artist_top_tracks = ( this.playArtistTopTracks(e)}> Play top tracks ) var add_to_queue = ( this.addToQueue(e)}> Add to queue ) var add_to_playlist = ( this.setPlaylistSubmenu()}> Add to playlist {this.renderPlaylistSubmenu()} ) var toggle_in_library = ( this.toggleInLibrary(e, context.in_library)}> {context.in_library ? 'Remove from library' : 'Add to library'} ) if (!this.props.lastfm_authorized){ var toggle_loved = null; } else if (helpers.isLoading(this.props.load_queue,['lastfm_track.getInfo'])){ var toggle_loved = ( Love track ) } else { var toggle_loved = ( this.toggleLoved(e, context.is_loved)}> {context.is_loved ? 'Unlove' : 'Love'} track ) } var go_to_artist = ( this.goToArtist(e)}> Go to artist ) var go_to_album = ( this.goToAlbum(e)}> Go to album ) var go_to_user = ( this.goToUser(e)}> Go to user ) var go_to_track = ( this.goToTrack(e)}> Track info ) var go_to_recommendations = ( this.goToRecommendations(e)}> Discover similar ) var start_radio = ( this.startRadio(e)}> Start radio ) var remove_from_queue = ( this.removeFromQueue(e)}> Remove ) var remove_from_playlist = ( this.removeFromPlaylist(e)}> Remove ) var delete_playlist = ( this.deletePlaylist(e)}> Delete ) var copy_uris = ( this.copyURIs(e)}> Copy URI{context.items_count > 1 ? 's' : ''} ) switch (context.name){ case 'album': return (
{play_uris} {play_uris_next} {add_to_queue} {this.canBeInLibrary() ?
: null} {this.canBeInLibrary() ? toggle_in_library : null}
{go_to_artist} {copy_uris}
) break case 'artist': return (
{context.source == 'spotify' ? play_artist_top_tracks : null} {context.source == 'spotify' ? start_radio : null} {this.canBeInLibrary() ?
: null} {this.canBeInLibrary() ? toggle_in_library : null}
{context.source == 'spotify' ? go_to_recommendations : null} {copy_uris}
) break case 'playlist': return (
{play_playlist} {shuffle_play_playlist} {this.canBeInLibrary() ?
: null} {this.canBeInLibrary() ? toggle_in_library : null}
{context.source == 'spotify' ? go_to_user : null} {copy_uris}
) break case 'editable-playlist': return (
{play_playlist} {shuffle_play_playlist} {this.canBeInLibrary() ?
: null} {this.canBeInLibrary() ? toggle_in_library : null}
{context.source == 'spotify' ? go_to_user : null} {copy_uris}
{delete_playlist}
) break case 'queue-track': return (
{context.items_count == 1 ? play_queue_item : null}
{add_to_playlist} {toggle_loved}
{context.source == 'spotify' && context.items_count <= 5 ? go_to_recommendations : null} {context.items_count == 1 ? go_to_track : null} {copy_uris}
{remove_from_queue}
) break case 'editable-playlist-track': return (
{play_uris} {play_uris_next} {add_to_queue} {context.source == 'spotify' && context.items_count == 1 ? start_radio : null}
{add_to_playlist} {toggle_loved}
{context.source == 'spotify' && context.items_count <= 5 ? go_to_recommendations : null} {context.items_count == 1 ? go_to_track : null} {copy_uris}
{remove_from_playlist}
) break default: return (
{play_uris} {play_uris_next} {add_to_queue} {context.source == 'spotify' && context.items_count == 1 ? start_radio : null}
{add_to_playlist} {toggle_loved}
{context.source == 'spotify' && context.items_count <= 5 ? go_to_recommendations : null} {context.items_count == 1 ? go_to_album : null} {context.items_count == 1 ? go_to_track : null}
{copy_uris}
) break } } render(){ if (!this.props.menu) return null; var style = { left: this.props.menu.position_x, top: this.props.menu.position_y, } var height = 200 // TODO: use jquery to detect height var className = "context-menu "+this.props.menu.context if (this.state.submenu_expanded){ className += ' context-menu--submenu-expanded' } if (this.props.menu.position_x > (window.innerWidth - 174)){ style.left = 'auto'; style.right = 10; } if (this.props.menu.position_y > (window.innerHeight - height)){ style.top = 'auto'; style.bottom = 10; } return (
{this.renderTitle()} {this.props.menu.context == 'custom' ? this.props.menu.options : this.renderItems()}
this.props.uiActions.hideContextMenu()}>
); } } const mapStateToProps = (state, ownProps) => { return { menu: state.ui.context_menu, load_queue: state.ui.load_queue, processes: state.ui.processes, current_track: state.core.current_track, current_tracklist: state.core.current_tracklist, spotify_library_playlists: state.spotify.library_playlists, spotify_library_playlists_loaded_all: state.spotify.library_playlists_loaded_all, mopidy_library_playlists: state.mopidy.library_playlists, mopidy_library_playlists_loaded_all: state.mopidy.library_playlists_loaded_all, spotify_library_artists: state.spotify.library_artists, mopidy_library_artists: state.mopidy.library_artists, spotify_library_albums: state.spotify.library_albums, mopidy_library_albums: state.mopidy.library_albums, playlists: state.core.playlists, tracks: state.core.tracks, spotify_authorized: state.spotify.authorization, lastfm_authorized: state.lastfm.authorization } } const mapDispatchToProps = (dispatch) => { return { coreActions: bindActionCreators(coreActions, dispatch), uiActions: bindActionCreators(uiActions, dispatch), pusherActions: bindActionCreators(pusherActions, dispatch), spotifyActions: bindActionCreators(spotifyActions, dispatch), lastfmActions: bindActionCreators(lastfmActions, dispatch), mopidyActions: bindActionCreators(mopidyActions, dispatch) } } export default connect(mapStateToProps, mapDispatchToProps)(ContextMenu)