URILink for encoding all URIs; fixes #220; current_track now just a URI
This commit is contained in:
@ -41,7 +41,7 @@ class AlbumGrid extends React.Component{
|
||||
key={album.uri}
|
||||
type="album"
|
||||
item={album}
|
||||
onClick={e => {hashHistory.push(global.baseURL+'album/'+album.uri)}}
|
||||
onClick={e => {hashHistory.push(global.baseURL+'album/'+encodeURIComponent(album.uri))}}
|
||||
onLoad={() => this.handleLoad(album.uri)}
|
||||
onContextMenu={e => this.handleContextMenu(e,album)}
|
||||
/>
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import { Link } from 'react-router'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
|
||||
import Thumbnail from './Thumbnail'
|
||||
import URILink from './URILink'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
@ -39,9 +39,10 @@ class ArtistGrid extends React.Component{
|
||||
this.props.artists.map(
|
||||
(artist, index) => {
|
||||
return (
|
||||
<Link
|
||||
<URILink
|
||||
className="grid-item"
|
||||
to={global.baseURL+'artist/'+artist.uri}
|
||||
type="artist"
|
||||
uri={artist.uri}
|
||||
key={index}
|
||||
onContextMenu={e => this.handleContextMenu(e,artist)}>
|
||||
<Thumbnail size="medium" images={artist.images} />
|
||||
@ -53,7 +54,7 @@ class ArtistGrid extends React.Component{
|
||||
{artist.followers ? artist.followers.total.toLocaleString()+' followers' : null}
|
||||
{artist.albums_uris && !artist.followers ? artist.albums_uris.length+' albums' : null}
|
||||
</div>
|
||||
</Link>
|
||||
</URILink>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
import { Link } from 'react-router'
|
||||
import URILink from './URILink'
|
||||
|
||||
export default class ArtistSentence extends React.Component{
|
||||
|
||||
@ -10,26 +10,34 @@ export default class ArtistSentence extends React.Component{
|
||||
}
|
||||
|
||||
render(){
|
||||
if (!this.props.artists ) return <span>-</span>
|
||||
if (!this.props.artists){
|
||||
return <span>-</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={ this.props.className ? this.props.className+" artist-sentence" : "artist-sentence" }>
|
||||
{
|
||||
this.props.artists.map((artist, index) => {
|
||||
if (!artist) return <span>-</span>
|
||||
|
||||
if (!artist){
|
||||
return <span>-</span>;
|
||||
}
|
||||
|
||||
var separator = null;
|
||||
if (index == this.props.artists.length - 2){
|
||||
separator = ' and ';
|
||||
}else if (index < this.props.artists.length - 2){
|
||||
} else if (index < this.props.artists.length - 2){
|
||||
separator = ', ';
|
||||
}
|
||||
|
||||
if (!artist.name){
|
||||
var content = <span>-</span>
|
||||
} else if (!artist.uri || this.props.nolinks){
|
||||
var content = <span>{ artist.name }</span>
|
||||
} else {
|
||||
var content = <Link className="artist" to={global.baseURL+'artist/'+artist.uri}>{ artist.name }</Link>
|
||||
var content = <URILink className="artist" type="artist" uri={artist.uri}>{ artist.name }</URILink>
|
||||
}
|
||||
|
||||
return (
|
||||
<span key={'index_'+artist.uri}>
|
||||
{ content }
|
||||
|
||||
@ -24,7 +24,7 @@ export default class CategoryGrid extends React.Component{
|
||||
key={category.id}
|
||||
type="category"
|
||||
item={category}
|
||||
onClick={e => {hashHistory.push(global.baseURL+'discover/categories/'+category.id)}}
|
||||
onClick={e => {hashHistory.push(global.baseURL+'discover/categories/'+encodeURIComponent(category.id))}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,165 +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 Dater from './Dater'
|
||||
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
|
||||
class FullPlayer extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
handleContextMenu(e,item){
|
||||
e.preventDefault()
|
||||
var data = {
|
||||
e: e,
|
||||
context: 'album',
|
||||
uris: [item.uri],
|
||||
items: [item]
|
||||
}
|
||||
this.props.uiActions.showContextMenu(data)
|
||||
}
|
||||
|
||||
renderPlayButton(){
|
||||
var button = <a onClick={() => this.props.mopidyActions.play()}><FontAwesome name="play" /> </a>
|
||||
if (this.props.play_state == 'playing'){
|
||||
button = <a onClick={() => this.props.mopidyActions.pause()}><FontAwesome name="pause" /> </a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
renderConsumeButton(){
|
||||
var button = <a className="has-tooltip" onClick={() => this.props.mopidyActions.instruct('tracklist.setConsume', [true])}><FontAwesome name="fire" /><span className="tooltip">Consume</span></a>
|
||||
if (this.props.consume){
|
||||
button = <a className="active has-tooltip" onClick={() => this.props.mopidyActions.instruct('tracklist.setConsume', [false])}><FontAwesome name="fire" /><span className="tooltip">Consume</span></a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
renderRandomButton(){
|
||||
var button = <a className="has-tooltip" onClick={() => this.props.mopidyActions.instruct('tracklist.setRandom', [true])}><FontAwesome name="random" /><span className="tooltip">Shuffle</span></a>
|
||||
if (this.props.random){
|
||||
button = <a className="active has-tooltip" onClick={() => this.props.mopidyActions.instruct('tracklist.setRandom', [false])}><FontAwesome name="random" /><span className="tooltip">Shuffle</span></a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
renderRepeatButton(){
|
||||
var button = <a className="has-tooltip" onClick={() => this.props.mopidyActions.instruct('tracklist.setRepeat', [true])}><FontAwesome name="repeat" /><span className="tooltip">Repeat</span></a>
|
||||
if (this.props.repeat){
|
||||
button = <a className="active has-tooltip" onClick={() => this.props.mopidyActions.instruct('tracklist.setRepeat', [false])}><FontAwesome name="repeat" /><span className="tooltip">Repeat</span></a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
renderArtwork(){
|
||||
if (
|
||||
!this.props.current_track ||
|
||||
!this.props.current_track.album ||
|
||||
!this.props.current_track.album.images){
|
||||
return (
|
||||
<span className={this.props.radio_enabled ? 'artwork radio-enabled' : 'artwork'}>
|
||||
{this.props.radio_enabled ? <img className="radio-overlay" src="assets/radio-overlay.png" /> : null}
|
||||
<Thumbnail size="huge" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
var images = this.props.current_track.album.images
|
||||
if (typeof(this.props.tracks[this.props.current_track.uri]) !== 'undefined'){
|
||||
images = this.props.tracks[this.props.current_track.uri].album.images
|
||||
}
|
||||
|
||||
var link = null
|
||||
if (this.props.current_track.album.uri ) link = '/album/'+this.props.current_track.album.uri
|
||||
return (
|
||||
<Link className={this.props.radio_enabled ? 'artwork radio-enabled' : '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={images} canZoom />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div className="player">
|
||||
|
||||
{ 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>
|
||||
|
||||
<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="progress">
|
||||
<ProgressSlider />
|
||||
<span className="grey-text pull-left">{ this.props.time_position ? <Dater type="length" data={this.props.time_position} /> : '-' }</span>
|
||||
<span className="pull-right">{ this.props.current_track ? <Dater type="length" data={this.props.current_track.length} /> : '-' }</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export our component
|
||||
*
|
||||
* We also integrate our global store, using connect()
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
radio_enabled: (state.ui.radio && state.ui.radio.enabled ? true : false),
|
||||
tracks: state.core.tracks,
|
||||
current_track: (typeof(state.core.current_track) !== 'undefined' && typeof(state.core.tracks) !== 'undefined' && typeof(state.core.tracks[state.core.current_track.uri]) !== 'undefined' ? state.core.tracks[state.core.current_track.uri] : null),
|
||||
play_state: state.mopidy.play_state,
|
||||
time_position: state.mopidy.time_position,
|
||||
consume: state.mopidy.consume,
|
||||
repeat: state.mopidy.repeat,
|
||||
random: state.mopidy.random
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FullPlayer)
|
||||
@ -2,11 +2,12 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import { hashHistory, Link } from 'react-router'
|
||||
import { hashHistory } from 'react-router'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
|
||||
import ArtistSentence from './ArtistSentence'
|
||||
import Dater from './Dater'
|
||||
import URILink from './URILink'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
@ -21,7 +22,7 @@ class List extends React.Component{
|
||||
|
||||
// make sure we haven't clicked a nested link (ie Artist name)
|
||||
if (e.target.tagName.toLowerCase() !== 'a'){
|
||||
hashHistory.push((this.props.link_prefix ? this.props.link_prefix : '') + uri );
|
||||
hashHistory.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(uri));
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +63,7 @@ class List extends React.Component{
|
||||
}
|
||||
|
||||
if (key_string === 'added_at') return <span><Dater type="ago" data={value} /> ago</span>
|
||||
if (key_string === 'owner') return <Link to={global.baseURL+'user/'+ value.uri}>{value.id}</Link>
|
||||
if (key_string === 'owner') return <URILink type="user" uri={value.uri}>{value.id}</URILink>
|
||||
if (key[0] === 'artists') return <ArtistSentence artists={value} />
|
||||
if (value === true) return <FontAwesome name="check" />
|
||||
if (typeof(value) === 'number') return <span>{value.toLocaleString()}</span>
|
||||
|
||||
@ -1,80 +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 VolumeControl from './VolumeControl'
|
||||
import ArtistSentence from './ArtistSentence'
|
||||
import Thumbnail from './Thumbnail'
|
||||
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
|
||||
class MiniPlayer extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderPlayButton(){
|
||||
var button = <a className="play" onClick={() => this.props.mopidyActions.play()}><FontAwesome name="play" /> </a>
|
||||
if (this.props.play_state == 'playing'){
|
||||
button = <a className="play" onClick={() => this.props.mopidyActions.pause()}><FontAwesome name="pause" /> </a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
render(){
|
||||
var images = []
|
||||
if (this.props.current_track && this.props.current_track.album && this.props.current_track.album.images){
|
||||
images = this.props.current_track.album.images
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="player mini-player">
|
||||
|
||||
<Thumbnail size="small" images={images} />
|
||||
|
||||
<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>
|
||||
|
||||
<div className="controls">
|
||||
{ this.renderPlayButton() }
|
||||
<a className="next" onClick={() => this.props.mopidyActions.next()}>
|
||||
<FontAwesome name="step-forward" />
|
||||
</a>
|
||||
<VolumeControl />
|
||||
</div>
|
||||
|
||||
<ProgressSlider />
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export our component
|
||||
*
|
||||
* We also integrate our global store, using connect()
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
current_track: (typeof(state.core.current_track) !== 'undefined' && typeof(state.core.tracks) !== 'undefined' && typeof(state.core.tracks[state.core.current_track.uri]) !== 'undefined' ? state.core.tracks[state.core.current_track.uri] : null),
|
||||
play_state: state.mopidy.play_state
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MiniPlayer)
|
||||
@ -135,7 +135,7 @@ class PlaybackControls extends React.Component{
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
current_track: (state.core.current_track !== undefined && state.core.tracks !== undefined && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null),
|
||||
current_track: (state.core.current_track !== undefined && state.core.tracks !== undefined && state.core.tracks[state.core.current_track] !== undefined ? state.core.tracks[state.core.current_track] : null),
|
||||
radio_enabled: (state.ui.radio && state.ui.radio.enabled ? true : false),
|
||||
play_state: state.mopidy.play_state,
|
||||
time_position: state.mopidy.time_position,
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import { Link, hashHistory } from 'react-router'
|
||||
import { hashHistory } from 'react-router'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
@ -41,7 +41,7 @@ class PlaylistGrid extends React.Component{
|
||||
key={playlist.uri}
|
||||
type="playlist"
|
||||
item={playlist}
|
||||
onClick={e => {hashHistory.push(global.baseURL+'playlist/'+playlist.uri)}}
|
||||
onClick={e => {hashHistory.push(global.baseURL+'playlist/'+encodeURIComponent(playlist.uri))}}
|
||||
onContextMenu={e => this.handleContextMenu(e,playlist)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import Thumbnail from './Thumbnail'
|
||||
import URILink from './URILink'
|
||||
|
||||
export default class RelatedArtists extends React.Component{
|
||||
|
||||
@ -18,10 +18,10 @@ export default class RelatedArtists extends React.Component{
|
||||
this.props.artists.map((artist, index) => {
|
||||
if (artist.uri){
|
||||
return (
|
||||
<Link to={global.baseURL+'artist/'+ artist.uri} key={artist.uri} className="artist">
|
||||
<URILink type="artist" uri={artist.uri} key={artist.uri} className="artist">
|
||||
<Thumbnail circle={true} size="small" images={artist.images} />
|
||||
<span className="name">{ artist.name }</span>
|
||||
</Link>
|
||||
</URILink>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link, hashHistory } from 'react-router'
|
||||
import { hashHistory } from 'react-router'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
|
||||
@ -38,19 +38,19 @@ class SearchForm extends React.Component{
|
||||
switch (helpers.uriType(this.state.query)){
|
||||
|
||||
case 'album':
|
||||
hashHistory.push(global.baseURL+'album/'+this.state.query)
|
||||
hashHistory.push(global.baseURL+'album/'+encodeURIComponent(this.state.query))
|
||||
break
|
||||
|
||||
case 'artist':
|
||||
hashHistory.push(global.baseURL+'artist/'+this.state.query)
|
||||
hashHistory.push(global.baseURL+'artist/'+encodeURIComponent(this.state.query))
|
||||
break
|
||||
|
||||
case 'playlist':
|
||||
hashHistory.push(global.baseURL+'playlist/'+this.state.query)
|
||||
hashHistory.push(global.baseURL+'playlist/'+encodeURIComponent(this.state.query))
|
||||
break
|
||||
|
||||
default:
|
||||
hashHistory.push(global.baseURL+'search/iris:search:'+this.state.query)
|
||||
hashHistory.push(global.baseURL+'search/iris:search:'+encodeURIComponent(this.state.query))
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ import { Link } from 'react-router'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
import ArtistSentence from './ArtistSentence'
|
||||
import Dater from './Dater'
|
||||
import URILink from './URILink'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
|
||||
export default class Track extends React.Component{
|
||||
@ -98,6 +100,7 @@ export default class Track extends React.Component{
|
||||
var track = this.props.track
|
||||
var className = 'list-item track'
|
||||
if (this.props.selected) className += ' selected'
|
||||
if (this.props.selected) className += ' selected'
|
||||
if (this.props.can_sort) className += ' can-sort'
|
||||
if (track.type !== undefined) className += ' '+track.type
|
||||
if (track.playing) className += ' playing'
|
||||
@ -106,7 +109,7 @@ export default class Track extends React.Component{
|
||||
var album = '-'
|
||||
if (track.album){
|
||||
if (track.album.uri){
|
||||
album = <Link to={global.baseURL+'album/'+track.album.uri}>{track.album.name}</Link>
|
||||
album = <URILink type="album" uri={track.album.uri}>{track.album.name}</URILink>
|
||||
} else {
|
||||
album = <span>{track.album.name}</span>
|
||||
}
|
||||
@ -137,9 +140,9 @@ export default class Track extends React.Component{
|
||||
if (track.added_from && track.added_by){
|
||||
var type = (track.added_from ? helpers.uriType(track.added_from) : null)
|
||||
if (type == 'discover'){
|
||||
var link = <Link to={global.baseURL+'discover/recommendations/'+helpers.getFromUri('seeds',track.added_from)}>discover</Link>
|
||||
var link = <URILink type="recommendations" uri={helpers.getFromUri('seeds',track.added_from)}>discover</URILink>
|
||||
} else {
|
||||
var link = <Link to={global.baseURL+type+'/'+track.added_from}>{type}</Link>
|
||||
var link = <URILink type={type} uri={track.added_from}>{type}</URILink>
|
||||
}
|
||||
var added = <span>{track.added_by} <span className="grey-text"> (from {link})</span></span>
|
||||
|
||||
|
||||
53
src/js/components/URILink.js
Executable file
53
src/js/components/URILink.js
Executable file
@ -0,0 +1,53 @@
|
||||
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
export default class URILink extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
render(){
|
||||
var to = null;
|
||||
var uri = encodeURIComponent(this.props.uri);
|
||||
|
||||
switch (this.props.type){
|
||||
|
||||
case 'playlist':
|
||||
to = global.baseURL+'playlist/'+uri;
|
||||
break;
|
||||
|
||||
case 'artist':
|
||||
to = global.baseURL+'artist/'+uri;
|
||||
break;
|
||||
|
||||
case 'album':
|
||||
to = global.baseURL+'album/'+uri;
|
||||
break;
|
||||
|
||||
case 'track':
|
||||
to = global.baseURL+'track/'+uri;
|
||||
break;
|
||||
|
||||
case 'user':
|
||||
to = global.baseURL+'user/'+uri;
|
||||
break;
|
||||
|
||||
case 'recommendations':
|
||||
to = global.baseURL+'discover/recommendations/'+uri;
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={this.props.className ? this.props.className : null}
|
||||
to={to}>
|
||||
{this.props.children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -224,6 +224,13 @@ export let formatTracks = function(tracks){
|
||||
track.date = track.release_date;
|
||||
}
|
||||
|
||||
// Copy images from albums (if applicable)
|
||||
if (track.album && track.album.images){
|
||||
if (!track.images || track.images.length > 0){
|
||||
track.images = track.album.images;
|
||||
}
|
||||
}
|
||||
|
||||
formatted.push(track);
|
||||
}
|
||||
|
||||
|
||||
@ -26,12 +26,17 @@ export default function reducer(core = {}, action){
|
||||
var metadata = {}
|
||||
}
|
||||
|
||||
var current_tlid = null;
|
||||
if (core.current_track && core.tracks && core.tracks[core.current_track] !== undefined && core.tracks[core.current_track].tlid !== undefined){
|
||||
current_tlid = core.tracks[core.current_track].tlid;
|
||||
}
|
||||
|
||||
var track = Object.assign(
|
||||
{},
|
||||
tltrack,
|
||||
metadata,
|
||||
{
|
||||
playing: (core.current_track && tltrack.tlid == core.current_track.tlid )
|
||||
playing: (tltrack.tlid == current_tlid)
|
||||
})
|
||||
tracklist.push(track)
|
||||
}
|
||||
@ -53,17 +58,9 @@ export default function reducer(core = {}, action){
|
||||
)
|
||||
}
|
||||
|
||||
var current_track = Object.assign(
|
||||
{},
|
||||
action.data.track,
|
||||
{
|
||||
tlid: action.data.tlid
|
||||
}
|
||||
)
|
||||
|
||||
return Object.assign({}, core, {
|
||||
current_tracklist: current_tracklist,
|
||||
current_track: current_track
|
||||
current_track: action.data.track.uri
|
||||
});
|
||||
|
||||
case 'TRACK_LOADED':
|
||||
|
||||
@ -1111,7 +1111,7 @@ const MopidyMiddleware = (function(){
|
||||
{},
|
||||
response,
|
||||
{
|
||||
uri: decodeURIComponent(response.uri),
|
||||
uri: response.uri,
|
||||
type: 'playlist',
|
||||
is_mopidy: true,
|
||||
tracks: (response.tracks ? response.tracks : []),
|
||||
@ -1602,7 +1602,7 @@ const MopidyMiddleware = (function(){
|
||||
{},
|
||||
response[i].album,
|
||||
{
|
||||
uri: decodeURIComponent(response[i].album.uri),
|
||||
uri: response[i].album.uri,
|
||||
}
|
||||
);
|
||||
if (album){
|
||||
@ -1630,9 +1630,7 @@ const MopidyMiddleware = (function(){
|
||||
albums_uris: helpers.arrayOf('uri',albums),
|
||||
tracks: response.slice(0,10)
|
||||
}
|
||||
);
|
||||
artist.uri = decodeURIComponent(artist.uri);
|
||||
|
||||
);
|
||||
store.dispatch({
|
||||
type: 'ARTIST_LOADED',
|
||||
key: artist.uri,
|
||||
@ -1666,7 +1664,6 @@ const MopidyMiddleware = (function(){
|
||||
is_mopidy: true
|
||||
}
|
||||
)
|
||||
artist.uri = decodeURIComponent(artist.uri);
|
||||
artists.push(artist)
|
||||
}
|
||||
}
|
||||
@ -1698,36 +1695,38 @@ const MopidyMiddleware = (function(){
|
||||
|
||||
case 'MOPIDY_CURRENTTLTRACK':
|
||||
if (action.data && action.data.track){
|
||||
var track = helpers.formatTracks(action.data);
|
||||
|
||||
// Fire off our universal track index loader
|
||||
store.dispatch({
|
||||
type: 'TRACK_LOADED',
|
||||
key: action.data.track.uri,
|
||||
track: action.data.track
|
||||
key: track.uri,
|
||||
track: track
|
||||
});
|
||||
|
||||
// We've got Spotify running, and it's a spotify track - go straight to the source!
|
||||
if (helpers.uriSource(action.data.track.uri) == 'spotify' && store.getState().spotify.enabled){
|
||||
store.dispatch(spotifyActions.getTrack(action.data.track.uri))
|
||||
if (helpers.uriSource(track.uri) == 'spotify' && store.getState().spotify.enabled){
|
||||
store.dispatch(spotifyActions.getTrack(track.uri))
|
||||
|
||||
// Some other source, rely on Mopidy backends to do their work
|
||||
} else {
|
||||
store.dispatch(mopidyActions.getImages('tracks',[action.data.track.uri]))
|
||||
store.dispatch(mopidyActions.getImages('tracks',[track.uri]))
|
||||
}
|
||||
}
|
||||
|
||||
next(action)
|
||||
break
|
||||
next(action);
|
||||
break;
|
||||
|
||||
case 'MOPIDY_GET_TRACK':
|
||||
instruct(socket, store, 'library.lookup', action.data )
|
||||
.then(
|
||||
response => {
|
||||
if (response.length > 0){
|
||||
var track = Object.assign({}, response[0]);
|
||||
store.dispatch({
|
||||
type: 'TRACK_LOADED',
|
||||
key: decodeURIComponent(action.data.uri),
|
||||
track: response[0]
|
||||
key: track.uri,
|
||||
track: track
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -1753,27 +1752,27 @@ const MopidyMiddleware = (function(){
|
||||
|
||||
var records = []
|
||||
for (var uri in response){
|
||||
uri = decodeURIComponent(uri);
|
||||
if (response.hasOwnProperty(uri)){
|
||||
|
||||
var images = response[uri];
|
||||
images = helpers.digestMopidyImages(store.getState().mopidy, images);
|
||||
records.push({
|
||||
uri: uri,
|
||||
images: images
|
||||
})
|
||||
if (images && images.length > 0){
|
||||
records.push({
|
||||
uri: uri,
|
||||
images: images
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var action_data = {
|
||||
type: (action.context+'_LOADED').toUpperCase()
|
||||
}
|
||||
action_data[action.context] = records
|
||||
store.dispatch(action_data)
|
||||
})
|
||||
action_data[action.context] = records;
|
||||
store.dispatch(action_data);
|
||||
});
|
||||
|
||||
next(action)
|
||||
break
|
||||
next(action);
|
||||
break;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -180,7 +180,7 @@ class Playlist extends React.Component{
|
||||
{ !this.props.slim_mode ? <li className="has-tooltip"><FontAwesome name={helpers.sourceIcon(this.props.params.uri )} /><span className="tooltip">{helpers.uriSource(this.props.params.uri )} playlist</span></li> : null }
|
||||
{ this.props.playlist.owner && !this.props.slim_mode ? <li><Link to={'/user/'+this.props.playlist.owner.uri}>{this.props.playlist.owner.id}</Link></li> : null }
|
||||
{ this.props.playlist.followers ? <li>{this.props.playlist.followers.total.toLocaleString()} followers</li> : null }
|
||||
{ this.props.playlist.last_modified ? <li><Dater type="ago" data={this.props.playlist.last_modified} /></li> : null }
|
||||
{ this.props.playlist.last_modified ? <li>Edited <Dater type="ago" data={this.props.playlist.last_modified} /></li> : null }
|
||||
<li>
|
||||
{ this.props.playlist.tracks_total ? this.props.playlist.tracks_total : '0'} tracks,
|
||||
{ this.props.playlist.tracks ? <Dater type="total-time" data={this.props.playlist.tracks} /> : '0 mins' }
|
||||
|
||||
@ -9,10 +9,10 @@ import Parallax from '../components/Parallax'
|
||||
import TrackList from '../components/TrackList'
|
||||
import Track from '../components/Track'
|
||||
import Dater from '../components/Dater'
|
||||
import FullPlayer from '../components/FullPlayer'
|
||||
import ArtistSentence from '../components/ArtistSentence'
|
||||
import Thumbnail from '../components/Thumbnail'
|
||||
import Header from '../components/Header'
|
||||
import URILink from '../components/URILink'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
@ -68,23 +68,28 @@ class Queue extends React.Component{
|
||||
)
|
||||
}
|
||||
|
||||
var link = null
|
||||
var uri = null
|
||||
if (this.props.current_track.album && this.props.current_track.album.uri){
|
||||
link = '/album/'+this.props.current_track.album.uri.replace(/[/]/g,'%2F');
|
||||
uri = this.props.current_track.album.uri;
|
||||
}
|
||||
return (
|
||||
<Link className={this.props.radio_enabled ? 'artwork radio-enabled' : 'artwork'} to={link}>
|
||||
{this.props.radio_enabled ? <img className="radio-overlay" src="assets/radio-overlay.png" /> : null}
|
||||
<Thumbnail image={image} circle={this.props.radio_enabled} />
|
||||
</Link>
|
||||
<URILink
|
||||
className={this.props.radio_enabled ? 'artwork radio-enabled' : 'artwork'}
|
||||
type="album"
|
||||
uri={uri}>
|
||||
{this.props.radio_enabled ? <img className="radio-overlay" src="assets/radio-overlay.png" /> : null}
|
||||
<Thumbnail image={image} circle={this.props.radio_enabled} />
|
||||
</URILink>
|
||||
)
|
||||
}
|
||||
|
||||
render(){
|
||||
var image = null
|
||||
if (this.props.current_track && this.props.current_track.images !== undefined){
|
||||
image = helpers.sizedImages(this.props.current_track.images)
|
||||
image = image.large
|
||||
if (this.props.current_track){
|
||||
if (this.props.current_track.images !== undefined && this.props.current_track.images){
|
||||
image = helpers.sizedImages(this.props.current_track.images)
|
||||
image = image.large
|
||||
}
|
||||
}
|
||||
|
||||
var options = (
|
||||
@ -118,7 +123,7 @@ class Queue extends React.Component{
|
||||
<div className="current-track">
|
||||
{ this.renderArtwork(image) }
|
||||
<div className="title">
|
||||
{this.props.current_track ? <Link to={global.baseURL+'track/'+this.props.current_track.uri}>{this.props.current_track.name}</Link> : <span>-</span>}
|
||||
{this.props.current_track ? <URILink type="track" uri={this.props.current_track.uri}>{this.props.current_track.name}</URILink> : <span>-</span>}
|
||||
</div>
|
||||
{this.props.current_track ? <ArtistSentence artists={ this.props.current_track.artists } /> : <ArtistSentence />}
|
||||
</div>
|
||||
@ -154,7 +159,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
radio: state.core.radio,
|
||||
radio_enabled: (state.core.radio && state.core.radio.enabled ? true : false),
|
||||
current_tracklist: state.core.current_tracklist,
|
||||
current_track: (state.core.current_track !== undefined && state.core.tracks !== undefined && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null)
|
||||
current_track: (state.core.current_track !== undefined && state.core.tracks !== undefined && state.core.tracks[state.core.current_track] !== undefined ? state.core.tracks[state.core.current_track] : null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -55,31 +55,11 @@ class LibraryBrowse extends React.Component{
|
||||
if (directory[i].type && directory[i].type == 'track'){
|
||||
tracks.push(directory[i] )
|
||||
} else {
|
||||
var uri = directory[i].uri
|
||||
|
||||
// If we've navigated to a handled asset type, use our standard views
|
||||
switch (helpers.uriType(uri)){
|
||||
case 'album':
|
||||
uri = global.baseURL+'album/'+uri
|
||||
break
|
||||
|
||||
case 'artist':
|
||||
uri = global.baseURL+'artist/'+uri
|
||||
break
|
||||
|
||||
case 'playlist':
|
||||
uri = global.baseURL+'playlist/'+uri
|
||||
break
|
||||
|
||||
default:
|
||||
uri = global.baseURL+"library/browse/"+uri.replace(/[/]/g,'%2F')
|
||||
}
|
||||
|
||||
folders.push(Object.assign(
|
||||
{},
|
||||
directory[i],
|
||||
{
|
||||
uri: uri
|
||||
uri: directory[i].uri
|
||||
}
|
||||
))
|
||||
}
|
||||
@ -91,119 +71,123 @@ class LibraryBrowse extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
if (typeof(this.props.params.uri) !== 'undefined' && this.props.params.uri){
|
||||
|
||||
var title = 'Browse';
|
||||
var uri_exploded = this.props.params.uri.split(':');
|
||||
if (uri_exploded.length > 0){
|
||||
title = uri_exploded[0];
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
}
|
||||
renderDirectory(){
|
||||
var title = 'Browse';
|
||||
var uri_exploded = this.props.params.uri.split(':');
|
||||
if (uri_exploded.length > 0){
|
||||
title = uri_exploded[0];
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
}
|
||||
|
||||
var options = null;
|
||||
if (this.props.params.uri != 'local:directory'){
|
||||
options = (
|
||||
<button className="no-hover" onClick={ () => window.history.back() }>
|
||||
<FontAwesome name="reply" />
|
||||
Back
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
if (!this.props.directory || helpers.isLoading(this.props.load_queue,['mopidy_browse'])){
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header icon="music" title={title} options={options} uiActions={this.props.uiActions} />
|
||||
<div className="body-loader loading">
|
||||
<div className="loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
var items = this.arrange_directory(this.props.directory )
|
||||
var options = (
|
||||
<button className="no-hover" onClick={ () => window.history.back() }>
|
||||
<FontAwesome name="reply" />
|
||||
Back
|
||||
</button>
|
||||
);
|
||||
|
||||
if (!this.props.directory || helpers.isLoading(this.props.load_queue,['mopidy_browse'])){
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header icon="music" title={title} options={options} uiActions={this.props.uiActions} />
|
||||
<section className="content-wrapper">
|
||||
<List
|
||||
columns={[{ name: 'name', width: '100'}]}
|
||||
rows={items.folders}
|
||||
className="library-local-directory-list" />
|
||||
<TrackList
|
||||
tracks={items.tracks}
|
||||
className="library-local-track-list"
|
||||
noheader />
|
||||
</section>
|
||||
<div className="body-loader loading">
|
||||
<div className="loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
var items = this.arrange_directory(this.props.directory);
|
||||
|
||||
var grid_items = []
|
||||
if (this.props.directory){
|
||||
for (var i = 0; i < this.props.directory.length; i++){
|
||||
var directory = this.props.directory[i]
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header icon="music" title={title} options={options} uiActions={this.props.uiActions} />
|
||||
<section className="content-wrapper">
|
||||
<List
|
||||
columns={[{ name: 'name', width: '100'}]}
|
||||
rows={items.folders}
|
||||
className="library-local-directory-list"
|
||||
link_prefix={global.baseURL+'library/browse/'}
|
||||
/>
|
||||
<TrackList
|
||||
tracks={items.tracks}
|
||||
className="library-local-track-list"
|
||||
noheader />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (directory.name){
|
||||
case 'Files':
|
||||
directory.icons = ['assets/backgrounds/browse-folders.jpg']
|
||||
break
|
||||
renderIndex(){
|
||||
var grid_items = []
|
||||
if (this.props.directory){
|
||||
for (var i = 0; i < this.props.directory.length; i++){
|
||||
var directory = this.props.directory[i]
|
||||
|
||||
case 'Local media':
|
||||
directory.icons = ['assets/backgrounds/browse-folders.jpg']
|
||||
break
|
||||
switch (directory.name){
|
||||
case 'Files':
|
||||
directory.icons = ['assets/backgrounds/browse-folders.jpg']
|
||||
break
|
||||
|
||||
case 'Spotify':
|
||||
case 'Spotify Browse':
|
||||
directory.icons = ['assets/backgrounds/browse-spotify.jpg']
|
||||
break
|
||||
case 'Local media':
|
||||
directory.icons = ['assets/backgrounds/browse-folders.jpg']
|
||||
break
|
||||
|
||||
case 'Spotify Tunigo':
|
||||
case 'Tunigo':
|
||||
directory.icons = ['assets/backgrounds/browse-tunigo.jpg']
|
||||
break
|
||||
case 'Spotify':
|
||||
case 'Spotify Browse':
|
||||
directory.icons = ['assets/backgrounds/browse-spotify.jpg']
|
||||
break
|
||||
|
||||
case 'TuneIn':
|
||||
directory.icons = ['assets/backgrounds/browse-tunein.jpg']
|
||||
break
|
||||
case 'Spotify Tunigo':
|
||||
case 'Tunigo':
|
||||
directory.icons = ['assets/backgrounds/browse-tunigo.jpg']
|
||||
break
|
||||
|
||||
default:
|
||||
directory.icons = ['assets/backgrounds/browse-default.jpg']
|
||||
}
|
||||
case 'TuneIn':
|
||||
directory.icons = ['assets/backgrounds/browse-tunein.jpg']
|
||||
break
|
||||
|
||||
grid_items.push({
|
||||
name: directory.name,
|
||||
link: global.baseURL+'library/browse/'+directory.uri.replace(/[/]/g,'%2F'),
|
||||
icons: directory.icons
|
||||
})
|
||||
default:
|
||||
directory.icons = ['assets/backgrounds/browse-default.jpg']
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header icon="folder" title="Browse" />
|
||||
<section className="content-wrapper">
|
||||
<div className="grid category-grid">
|
||||
{
|
||||
grid_items.map(
|
||||
(item, index) => {
|
||||
return (
|
||||
<GridItem
|
||||
item={item}
|
||||
key={index}
|
||||
onClick={e => hashHistory.push(item.link)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
grid_items.push({
|
||||
name: directory.name,
|
||||
link: global.baseURL+'library/browse/'+encodeURIComponent(directory.uri),
|
||||
icons: directory.icons
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header icon="folder" title="Browse" />
|
||||
<section className="content-wrapper">
|
||||
<div className="grid category-grid">
|
||||
{
|
||||
grid_items.map(
|
||||
(item, index) => {
|
||||
return (
|
||||
<GridItem
|
||||
item={item}
|
||||
key={index}
|
||||
onClick={e => hashHistory.push(item.link)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(){
|
||||
if (this.props.params.uri !== undefined && this.props.params.uri){
|
||||
return this.renderDirectory();
|
||||
} else {
|
||||
return this.renderIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user