diff --git a/mopidy_iris/core.py b/mopidy_iris/core.py
index 7dd705b1..473af375 100755
--- a/mopidy_iris/core.py
+++ b/mopidy_iris/core.py
@@ -374,39 +374,53 @@ class IrisCore(object):
# We only want to play the first batch
added = self.core.tracklist.add(uris = uris[0:3])
+ if (not added.get()):
+ logger.error("No recommendations added to queue")
+
+ self.radio['enabled'] = 0;
+ error = {
+ 'message': 'No recommendations added to queue',
+ 'radio': self.radio
+ }
+ if (callback):
+ callback(False, error)
+ else:
+ return error
+
# Save results (minus first batch) for later use
self.radio['results'] = uris[3:]
- if added.get():
- if starting:
- self.core.playback.play()
- self.broadcast(
- data={
- 'type': 'radio_started',
- 'radio': self.radio
- }
- )
- else:
- self.broadcast(
- data={
- 'type': 'radio_changed',
- 'radio': self.radio
- }
- )
+ if starting:
+ self.core.playback.play()
+ self.broadcast(
+ data={
+ 'type': 'radio_started',
+ 'radio': self.radio
+ }
+ )
+ else:
+ self.broadcast(
+ data={
+ 'type': 'radio_changed',
+ 'radio': self.radio
+ }
+ )
- self.get_radio(callback=callback)
- return
+ self.get_radio(callback=callback)
+ return
- # failed fetching/adding tracks, so no-go
- self.radio['enabled'] = 0;
- error = {
- 'message': 'Could not start radio',
- 'radio': self.radio
- }
- if (callback):
- callback(False, error)
+ # Failed fetching/adding tracks, so no-go
else:
- return error
+ logger.error("No recommendations returned by Spotify")
+ self.radio['enabled'] = 0;
+ error = {
+ 'message': 'Could not start radio',
+ 'radio': self.radio
+ }
+ if (callback):
+ callback(False, error)
+ else:
+ return error
def stop_radio(self, *args, **kwargs):
@@ -442,13 +456,10 @@ class IrisCore(object):
def load_more_tracks(self, *args, **kwargs):
- # this is crude, but it means we don't need to handle expired tokens
- # TODO: address this when it's clear what Jodal and the team want to do with Pyspotify
- self.refresh_spotify_token()
-
try:
- token = self.spotify_token
- token = token['access_token']
+ self.get_spotify_token()
+ spotify_token = self.spotify_token
+ access_token = spotify_token['access_token']
except:
error = 'IrisFrontend: access_token missing or invalid'
logger.error(error)
@@ -462,7 +473,7 @@ class IrisCore(object):
url = url+'&limit=50'
req = urllib2.Request(url)
- req.add_header('Authorization', 'Bearer '+self.spotify_token['access_token'])
+ req.add_header('Authorization', 'Bearer '+access_token)
response = urllib2.urlopen(req, timeout=30).read()
response_dict = json.loads(response)
@@ -556,21 +567,6 @@ class IrisCore(object):
self.queue_metadata = cleaned_queue_metadata
- self.broadcast(
- data={
- 'type': 'queue_metadata_changed',
- 'queue_metadata': self.queue_metadata
- }
- )
-
- response = {
- 'message': 'Cleaned queue metadata'
- }
- if (callback):
- callback(response)
- else:
- return response
-
##
# Spotify authentication
@@ -582,6 +578,11 @@ class IrisCore(object):
def get_spotify_token(self, *args, **kwargs):
callback = kwargs.get('callback', False)
+
+ # Expired, so go get a new one
+ if (not self.spotify_token or self.spotify_token['expires_at'] <= time.time()):
+ self.refresh_spotify_token()
+
response = {
'spotify_token': self.spotify_token
}
@@ -608,6 +609,10 @@ class IrisCore(object):
request = tornado.httpclient.HTTPRequest(url, method='POST', body=urllib.urlencode(data))
response = http_client.fetch(request)
+ token = json.loads(response.body)
+ token['expires_at'] = time.time() + token['expires_in']
+ self.spotify_token = token
+
self.broadcast(
data={
'type': 'spotify_token_changed',
@@ -615,12 +620,9 @@ class IrisCore(object):
}
)
- token = json.loads(response.body)
- self.spotify_token = token
response = {
'spotify_token': token
}
-
if (callback):
callback(response)
else:
diff --git a/mopidy_iris/frontend.py b/mopidy_iris/frontend.py
index 1d35f2e0..6e4e0b90 100755
--- a/mopidy_iris/frontend.py
+++ b/mopidy_iris/frontend.py
@@ -19,9 +19,9 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
def on_start(self):
logger.info('Starting Iris '+mem.iris.version)
- def track_playback_ended( self, tl_track, time_position ):
+ def track_playback_ended(self, tl_track, time_position):
mem.iris.check_for_radio_update()
- def tracklist_changed( self ):
+ def tracklist_changed(self):
mem.iris.clean_queue_metadata()
\ No newline at end of file
diff --git a/src/js/components/List.js b/src/js/components/List.js
index a4eeb98a..672a1a55 100755
--- a/src/js/components/List.js
+++ b/src/js/components/List.js
@@ -39,12 +39,14 @@ class List extends React.Component{
return (
- {
- this.props.columns.map((col, col_index) => {
- var className = 'col '+col.name.replace('.','_')
- return
{ col.label ? col.label : col.name }
- })
- }
+
+ {
+ this.props.columns.map((col, col_index) => {
+ var className = 'col '+col.name.replace('.','_')
+ return
{ col.label ? col.label : col.name }
+ })
+ }
+
)
}
@@ -54,7 +56,7 @@ class List extends React.Component{
var value = row
for (var i = 0; i < key.length; i++){
- if (typeof(value[key[i]]) === 'undefined'){
+ if (value[key[i]] === undefined){
return -
} else if (typeof(value[key[i]]) === 'string' && value[key[i]].replace(' ','') == ''){
return -
diff --git a/src/js/components/Modal/EditRadioModal.js b/src/js/components/Modal/EditRadioModal.js
index 9bea050e..8ad914c6 100755
--- a/src/js/components/Modal/EditRadioModal.js
+++ b/src/js/components/Modal/EditRadioModal.js
@@ -26,15 +26,43 @@ export default class EditRadioModal extends React.Component{
}
handleStart(e){
- e.preventDefault()
- this.props.pusherActions.startRadio(this.state.seeds)
- this.props.uiActions.closeModal()
+ e.preventDefault();
+
+ var valid_seeds = true;
+ var seeds = this.mapSeeds();
+ for (var i = 0; i < seeds.length; i++){
+ if (seeds[i].unresolved !== undefined){
+ valid_seeds = false;
+ continue;
+ }
+ }
+
+ if (valid_seeds){
+ this.props.pusherActions.startRadio(this.state.seeds);
+ this.props.uiActions.closeModal();
+ } else {
+ this.setState({error_message: "Invalid seed URI(s)"});
+ }
}
handleUpdate(e){
- e.preventDefault()
- this.props.pusherActions.updateRadio(this.state.seeds)
- this.props.uiActions.closeModal()
+ e.preventDefault();
+
+ var valid_seeds = true;
+ var seeds = this.mapSeeds();
+ for (var i = 0; i < seeds.length; i++){
+ if (seeds[i].unresolved !== undefined){
+ valid_seeds = false;
+ continue;
+ }
+ }
+
+ if (valid_seeds){
+ this.props.pusherActions.updateRadio(this.state.seeds);
+ this.props.uiActions.closeModal();
+ } else {
+ this.setState({error_message: "Invalid seed URI(s)"});
+ }
}
handleStop(e){
@@ -46,7 +74,7 @@ export default class EditRadioModal extends React.Component{
addSeed(){
if (this.state.uri == ''){
this.setState({error_message: 'Cannot be empty'});
- return null;
+ return;
}
var seeds = Object.assign([],this.state.seeds);
@@ -62,7 +90,18 @@ export default class EditRadioModal extends React.Component{
} else {
seeds.push(uris[i]);
this.setState({error_message: null});
- }
+ }
+
+ // Resolve
+ switch (helpers.uriType(uris[i])){
+ case 'track':
+ this.props.spotifyActions.getTrack(uris[i]);
+ break;
+
+ case 'artist':
+ this.props.spotifyActions.getArtist(uris[i]);
+ break;
+ }
}
// commit to state
@@ -79,10 +118,10 @@ export default class EditRadioModal extends React.Component{
seeds.push(this.state.seeds[i])
}
}
- this.setState({seeds: seeds})
+ this.setState({seeds: seeds});
}
- renderSeeds(){
+ mapSeeds(){
var seeds = []
if (this.state.seeds){
@@ -91,20 +130,18 @@ export default class EditRadioModal extends React.Component{
if (uri){
if (helpers.uriType(uri) == 'artist'){
if (this.props.artists && this.props.artists.hasOwnProperty(uri)){
- seeds.push(this.props.artists[uri])
+ seeds.push(this.props.artists[uri]);
} else {
seeds.push({
- type: 'artist',
unresolved: true,
uri: uri
})
}
} else if (helpers.uriType(uri) == 'track'){
if (this.props.tracks && this.props.tracks.hasOwnProperty(uri)){
- seeds.push(this.props.tracks[uri])
+ seeds.push(this.props.tracks[uri]);
} else {
seeds.push({
- type: 'track',
unresolved: true,
uri: uri
})
@@ -114,6 +151,12 @@ export default class EditRadioModal extends React.Component{
}
}
+ return seeds;
+ }
+
+ renderSeeds(){
+ var seeds = this.mapSeeds();
+
if (seeds.length > 0){
return (
@@ -123,7 +166,7 @@ export default class EditRadioModal extends React.Component{
return (
{seed.unresolved ?
{seed.uri} :
{seed.name} }
-
({seed.type})
+ {!seed.unresolved ?
({seed.type}) : null}
diff --git a/src/js/components/Track.js b/src/js/components/Track.js
index 021426f7..6b250463 100755
--- a/src/js/components/Track.js
+++ b/src/js/components/Track.js
@@ -262,7 +262,9 @@ export default class Track extends React.Component{
return (
{track_actions}
- {track_columns}
+
+ {track_columns}
+
)
} else {
diff --git a/src/js/services/pusher/middleware.js b/src/js/services/pusher/middleware.js
index e4472c22..99a5a3d8 100755
--- a/src/js/services/pusher/middleware.js
+++ b/src/js/services/pusher/middleware.js
@@ -83,7 +83,7 @@ const PusherMiddleware = (function(){
store.dispatch(uiActions.stopLoading(request_id));
reject({message: "Request timed out", method: method, data: data});
},
- 5000 // 30000
+ 30000
);
// add query to our deferred responses
@@ -379,12 +379,13 @@ const PusherMiddleware = (function(){
request(store, 'change_radio', data)
.then(
response => {
+ store.dispatch(uiActions.processFinished('PUSHER_RADIO_PROCESS'));
if (response.status == 0){
- store.dispatch(uiActions.createNotification(response.message, 'bad'))
+ store.dispatch(uiActions.createNotification(response.message, 'bad'));
}
- store.dispatch(uiActions.processFinished('PUSHER_RADIO_PROCESS'))
},
- error => {
+ error => {
+ store.dispatch(uiActions.processFinished('PUSHER_RADIO_PROCESS'));
store.dispatch(coreActions.handleException(
'Could not change radio',
error
diff --git a/src/js/views/Settings.js b/src/js/views/Settings.js
index 3ee2b728..1a650d28 100755
--- a/src/js/views/Settings.js
+++ b/src/js/views/Settings.js
@@ -466,7 +466,7 @@ class Settings extends React.Component {
GitHub
-

+
Licence
diff --git a/src/scss/components/_lists.scss b/src/scss/components/_lists.scss
index 5aa41371..25e1764a 100755
--- a/src/scss/components/_lists.scss
+++ b/src/scss/components/_lists.scss
@@ -1,7 +1,6 @@
.list {
.list-item {
- @include clearfix;
-webkit-touch-callout: none;
-webkit-user-select: none;
@@ -12,13 +11,17 @@
display: block;
position: relative;
- padding: 14px 30px 13px 10px;
margin: 0 -10px -1px -10px;
cursor: pointer;
border-radius: 3px;
border-bottom: 1px solid rgba(255,255,255,0.05);
border-top: 1px solid rgba(255,255,255,0.05);
+ .liner {
+ @include clearfix;
+ padding: 14px 30px 13px 10px;
+ }
+
&.selected {
background: rgba(255,255,255,0.08) !important;
@@ -229,11 +232,11 @@
.list-item {
&.can-sort {
- padding-left: 70px !important;
+ padding-left: 60px !important;
}
&:not(.can-sort){
- padding-left: 42px !important;
+ padding-left: 35px !important;
}
.select-zone {
@@ -247,8 +250,9 @@
.fa {
position: absolute;
- top: 27px;
+ top: 50%;
left: 17px;
+ margin-top: -3px;
pointer-events: none;
color: $white;
z-index: 1;
@@ -262,8 +266,9 @@
width: 14px;
height: 14px;
position: absolute;
- top: 23px;
+ top: 50%;
left: 14px;
+ margin-top: -7px;
}
}
@@ -279,8 +284,9 @@
.fa {
position: absolute;
- top: 24px;
+ top: 50%;
left: 6px;
+ margin-top: -6px;
pointer-events: none;
}
}
@@ -290,7 +296,10 @@
@include responsive($bp_medium){
.list-item {
- padding: 12px !important;
+
+ .liner {
+ padding: 12px !important;
+ }
.source {
position: static;