Better UX on dropdown field; Adding search added_from in queue

This commit is contained in:
James Barnsley
2019-10-13 09:29:41 +13:00
parent 9b2216ca62
commit 68de0feed6
21 changed files with 667 additions and 493 deletions

View File

@ -180,24 +180,25 @@ class Artist extends React.Component {
<div className="col col--w5" />
{artist.related_artists && artist.related_artists.length > 0 ? (
{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>
) : null}
)}
<div className="cf" />
{artist.albums ? (
{artist.albums && (
<div className="albums">
<h4>
<div>Albums</div>
<DropdownField
icon="sort"
icon="swap_vert"
name="Sort"
value={this.props.sort}
valueAsLabel
options={sort_options}
selected_icon={this.props.sort ? (this.props.sort_reverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
handleChange={(value) => { this.setSort(value); this.props.uiActions.hideContextMenu(); }}
@ -206,6 +207,7 @@ class Artist extends React.Component {
icon="filter_list"
name="Filter"
value={this.props.filter}
valueAsLabel
options={filter_options}
handleChange={(value) => { this.setFilter(value); this.props.uiActions.hideContextMenu(); }}
/>
@ -226,7 +228,7 @@ class Artist extends React.Component {
/>
</section>
</div>
) : null}
)}
</div>
);
}

View File

@ -144,37 +144,50 @@ class Queue extends React.Component {
const items = [];
// Radio nests it's seed URIs in an encoded URI format
if (uri_type === 'radio') {
const radio_seeds = helpers.getFromUri('seeds', added_from_uri);
for (let seed of radio_seeds) {
let item_type = helpers.uriType(seed);
let item_library = this.props[`${item_type}s`];
if (item_library && item_library[seed]) {
items.push(item_library[seed]);
switch (uri_type){
case 'radio':
const radio_seeds = helpers.getFromUri('seeds', added_from_uri);
for (let seed of radio_seeds) {
let item_type = helpers.uriType(seed);
let item_library = this.props[`${item_type}s`];
if (item_library && item_library[seed]) {
items.push(item_library[seed]);
}
}
}
} else {
const item_library = this.props[`${uri_type}s`];
if (item_library && item_library[added_from_uri]) {
items.push(item_library[added_from_uri]);
}
break;
case 'search':
items.push({
uri: added_from_uri,
name: `"${helpers.getFromUri('searchterm', added_from_uri)}" search`,
})
break;
default:
const item_library = this.props[`${uri_type}s`];
if (item_library && item_library[added_from_uri]) {
items.push(item_library[added_from_uri]);
}
break;
}
if (items.length <= 0) return null;
return (
<div className="current-track__added-from">
<URILink
uri={items[0].uri}
className="current-track__added-from__thumbnail"
>
<Thumbnail
images={items[0].images}
size="small"
circle={helpers.uriType(items[0].uri) === 'artist'}
/>
</URILink>
{items[0].images && (
<URILink
uri={items[0].uri}
className="current-track__added-from__thumbnail"
>
<Thumbnail
images={items[0].images}
size="small"
circle={helpers.uriType(items[0].uri) === 'artist'}
/>
</URILink>
)}
<div className="current-track__added-from__text">
{'Playing from '}
<LinksSentence items={items} />
@ -299,26 +312,31 @@ const mapStateToProps = (state, ownProps) => {
if (state.core.queue && state.core.tracks) {
for (const queue_track of state.core.queue) {
let track = { ...queue_track };
let track = {
...queue_track,
playing: current_track && current_track.tlid == queue_track.tlid,
};
// If we have the track in our index, merge it in.
// We prioritise queue track over index track as queue has unique data, like which track
// is playing and tlids.
if (state.core.tracks.hasOwnProperty(track.uri)) {
track = { ...state.core.tracks[track.uri], ...track, playing: current_track && current_track.tlid == track.tlid };
track = {
...state.core.tracks[track.uri],
...track,
};
}
// Now merge in our queue metadata
if (state.core.queue_metadata[`tlid_${track.tlid}`] !== undefined) {
track = {
...track,
...state.core.queue_metadata[`tlid_${track.tlid}`],
};
}
// Siphon off this track if it's a full representation of our current track (by tlid)
if (current_track && current_track.uri == track.uri) {
// Siphon off this track if it's a full representation of our current track
if (track.playing) {
current_track = track;
}

View File

@ -22,7 +22,7 @@ import * as uiActions from '../services/ui/actions';
import * as mopidyActions from '../services/mopidy/actions';
import * as spotifyActions from '../services/spotify/actions';
class Search extends React.Component {
class Search extends React.Component {
constructor(props) {
super(props);
@ -43,7 +43,7 @@ class Search extends React.Component {
this.digestUri();
}
componentWillReceiveProps(nextProps) {
componentWillReceiveProps(nextProps) {
// Query changed
if (nextProps.term !== this.props.term || nextProps.type !== this.props.type) {
this.digestUri(nextProps);
@ -90,10 +90,10 @@ class Search extends React.Component {
}
}
search(type = this.state.type, term = this.state.term, provider) {
search(type = this.state.type, term = this.state.term, provider) {
this.props.uiActions.setWindowTitle(`Search: ${term}`);
if (type && term) {
if (type && term) {
if (provider == 'mopidy' || (this.props.mopidy_connected && this.props.uri_schemes_search_enabled)) {
if (this.props.mopidy_search_results.query === undefined || this.props.mopidy_search_results.query != term) {
this.props.mopidyActions.clearSearchResults();
@ -126,6 +126,11 @@ class Search extends React.Component {
this.props.uiActions.set(data);
}
handleSourceChange(value) {
this.props.uiActions.set({ uri_schemes_search_enabled: value });
this.props.uiActions.hideContextMenu();
}
renderArtists(artists, spotify_search_enabled) {
return (
<div>
@ -133,9 +138,8 @@ class Search extends React.Component {
<URILink type="search" uri={`search:all:${this.state.term}`}>
Search
</URILink>
<Icon type="fontawesome" name="angle-right" />
&nbsp;
Artists
<Icon type="fontawesome" name="angle-right" />
{` Artists`}
</h4>
<section className="grid-wrapper">
<ArtistGrid artists={artists} show_source_icon />
@ -150,11 +154,10 @@ class Search extends React.Component {
<div>
<h4>
<URILink type="search" uri={`search:all:${this.state.term}`}>
Search
{`Search `}
</URILink>
<Icon type="fontawesome" name="angle-right" />
&nbsp;
Albums
<Icon type="fontawesome" name="angle-right" />
{` Albums`}
</h4>
<section className="grid-wrapper">
<AlbumGrid albums={albums} show_source_icon />
@ -169,11 +172,10 @@ class Search extends React.Component {
<div>
<h4>
<URILink type="search" uri={`search:all:${this.state.term}`}>
Search
{`Search `}
</URILink>
<Icon type="fontawesome" name="angle-right" />
&nbsp;
Playlists
<Icon type="fontawesome" name="angle-right" />
{` Playlists`}
</h4>
<section className="grid-wrapper">
<PlaylistGrid playlists={playlists} show_source_icon />
@ -188,11 +190,10 @@ class Search extends React.Component {
<div>
<h4>
<URILink type="search" uri={`search:all:${this.state.term}`}>
Search
{`Search `}
</URILink>
<Icon type="fontawesome" name="angle-right" />
&nbsp;
Tracks
<Icon type="fontawesome" name="angle-right" />
{` Tracks`}
</h4>
<section className="list-wrapper">
<TrackList tracks={tracks} uri={`iris:search:${this.state.type}:${this.state.term}`} show_source_icon />
@ -211,13 +212,11 @@ class Search extends React.Component {
<h4>Artists</h4>
</URILink>
<ArtistGrid mini show_source_icon artists={artists.slice(0, 6)} />
{artists.length >= 6 ? (
{artists.length >= 6 && (
<URILink type="search" uri={`search:artist:${this.state.term}`} className="button button--default">
All artists (
{artists.length}
)
</URILink>
) : null}
{`All artists (${artists.length})`}
</URILink>
)}
</div>
</section>
);
@ -233,13 +232,11 @@ class Search extends React.Component {
<h4>Albums</h4>
</URILink>
<AlbumGrid mini show_source_icon albums={albums.slice(0, 6)} />
{albums.length >= 6 ? (
{albums.length >= 6 && (
<URILink type="search" uri={`search:album:${this.state.term}`} className="button button--default">
All albums (
{albums.length}
)
</URILink>
) : null}
{`All albums (${albums.length})`}
</URILink>
)}
</div>
</section>
);
@ -255,13 +252,11 @@ class Search extends React.Component {
<h4>Playlists</h4>
</URILink>
<PlaylistGrid mini show_source_icon playlists={playlists.slice(0, 6)} />
{playlists.length >= 6 ? (
{playlists.length >= 6 && (
<URILink type="search" uri={`search:playlist:${this.state.term}`} className="button button--default">
All playlists (
{playlists.length}
)
</URILink>
) : null}
{`All playlists (${playlists.length})`}
</URILink>
)}
</div>
</section>
);
@ -381,19 +376,25 @@ class Search extends React.Component {
const options = (
<span>
<DropdownField
icon="sort"
icon="swap_vert"
name="Sort"
value={this.props.sort}
valueAsLabel
options={sort_options}
selected_icon={this.props.sort_reverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down'}
handleChange={(value) => { this.setSort(value); this.props.uiActions.hideContextMenu(); }}
handleChange={(value) => { this.setSort(value); this.props.uiActions.hideContextMenu(); }}
/>
<DropdownField
icon="cloud"
name="Sources"
value={this.props.uri_schemes_search_enabled}
options={provider_options}
handleChange={(value) => { this.props.uiActions.set({ uri_schemes_search_enabled: value }); this.props.uiActions.hideContextMenu(); }}
handleChange={(value) => this.handleSourceChange(value)}
onClose={() => {
this.props.spotifyActions.clearSearchResults();
this.props.mopidyActions.clearSearchResults();
this.search()
}}
/>
</span>
);
@ -407,7 +408,7 @@ class Search extends React.Component {
<SearchForm
history={this.props.history}
term={this.state.term}
onSubmit={(term) => this.handleSubmit(term)}
onSubmit={(term) => this.handleSubmit(term)}
/>
<div className="content-wrapper">
@ -464,4 +465,4 @@ const mapDispatchToProps = (dispatch) => ({
spotifyActions: bindActionCreators(spotifyActions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Search);
export default connect(mapStateToProps, mapDispatchToProps)(Search);

View File

@ -277,7 +277,7 @@ class LibraryAlbums extends React.Component {
const sort_options = [
{
value: null,
label: 'Default',
label: 'As loaded',
},
{
value: 'name',
@ -302,16 +302,17 @@ class LibraryAlbums extends React.Component {
];
const options = (
<span>
<div className="header__options__wrapper">
<FilterField
initialValue={this.state.filter}
handleChange={(value) => this.setState({ filter: value, limit: this.state.per_page })}
onSubmit={e => this.props.uiActions.hideContextMenu()}
/>
<DropdownField
icon="sort"
icon="swap_vert"
name="Sort"
value={this.props.sort}
valueAsLabel
options={sort_options}
selected_icon={this.props.sort ? (this.props.sort_reverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
handleChange={(val) => { this.setSort(val); this.props.uiActions.hideContextMenu(); }}
@ -320,6 +321,7 @@ class LibraryAlbums extends React.Component {
icon="visibility"
name="View"
value={this.props.view}
valueAsLabel
options={view_options}
handleChange={(val) => { this.props.uiActions.set({ library_albums_view: val }); this.props.uiActions.hideContextMenu(); }}
/>
@ -327,10 +329,11 @@ class LibraryAlbums extends React.Component {
icon="cloud"
name="Source"
value={this.props.source}
valueAsLabel
options={source_options}
handleChange={(val) => { this.props.uiActions.set({ library_albums_source: val }); this.props.uiActions.hideContextMenu(); }}
/>
</span>
</div>
);
return (

View File

@ -257,20 +257,20 @@ class LibraryArtists extends React.Component {
const sort_options = [
{
label: 'Default',
value: null,
label: 'As loaded',
},
{
label: 'Name',
value: 'name',
label: 'Name',
},
{
label: 'Followers',
value: 'followers',
label: 'Followers',
},
{
label: 'Popularity',
value: 'popularity',
label: 'Popularity',
},
];
@ -282,9 +282,10 @@ class LibraryArtists extends React.Component {
onSubmit={e => this.props.uiActions.hideContextMenu()}
/>
<DropdownField
icon="sort"
icon="swap_vert"
name="Sort"
value={this.props.sort}
valueAsLabel
options={sort_options}
selected_icon={this.props.sort ? (this.props.sort_reverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
handleChange={(value) => { this.setSort(value); this.props.uiActions.hideContextMenu(); }}
@ -293,6 +294,7 @@ class LibraryArtists extends React.Component {
icon="visibility"
name="View"
value={this.props.view}
valueAsLabel
options={view_options}
handleChange={(value) => { this.props.uiActions.set({ library_artists_view: value }); this.props.uiActions.hideContextMenu(); }}
/>
@ -300,6 +302,7 @@ class LibraryArtists extends React.Component {
icon="cloud"
name="Source"
value={this.props.source}
valueAsLabel
options={source_options}
handleChange={(value) => { this.props.uiActions.set({ library_artists_source: value }); this.props.uiActions.hideContextMenu(); }}
/>

View File

@ -208,7 +208,7 @@ class LibraryPlaylists extends React.Component {
const sort_options = [
{
value: null,
label: 'Default',
label: 'As loaded',
},
{
value: 'name',
@ -240,9 +240,10 @@ class LibraryPlaylists extends React.Component {
onSubmit={e => this.props.uiActions.hideContextMenu()}
/>
<DropdownField
icon="sort"
icon="swap_vert"
name="Sort"
value={this.props.sort}
valueAsLabel
options={sort_options}
selected_icon={this.props.sort ? (this.props.sort_reverse ? 'keyboard_arrow_up' : 'keyboard_arrow_down') : null}
handleChange={(value) => { this.setSort(value); this.props.uiActions.hideContextMenu(); }}
@ -250,6 +251,7 @@ class LibraryPlaylists extends React.Component {
<DropdownField
icon="visibility"
name="View"
valueAsLabel
value={this.props.view}
options={view_options}
handleChange={(value) => { this.props.uiActions.set({ library_playlists_view: value }); this.props.uiActions.hideContextMenu(); }}
@ -257,6 +259,7 @@ class LibraryPlaylists extends React.Component {
<DropdownField
icon="cloud"
name="Source"
valueAsLabel
value={this.props.source}
options={source_options}
handleChange={(value) => { this.props.uiActions.set({ library_playlists_source: value }); this.props.uiActions.hideContextMenu(); }}