Mergingcurrent_track and current_tracklist; Smarter reducers for UI

This commit is contained in:
James Barnsley
2016-11-09 13:18:03 +13:00
parent c87a7fbd17
commit 88caab6aef
24 changed files with 5712 additions and 5915 deletions

7
src/js/bootstrap.js vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>&nbsp;
{ this.renderPlayButton() }
<a onClick={() => this.props.mopidyActions.next()}>
<FontAwesome name="step-forward" />
</a>&nbsp;
<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>&nbsp;
{ this.renderPlayButton() }
<a onClick={() => this.props.mopidyActions.stop()}>
<FontAwesome name="stop" />
</a>&nbsp;
<a onClick={() => this.props.mopidyActions.next()}>
<FontAwesome name="step-forward" />
</a>&nbsp;
</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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
var actions = require('./actions.js')
const SpotifyMiddleware = (function(){

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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