Merge branch '47-context'
This commit is contained in:
@ -1,13 +1,36 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import GridItem from './GridItem'
|
||||
import { connect } from 'react-redux'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import { hashHistory } from 'react-router'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
|
||||
export default class AlbumGrid extends React.Component{
|
||||
import Thumbnail from './Thumbnail'
|
||||
import ArtistSentence from './ArtistSentence'
|
||||
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
|
||||
class AlbumGrid extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
handleClick(e,link){
|
||||
if( e.target.tagName.toLowerCase() !== 'a' ){
|
||||
hashHistory.push(link)
|
||||
}
|
||||
}
|
||||
|
||||
handleContextMenu(e,item){
|
||||
e.preventDefault()
|
||||
var data = {
|
||||
uris: [item.uri],
|
||||
item: item
|
||||
}
|
||||
this.props.uiActions.showContextMenu( e, data, 'album', 'click' )
|
||||
}
|
||||
|
||||
render(){
|
||||
if( this.props.albums ){
|
||||
var className = "grid album-grid"
|
||||
@ -17,7 +40,18 @@ export default class AlbumGrid extends React.Component{
|
||||
{
|
||||
this.props.albums.map(
|
||||
(album, index) => {
|
||||
return <GridItem item={album} key={index} link={global.baseURL+'album/'+album.uri} />
|
||||
return (
|
||||
<div className="grid-item"
|
||||
key={index}
|
||||
onClick={ (e) => this.handleClick(e,global.baseURL+'album/'+album.uri) }
|
||||
onContextMenu={e => this.handleContextMenu(e,album)}>
|
||||
<Thumbnail size="medium" images={album.images} />
|
||||
<div className="name">{ album.name }</div>
|
||||
<div className="secondary">
|
||||
{ album.artists ? <ArtistSentence artists={album.artists} /> : <span>-</span> }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -28,3 +62,15 @@ export default class AlbumGrid extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AlbumGrid)
|
||||
|
||||
|
||||
@ -1,13 +1,28 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import GridItem from './GridItem'
|
||||
import { connect } from 'react-redux'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
export default class ArtistGrid extends React.Component{
|
||||
import Thumbnail from './Thumbnail'
|
||||
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
|
||||
class ArtistGrid extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
handleContextMenu(e,item){
|
||||
e.preventDefault()
|
||||
var data = {
|
||||
uris: [item.uri],
|
||||
item: item
|
||||
}
|
||||
this.props.uiActions.showContextMenu( e, data, 'artist', 'click' )
|
||||
}
|
||||
|
||||
render(){
|
||||
if( this.props.artists ){
|
||||
var className = "grid artist-grid"
|
||||
@ -17,7 +32,19 @@ export default class ArtistGrid extends React.Component{
|
||||
{
|
||||
this.props.artists.map(
|
||||
(artist, index) => {
|
||||
return <GridItem item={artist} key={index} link={global.baseURL+'artist/'+artist.uri} />
|
||||
return (
|
||||
<Link
|
||||
className="grid-item"
|
||||
to={global.baseURL+'artist/'+artist.uri}
|
||||
key={index}
|
||||
onContextMenu={e => this.handleContextMenu(e,artist)}>
|
||||
<Thumbnail size="medium" images={artist.images} />
|
||||
<div className="name">{ artist.name }</div>
|
||||
<div className="secondary">
|
||||
{artist.followers ? artist.followers.total.toLocaleString()+' followers' : <span>0 followers</span>}
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -28,3 +55,15 @@ export default class ArtistGrid extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ArtistGrid)
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
@ -7,6 +8,7 @@ import FontAwesome from 'react-fontawesome'
|
||||
import TrackList from './TrackList'
|
||||
import * as helpers from '../helpers'
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
import * as pusherActions from '../services/pusher/actions'
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
import * as spotifyActions from '../services/spotify/actions'
|
||||
|
||||
@ -41,63 +43,64 @@ class ContextMenu extends React.Component{
|
||||
}
|
||||
|
||||
playQueueItem(){
|
||||
var selectedTracks = this.props.context_menu.data.selected_tracks;
|
||||
this.props.mopidyActions.changeTrack( selectedTracks[0].tlid );
|
||||
var tracks = this.props.context_menu.data.items;
|
||||
this.props.mopidyActions.changeTrack( tracks[0].tlid );
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
removeFromQueue(){
|
||||
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 );
|
||||
var tracks = this.props.context_menu.data.items;
|
||||
var tracks_tlids = [];
|
||||
for( var i = 0; i < tracks.length; i++ ){
|
||||
tracks_tlids.push( tracks[i].tlid );
|
||||
}
|
||||
this.props.mopidyActions.removeTracks( selected_tracks_tlids );
|
||||
this.props.mopidyActions.removeTracks( tracks_tlids );
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
playItems(){
|
||||
var selected_tracks_uris = helpers.asURIs(this.props.context_menu.data.selected_tracks)
|
||||
this.props.mopidyActions.playURIs(selected_tracks_uris);
|
||||
playURIs(){
|
||||
this.props.mopidyActions.playURIs(this.props.context_menu.data.uris);
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
playItemsNext(){
|
||||
var selected_tracks_uris = helpers.asURIs(this.props.context_menu.data.selected_tracks)
|
||||
this.props.mopidyActions.enqueueTracksNext(selected_tracks_uris);
|
||||
playURIsNext(){
|
||||
this.props.mopidyActions.enqueueURIsNext(this.props.context_menu.data.uris);
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
addToPlaylist(){
|
||||
var selected_tracks_uris = helpers.asURIs(this.props.context_menu.data.selected_tracks)
|
||||
this.props.uiActions.openModal( 'add_to_playlist', { tracks_uris: selected_tracks_uris } )
|
||||
this.props.uiActions.openModal( 'add_to_playlist', { tracks_uris: this.props.context_menu.data.uris } )
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
addToQueue(){
|
||||
var selected_tracks_uris = helpers.asURIs(this.props.context_menu.data.selected_tracks)
|
||||
this.props.mopidyActions.enqueueTracks(selected_tracks_uris);
|
||||
this.props.uiActions.hideContextMenu();
|
||||
this.props.mopidyActions.enqueueURIs(this.props.context_menu.data.uris)
|
||||
this.props.uiActions.hideContextMenu()
|
||||
}
|
||||
|
||||
addTracksToPlaylist( playlist_uri ){
|
||||
var selected_tracks_uris = helpers.asURIs(this.props.context_menu.data.selected_tracks)
|
||||
this.props.uiActions.addTracksToPlaylist( playlist_uri, selected_tracks_uris )
|
||||
this.props.uiActions.addTracksToPlaylist( playlist_uri, this.props.context_menu.data.uris )
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
removeFromPlaylist(){
|
||||
this.props.uiActions.removeTracksFromPlaylist( this.props.playlist.uri, this.props.context_menu.data.selected_tracks_indexes )
|
||||
this.props.uiActions.removeTracksFromPlaylist( this.props.playlist.uri, this.props.context_menu.data.indexes )
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
copyURIs(){
|
||||
var uris = '';
|
||||
for( var i = 0; i < this.props.context_menu.data.selected_tracks.length; i++ ){
|
||||
if( i > 0 ) uris += ','
|
||||
uris += this.props.context_menu.data.selected_tracks[i].uri
|
||||
}
|
||||
console.log('Yet to be implemented', uris)
|
||||
startRadio(){
|
||||
this.props.pusherActions.startRadio(this.props.context_menu.data.uris)
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
copyURIs(e){
|
||||
var temp = $("<input>");
|
||||
$("body").append(temp);
|
||||
temp.val(this.props.context_menu.data.uris.join(',')).select();
|
||||
document.execCommand("copy");
|
||||
temp.remove();
|
||||
|
||||
this.props.uiActions.createNotification( "Copied "+this.props.context_menu.data.uris.length+" URIs" )
|
||||
this.props.uiActions.hideContextMenu()
|
||||
}
|
||||
|
||||
@ -130,7 +133,7 @@ class ContextMenu extends React.Component{
|
||||
)
|
||||
}
|
||||
|
||||
getItems(trigger){
|
||||
getItems(){
|
||||
switch (this.props.context_menu.context) {
|
||||
|
||||
case 'queue':
|
||||
@ -142,12 +145,39 @@ class ContextMenu extends React.Component{
|
||||
]
|
||||
break
|
||||
|
||||
case 'editable-playlist':
|
||||
case 'album':
|
||||
var items = [
|
||||
{ handleClick: 'playItems', label: 'Play', icon: 'play' },
|
||||
{ handleClick: 'playItemsNext', label: 'Play next', icon: 'play' },
|
||||
{ handleClick: 'playURIs', label: 'Play', icon: 'play' },
|
||||
{ handleClick: 'playURIsNext', label: 'Play next', icon: 'play' },
|
||||
{ handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' },
|
||||
{ handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' },
|
||||
{ handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' }
|
||||
]
|
||||
break
|
||||
|
||||
case 'artist':
|
||||
var items = [
|
||||
{ handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' },
|
||||
{ handleClick: 'toggleFollow', label: 'Follow/unfollow', icon: 'toggle-on' },
|
||||
{ handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' }
|
||||
]
|
||||
break
|
||||
|
||||
case 'playlist':
|
||||
var items = [
|
||||
{ handleClick: 'playURIs', label: 'Play', icon: 'play' },
|
||||
{ handleClick: 'toggleFollow', label: 'Follow/unfollow', icon: 'toggle-on' },
|
||||
{ handleClick: 'copyURIs', label: 'Copy URI', icon: 'copy' }
|
||||
]
|
||||
break
|
||||
|
||||
case 'editable-playlist-track':
|
||||
var items = [
|
||||
{ handleClick: 'playURIs', label: 'Play', icon: 'play' },
|
||||
{ handleClick: 'playURIsNext', label: 'Play next', icon: 'play' },
|
||||
{ handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' },
|
||||
{ handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true },
|
||||
{ handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' },
|
||||
{ handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' },
|
||||
{ handleClick: 'removeFromPlaylist', label: 'Remove', icon: 'trash' }
|
||||
]
|
||||
@ -155,10 +185,11 @@ class ContextMenu extends React.Component{
|
||||
|
||||
default:
|
||||
var items = [
|
||||
{ handleClick: 'playItems', label: 'Play', icon: 'play' },
|
||||
{ handleClick: 'playItemsNext', label: 'Play next', icon: 'play' },
|
||||
{ handleClick: 'playURIs', label: 'Play', icon: 'play' },
|
||||
{ handleClick: 'playURIsNext', label: 'Play next', icon: 'play' },
|
||||
{ handleClick: 'addToQueue', label: 'Add to queue', icon: 'plus' },
|
||||
{ handleClick: 'addToPlaylist', label: 'Add to playlist', icon: 'plus', playlists: true },
|
||||
{ handleClick: 'startRadio', label: 'Start radio', icon: 'spotify' },
|
||||
{ handleClick: 'copyURIs', label: 'Copy URIs', icon: 'copy' }
|
||||
]
|
||||
break
|
||||
@ -167,17 +198,24 @@ class ContextMenu extends React.Component{
|
||||
return items
|
||||
}
|
||||
|
||||
renderItems(trigger){
|
||||
var items = this.getItems(trigger)
|
||||
|
||||
var closeItem = (
|
||||
<span className="menu-item-wrapper cancel">
|
||||
<a className="menu-item" onClick={ (e) => this.closeAndDeselectTracks(e) }>
|
||||
<FontAwesome className="icon" fixedWidth name='close' />
|
||||
<span className="label">Cancel</span>
|
||||
</a>
|
||||
</span>
|
||||
renderTitle(){
|
||||
var item = this.props.context_menu.data.item
|
||||
var style = null
|
||||
if (item && item.images){
|
||||
style = {
|
||||
backgroundImage: 'url('+helpers.sizedImages(item.images).medium+')'
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Link className="title" to={global.baseURL+helpers.uriType(item.uri)+'/'+item.uri}>
|
||||
{style ? <div className="background" style={style}></div> : null}
|
||||
<div className="text">{item.name}</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
renderItems(){
|
||||
var items = this.getItems()
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -186,7 +224,7 @@ class ContextMenu extends React.Component{
|
||||
if( item.playlists ){
|
||||
return (
|
||||
<span key={item.handleClick} className="menu-item-wrapper has-submenu">
|
||||
<a className="menu-item" onClick={ (e) => this[item.handleClick](e) }>
|
||||
<a className="menu-item" onClick={e => this[item.handleClick](e)}>
|
||||
<FontAwesome className="icon" fixedWidth name={item.icon} />
|
||||
<span className="label">{ item.label }</span>
|
||||
<FontAwesome className="submenu-icon" name="caret-right" />
|
||||
@ -197,7 +235,7 @@ class ContextMenu extends React.Component{
|
||||
}else{
|
||||
return (
|
||||
<span className="menu-item-wrapper" key={item.handleClick}>
|
||||
<a className="menu-item" onClick={ (e) => this[item.handleClick](e) }>
|
||||
<a className="menu-item" onClick={e => this[item.handleClick](e)}>
|
||||
<FontAwesome className="icon" fixedWidth name={item.icon} />
|
||||
<span className="label">{ item.label }</span>
|
||||
</a>
|
||||
@ -206,7 +244,6 @@ class ContextMenu extends React.Component{
|
||||
}
|
||||
})
|
||||
}
|
||||
{ trigger == 'touch' ? closeItem : null }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -241,7 +278,8 @@ class ContextMenu extends React.Component{
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
{ this.renderItems(trigger) }
|
||||
{this.props.context_menu.data.item ? this.renderTitle() : null}
|
||||
{this.renderItems(trigger)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -261,6 +299,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
pusherActions: bindActionCreators(pusherActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch)
|
||||
}
|
||||
|
||||
50
src/js/components/DebugInfo.js
Executable file
50
src/js/components/DebugInfo.js
Executable file
@ -0,0 +1,50 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
|
||||
class DebugInfo extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div className="debug-info">
|
||||
<div className="item">
|
||||
{this.props.ui.albums ? Object.keys(this.props.ui.albums).length : '0'} albums
|
||||
</div>
|
||||
<div className="item">
|
||||
{this.props.ui.artists ? Object.keys(this.props.ui.artists).length : '0'} artists
|
||||
</div>
|
||||
<div className="item">
|
||||
{this.props.ui.playlists ? Object.keys(this.props.ui.playlists).length : '0'} playlists
|
||||
</div>
|
||||
<div className="item">
|
||||
{this.props.ui.users ? Object.keys(this.props.ui.users).length : '0'} users
|
||||
</div>
|
||||
<div className="item">
|
||||
{'ontouchstart' in document.documentElement ? 'can touch' : 'no touch'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
ui: state.ui
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DebugInfo)
|
||||
@ -1,9 +1,16 @@
|
||||
|
||||
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 GridItem from './GridItem'
|
||||
|
||||
export default class GridSlider extends React.Component{
|
||||
import ArtistSentence from './ArtistSentence'
|
||||
import Thumbnail from './Thumbnail'
|
||||
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
|
||||
class GridSlider extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
@ -15,6 +22,21 @@ export default class GridSlider extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
handleClick(e,link){
|
||||
if( e.target.tagName.toLowerCase() !== 'a' ){
|
||||
hashHistory.push(link)
|
||||
}
|
||||
}
|
||||
|
||||
handleContextMenu(e,item){
|
||||
e.preventDefault()
|
||||
var data = {
|
||||
uris: [item.uri],
|
||||
item: item
|
||||
}
|
||||
this.props.uiActions.showContextMenu( e, data, 'album', 'click' )
|
||||
}
|
||||
|
||||
next(){
|
||||
if (this.state.page >= this._pagelimit) return false
|
||||
this.setState({ page: this.state.page + 1 })
|
||||
@ -47,8 +69,19 @@ export default class GridSlider extends React.Component{
|
||||
{
|
||||
this.props.tracks.map(
|
||||
(track, index) => {
|
||||
var item = Object.assign({}, track.album, { artists: track.artists })
|
||||
return <GridItem item={item} key={index} link={global.baseURL+'album/'+track.album.uri} />
|
||||
var album = Object.assign({}, track.album, { artists: track.artists })
|
||||
return (
|
||||
<div className="grid-item"
|
||||
key={index}
|
||||
onClick={ (e) => this.handleClick(e,global.baseURL+'album/'+album.uri) }
|
||||
onContextMenu={e => this.handleContextMenu(e,album)}>
|
||||
<Thumbnail size="medium" images={album.images} />
|
||||
<div className="name">{ album.name }</div>
|
||||
<div className="secondary">
|
||||
{ album.artists ? <ArtistSentence artists={album.artists} /> : <span>-</span> }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -61,3 +94,15 @@ export default class GridSlider extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(GridSlider)
|
||||
|
||||
|
||||
@ -19,8 +19,8 @@ export default class LazyLoadListener extends React.Component{
|
||||
}
|
||||
|
||||
handleScroll(e){
|
||||
if( (window.innerHeight + window.scrollY) >= document.body.offsetHeight ){
|
||||
if( !this.state.loading ){
|
||||
if( (window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 80) ){
|
||||
if( !this.state.loading && this.props.enabled ){
|
||||
this.setState({ loading: true })
|
||||
this.props.loadMore();
|
||||
}
|
||||
@ -30,6 +30,10 @@ export default class LazyLoadListener extends React.Component{
|
||||
}
|
||||
|
||||
render(){
|
||||
return <div className='lazy-loader'></div>
|
||||
return (
|
||||
<div className={this.state.loading ? "lazy-loader loading" : "lazy-loader"}>
|
||||
<div className="loader"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,17 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Link, hashHistory } from 'react-router'
|
||||
import { connect } from 'react-redux'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import { hashHistory, Link } from 'react-router'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
|
||||
import ArtistSentence from './ArtistSentence'
|
||||
import Dater from './Dater'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
|
||||
export default class List extends React.Component{
|
||||
class List extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -22,6 +25,15 @@ export default class List extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
handleContextMenu(e,item){
|
||||
e.preventDefault()
|
||||
var data = {
|
||||
uris: [item.uri],
|
||||
item: item
|
||||
}
|
||||
this.props.uiActions.showContextMenu(e, data, helpers.uriType(item.uri), 'click')
|
||||
}
|
||||
|
||||
renderHeader(){
|
||||
if (!this.props.columns || this.props.noheader) return null
|
||||
|
||||
@ -71,7 +83,11 @@ export default class List extends React.Component{
|
||||
if( row.type ) className += ' '+row.type
|
||||
|
||||
return (
|
||||
<div onClick={ e => this.handleClick(e, row.uri)} className={className} key={row_index}>
|
||||
<div
|
||||
onClick={e => this.handleClick(e, row.uri)}
|
||||
onContextMenu={e => this.handleContextMenu(e,row)}
|
||||
className={className}
|
||||
key={row_index}>
|
||||
{
|
||||
this.props.columns.map( (col, col_index) => {
|
||||
return (
|
||||
@ -89,4 +105,16 @@ export default class List extends React.Component{
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(List)
|
||||
|
||||
@ -1,13 +1,29 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import GridItem from './GridItem'
|
||||
import { connect } from 'react-redux'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import { Link } from 'react-router'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
|
||||
export default class PlaylistGrid extends React.Component{
|
||||
import Thumbnail from './Thumbnail'
|
||||
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
|
||||
class PlaylistGrid extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
handleContextMenu(e,item){
|
||||
e.preventDefault()
|
||||
var data = {
|
||||
uris: [item.uri],
|
||||
item: item
|
||||
}
|
||||
this.props.uiActions.showContextMenu( e, data, 'playlist', 'click' )
|
||||
}
|
||||
|
||||
render(){
|
||||
if( !this.props.playlists ) return null
|
||||
|
||||
@ -17,8 +33,21 @@ export default class PlaylistGrid extends React.Component{
|
||||
<div className={className}>
|
||||
{
|
||||
this.props.playlists.map(
|
||||
(playlist, index) => {
|
||||
return <GridItem item={playlist} key={index} link={global.baseURL+'playlist/'+playlist.uri} />
|
||||
(playlist, index) => {
|
||||
return (
|
||||
<Link
|
||||
className="grid-item"
|
||||
to={global.baseURL+'playlist/'+playlist.uri}
|
||||
key={index}
|
||||
onContextMenu={e => this.handleContextMenu(e,playlist)}>
|
||||
<Thumbnail size="medium" images={playlist.images} />
|
||||
<div className="name">{ playlist.name }</div>
|
||||
<div className="secondary">
|
||||
{ playlist.tracks_total ? playlist.tracks_total+' tracks' : <span>0 tracks</span> }
|
||||
{ playlist.can_edit ? <FontAwesome name="edit" /> : null }
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -27,3 +56,15 @@ export default class PlaylistGrid extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PlaylistGrid)
|
||||
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
|
||||
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={global.baseURL+'user/'+this.props.item.owner.id}>
|
||||
{ this.props.item.owner.id }
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
render(){
|
||||
if( !this.props.item ) return null
|
||||
|
||||
return (
|
||||
<ListItem extraClasses="playlist" link={global.baseURL+'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 @@ class Sidebar extends React.Component{
|
||||
|
||||
<section>
|
||||
<title>Discover</title>
|
||||
<Link activeClassName="active" to={global.baseURL+"discover"}>
|
||||
<Link activeClassName="active" disabled={!this.props.spotify_authorized} to={this.props.spotify_authorized ? global.baseURL+"discover" : null}>
|
||||
<Icon name="compass" className="white" />
|
||||
Discover
|
||||
</Link>
|
||||
|
||||
@ -5,6 +5,7 @@ import { bindActionCreators } from 'redux'
|
||||
|
||||
import Track from './Track'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
|
||||
@ -78,8 +79,9 @@ class TrackList extends React.Component{
|
||||
var selected_tracks = this.selectedTracks()
|
||||
if( selected_tracks.length > 0 ){
|
||||
var data = {
|
||||
selected_tracks: selected_tracks,
|
||||
selected_tracks_indexes: this.tracksIndexes( selected_tracks )
|
||||
items: selected_tracks,
|
||||
uris: helpers.asURIs(selected_tracks),
|
||||
indexes: this.tracksIndexes(selected_tracks)
|
||||
}
|
||||
this.props.uiActions.showContextMenu( e, data, this.props.context, 'touch' )
|
||||
}else{
|
||||
@ -133,8 +135,9 @@ class TrackList extends React.Component{
|
||||
handleContextMenu(e, index){
|
||||
var selected_tracks = this.selectedTracks()
|
||||
var data = {
|
||||
selected_tracks: selected_tracks,
|
||||
selected_tracks_indexes: this.tracksIndexes( selected_tracks )
|
||||
items: selected_tracks,
|
||||
uris: helpers.asURIs(selected_tracks),
|
||||
indexes: this.tracksIndexes(selected_tracks)
|
||||
}
|
||||
this.props.uiActions.showContextMenu( e, data, this.props.context, 'click' )
|
||||
}
|
||||
|
||||
@ -161,6 +161,7 @@ export let getFromUri = function( element, uri ){
|
||||
if( element == 'userid' && exploded[1] == 'user' ) return exploded[2];
|
||||
if( element == 'playlistid' && exploded[3] == 'playlist' ) return exploded[4];
|
||||
if( element == 'artistid' && exploded[1] == 'artist' ) return exploded[2];
|
||||
if( element == 'artistid' && exploded[3] == 'playlist' ) return exploded[2];
|
||||
if( element == 'albumid' && exploded[1] == 'album' ) return exploded[2];
|
||||
if( element == 'trackid' && exploded[1] == 'track' ) return exploded[2];
|
||||
return null;
|
||||
|
||||
@ -56,15 +56,15 @@ export function playURIs( uris ){
|
||||
}
|
||||
}
|
||||
|
||||
export function enqueueTracksNext( uris ){
|
||||
export function enqueueURIsNext( uris ){
|
||||
if( typeof(uris) !== 'object' ) uris = [uris]
|
||||
return {
|
||||
type: 'MOPIDY_ENQUEUE_TRACKS_NEXT',
|
||||
type: 'MOPIDY_ENQUEUE_URIS_NEXT',
|
||||
uris: uris
|
||||
}
|
||||
}
|
||||
|
||||
export function enqueueTracks( uris, at_position = false ){
|
||||
export function enqueueURIs( uris, at_position = false ){
|
||||
if( typeof(uris) !== 'object' ) uris = [uris];
|
||||
var value = { uris: uris };
|
||||
if( at_position ) value.at_position = at_position;
|
||||
|
||||
@ -3,6 +3,7 @@ import Mopidy from 'mopidy'
|
||||
import * as helpers from '../../helpers'
|
||||
|
||||
var mopidyActions = require('./actions.js')
|
||||
var uiActions = require('../ui/actions.js')
|
||||
var lastfmActions = require('../lastfm/actions.js')
|
||||
var pusherActions = require('../pusher/actions.js')
|
||||
|
||||
@ -229,7 +230,7 @@ const MopidyMiddleware = (function(){
|
||||
store.dispatch({ type: 'MOPIDY_URISCHEMES_FILTERED', data: uri_schemes });
|
||||
break;
|
||||
|
||||
case 'MOPIDY_ENQUEUE_TRACKS_NEXT':
|
||||
case 'MOPIDY_ENQUEUE_URIS_NEXT':
|
||||
|
||||
var current_track = store.getState().ui.current_track
|
||||
var current_tracklist = store.getState().ui.current_tracklist
|
||||
@ -268,7 +269,7 @@ const MopidyMiddleware = (function(){
|
||||
// add the rest of our uris (if any)
|
||||
action.uris.shift();
|
||||
if( action.uris.length > 0 ){
|
||||
store.dispatch( mopidyActions.enqueueTracks( action.uris, 1 ) )
|
||||
store.dispatch( mopidyActions.enqueueURIs( action.uris, 1 ) )
|
||||
}
|
||||
})
|
||||
break;
|
||||
|
||||
@ -214,7 +214,7 @@ const PusherMiddleware = (function(){
|
||||
})
|
||||
.then(
|
||||
response => {
|
||||
uiActions.createNotification('Starting radio...')
|
||||
store.dispatch(uiActions.createNotification('Starting radio...'))
|
||||
}
|
||||
)
|
||||
|
||||
@ -252,11 +252,11 @@ const PusherMiddleware = (function(){
|
||||
})
|
||||
.then(
|
||||
response => {
|
||||
uiActions.createNotification('Stopping radio')
|
||||
store.dispatch(uiActions.createNotification('Stopping radio'))
|
||||
}
|
||||
)
|
||||
|
||||
store.dispatch( uiActions.createNotification('Stopping radio') )
|
||||
store.dispatch(uiActions.createNotification('Stopping radio'))
|
||||
var data = {
|
||||
action: 'stop_radio',
|
||||
seed_artists: [],
|
||||
|
||||
@ -532,10 +532,9 @@ export function resolveRadioSeeds( radio ){
|
||||
|
||||
// add to our list of async requests
|
||||
requests.push(
|
||||
sendRequest( dispatch, getState, 'tracks/'+ track_ids )
|
||||
sendRequest( dispatch, getState, 'tracks?ids='+ track_ids )
|
||||
.then( response => {
|
||||
if (!(response instanceof Array)) response = [response]
|
||||
Object.assign(resolved_seeds.seed_tracks, response);
|
||||
Object.assign(resolved_seeds.seed_tracks, response.tracks);
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -56,6 +56,11 @@ const SpotifyMiddleware = (function(){
|
||||
store.dispatch( uiActions.createNotification( "Must be logged in to Spotify to do that", 'bad' ) )
|
||||
return
|
||||
}
|
||||
|
||||
if( !store.getState().spotify.me || store.getState().spotify.me.id != helpers.getFromUri('artistid',action.key) ){
|
||||
store.dispatch( uiActions.createNotification( "You can't edit a playlist you don't own", 'bad' ) )
|
||||
return
|
||||
}
|
||||
store.dispatch( spotifyActions.reorderPlaylistTracks( action.key, action.range_start, action.range_length, action.insert_before, action.snapshot_id ))
|
||||
break
|
||||
|
||||
@ -89,7 +94,7 @@ const SpotifyMiddleware = (function(){
|
||||
|
||||
// only resolve if radio is enabled
|
||||
if( action.data.radio.enabled ){
|
||||
store.dispatch( spotifyActions.resolveRadioSeeds( action.data.radio ) )
|
||||
store.dispatch(spotifyActions.resolveRadioSeeds(action.data.radio))
|
||||
}
|
||||
break
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import LazyLoadListener from '../components/LazyLoadListener'
|
||||
import SidebarToggleButton from '../components/SidebarToggleButton'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
import * as mopidyActions from '../services/mopidy/actions'
|
||||
import * as spotifyActions from '../services/spotify/actions'
|
||||
|
||||
@ -28,6 +29,12 @@ class Album extends React.Component{
|
||||
this.loadAlbum()
|
||||
}
|
||||
|
||||
handleContextMenu(e){
|
||||
e.preventDefault()
|
||||
var data = { uris: [this.props.params.uri] }
|
||||
this.props.uiActions.showContextMenu( e, data, 'album', 'click' )
|
||||
}
|
||||
|
||||
componentWillReceiveProps( nextProps ){
|
||||
|
||||
// if our URI has changed, fetch new album
|
||||
@ -66,7 +73,6 @@ class Album extends React.Component{
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
if( !this.props.album.tracks_more ) return
|
||||
this.props.spotifyActions.getURL( this.props.album.tracks_more, 'SPOTIFY_ALBUM_LOADED_MORE' );
|
||||
}
|
||||
|
||||
@ -92,7 +98,7 @@ class Album extends React.Component{
|
||||
<div className="view album-view">
|
||||
<SidebarToggleButton />
|
||||
<div className="intro">
|
||||
<Thumbnail size="large" canZoom images={ this.props.album.images } />
|
||||
<Thumbnail size="large" canZoom images={ this.props.album.images } handleContextMenu={ e => this.handleContextMenu(e) } />
|
||||
<ArtistGrid artists={artists} />
|
||||
|
||||
<div className="actions">
|
||||
@ -118,7 +124,7 @@ class Album extends React.Component{
|
||||
|
||||
<section className="list-wrapper">
|
||||
{ this.props.album.tracks ? <TrackList tracks={ this.props.album.tracks } /> : null }
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
<LazyLoadListener enabled={this.props.album.tracks_more} loadMore={ () => this.loadMore() }/>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@ -146,6 +152,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch)
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import ContextMenu from '../components/ContextMenu'
|
||||
import Dragger from '../components/Dragger'
|
||||
import Modal from '../components/Modal/Modal'
|
||||
import Notifications from '../components/Notifications'
|
||||
import DebugInfo from '../components/DebugInfo'
|
||||
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
import * as pusherActions from '../services/pusher/actions'
|
||||
@ -132,6 +133,7 @@ class App extends React.Component{
|
||||
<Dragger />
|
||||
<Modal />
|
||||
<Notifications />
|
||||
{this.props.debug_info ? <DebugInfo /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -151,7 +153,8 @@ const mapStateToProps = (state, ownProps) => {
|
||||
sidebar_open: state.ui.sidebar_open,
|
||||
dragger: state.ui.dragger,
|
||||
modal: state.ui.modal,
|
||||
context_menu: state.ui.context_menu
|
||||
context_menu: state.ui.context_menu,
|
||||
debug_info: state.ui.debug_info
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -81,7 +81,6 @@ class Artist extends React.Component{
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
if( !this.props.artist.albums_more ) return
|
||||
this.props.spotifyActions.getURL( this.props.artist.albums_more, 'SPOTIFY_ARTIST_ALBUMS_LOADED', this.props.params.uri );
|
||||
}
|
||||
|
||||
@ -164,7 +163,7 @@ class Artist extends React.Component{
|
||||
<h4 className="left-padding">Albums</h4>
|
||||
<section className="grid-wrapper no-top-padding">
|
||||
<AlbumGrid albums={albums} />
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
<LazyLoadListener enabled={this.props.artist.albums_more} loadMore={ () => this.loadMore() }/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -87,6 +87,14 @@ class Debug extends React.Component{
|
||||
onChange={ e => this.props.uiActions.set({ log_actions: !this.props.log_actions })} />
|
||||
<span className="label">Log actions in developer console</span>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="debug_info"
|
||||
checked={ this.props.debug_info }
|
||||
onChange={ e => this.props.uiActions.set({ debug_info: !this.props.debug_info })} />
|
||||
<span className="label">Show debug info</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -161,6 +169,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
connectionid: state.pusher.connectionid,
|
||||
emulate_touch: state.ui.emulate_touch,
|
||||
log_actions: state.ui.log_actions,
|
||||
debug_info: state.ui.debug_info,
|
||||
debug_response: state.ui.debug_response
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +54,6 @@ class Playlist extends React.Component{
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
if( !this.props.playlist.tracks_more ) return
|
||||
this.props.spotifyActions.getURL( this.props.playlist.tracks_more, 'PLAYLIST_LOADED_MORE_TRACKS', this.props.playlist.uri );
|
||||
}
|
||||
|
||||
@ -154,8 +153,8 @@ class Playlist extends React.Component{
|
||||
</div>
|
||||
|
||||
<section className="list-wrapper">
|
||||
{ this.props.playlist.tracks ? <TrackList context={ this.props.playlist.can_edit ? 'editable-playlist' : 'playlist'} tracks={ this.props.playlist.tracks } removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null }
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
{ this.props.playlist.tracks ? <TrackList context={ this.props.playlist.can_edit ? 'editable-playlist-track' : 'playlist-track'} tracks={ this.props.playlist.tracks } removeTracks={ tracks_indexes => this.removeTracks(tracks_indexes) } reorderTracks={ (indexes, index) => this.reorderTracks(indexes, index) } /> : null }
|
||||
<LazyLoadListener enabled={this.props.playlist.tracks_more} loadMore={ () => this.loadMore() }/>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
@ -67,7 +67,7 @@ class Queue extends React.Component{
|
||||
}
|
||||
}
|
||||
if (this.props.radio.seed_genres.length > 0) seed_sentence =this.props.radio.seed_artists.length+' genres'
|
||||
if (this.props.radio.seed_tracks.length > 0) seed_sentence =this.props.radio.seed_artists.length+' tracks'
|
||||
if (this.props.radio.seed_tracks.length > 0) seed_sentence =this.props.radio.seed_tracks.length+' tracks'
|
||||
|
||||
return (
|
||||
<div className="radio">
|
||||
|
||||
@ -47,9 +47,7 @@ class Search extends React.Component{
|
||||
}
|
||||
|
||||
loadMore(type){
|
||||
if( this.props[type+'_more'] ){
|
||||
this.props.spotifyActions.getURL( this.props[type+'_more'], 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_'+type.toUpperCase());
|
||||
}
|
||||
this.props.spotifyActions.getURL( this.props[type+'_more'], 'SPOTIFY_SEARCH_RESULTS_LOADED_MORE_'+type.toUpperCase());
|
||||
}
|
||||
|
||||
renderResults(){
|
||||
@ -91,7 +89,7 @@ class Search extends React.Component{
|
||||
<div>
|
||||
<section className="grid-wrapper">
|
||||
<ArtistGrid artists={artists} />
|
||||
<LazyLoadListener loadMore={ () => this.loadMore('artists') }/>
|
||||
<LazyLoadListener enabled={this.props['artists_more']} loadMore={ () => this.loadMore('artists') }/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
@ -102,7 +100,7 @@ class Search extends React.Component{
|
||||
<div>
|
||||
<section className="grid-wrapper">
|
||||
<AlbumGrid albums={albums} />
|
||||
<LazyLoadListener loadMore={ () => this.loadMore('albums') }/>
|
||||
<LazyLoadListener enabled={this.props['albums_more']} loadMore={ () => this.loadMore('albums') }/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
@ -113,7 +111,7 @@ class Search extends React.Component{
|
||||
<div>
|
||||
<section className="grid-wrapper">
|
||||
<PlaylistGrid playlists={playlists} />
|
||||
<LazyLoadListener loadMore={ () => this.loadMore('playlists') }/>
|
||||
<LazyLoadListener enabled={this.props['playlists_more']} loadMore={ () => this.loadMore('playlists') }/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
@ -124,7 +122,7 @@ class Search extends React.Component{
|
||||
<div>
|
||||
<section className="list-wrapper">
|
||||
<TrackList show_source_icon={true} tracks={ this.props.tracks } />
|
||||
<LazyLoadListener loadMore={ () => this.loadMore('tracks') }/>
|
||||
<LazyLoadListener enabled={this.props['tracks_more']} loadMore={ () => this.loadMore('tracks') }/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -33,7 +33,6 @@ class User extends React.Component{
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
if( !this.props.user.playlists_more ) return
|
||||
this.props.spotifyActions.getURL( this.props.user.playlists_more, 'SPOTIFY_USER_PLAYLISTS_LOADED', this.props.params.uri )
|
||||
}
|
||||
|
||||
@ -83,7 +82,7 @@ class User extends React.Component{
|
||||
|
||||
<section className="grid-wrapper">
|
||||
<PlaylistGrid playlists={playlists} />
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
<LazyLoadListener enabled={this.props.user.playlists_more} loadMore={ () => this.loadMore() }/>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
@ -30,9 +30,7 @@ class DiscoverCategory extends React.Component{
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
if (this.props.category.playlists_more){
|
||||
this.props.spotifyActions.getURL( this.props.category.playlists_more, 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', 'category:'+this.props.params.id );
|
||||
}
|
||||
this.props.spotifyActions.getURL( this.props.category.playlists_more, 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED', 'category:'+this.props.params.id );
|
||||
}
|
||||
|
||||
render(){
|
||||
@ -54,7 +52,7 @@ class DiscoverCategory extends React.Component{
|
||||
<section className="grid-wrapper">
|
||||
<PlaylistGrid playlists={playlists} />
|
||||
</section>
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
<LazyLoadListener enabled={this.props.category.playlists_more} loadMore={ () => this.loadMore() }/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ class DiscoverNewReleases extends React.Component{
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
if (!this.props.new_releases_more) return
|
||||
this.props.spotifyActions.getURL(this.props.new_releases_more, 'SPOTIFY_NEW_RELEASES_LOADED');
|
||||
}
|
||||
|
||||
@ -43,7 +42,7 @@ class DiscoverNewReleases extends React.Component{
|
||||
<section className="grid-wrapper">
|
||||
<AlbumGrid albums={albums} />
|
||||
</section>
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
<LazyLoadListener enabled={this.props.new_releases_more} loadMore={ () => this.loadMore() }/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -28,8 +28,13 @@ class LibraryAlbums extends React.Component{
|
||||
if (!this.props.library_albums) this.props.spotifyActions.getLibraryAlbums();
|
||||
}
|
||||
|
||||
handleContextMenu(e,uri){
|
||||
e.preventDefault()
|
||||
var data = { uris: [uri] }
|
||||
this.props.uiActions.showContextMenu( e, data, 'album', 'click' )
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
if( !this.props.library_albums_more ) return
|
||||
this.props.spotifyActions.getURL( this.props.library_albums_more, 'SPOTIFY_LIBRARY_ALBUMS_LOADED' );
|
||||
}
|
||||
|
||||
@ -77,13 +82,19 @@ class LibraryAlbums extends React.Component{
|
||||
]
|
||||
return (
|
||||
<section className="list-wrapper">
|
||||
<List rows={albums} columns={columns} link_prefix={global.baseURL+"album/"} />
|
||||
<List
|
||||
handleContextMenu={(e,uri) => this.handleContextMenu(e,uri)}
|
||||
rows={albums}
|
||||
columns={columns}
|
||||
link_prefix={global.baseURL+"album/"} />
|
||||
</section>
|
||||
)
|
||||
}else if( this.props.view == 'thumbnails' ){
|
||||
return (
|
||||
<section className="grid-wrapper">
|
||||
<AlbumGrid albums={albums} />
|
||||
<AlbumGrid
|
||||
handleContextMenu={(e,uri) => this.handleContextMenu(e,uri)}
|
||||
albums={albums} />
|
||||
</section>
|
||||
)
|
||||
}else{
|
||||
@ -173,7 +184,7 @@ class LibraryAlbums extends React.Component{
|
||||
<div className="view library-albums-view">
|
||||
<Header icon="cd" title="My albums" actions={actions} />
|
||||
{ this.renderView(albums) }
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
<LazyLoadListener enabled={this.props.library_albums_more} loadMore={ () => this.loadMore() }/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -21,12 +21,17 @@ class LibraryArtists extends React.Component{
|
||||
super(props);
|
||||
}
|
||||
|
||||
handleContextMenu(e,uri){
|
||||
e.preventDefault()
|
||||
var data = { uris: [uri] }
|
||||
this.props.uiActions.showContextMenu( e, data, 'artist', 'click' )
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
if (!this.props.library_artists) this.props.spotifyActions.getLibraryArtists();
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
if( !this.props.library_artists_more ) return
|
||||
this.props.spotifyActions.getURL( this.props.library_artists_more, 'SPOTIFY_LIBRARY_ARTISTS_LOADED' );
|
||||
}
|
||||
|
||||
@ -62,13 +67,19 @@ class LibraryArtists extends React.Component{
|
||||
]
|
||||
return (
|
||||
<section className="list-wrapper">
|
||||
<List rows={artists} columns={columns} link_prefix={global.baseURL+"artist/"} show_source_icon={true} />
|
||||
<List
|
||||
handleContextMenu={(e,uri) => this.handleContextMenu(e,uri)}
|
||||
rows={artists}
|
||||
columns={columns}
|
||||
link_prefix={global.baseURL+"artist/"} show_source_icon={true} />
|
||||
</section>
|
||||
)
|
||||
}else{
|
||||
return (
|
||||
<section className="grid-wrapper">
|
||||
<ArtistGrid artists={artists} />
|
||||
<ArtistGrid
|
||||
handleContextMenu={(e,uri) => this.handleContextMenu(e,uri)}
|
||||
artists={artists} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@ -127,7 +138,7 @@ class LibraryArtists extends React.Component{
|
||||
<div className="view library-artists-view">
|
||||
<Header icon="mic" title="My artists" actions={actions} />
|
||||
{ this.renderView(artists) }
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
<LazyLoadListener enabled={this.props.library_artists_more} loadMore={ () => this.loadMore() }/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ class LibraryLocalAlbums extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
moreURIsToLoad(){
|
||||
var uris = []
|
||||
if (this.props.albums && this.props.local_albums){
|
||||
for (var i = 0; i < this.props.local_albums.length; i++){
|
||||
@ -50,7 +50,12 @@ class LibraryLocalAlbums extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
if (uris && uris.length > 0) this.props.mopidyActions.getAlbums(uris)
|
||||
return uris
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
var uris = this.moreURIsToLoad()
|
||||
this.props.mopidyActions.getAlbums(uris)
|
||||
}
|
||||
|
||||
setSort(value){
|
||||
@ -147,7 +152,7 @@ class LibraryLocalAlbums extends React.Component{
|
||||
<div className="view library-local-view">
|
||||
<Header icon="music" title="Local albums" actions={actions} />
|
||||
{this.renderView(albums)}
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
<LazyLoadListener enabled={(this.moreURIsToLoad().length > 0)} loadMore={ () => this.loadMore() }/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -23,7 +23,6 @@ class LibraryTracks extends React.Component{
|
||||
}
|
||||
|
||||
loadMore(){
|
||||
if( !this.props.tracks_more ) return
|
||||
this.props.spotifyActions.getURL( this.props.tracks_more, 'SPOTIFY_LIBRARY_TRACKS_LOADED_MORE' );
|
||||
}
|
||||
|
||||
@ -33,7 +32,7 @@ class LibraryTracks extends React.Component{
|
||||
<Header icon="music" title="My tracks" />
|
||||
<section className="list-wrapper">
|
||||
{ this.props.tracks ? <TrackList tracks={this.props.tracks} /> : null }
|
||||
<LazyLoadListener loadMore={ () => this.loadMore() }/>
|
||||
<LazyLoadListener enabled={this.props.tracks_more} loadMore={ () => this.loadMore() }/>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -22,6 +22,8 @@
|
||||
@import 'components/header';
|
||||
@import 'components/notifications';
|
||||
@import 'components/dropdown-field';
|
||||
@import 'components/loader';
|
||||
@import 'components/debug';
|
||||
|
||||
@import 'views/artist';
|
||||
@import 'views/user';
|
||||
|
||||
@ -21,6 +21,45 @@
|
||||
bottom: 0 !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
width: 140px;
|
||||
font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
background: $darkest_grey;
|
||||
|
||||
.background {
|
||||
@include blur(5px);
|
||||
background-size: cover;
|
||||
background-position: 50% 20%;
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: -10px;
|
||||
bottom: -10px;
|
||||
right: -10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.text {
|
||||
@include one_line_text();
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
.background {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item-wrapper {
|
||||
display: block;
|
||||
position: relative;
|
||||
@ -65,7 +104,7 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:not(:first-child) .menu-item {
|
||||
.menu-item {
|
||||
border-top: 1px solid lighten($dark_grey, 8%);
|
||||
}
|
||||
|
||||
|
||||
11
src/scss/components/_debug.scss
Executable file
11
src/scss/components/_debug.scss
Executable file
@ -0,0 +1,11 @@
|
||||
|
||||
.debug-info {
|
||||
font-size: 10px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 10px;
|
||||
background: rgba(0,0,0,0.5);
|
||||
color: #FFFFFF;
|
||||
pointer-events: none;
|
||||
}
|
||||
@ -16,6 +16,10 @@
|
||||
|
||||
.grid {
|
||||
|
||||
.grid-item-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
||||
24
src/scss/components/_loader.scss
Executable file
24
src/scss/components/_loader.scss
Executable file
@ -0,0 +1,24 @@
|
||||
|
||||
.lazy-loader {
|
||||
padding: 20px 0 40px;
|
||||
|
||||
.loader {
|
||||
@include animate();
|
||||
opacity: 0;
|
||||
margin: 0 auto;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: {
|
||||
radius: 100%;
|
||||
style: solid;
|
||||
color: $mid_grey;
|
||||
width: 2px;
|
||||
}
|
||||
border-top-color: transparent;
|
||||
}
|
||||
|
||||
&.loading .loader {
|
||||
@include spin();
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@ -49,6 +49,18 @@ $bp_shallow: 650px;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@mixin spin( $duration: 0.5s ){
|
||||
animation-name: spin;
|
||||
animation-duration: $duration;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@mixin blur( $size: 10px ) {
|
||||
-webkit-filter: blur( $size );
|
||||
filter: blur( $size );
|
||||
|
||||
Reference in New Issue
Block a user