diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index 3d5d0e08..426cd31b 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -29,6 +29,8 @@ class ContextMenu extends React.Component{ case 'editable-playlist': items = [ { handleClick: 'playItems', label: 'Play' }, + { handleClick: 'playItemsNext', label: 'Play next' }, + { handleClick: 'addToQueue', label: 'Add to queue' }, { handleClick: 'removeFromPlaylist', label: 'Remove' } ]; break; @@ -113,7 +115,7 @@ class ContextMenu extends React.Component{ } removeFromPlaylist(){ - console.log('removeFromPlaylist') + this.props.uiActions.removeTracksFromPlaylist( this.props.context_menu.data.selected_tracks_indexes ) this.props.uiActions.hideContextMenu(); } diff --git a/src/js/components/TrackList.js b/src/js/components/TrackList.js index cdf011a8..ac04f842 100755 --- a/src/js/components/TrackList.js +++ b/src/js/components/TrackList.js @@ -92,8 +92,10 @@ class TrackList extends React.Component{ } handleContextMenu(e, index){ + var selected_tracks = this.selectedTracks() var data = { - selected_tracks: this.selectedTracks() + selected_tracks: selected_tracks, + selected_tracks_indexes: this.tracksIndexes( selected_tracks ) } this.props.uiActions.showContextMenu( e, this.props.context, data ) } @@ -105,6 +107,14 @@ class TrackList extends React.Component{ return this.state.tracks.filter(isSelected) } + tracksIndexes(tracks){ + var indexes = [] + for( var i = 0; i < tracks.length; i++ ){ + indexes.push( this.props.tracks.indexOf(tracks[i])) + } + return indexes + } + playTracks(){ var tracks = this.selectedTracks(); @@ -123,12 +133,12 @@ class TrackList extends React.Component{ } removeTracks(){ - - var tracks = this.selectedTracks(); - // if we've got a specific action, run it + // if this tracklist handles removal, handle it if( typeof(this.props.removeTracks) !== 'undefined' ){ - return this.props.removeTracks( tracks ); + var tracks = this.selectedTracks(); + var tracks_indexes = this.tracksIndexes(tracks); + return this.props.removeTracks( tracks_indexes ); } // by default, do nothing diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index 1799a1f2..5c3a7943 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -26,7 +26,7 @@ const sendRequest = ( dispatch, getState, endpoint, method = 'GET', data = false headers: { Authorization: 'Bearer '+ response }, - data: data + data: JSON.stringify(data) }).then( response => { resolve(response) @@ -326,75 +326,6 @@ export function getAlbum( uri ){ } } -/** - * Single playlist - * - * @oaram uri string - **/ -export function getPlaylist( uri ){ - return (dispatch, getState) => { - - // flush out the previous store value - dispatch({ type: 'SPOTIFY_PLAYLIST_LOADED', data: false }); - - var playlist = {}; - - $.when( - - sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'?market='+getState().spotify.country ) - .then( response => { - Object.assign( playlist, response ); - }), - - // TODO: Check if we're authenticated before sending this request, otherwise we get a 403 - sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) + '/playlists/'+ helpers.getFromUri('playlistid',uri) + '/followers/contains?ids='+getState().spotify.me.id ) - .then( response => { - var is_following = 0 - if( response.length > 0 ) is_following = response[0] - Object.assign(playlist, { following: response[0] } ); - }) - - ).then( () => { - dispatch({ - type: 'SPOTIFY_PLAYLIST_LOADED', - data: playlist - }); - }); - } -} - - -/** - * All of my playlists - **/ - -function loadNextPlaylistsBatch( dispatch, getState, playlists, lastResponse ){ - if( lastResponse.next ){ - sendRequest( dispatch, getState, lastResponse.next ) - .then( response => { - playlists = [...playlists, ...response.items] - loadNextPlaylistsBatch( dispatch, getState, playlists, response ) - }); - }else{ - dispatch({ - type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED', - data: playlists - }); - } -} - -export function getAllLibraryPlaylists(){ - return (dispatch, getState) => { - - dispatch({ type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED', data: false }); - - sendRequest( dispatch, getState, 'me/playlists?limit=50' ) - .then( response => { - loadNextPlaylistsBatch( dispatch, getState, response.items, response ) - }); - } -} - export function getLibraryArtists(){ return (dispatch, getState) => { @@ -567,10 +498,70 @@ export function getSearchResults( query, type = 'album,artist,playlist,track', l + /** - * Following toggles + * Playlists **/ +export function getPlaylist( uri ){ + return (dispatch, getState) => { + + // flush out the previous store value + dispatch({ type: 'SPOTIFY_PLAYLIST_LOADED', data: false }); + + var playlist = {}; + + $.when( + + sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'?market='+getState().spotify.country ) + .then( response => { + Object.assign( playlist, response ); + }), + + // TODO: Check if we're authenticated before sending this request, otherwise we get a 403 + sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) + '/playlists/'+ helpers.getFromUri('playlistid',uri) + '/followers/contains?ids='+getState().spotify.me.id ) + .then( response => { + var is_following = 0 + if( response.length > 0 ) is_following = response[0] + Object.assign(playlist, { following: response[0] } ); + }) + + ).then( () => { + dispatch({ + type: 'SPOTIFY_PLAYLIST_LOADED', + data: playlist + }); + }); + } +} + +function loadNextPlaylistsBatch( dispatch, getState, playlists, lastResponse ){ + if( lastResponse.next ){ + sendRequest( dispatch, getState, lastResponse.next ) + .then( response => { + playlists = [...playlists, ...response.items] + loadNextPlaylistsBatch( dispatch, getState, playlists, response ) + }); + }else{ + dispatch({ + type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED', + data: playlists + }); + } +} + +export function getAllLibraryPlaylists(){ + return (dispatch, getState) => { + + dispatch({ type: 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED', data: false }); + + sendRequest( dispatch, getState, 'me/playlists?limit=50' ) + .then( response => { + loadNextPlaylistsBatch( dispatch, getState, response.items, response ) + }); + } +} + export function toggleFollowingPlaylist( uri, method ){ if( method == 'PUT' ) var new_state = 1 if( method == 'DELETE' ) var new_state = 0 @@ -586,5 +577,16 @@ export function toggleFollowingPlaylist( uri, method ){ } } - +export function deleteTracksFromPlaylist( uri, snapshot_id, positions ){ + return (dispatch, getState) => { + sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) + '/playlists/'+ helpers.getFromUri('playlistid',uri) + '/tracks', 'DELETE', { snapshot_id: snapshot_id, positions: positions } ) + .then( response => { + dispatch({ + type: 'SPOTIFY_PLAYLIST_TRACKS_REMOVED', + positions: positions, + snapshot_id: response.snapshot_id + }); + }); + } +} diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js index 475c6137..58d0767c 100755 --- a/src/js/services/ui/actions.js +++ b/src/js/services/ui/actions.js @@ -65,4 +65,11 @@ export function dragEnd(){ return { type: 'DRAG_END' } } +export function removeTracksFromPlaylist( track_indexes ){ + return { + type: 'REMOVE_TRACKS_FROM_PLAYLIST', + track_indexes: track_indexes + } +} + diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js index 5f97338b..f7f94395 100755 --- a/src/js/services/ui/reducer.js +++ b/src/js/services/ui/reducer.js @@ -178,6 +178,14 @@ export default function reducer(ui = {}, action){ var playlist = Object.assign({}, ui.playlist, { following: action.data }) return Object.assign({}, ui, { playlist: playlist }); + case 'SPOTIFY_PLAYLIST_TRACKS_REMOVED': + var tracks = Object.assign([], ui.playlist.tracks) + for( var i = 0; i < action.positions.length; i++ ){ + tracks.splice( action.positions[i], 1 ) + } + var playlist = Object.assign({}, ui.playlist, action.snapshot_id, { tracks: tracks }) + return Object.assign({}, ui, { playlist: playlist }); + /** * Library Playlists diff --git a/src/js/views/Playlist.js b/src/js/views/Playlist.js index 6a34e5d8..202f0954 100755 --- a/src/js/views/Playlist.js +++ b/src/js/views/Playlist.js @@ -11,8 +11,9 @@ import Dater from '../components/Dater' import ConfirmationButton from '../components/ConfirmationButton' import LazyLoadListener from '../components/LazyLoadListener' -import * as spotifyActions from '../services/spotify/actions' +import * as uiActions from '../services/ui/actions' import * as mopidyActions from '../services/mopidy/actions' +import * as spotifyActions from '../services/spotify/actions' class Playlist extends React.Component{ @@ -35,11 +36,16 @@ class Playlist extends React.Component{ } loadPlaylist( props = this.props ){ - var source = helpers.uriSource( props.params.uri ); - if( source == 'spotify' ){ - this.props.spotifyActions.getPlaylist( props.params.uri ); - }else if( source == 'm3u' && props.mopidy_connected ){ - this.props.mopidyActions.getPlaylist( props.params.uri ); + switch( helpers.uriSource( props.params.uri ) ){ + + case 'spotify': + this.props.spotifyActions.getPlaylist( props.params.uri ); + break + + case 'm3u': + if( props.mopidy_connected ) this.props.mopidyActions.getPlaylist( props.params.uri ); + break + } } @@ -64,6 +70,10 @@ class Playlist extends React.Component{ alert('Delete me') } + removeTracks( track_indexes ){ + this.props.uiActions.removeTracksFromPlaylist( track_indexes ) + } + renderFollowOrDeleteButton(){ if( !this.props.spotify_authorized ) return null @@ -117,7 +127,7 @@ class Playlist extends React.Component{
- { this.props.playlist.tracks ? : null } + { this.props.playlist.tracks ? this.removeTracks(track_indexes) } /> : null } this.loadMore() }/>
@@ -145,6 +155,7 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = (dispatch) => { return { + uiActions: bindActionCreators(uiActions, dispatch), mopidyActions: bindActionCreators(mopidyActions, dispatch), spotifyActions: bindActionCreators(spotifyActions, dispatch) }