From 598dd9f0b03279a4e75a09aa22c653b7b052b457 Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Fri, 27 Jan 2017 08:15:59 +1300 Subject: [PATCH 1/7] Lazy loader loading indicator --- src/js/components/LazyLoadListener.js | 10 +++++--- src/js/views/Album.js | 3 +-- src/js/views/Artist.js | 3 +-- src/js/views/Playlist.js | 3 +-- src/js/views/Search.js | 12 ++++------ src/js/views/User.js | 3 +-- src/js/views/discover/DiscoverCategory.js | 6 ++--- src/js/views/discover/DiscoverNewReleases.js | 3 +-- src/js/views/library/LibraryAlbums.js | 3 +-- src/js/views/library/LibraryArtists.js | 3 +-- src/js/views/library/LibraryLocalAlbums.js | 11 ++++++--- src/js/views/library/LibraryTracks.js | 3 +-- src/scss/app.scss | 1 + src/scss/components/_loader.scss | 24 ++++++++++++++++++++ src/scss/global/_variables.scss | 12 ++++++++++ 15 files changed, 67 insertions(+), 33 deletions(-) create mode 100755 src/scss/components/_loader.scss diff --git a/src/js/components/LazyLoadListener.js b/src/js/components/LazyLoadListener.js index 3ce1f4c7..a9d29778 100755 --- a/src/js/components/LazyLoadListener.js +++ b/src/js/components/LazyLoadListener.js @@ -19,8 +19,8 @@ export default class LazyLoadListener extends React.Component{ } handleScroll(e){ - if( (window.innerHeight + window.scrollY) >= document.body.offsetHeight ){ - if( !this.state.loading ){ + if( (window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 80) ){ + if( !this.state.loading && this.props.enabled ){ this.setState({ loading: true }) this.props.loadMore(); } @@ -30,6 +30,10 @@ export default class LazyLoadListener extends React.Component{ } render(){ - return
+ return ( +
+
+
+ ) } } \ No newline at end of file diff --git a/src/js/views/Album.js b/src/js/views/Album.js index 0b14193a..86b59b3a 100755 --- a/src/js/views/Album.js +++ b/src/js/views/Album.js @@ -66,7 +66,6 @@ class Album extends React.Component{ } loadMore(){ - if( !this.props.album.tracks_more ) return this.props.spotifyActions.getURL( this.props.album.tracks_more, 'SPOTIFY_ALBUM_LOADED_MORE' ); } @@ -118,7 +117,7 @@ class Album extends React.Component{
{ this.props.album.tracks ? : null } - this.loadMore() }/> + this.loadMore() }/>
diff --git a/src/js/views/Artist.js b/src/js/views/Artist.js index bba09849..6c2ab45f 100755 --- a/src/js/views/Artist.js +++ b/src/js/views/Artist.js @@ -81,7 +81,6 @@ class Artist extends React.Component{ } loadMore(){ - if( !this.props.artist.albums_more ) return this.props.spotifyActions.getURL( this.props.artist.albums_more, 'SPOTIFY_ARTIST_ALBUMS_LOADED', this.props.params.uri ); } @@ -164,7 +163,7 @@ class Artist extends React.Component{

Albums

- this.loadMore() }/> + this.loadMore() }/>
) diff --git a/src/js/views/Playlist.js b/src/js/views/Playlist.js index 00b1ccce..c94477ed 100755 --- a/src/js/views/Playlist.js +++ b/src/js/views/Playlist.js @@ -54,7 +54,6 @@ class Playlist extends React.Component{ } loadMore(){ - if( !this.props.playlist.tracks_more ) return this.props.spotifyActions.getURL( this.props.playlist.tracks_more, 'PLAYLIST_LOADED_MORE_TRACKS', this.props.playlist.uri ); } @@ -155,7 +154,7 @@ class Playlist extends React.Component{
{ this.props.playlist.tracks ? this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null } - this.loadMore() }/> + this.loadMore() }/>
diff --git a/src/js/views/Search.js b/src/js/views/Search.js index 40051712..80c957df 100755 --- a/src/js/views/Search.js +++ b/src/js/views/Search.js @@ -47,9 +47,7 @@ class Search extends React.Component{ } loadMore(type){ - if( this.props[type+'_more'] ){ - this.props.spotifyActions.getURL( this.props[type+'_more'], 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_'+type.toUpperCase()); - } + this.props.spotifyActions.getURL( this.props[type+'_more'], 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_'+type.toUpperCase()); } renderResults(){ @@ -91,7 +89,7 @@ class Search extends React.Component{
- this.loadMore('artists') }/> + this.loadMore('artists') }/>
) @@ -102,7 +100,7 @@ class Search extends React.Component{
- this.loadMore('albums') }/> + this.loadMore('albums') }/>
) @@ -113,7 +111,7 @@ class Search extends React.Component{
- this.loadMore('playlists') }/> + this.loadMore('playlists') }/>
) @@ -124,7 +122,7 @@ class Search extends React.Component{
- this.loadMore('tracks') }/> + this.loadMore('tracks') }/>
) diff --git a/src/js/views/User.js b/src/js/views/User.js index 5493546a..190d2500 100755 --- a/src/js/views/User.js +++ b/src/js/views/User.js @@ -33,7 +33,6 @@ class User extends React.Component{ } loadMore(){ - if( !this.props.user.playlists_more ) return this.props.spotifyActions.getURL( this.props.user.playlists_more, 'SPOTIFY_USER_PLAYLISTS_LOADED', this.props.params.uri ) } @@ -83,7 +82,7 @@ class User extends React.Component{
- this.loadMore() }/> + this.loadMore() }/>
diff --git a/src/js/views/discover/DiscoverCategory.js b/src/js/views/discover/DiscoverCategory.js index 382928e6..a7ed4ec4 100755 --- a/src/js/views/discover/DiscoverCategory.js +++ b/src/js/views/discover/DiscoverCategory.js @@ -30,9 +30,7 @@ class DiscoverCategory extends React.Component{ } loadMore(){ - if (this.props.category.playlists_more){ - this.props.spotifyActions.getURL( this.props.category.playlists_more, 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', 'category:'+this.props.params.id ); - } + this.props.spotifyActions.getURL( this.props.category.playlists_more, 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', 'category:'+this.props.params.id ); } render(){ @@ -54,7 +52,7 @@ class DiscoverCategory extends React.Component{
- this.loadMore() }/> + this.loadMore() }/> ); } diff --git a/src/js/views/discover/DiscoverNewReleases.js b/src/js/views/discover/DiscoverNewReleases.js index a6a50f7f..fe63e323 100755 --- a/src/js/views/discover/DiscoverNewReleases.js +++ b/src/js/views/discover/DiscoverNewReleases.js @@ -21,7 +21,6 @@ class DiscoverNewReleases extends React.Component{ } loadMore(){ - if (!this.props.new_releases_more) return this.props.spotifyActions.getURL(this.props.new_releases_more, 'SPOTIFY_NEW_RELEASES_LOADED'); } @@ -43,7 +42,7 @@ class DiscoverNewReleases extends React.Component{
- this.loadMore() }/> + this.loadMore() }/> ); } diff --git a/src/js/views/library/LibraryAlbums.js b/src/js/views/library/LibraryAlbums.js index 8ddd73bc..e4b75dfd 100755 --- a/src/js/views/library/LibraryAlbums.js +++ b/src/js/views/library/LibraryAlbums.js @@ -29,7 +29,6 @@ class LibraryAlbums extends React.Component{ } loadMore(){ - if( !this.props.library_albums_more ) return this.props.spotifyActions.getURL( this.props.library_albums_more, 'SPOTIFY_LIBRARY_ALBUMS_LOADED' ); } @@ -173,7 +172,7 @@ class LibraryAlbums extends React.Component{
{ this.renderView(albums) } - this.loadMore() }/> + this.loadMore() }/>
); } diff --git a/src/js/views/library/LibraryArtists.js b/src/js/views/library/LibraryArtists.js index 748c23dc..f1d8ec2f 100755 --- a/src/js/views/library/LibraryArtists.js +++ b/src/js/views/library/LibraryArtists.js @@ -26,7 +26,6 @@ class LibraryArtists extends React.Component{ } loadMore(){ - if( !this.props.library_artists_more ) return this.props.spotifyActions.getURL( this.props.library_artists_more, 'SPOTIFY_LIBRARY_ARTISTS_LOADED' ); } @@ -127,7 +126,7 @@ class LibraryArtists extends React.Component{
{ this.renderView(artists) } - this.loadMore() }/> + this.loadMore() }/>
); } diff --git a/src/js/views/library/LibraryLocalAlbums.js b/src/js/views/library/LibraryLocalAlbums.js index f0b95b30..dac9dc63 100755 --- a/src/js/views/library/LibraryLocalAlbums.js +++ b/src/js/views/library/LibraryLocalAlbums.js @@ -36,7 +36,7 @@ class LibraryLocalAlbums extends React.Component{ } } - loadMore(){ + moreURIsToLoad(){ var uris = [] if (this.props.albums && this.props.local_albums){ for (var i = 0; i < this.props.local_albums.length; i++){ @@ -50,7 +50,12 @@ class LibraryLocalAlbums extends React.Component{ } } - if (uris && uris.length > 0) this.props.mopidyActions.getAlbums(uris) + return uris + } + + loadMore(){ + var uris = this.moreURIsToLoad() + this.props.mopidyActions.getAlbums(uris) } setSort(value){ @@ -147,7 +152,7 @@ class LibraryLocalAlbums extends React.Component{
{this.renderView(albums)} - this.loadMore() }/> + 0)} loadMore={ () => this.loadMore() }/>
) } diff --git a/src/js/views/library/LibraryTracks.js b/src/js/views/library/LibraryTracks.js index e1f0c5aa..544e4672 100755 --- a/src/js/views/library/LibraryTracks.js +++ b/src/js/views/library/LibraryTracks.js @@ -23,7 +23,6 @@ class LibraryTracks extends React.Component{ } loadMore(){ - if( !this.props.tracks_more ) return this.props.spotifyActions.getURL( this.props.tracks_more, 'SPOTIFY_LIBRARY_TRACKS_LOADED_MORE' ); } @@ -33,7 +32,7 @@ class LibraryTracks extends React.Component{
{ this.props.tracks ? : null } - this.loadMore() }/> + this.loadMore() }/>
); diff --git a/src/scss/app.scss b/src/scss/app.scss index 9b71437f..cda2f2f3 100755 --- a/src/scss/app.scss +++ b/src/scss/app.scss @@ -22,6 +22,7 @@ @import 'components/header'; @import 'components/notifications'; @import 'components/dropdown-field'; +@import 'components/loader'; @import 'views/artist'; @import 'views/user'; diff --git a/src/scss/components/_loader.scss b/src/scss/components/_loader.scss new file mode 100755 index 00000000..b50200e9 --- /dev/null +++ b/src/scss/components/_loader.scss @@ -0,0 +1,24 @@ + +.lazy-loader { + padding: 20px 0 40px; + + .loader { + @include animate(); + opacity: 0; + margin: 0 auto; + width: 30px; + height: 30px; + border: { + radius: 100%; + style: solid; + color: $mid_grey; + width: 2px; + } + border-top-color: transparent; + } + + &.loading .loader { + @include spin(); + opacity: 1; + } +} \ No newline at end of file diff --git a/src/scss/global/_variables.scss b/src/scss/global/_variables.scss index d9366a58..662e6e92 100755 --- a/src/scss/global/_variables.scss +++ b/src/scss/global/_variables.scss @@ -49,6 +49,18 @@ $bp_shallow: 650px; animation-timing-function: linear; } +@keyframes spin { + from { transform: rotate(0); } + to { transform: rotate(360deg); } +} + +@mixin spin( $duration: 0.5s ){ + animation-name: spin; + animation-duration: $duration; + animation-timing-function: linear; + animation-iteration-count: infinite; +} + @mixin blur( $size: 10px ) { -webkit-filter: blur( $size ); filter: blur( $size ); From 60f49d46112a61b069acd08310b70d908e841b9f Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Fri, 27 Jan 2017 09:55:19 +1300 Subject: [PATCH 2/7] Making context menu more generic for other assets; Copy to clipboard --- src/js/components/ContextMenu.js | 69 +++++++++++++++------------- src/js/components/Thumbnail.js | 9 +++- src/js/components/TrackList.js | 11 +++-- src/js/services/mopidy/actions.js | 6 +-- src/js/services/mopidy/middleware.js | 2 +- src/js/views/Album.js | 10 +++- 6 files changed, 65 insertions(+), 42 deletions(-) diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index 1aa59037..a8a9cea8 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -41,63 +41,59 @@ class ContextMenu extends React.Component{ } playQueueItem(){ - var selectedTracks = this.props.context_menu.data.selected_tracks; - this.props.mopidyActions.changeTrack( selectedTracks[0].tlid ); + var tracks = this.props.context_menu.data.items; + this.props.mopidyActions.changeTrack( tracks[0].tlid ); this.props.uiActions.hideContextMenu(); } removeFromQueue(){ - var selected_tracks = this.props.context_menu.data.selected_tracks; - var selected_tracks_tlids = []; - for( var i = 0; i < selected_tracks.length; i++ ){ - selected_tracks_tlids.push( selected_tracks[i].tlid ); + var tracks = this.props.context_menu.data.items; + var tracks_tlids = []; + for( var i = 0; i < tracks.length; i++ ){ + tracks_tlids.push( tracks[i].tlid ); } - this.props.mopidyActions.removeTracks( selected_tracks_tlids ); + this.props.mopidyActions.removeTracks( tracks_tlids ); this.props.uiActions.hideContextMenu(); } - playItems(){ - var selected_tracks_uris = helpers.asURIs(this.props.context_menu.data.selected_tracks) - this.props.mopidyActions.playURIs(selected_tracks_uris); + playURIs(){ + this.props.mopidyActions.playURIs(this.props.context_menu.data.uris); this.props.uiActions.hideContextMenu(); } - playItemsNext(){ - var selected_tracks_uris = helpers.asURIs(this.props.context_menu.data.selected_tracks) - this.props.mopidyActions.enqueueTracksNext(selected_tracks_uris); + playURIsNext(){ + this.props.mopidyActions.enqueueURIsNext(this.props.context_menu.data.uris); this.props.uiActions.hideContextMenu(); } addToPlaylist(){ - var selected_tracks_uris = helpers.asURIs(this.props.context_menu.data.selected_tracks) - this.props.uiActions.openModal( 'add_to_playlist', { tracks_uris: selected_tracks_uris } ) + this.props.uiActions.openModal( 'add_to_playlist', { tracks_uris: this.props.context_menu.data.uris } ) this.props.uiActions.hideContextMenu(); } addToQueue(){ - var selected_tracks_uris = helpers.asURIs(this.props.context_menu.data.selected_tracks) - this.props.mopidyActions.enqueueTracks(selected_tracks_uris); - this.props.uiActions.hideContextMenu(); + this.props.mopidyActions.enqueueURIs(this.props.context_menu.data.uris) + this.props.uiActions.hideContextMenu() } addTracksToPlaylist( playlist_uri ){ - var selected_tracks_uris = helpers.asURIs(this.props.context_menu.data.selected_tracks) - this.props.uiActions.addTracksToPlaylist( playlist_uri, selected_tracks_uris ) + this.props.uiActions.addTracksToPlaylist( playlist_uri, this.props.context_menu.data.uris ) this.props.uiActions.hideContextMenu(); } removeFromPlaylist(){ - this.props.uiActions.removeTracksFromPlaylist( this.props.playlist.uri, this.props.context_menu.data.selected_tracks_indexes ) + this.props.uiActions.removeTracksFromPlaylist( this.props.playlist.uri, this.props.context_menu.data.indexes ) this.props.uiActions.hideContextMenu(); } - copyURIs(){ - var uris = ''; - for( var i = 0; i < this.props.context_menu.data.selected_tracks.length; i++ ){ - if( i > 0 ) uris += ',' - uris += this.props.context_menu.data.selected_tracks[i].uri - } - console.log('Yet to be implemented', uris) + copyURIs(e){ + var temp = $(""); + $("body").append(temp); + temp.val(this.props.context_menu.data.uris.join(',')).select(); + document.execCommand("copy"); + temp.remove(); + + this.props.uiActions.createNotification( "Copied "+this.props.context_menu.data.uris.length+" URIs" ) this.props.uiActions.hideContextMenu() } @@ -144,8 +140,8 @@ class ContextMenu extends React.Component{ case 'editable-playlist': var items = [ - { handleClick: 'playItems', label: 'Play', icon: 'play' }, - { handleClick: 'playItemsNext', label: 'Play next', icon: 'play' }, + { handleClick: 'playURIs', label: 'Play', icon: 'play' }, + { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, { handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true }, { handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' }, @@ -153,10 +149,19 @@ class ContextMenu extends React.Component{ ] break + case 'album': + var items = [ + { handleClick: 'playURIs', label: 'Play', icon: 'play' }, + { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, + { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, + { handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' } + ] + break + default: var items = [ - { handleClick: 'playItems', label: 'Play', icon: 'play' }, - { handleClick: 'playItemsNext', label: 'Play next', icon: 'play' }, + { handleClick: 'playURIs', label: 'Play', icon: 'play' }, + { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, { handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true }, { handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' } diff --git a/src/js/components/Thumbnail.js b/src/js/components/Thumbnail.js index f6e08e18..e0a2f217 100755 --- a/src/js/components/Thumbnail.js +++ b/src/js/components/Thumbnail.js @@ -39,6 +39,13 @@ class Thumbnail extends React.Component{ return false } + handleContextMenu(e){ + if (this.props.handleContextMenu){ + e.preventDefault() + this.props.handleContextMenu(e) + } + } + mapImageSizes( props = this.props ){ // no images @@ -75,7 +82,7 @@ class Thumbnail extends React.Component{ } return ( -
+
this.handleContextMenu(e) }>
{zoom_icon}
diff --git a/src/js/components/TrackList.js b/src/js/components/TrackList.js index b4e40abe..b43da2e5 100755 --- a/src/js/components/TrackList.js +++ b/src/js/components/TrackList.js @@ -5,6 +5,7 @@ import { bindActionCreators } from 'redux' import Track from './Track' +import * as helpers from '../helpers' import * as mopidyActions from '../services/mopidy/actions' import * as uiActions from '../services/ui/actions' @@ -78,8 +79,9 @@ class TrackList extends React.Component{ var selected_tracks = this.selectedTracks() if( selected_tracks.length > 0 ){ var data = { - selected_tracks: selected_tracks, - selected_tracks_indexes: this.tracksIndexes( selected_tracks ) + items: selected_tracks, + uris: helpers.asURIs(selected_tracks), + indexes: this.tracksIndexes(selected_tracks) } this.props.uiActions.showContextMenu( e, data, this.props.context, 'touch' ) }else{ @@ -133,8 +135,9 @@ class TrackList extends React.Component{ handleContextMenu(e, index){ var selected_tracks = this.selectedTracks() var data = { - selected_tracks: selected_tracks, - selected_tracks_indexes: this.tracksIndexes( selected_tracks ) + items: selected_tracks, + uris: helpers.asURIs(selected_tracks), + indexes: this.tracksIndexes(selected_tracks) } this.props.uiActions.showContextMenu( e, data, this.props.context, 'click' ) } diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js index 1df3fb96..0fe7d7f0 100755 --- a/src/js/services/mopidy/actions.js +++ b/src/js/services/mopidy/actions.js @@ -56,15 +56,15 @@ export function playURIs( uris ){ } } -export function enqueueTracksNext( uris ){ +export function enqueueURIsNext( uris ){ if( typeof(uris) !== 'object' ) uris = [uris] return { - type: 'MOPIDY_ENQUEUE_TRACKS_NEXT', + type: 'MOPIDY_ENQUEUE_URIS_NEXT', uris: uris } } -export function enqueueTracks( uris, at_position = false ){ +export function enqueueURIs( uris, at_position = false ){ if( typeof(uris) !== 'object' ) uris = [uris]; var value = { uris: uris }; if( at_position ) value.at_position = at_position; diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index c19bea99..316c4575 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -229,7 +229,7 @@ const MopidyMiddleware = (function(){ store.dispatch({ type: 'MOPIDY_URISCHEMES_FILTERED', data: uri_schemes }); break; - case 'MOPIDY_ENQUEUE_TRACKS_NEXT': + case 'MOPIDY_ENQUEUE_URIS_NEXT': var current_track = store.getState().ui.current_track var current_tracklist = store.getState().ui.current_tracklist diff --git a/src/js/views/Album.js b/src/js/views/Album.js index 86b59b3a..21fea844 100755 --- a/src/js/views/Album.js +++ b/src/js/views/Album.js @@ -15,6 +15,7 @@ import LazyLoadListener from '../components/LazyLoadListener' import SidebarToggleButton from '../components/SidebarToggleButton' import * as helpers from '../helpers' +import * as uiActions from '../services/ui/actions' import * as mopidyActions from '../services/mopidy/actions' import * as spotifyActions from '../services/spotify/actions' @@ -28,6 +29,12 @@ class Album extends React.Component{ this.loadAlbum() } + handleContextMenu(e){ + e.preventDefault() + var data = { uris: [this.props.params.uri] } + this.props.uiActions.showContextMenu( e, data, 'album', 'click' ) + } + componentWillReceiveProps( nextProps ){ // if our URI has changed, fetch new album @@ -91,7 +98,7 @@ class Album extends React.Component{
- + this.handleContextMenu(e) } />
@@ -145,6 +152,7 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = (dispatch) => { return { + uiActions: bindActionCreators(uiActions, dispatch), mopidyActions: bindActionCreators(mopidyActions, dispatch), spotifyActions: bindActionCreators(spotifyActions, dispatch) } From c22c93428db7b849ed907f93d9808bdbcf157e5c Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Fri, 27 Jan 2017 12:13:18 +1300 Subject: [PATCH 3/7] Preventing shuffling of playlists you don't own --- src/js/helpers.js | 1 + src/js/services/spotify/middleware.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/js/helpers.js b/src/js/helpers.js index e24b8e42..64bd088f 100755 --- a/src/js/helpers.js +++ b/src/js/helpers.js @@ -161,6 +161,7 @@ export let getFromUri = function( element, uri ){ if( element == 'userid' && exploded[1] == 'user' ) return exploded[2]; if( element == 'playlistid' && exploded[3] == 'playlist' ) return exploded[4]; if( element == 'artistid' && exploded[1] == 'artist' ) return exploded[2]; + if( element == 'artistid' && exploded[3] == 'playlist' ) return exploded[2]; if( element == 'albumid' && exploded[1] == 'album' ) return exploded[2]; if( element == 'trackid' && exploded[1] == 'track' ) return exploded[2]; return null; diff --git a/src/js/services/spotify/middleware.js b/src/js/services/spotify/middleware.js index d9617aba..78214047 100755 --- a/src/js/services/spotify/middleware.js +++ b/src/js/services/spotify/middleware.js @@ -56,6 +56,11 @@ const SpotifyMiddleware = (function(){ store.dispatch( uiActions.createNotification( "Must be logged in to Spotify to do that", 'bad' ) ) return } + + if( !store.getState().spotify.me || store.getState().spotify.me.id != helpers.getFromUri('artistid',action.key) ){ + store.dispatch( uiActions.createNotification( "You can't edit a playlist you don't own", 'bad' ) ) + return + } store.dispatch( spotifyActions.reorderPlaylistTracks( action.key, action.range_start, action.range_length, action.insert_before, action.snapshot_id )) break From 31472ae564cdb90904c5f12c9c45a66914f9a8df Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Fri, 27 Jan 2017 13:24:36 +1300 Subject: [PATCH 4/7] Album and Artist grids with context menus --- src/js/components/AlbumGrid.js | 49 ++++++++++++++++++++++++++-- src/js/components/ArtistGrid.js | 42 ++++++++++++++++++++++-- src/js/components/ContextMenu.js | 10 +++++- src/js/components/Thumbnail.js | 9 +---- src/js/services/mopidy/middleware.js | 2 +- src/scss/components/_grid.scss | 4 +++ 6 files changed, 100 insertions(+), 16 deletions(-) diff --git a/src/js/components/AlbumGrid.js b/src/js/components/AlbumGrid.js index a981989d..63184cf9 100755 --- a/src/js/components/AlbumGrid.js +++ b/src/js/components/AlbumGrid.js @@ -1,13 +1,33 @@ import React, { PropTypes } from 'react' -import GridItem from './GridItem' +import { connect } from 'react-redux' +import { createStore, bindActionCreators } from 'redux' +import { hashHistory } from 'react-router' +import FontAwesome from 'react-fontawesome' -export default class AlbumGrid extends React.Component{ +import Thumbnail from './Thumbnail' +import ArtistSentence from './ArtistSentence' + +import * as uiActions from '../services/ui/actions' + +class AlbumGrid extends React.Component{ constructor(props) { super(props); } + handleClick(e,link){ + if( e.target.tagName.toLowerCase() !== 'a' ){ + hashHistory.push(link) + } + } + + handleContextMenu(e,uri){ + e.preventDefault() + var data = { uris: [uri] } + this.props.uiActions.showContextMenu( e, data, 'album', 'click' ) + } + render(){ if( this.props.albums ){ var className = "grid album-grid" @@ -17,7 +37,18 @@ export default class AlbumGrid extends React.Component{ { this.props.albums.map( (album, index) => { - return + return ( +
this.handleClick(e,global.baseURL+'album/'+album.uri) } + onContextMenu={e => this.handleContextMenu(e,album.uri)}> + +
{ album.name }
+
+ { album.artists ? : - } +
+
+ ) } ) } @@ -28,3 +59,15 @@ export default class AlbumGrid extends React.Component{ } } +const mapStateToProps = (state, ownProps) => { + return {} +} + +const mapDispatchToProps = (dispatch) => { + return { + uiActions: bindActionCreators(uiActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AlbumGrid) + diff --git a/src/js/components/ArtistGrid.js b/src/js/components/ArtistGrid.js index 7dc83808..4747183e 100755 --- a/src/js/components/ArtistGrid.js +++ b/src/js/components/ArtistGrid.js @@ -1,13 +1,25 @@ import React, { PropTypes } from 'react' -import GridItem from './GridItem' +import { connect } from 'react-redux' +import { createStore, bindActionCreators } from 'redux' +import { Link } from 'react-router' -export default class ArtistGrid extends React.Component{ +import Thumbnail from './Thumbnail' + +import * as uiActions from '../services/ui/actions' + +class ArtistGrid extends React.Component{ constructor(props) { super(props); } + handleContextMenu(e,uri){ + e.preventDefault() + var data = { uris: [uri] } + this.props.uiActions.showContextMenu( e, data, 'artist', 'click' ) + } + render(){ if( this.props.artists ){ var className = "grid artist-grid" @@ -17,7 +29,19 @@ export default class ArtistGrid extends React.Component{ { this.props.artists.map( (artist, index) => { - return + return ( + this.handleContextMenu(e,artist.uri)}> + +
{ artist.name }
+
+ {artist.followers ? artist.followers.total.toLocaleString()+' followers' : 0 followers} +
+ + ) } ) } @@ -28,3 +52,15 @@ export default class ArtistGrid extends React.Component{ } } +const mapStateToProps = (state, ownProps) => { + return {} +} + +const mapDispatchToProps = (dispatch) => { + return { + uiActions: bindActionCreators(uiActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(ArtistGrid) + diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index a8a9cea8..c30f9638 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -154,7 +154,15 @@ class ContextMenu extends React.Component{ { handleClick: 'playURIs', label: 'Play', icon: 'play' }, { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, - { handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' } + { handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' } + ] + break + + case 'artist': + var items = [ + { handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, + { handleClick: 'toggleFollow', label: 'Follow/unfollow', icon: 'toggle-on' }, + { handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' } ] break diff --git a/src/js/components/Thumbnail.js b/src/js/components/Thumbnail.js index e0a2f217..f6e08e18 100755 --- a/src/js/components/Thumbnail.js +++ b/src/js/components/Thumbnail.js @@ -39,13 +39,6 @@ class Thumbnail extends React.Component{ return false } - handleContextMenu(e){ - if (this.props.handleContextMenu){ - e.preventDefault() - this.props.handleContextMenu(e) - } - } - mapImageSizes( props = this.props ){ // no images @@ -82,7 +75,7 @@ class Thumbnail extends React.Component{ } return ( -
this.handleContextMenu(e) }> +
{zoom_icon}
diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index 316c4575..7782d204 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -268,7 +268,7 @@ const MopidyMiddleware = (function(){ // add the rest of our uris (if any) action.uris.shift(); if( action.uris.length > 0 ){ - store.dispatch( mopidyActions.enqueueTracks( action.uris, 1 ) ) + store.dispatch( mopidyActions.enqueueURIs( action.uris, 1 ) ) } }) break; diff --git a/src/scss/components/_grid.scss b/src/scss/components/_grid.scss index fe916bf4..6b65b4e3 100755 --- a/src/scss/components/_grid.scss +++ b/src/scss/components/_grid.scss @@ -16,6 +16,10 @@ .grid { + .grid-item-wrapper { + display: inline-block; + } + .grid-item { display: inline-block; vertical-align: top; From cd17467cedfa2845301324f3125aa52c466d8920 Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Fri, 27 Jan 2017 16:13:11 +1300 Subject: [PATCH 5/7] Nicer context menus --- src/js/components/ContextMenu.js | 26 +++++++++++++- src/js/components/List.js | 33 ++++++++++++++--- src/js/components/PlaylistGrid.js | 49 +++++++++++++++++++++++--- src/js/components/PlaylistListItem.js | 41 --------------------- src/js/services/mopidy/middleware.js | 1 + src/js/views/library/LibraryAlbums.js | 16 +++++++-- src/js/views/library/LibraryArtists.js | 16 +++++++-- src/scss/components/_context-menu.scss | 22 ++++++++++++ 8 files changed, 150 insertions(+), 54 deletions(-) delete mode 100755 src/js/components/PlaylistListItem.js diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index c30f9638..4a728f5c 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -166,6 +166,14 @@ class ContextMenu extends React.Component{ ] break + case 'playlist': + var items = [ + { handleClick: 'playURIs', label: 'Play', icon: 'play' }, + { handleClick: 'toggleFollow', label: 'Follow/unfollow', icon: 'toggle-on' }, + { handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' } + ] + break + default: var items = [ { handleClick: 'playURIs', label: 'Play', icon: 'play' }, @@ -180,6 +188,21 @@ class ContextMenu extends React.Component{ return items } + renderTitle(){ + var style = {} + var item = this.props.context_menu.data.item + var image = null + if (item && item.images){ + image = helpers.sizedImages(item.images).medium + } + return ( +
+ {image ? : null} + {this.props.context_menu.data.item.name} +
+ ) + } + renderItems(trigger){ var items = this.getItems(trigger) @@ -254,7 +277,8 @@ class ContextMenu extends React.Component{ return (
- { this.renderItems(trigger) } + {this.renderTitle()} + {this.renderItems(trigger)}
); } diff --git a/src/js/components/List.js b/src/js/components/List.js index a24303bc..88f2de74 100755 --- a/src/js/components/List.js +++ b/src/js/components/List.js @@ -1,14 +1,17 @@ import React, { PropTypes } from 'react' -import { Link, hashHistory } from 'react-router' +import { connect } from 'react-redux' +import { createStore, bindActionCreators } from 'redux' +import { hashHistory, Link } from 'react-router' import FontAwesome from 'react-fontawesome' import ArtistSentence from './ArtistSentence' import Dater from './Dater' import * as helpers from '../helpers' +import * as uiActions from '../services/ui/actions' -export default class List extends React.Component{ +class List extends React.Component{ constructor(props) { super(props); @@ -22,6 +25,12 @@ export default class List extends React.Component{ } } + handleContextMenu(e,uri){ + e.preventDefault() + var data = { uris: [uri] } + this.props.uiActions.showContextMenu(e, data, helpers.uriType(uri), 'click') + } + renderHeader(){ if (!this.props.columns || this.props.noheader) return null @@ -71,7 +80,11 @@ export default class List extends React.Component{ if( row.type ) className += ' '+row.type return ( -
this.handleClick(e, row.uri)} className={className} key={row_index}> +
this.handleClick(e, row.uri)} + onContextMenu={e => this.handleContextMenu(e,row.uri)} + className={className} + key={row_index}> { this.props.columns.map( (col, col_index) => { return ( @@ -89,4 +102,16 @@ export default class List extends React.Component{
); } -} \ No newline at end of file +} + +const mapStateToProps = (state, ownProps) => { + return {} +} + +const mapDispatchToProps = (dispatch) => { + return { + uiActions: bindActionCreators(uiActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(List) diff --git a/src/js/components/PlaylistGrid.js b/src/js/components/PlaylistGrid.js index f8d703c3..6ce3924a 100755 --- a/src/js/components/PlaylistGrid.js +++ b/src/js/components/PlaylistGrid.js @@ -1,13 +1,29 @@ import React, { PropTypes } from 'react' -import GridItem from './GridItem' +import { connect } from 'react-redux' +import { createStore, bindActionCreators } from 'redux' +import { Link } from 'react-router' +import FontAwesome from 'react-fontawesome' -export default class PlaylistGrid extends React.Component{ +import Thumbnail from './Thumbnail' + +import * as uiActions from '../services/ui/actions' + +class PlaylistGrid extends React.Component{ constructor(props) { super(props); } + handleContextMenu(e,item){ + e.preventDefault() + var data = { + uris: [item.uri], + item: item + } + this.props.uiActions.showContextMenu( e, data, 'playlist', 'click' ) + } + render(){ if( !this.props.playlists ) return null @@ -17,8 +33,21 @@ export default class PlaylistGrid extends React.Component{
{ this.props.playlists.map( - (playlist, index) => { - return + (playlist, index) => { + return ( + this.handleContextMenu(e,playlist)}> + +
{ playlist.name }
+
+ { playlist.tracks_total ? playlist.tracks_total+' tracks' : 0 tracks } + { playlist.can_edit ? : null } +
+ + ) } ) } @@ -27,3 +56,15 @@ export default class PlaylistGrid extends React.Component{ } } +const mapStateToProps = (state, ownProps) => { + return {} +} + +const mapDispatchToProps = (dispatch) => { + return { + uiActions: bindActionCreators(uiActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(PlaylistGrid) + diff --git a/src/js/components/PlaylistListItem.js b/src/js/components/PlaylistListItem.js deleted file mode 100755 index f9639176..00000000 --- a/src/js/components/PlaylistListItem.js +++ /dev/null @@ -1,41 +0,0 @@ - -import React, { PropTypes } from 'react' -import FontAwesome from 'react-fontawesome' -import { Link } from 'react-router' - -import ListItem from './ListItem' -import ItemSource from './ItemSource' - -export default class PlaylistListItem extends React.Component{ - - constructor(props) { - super(props); - } - - renderOwner(){ - if( !this.props.item.owner ) return null; - return ( - - { this.props.item.owner.id } - - ); - } - - render(){ - if( !this.props.item ) return null - - return ( - - - {this.props.item.name} - - - { this.renderOwner() } - - - - - - ); - } -} \ No newline at end of file diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index 7782d204..db0654c4 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -3,6 +3,7 @@ import Mopidy from 'mopidy' import * as helpers from '../../helpers' var mopidyActions = require('./actions.js') +var uiActions = require('../ui/actions.js') var lastfmActions = require('../lastfm/actions.js') var pusherActions = require('../pusher/actions.js') diff --git a/src/js/views/library/LibraryAlbums.js b/src/js/views/library/LibraryAlbums.js index e4b75dfd..ccbcb21b 100755 --- a/src/js/views/library/LibraryAlbums.js +++ b/src/js/views/library/LibraryAlbums.js @@ -28,6 +28,12 @@ class LibraryAlbums extends React.Component{ if (!this.props.library_albums) this.props.spotifyActions.getLibraryAlbums(); } + handleContextMenu(e,uri){ + e.preventDefault() + var data = { uris: [uri] } + this.props.uiActions.showContextMenu( e, data, 'album', 'click' ) + } + loadMore(){ this.props.spotifyActions.getURL( this.props.library_albums_more, 'SPOTIFY_LIBRARY_ALBUMS_LOADED' ); } @@ -76,13 +82,19 @@ class LibraryAlbums extends React.Component{ ] return (
- + this.handleContextMenu(e,uri)} + rows={albums} + columns={columns} + link_prefix={global.baseURL+"album/"} />
) }else if( this.props.view == 'thumbnails' ){ return (
- + this.handleContextMenu(e,uri)} + albums={albums} />
) }else{ diff --git a/src/js/views/library/LibraryArtists.js b/src/js/views/library/LibraryArtists.js index f1d8ec2f..3d994b1f 100755 --- a/src/js/views/library/LibraryArtists.js +++ b/src/js/views/library/LibraryArtists.js @@ -21,6 +21,12 @@ class LibraryArtists extends React.Component{ super(props); } + handleContextMenu(e,uri){ + e.preventDefault() + var data = { uris: [uri] } + this.props.uiActions.showContextMenu( e, data, 'artist', 'click' ) + } + componentDidMount(){ if (!this.props.library_artists) this.props.spotifyActions.getLibraryArtists(); } @@ -61,13 +67,19 @@ class LibraryArtists extends React.Component{ ] return (
- + this.handleContextMenu(e,uri)} + rows={artists} + columns={columns} + link_prefix={global.baseURL+"artist/"} show_source_icon={true} />
) }else{ return (
- + this.handleContextMenu(e,uri)} + artists={artists} />
) } diff --git a/src/scss/components/_context-menu.scss b/src/scss/components/_context-menu.scss index 7c4c6207..31142f8d 100755 --- a/src/scss/components/_context-menu.scss +++ b/src/scss/components/_context-menu.scss @@ -21,6 +21,28 @@ bottom: 0 !important; } + .title { + cursor: pointer; + display: block; + padding: 8px 12px; + width: 100%; + font-weight: bold; + box-sizing: border-box; + position: relative; + overflow: hidden; + + img { + @include blur(5px); + background-size: cover; + background-position: 50% 0%; + opacity: 0.2; + position: absolute; + top: 0; + left: 0; + right: 0; + } + } + .menu-item-wrapper { display: block; position: relative; From 55c3bdc5ced216a50cb2de653c653b55acf8589f Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Sun, 29 Jan 2017 11:56:58 +1300 Subject: [PATCH 6/7] Context menus on all key grid and list views --- src/js/components/AlbumGrid.js | 9 ++-- src/js/components/ArtistGrid.js | 9 ++-- src/js/components/ContextMenu.js | 70 +++++++++++++------------- src/js/components/GridSlider.js | 53 +++++++++++++++++-- src/js/components/List.js | 11 ++-- src/js/components/Sidebar.js | 2 +- src/js/services/pusher/middleware.js | 6 +-- src/js/services/spotify/actions.js | 5 +- src/js/services/spotify/middleware.js | 2 +- src/js/views/Playlist.js | 2 +- src/js/views/Queue.js | 2 +- src/scss/components/_context-menu.scss | 33 +++++++++--- 12 files changed, 138 insertions(+), 66 deletions(-) diff --git a/src/js/components/AlbumGrid.js b/src/js/components/AlbumGrid.js index 63184cf9..e4c27c3b 100755 --- a/src/js/components/AlbumGrid.js +++ b/src/js/components/AlbumGrid.js @@ -22,9 +22,12 @@ class AlbumGrid extends React.Component{ } } - handleContextMenu(e,uri){ + handleContextMenu(e,item){ e.preventDefault() - var data = { uris: [uri] } + var data = { + uris: [item.uri], + item: item + } this.props.uiActions.showContextMenu( e, data, 'album', 'click' ) } @@ -41,7 +44,7 @@ class AlbumGrid extends React.Component{
this.handleClick(e,global.baseURL+'album/'+album.uri) } - onContextMenu={e => this.handleContextMenu(e,album.uri)}> + onContextMenu={e => this.handleContextMenu(e,album)}>
{ album.name }
diff --git a/src/js/components/ArtistGrid.js b/src/js/components/ArtistGrid.js index 4747183e..9088c1f2 100755 --- a/src/js/components/ArtistGrid.js +++ b/src/js/components/ArtistGrid.js @@ -14,9 +14,12 @@ class ArtistGrid extends React.Component{ super(props); } - handleContextMenu(e,uri){ + handleContextMenu(e,item){ e.preventDefault() - var data = { uris: [uri] } + var data = { + uris: [item.uri], + item: item + } this.props.uiActions.showContextMenu( e, data, 'artist', 'click' ) } @@ -34,7 +37,7 @@ class ArtistGrid extends React.Component{ className="grid-item" to={global.baseURL+'artist/'+artist.uri} key={index} - onContextMenu={e => this.handleContextMenu(e,artist.uri)}> + onContextMenu={e => this.handleContextMenu(e,artist)}>
{ artist.name }
diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index 4a728f5c..2b940d56 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -1,5 +1,6 @@ import React, { PropTypes } from 'react' +import { Link } from 'react-router' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import FontAwesome from 'react-fontawesome' @@ -7,6 +8,7 @@ import FontAwesome from 'react-fontawesome' import TrackList from './TrackList' import * as helpers from '../helpers' import * as uiActions from '../services/ui/actions' +import * as pusherActions from '../services/pusher/actions' import * as mopidyActions from '../services/mopidy/actions' import * as spotifyActions from '../services/spotify/actions' @@ -86,6 +88,11 @@ class ContextMenu extends React.Component{ this.props.uiActions.hideContextMenu(); } + startRadio(){ + this.props.pusherActions.startRadio(this.props.context_menu.data.uris) + this.props.uiActions.hideContextMenu(); + } + copyURIs(e){ var temp = $(""); $("body").append(temp); @@ -126,7 +133,7 @@ class ContextMenu extends React.Component{ ) } - getItems(trigger){ + getItems(){ switch (this.props.context_menu.context) { case 'queue': @@ -138,22 +145,12 @@ class ContextMenu extends React.Component{ ] break - case 'editable-playlist': - var items = [ - { handleClick: 'playURIs', label: 'Play', icon: 'play' }, - { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, - { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, - { handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true }, - { handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' }, - { handleClick: 'removeFromPlaylist', label: 'Remove', icon: 'trash' } - ] - break - case 'album': var items = [ { handleClick: 'playURIs', label: 'Play', icon: 'play' }, { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, + { handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, { handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' } ] break @@ -174,12 +171,25 @@ class ContextMenu extends React.Component{ ] break + case 'editable-playlist-track': + var items = [ + { handleClick: 'playURIs', label: 'Play', icon: 'play' }, + { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, + { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, + { handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true }, + { handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, + { handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' }, + { handleClick: 'removeFromPlaylist', label: 'Remove', icon: 'trash' } + ] + break + default: var items = [ { handleClick: 'playURIs', label: 'Play', icon: 'play' }, { handleClick: 'playURIsNext', label: 'Play next', icon: 'play' }, { handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' }, { handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true }, + { handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' }, { handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' } ] break @@ -189,31 +199,23 @@ class ContextMenu extends React.Component{ } renderTitle(){ - var style = {} var item = this.props.context_menu.data.item - var image = null + var style = null if (item && item.images){ - image = helpers.sizedImages(item.images).medium + style = { + backgroundImage: 'url('+helpers.sizedImages(item.images).medium+')' + } } return ( -
- {image ? : null} - {this.props.context_menu.data.item.name} -
+ + {style ?
: null} +
{item.name}
+ ) } - renderItems(trigger){ - var items = this.getItems(trigger) - - var closeItem = ( - - this.closeAndDeselectTracks(e) }> - - Cancel - - - ) + renderItems(){ + var items = this.getItems() return (
@@ -222,7 +224,7 @@ class ContextMenu extends React.Component{ if( item.playlists ){ return ( - this[item.handleClick](e) }> + this[item.handleClick](e)}> { item.label } @@ -233,7 +235,7 @@ class ContextMenu extends React.Component{ }else{ return ( - this[item.handleClick](e) }> + this[item.handleClick](e)}> { item.label } @@ -242,7 +244,6 @@ class ContextMenu extends React.Component{ } }) } - { trigger == 'touch' ? closeItem : null }
) } @@ -277,7 +278,7 @@ class ContextMenu extends React.Component{ return (
- {this.renderTitle()} + {this.props.context_menu.data.item ? this.renderTitle() : null} {this.renderItems(trigger)}
); @@ -298,6 +299,7 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = (dispatch) => { return { uiActions: bindActionCreators(uiActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), spotifyActions: bindActionCreators(spotifyActions, dispatch), mopidyActions: bindActionCreators(mopidyActions, dispatch) } diff --git a/src/js/components/GridSlider.js b/src/js/components/GridSlider.js index 79d85320..0098b95c 100755 --- a/src/js/components/GridSlider.js +++ b/src/js/components/GridSlider.js @@ -1,9 +1,16 @@ import React, { PropTypes } from 'react' +import { connect } from 'react-redux' +import { createStore, bindActionCreators } from 'redux' +import { Link } from 'react-router' import FontAwesome from 'react-fontawesome' -import GridItem from './GridItem' -export default class GridSlider extends React.Component{ +import ArtistSentence from './ArtistSentence' +import Thumbnail from './Thumbnail' + +import * as uiActions from '../services/ui/actions' + +class GridSlider extends React.Component{ constructor(props) { super(props) @@ -15,6 +22,21 @@ export default class GridSlider extends React.Component{ } } + handleClick(e,link){ + if( e.target.tagName.toLowerCase() !== 'a' ){ + hashHistory.push(link) + } + } + + handleContextMenu(e,item){ + e.preventDefault() + var data = { + uris: [item.uri], + item: item + } + this.props.uiActions.showContextMenu( e, data, 'album', 'click' ) + } + next(){ if (this.state.page >= this._pagelimit) return false this.setState({ page: this.state.page + 1 }) @@ -47,8 +69,19 @@ export default class GridSlider extends React.Component{ { this.props.tracks.map( (track, index) => { - var item = Object.assign({}, track.album, { artists: track.artists }) - return + var album = Object.assign({}, track.album, { artists: track.artists }) + return ( +
this.handleClick(e,global.baseURL+'album/'+album.uri) } + onContextMenu={e => this.handleContextMenu(e,album)}> + +
{ album.name }
+
+ { album.artists ? : - } +
+
+ ) } ) } @@ -61,3 +94,15 @@ export default class GridSlider extends React.Component{ } } +const mapStateToProps = (state, ownProps) => { + return {} +} + +const mapDispatchToProps = (dispatch) => { + return { + uiActions: bindActionCreators(uiActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(GridSlider) + diff --git a/src/js/components/List.js b/src/js/components/List.js index 88f2de74..43f5c8cb 100755 --- a/src/js/components/List.js +++ b/src/js/components/List.js @@ -25,10 +25,13 @@ class List extends React.Component{ } } - handleContextMenu(e,uri){ + handleContextMenu(e,item){ e.preventDefault() - var data = { uris: [uri] } - this.props.uiActions.showContextMenu(e, data, helpers.uriType(uri), 'click') + var data = { + uris: [item.uri], + item: item + } + this.props.uiActions.showContextMenu(e, data, helpers.uriType(item.uri), 'click') } renderHeader(){ @@ -82,7 +85,7 @@ class List extends React.Component{ return (
this.handleClick(e, row.uri)} - onContextMenu={e => this.handleContextMenu(e,row.uri)} + onContextMenu={e => this.handleContextMenu(e,row)} className={className} key={row_index}> { diff --git a/src/js/components/Sidebar.js b/src/js/components/Sidebar.js index 0f8af507..2115136f 100755 --- a/src/js/components/Sidebar.js +++ b/src/js/components/Sidebar.js @@ -39,7 +39,7 @@ class Sidebar extends React.Component{
Discover - + Discover diff --git a/src/js/services/pusher/middleware.js b/src/js/services/pusher/middleware.js index 588f2372..eb8082fe 100755 --- a/src/js/services/pusher/middleware.js +++ b/src/js/services/pusher/middleware.js @@ -214,7 +214,7 @@ const PusherMiddleware = (function(){ }) .then( response => { - uiActions.createNotification('Starting radio...') + store.dispatch(uiActions.createNotification('Starting radio...')) } ) @@ -252,11 +252,11 @@ const PusherMiddleware = (function(){ }) .then( response => { - uiActions.createNotification('Stopping radio') + store.dispatch(uiActions.createNotification('Stopping radio')) } ) - store.dispatch( uiActions.createNotification('Stopping radio') ) + store.dispatch(uiActions.createNotification('Stopping radio')) var data = { action: 'stop_radio', seed_artists: [], diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index d5431a51..c71d15c8 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -532,10 +532,9 @@ export function resolveRadioSeeds( radio ){ // add to our list of async requests requests.push( - sendRequest( dispatch, getState, 'tracks/'+ track_ids ) + sendRequest( dispatch, getState, 'tracks?ids='+ track_ids ) .then( response => { - if (!(response instanceof Array)) response = [response] - Object.assign(resolved_seeds.seed_tracks, response); + Object.assign(resolved_seeds.seed_tracks, response.tracks); }) ) } diff --git a/src/js/services/spotify/middleware.js b/src/js/services/spotify/middleware.js index 78214047..6aca393c 100755 --- a/src/js/services/spotify/middleware.js +++ b/src/js/services/spotify/middleware.js @@ -94,7 +94,7 @@ const SpotifyMiddleware = (function(){ // only resolve if radio is enabled if( action.data.radio.enabled ){ - store.dispatch( spotifyActions.resolveRadioSeeds( action.data.radio ) ) + store.dispatch(spotifyActions.resolveRadioSeeds(action.data.radio)) } break diff --git a/src/js/views/Playlist.js b/src/js/views/Playlist.js index c94477ed..91228e00 100755 --- a/src/js/views/Playlist.js +++ b/src/js/views/Playlist.js @@ -153,7 +153,7 @@ class Playlist extends React.Component{
- { this.props.playlist.tracks ? this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null } + { this.props.playlist.tracks ? this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null } this.loadMore() }/>
diff --git a/src/js/views/Queue.js b/src/js/views/Queue.js index db32cb7f..7c7197d5 100755 --- a/src/js/views/Queue.js +++ b/src/js/views/Queue.js @@ -67,7 +67,7 @@ class Queue extends React.Component{ } } if (this.props.radio.seed_genres.length > 0) seed_sentence =this.props.radio.seed_artists.length+' genres' - if (this.props.radio.seed_tracks.length > 0) seed_sentence =this.props.radio.seed_artists.length+' tracks' + if (this.props.radio.seed_tracks.length > 0) seed_sentence =this.props.radio.seed_tracks.length+' tracks' return (
diff --git a/src/scss/components/_context-menu.scss b/src/scss/components/_context-menu.scss index 31142f8d..c3bed4a3 100755 --- a/src/scss/components/_context-menu.scss +++ b/src/scss/components/_context-menu.scss @@ -25,21 +25,38 @@ cursor: pointer; display: block; padding: 8px 12px; - width: 100%; + width: 140px; font-weight: bold; box-sizing: border-box; position: relative; overflow: hidden; + text-decoration: none; + background: $darkest_grey; - img { + .background { @include blur(5px); background-size: cover; - background-position: 50% 0%; - opacity: 0.2; + background-position: 50% 20%; + opacity: 0.5; position: absolute; - top: 0; - left: 0; - right: 0; + top: -10px; + left: -10px; + bottom: -10px; + right: -10px; + z-index: 1; + } + + .text { + @include one_line_text(); + position: relative; + z-index: 2; + color: #FFFFFF; + } + + &:hover{ + .background { + opacity: 0.7; + } } } @@ -87,7 +104,7 @@ opacity: 0.5; } - &:not(:first-child) .menu-item { + .menu-item { border-top: 1px solid lighten($dark_grey, 8%); } From cdbcbd10a8c9622f8fab2584ae316512a2464193 Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Sun, 29 Jan 2017 12:54:06 +1300 Subject: [PATCH 7/7] Debug info panel --- src/js/components/DebugInfo.js | 50 +++++++++++++++++++++++++++++++++ src/js/views/App.js | 5 +++- src/js/views/Debug.js | 9 ++++++ src/scss/app.scss | 1 + src/scss/components/_debug.scss | 11 ++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100755 src/js/components/DebugInfo.js create mode 100755 src/scss/components/_debug.scss diff --git a/src/js/components/DebugInfo.js b/src/js/components/DebugInfo.js new file mode 100755 index 00000000..4d808866 --- /dev/null +++ b/src/js/components/DebugInfo.js @@ -0,0 +1,50 @@ + +import React, { PropTypes } from 'react' +import { connect } from 'react-redux' +import { Link } from 'react-router' +import { createStore, bindActionCreators } from 'redux' + +import * as uiActions from '../services/ui/actions' + +class DebugInfo extends React.Component{ + + constructor(props) { + super(props); + } + + render(){ + return ( +
+
+ {this.props.ui.albums ? Object.keys(this.props.ui.albums).length : '0'} albums +
+
+ {this.props.ui.artists ? Object.keys(this.props.ui.artists).length : '0'} artists +
+
+ {this.props.ui.playlists ? Object.keys(this.props.ui.playlists).length : '0'} playlists +
+
+ {this.props.ui.users ? Object.keys(this.props.ui.users).length : '0'} users +
+
+ {'ontouchstart' in document.documentElement ? 'can touch' : 'no touch'} +
+
+ ); + } +} + +const mapStateToProps = (state, ownProps) => { + return { + ui: state.ui + } +} + +const mapDispatchToProps = (dispatch) => { + return { + uiActions: bindActionCreators(uiActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(DebugInfo) \ No newline at end of file diff --git a/src/js/views/App.js b/src/js/views/App.js index 45e80322..76605906 100755 --- a/src/js/views/App.js +++ b/src/js/views/App.js @@ -12,6 +12,7 @@ import ContextMenu from '../components/ContextMenu' import Dragger from '../components/Dragger' import Modal from '../components/Modal/Modal' import Notifications from '../components/Notifications' +import DebugInfo from '../components/DebugInfo' import * as uiActions from '../services/ui/actions' import * as pusherActions from '../services/pusher/actions' @@ -132,6 +133,7 @@ class App extends React.Component{ + {this.props.debug_info ? : null}
); } @@ -151,7 +153,8 @@ const mapStateToProps = (state, ownProps) => { sidebar_open: state.ui.sidebar_open, dragger: state.ui.dragger, modal: state.ui.modal, - context_menu: state.ui.context_menu + context_menu: state.ui.context_menu, + debug_info: state.ui.debug_info } } diff --git a/src/js/views/Debug.js b/src/js/views/Debug.js index 02fe0c46..df52f463 100755 --- a/src/js/views/Debug.js +++ b/src/js/views/Debug.js @@ -87,6 +87,14 @@ class Debug extends React.Component{ onChange={ e => this.props.uiActions.set({ log_actions: !this.props.log_actions })} /> Log actions in developer console +
@@ -161,6 +169,7 @@ const mapStateToProps = (state, ownProps) => { connectionid: state.pusher.connectionid, emulate_touch: state.ui.emulate_touch, log_actions: state.ui.log_actions, + debug_info: state.ui.debug_info, debug_response: state.ui.debug_response } } diff --git a/src/scss/app.scss b/src/scss/app.scss index cda2f2f3..8b169085 100755 --- a/src/scss/app.scss +++ b/src/scss/app.scss @@ -23,6 +23,7 @@ @import 'components/notifications'; @import 'components/dropdown-field'; @import 'components/loader'; +@import 'components/debug'; @import 'views/artist'; @import 'views/user'; diff --git a/src/scss/components/_debug.scss b/src/scss/components/_debug.scss new file mode 100755 index 00000000..8e682756 --- /dev/null +++ b/src/scss/components/_debug.scss @@ -0,0 +1,11 @@ + +.debug-info { + font-size: 10px; + position: fixed; + top: 0; + right: 0; + padding: 10px; + background: rgba(0,0,0,0.5); + color: #FFFFFF; + pointer-events: none; +} \ No newline at end of file