Context menu on related artists; Starting breadcrumb for Browse
This commit is contained in:
@ -136,35 +136,35 @@ export class App extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate({
|
||||
location: prevLocation,
|
||||
}) {
|
||||
const {
|
||||
location = {},
|
||||
allow_reporting,
|
||||
uiActions,
|
||||
context_menu,
|
||||
} = this.props;
|
||||
|
||||
// When we have navigated to a new route
|
||||
if (this.props.location !== prevProps.location) {
|
||||
if (location !== prevLocation) {
|
||||
// Log our pageview
|
||||
if (this.props.allow_reporting) {
|
||||
ReactGA.set({ page: this.props.location.pathname });
|
||||
ReactGA.pageview(this.props.location.pathname);
|
||||
if (allow_reporting) {
|
||||
ReactGA.set({ page: location.pathname });
|
||||
ReactGA.pageview(location.pathname);
|
||||
}
|
||||
|
||||
// If the location has a "scroll_position" state variable, scroll to it.
|
||||
// This is invisibly injected to the history by the Link component when navigating, so
|
||||
// hitting back in the browser allows us to restore the position
|
||||
const location_state = this.props.location.state
|
||||
? this.props.location.state
|
||||
: {};
|
||||
const location_state = location.state || {};
|
||||
if (location_state.scroll_position) {
|
||||
scrollTo(parseInt(location_state.scroll_position), false);
|
||||
}
|
||||
|
||||
// Hide our sidebar
|
||||
this.props.uiActions.toggleSidebar(false);
|
||||
|
||||
// Unselect any tracks
|
||||
this.props.uiActions.setSelectedTracks([]);
|
||||
|
||||
// Close context menu
|
||||
if (this.props.context_menu) {
|
||||
this.props.uiActions.hideContextMenu();
|
||||
}
|
||||
uiActions.toggleSidebar(false);
|
||||
uiActions.setSelectedTracks([]);
|
||||
if (context_menu) uiActions.hideContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,14 +177,16 @@ export class App extends React.Component {
|
||||
*
|
||||
* @param e Event
|
||||
* */
|
||||
handleFocusAndBlur(e) {
|
||||
this.props.uiActions.setWindowFocus(document.hasFocus());
|
||||
handleFocusAndBlur() {
|
||||
const { uiActions: { setWindowFocus } } = this.props;
|
||||
setWindowFocus(document.hasFocus());
|
||||
}
|
||||
|
||||
handleInstallPrompt(e) {
|
||||
const { uiActions: { installPrompt } } = this.props;
|
||||
e.preventDefault();
|
||||
console.log('Install prompt detected');
|
||||
this.props.uiActions.installPrompt(e);
|
||||
installPrompt(e);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@ -32,6 +32,9 @@ export default ({
|
||||
{
|
||||
...location.state,
|
||||
scroll_position: main.scrollTop,
|
||||
previous: {
|
||||
pathname: location.pathname,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@ -53,7 +56,7 @@ export default ({
|
||||
return (
|
||||
<Link
|
||||
onClick={onClick}
|
||||
onContextMenu={onContextMenu || null}
|
||||
onContextMenu={onContextMenu}
|
||||
className={`${className} ${active}`}
|
||||
to={to}
|
||||
>
|
||||
|
||||
@ -3,36 +3,55 @@ import React, { memo } from 'react';
|
||||
import Thumbnail from './Thumbnail';
|
||||
import URILink from './URILink';
|
||||
|
||||
export default memo((props) => {
|
||||
if (!props.artists) {
|
||||
return null;
|
||||
}
|
||||
export default memo(({ artists, uiActions: { showContextMenu } = {} }) => {
|
||||
if (!artists) return null;
|
||||
|
||||
const onContextMenu = (e, item) => {
|
||||
if (showContextMenu){
|
||||
e.preventDefault();
|
||||
showContextMenu({
|
||||
e,
|
||||
context: 'artist',
|
||||
uris: [item.uri],
|
||||
items: [item],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="related-artists">
|
||||
{
|
||||
props.artists.map((artist, index) => {
|
||||
let { images } = artist;
|
||||
if (Array.isArray(images)) {
|
||||
images = images[0];
|
||||
}
|
||||
{artists.map((artist) => {
|
||||
let { images } = artist;
|
||||
if (Array.isArray(images)) {
|
||||
images = images[0];
|
||||
}
|
||||
|
||||
if (artist.uri) {
|
||||
return (
|
||||
<URILink type="artist" uri={artist.uri} key={artist.uri} className="related-artists__item related-artists__item--link">
|
||||
<Thumbnail className="related-artists__item__thumbnail" circle size="small" images={images} />
|
||||
<span className="related-artists__item__name">{ artist.name }</span>
|
||||
</URILink>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span key={artist.uri} className="related-artists__item">
|
||||
<Thumbnail className="related-artists__item__thumbnail" circle size="small" images={images} />
|
||||
<span className="related-artists__item__name">{ artist.name }</span>
|
||||
</span>
|
||||
);
|
||||
})
|
||||
}
|
||||
if (artist.uri) {
|
||||
return (
|
||||
<URILink
|
||||
type="artist"
|
||||
uri={artist.uri}
|
||||
key={artist.uri}
|
||||
className="related-artists__item related-artists__item--link"
|
||||
handleContextMenu={(e) => onContextMenu(e, artist)}
|
||||
>
|
||||
<Thumbnail className="related-artists__item__thumbnail" circle size="small" images={images} />
|
||||
<span className="related-artists__item__name">{ artist.name }</span>
|
||||
</URILink>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span key={artist.uri} className="related-artists__item">
|
||||
<Thumbnail
|
||||
className="related-artists__item__thumbnail"
|
||||
circle
|
||||
size="small"
|
||||
images={images}
|
||||
/>
|
||||
<span className="related-artists__item__name">{ artist.name }</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
formatImages,
|
||||
formatTrack,
|
||||
formatTracks,
|
||||
formatSimpleObject,
|
||||
getTrackIcon,
|
||||
} from '../../util/format';
|
||||
import {
|
||||
@ -2399,6 +2400,17 @@ const MopidyMiddleware = (function () {
|
||||
type: 'MOPIDY_DIRECTORY_FLUSH',
|
||||
});
|
||||
|
||||
if (action.uri) {
|
||||
request(socket, store, 'library.lookup', { uris: [action.uri] })
|
||||
.then((response) => {
|
||||
if (!response[action.uri] || !response[action.uri].length) return;
|
||||
store.dispatch({
|
||||
type: 'MOPIDY_DIRECTORY_LOADED',
|
||||
directory: formatSimpleObject(response[action.uri][0]),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
request(socket, store, 'library.browse', { uri: action.uri })
|
||||
.then((response) => {
|
||||
const tracks_uris = [];
|
||||
|
||||
@ -104,7 +104,10 @@ export default function reducer(mopidy = {}, action) {
|
||||
return { ...mopidy, directory: null };
|
||||
|
||||
case 'MOPIDY_DIRECTORY_LOADED':
|
||||
return { ...mopidy, directory: action.directory };
|
||||
return {
|
||||
...mopidy,
|
||||
directory: { ...mopidy.directory, ...action.directory },
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -117,6 +117,7 @@ class Artist extends React.Component {
|
||||
}
|
||||
|
||||
renderOverview() {
|
||||
const { uiActions } = this.props;
|
||||
const artist = collate(
|
||||
this.props.artist,
|
||||
{
|
||||
@ -193,8 +194,19 @@ class Artist extends React.Component {
|
||||
{artist.related_artists && artist.related_artists.length > 0 && (
|
||||
<div className="col col--w25 related-artists">
|
||||
<h4>Related artists</h4>
|
||||
<div className="list-wrapper"><RelatedArtists artists={artist.related_artists.slice(0, 6)} /></div>
|
||||
<Link to={`/artist/${encodeURIComponent(this.props.uri)}/related-artists`} scrollTo="#sub-views-menu" className="button button--default">All related artists</Link>
|
||||
<div className="list-wrapper">
|
||||
<RelatedArtists
|
||||
artists={artist.related_artists.slice(0, 6)}
|
||||
uiActions={uiActions}
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
to={`/artist/${encodeURIComponent(this.props.uri)}/related-artists`}
|
||||
scrollTo="#sub-views-menu"
|
||||
className="button button--default"
|
||||
>
|
||||
All related artists
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@ -472,7 +472,7 @@ class Discover extends React.Component {
|
||||
<div className="col col--w25 others">
|
||||
<section>
|
||||
<h4>Artists</h4>
|
||||
<RelatedArtists artists={artists} />
|
||||
<RelatedArtists artists={artists} uiActions={this.props.uiActions} />
|
||||
</section>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
@ -97,29 +97,14 @@ class LibraryBrowseDirectory extends React.Component {
|
||||
}
|
||||
|
||||
if (parent_uri.startsWith('file://')) {
|
||||
const uri = parent_uri.replace('file:///', '');
|
||||
const uri_elements = uri.split('/');
|
||||
parent_uri = parent_uri.substring(0, parent_uri.lastIndexOf('/'));
|
||||
|
||||
return (
|
||||
<h4 className="breadcrumbs">
|
||||
{uri_elements.map((uri_element, index) => {
|
||||
// Reconstruct a URL to this element
|
||||
let uri = 'file://';
|
||||
for (let i = 0; i <= index; i++) {
|
||||
uri += `/${uri_elements[i]}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<span key={uri}>
|
||||
{index > 0 && (
|
||||
<Icon type="fontawesome" name="angle-right" />
|
||||
)}
|
||||
<URILink type="browse" uri={uri}>
|
||||
{decodeURI(uri_element)}
|
||||
</URILink>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
<Icon type="fontawesome" name="angle-left" />
|
||||
<URILink type="browse" uri={parent_uri}>
|
||||
{decodeURI(parent_uri)}
|
||||
</URILink>
|
||||
</h4>
|
||||
);
|
||||
}
|
||||
@ -156,24 +141,38 @@ class LibraryBrowseDirectory extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let title = 'Directory';
|
||||
const uri_exploded = this.props.uri.split(':');
|
||||
if (uri_exploded.length > 0) {
|
||||
title = uri_exploded[0];
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
}
|
||||
render = () => {
|
||||
const {
|
||||
uri,
|
||||
directory,
|
||||
load_queue,
|
||||
uiActions,
|
||||
view,
|
||||
} = this.props;
|
||||
const { limit } = this.state;
|
||||
|
||||
if (!this.props.directory || isLoading(this.props.load_queue, ['mopidy_browse'])) {
|
||||
let title = 'Directory';
|
||||
|
||||
if (!directory || isLoading(load_queue, ['mopidy_browse'])) {
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header icon="music" title={title} uiActions={this.props.uiActions} />
|
||||
<Header icon="music" title={title} uiActions={uiActions} />
|
||||
<Loader body loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let subdirectories = (this.props.directory.subdirectories && this.props.directory.subdirectories.length > 0 ? this.props.directory.subdirectories : null);
|
||||
if (directory.name) {
|
||||
title = directory.name;
|
||||
} else {
|
||||
const uri_exploded = uri.split(':');
|
||||
if (uri_exploded.length > 0) {
|
||||
title = uri_exploded[0];
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
}
|
||||
};
|
||||
|
||||
let subdirectories = (directory.subdirectories && directory.subdirectories.length > 0 ? directory.subdirectories : null);
|
||||
subdirectories = sortItems(subdirectories, 'name');
|
||||
|
||||
const total_items = (tracks ? tracks.length : 0) + (subdirectories ? subdirectories.length : 0);
|
||||
@ -182,7 +181,7 @@ class LibraryBrowseDirectory extends React.Component {
|
||||
let tracks = null;
|
||||
let limit_remaining = this.state.limit - subdirectories;
|
||||
if (limit_remaining > 0) {
|
||||
all_tracks = (this.props.directory.tracks && this.props.directory.tracks.length > 0 ? this.props.directory.tracks : null);
|
||||
all_tracks = (directory.tracks && directory.tracks.length > 0 ? directory.tracks : null);
|
||||
all_tracks = sortItems(all_tracks, 'name');
|
||||
tracks = all_tracks.slice(0, limit_remaining);
|
||||
}
|
||||
@ -203,18 +202,18 @@ class LibraryBrowseDirectory extends React.Component {
|
||||
<DropdownField
|
||||
icon="visibility"
|
||||
name="View"
|
||||
value={this.props.view}
|
||||
value={view}
|
||||
valueAsLabel
|
||||
options={view_options}
|
||||
handleChange={(value) => { this.props.uiActions.set({ library_directory_view: value }); this.props.uiActions.hideContextMenu(); }}
|
||||
handleChange={(value) => { uiActions.set({ library_directory_view: value }); uiActions.hideContextMenu(); }}
|
||||
/>
|
||||
{tracks && (
|
||||
<a className="button button--no-hover" onClick={(e) => { this.props.uiActions.hideContextMenu(); this.playAll(e, all_tracks); }}>
|
||||
<a className="button button--no-hover" onClick={(e) => { uiActions.hideContextMenu(); this.playAll(e, all_tracks); }}>
|
||||
<Icon name="play_circle_filled" />
|
||||
Play all
|
||||
</a>
|
||||
)}
|
||||
<a className="button button--no-hover" onClick={(e) => { this.props.uiActions.hideContextMenu(); this.goBack(e); }}>
|
||||
<a className="button button--no-hover" onClick={(e) => { uiActions.hideContextMenu(); this.goBack(e); }}>
|
||||
<Icon name="keyboard_backspace" />
|
||||
Back
|
||||
</a>
|
||||
@ -223,7 +222,7 @@ class LibraryBrowseDirectory extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="view library-local-view">
|
||||
<Header options={options} uiActions={this.props.uiActions}>
|
||||
<Header options={options} uiActions={uiActions}>
|
||||
<Icon name="folder" type="material" />
|
||||
{title}
|
||||
</Header>
|
||||
@ -237,14 +236,14 @@ class LibraryBrowseDirectory extends React.Component {
|
||||
{tracks && (
|
||||
<TrackList
|
||||
tracks={tracks}
|
||||
uri={`iris:browse:${this.props.uri}`}
|
||||
uri={`iris:browse:${uri}`}
|
||||
className="library-local-track-list"
|
||||
/>
|
||||
)}
|
||||
|
||||
<LazyLoadListener
|
||||
loadKey={total_items > this.state.limit ? this.state.limit : total_items}
|
||||
showLoader={this.state.limit < total_items}
|
||||
loadKey={total_items > limit ? limit : total_items}
|
||||
showLoader={limit < total_items}
|
||||
loadMore={() => this.loadMore()}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user