Edit radio popup; Capture errors and broadcast via pusher

This commit is contained in:
James Barnsley
2017-01-31 08:44:12 +13:00
parent 53e9a456f8
commit cab4218da2
9 changed files with 147 additions and 84 deletions

View File

@ -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

View File

@ -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)} />
&nbsp;
<span className="name">{ playlist.name }</span>

View File

@ -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>
)

View File

@ -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 }
&nbsp;
<span className="grey-text">({ connection.ip })</span>

View File

@ -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: [],

View File

@ -57,27 +57,18 @@ class Queue extends React.Component{
render(){
var actions = (
<button onClick={() => this.props.mopidyActions.clearTracklist()}>
<FontAwesome name="trash" />&nbsp;
Clear
</button>
<span>
<button onClick={() => this.props.uiActions.openModal('edit_radio')}>
<FontAwesome name="spotify" />&nbsp;
Radio
</button>
<button onClick={() => this.props.mopidyActions.clearTracklist()}>
<FontAwesome name="trash" />&nbsp;
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" />&nbsp;
Edit
</button>
<button onClick={() => this.props.pusherActions.stopRadio()}>
<FontAwesome name="trash" />&nbsp;
Stop
</button>
</span>
)
}
return (
<div className="view queue-view">
<Header icon="play" title="Now playing" actions={actions} />

View File

@ -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 {

View File

@ -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;

View File

@ -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;