diff --git a/README.md b/README.md
index 8b2ada38..d47249fc 100755
--- a/README.md
+++ b/README.md
@@ -1,33 +1,17 @@
-Spotmop
+Iris
=======
-Mopidy web-based frontend that utilizes Spotify to create an interactive, user-friendly and collaborative music interface. Built and maintained by James Barnsley.
+Iris (previously known as Spotmop) is a Mopidy HTTP client that utilizes Spotify to create an interactive, user-friendly and collaborative music interface. Built and maintained by James Barnsley.


[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=james%40barnsley%2enz&lc=NZ&item_name=James%20Barnsley¤cy_code=NZD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted)
-
-Requirements
---------
-
-* Mopidy
-* Mopidy-Spotify
-* Spotify Premium account
-* Mopidy-Local-Sqlite (recommended, not required)
-
-Installation
---------
-
-1. Install using pip: `sudo pip install Mopidy-Spotmop`
-2. Restart Mopidy server
-3. Navigate to Mopidy interface (ie http://localhost:6680/spotmop)
-
Features
--------
* Full web-based interface controls for Mopidy
-* Uses Spotify API to deliver high-quality audio and music information
+* Integrated with Spotify and LastFM APIs for high-quality artwork and extra info
* Improved support for local libraries using SQLite extension
* Browse and manage your playlists, along with top tracks, new releases and genre browser
* Spotmop can be run completely independently of your Mopidy machine (ie on a remote server), just set your URL in the settings tab
@@ -49,11 +33,27 @@ Screenshots

-To-do
+Requirements
+--------
+
+* Mopidy
+* Mopidy-Spotify
+* Spotify Premium account
+* Mopidy-Local-Sqlite (recommended, not required)
+
+Installation
+--------
+
+1. Install using pip: `sudo pip install Mopidy-Spotmop`
+2. Restart Mopidy server
+3. Navigate to Mopidy interface (ie http://localhost:6680/spotmop)
+
+Development
-----
-* Increase stability of Mopidy server (perhaps limitation of Rpi?)
-* Improve websockets integration to attach users to tracks and changes within tracklist
+1. Clone this repository into a your Apache2 web directory (ie `/var/www/html/`)
+2. Install in develop mode `python setup.py install develop`
+3. Restart mopidy
Support
-------
diff --git a/src/js/components/ArtistList.js b/src/js/components/ArtistList.js
index fcd80dee..d9168f55 100755
--- a/src/js/components/ArtistList.js
+++ b/src/js/components/ArtistList.js
@@ -16,13 +16,21 @@ export default class ArtistList extends React.Component{
{
this.props.artists.map( (artist, index) => {
- var link = '/artist/' + artist.uri;
- return (
-
-
- { artist.name }
-
- );
+ if( artist.uri ){
+ return (
+
+
+ { artist.name }
+
+ )
+ }else{
+ return (
+
+
+ { artist.name }
+
+ )
+ }
})
}
diff --git a/src/js/components/Track.js b/src/js/components/Track.js
index d722a49a..52348f2c 100755
--- a/src/js/components/Track.js
+++ b/src/js/components/Track.js
@@ -41,6 +41,15 @@ export default class Track extends React.Component{
if( typeof(track.selected) !== 'undefined' && track.selected ) className += ' selected';
if( track.playing ) className += ' playing';
+ var album = '-'
+ if( track.album ){
+ if( track.album.uri ){
+ album = {track.album.name}
+ } else {
+ album = {track.album.name}
+ }
+ }
+
return (
: '-' }
- { track.album ? {track.album.name} : '-' }
+ {album}
{ track.duration_ms ? : null }
diff --git a/src/js/index.js b/src/js/index.js
index a3785265..5e6c64e6 100755
--- a/src/js/index.js
+++ b/src/js/index.js
@@ -19,6 +19,7 @@ import Playlist from './views/Playlist'
import User from './views/User'
import Queue from './views/Queue'
import Settings from './views/Settings'
+import Debug from './views/Debug'
import Search from './views/Search'
import Discover from './views/discover/Discover'
@@ -44,6 +45,7 @@ ReactDOM.render(
+
diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js
index fcd10b73..242d943d 100755
--- a/src/js/services/mopidy/actions.js
+++ b/src/js/services/mopidy/actions.js
@@ -28,6 +28,14 @@ export function instruct( call, value ){
}
}
+export function debug( call, value ){
+ return {
+ type: 'MOPIDY_DEBUG',
+ call: call,
+ value: value
+ }
+}
+
/**
* Playback-oriented actions
diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js
index 5b135963..922591f1 100755
--- a/src/js/services/mopidy/middleware.js
+++ b/src/js/services/mopidy/middleware.js
@@ -161,6 +161,14 @@ const MopidyMiddleware = (function(){
instruct( socket, store, action.call, action.value )
break;
+ // send an instruction to the websocket
+ case 'MOPIDY_DEBUG':
+ instruct( socket, store, action.call, action.value )
+ .then( response => {
+ store.dispatch({ type: 'DEBUG', response: response })
+ })
+ break;
+
// send an instruction to the websocket
case 'MOPIDY_NEXT':
var icon = false
diff --git a/src/js/services/pusher/actions.js b/src/js/services/pusher/actions.js
index 8d14de54..7e5f00a7 100755
--- a/src/js/services/pusher/actions.js
+++ b/src/js/services/pusher/actions.js
@@ -49,4 +49,12 @@ export function instruct( action, data = null ){
action: action,
data: data
}
+}
+
+export function debug( call, data = null ){
+ return {
+ type: 'PUSHER_DEBUG',
+ call: call,
+ data: data
+ }
}
\ No newline at end of file
diff --git a/src/js/services/pusher/middleware.js b/src/js/services/pusher/middleware.js
index d3015351..525aa47c 100755
--- a/src/js/services/pusher/middleware.js
+++ b/src/js/services/pusher/middleware.js
@@ -87,7 +87,35 @@ const PusherMiddleware = (function(){
break;
case 'PUSHER_INSTRUCT':
- makeRequest({ action: action.action, data: action.data });
+ switch( action.action ){
+ case 'query':
+ makeRequest( action.data )
+ break
+ case 'broadcast':
+ broadcast( action.data )
+ break
+ }
+ break;
+
+ case 'PUSHER_DEBUG':
+ switch( action.call ){
+ case 'query':
+ makeRequest( action.data )
+ // THIS DOES NOT RETURN A PROMISE SO CAN'T DETECT RELATED RESPONSES
+ /*.then( response => {
+ store.dispatch({ type: 'DEBUG', response: response })
+ })*/
+ break
+ case 'broadcast':
+ broadcast( action.data )
+ /*.then( response => {
+ store.dispatch({ type: 'DEBUG', response: response })
+ })*/
+ break
+ default:
+ store.dispatch({ type: 'DEBUG', response: '{ "error": "Invalid call" }' })
+ break
+ }
break;
case 'PUSHER_SEND_BROADCAST':
diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js
index b3b60c83..40ea7874 100755
--- a/src/js/services/ui/reducer.js
+++ b/src/js/services/ui/reducer.js
@@ -7,6 +7,9 @@ export default function reducer(ui = {}, action){
case 'LAZY_LOADING':
return Object.assign({}, ui, { lazy_loading: action.start });
+ case 'DEBUG':
+ return Object.assign({}, ui, { debug_response: action.response })
+
/**
* UI views and view modes
diff --git a/src/js/views/Debug.js b/src/js/views/Debug.js
new file mode 100755
index 00000000..9c19ff67
--- /dev/null
+++ b/src/js/views/Debug.js
@@ -0,0 +1,160 @@
+
+import React, { PropTypes } from 'react'
+import { connect } from 'react-redux'
+import { Link } from 'react-router'
+import { bindActionCreators } from 'redux'
+import FontAwesome from 'react-fontawesome'
+
+import SpotifyAuthenticationFrame from '../components/SpotifyAuthenticationFrame'
+import ConfirmationButton from '../components/ConfirmationButton'
+import PusherConnectionList from '../components/PusherConnectionList'
+import URISchemesList from '../components/URISchemesList'
+import VersionManager from '../components/VersionManager'
+import Header from '../components/Header'
+import Thumbnail from '../components/Thumbnail'
+
+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'
+
+class Debug extends React.Component{
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ mopidy_call: 'playlists.asList',
+ mopidy_data: '{}',
+ pusher_call: 'broadcast',
+ pusher_data: '{}'
+ }
+ }
+
+ componentDidMount(){
+ if( this.props.pusher.connectionid ){
+ var data = {
+ action: "notification",
+ recipients: [this.props.pusher.connectionid],
+ data: {
+ title: "Title",
+ body: "Test notification",
+ icon: "http://lorempixel.com/100/100/nature/"
+ }
+ }
+ this.setState({ pusher_data: JSON.stringify(data) })
+ }
+ }
+
+ callMopidy(e){
+ e.preventDefault()
+ console.info('Mopidy Debugger', this.state.mopidy_call, JSON.parse(this.state.mopidy_data) )
+ this.props.mopidyActions.debug( this.state.mopidy_call, JSON.parse(this.state.mopidy_data) )
+ }
+
+ callPusher(e){
+ e.preventDefault()
+ console.info('Pusher Debugger', this.state.pusher_call, JSON.parse(this.state.pusher_data) )
+ this.props.pusherActions.debug( this.state.pusher_call, JSON.parse(this.state.pusher_data) )
+ }
+
+ render(){
+ return (
+
+
+
+
+
+
+
+ Pusher
+ this.callPusher(e)}>
+
+
Call
+
+ this.setState({ pusher_call: e.target.value })}
+ value={ this.state.pusher_call } />
+
+
+
+
Data
+
+ this.setState({ pusher_data: e.target.value })}
+ value={ this.state.pusher_data }>
+
+
+
+
+
+
+
+
+
+ Response
+
+ { this.props.ui.debug_response ? JSON.stringify(this.props.ui.debug_response, null, 2) : null }
+
+
+
+ );
+ }
+}
+
+
+/**
+ * Export our component
+ *
+ * We also integrate our global store, using connect()
+ **/
+
+const mapStateToProps = (state, ownProps) => {
+ return state;
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ uiActions: bindActionCreators(uiActions, dispatch),
+ pusherActions: bindActionCreators(pusherActions, dispatch),
+ mopidyActions: bindActionCreators(mopidyActions, dispatch),
+ spotifyActions: bindActionCreators(spotifyActions, dispatch)
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Debug)
\ No newline at end of file
diff --git a/src/js/views/Settings.js b/src/js/views/Settings.js
index c7d549fd..941fd972 100755
--- a/src/js/views/Settings.js
+++ b/src/js/views/Settings.js
@@ -1,7 +1,7 @@
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
-import { Link } from 'react-router'
+import { browserHistory, Link } from 'react-router'
import { bindActionCreators } from 'redux'
import FontAwesome from 'react-fontawesome'
@@ -247,7 +247,17 @@ class Settings extends React.Component{
- this.resetAllSettings()} />
+
+
+
+
+ this.resetAllSettings()} />
+
+ browserHistory.push('debug') }>
+ Debugger
+
+
+
diff --git a/src/scss/global/_core.scss b/src/scss/global/_core.scss
index 793c0ddf..476c4e2e 100755
--- a/src/scss/global/_core.scss
+++ b/src/scss/global/_core.scss
@@ -71,6 +71,11 @@ main {
padding: 10px 0;
}
}
+
+ pre {
+ padding: 20px;
+ background: $faint_grey;
+ }
}
h1 {
diff --git a/src/scss/global/_forms.scss b/src/scss/global/_forms.scss
index 231640d9..e13b8854 100755
--- a/src/scss/global/_forms.scss
+++ b/src/scss/global/_forms.scss
@@ -125,13 +125,15 @@ input[type="submit"] {
width: 85%;
float: left;
- input {
+ input,
+ select,
+ textarea {
width: 100%;
}
.text {
display: block;
- padding: 10px 14px;
+ padding: 10px 14px 10px 0;
}
}
diff --git a/src/scss/views/_settings.scss b/src/scss/views/_settings.scss
index 5406955d..1b947a82 100755
--- a/src/scss/views/_settings.scss
+++ b/src/scss/views/_settings.scss
@@ -47,4 +47,11 @@
padding: 20px;
}
}
+}
+
+
+.debugger-view {
+ section {
+ padding: 40px;
+ }
}
\ No newline at end of file