diff --git a/src/js/components/AlbumGrid.js b/src/js/components/AlbumGrid.js
index a981989d..e4c27c3b 100755
--- a/src/js/components/AlbumGrid.js
+++ b/src/js/components/AlbumGrid.js
@@ -1,13 +1,36 @@
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,item){
+ e.preventDefault()
+ var data = {
+ uris: [item.uri],
+ item: item
+ }
+ this.props.uiActions.showContextMenu( e, data, 'album', 'click' )
+ }
+
render(){
if( this.props.albums ){
var className = "grid album-grid"
@@ -17,7 +40,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)}>
+
+
{ album.name }
+
+ { album.artists ?
:
- }
+
+
+ )
}
)
}
@@ -28,3 +62,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..9088c1f2 100755
--- a/src/js/components/ArtistGrid.js
+++ b/src/js/components/ArtistGrid.js
@@ -1,13 +1,28 @@
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,item){
+ e.preventDefault()
+ var data = {
+ uris: [item.uri],
+ item: item
+ }
+ this.props.uiActions.showContextMenu( e, data, 'artist', 'click' )
+ }
+
render(){
if( this.props.artists ){
var className = "grid artist-grid"
@@ -17,7 +32,19 @@ export default class ArtistGrid extends React.Component{
{
this.props.artists.map(
(artist, index) => {
- return
+ return (
+ this.handleContextMenu(e,artist)}>
+
+ { artist.name }
+
+ {artist.followers ? artist.followers.total.toLocaleString()+' followers' : 0 followers}
+
+
+ )
}
)
}
@@ -28,3 +55,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 1aa59037..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'
@@ -41,63 +43,64 @@ 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)
+ startRadio(){
+ this.props.pusherActions.startRadio(this.props.context_menu.data.uris)
+ this.props.uiActions.hideContextMenu();
+ }
+
+ 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()
}
@@ -130,7 +133,7 @@ class ContextMenu extends React.Component{
)
}
- getItems(trigger){
+ getItems(){
switch (this.props.context_menu.context) {
case 'queue':
@@ -142,12 +145,39 @@ class ContextMenu extends React.Component{
]
break
- case 'editable-playlist':
+ case 'album':
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: 'startRadio', label: 'Start radio', icon: 'spotify' },
+ { 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
+
+ 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
+
+ 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' }
]
@@ -155,10 +185,11 @@ class ContextMenu extends React.Component{
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: 'startRadio', label: 'Start radio', icon: 'spotify' },
{ handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' }
]
break
@@ -167,17 +198,24 @@ class ContextMenu extends React.Component{
return items
}
- renderItems(trigger){
- var items = this.getItems(trigger)
-
- var closeItem = (
-
- this.closeAndDeselectTracks(e) }>
-
- Cancel
-
-
+ renderTitle(){
+ var item = this.props.context_menu.data.item
+ var style = null
+ if (item && item.images){
+ style = {
+ backgroundImage: 'url('+helpers.sizedImages(item.images).medium+')'
+ }
+ }
+ return (
+
+ {style ? : null}
+ {item.name}
+
)
+ }
+
+ renderItems(){
+ var items = this.getItems()
return (
)
}
@@ -241,7 +278,8 @@ class ContextMenu extends React.Component{
return (
- { this.renderItems(trigger) }
+ {this.props.context_menu.data.item ? this.renderTitle() : null}
+ {this.renderItems(trigger)}
);
}
@@ -261,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/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/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/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/components/List.js b/src/js/components/List.js
index a24303bc..43f5c8cb 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,15 @@ export default class List extends React.Component{
}
}
+ handleContextMenu(e,item){
+ e.preventDefault()
+ var data = {
+ uris: [item.uri],
+ item: item
+ }
+ this.props.uiActions.showContextMenu(e, data, helpers.uriType(item.uri), 'click')
+ }
+
renderHeader(){
if (!this.props.columns || this.props.noheader) return null
@@ -71,7 +83,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)}
+ className={className}
+ key={row_index}>
{
this.props.columns.map( (col, col_index) => {
return (
@@ -89,4 +105,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/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/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/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/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..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')
@@ -229,7 +230,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
@@ -268,7 +269,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/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 d9617aba..6aca393c 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
@@ -89,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/Album.js b/src/js/views/Album.js
index 0b14193a..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
@@ -66,7 +73,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' );
}
@@ -92,7 +98,7 @@ class Album extends React.Component{
-
+
this.handleContextMenu(e) } />
@@ -118,7 +124,7 @@ class Album extends React.Component{
{ this.props.album.tracks ? : null }
- this.loadMore() }/>
+ this.loadMore() }/>
@@ -146,6 +152,7 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = (dispatch) => {
return {
+ uiActions: bindActionCreators(uiActions, dispatch),
mopidyActions: bindActionCreators(mopidyActions, dispatch),
spotifyActions: bindActionCreators(spotifyActions, dispatch)
}
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/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/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/js/views/Playlist.js b/src/js/views/Playlist.js
index 00b1ccce..91228e00 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 );
}
@@ -154,8 +153,8 @@ class Playlist extends React.Component{
- { this.props.playlist.tracks ? this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null }
- this.loadMore() }/>
+ { 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/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..ccbcb21b 100755
--- a/src/js/views/library/LibraryAlbums.js
+++ b/src/js/views/library/LibraryAlbums.js
@@ -28,8 +28,13 @@ 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(){
- if( !this.props.library_albums_more ) return
this.props.spotifyActions.getURL( this.props.library_albums_more, 'SPOTIFY_LIBRARY_ALBUMS_LOADED' );
}
@@ -77,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{
@@ -173,7 +184,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..3d994b1f 100755
--- a/src/js/views/library/LibraryArtists.js
+++ b/src/js/views/library/LibraryArtists.js
@@ -21,12 +21,17 @@ 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();
}
loadMore(){
- if( !this.props.library_artists_more ) return
this.props.spotifyActions.getURL( this.props.library_artists_more, 'SPOTIFY_LIBRARY_ARTISTS_LOADED' );
}
@@ -62,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} />
)
}
@@ -127,7 +138,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..8b169085 100755
--- a/src/scss/app.scss
+++ b/src/scss/app.scss
@@ -22,6 +22,8 @@
@import 'components/header';
@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/_context-menu.scss b/src/scss/components/_context-menu.scss
index 7c4c6207..c3bed4a3 100755
--- a/src/scss/components/_context-menu.scss
+++ b/src/scss/components/_context-menu.scss
@@ -21,6 +21,45 @@
bottom: 0 !important;
}
+ .title {
+ cursor: pointer;
+ display: block;
+ padding: 8px 12px;
+ width: 140px;
+ font-weight: bold;
+ box-sizing: border-box;
+ position: relative;
+ overflow: hidden;
+ text-decoration: none;
+ background: $darkest_grey;
+
+ .background {
+ @include blur(5px);
+ background-size: cover;
+ background-position: 50% 20%;
+ opacity: 0.5;
+ position: absolute;
+ 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;
+ }
+ }
+ }
+
.menu-item-wrapper {
display: block;
position: relative;
@@ -65,7 +104,7 @@
opacity: 0.5;
}
- &:not(:first-child) .menu-item {
+ .menu-item {
border-top: 1px solid lighten($dark_grey, 8%);
}
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
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;
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 );