Playlist list... not sure if cleanest way to build components
This commit is contained in:
15
src/js/components/ItemSource.js
Executable file
15
src/js/components/ItemSource.js
Executable file
@ -0,0 +1,15 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
export default class extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render(){
|
||||
var uriElements = this.props.uri.split(':');
|
||||
if( uriElements.length <= 0 ) return false;
|
||||
return <span>{ uriElements[0] }</span>;
|
||||
}
|
||||
}
|
||||
26
src/js/components/ListItem.js
Executable file
26
src/js/components/ListItem.js
Executable file
@ -0,0 +1,26 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
import { browserHistory } from 'react-router'
|
||||
|
||||
export default class ListItem extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
handleClick(e){
|
||||
browserHistory.push( this.props.link );
|
||||
}
|
||||
|
||||
render(){
|
||||
var className = 'list-item';
|
||||
if( this.props.extraClasses ) className += ' '+this.props.extraClasses;
|
||||
|
||||
return (
|
||||
<li className={className} onClick={ (e) => this.handleClick(e) }>
|
||||
{ this.props.children }
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
39
src/js/components/PlaylistListItem.js
Executable file
39
src/js/components/PlaylistListItem.js
Executable file
@ -0,0 +1,39 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import ListItem from './ListItem'
|
||||
import ItemSource from './ItemSource'
|
||||
|
||||
export default class PlaylistListItem extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderOwner(){
|
||||
if( !this.props.item.owner ) return null;
|
||||
return (
|
||||
<Link to={ '/user/'+this.props.item.owner.uri }>
|
||||
{ this.props.item.owner.id }
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<ListItem extraClasses="playlist" link={ '/playlist/'+this.props.item.uri }>
|
||||
<span className="col name">
|
||||
{this.props.item.name}
|
||||
</span>
|
||||
<span className="col owner">
|
||||
{ this.renderOwner() }
|
||||
</span>
|
||||
<span className="col source">
|
||||
<ItemSource uri={this.props.item.uri} />
|
||||
</span>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -39,7 +39,7 @@ export default class Track extends React.Component{
|
||||
|
||||
render(){
|
||||
|
||||
var className = 'track';
|
||||
var className = 'list-item track';
|
||||
var selectedIcon = 'square-o';
|
||||
if( typeof(this.props.track.selected) !== 'undefined' && this.props.track.selected ){
|
||||
selectedIcon = 'check-square-o';
|
||||
|
||||
@ -105,6 +105,12 @@ class TrackList extends React.Component{
|
||||
return (
|
||||
<div>
|
||||
<ul>
|
||||
<li className="list-item header track">
|
||||
<span className="col name">Name</span>
|
||||
<span className="col artists">Artists</span>
|
||||
<span className="col album">Album</span>
|
||||
<span className="col duration">Duration</span>
|
||||
</li>
|
||||
{
|
||||
this.state.tracks.map(
|
||||
(track, index) => {
|
||||
|
||||
@ -6,7 +6,7 @@ import React, { PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux'
|
||||
import { createStore } from 'redux'
|
||||
import { Router, Route, Link, hashHistory } from 'react-router'
|
||||
import { Router, Route, Link, browserHistory } from 'react-router'
|
||||
|
||||
import store from './bootstrap.js'
|
||||
|
||||
@ -26,7 +26,7 @@ import LibraryPlaylists from './views/library/LibraryPlaylists'
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<Router history={hashHistory}>
|
||||
<Router history={browserHistory}>
|
||||
<Route path="/" component={App}>
|
||||
|
||||
<Route path="library/artists" component={LibraryArtists} />
|
||||
|
||||
@ -22,6 +22,19 @@ export function disconnect(){
|
||||
}
|
||||
}
|
||||
|
||||
export function instruct( call, value ){
|
||||
return {
|
||||
type: 'MOPIDY_INSTRUCT',
|
||||
call: call,
|
||||
value: value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Playback-oriented actions
|
||||
**/
|
||||
|
||||
export function changeTrack( tlid ){
|
||||
return {
|
||||
type: 'MOPIDY_INSTRUCT',
|
||||
@ -56,14 +69,6 @@ export function removeTracks( tlids ){
|
||||
}
|
||||
}
|
||||
|
||||
export function instruct( call, value ){
|
||||
return {
|
||||
type: 'MOPIDY_INSTRUCT',
|
||||
call: call,
|
||||
value: value
|
||||
}
|
||||
}
|
||||
|
||||
export function play(){
|
||||
return instruct('playback.play');
|
||||
}
|
||||
@ -79,3 +84,14 @@ export function next(){
|
||||
export function previous(){
|
||||
return instruct('playback.previous');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Asset-oriented actions
|
||||
**/
|
||||
|
||||
export function getPlaylists(){
|
||||
return { type: 'MOPIDY_PLAYLISTS' }
|
||||
}
|
||||
|
||||
|
||||
@ -154,6 +154,13 @@ const MopidyMiddleware = (function(){
|
||||
})
|
||||
break;
|
||||
|
||||
case 'MOPIDY_PLAYLISTS':
|
||||
instruct( socket, store, 'playlists.asList' )
|
||||
.then( response => {
|
||||
store.dispatch({ type: 'MOPIDY_PLAYLISTS_LOADED', data: response });
|
||||
})
|
||||
break;
|
||||
|
||||
// This action is irrelevant to us, pass it on to the next middleware
|
||||
default:
|
||||
return next(action);
|
||||
|
||||
@ -31,6 +31,11 @@ export default function reducer(mopidy = {}, action){
|
||||
}
|
||||
return Object.assign({}, mopidy, { tracks: mopidy.tracks });
|
||||
|
||||
case 'MOPIDY_PLAYLISTS_LOADED':
|
||||
if( !action.data ) return mopidy;
|
||||
return Object.assign({}, mopidy, { playlists: action.data });
|
||||
|
||||
|
||||
/**
|
||||
* Websocket-initiated actions
|
||||
**/
|
||||
|
||||
@ -4,6 +4,8 @@ import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import PlaylistListItem from '../../components/PlaylistListItem'
|
||||
|
||||
import * as mopidyActions from '../../services/mopidy/actions'
|
||||
import * as spotifyActions from '../../services/spotify/actions'
|
||||
|
||||
@ -15,26 +17,60 @@ class LibraryPlaylists extends React.Component{
|
||||
|
||||
// on render
|
||||
componentDidMount(){
|
||||
if( this.props.mopidy.connected ) this.loadPlaylists();
|
||||
}
|
||||
|
||||
componentWillReceiveProps( nextProps ){
|
||||
if( !this.props.mopidy.connected && nextProps.mopidy.connected ) this.loadPlaylists();
|
||||
}
|
||||
|
||||
loadPlaylists(){
|
||||
this.props.spotifyActions.getLibraryPlaylists();
|
||||
this.props.mopidyActions.getPlaylists();
|
||||
}
|
||||
|
||||
compiledPlaylistSources(){
|
||||
var playlists = [];
|
||||
|
||||
if( this.props.mopidy.playlists ){
|
||||
Object.assign(playlists, this.props.mopidy.playlists)
|
||||
}
|
||||
|
||||
if( this.props.spotify.libraryPlaylists ){
|
||||
Object.assign(playlists, this.props.spotify.libraryPlaylists.items)
|
||||
}
|
||||
|
||||
return playlists;
|
||||
}
|
||||
|
||||
renderPlaylists(){
|
||||
if( !this.compiledPlaylistSources() ) return null;
|
||||
|
||||
return (
|
||||
<ul>
|
||||
<li className="list-item header playlist">
|
||||
<span className="col name">Name</span>
|
||||
<span className="col owner">Owner</span>
|
||||
<span className="col source">Source</span>
|
||||
</li>
|
||||
{
|
||||
this.compiledPlaylistSources().map( (playlist, index) => {
|
||||
return (
|
||||
<PlaylistListItem key={index} item={playlist} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
render(){
|
||||
if( this.props.spotify.libraryPlaylists ){
|
||||
return (
|
||||
<div>
|
||||
<h3>My playlists</h3>
|
||||
<ul>
|
||||
{
|
||||
this.props.spotify.libraryPlaylists.items.map( (playlist, index) => {
|
||||
var link = '/playlist/' + playlist.uri;
|
||||
return <li key={index}><Link to={link}>{ playlist.name }</Link></li>
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<div>
|
||||
<h3>My playlists</h3>
|
||||
{ this.renderPlaylists() }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,5 +5,6 @@
|
||||
@import 'global/reset';
|
||||
@import 'global/forms';
|
||||
|
||||
@import 'components/lists';
|
||||
@import 'components/track';
|
||||
|
||||
|
||||
81
src/scss/components/_lists.scss
Executable file
81
src/scss/components/_lists.scss
Executable file
@ -0,0 +1,81 @@
|
||||
|
||||
.list-item {
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 8px 10px 8px 30px;
|
||||
border-top: 1px solid #EEEEEE;
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
margin-bottom: -1px;
|
||||
|
||||
.col {
|
||||
width: 10%;
|
||||
min-height: 1px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: #FFF39C !important;
|
||||
}
|
||||
|
||||
&:not(.header):hover {
|
||||
background: #F4F4F4;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.header {
|
||||
font-size: 10px;
|
||||
color: #CCCCCC;
|
||||
text-transform: uppercase;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
|
||||
&.track {
|
||||
|
||||
.col {
|
||||
&.name,
|
||||
&.artists,
|
||||
&.album {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
.select-state {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
&.playing {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.playlist {
|
||||
|
||||
.col {
|
||||
&.name {
|
||||
width: 30%;
|
||||
}
|
||||
&.owner {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
|
||||
.track {
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
position: relative;
|
||||
padding: 8px 10px 8px 30px;
|
||||
border-top: 1px solid #EEEEEE;
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
margin-bottom: -1px;
|
||||
|
||||
.col {
|
||||
width: 10%;
|
||||
min-height: 1px;
|
||||
float: left;
|
||||
|
||||
&.name,
|
||||
&.artists,
|
||||
&.album {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
.select-state {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.playing {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.selected,
|
||||
&.selected:hover {
|
||||
background: #FFF39C;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
background: #F4F4F4;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user