Mergingcurrent_track and current_tracklist; Smarter reducers for UI
This commit is contained in:
7
src/js/bootstrap.js
vendored
7
src/js/bootstrap.js
vendored
@ -29,7 +29,8 @@ var initialState = {
|
||||
host: window.location.hostname,
|
||||
port: 6680,
|
||||
volume: 0,
|
||||
progress: 0
|
||||
progress: 0,
|
||||
play_state: false
|
||||
},
|
||||
pusher: {
|
||||
connected: false,
|
||||
@ -54,7 +55,9 @@ var initialState = {
|
||||
ui: {
|
||||
context_menu: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
current_tracklist: [],
|
||||
current_tltrack: false
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
|
||||
import TrackList from './TrackList'
|
||||
import * as spotifyActions from '../services/spotify/actions'
|
||||
|
||||
class Artist extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
// on render
|
||||
componentDidMount(){
|
||||
this.props.spotifyActions.loadArtist( this.props.params.uri );
|
||||
}
|
||||
|
||||
// when props changed
|
||||
componentWillReceiveProps( nextProps ){
|
||||
if( nextProps.params.uri != this.props.params.uri ){
|
||||
this.props.spotifyActions.loadArtist( nextProps.params.uri );
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
if( this.props.spotify.artist ){
|
||||
return (
|
||||
<div>
|
||||
<h4>{ this.props.spotify.artist.name }</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export our component
|
||||
*
|
||||
* We also integrate our global store, using connect()
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Artist)
|
||||
@ -20,7 +20,7 @@ class ContextMenu extends React.Component{
|
||||
renderItems(){
|
||||
var items = [];
|
||||
|
||||
switch( this.props.state.context ){
|
||||
switch( this.props.context_menu.context ){
|
||||
|
||||
case 'queue':
|
||||
items = [
|
||||
@ -57,56 +57,61 @@ class ContextMenu extends React.Component{
|
||||
}
|
||||
|
||||
playQueueItem(){
|
||||
var selectedTracks = this.props.state.data.selected_tracks;
|
||||
var selectedTracks = this.props.context_menu.data.selected_tracks;
|
||||
this.props.mopidyActions.changeTrack( selectedTracks[0].tlid );
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
removeFromQueue(){
|
||||
var selectedTracks = this.props.state.data.selected_tracks;
|
||||
var selectedTracksTlids = [];
|
||||
for( var i = 0; i < selectedTracks.length; i++ ){
|
||||
selectedTracksTlids.push( selectedTracks[i].tlid );
|
||||
var selected_tracks = this.props.context_menu.data.selected_tracks;
|
||||
var selected_tracks_tlids = [];
|
||||
for( var i = 0; i < selected_tracks.length; i++ ){
|
||||
selected_tracks_tlids.push( selected_tracks[i].tlid );
|
||||
}
|
||||
this.props.mopidyActions.removeTracks( selectedTracksTlids );
|
||||
this.props.mopidyActions.removeTracks( selected_tracks_tlids );
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
playItems(){
|
||||
var selectedTracks = this.props.state.data.selected_tracks;
|
||||
var selectedTracksUris = [];
|
||||
for( var i = 0; i < selectedTracks.length; i++ ){
|
||||
selectedTracksUris.push( selectedTracks[i].uri );
|
||||
var selected_tracks = this.props.context_menu.data.selected_tracks;
|
||||
var selected_tracks_uris = [];
|
||||
for( var i = 0; i < selected_tracks.length; i++ ){
|
||||
selected_tracks_uris.push( selected_tracks[i].uri );
|
||||
}
|
||||
this.props.mopidyActions.playTracks(selectedTracksUris);
|
||||
this.props.mopidyActions.playTracks(selected_tracks_uris);
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
playItemsNext(){
|
||||
var selectedTracks = this.props.state.data.selected_tracks;
|
||||
var selectedTracksUris = [];
|
||||
for( var i = 0; i < selectedTracks.length; i++ ){
|
||||
selectedTracksUris.push( selectedTracks[i].uri );
|
||||
var selected_tracks = this.props.context_menu.data.selected_tracks;
|
||||
var selected_tracks_uris = [];
|
||||
for( var i = 0; i < selected_tracks.length; i++ ){
|
||||
selected_tracks_uris.push( selected_tracks[i].uri );
|
||||
}
|
||||
|
||||
var current_tltrack = this.props.mopidy.current_tltrack;
|
||||
function isCurrentTlid( tltrack ){
|
||||
return ( tltrack.tlid == current_tltrack.tlid );
|
||||
var current_track = this.props.current_track
|
||||
var current_track_index = -1
|
||||
for( var i = 0; i < this.props.current_tracklist.length; i++ ){
|
||||
if( this.props.current_tracklist[i].tlid == this.props.current_track.tlid ){
|
||||
current_track_index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
var currentTrack = this.props.mopidy.tracks.find( isCurrentTlid );
|
||||
var currentTrackIndex = this.props.mopidy.tracks.indexOf( currentTrack );
|
||||
|
||||
this.props.mopidyActions.enqueueTracks(selectedTracksUris, currentTrackIndex+1);
|
||||
var at_position = null
|
||||
if( current_track_index > -1 ) at_position = current_track_index + 1
|
||||
|
||||
this.props.mopidyActions.enqueueTracks(selected_tracks_uris, at_position);
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
addToQueue(){
|
||||
var selectedTracks = this.props.state.data.selected_tracks;
|
||||
var selectedTracksUris = [];
|
||||
for( var i = 0; i < selectedTracks.length; i++ ){
|
||||
selectedTracksUris.push( selectedTracks[i].uri );
|
||||
var selected_tracks = this.props.context_menu.data.selected_tracks;
|
||||
var selected_tracks_uris = [];
|
||||
for( var i = 0; i < selected_tracks.length; i++ ){
|
||||
selected_tracks_uris.push( selected_tracks[i].uri );
|
||||
}
|
||||
this.props.mopidyActions.enqueueTracks(selectedTracksUris);
|
||||
this.props.mopidyActions.enqueueTracks(selected_tracks_uris);
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
@ -116,11 +121,11 @@ class ContextMenu extends React.Component{
|
||||
}
|
||||
|
||||
render(){
|
||||
if( !this.props.state.show ) return null;
|
||||
if( !this.props.context_menu.show ) return null;
|
||||
|
||||
var style = {
|
||||
left: this.props.state.position_x,
|
||||
top: this.props.state.position_y,
|
||||
left: this.props.context_menu.position_x,
|
||||
top: this.props.context_menu.position_y,
|
||||
}
|
||||
|
||||
return (
|
||||
@ -132,7 +137,11 @@ class ContextMenu extends React.Component{
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
return {
|
||||
context_menu: state.ui.context_menu,
|
||||
current_track: state.ui.current_track,
|
||||
current_tracklist: state.ui.current_tracklist
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
|
||||
@ -19,7 +19,7 @@ class FullPlayer extends React.Component{
|
||||
|
||||
renderPlayButton(){
|
||||
var button = <a onClick={() => this.props.mopidyActions.play()}><FontAwesome name="play" /> </a>
|
||||
if( this.props.mopidy.state == 'playing' ){
|
||||
if( this.props.play_state == 'playing' ){
|
||||
button = <a onClick={() => this.props.mopidyActions.pause()}><FontAwesome name="pause" /> </a>
|
||||
}
|
||||
return button;
|
||||
@ -27,7 +27,7 @@ class FullPlayer extends React.Component{
|
||||
|
||||
renderConsumeButton(){
|
||||
var button = <a onClick={() => this.props.mopidyActions.instruct('tracklist.setConsume', [true])}><FontAwesome name="fire" /></a>
|
||||
if( this.props.mopidy.consume ){
|
||||
if( this.props.consume ){
|
||||
button = <a className="active" onClick={() => this.props.mopidyActions.instruct('tracklist.setConsume', [false])}><FontAwesome name="fire" /></a>
|
||||
}
|
||||
return button;
|
||||
@ -35,7 +35,7 @@ class FullPlayer extends React.Component{
|
||||
|
||||
renderRandomButton(){
|
||||
var button = <a onClick={() => this.props.mopidyActions.instruct('tracklist.setRandom', [true])}><FontAwesome name="random" /></a>
|
||||
if( this.props.mopidy.random ){
|
||||
if( this.props.random ){
|
||||
button = <a className="active" onClick={() => this.props.mopidyActions.instruct('tracklist.setRandom', [false])}><FontAwesome name="random" /></a>
|
||||
}
|
||||
return button;
|
||||
@ -43,52 +43,42 @@ class FullPlayer extends React.Component{
|
||||
|
||||
renderRepeatButton(){
|
||||
var button = <a onClick={() => this.props.mopidyActions.instruct('tracklist.setRepeat', [true])}><FontAwesome name="repeat" /></a>
|
||||
if( this.props.mopidy.repeat ){
|
||||
if( this.props.repeat ){
|
||||
button = <a className="active" onClick={() => this.props.mopidyActions.instruct('tracklist.setRepeat', [false])}><FontAwesome name="repeat" /></a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
renderArtwork(){
|
||||
if( this.props.spotify.track ){
|
||||
return (
|
||||
<Link className="artwork" to={'/album/'+this.props.spotify.track.album.uri}>
|
||||
<Thumbnail size="huge" images={this.props.spotify.track.album.images} />
|
||||
</Link>
|
||||
)
|
||||
}else if(
|
||||
this.props.mopidy.current_tltrack &&
|
||||
this.props.mopidy.current_tltrack.track &&
|
||||
this.props.mopidy.current_tltrack.track.album &&
|
||||
this.props.mopidy.current_tltrack.track.album.images ){
|
||||
return (
|
||||
<span className="artwork">
|
||||
<Thumbnail size="huge" images={mopidy_track.track.album.images} />
|
||||
</span>
|
||||
)
|
||||
if(
|
||||
!this.props.current_track ||
|
||||
!this.props.current_track.album ||
|
||||
!this.props.current_track.album.images ){
|
||||
return (
|
||||
<span className="artwork">
|
||||
<Thumbnail size="huge" images={[]} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: get artwork from lastFM
|
||||
|
||||
return <span className="artwork"><Thumbnail size="huge" images={[]} /></span>
|
||||
var link = null
|
||||
if( this.props.current_track.album.uri ) link = '/album/'+this.props.current_track.album.uri
|
||||
return (
|
||||
<Link className="artwork" to={link}>
|
||||
<Thumbnail size="huge" images={this.props.current_track.album.images} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
render(){
|
||||
var mopidy_track = false;
|
||||
var images = [];
|
||||
if( typeof(this.props.mopidy.current_tltrack) !== 'undefined' && typeof(this.props.mopidy.current_tltrack.track) !== 'undefined' ){
|
||||
mopidy_track = this.props.mopidy.current_tltrack;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="player">
|
||||
|
||||
{ this.renderArtwork() }
|
||||
|
||||
<div className="current-track">
|
||||
<div className="title">{ mopidy_track ? mopidy_track.track.name : null }</div>
|
||||
{ mopidy_track ? <ArtistSentence artists={ mopidy_track.track.artists } /> : null }
|
||||
<div className="title">{ this.props.current_track ? this.props.current_track.name : null }</div>
|
||||
{ this.props.current_track ? <ArtistSentence artists={ this.props.current_track.artists } /> : null }
|
||||
</div>
|
||||
|
||||
<div className="controls cf">
|
||||
@ -126,7 +116,13 @@ class FullPlayer extends React.Component{
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
return {
|
||||
current_track: state.ui.current_track,
|
||||
play_state: state.mopidy.play_state,
|
||||
consume: state.mopidy.consume,
|
||||
repeat: state.mopidy.repeat,
|
||||
random: state.mopidy.random
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
|
||||
@ -20,22 +20,19 @@ class MiniPlayer extends React.Component{
|
||||
|
||||
renderPlayButton(){
|
||||
var button = <a onClick={() => this.props.mopidyActions.play()}><FontAwesome name="play" /> </a>
|
||||
if( this.props.mopidy.state == 'playing' ){
|
||||
if( this.props.play_state == 'playing' ){
|
||||
button = <a onClick={() => this.props.mopidyActions.pause()}><FontAwesome name="pause" /> </a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
render(){
|
||||
var mopidy_track = false;
|
||||
if( typeof(this.props.mopidy.current_tltrack) !== 'undefined' && typeof(this.props.mopidy.current_tltrack.track) !== 'undefined' ) mopidy_track = this.props.mopidy.current_tltrack;
|
||||
|
||||
return (
|
||||
<div className="player">
|
||||
|
||||
<div className="current-track">
|
||||
<div className="title">{ mopidy_track ? mopidy_track.track.name : null }</div>
|
||||
{ mopidy_track ? <ArtistSentence artists={ mopidy_track.track.artists } /> : null }
|
||||
<div className="title">{ this.props.current_track ? this.props.current_track.name : null }</div>
|
||||
{ this.props.current_track ? <ArtistSentence artists={ this.props.current_track.artists } /> : null }
|
||||
</div>
|
||||
|
||||
<div className="controls">
|
||||
@ -59,7 +56,10 @@ class MiniPlayer extends React.Component{
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
return {
|
||||
current_track: state.ui.current_track,
|
||||
play_state: state.mopidy.play_state
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
|
||||
@ -1,144 +0,0 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
import ProgressSlider from './ProgressSlider'
|
||||
import ArtistSentence from './ArtistSentence'
|
||||
import Thumbnail from './Thumbnail'
|
||||
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
|
||||
class Player extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderPlayButton(){
|
||||
var button = <a onClick={() => this.props.mopidyActions.play()}><FontAwesome name="play" /> </a>
|
||||
if( this.props.mopidy.state == 'playing' ){
|
||||
button = <a onClick={() => this.props.mopidyActions.pause()}><FontAwesome name="pause" /> </a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
renderConsumeButton(){
|
||||
var button = <a onClick={() => this.props.mopidyActions.instruct('tracklist.setConsume', [true])}><FontAwesome name="fire" /></a>
|
||||
if( this.props.mopidy.consume ){
|
||||
button = <a className="active" onClick={() => this.props.mopidyActions.instruct('tracklist.setConsume', [false])}><FontAwesome name="fire" /></a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
renderRandomButton(){
|
||||
var button = <a onClick={() => this.props.mopidyActions.instruct('tracklist.setRandom', [true])}><FontAwesome name="random" /></a>
|
||||
if( this.props.mopidy.random ){
|
||||
button = <a className="active" onClick={() => this.props.mopidyActions.instruct('tracklist.setRandom', [false])}><FontAwesome name="random" /></a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
renderRepeatButton(){
|
||||
var button = <a onClick={() => this.props.mopidyActions.instruct('tracklist.setRepeat', [true])}><FontAwesome name="repeat" /></a>
|
||||
if( this.props.mopidy.repeat ){
|
||||
button = <a className="active" onClick={() => this.props.mopidyActions.instruct('tracklist.setRepeat', [false])}><FontAwesome name="repeat" /></a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
renderMiniPlayer(){
|
||||
var mopidy_track = false;
|
||||
if( typeof(this.props.mopidy.current_tltrack) !== 'undefined' && typeof(this.props.mopidy.current_tltrack.track) !== 'undefined' ) mopidy_track = this.props.mopidy.current_tltrack;
|
||||
|
||||
return (
|
||||
<div className="player">
|
||||
|
||||
<div className="current-track">
|
||||
<div className="title">{ mopidy_track ? mopidy_track.track.name : null }</div>
|
||||
{ mopidy_track ? <ArtistSentence artists={ mopidy_track.track.artists } /> : null }
|
||||
</div>
|
||||
|
||||
<div className="controls">
|
||||
<a onClick={() => this.props.mopidyActions.previous()}>
|
||||
<FontAwesome name="step-backward" />
|
||||
</a>
|
||||
{ this.renderPlayButton() }
|
||||
<a onClick={() => this.props.mopidyActions.next()}>
|
||||
<FontAwesome name="step-forward" />
|
||||
</a>
|
||||
<ProgressSlider />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderFullPlayer(){
|
||||
var mopidy_track = false;
|
||||
var images = [];
|
||||
if( typeof(this.props.mopidy.current_tltrack) !== 'undefined' && typeof(this.props.mopidy.current_tltrack.track) !== 'undefined' ){
|
||||
mopidy_track = this.props.mopidy.current_tltrack;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="player">
|
||||
|
||||
{ this.props.spotify.track && !this.props.mini ? <Link className="artwork" to={'/album/'+this.props.spotify.track.album.uri}><Thumbnail size="huge" images={this.props.spotify.track.album.images} /></Link> : <Link className="artwork"><Thumbnail size="huge" images={[]} /></Link> }
|
||||
|
||||
<div className="controls cf">
|
||||
<div className="pull-left">
|
||||
<a onClick={() => this.props.mopidyActions.previous()}>
|
||||
<FontAwesome name="step-backward" />
|
||||
</a>
|
||||
{ this.renderPlayButton() }
|
||||
<a onClick={() => this.props.mopidyActions.stop()}>
|
||||
<FontAwesome name="stop" />
|
||||
</a>
|
||||
<a onClick={() => this.props.mopidyActions.next()}>
|
||||
<FontAwesome name="step-forward" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="pull-right">
|
||||
{ this.renderConsumeButton() }
|
||||
{ this.renderRandomButton() }
|
||||
{ this.renderRepeatButton() }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="current-track">
|
||||
<div className="title">{ mopidy_track ? mopidy_track.track.name : null }</div>
|
||||
{ mopidy_track ? <ArtistSentence artists={ mopidy_track.track.artists } /> : null }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(){
|
||||
if( this.props.mini ){
|
||||
return this.renderMiniPlayer()
|
||||
}else{
|
||||
return this.renderFullPlayer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export our component
|
||||
*
|
||||
* We also integrate our global store, using connect()
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Player)
|
||||
@ -19,13 +19,13 @@ class ProgressSlider extends React.Component{
|
||||
componentDidMount(){
|
||||
var interval_counter = 0
|
||||
setInterval(() => {
|
||||
if( this.props.mopidy.state == 'playing' ){
|
||||
if( this.props.play_state == 'playing' ){
|
||||
|
||||
// every 10 seconds get real position from Mopidy
|
||||
if( interval_counter % 10 == 0 ){
|
||||
this.updateProgress()
|
||||
}else{
|
||||
var time_position = this.props.mopidy.time_position
|
||||
var time_position = this.props.time_position
|
||||
|
||||
// only add 600ms every 1000ms as Mopidy's time tracker is a bit shit
|
||||
// TODO: Why does this kill UI?
|
||||
@ -38,7 +38,7 @@ class ProgressSlider extends React.Component{
|
||||
}
|
||||
|
||||
updateProgress(){
|
||||
if( this.props.mopidy.connected && this.props.mopidy.state == 'playing' ){
|
||||
if( this.props.connected && this.props.play_state == 'playing' ){
|
||||
this.props.mopidyActions.getTimePosition()
|
||||
}
|
||||
}
|
||||
@ -51,8 +51,8 @@ class ProgressSlider extends React.Component{
|
||||
var sliderWidth = slider.getBoundingClientRect().width;
|
||||
var percent = ( sliderX / sliderWidth ).toFixed(2);
|
||||
|
||||
if( this.props.mopidy.connected && typeof(this.props.mopidy.current_tltrack) !== 'undefined' && typeof(this.props.mopidy.current_tltrack.track) !== 'undefined' ){
|
||||
var destination_time = this.props.mopidy.current_tltrack.track.length * percent
|
||||
if( this.props.connected && this.props.current_tltrack ){
|
||||
var destination_time = this.props.current_track.length * percent
|
||||
this.props.mopidyActions.seek( destination_time )
|
||||
this.setState({ animating: false })
|
||||
}
|
||||
@ -60,8 +60,8 @@ class ProgressSlider extends React.Component{
|
||||
|
||||
render(){
|
||||
var percent = 0
|
||||
if( this.props.mopidy.connected && typeof(this.props.mopidy.current_tltrack) !== 'undefined' && typeof(this.props.mopidy.current_tltrack.track) !== 'undefined' ){
|
||||
percent = this.props.mopidy.time_position / this.props.mopidy.current_tltrack.track.length
|
||||
if( this.props.connected && this.props.current_track ){
|
||||
percent = this.props.time_position / this.props.current_track.length
|
||||
percent = percent * 100;
|
||||
if( percent > 100 ) percent = 100
|
||||
}
|
||||
@ -83,7 +83,12 @@ class ProgressSlider extends React.Component{
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
return {
|
||||
current_track: state.ui.current_track,
|
||||
connected: state.mopidy.connected,
|
||||
time_position: state.mopidy.time_position,
|
||||
play_state: state.mopidy.play_state
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
|
||||
@ -22,7 +22,7 @@ class Sidebar extends React.Component{
|
||||
return (
|
||||
<aside>
|
||||
|
||||
{ this.props.spotify && this.props.spotify.track ? <Thumbnail size="large" images={this.props.spotify.track.album.images} /> : null }
|
||||
{ this.props.current_track && this.props.current_track.album.images ? <Thumbnail size="large" images={this.props.current_track.album.images} /> : null }
|
||||
|
||||
<SearchForm context="sidebar" />
|
||||
|
||||
@ -61,15 +61,15 @@ class Sidebar extends React.Component{
|
||||
<Icon name="playlist" className="white" />
|
||||
Playlists
|
||||
</Link>
|
||||
<Link activeClassName="active" disabled={!this.props.spotify.authorized} to="/library/artists">
|
||||
<Link activeClassName="active" disabled={!this.props.spotify_authorized} to="/library/artists">
|
||||
<Icon name="mic" className="white" />
|
||||
Artists
|
||||
</Link>
|
||||
<Link activeClassName="active" disabled={!this.props.spotify.authorized} to="/library/albums">
|
||||
<Link activeClassName="active" disabled={!this.props.spotify_authorized} to="/library/albums">
|
||||
<Icon name="cd" className="white" />
|
||||
Albums
|
||||
</Link>
|
||||
<Link activeClassName="active" disabled={!this.props.spotify.authorized} to="/library/tracks">
|
||||
<Link activeClassName="active" disabled={!this.props.spotify_authorized} to="/library/tracks">
|
||||
<Icon name="music" className="white" />
|
||||
Tracks
|
||||
</Link>
|
||||
@ -83,7 +83,7 @@ class Sidebar extends React.Component{
|
||||
<Link activeClassName="active" to="/settings">
|
||||
<Icon name="cog" className="white" />
|
||||
Settings
|
||||
{ !this.props.mopidy.connected || !this.props.spotify.connected || !this.props.pusher.connected ? <FontAwesome name="exclamation-triangle" className="red-text pull-right" /> : null }
|
||||
{ !this.props.mopidy_connected || !this.props.spotify_connected || !this.props.pusher_connected ? <FontAwesome name="exclamation-triangle" className="red-text pull-right" /> : null }
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
@ -104,7 +104,13 @@ class Sidebar extends React.Component{
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
return {
|
||||
mopidy_connected: state.mopidy.connected,
|
||||
pusher_connected: state.pusher.connected,
|
||||
spotify_connected: state.spotify.connected,
|
||||
spotify_authorized: state.spotify.authorized,
|
||||
current_track: state.ui.current_track
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
|
||||
@ -48,7 +48,7 @@ class TrackList extends React.Component{
|
||||
}
|
||||
|
||||
handleClick( e, index ){
|
||||
if( this.props.ui.context_menu.show ) this.props.uiActions.hideContextMenu();
|
||||
if( this.props.context_menu.show ) this.props.uiActions.hideContextMenu();
|
||||
|
||||
var tracks = this.state.tracks;
|
||||
|
||||
@ -82,7 +82,7 @@ class TrackList extends React.Component{
|
||||
}
|
||||
|
||||
handleDoubleClick( e, index ){
|
||||
if( this.props.ui.context_menu.show ) this.props.uiActions.hideContextMenu();
|
||||
if( this.props.context_menu.show ) this.props.uiActions.hideContextMenu();
|
||||
this.playTracks()
|
||||
}
|
||||
|
||||
@ -144,24 +144,6 @@ class TrackList extends React.Component{
|
||||
{
|
||||
this.state.tracks.map(
|
||||
(track, index) => {
|
||||
|
||||
// flatten nested track objects (as in the case of TlTracks)
|
||||
if( typeof(track.track) !== 'undefined' ){
|
||||
|
||||
// see if we're the current tlTrack
|
||||
// TODO: figure out why this isn't fired when the tracklist changes
|
||||
if( self.props.mopidy.current_tltrack && self.props.mopidy.current_tltrack.tlid == track.tlid ){
|
||||
track.playing = true;
|
||||
}else{
|
||||
track.playing = false;
|
||||
}
|
||||
|
||||
for( var property in track.track ){
|
||||
if( track.track.hasOwnProperty(property) ){
|
||||
track[property] = track.track[property]
|
||||
}
|
||||
}
|
||||
}
|
||||
return <Track
|
||||
show_source_icon={ this.props.show_source_icon }
|
||||
key={index+'_'+track.uri}
|
||||
@ -185,7 +167,10 @@ class TrackList extends React.Component{
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
return {
|
||||
current_track: state.ui.current_track,
|
||||
context_menu: state.ui.context_menu
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
|
||||
@ -30,7 +30,7 @@ class VolumeControl extends React.Component{
|
||||
<div className="slider-wrapper">
|
||||
<div className="slider vertical" onClick={ (e) => this.handleClick(e) } >
|
||||
<div className="track">
|
||||
<div className="progress" style={{ height: this.props.mopidy.volume+'%' }}></div>
|
||||
<div className="progress" style={{ height: this.props.volume+'%' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -40,7 +40,9 @@ class VolumeControl extends React.Component{
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
return {
|
||||
volume: state.mopidy.volume
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
|
||||
@ -5,9 +5,6 @@ export default function reducer(lastfm = {}, action){
|
||||
case 'LASTFM_ARTIST_LOADED':
|
||||
return Object.assign({}, lastfm, { artist: action.data });
|
||||
|
||||
case 'LASTFM_ALBUM_LOADED':
|
||||
return Object.assign({}, lastfm, { album: action.data });
|
||||
|
||||
case 'LASTFM_TRACK_LOADED':
|
||||
return Object.assign({}, lastfm, { track: action.data });
|
||||
|
||||
|
||||
@ -235,7 +235,7 @@ const MopidyMiddleware = (function(){
|
||||
break;
|
||||
|
||||
case 'MOPIDY_ALBUM':
|
||||
store.dispatch({ type: 'MOPIDY_ALBUM_LOADED', data: false });
|
||||
//store.dispatch({ type: 'MOPIDY_ALBUM_LOADED', data: false });
|
||||
instruct( socket, store, 'library.lookup', action.data )
|
||||
.then( response => {
|
||||
var album = response[0].album;
|
||||
|
||||
@ -34,7 +34,7 @@ export default function reducer(mopidy = {}, action){
|
||||
**/
|
||||
case 'MOPIDY_STATE':
|
||||
return Object.assign({}, mopidy, {
|
||||
state: action.data
|
||||
play_state: action.data
|
||||
});
|
||||
|
||||
case 'MOPIDY_CONSUME':
|
||||
@ -67,26 +67,6 @@ export default function reducer(mopidy = {}, action){
|
||||
* Asset-oriented actions
|
||||
**/
|
||||
|
||||
case 'MOPIDY_TLTRACKS':
|
||||
return Object.assign({}, mopidy, {
|
||||
tracks: action.data
|
||||
});
|
||||
|
||||
case 'MOPIDY_CURRENTTLTRACK':
|
||||
if( !action.data ) return mopidy;
|
||||
|
||||
var tracks = [];
|
||||
Object.assign(tracks, mopidy.tracks);
|
||||
|
||||
for( var i = 0; i < tracks.length; i++ ){
|
||||
Object.assign(tracks[i].track, { playing: ( tracks[i].tlid == action.data.tlid ) });
|
||||
}
|
||||
|
||||
return Object.assign({}, mopidy, {
|
||||
current_tltrack: action.data,
|
||||
tracks: tracks
|
||||
});
|
||||
|
||||
case 'MOPIDY_PLAYLISTS_LOADED':
|
||||
if( !action.data ) return mopidy;
|
||||
return Object.assign({}, mopidy, { playlists: action.data });
|
||||
@ -101,11 +81,6 @@ export default function reducer(mopidy = {}, action){
|
||||
playlist: action.data
|
||||
});
|
||||
|
||||
case 'MOPIDY_ALBUM_LOADED':
|
||||
return Object.assign({}, mopidy, {
|
||||
album: action.data
|
||||
});
|
||||
|
||||
case 'MOPIDY_ARTIST_LOADED':
|
||||
return Object.assign({}, mopidy, {
|
||||
artist: action.data
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
|
||||
|
||||
var actions = require('./actions.js')
|
||||
|
||||
const SpotifyMiddleware = (function(){
|
||||
|
||||
@ -55,9 +55,6 @@ export default function reducer(spotify = {}, action){
|
||||
case 'SPOTIFY_ME_LOADED':
|
||||
return Object.assign({}, spotify, { me: action.data });
|
||||
|
||||
case 'SPOTIFY_TRACK_LOADED':
|
||||
return Object.assign({}, spotify, { track: action.data });
|
||||
|
||||
case 'SPOTIFY_PLAYLIST_LOADED':
|
||||
return Object.assign({}, spotify, { playlist: action.data });
|
||||
|
||||
@ -71,19 +68,6 @@ export default function reducer(spotify = {}, action){
|
||||
}})
|
||||
return Object.assign({}, spotify, { playlist: playlist });
|
||||
|
||||
case 'SPOTIFY_ALBUM_LOADED':
|
||||
return Object.assign({}, spotify, { album: action.data });
|
||||
|
||||
case 'SPOTIFY_ALBUM_LOADED_MORE':
|
||||
var album = spotify.album
|
||||
Object.assign(album, { tracks: {
|
||||
href: action.data.href,
|
||||
next: action.data.next,
|
||||
previous: action.data.previous,
|
||||
items: [ ...spotify.album.tracks.items, ...action.data.items ]
|
||||
}})
|
||||
return Object.assign({}, spotify, { album: album });
|
||||
|
||||
case 'SPOTIFY_ARTIST_LOADED':
|
||||
return Object.assign({}, spotify, { artist: action.data });
|
||||
|
||||
|
||||
@ -16,13 +16,6 @@ export function hideContextMenu(){
|
||||
}
|
||||
}
|
||||
|
||||
export function setCurrentTrack( track ){
|
||||
return {
|
||||
type: 'UI_CURRENT_TRACK',
|
||||
track: track
|
||||
}
|
||||
}
|
||||
|
||||
export function lazyLoading( start ){
|
||||
return {
|
||||
type: 'UI_LAZY_LOADING',
|
||||
|
||||
@ -19,11 +19,65 @@ export default function reducer(ui = {}, action){
|
||||
case 'UI_LAZY_LOADING':
|
||||
return Object.assign({}, ui, { lazy_loading: action.start });
|
||||
|
||||
case 'UI_CURRENT_TRACK':
|
||||
var current_track = ui.current_track;
|
||||
Object.assign({}, current_track, action.track );
|
||||
case 'SPOTIFY_ALBUM_LOADED':
|
||||
case 'MOPIDY_ALBUM_LOADED':
|
||||
return Object.assign({}, ui, { album: action.data });
|
||||
|
||||
case 'LASTFM_ALBUM_LOADED':
|
||||
//if( !action.data.image ) return ui
|
||||
var album = Object.assign(ui.album, { images: action.data.image })
|
||||
return Object.assign({}, ui, { album: album });
|
||||
|
||||
case 'SPOTIFY_ALBUM_LOADED_MORE':
|
||||
var album = ui.album
|
||||
Object.assign(album, { tracks: {
|
||||
href: action.data.href,
|
||||
next: action.data.next,
|
||||
previous: action.data.previous,
|
||||
items: [ ...ui.album.tracks.items, ...action.data.items ]
|
||||
}})
|
||||
return Object.assign({}, ui, { album: album });
|
||||
|
||||
|
||||
/**
|
||||
* Current track and tracklist
|
||||
**/
|
||||
|
||||
case 'MOPIDY_TLTRACKS':
|
||||
if( !action.data ) return ui
|
||||
|
||||
var tracklist = []
|
||||
for( var i = 0; i < action.data.length; i++ ){
|
||||
tracklist.push( Object.assign({}, action.data[i].track, { tlid: action.data[i].tlid }) )
|
||||
}
|
||||
return Object.assign({}, ui, { current_tracklist: tracklist });
|
||||
|
||||
case 'SPOTIFY_TRACK_LOADED':
|
||||
if( !action.data ) return ui
|
||||
|
||||
var current_track = Object.assign({}, ui.current_track, action.data)
|
||||
return Object.assign({}, ui, { current_track: current_track });
|
||||
|
||||
case 'MOPIDY_CURRENTTLTRACK':
|
||||
if( !action.data ) return ui
|
||||
|
||||
var current_tracklist = []
|
||||
Object.assign(current_tracklist, ui.current_tracklist)
|
||||
|
||||
for( var i = 0; i < current_tracklist.length; i++ ){
|
||||
Object.assign(
|
||||
current_tracklist[i],
|
||||
{ playing: ( current_tracklist[i].tlid == action.data.tlid ) }
|
||||
)
|
||||
}
|
||||
|
||||
var current_track = action.data.track
|
||||
Object.assign(current_track, { tlid: action.data.tlid })
|
||||
|
||||
return Object.assign({}, ui, {
|
||||
current_track: current_track
|
||||
});
|
||||
|
||||
default:
|
||||
return ui
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ import Dater from '../components/Dater'
|
||||
import LazyLoadListener from '../components/LazyLoadListener'
|
||||
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
import * as lastfmActions from '../services/lastfm/actions'
|
||||
import * as spotifyActions from '../services/spotify/actions'
|
||||
|
||||
class Album extends React.Component{
|
||||
@ -28,9 +27,14 @@ class Album extends React.Component{
|
||||
}
|
||||
|
||||
componentWillReceiveProps( nextProps ){
|
||||
console.log('componentWillReceiveProps')
|
||||
|
||||
// if our URI has changed, fetch new album
|
||||
if( nextProps.params.uri != this.props.params.uri ){
|
||||
this.loadAlbum( nextProps )
|
||||
}else if( !this.props.mopidy.connected && nextProps.mopidy.connected ){
|
||||
|
||||
// if mopidy has just connected AND we're a local album, go get
|
||||
}else if( !this.props.mopidy_connected && nextProps.mopidy_connected ){
|
||||
if( helpers.uriSource( this.props.params.uri ) == 'local' ){
|
||||
this.loadAlbum( nextProps )
|
||||
}
|
||||
@ -38,70 +42,51 @@ class Album extends React.Component{
|
||||
}
|
||||
|
||||
loadAlbum( props = this.props ){
|
||||
var source = helpers.uriSource( props.params.uri );
|
||||
switch( helpers.uriSource( props.params.uri ) ){
|
||||
|
||||
if( source == 'spotify' ){
|
||||
this.props.spotifyActions.getAlbum( props.params.uri );
|
||||
case 'spotify':
|
||||
this.props.spotifyActions.getAlbum( props.params.uri );
|
||||
break;
|
||||
|
||||
}else if( source == 'local' && props.mopidy.connected ){
|
||||
this.props.mopidyActions.getAlbum( props.params.uri );
|
||||
case 'local':
|
||||
if( props.mopidy_connected ) this.props.mopidyActions.getAlbum( props.params.uri );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
if( !this.props.spotify.album || !this.props.spotify.album.tracks.next ) return
|
||||
this.props.spotifyActions.getURL( this.props.spotify.album.tracks.next, 'SPOTIFY_ALBUM_LOADED_MORE' );
|
||||
if( !this.props.album.tracks.next ) return
|
||||
this.props.spotifyActions.getURL( this.props.album.tracks.next, 'SPOTIFY_ALBUM_LOADED_MORE' );
|
||||
}
|
||||
|
||||
album(){
|
||||
var album = {
|
||||
name: false,
|
||||
tracks: {
|
||||
items: []
|
||||
},
|
||||
artists: [],
|
||||
images: []
|
||||
}
|
||||
|
||||
switch( helpers.uriSource( this.props.params.uri ) ){
|
||||
|
||||
case 'spotify':
|
||||
Object.assign(album, this.props.spotify.album)
|
||||
album.artists = this.props.spotify.artists
|
||||
break
|
||||
|
||||
case 'local':
|
||||
Object.assign(album, this.props.mopidy.album)
|
||||
if( this.props.lastfm.album.image ) album.images = this.props.lastfm.album.image
|
||||
break
|
||||
}
|
||||
|
||||
return album
|
||||
renderThumbnail(){
|
||||
if( !this.props.album.images ) return <Thumbnail size="large" images={[]} />
|
||||
return <Thumbnail size="large" images={ this.props.album.images } />
|
||||
}
|
||||
|
||||
render(){
|
||||
var album = this.album()
|
||||
if( !this.props.album ) return null
|
||||
|
||||
return (
|
||||
<div className="view album-view">
|
||||
<div className="intro">
|
||||
<Thumbnail size="large" images={ album.images } />
|
||||
<ArtistGrid artists={ album.artists } />
|
||||
{ this.renderThumbnail() }
|
||||
<ArtistGrid artists={ this.props.album.artists } />
|
||||
<ul className="details">
|
||||
<li>{ album.tracks.total } tracks, <Dater type="total-time" data={album.tracks.items} /></li>
|
||||
{ album.release_date ? <li>Released <Dater type="date" data={ album.release_date } /></li> : null }
|
||||
<li>{ this.props.album.tracks.total } tracks, <Dater type="total-time" data={this.props.album.tracks.items} /></li>
|
||||
{ this.props.album.release_date ? <li>Released <Dater type="date" data={ this.props.album.release_date } /></li> : null }
|
||||
<li><FontAwesome name={helpers.sourceIcon( this.props.params.uri )} /> {helpers.uriSource( this.props.params.uri )} playlist</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="main">
|
||||
|
||||
<div className="title">
|
||||
<h1>{ album.name }</h1>
|
||||
<h3><ArtistSentence artists={ album.artists } /></h3>
|
||||
<h1>{ this.props.album.name }</h1>
|
||||
<h3><ArtistSentence artists={ this.props.album.artists } /></h3>
|
||||
</div>
|
||||
|
||||
<section className="list-wrapper">
|
||||
<TrackList tracks={ album.tracks.items } />
|
||||
<TrackList tracks={ this.props.album.tracks.items } />
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
</section>
|
||||
|
||||
@ -119,13 +104,15 @@ class Album extends React.Component{
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
return {
|
||||
album: state.ui.album,
|
||||
mopidy_connected: state.mopidy.connected
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
lastfmActions: bindActionCreators(lastfmActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ class App extends React.Component{
|
||||
|
||||
switch(e.keyCode){
|
||||
case 32: // spacebar
|
||||
if( this.props.mopidy.state == 'playing' ){
|
||||
if( this.props.play_state == 'playing' ){
|
||||
this.props.mopidyActions.pause();
|
||||
}else{
|
||||
this.props.mopidyActions.play();
|
||||
@ -78,8 +78,7 @@ class App extends React.Component{
|
||||
<main>
|
||||
{this.props.children}
|
||||
</main>
|
||||
{ this.props.ui.context_menu.test }
|
||||
<ContextMenu state={this.props.ui.context_menu} />
|
||||
<ContextMenu state={this.props.context_menu} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -92,7 +91,10 @@ class App extends React.Component{
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
return {
|
||||
play_state: state.mopidy.play_state,
|
||||
context_menu: state.ui.context_menu
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
|
||||
@ -21,22 +21,6 @@ class Queue extends React.Component{
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderTrackList(){
|
||||
if( this.props.mopidy && this.props.mopidy.tracks ){
|
||||
return (
|
||||
<TrackList
|
||||
show_source_icon={true}
|
||||
context="queue"
|
||||
tracks={this.props.mopidy.tracks}
|
||||
removeTracks={ tracks => this.removeTracks( tracks ) }
|
||||
playTracks={ tracks => this.playTracks( tracks ) }
|
||||
playTrack={ track => this.playTrack( track ) }
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
removeTracks( tracks ){
|
||||
var tlids = [];
|
||||
for( var i = 0; i < tracks.length; i++ ){
|
||||
@ -60,7 +44,13 @@ class Queue extends React.Component{
|
||||
<FullPlayer />
|
||||
|
||||
<section className="list-wrapper">
|
||||
{ this.renderTrackList() }
|
||||
<TrackList
|
||||
show_source_icon={true}
|
||||
context="queue"
|
||||
tracks={this.props.current_tracklist}
|
||||
removeTracks={ tracks => this.removeTracks( tracks ) }
|
||||
playTracks={ tracks => this.playTracks( tracks ) }
|
||||
playTrack={ track => this.playTrack( track ) } />
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@ -76,7 +66,9 @@ class Queue extends React.Component{
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return state;
|
||||
return {
|
||||
current_tracklist: state.ui.current_tracklist
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
|
||||
Reference in New Issue
Block a user