From 1de58afa4485ea29ef24265e9243a7a483c2a175 Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Mon, 14 Nov 2016 11:20:02 +1300 Subject: [PATCH] Creation and deletion of playlists --- src/js/components/Modal.js | 76 ++++++++++++++++++- src/js/components/Sidebar.js | 6 +- src/js/services/mopidy/actions.js | 7 ++ src/js/services/mopidy/middleware.js | 97 ++++++++++++++++-------- src/js/services/spotify/actions.js | 10 +++ src/js/services/spotify/middleware.js | 11 +++ src/js/services/ui/actions.js | 25 ++++-- src/js/views/Playlist.js | 7 +- src/js/views/Settings.js | 72 +++++++++--------- src/js/views/library/LibraryPlaylists.js | 17 ++++- src/scss/components/_context-menu.scss | 3 +- src/scss/global/_forms.scss | 93 ++++++++++++++++++++++- 12 files changed, 338 insertions(+), 86 deletions(-) diff --git a/src/js/components/Modal.js b/src/js/components/Modal.js index f2e20836..9ee2111d 100755 --- a/src/js/components/Modal.js +++ b/src/js/components/Modal.js @@ -12,6 +12,15 @@ import * as spotifyActions from '../services/spotify/actions' class Modal extends React.Component{ + constructor(props){ + super(props) + this.state = { + playlist_name: '', + playlist_scheme: 'spotify', + playlist_private: true + } + } + playlistSelected( playlist_uri ){ this.props.uiActions.addTracksToPlaylist( playlist_uri, this.props.modal.data.track_indexes ) this.props.uiActions.closeModal() @@ -49,6 +58,68 @@ class Modal extends React.Component{ ) } + createPlaylist(e){ + e.preventDefault(); + this.props.uiActions.createPlaylist( this.state.playlist_scheme, this.state.playlist_name, this.state.playlist_public ); + return false; + } + + renderCreatePlaylist(){ + return ( +
+

Create playlist

+
this.createPlaylist(e)}> +
+
Name
+
+ this.setState({ playlist_name: e.target.value })} + value={ this.state.playlist_name } /> +
+
+
+
Provider
+
+ + +
+
+
+
Options
+
+ +
+
+ +
+
+ ) + } + render(){ if( !this.props.modal ) return null; @@ -59,9 +130,8 @@ class Modal extends React.Component{
-

{ this.props.modal.name }

- - { this.renderEditablePlaylists() } + { this.props.modal.name == 'add_to_playlist' ? this.renderEditablePlaylists() : null } + { this.props.modal.name == 'create_playlist' ? this.renderCreatePlaylist() : null }
diff --git a/src/js/components/Sidebar.js b/src/js/components/Sidebar.js index d4836fc1..4eed86af 100755 --- a/src/js/components/Sidebar.js +++ b/src/js/components/Sidebar.js @@ -62,15 +62,15 @@ class Sidebar extends React.Component{ Playlists - + Artists - + Albums - + Tracks diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js index 60792281..5901c070 100755 --- a/src/js/services/mopidy/actions.js +++ b/src/js/services/mopidy/actions.js @@ -125,6 +125,13 @@ export function setTimePosition( time_position ){ * Asset-oriented actions **/ +export function deletePlaylist( uri ){ + return { + type: 'MOPIDY_DELETE_PLAYLIST', + uri: uri + } +} + export function getPlaylists(){ return { type: 'MOPIDY_PLAYLISTS' } } diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index 48b1e984..6f8fe1c0 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -85,18 +85,30 @@ const MopidyMiddleware = (function(){ var callParts = call.split('.'); var model = callParts[0]; var method = callParts[1]; - if( method ){ - var mopidyObject = ws[model][method] - var property = method; - }else{ - var mopidyObject = ws[model] - var property = model; - } - - property = property.replace('get',''); - property = property.replace('set',''); return new Promise( (resolve, reject) => { + + if( model in ws ){ + if( method in ws[model] ){ + var mopidyObject = ws[model][method] + var property = method; + }else{ + var mopidyObject = ws[model] + var property = model; + } + }else{ + var error = { + message: 'Call to an invalid object. Check your are calling a valid Mopidy endpoint.', + call: call, + value: value + } + console.error(error) + reject(error) + } + + property = property.replace('get',''); + property = property.replace('set',''); + mopidyObject( value ) .then( response => { @@ -219,32 +231,39 @@ const MopidyMiddleware = (function(){ uris.push( playlist.tracks[i].uri ); } - instruct( socket, store, 'library.lookup', { uris: uris } ) - .then( response => { + // no tracks + if( uris.length <= 0 ){ + store.dispatch({ type: 'MOPIDY_PLAYLIST_LOADED', data: playlist }); - for(var uri in response){ - if (response.hasOwnProperty(uri)) { + // tracks? let's flesh them out with full track objects, rather than just reference objects + }else{ + instruct( socket, store, 'library.lookup', { uris: uris } ) + .then( response => { - var track = response[uri][0]; - if( track ){ - - // find the track reference, and drop in the full track data - function getByURI( trackReference ){ - return track.uri == trackReference.uri - } - var trackReferences = playlist.tracks.filter(getByURI); - - // there could be multiple instances of this track, so accommodate this - for( var j = 0; j < trackReferences.length; j++){ - var key = playlist.tracks.indexOf( trackReferences[j] ); - playlist.tracks[ key ] = track; + for(var uri in response){ + if (response.hasOwnProperty(uri)) { + + var track = response[uri][0]; + if( track ){ + + // find the track reference, and drop in the full track data + function getByURI( trackReference ){ + return track.uri == trackReference.uri + } + var trackReferences = playlist.tracks.filter(getByURI); + + // there could be multiple instances of this track, so accommodate this + for( var j = 0; j < trackReferences.length; j++){ + var key = playlist.tracks.indexOf( trackReferences[j] ); + playlist.tracks[ key ] = track; + } } } } - } - store.dispatch({ type: 'MOPIDY_PLAYLIST_LOADED', data: playlist }); - }) + store.dispatch({ type: 'MOPIDY_PLAYLIST_LOADED', data: playlist }); + }) + } }) break; @@ -292,6 +311,24 @@ const MopidyMiddleware = (function(){ }); break + case 'MOPIDY_CREATE_PLAYLIST': + instruct( socket, store, 'playlists.create', { name: action.name, uri_scheme: action.scheme }) + .then( response => { + + // re-load our global playlists + store.dispatch({ type: 'MOPIDY_PLAYLISTS' }); + }); + break + + case 'MOPIDY_DELETE_PLAYLIST': + instruct( socket, store, 'playlists.delete', { uri: action.uri }) + .then( response => { + + // re-load our global playlists + store.dispatch({ type: 'MOPIDY_PLAYLISTS' }); + }); + break + case 'MOPIDY_ALBUM': //store.dispatch({ type: 'MOPIDY_ALBUM_LOADED', data: false }); instruct( socket, store, 'library.lookup', action.data ) diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index 040f143e..615d7549 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -503,6 +503,16 @@ export function getSearchResults( query, type = 'album,artist,playlist,track', l * Playlists **/ +export function createPlaylist( name, is_private ){ + return (dispatch, getState) => { + + sendRequest( dispatch, getState, 'users/'+ getState().spotify.me.id +'/playlists/', 'POST', { name: name, public: !is_private } ) + .then( response => { + dispatch( getAllLibraryPlaylists() ); + }) + } +} + export function getPlaylist( uri ){ return (dispatch, getState) => { diff --git a/src/js/services/spotify/middleware.js b/src/js/services/spotify/middleware.js index 8d86ece7..6430c51b 100755 --- a/src/js/services/spotify/middleware.js +++ b/src/js/services/spotify/middleware.js @@ -16,6 +16,17 @@ const SpotifyMiddleware = (function(){ store.dispatch( actions.getMe() ) + case 'SPOTIFY_CREATE_PLAYLIST': + var playlist = state.ui.playlist + + if( !store.getState().spotify.authorized ){ + alert('Must be logged in to Spotify to do this') + return + } + store.dispatch( actions.createPlaylist( action.name, action.is_private )) + break + + case 'SPOTIFY_REMOVE_PLAYLIST_TRACKS': var playlist = state.ui.playlist diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js index 75b4df56..8f21b4e0 100755 --- a/src/js/services/ui/actions.js +++ b/src/js/services/ui/actions.js @@ -66,6 +66,26 @@ export function dragEnd(){ return { type: 'DRAG_END' } } +export function createPlaylist( scheme, name, is_private = false ){ + switch( scheme ){ + + case 'spotify': + return { + type: 'SPOTIFY_CREATE_PLAYLIST', + name: name, + is_private: is_private + } + + case 'm3u': + return { + type: 'MOPIDY_CREATE_PLAYLIST', + scheme: scheme, + name: name + } + } + return false +} + export function removeTracksFromPlaylist( playlist_uri, tracks_indexes ){ switch( helpers.uriSource( playlist_uri ) ){ @@ -82,9 +102,6 @@ export function removeTracksFromPlaylist( playlist_uri, tracks_indexes ){ playlist_uri: playlist_uri, tracks_indexes: tracks_indexes } - - defaut: - return ui } } @@ -103,7 +120,6 @@ export function addTracksToPlaylist( playlist_uri, tracks ){ playlist_uri: playlist_uri, tracks_uris: tracks_uris } - break; case 'm3u': return { @@ -111,7 +127,6 @@ export function addTracksToPlaylist( playlist_uri, tracks ){ playlist_uri: playlist_uri, tracks_uris: tracks_uris } - break; } } diff --git a/src/js/views/Playlist.js b/src/js/views/Playlist.js index 640e5386..9bf4c697 100755 --- a/src/js/views/Playlist.js +++ b/src/js/views/Playlist.js @@ -67,7 +67,7 @@ class Playlist extends React.Component{ } delete(){ - alert('Delete me') + this.props.mopidyActions.deletePlaylist( this.props.params.uri ) } removeTracks( track_indexes ){ @@ -75,15 +75,14 @@ class Playlist extends React.Component{ } renderFollowOrDeleteButton(){ - if( !this.props.spotify_authorized ) return null - switch( helpers.uriSource( this.props.params.uri ) ){ case 'm3u': return this.delete() } /> break - case 'spotify': + case 'spotify': + if( !this.props.spotify_authorized ) return null if( this.props.playlist.owner && this.props.playlist.owner.id == this.props.spotify_userid ){ return this.unfollow() } /> }else if( this.props.playlist.following ){ diff --git a/src/js/views/Settings.js b/src/js/views/Settings.js index 669130ce..dce74d3d 100755 --- a/src/js/views/Settings.js +++ b/src/js/views/Settings.js @@ -93,43 +93,43 @@ class Settings extends React.Component{

Mopidy

this.setMopidyConfig(e)}> -