Links for grid items, may need to add more context options
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -98,7 +98,7 @@
|
||||
|
||||
// Release details
|
||||
// These are automatically injected to built HTML
|
||||
var build = "1547510940";
|
||||
var build = "1547579515";
|
||||
var version = "3.31.8";
|
||||
|
||||
// Construct the script tag
|
||||
|
||||
@ -36,6 +36,7 @@ import LibraryAlbums from './views/library/LibraryAlbums';
|
||||
import LibraryTracks from './views/library/LibraryTracks';
|
||||
import LibraryPlaylists from './views/library/LibraryPlaylists';
|
||||
import LibraryBrowse from './views/library/LibraryBrowse';
|
||||
import LibraryBrowseDirectory from './views/library/LibraryBrowseDirectory';
|
||||
|
||||
import EditPlaylist from './views/modals/EditPlaylist';
|
||||
import CreatePlaylist from './views/modals/CreatePlaylist';
|
||||
@ -387,30 +388,31 @@ class App extends React.Component{
|
||||
<Switch>
|
||||
<Route exact path="/" component={Queue} />
|
||||
|
||||
<Route path="/queue" component={Queue} />
|
||||
<Route path="/queue/history" component={QueueHistory} />
|
||||
<Route path="/settings/debug" component={Debug} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/settings/service/:sub_view?" component={Settings} />
|
||||
<Route exact path="/queue" component={Queue} />
|
||||
<Route exact path="/queue/history" component={QueueHistory} />
|
||||
<Route exact path="/settings/debug" component={Debug} />
|
||||
<Route exact path="/settings" component={Settings} />
|
||||
<Route exact path="/settings/service/:sub_view?" component={Settings} />
|
||||
|
||||
<Route path="/search/:type/:term?" component={Search} />
|
||||
<Route path="/album/:uri" component={Album} />
|
||||
<Route path="/artist/:uri/:sub_view?" component={Artist} />
|
||||
<Route path="/playlist/:uri" component={Playlist} />
|
||||
<Route path="/user/:uri" component={User} />
|
||||
<Route path="/track/:uri" component={Track} />
|
||||
<Route exact path="/search/(:type/:term)?" component={Search} />
|
||||
<Route exact path="/album/:uri" component={Album} />
|
||||
<Route exact path="/artist/:uri/:sub_view?" component={Artist} />
|
||||
<Route exact path="/playlist/:uri" component={Playlist} />
|
||||
<Route exact path="/user/:uri" component={User} />
|
||||
<Route exact path="/track/:uri" component={Track} />
|
||||
|
||||
<Route path="/discover/recommendations/:seeds?" component={DiscoverRecommendations} />
|
||||
<Route path="/discover/featured" component={DiscoverFeatured} />
|
||||
<Route path="/discover/categories" component={DiscoverCategories} />
|
||||
<Route path="/discover/categories/:id" component={DiscoverCategory} />
|
||||
<Route path="/discover/new-releases" component={DiscoverNewReleases} />
|
||||
<Route exact path="/discover/recommendations/:seeds?" component={DiscoverRecommendations} />
|
||||
<Route exact path="/discover/featured" component={DiscoverFeatured} />
|
||||
<Route exact path="/discover/categories/:id" component={DiscoverCategory} />
|
||||
<Route exact path="/discover/categories" component={DiscoverCategories} />
|
||||
<Route exact path="/discover/new-releases" component={DiscoverNewReleases} />
|
||||
|
||||
<Route path="/library/artists" component={LibraryArtists} />
|
||||
<Route path="/library/albums" component={LibraryAlbums} />
|
||||
<Route path="/library/tracks" component={LibraryTracks} />
|
||||
<Route path="/library/playlists" component={LibraryPlaylists} />
|
||||
<Route path="/library/browse/:uri?" component={LibraryBrowse} />
|
||||
<Route exact path="/library/artists" component={LibraryArtists} />
|
||||
<Route exact path="/library/albums" component={LibraryAlbums} />
|
||||
<Route exact path="/library/tracks" component={LibraryTracks} />
|
||||
<Route exact path="/library/playlists" component={LibraryPlaylists} />
|
||||
<Route exact path="/library/browse/:uri" component={LibraryBrowseDirectory} />
|
||||
<Route exact path="/library/browse" component={LibraryBrowse} />
|
||||
|
||||
<Route>
|
||||
<h1>I'm lost</h1>
|
||||
|
||||
@ -26,7 +26,7 @@ export default class CategoryGrid extends React.Component{
|
||||
key={category.id}
|
||||
type="category"
|
||||
item={category}
|
||||
onClick={e => {this.props.history.push(global.baseURL+'discover/categories/'+encodeURIComponent(category.id))}}
|
||||
link={global.baseURL+'discover/categories/'+encodeURIComponent(category.id)}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
@ -107,8 +107,18 @@ export default class GridItem extends React.Component{
|
||||
images = this.props.item.icons;
|
||||
}
|
||||
|
||||
if (this.props.link){
|
||||
var link = this.props.link;
|
||||
} else {
|
||||
var link = global.baseURL+this.props.type+'/'+encodeURIComponent(item.uri);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link className={"grid__item grid__item--"+this.props.type} to={global.baseURL+this.props.type+'/'+encodeURIComponent(item.uri)} onClick={e => helpers.scrollTo()} onContextMenu={e => this.handleContextMenu(e)}>
|
||||
<Link
|
||||
className={"grid__item grid__item--"+this.props.type}
|
||||
to={link}
|
||||
onClick={e => helpers.scrollTo()}
|
||||
onContextMenu={e => this.handleContextMenu(e)}>
|
||||
<Thumbnail size="medium" className="grid__item__thumbnail" images={images} />
|
||||
<div className="grid__item__name">
|
||||
{item.name ? item.name : <span className="opaque-text">{item.uri}</span>}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import React, { PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Redirect } from 'react-router';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import Link from '../components/Link';
|
||||
import LazyLoadListener from '../components/LazyLoadListener'
|
||||
@ -105,18 +107,7 @@ class Artist extends React.Component{
|
||||
this.props.uiActions.set({artist_albums_filter: value});
|
||||
}
|
||||
|
||||
renderSubViewMenu(){
|
||||
return (
|
||||
<div className="sub-views" id="sub-views-menu">
|
||||
<Link className="option" activeClassName="active" to={global.baseURL+'artist/'+this.props.uri} scrollTo="sub-views-menu"><h4>Overview</h4></Link>
|
||||
{this.props.artist.related_artists_uris ? <Link className="option" activeClassName="active" to={global.baseURL+'artist/'+this.props.uri+'/related-artists'} scrollTo="sub-views-menu"><h4>Related artists</h4></Link> : null}
|
||||
<Link className="option" activeClassName="active" to={global.baseURL+'artist/'+this.props.uri+'/about'} scrollTo="sub-views-menu"><h4>About</h4></Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderBody(){
|
||||
var scheme = helpers.uriSource(this.props.uri);
|
||||
renderOverview(){
|
||||
var artist = helpers.collate(
|
||||
this.props.artist,
|
||||
{
|
||||
@ -134,52 +125,6 @@ class Artist extends React.Component{
|
||||
artist.albums = helpers.applyFilter('type', this.props.filter, artist.albums);
|
||||
}
|
||||
|
||||
switch (this.props.match.params.sub_view){
|
||||
|
||||
case 'related-artists':
|
||||
return (
|
||||
<div className="body related-artists">
|
||||
<section className="grid-wrapper no-top-padding">
|
||||
<ArtistGrid artists={artist.related_artists} />
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
|
||||
case 'about':
|
||||
|
||||
var thumbnails = [];
|
||||
if (artist.images && artist.images.length > 0){
|
||||
for (var i = 0; i < artist.images.length; i++){
|
||||
thumbnails.push(
|
||||
<div className="tile thumbnail-wrapper" key={i}>
|
||||
<Thumbnail size="huge" canZoom images={artist.images[i]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body about">
|
||||
|
||||
<div className="col col--w40 tiles artist-stats">
|
||||
{thumbnails}
|
||||
{artist.followers ? <div className="tile"><span className="content"><Icon type="fontawesome" name="users" />{artist.followers.toLocaleString()} followers</span></div> : null}
|
||||
{artist.popularity ? <div className="tile"><span className="content"><Icon type="fontawesome" name="fire" />{artist.popularity }% popularity</span></div> : null}
|
||||
{artist.listeners ? <div className="tile"><span className="content"><Icon type="fontawesome" name="headphones" />{ artist.listeners.toLocaleString() } listeners</span></div> : null }
|
||||
</div>
|
||||
|
||||
<div className="col col--w60 biography">
|
||||
<section>
|
||||
{ artist.biography ? <div className="biography-text"><p>{artist.biography}</p><br />
|
||||
<div className="mid_grey-text">Published: { artist.biography_publish_date }</div>
|
||||
<div className="mid_grey-text">Origin: <a href={ artist.biography_link } target="_blank">{ artist.biography_link }</a></div></div> : null }
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
default:
|
||||
|
||||
var sort_options = [
|
||||
{
|
||||
value: null,
|
||||
@ -270,8 +215,64 @@ class Artist extends React.Component{
|
||||
</section>
|
||||
</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderRelatedArtists(){
|
||||
var artist = helpers.collate(
|
||||
this.props.artist,
|
||||
{
|
||||
artists: this.props.artists
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="body related-artists">
|
||||
<section className="grid-wrapper no-top-padding">
|
||||
<ArtistGrid artists={artist.related_artists} />
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderAbout(){
|
||||
var artist = helpers.collate(
|
||||
this.props.artist,
|
||||
{
|
||||
artists: this.props.artists
|
||||
}
|
||||
);
|
||||
|
||||
var thumbnails = [];
|
||||
if (artist.images && artist.images.length > 0){
|
||||
for (var i = 0; i < artist.images.length; i++){
|
||||
thumbnails.push(
|
||||
<div className="tile thumbnail-wrapper" key={i}>
|
||||
<Thumbnail size="huge" canZoom images={artist.images[i]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body about">
|
||||
|
||||
<div className="col col--w40 tiles artist-stats">
|
||||
{thumbnails}
|
||||
{artist.followers ? <div className="tile"><span className="content"><Icon type="fontawesome" name="users" />{artist.followers.toLocaleString()} followers</span></div> : null}
|
||||
{artist.popularity ? <div className="tile"><span className="content"><Icon type="fontawesome" name="fire" />{artist.popularity }% popularity</span></div> : null}
|
||||
{artist.listeners ? <div className="tile"><span className="content"><Icon type="fontawesome" name="headphones" />{ artist.listeners.toLocaleString() } listeners</span></div> : null }
|
||||
</div>
|
||||
|
||||
<div className="col col--w60 biography">
|
||||
<section>
|
||||
{ artist.biography ? <div className="biography-text"><p>{artist.biography}</p><br />
|
||||
<div className="mid_grey-text">Published: { artist.biography_publish_date }</div>
|
||||
<div className="mid_grey-text">Origin: <a href={ artist.biography_link } target="_blank">{ artist.biography_link }</a></div></div> : null }
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(){
|
||||
@ -316,11 +317,49 @@ class Artist extends React.Component{
|
||||
{is_spotify ? <FollowButton uri={this.props.uri} removeText="Remove from library" addText="Add to library" is_following={this.inLibrary()} /> : null}
|
||||
<ContextMenuTrigger className="white" onTrigger={e => this.handleContextMenu(e)} />
|
||||
</div>
|
||||
{ this.renderSubViewMenu() }
|
||||
<div className="sub-views" id="sub-views-menu">
|
||||
<Link
|
||||
nav
|
||||
className="option"
|
||||
activeClassName="active"
|
||||
to={global.baseURL+'artist/'+this.props.uri+'/overview'}
|
||||
scrollTo="sub-views-menu">
|
||||
<h4>Overview</h4>
|
||||
</Link>
|
||||
{this.props.artist.related_artists_uris ? <Link
|
||||
nav
|
||||
className="option"
|
||||
activeClassName="active"
|
||||
to={global.baseURL+'artist/'+this.props.uri+'/related-artists'}
|
||||
scrollTo="sub-views-menu">
|
||||
<h4>Related artists</h4>
|
||||
</Link> : null}
|
||||
<Link
|
||||
nav
|
||||
className="option"
|
||||
activeClassName="active"
|
||||
to={global.baseURL+'artist/'+this.props.uri+'/about'}
|
||||
scrollTo="sub-views-menu">
|
||||
<h4>About</h4>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="content-wrapper">
|
||||
{this.renderBody()}
|
||||
<Switch>
|
||||
<Route exact path="/artist/:id/related-artists">
|
||||
{this.renderRelatedArtists()}
|
||||
</Route>
|
||||
<Route exact path="/artist/:id/about">
|
||||
{this.renderAbout()}
|
||||
</Route>
|
||||
<Route exact path="/artist/:id/overview">
|
||||
{this.renderOverview()}
|
||||
</Route>
|
||||
<Route>
|
||||
{this.renderOverview()}
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -230,7 +230,7 @@ class Playlist extends React.Component{
|
||||
<TrackList
|
||||
uri={playlist.uri}
|
||||
className="playlist-track-list"
|
||||
context={context}
|
||||
track_context={context}
|
||||
tracks={playlist.tracks}
|
||||
removeTracks={tracks_indexes => this.removeTracks(tracks_indexes)}
|
||||
reorderTracks={(indexes, index) => this.reorderTracks(indexes, index)}
|
||||
|
||||
@ -52,7 +52,7 @@ class Search extends React.Component{
|
||||
componentWillReceiveProps(nextProps){
|
||||
|
||||
// Query changed
|
||||
if (nextProps.params.term !== this.props.params.term || nextProps.params.type !== this.props.params.type){
|
||||
if (nextProps.term !== this.props.term || nextProps.type !== this.props.type){
|
||||
this.digestUri(nextProps);
|
||||
}
|
||||
|
||||
@ -69,13 +69,13 @@ class Search extends React.Component{
|
||||
// Digest the URI query property
|
||||
// Triggered when the URL changes
|
||||
digestUri(props = this.props){
|
||||
if (props.params && props.params.type && props.params.term){
|
||||
if (props.params && props.type && props.term){
|
||||
this.setState({
|
||||
type: props.params.type,
|
||||
term: props.params.term
|
||||
type: props.type,
|
||||
term: props.term
|
||||
});
|
||||
|
||||
this.search(props.params.type, props.params.term);
|
||||
this.search(props.type, props.term);
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,6 +429,8 @@ class Search extends React.Component{
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
type: ownProps.match.params.type,
|
||||
term: ownProps.match.params.term,
|
||||
mopidy_connected: state.mopidy.connected,
|
||||
albums: (state.core.albums ? state.core.albums : []),
|
||||
artists: (state.core.artists ? state.core.artists : []),
|
||||
|
||||
@ -24,7 +24,7 @@ class DiscoverCategory extends React.Component{
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
if (nextProps.params.id != this.props.params.id){
|
||||
if (nextProps.match.params.id != this.props.match.params.id){
|
||||
this.loadCategory();
|
||||
}
|
||||
|
||||
@ -43,11 +43,11 @@ class DiscoverCategory extends React.Component{
|
||||
|
||||
loadCategory(){
|
||||
if (!this.props.category){
|
||||
this.props.spotifyActions.getCategory(this.props.params.id);
|
||||
this.props.spotifyActions.getCategory(this.props.match.params.id);
|
||||
}
|
||||
|
||||
if (!this.props.category.playlists_uris){
|
||||
this.props.spotifyActions.getCategoryPlaylists(this.props.params.id);
|
||||
this.props.spotifyActions.getCategoryPlaylists(this.props.match.params.id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ class DiscoverCategory extends React.Component{
|
||||
null,
|
||||
{
|
||||
type: 'SPOTIFY_CATEGORY_PLAYLISTS_LOADED_MORE',
|
||||
uri: 'category:'+this.props.params.id
|
||||
uri: 'category:'+this.props.match.params.id
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -104,18 +104,11 @@ class DiscoverCategory extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export our component
|
||||
*
|
||||
* We also integrate our global store, using connect()
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
load_queue: state.ui.load_queue,
|
||||
playlists: state.core.playlists,
|
||||
category: (state.spotify.categories && state.spotify.categories['category:'+ownProps.params.id] !== undefined ? state.spotify.categories['category:'+ownProps.params.id] : false )
|
||||
category: (state.spotify.categories && state.spotify.categories['category:'+ownProps.match.params.id] !== undefined ? state.spotify.categories['category:'+ownProps.match.params.id] : false )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import React, { PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import Link from '../../components/Link';
|
||||
|
||||
import Header from '../../components/Header'
|
||||
import List from '../../components/List'
|
||||
import TrackList from '../../components/TrackList'
|
||||
import GridItem from '../../components/GridItem'
|
||||
import DropdownField from '../../components/Fields/DropdownField'
|
||||
import Icon from '../../components/Icon'
|
||||
import URILink from '../../components/URILink'
|
||||
import ErrorBoundary from '../../components/ErrorBoundary'
|
||||
import Header from '../../components/Header';
|
||||
import List from '../../components/List';
|
||||
import TrackList from '../../components/TrackList';
|
||||
import GridItem from '../../components/GridItem';
|
||||
import DropdownField from '../../components/Fields/DropdownField';
|
||||
import Icon from '../../components/Icon';
|
||||
import URILink from '../../components/URILink';
|
||||
import ErrorBoundary from '../../components/ErrorBoundary';
|
||||
|
||||
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'
|
||||
import * as helpers from '../../helpers';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
import * as mopidyActions from '../../services/mopidy/actions';
|
||||
|
||||
class LibraryBrowse extends React.Component{
|
||||
|
||||
@ -36,182 +36,19 @@ class LibraryBrowse extends React.Component{
|
||||
if (!this.props.mopidy_connected && nextProps.mopidy_connected){
|
||||
this.loadDirectory(nextProps);
|
||||
}
|
||||
|
||||
// our uri changes
|
||||
if (nextProps.match.params.uri != this.props.match.params.uri){
|
||||
this.loadDirectory(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
loadDirectory(props = this.props){
|
||||
if (props.mopidy_connected){
|
||||
var uri = null
|
||||
if (props.match.params.uri !== undefined){
|
||||
uri = props.match.params.uri;
|
||||
if (props.uri !== undefined){
|
||||
uri = props.uri;
|
||||
}
|
||||
this.props.mopidyActions.getDirectory(uri);
|
||||
}
|
||||
}
|
||||
|
||||
playAll(e, tracks){
|
||||
var tracks_uris = helpers.arrayOf('uri',tracks);
|
||||
this.props.mopidyActions.playURIs(tracks_uris, "iris:browse:"+this.props.match.params.uri);
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
goBack(e){
|
||||
window.history.back();
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
renderBreadcrumbs(){
|
||||
|
||||
if (this.props.match.params.uri){
|
||||
var parent_uri = this.props.match.params.uri;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parent_uri.startsWith('file://')){
|
||||
var uri = parent_uri.replace('file:///','');
|
||||
var uri_elements = uri.split('/');
|
||||
|
||||
return (
|
||||
<h4>
|
||||
{uri_elements.map((uri_element, index) => {
|
||||
|
||||
// Reconstruct a URL to this element
|
||||
var uri = "file://";
|
||||
for (var i = 0; i <= index; i++){
|
||||
uri += "/"+uri_elements[i];
|
||||
}
|
||||
|
||||
return (
|
||||
<span key={uri}>
|
||||
{index > 0 ? <span> <Icon type="fontawesome" name="angle-right" /> </span> : null}
|
||||
<URILink type="browse" uri={uri}>
|
||||
{decodeURI(uri_element)}
|
||||
</URILink>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</h4>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderSubdirectories(subdirectories){
|
||||
if (this.props.view == 'list'){
|
||||
return (
|
||||
<List
|
||||
nocontext
|
||||
rows={subdirectories}
|
||||
className="library-local-directory-list"
|
||||
link_prefix={global.baseURL+'library/browse/'}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="grid category-grid">
|
||||
{
|
||||
subdirectories.map(subdirectory => {
|
||||
return (
|
||||
<GridItem
|
||||
key={subdirectory.uri}
|
||||
type="category"
|
||||
item={subdirectory}
|
||||
onClick={e => {this.props.history.push(global.baseURL+'library/browse/'+encodeURIComponent(subdirectory.uri))}}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderDirectory(directory){
|
||||
var title = 'Browse';
|
||||
var uri_exploded = this.props.match.params.uri.split(':');
|
||||
if (uri_exploded.length > 0){
|
||||
title = uri_exploded[0];
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
}
|
||||
|
||||
if (!this.props.directory || helpers.isLoading(this.props.load_queue,['mopidy_browse'])){
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header icon="music" title={title} uiActions={this.props.uiActions} />
|
||||
<div className="body-loader loading">
|
||||
<div className="loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
var tracks = (this.props.directory.tracks && this.props.directory.tracks.length > 0 ? this.props.directory.tracks : null);
|
||||
tracks = helpers.sortItems(tracks, 'name');
|
||||
|
||||
var subdirectories = (this.props.directory.subdirectories && this.props.directory.subdirectories.length > 0 ? this.props.directory.subdirectories : null);
|
||||
subdirectories = helpers.sortItems(subdirectories, 'name');
|
||||
|
||||
var view_options = [
|
||||
{
|
||||
label: 'Thumbnails',
|
||||
value: 'thumbnails'
|
||||
},
|
||||
{
|
||||
label: 'List',
|
||||
value: 'list'
|
||||
}
|
||||
];
|
||||
|
||||
var options = (
|
||||
<span>
|
||||
<DropdownField
|
||||
icon="visibility"
|
||||
name="View"
|
||||
value={this.props.view}
|
||||
options={view_options}
|
||||
handleChange={value => {this.props.uiActions.set({ library_directory_view: value }); this.props.uiActions.hideContextMenu()}}
|
||||
/>
|
||||
{tracks ? <a className="button button--no-hover" onClick={e => {this.props.uiActions.hideContextMenu(); this.playAll(e, tracks)}}>
|
||||
<Icon name="play_circle_filled" />Play all
|
||||
</a> : null }
|
||||
<a className="button button--no-hover" onClick={e => {this.props.uiActions.hideContextMenu(); this.goBack(e)}}>
|
||||
<Icon name="keyboard_backspace" />Back
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header options={options} uiActions={this.props.uiActions}>
|
||||
<Icon name="folder" type="material" />
|
||||
{title}
|
||||
</Header>
|
||||
<section className="content-wrapper">
|
||||
<ErrorBoundary>
|
||||
|
||||
{this.renderBreadcrumbs()}
|
||||
|
||||
{subdirectories ? this.renderSubdirectories(subdirectories) : null}
|
||||
|
||||
{tracks ? <TrackList
|
||||
tracks={this.props.directory.tracks}
|
||||
uri={"iris:browse:"+this.props.match.params.uri}
|
||||
className="library-local-track-list"
|
||||
/> : null }
|
||||
|
||||
</ErrorBoundary>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderIndex(){
|
||||
render(){
|
||||
var grid_items = []
|
||||
if (this.props.directory){
|
||||
for (var subdirectory of this.props.directory.subdirectories){
|
||||
@ -269,7 +106,7 @@ class LibraryBrowse extends React.Component{
|
||||
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header uiActions={this.props.uiActions}>
|
||||
<Header>
|
||||
<Icon name="folder" type="material" />
|
||||
Browse
|
||||
</Header>
|
||||
@ -284,7 +121,6 @@ class LibraryBrowse extends React.Component{
|
||||
item={item}
|
||||
key={index}
|
||||
type="category"
|
||||
onClick={e => this.props.history.push(item.link)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -296,14 +132,6 @@ class LibraryBrowse extends React.Component{
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(){
|
||||
if (this.props.match.params.uri !== undefined && this.props.match.params.uri){
|
||||
return this.renderDirectory();
|
||||
} else {
|
||||
return this.renderIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
@ -318,8 +146,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch)
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
235
src/js/views/library/LibraryBrowseDirectory.js
Executable file
235
src/js/views/library/LibraryBrowseDirectory.js
Executable file
@ -0,0 +1,235 @@
|
||||
|
||||
import React, { PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import Link from '../../components/Link';
|
||||
import Header from '../../components/Header';
|
||||
import List from '../../components/List';
|
||||
import TrackList from '../../components/TrackList';
|
||||
import GridItem from '../../components/GridItem';
|
||||
import DropdownField from '../../components/Fields/DropdownField';
|
||||
import Icon from '../../components/Icon';
|
||||
import URILink from '../../components/URILink';
|
||||
import ErrorBoundary from '../../components/ErrorBoundary';
|
||||
|
||||
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';
|
||||
|
||||
class LibraryBrowseDirectory extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.props.uiActions.setWindowTitle("Browse");
|
||||
this.loadDirectory();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
|
||||
// mopidy goes online
|
||||
if (!this.props.mopidy_connected && nextProps.mopidy_connected){
|
||||
this.loadDirectory(nextProps);
|
||||
}
|
||||
|
||||
// our uri changes
|
||||
if (nextProps.uri && nextProps.uri !== this.props.uri){
|
||||
|
||||
console.log(nextProps.uri,this.props.uri);
|
||||
|
||||
//this.loadDirectory(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
loadDirectory(props = this.props){
|
||||
if (props.mopidy_connected){
|
||||
var uri = null
|
||||
if (props.uri !== undefined){
|
||||
uri = props.uri;
|
||||
}
|
||||
this.props.mopidyActions.getDirectory(uri);
|
||||
}
|
||||
}
|
||||
|
||||
playAll(e, tracks){
|
||||
var tracks_uris = helpers.arrayOf('uri',tracks);
|
||||
this.props.mopidyActions.playURIs(tracks_uris, "iris:browse:"+this.props.uri);
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
goBack(e){
|
||||
window.history.back();
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
|
||||
renderBreadcrumbs(){
|
||||
|
||||
if (this.props.uri){
|
||||
var parent_uri = this.props.uri;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parent_uri.startsWith('file://')){
|
||||
var uri = parent_uri.replace('file:///','');
|
||||
var uri_elements = uri.split('/');
|
||||
|
||||
return (
|
||||
<h4>
|
||||
{uri_elements.map((uri_element, index) => {
|
||||
|
||||
// Reconstruct a URL to this element
|
||||
var uri = "file://";
|
||||
for (var i = 0; i <= index; i++){
|
||||
uri += "/"+uri_elements[i];
|
||||
}
|
||||
|
||||
return (
|
||||
<span key={uri}>
|
||||
{index > 0 ? <span> <Icon type="fontawesome" name="angle-right" /> </span> : null}
|
||||
<URILink type="browse" uri={uri}>
|
||||
{decodeURI(uri_element)}
|
||||
</URILink>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</h4>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderSubdirectories(subdirectories){
|
||||
if (this.props.view == 'list'){
|
||||
return (
|
||||
<List
|
||||
nocontext
|
||||
rows={subdirectories}
|
||||
className="library-local-directory-list"
|
||||
link_prefix={global.baseURL+'library/browse/'}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="grid category-grid">
|
||||
{
|
||||
subdirectories.map(subdirectory => {
|
||||
return (
|
||||
<GridItem
|
||||
key={subdirectory.uri}
|
||||
type="directory"
|
||||
item={subdirectory}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
var title = 'Directory';
|
||||
var uri_exploded = this.props.uri.split(':');
|
||||
if (uri_exploded.length > 0){
|
||||
title = uri_exploded[0];
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
}
|
||||
|
||||
if (!this.props.directory || helpers.isLoading(this.props.load_queue,['mopidy_browse'])){
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header icon="music" title={title} uiActions={this.props.uiActions} />
|
||||
<div className="body-loader loading">
|
||||
<div className="loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
var tracks = (this.props.directory.tracks && this.props.directory.tracks.length > 0 ? this.props.directory.tracks : null);
|
||||
tracks = helpers.sortItems(tracks, 'name');
|
||||
|
||||
var subdirectories = (this.props.directory.subdirectories && this.props.directory.subdirectories.length > 0 ? this.props.directory.subdirectories : null);
|
||||
subdirectories = helpers.sortItems(subdirectories, 'name');
|
||||
|
||||
var view_options = [
|
||||
{
|
||||
label: 'Thumbnails',
|
||||
value: 'thumbnails'
|
||||
},
|
||||
{
|
||||
label: 'List',
|
||||
value: 'list'
|
||||
}
|
||||
];
|
||||
|
||||
var options = (
|
||||
<span>
|
||||
<DropdownField
|
||||
icon="visibility"
|
||||
name="View"
|
||||
value={this.props.view}
|
||||
options={view_options}
|
||||
handleChange={value => {this.props.uiActions.set({ library_directory_view: value }); this.props.uiActions.hideContextMenu()}}
|
||||
/>
|
||||
{tracks ? <a className="button button--no-hover" onClick={e => {this.props.uiActions.hideContextMenu(); this.playAll(e, tracks)}}>
|
||||
<Icon name="play_circle_filled" />Play all
|
||||
</a> : null }
|
||||
<a className="button button--no-hover" onClick={e => {this.props.uiActions.hideContextMenu(); this.goBack(e)}}>
|
||||
<Icon name="keyboard_backspace" />Back
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header options={options} uiActions={this.props.uiActions}>
|
||||
<Icon name="folder" type="material" />
|
||||
{title}
|
||||
</Header>
|
||||
<section className="content-wrapper">
|
||||
<ErrorBoundary>
|
||||
|
||||
{this.renderBreadcrumbs()}
|
||||
|
||||
{subdirectories ? this.renderSubdirectories(subdirectories) : null}
|
||||
|
||||
{tracks ? <TrackList
|
||||
tracks={this.props.directory.tracks}
|
||||
uri={"iris:browse:"+this.props.uri}
|
||||
className="library-local-track-list"
|
||||
/> : null }
|
||||
|
||||
</ErrorBoundary>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
var uri = decodeURIComponent(ownProps.match.params.uri);
|
||||
return {
|
||||
uri: uri,
|
||||
load_queue: state.ui.load_queue,
|
||||
mopidy_connected: state.mopidy.connected,
|
||||
directory: state.mopidy.directory,
|
||||
view: state.ui.library_directory_view
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
||||
spotifyActions: bindActionCreators(spotifyActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LibraryBrowseDirectory)
|
||||
Reference in New Issue
Block a user