Overhaul radio UI, fixes #93
This commit is contained in:
@ -260,9 +260,11 @@ class IrisCore(object):
|
||||
|
||||
# no uris means we can't play radio
|
||||
if not uris:
|
||||
self.radio['enabled'] = 0
|
||||
return {
|
||||
'status': 0,
|
||||
'message': 'No recommendations found'
|
||||
'message': 'No recommendations found',
|
||||
'radio': self.radio
|
||||
}
|
||||
|
||||
# if we got recommendations
|
||||
@ -279,6 +281,31 @@ class IrisCore(object):
|
||||
|
||||
return self.get_radio({})
|
||||
|
||||
def update_radio(self, data):
|
||||
self.radio = data
|
||||
self.radio['enabled'] = 1;
|
||||
|
||||
uris = self.load_more_tracks()
|
||||
|
||||
# no uris means we can't play radio
|
||||
if not uris:
|
||||
self.radio['enabled'] = 0
|
||||
return {
|
||||
'status': 0,
|
||||
'message': 'No recommendations found',
|
||||
'radio': self.radio
|
||||
}
|
||||
|
||||
# if we got recommendations
|
||||
else:
|
||||
self.core.tracklist.add( uris = uris )
|
||||
self.broadcast({
|
||||
'type': 'radio_updated',
|
||||
'radio': self.radio
|
||||
})
|
||||
|
||||
return self.get_radio({})
|
||||
|
||||
|
||||
def stop_radio(self, data):
|
||||
self.radio = {
|
||||
|
||||
BIN
src/assets/radio-overlay.png
Executable file
BIN
src/assets/radio-overlay.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@ -69,6 +69,7 @@ class FullPlayer extends React.Component{
|
||||
!this.props.current_track.album.images ){
|
||||
return (
|
||||
<span className="artwork">
|
||||
{this.props.radio_enabled ? <img className="radio-overlay" src="assets/radio-overlay.png" /> : null}
|
||||
<Thumbnail size="huge" />
|
||||
</span>
|
||||
)
|
||||
@ -78,6 +79,7 @@ class FullPlayer extends React.Component{
|
||||
if( this.props.current_track.album.uri ) link = '/album/'+this.props.current_track.album.uri
|
||||
return (
|
||||
<Link className="artwork" to={link} onContextMenu={e => this.handleContextMenu(e,this.props.current_track.album)}>
|
||||
{this.props.radio_enabled ? <img className="radio-overlay" src="assets/radio-overlay.png" /> : null}
|
||||
<Thumbnail size="huge" images={this.props.current_track.album.images} canZoom />
|
||||
</Link>
|
||||
)
|
||||
@ -90,8 +92,10 @@ class FullPlayer extends React.Component{
|
||||
{ this.renderArtwork() }
|
||||
|
||||
<div className="current-track">
|
||||
<div className="title">{ this.props.current_track ? this.props.current_track.name : <span>-</span> }</div>
|
||||
{ this.props.current_track ? <ArtistSentence artists={ this.props.current_track.artists } /> : <ArtistSentence /> }
|
||||
<div className="title">
|
||||
{this.props.current_track ? this.props.current_track.name : <span>-</span>}
|
||||
</div>
|
||||
{this.props.current_track ? <ArtistSentence artists={ this.props.current_track.artists } /> : <ArtistSentence />}
|
||||
</div>
|
||||
|
||||
<div className="controls cf">
|
||||
@ -135,6 +139,7 @@ class FullPlayer extends React.Component{
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
radio_enabled: (state.ui.radio && state.ui.radio.enabled ? true : false),
|
||||
current_track: state.ui.current_track,
|
||||
play_state: state.mopidy.play_state,
|
||||
time_position: state.mopidy.time_position,
|
||||
|
||||
@ -12,7 +12,8 @@ export default class EditRadioModal extends React.Component{
|
||||
this.state = {
|
||||
enabled: false,
|
||||
seeds: [],
|
||||
uri: ''
|
||||
uri: '',
|
||||
error_message: null
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,26 +25,39 @@ export default class EditRadioModal extends React.Component{
|
||||
this.props.spotifyActions.resolveRadioSeeds(this.props.radio)
|
||||
}
|
||||
|
||||
handleSubmit(e){
|
||||
if (this.state.enabled){
|
||||
this.props.pusherActions.startRadio(this.state.seeds)
|
||||
}else{
|
||||
this.props.pusherActions.stopRadio()
|
||||
}
|
||||
handleStart(e){
|
||||
e.preventDefault()
|
||||
this.props.pusherActions.startRadio(this.state.seeds)
|
||||
this.props.uiActions.closeModal()
|
||||
}
|
||||
|
||||
handleUpdate(e){
|
||||
e.preventDefault()
|
||||
this.props.pusherActions.updateRadio(this.state.seeds)
|
||||
this.props.uiActions.closeModal()
|
||||
}
|
||||
|
||||
handleStop(e){
|
||||
e.preventDefault()
|
||||
this.props.pusherActions.stopRadio()
|
||||
this.props.uiActions.closeModal()
|
||||
}
|
||||
|
||||
addSeed(){
|
||||
if (this.state.uri == '') return null
|
||||
if (this.state.uri == ''){
|
||||
this.setState({error_message: 'Cannot be empty'})
|
||||
return null
|
||||
}
|
||||
|
||||
var seeds = Object.assign([],this.state.seeds)
|
||||
var uris = this.state.uri.split(',')
|
||||
|
||||
for (var i = 0; i < uris.length; i++){
|
||||
if (seeds.indexOf(uris[i]) <= -1){
|
||||
seeds.push(uris[i])
|
||||
if (seeds.indexOf(uris[i]) > -1){
|
||||
this.setState({error_message: 'URI already added'})
|
||||
} else {
|
||||
this.props.uiActions.createNotification(uris[i]+' already added','bad')
|
||||
seeds.push(uris[i])
|
||||
this.setState({error_message: null})
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,8 +85,8 @@ export default class EditRadioModal extends React.Component{
|
||||
for (var i = 0; i < this.state.seeds.length; i++){
|
||||
var uri = this.state.seeds[i]
|
||||
if (uri){
|
||||
if (helpers.uriType(uri) == 'artist' && this.props.artists){
|
||||
if (this.props.artists.hasOwnProperty(uri)){
|
||||
if (helpers.uriType(uri) == 'artist'){
|
||||
if (this.props.artists && this.props.artists.hasOwnProperty(uri)){
|
||||
seeds.push(this.props.artists[uri])
|
||||
} else {
|
||||
seeds.push({
|
||||
@ -81,8 +95,8 @@ export default class EditRadioModal extends React.Component{
|
||||
uri: uri
|
||||
})
|
||||
}
|
||||
} else if (helpers.uriType(uri) == 'track' && this.props.tracks){
|
||||
if (this.props.tracks.hasOwnProperty(uri)){
|
||||
} else if (helpers.uriType(uri) == 'track'){
|
||||
if (this.props.tracks && this.props.tracks.hasOwnProperty(uri)){
|
||||
seeds.push(this.props.tracks[uri])
|
||||
} else {
|
||||
seeds.push({
|
||||
@ -105,7 +119,9 @@ export default class EditRadioModal extends React.Component{
|
||||
<div className="list-item" key={seed.uri}>
|
||||
{seed.unresolved ? <span className="grey-text">{seed.uri}</span> : <span>{seed.name}</span> }
|
||||
<span className="grey-text"> ({seed.type})</span>
|
||||
<FontAwesome name="close" className="pull-right destructive" onClick={() => this.removeSeed(seed.uri)} />
|
||||
<button className="discrete remove-uri" onClick={e => this.removeSeed(seed.uri)}>
|
||||
<FontAwesome name="close" /> Remove
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@ -115,41 +131,29 @@ export default class EditRadioModal extends React.Component{
|
||||
)
|
||||
}
|
||||
|
||||
renderAddSeeds(){
|
||||
return (
|
||||
<div className="field no-top-margin">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Comma-separated URIs"
|
||||
onChange={e => this.setState({uri: e.target.value})}
|
||||
value={this.state.uri} />
|
||||
<button type="button" className="discrete" onClick={e => this.addSeed()}><FontAwesome name="plus" /></button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<h4>Edit radio</h4>
|
||||
<h4>Manage radio</h4>
|
||||
|
||||
<form onSubmit={e => this.handleSubmit(e)}>
|
||||
<div className="field checkbox white">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="enabled"
|
||||
checked={ this.state.enabled }
|
||||
onChange={ e => this.setState({ enabled: !this.state.enabled })} />
|
||||
<span className="label">Radio mode enabled</span>
|
||||
</label>
|
||||
<form>
|
||||
{this.renderSeeds()}
|
||||
|
||||
<div className="field no-top-margin">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Comma-separated URIs"
|
||||
onChange={e => this.setState({uri: e.target.value, error_message: null})}
|
||||
value={this.state.uri} />
|
||||
<button className="discrete add-uri" onClick={e => this.addSeed()}>
|
||||
<FontAwesome name="plus" /> Add
|
||||
</button>
|
||||
{this.state.error_message ? <span className="error">{this.state.error_message}</span> : null}
|
||||
</div>
|
||||
|
||||
{this.state.enabled ? this.renderSeeds() : null}
|
||||
{this.state.enabled ? this.renderAddSeeds() : null}
|
||||
|
||||
<div className="actions centered-text">
|
||||
<button type="submit" className="primary wide">Save</button>
|
||||
{this.state.enabled ? <button className="destructive wide" onClick={e => this.handleStop(e)}>Stop</button> : null}
|
||||
{this.state.enabled ? <button className="primary wide" onClick={e => this.handleUpdate(e)}>Save</button> : <button className="primary wide" onClick={e => this.handleStart(e)}>Start</button>}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -76,6 +76,13 @@ export function startRadio( uris ){
|
||||
}
|
||||
}
|
||||
|
||||
export function updateRadio( uris ){
|
||||
return {
|
||||
type: 'PUSHER_UPDATE_RADIO',
|
||||
uris: uris
|
||||
}
|
||||
}
|
||||
|
||||
export function stopRadio(){
|
||||
return {
|
||||
type: 'PUSHER_STOP_RADIO'
|
||||
|
||||
@ -243,7 +243,16 @@ const PusherMiddleware = (function(){
|
||||
break
|
||||
|
||||
case 'PUSHER_START_RADIO':
|
||||
store.dispatch(uiActions.createNotification('Starting radio...'))
|
||||
case 'PUSHER_UPDATE_RADIO':
|
||||
if (action.type == 'PUSHER_UPDATE_RADIO'){
|
||||
var method = 'update_radio'
|
||||
var process_text = 'Updating radio'
|
||||
} else {
|
||||
var method = 'start_radio'
|
||||
var process_text = 'Starting radio'
|
||||
}
|
||||
|
||||
store.dispatch(uiActions.startProcess('PUSHER_RADIO', process_text))
|
||||
|
||||
var data = {
|
||||
seed_artists: [],
|
||||
@ -264,19 +273,11 @@ const PusherMiddleware = (function(){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// we don't need to wait for response, as change will be broadcast
|
||||
request(store, 'start_radio', data)
|
||||
break
|
||||
|
||||
case 'PUSHER_RADIO_STARTED':
|
||||
var data = {
|
||||
type: 'browser_notification',
|
||||
title: 'Radio',
|
||||
body: 'Radio mode started',
|
||||
icon: (store.getState().ui.current_track ? helpers.getTrackIcon( store.getState().ui.current_track ) : false)
|
||||
}
|
||||
store.dispatch( pusherActions.deliverBroadcast(data) )
|
||||
request(store, method, data)
|
||||
.then(response => {
|
||||
store.dispatch(uiActions.stopProcess('PUSHER_RADIO'))
|
||||
})
|
||||
break
|
||||
|
||||
case 'PUSHER_STOP_RADIO':
|
||||
@ -292,22 +293,11 @@ const PusherMiddleware = (function(){
|
||||
request(store, 'stop_radio', data)
|
||||
break
|
||||
|
||||
case 'PUSHER_RADIO_STOPPED':
|
||||
var data = {
|
||||
type: 'browser_notification',
|
||||
title: 'Radio',
|
||||
body: 'Radio mode stopped',
|
||||
icon: (store.getState().ui.current_track ? helpers.getTrackIcon( store.getState().ui.current_track ) : false)
|
||||
}
|
||||
store.dispatch( pusherActions.deliverBroadcast(data) )
|
||||
break
|
||||
|
||||
case 'PUSHER_BROWSER_NOTIFICATION':
|
||||
store.dispatch(uiActions.createBrowserNotification(action))
|
||||
break
|
||||
|
||||
case 'PUSHER_RESTART':
|
||||
|
||||
// Hard reload. This doesn't strictly clear the cache, but our compiler's
|
||||
// cache buster should handle that
|
||||
window.location.reload(true);
|
||||
|
||||
@ -528,13 +528,16 @@ export function resolveRadioSeeds( radio ){
|
||||
artist_ids += helpers.getFromUri('artistid', radio.seed_artists[i])
|
||||
}
|
||||
|
||||
sendRequest( dispatch, getState, 'artists/'+ artist_ids )
|
||||
sendRequest( dispatch, getState, 'artists?ids='+ artist_ids )
|
||||
.then( response => {
|
||||
if (!(response instanceof Array)) response = [response]
|
||||
dispatch({
|
||||
type: 'ARTISTS_LOADED',
|
||||
artists: response
|
||||
})
|
||||
if (response && response.artists){
|
||||
dispatch({
|
||||
type: 'ARTISTS_LOADED',
|
||||
artists: response.artists
|
||||
})
|
||||
} else {
|
||||
console.error('No Spotify artists returned', artist_ids)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -62,6 +62,7 @@ class Queue extends React.Component{
|
||||
<button onClick={e => this.props.uiActions.openModal('edit_radio')}>
|
||||
<FontAwesome name="podcast" />
|
||||
Radio
|
||||
{this.props.radio && this.props.radio.enabled ? <span className="flag blue">On</span> : null}
|
||||
</button>
|
||||
<button onClick={e => hashHistory.push(global.baseURL+'queue-history')}>
|
||||
<FontAwesome name="history" />
|
||||
|
||||
@ -284,6 +284,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.queue-track-list {
|
||||
.list-item {
|
||||
.artists {
|
||||
padding-left: 0 !important;
|
||||
|
||||
&:before {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.source,
|
||||
.added {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.edit-mode {
|
||||
.list-item {
|
||||
padding-left: 40px !important;
|
||||
|
||||
@ -137,21 +137,24 @@
|
||||
.field {
|
||||
position: relative;
|
||||
margin-top: 10px;
|
||||
button {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 0;
|
||||
padding: 14px;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
input[type="text"]{
|
||||
font-size: 14px;
|
||||
padding: 16px 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.add-uri,
|
||||
button.remove-uri {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 0;
|
||||
padding: 14px;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
&.kiosk_mode {
|
||||
|
||||
@ -52,6 +52,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.artwork {
|
||||
position: relative;
|
||||
|
||||
.radio-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&.mini-player {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
||||
@ -270,7 +270,6 @@ footer {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
background: $light_grey;
|
||||
color: $dark_grey;
|
||||
|
||||
|
||||
@ -198,6 +198,15 @@ input[type="submit"] {
|
||||
}
|
||||
}
|
||||
|
||||
.flag {
|
||||
font-size: 10px;
|
||||
margin-left: 3px;
|
||||
padding: 1px 4px;
|
||||
line-height: 1.2em;
|
||||
vertical-align: top;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
@include responsive( $bp_medium ){
|
||||
min-width: 120px;
|
||||
margin-right: 10px;
|
||||
@ -336,6 +345,11 @@ input[type="submit"] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: $red;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@include responsive( $bp_medium ){
|
||||
|
||||
Reference in New Issue
Block a user