diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index 62899e51..f60b6e9c 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -1,815 +1,851 @@ - -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 Thumbnail from './Thumbnail' - -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: null - } - 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: null }); - - 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]); - } - } - - 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 - } - - renderTitle(){ - - var context = this.getContext() - - if (context.items_count > 1){ - return ( -
-
- {context.items_count} {context.nice_name}{context.items_count > 1 ? 's' : null} selected {this.props.uiActions.setSelectedTracks([]); this.props.uiActions.hideContextMenu()}}> -
-
- ) - } - - if (context.name == 'custom'){ - if (!this.props.menu.title){ - return null; - } - - return ( -
-
- {this.props.menu.title} -
-
- ) - } - - // Do we need titles or does it just confuse things and rip off Spotify too hard? - return null; -/* - switch (context.type){ - - case 'artist': - case 'album': - case 'playlist': - var style = null; - - return ( -
- -
{context.item.name}
-
- {context.source} -
-
- ); - - default: - return ( -
-
- {context.items_count} {context.nice_name}{context.items_count > 1 ? 's' : null} -
-
- {context.source} -
-
- ); - - }*/ - } - - setSubmenu(name){ - - if (this.state.submenu !== name && name == 'add-to-playlist'){ - if (!this.props.spotify_library_playlists_loaded_all){ - this.props.spotifyActions.getLibraryPlaylists() - } - if (!this.props.mopidy_library_playlists_loaded_all){ - this.props.mopidyActions.getLibraryPlaylists() - } - } - - this.setState({submenu: name}); - } - - renderSubmenu(){ - var list = null; - var loader = null; - - switch (this.state.submenu){ - - case 'add-to-playlist': - - 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') - - if (this.props.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR && this.props.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR.status == 'running'){ - loader = ( -
-
-
-
-
- ) - } - - list = No writable playlists - if (playlists.length > 0){ - list = playlists.map(playlist => { - return ( - - this.addTracksToPlaylist(e,playlist.uri) }> - { playlist.name } - - - ) - }) - } - - } - - return ( -
-
- this.setState({submenu: null})}> - - Back - - -
- {list} - {loader} -
- ); - } - - 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.setSubmenu('add-to-playlist')}> - Add to playlist - - -
- ) - - 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_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_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){ - className += ' context-menu--submenu-expanded'; - } - - if (this.props.menu.closing){ - className += ' context-menu--closing'; - } - - 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.renderSubmenu()} -
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) \ No newline at end of file + +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 Thumbnail from './Thumbnail' + +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: null + } + 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: null }); + + 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': + case 'editable-playlist': + case 'track': + case 'playlist-track': + case 'editable-playlist-track': + case 'queue-track': + 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; + case 'track': + return (this.props.spotify_library_tracks && this.props.spotify_library_tracks.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]); + } + } + + 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 + } + + renderTitle(){ + + var context = this.getContext() + + if (context.items_count > 1){ + return ( +
+
+ {context.items_count} {context.nice_name}{context.items_count > 1 ? 's' : null} selected {this.props.uiActions.setSelectedTracks([]); this.props.uiActions.hideContextMenu()}}> +
+
+ ) + } + + if (context.name == 'custom'){ + if (!this.props.menu.title){ + return null; + } + + return ( +
+
+ {this.props.menu.title} +
+
+ ) + } + + // Do we need titles or does it just confuse things and rip off Spotify too hard? + return null; +/* + switch (context.type){ + + case 'artist': + case 'album': + case 'playlist': + var style = null; + + return ( +
+ +
{context.item.name}
+
+ {context.source} +
+
+ ); + + default: + return ( +
+
+ {context.items_count} {context.nice_name}{context.items_count > 1 ? 's' : null} +
+
+ {context.source} +
+
+ ); + + }*/ + } + + setSubmenu(name){ + + if (this.state.submenu !== name && name == 'add-to-playlist'){ + if (!this.props.spotify_library_playlists_loaded_all){ + this.props.spotifyActions.getLibraryPlaylists() + } + if (!this.props.mopidy_library_playlists_loaded_all){ + this.props.mopidyActions.getLibraryPlaylists() + } + } + + this.setState({submenu: name}); + } + + renderSubmenu(){ + var list = null; + var loader = null; + + switch (this.state.submenu){ + + case 'add-to-playlist': + + 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') + + if (this.props.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR && this.props.processes.SPOTIFY_GET_LIBRARY_PLAYLISTS_PROCESSOR.status == 'running'){ + loader = ( +
+
+
+
+
+ ) + } + + list = No writable playlists + if (playlists.length > 0){ + list = playlists.map(playlist => { + return ( + + this.addTracksToPlaylist(e,playlist.uri) }> + { playlist.name } + + + ) + }) + } + + } + + return ( + + ); + } + + renderItems(){ + var context = this.getContext() + + var play_uris = ( + + ) + + var play_playlist = ( + + ) + + var shuffle_play_playlist = ( + + ) + + var play_queue_item = ( + + ) + + var play_uris_next = ( + + ) + + var play_artist_top_tracks = ( + + ) + + var add_to_queue = ( + + ) + + var add_to_playlist = ( + + ) + + var toggle_in_library = ( + + ) + + if (!this.props.spotify_authorized){ + var toggle_in_library = null; + } else if (helpers.isLoading(this.props.load_queue,['spotify_me/tracks/contains','spotify_me/playlists/contains','spotify_me/albums/contains','spotify_me/artists/contains'])){ + var toggle_in_library = ( + + ) + } else { + var toggle_in_library = ( + + ) + } + + if (!this.props.lastfm_authorized){ + var toggle_loved = null; + } else if (helpers.isLoading(this.props.load_queue,['lastfm_track.getInfo'])){ + var toggle_loved = ( + + ) + } else { + var toggle_loved = ( + + ) + } + + var go_to_artist = ( + + ) + + var go_to_user = ( + + ) + + var go_to_track = ( + + ) + + var go_to_recommendations = ( + + ) + + var start_radio = ( + + ) + + var remove_from_queue = ( + + ) + + var remove_from_playlist = ( + + ) + + var delete_playlist = ( + + ) + + var copy_uris = ( + + ) + + 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} + {this.canBeInLibrary() ? toggle_in_library : null} + {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} + {this.canBeInLibrary() ? toggle_in_library : null} + {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} + {this.canBeInLibrary() ? toggle_in_library : null} + {toggle_loved} +
+ {context.source == 'spotify' && context.items_count <= 5 ? go_to_recommendations : 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){ + className += ' context-menu--submenu-expanded'; + } + + if (this.props.menu.closing){ + className += ' context-menu--closing'; + } + + 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.renderSubmenu()} +
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, + spotify_library_tracks: state.spotify.library_tracks, + 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) diff --git a/src/js/services/spotify/reducer.js b/src/js/services/spotify/reducer.js index e3f20a42..5c0c7e57 100755 --- a/src/js/services/spotify/reducer.js +++ b/src/js/services/spotify/reducer.js @@ -307,6 +307,16 @@ export default function reducer(spotify = {}, action){ } return Object.assign({}, spotify, { library_playlists: items }); + case 'SPOTIFY_LIBRARY_TRACK_CHECK': + var items = Object.assign([], spotify.library_tracks) + var index = items.indexOf(action.key) + if (index > -1 && !action.in_library) { + items.splice(index, 1) + } else if (index < 0 && action.in_library){ + items.push(action.key) + } + return Object.assign({}, spotify, { library_tracks: items }); + /** * Searching diff --git a/src/js/views/library/LibraryTracks.js b/src/js/views/library/LibraryTracks.js index 9030ae26..e62a3659 100755 --- a/src/js/views/library/LibraryTracks.js +++ b/src/js/views/library/LibraryTracks.js @@ -46,7 +46,9 @@ class LibraryTracks extends React.Component{ } render(){ - if (helpers.isLoading(this.props.load_queue,['spotify_me/tracks'])){ + + // Note trailing "?" makes sure our context menu in_library checks doesn't interfere + if (helpers.isLoading(this.props.load_queue,['spotify_me/tracks?'])){ return (