Edit radio popup; Capture errors and broadcast via pusher
This commit is contained in:
@ -98,6 +98,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
|
||||
self.load_more_tracks()
|
||||
|
||||
except RuntimeError:
|
||||
pusher.broadcast('error', {'source': 'check_for_radio_update', 'message': 'Could not fetch tracklist length'})
|
||||
logger.warning('IrisFrontend: Could not fetch tracklist length')
|
||||
pass
|
||||
|
||||
@ -118,6 +119,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
|
||||
token = token['access_token']
|
||||
except:
|
||||
logger.error('IrisFrontend: access_token missing or invalid')
|
||||
pusher.broadcast('error', {'source': 'load_more_tracks', 'message': 'access_token missing or invalid'})
|
||||
|
||||
try:
|
||||
spotify = Spotify( auth = token )
|
||||
@ -129,7 +131,8 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
|
||||
|
||||
self.core.tracklist.add( uris = uris )
|
||||
except:
|
||||
logger.error('IrisFrontend: Failed to fetch recommendations from Spotify')
|
||||
pusher.broadcast('error', {'source': 'load_more_tracks', 'message': 'Failed to fetch Spotify recommendations'})
|
||||
logger.error('IrisFrontend: Failed to fetch Spotify recommendations')
|
||||
|
||||
|
||||
##
|
||||
@ -154,7 +157,7 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener):
|
||||
self.core.playback.play()
|
||||
|
||||
# notify clients
|
||||
pusher.broadcast( 'radio', { 'radio': self.radio })
|
||||
pusher.broadcast('radio', { 'radio': self.radio })
|
||||
|
||||
# return new radio state to initial call
|
||||
return self.radio
|
||||
|
||||
@ -27,11 +27,11 @@ export default class AddToPlaylistModal extends React.Component{
|
||||
return (
|
||||
<div>
|
||||
<h4>Add to playlist</h4>
|
||||
<div className="list playlists">
|
||||
<div className="list small playlists">
|
||||
{
|
||||
playlists.map( playlist => {
|
||||
return (
|
||||
<div className="list-item small" key={playlist.uri} onClick={ () => this.playlistSelected(playlist.uri) }>
|
||||
<div className="list-item" key={playlist.uri} onClick={ () => this.playlistSelected(playlist.uri) }>
|
||||
<FontAwesome className="source" name={helpers.sourceIcon(playlist.uri)} />
|
||||
|
||||
<span className="name">{ playlist.name }</span>
|
||||
|
||||
@ -10,31 +10,67 @@ export default class EditRadioModal extends React.Component{
|
||||
constructor(props){
|
||||
super(props)
|
||||
this.state = {
|
||||
seed_tracks: [],
|
||||
seed_genres: [],
|
||||
seed_artists: [],
|
||||
uri: ''
|
||||
enabled: false,
|
||||
seeds: [],
|
||||
uri: '',
|
||||
uri_validated: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.setState(this.props.radio)
|
||||
if (!this.props.radio || !this.props.radio.enabled) return null
|
||||
var seeds = [...this.props.radio.seed_tracks, ...this.props.radio.seed_artists, ...this.props.radio.seed_genres]
|
||||
this.setState({seeds: seeds, enabled: this.props.radio.enabled})
|
||||
}
|
||||
|
||||
handleChange(uri){
|
||||
this.setState({uri: uri})
|
||||
|
||||
var allowed_types = ['artist','track']
|
||||
if (allowed_types.indexOf(helpers.uriType(uri)) > -1){
|
||||
this.setState({uri_validated: true})
|
||||
}
|
||||
}
|
||||
|
||||
save(){
|
||||
this.props.pusherActions.startRadio(this.state.seeds)
|
||||
this.props.uiActions.closeModal()
|
||||
}
|
||||
|
||||
addSeed(seed){
|
||||
this.setState({uri: ''})
|
||||
stop(){
|
||||
this.props.pusherActions.stopRadio()
|
||||
this.props.uiActions.closeModal()
|
||||
}
|
||||
|
||||
removeSeed(type,seed){
|
||||
console.log('remove', type, seed)
|
||||
addSeed(){
|
||||
if (!this.state.uri_validated || this.state.uri == '') return null
|
||||
|
||||
var seeds = Object.assign([],this.state.seeds)
|
||||
|
||||
if (seeds.indexOf(this.state.uri) <= -1){
|
||||
seeds.push(this.state.uri)
|
||||
} else {
|
||||
this.props.uiActions.createNotification('Seed already exists','bad')
|
||||
}
|
||||
|
||||
// commit to state
|
||||
this.setState({
|
||||
seeds: seeds,
|
||||
uri: '',
|
||||
uri_validated: false
|
||||
})
|
||||
}
|
||||
|
||||
removeSeed(uri){
|
||||
var seeds = Object.assign([],this.state.seeds)
|
||||
var index = seeds.indexOf(uri)
|
||||
if (index > -1){
|
||||
delete seeds[index]
|
||||
this.setState({seeds: seeds})
|
||||
}
|
||||
}
|
||||
|
||||
renderSeeds(){
|
||||
if (!this.props.radio || !this.props.radio.enabled) return null
|
||||
/*
|
||||
var seeds = this.props.radio.resolved_seeds
|
||||
var uri
|
||||
@ -56,31 +92,11 @@ export default class EditRadioModal extends React.Component{
|
||||
return (
|
||||
<div className="list">
|
||||
{
|
||||
this.state.seed_tracks.map(seed => {
|
||||
this.state.seeds.map((seed,index) => {
|
||||
return (
|
||||
<div className="list-item" key={seed}>
|
||||
<div className="list-item" key={index+'_'+seed}>
|
||||
{seed}
|
||||
<FontAwesome name="close" className="pull-right destructive" onClick={() => this.removeSeed('track',seed)} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
{
|
||||
this.state.seed_genres.map(seed => {
|
||||
return (
|
||||
<div className="list-item" key={seed}>
|
||||
{seed}
|
||||
<FontAwesome name="close" className="pull-right destructive" onClick={() => this.removeSeed('genre',seed)} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
{
|
||||
this.state.seed_artists.map(seed => {
|
||||
return (
|
||||
<div className="list-item" key={seed}>
|
||||
{seed}
|
||||
<FontAwesome name="close" className="pull-right destructive" onClick={() => this.removeSeed('artist',seed)} />
|
||||
<FontAwesome name="close" className="pull-right destructive" onClick={() => this.removeSeed(seed)} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@ -89,21 +105,43 @@ export default class EditRadioModal extends React.Component{
|
||||
)
|
||||
}
|
||||
|
||||
renderActions(){
|
||||
if (this.state.enabled){
|
||||
return (
|
||||
<span>
|
||||
<button className="primary wide" onClick={e => this.save()}>Save</button>
|
||||
<button className="destructive wide" onClick={e => this.stop()}>Stop</button>
|
||||
</span>
|
||||
)
|
||||
}else{
|
||||
return (
|
||||
<span>
|
||||
<button className="primary wide" onClick={e => this.save()}>Start</button>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<h4>Edit radio</h4>
|
||||
<form>
|
||||
{this.renderSeeds()}
|
||||
|
||||
<div className="field">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="URI"
|
||||
onChange={e => this.setState({uri: e.target.value})}
|
||||
placeholder="Spotify URI"
|
||||
onChange={e => this.handleChange(e.target.value)}
|
||||
value={this.state.uri} />
|
||||
<button disabled={!this.state.uri_validated} onClick={e => this.addSeed()}>Add</button>
|
||||
</div>
|
||||
|
||||
{this.renderSeeds()}
|
||||
|
||||
<div className="actions centered-text">
|
||||
{this.renderActions()}
|
||||
</div>
|
||||
<button className="secondary" onClick={e => this.addSeed(this.state.uri)}>Add</button>
|
||||
<button type="submit" className="primary" onClick={e => this.save()}>Save and close</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -27,7 +27,7 @@ class SendAuthorizationModal extends React.Component{
|
||||
<div>
|
||||
<h4 className="no-bottom-padding">Share Spotify authentication</h4>
|
||||
<h3 className="grey-text">Send your authentication tokens to another client. When the recipient client imports this, their Iris will have full access to your Spotify account ({this.props.me.id}).</h3>
|
||||
<div className="list pusher-connection-list">
|
||||
<div className="list small pusher-connection-list">
|
||||
{
|
||||
this.props.connections.map( (connection, index) => {
|
||||
|
||||
@ -35,7 +35,7 @@ class SendAuthorizationModal extends React.Component{
|
||||
if (connection.connectionid == this.props.connectionid) return null
|
||||
|
||||
return (
|
||||
<div className='list-item small connection' key={connection.connectionid} onClick={ e => this.handleClick(e, connection.connectionid) }>
|
||||
<div className='list-item connection' key={connection.connectionid} onClick={ e => this.handleClick(e, connection.connectionid) }>
|
||||
{ connection.username }
|
||||
|
||||
<span className="grey-text">({ connection.ip })</span>
|
||||
|
||||
@ -128,6 +128,10 @@ const PusherMiddleware = (function(){
|
||||
return next(action);
|
||||
break;
|
||||
|
||||
case 'ERROR':
|
||||
store.dispatch( uiActions.createNotification(action.data.source+': '+action.data.message,'bad') )
|
||||
break;
|
||||
|
||||
case 'START_UPGRADE':
|
||||
request({ action: 'upgrade' })
|
||||
.then(
|
||||
@ -233,6 +237,9 @@ const PusherMiddleware = (function(){
|
||||
case 'track':
|
||||
data.seed_tracks.push( action.uris[i] );
|
||||
break;
|
||||
case 'genre':
|
||||
data.seed_genres.push( action.uris[i] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,7 +263,6 @@ const PusherMiddleware = (function(){
|
||||
}
|
||||
)
|
||||
|
||||
store.dispatch(uiActions.createNotification('Stopping radio'))
|
||||
var data = {
|
||||
action: 'stop_radio',
|
||||
seed_artists: [],
|
||||
|
||||
@ -57,27 +57,18 @@ class Queue extends React.Component{
|
||||
|
||||
render(){
|
||||
var actions = (
|
||||
<button onClick={() => this.props.mopidyActions.clearTracklist()}>
|
||||
<FontAwesome name="trash" />
|
||||
Clear
|
||||
</button>
|
||||
<span>
|
||||
<button onClick={() => this.props.uiActions.openModal('edit_radio')}>
|
||||
<FontAwesome name="spotify" />
|
||||
Radio
|
||||
</button>
|
||||
<button onClick={() => this.props.mopidyActions.clearTracklist()}>
|
||||
<FontAwesome name="trash" />
|
||||
Clear
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
|
||||
if (this.props.radio && this.props.radio.enabled){
|
||||
var actions = (
|
||||
<span>
|
||||
<button onClick={() => this.props.uiActions.openModal('edit_radio')}>
|
||||
<FontAwesome name="pencil" />
|
||||
Edit
|
||||
</button>
|
||||
<button onClick={() => this.props.pusherActions.stopRadio()}>
|
||||
<FontAwesome name="trash" />
|
||||
Stop
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="view queue-view">
|
||||
<Header icon="play" title="Now playing" actions={actions} />
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
|
||||
.list {
|
||||
@include clearfix();
|
||||
padding-top: 30px;
|
||||
|
||||
.list-item {
|
||||
box-sizing: border-box;
|
||||
@ -81,20 +82,6 @@
|
||||
border-top: 1px solid $mid_grey;
|
||||
}
|
||||
|
||||
&.small {
|
||||
padding-left: 30px;
|
||||
width: 49%;
|
||||
float: left;
|
||||
|
||||
&:nth-child(2){
|
||||
border-top: 1px solid $mid_grey;
|
||||
}
|
||||
|
||||
&:nth-child(2n){
|
||||
margin-left: 2%;
|
||||
}
|
||||
}
|
||||
|
||||
.source {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
@ -107,8 +94,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.pusher-connection-list {
|
||||
padding-top: 50px;
|
||||
&.playlists {
|
||||
padding-top: 0;
|
||||
|
||||
.list-item {
|
||||
padding-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
&.small .list-item {
|
||||
width: 49%;
|
||||
float: left;
|
||||
|
||||
&:nth-child(2){
|
||||
border-top: 1px solid $mid_grey;
|
||||
}
|
||||
|
||||
&:nth-child(2n){
|
||||
margin-left: 2%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -127,6 +131,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.edit_radio {
|
||||
.field {
|
||||
position: relative;
|
||||
button {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding-top: 50px;
|
||||
text-align: center;
|
||||
|
||||
.button,
|
||||
button {
|
||||
margin: 0 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@include responsive( $bp_medium ){
|
||||
|
||||
.content {
|
||||
|
||||
@ -166,6 +166,7 @@ h4 {
|
||||
.red-text { color: $red !important; }
|
||||
.green-text { color: $green !important; }
|
||||
.dark-text { color: $dark_grey !important; }
|
||||
.centred-text { text-align: center; }
|
||||
|
||||
footer {
|
||||
display: block;
|
||||
|
||||
@ -96,17 +96,16 @@ input[type="submit"] {
|
||||
}
|
||||
}
|
||||
|
||||
&.centered {
|
||||
display: block;
|
||||
&.wide {
|
||||
display: inline-block;
|
||||
min-width: 200px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
&[disabled="disabled"] {
|
||||
&[disabled="disabled"]{
|
||||
background: rgba(200,200,200,0.4) !important;
|
||||
color: $mid_grey !important;
|
||||
cursor: not-allowed !important;
|
||||
|
||||
Reference in New Issue
Block a user