Adding lastfm artwork lookup to list views; Splitting List into List and ListItem for better granularity

This commit is contained in:
James Barnsley
2019-02-22 13:17:14 +13:00
parent 5350500ff5
commit 33935115ab
6 changed files with 2776 additions and 3276 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -98,7 +98,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1550726971";
var build = "1550778095";
var version = "3.33.0";
// Construct the script tag

View File

@ -4,16 +4,11 @@ import { connect } from 'react-redux';
import { createStore, bindActionCreators } from 'redux';
import { withRouter } from 'react-router';
import ArtistSentence from './ArtistSentence';
import Dater from './Dater';
import URILink from './URILink';
import ContextMenuTrigger from './ContextMenuTrigger';
import Icon from './Icon';
import Thumbnail from './Thumbnail';
import Popularity from './Popularity';
import ListItem from './ListItem';
import * as helpers from '../helpers';
import * as uiActions from '../services/ui/actions';
import * as lastfmActions from '../services/lastfm/actions';
class List extends React.Component{
@ -21,26 +16,6 @@ class List extends React.Component{
super(props);
}
handleClick(e, uri){
// make sure we haven't clicked a nested link (ie Artist name)
if (e.target.tagName.toLowerCase() !== 'a'){
e.preventDefault();
this.props.history.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(uri));
helpers.scrollTo();
}
}
handleMouseDown(e, uri){
// make sure we haven't clicked a nested link (ie Artist name)
if (e.target.tagName.toLowerCase() !== 'a'){
e.preventDefault();
this.props.history.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(uri));
helpers.scrollTo();
}
}
handleContextMenu(e,item){
if (this.props.handleContextMenu){
e.preventDefault();
@ -48,31 +23,6 @@ class List extends React.Component{
}
}
renderValue(row, key_string){
var key = key_string.split('.');
var value = row;
for (var i = 0; i < key.length; i++){
if (value[key[i]] === undefined){
return null
} else if (typeof(value[key[i]]) === 'string' && value[key[i]].replace(' ','') == ''){
return null
} else {
value = value[key[i]]
}
}
if (key_string === 'tracks_total' || key_string === 'tracks_uris.length') return <span>{value} tracks</span>
if (key_string === 'followers') return <span>{value.toLocaleString()} followers</span>
if (key_string === 'added_at') return <span>Added <Dater type="ago" data={value} /> ago</span>
if (key_string === 'owner') return <URILink type="user" uri={value.uri}>{value.id}</URILink>
if (key_string === 'popularity') return <Popularity full popularity={value} />
if (key[0] === 'artists') return <ArtistSentence artists={value} />
if (value === true) return <Icon name="check" />
if (typeof(value) === 'number') return <span>{value.toLocaleString()}</span>
return <span>{value}</span>
}
render(){
if (!this.props.rows) return null
@ -84,87 +34,19 @@ class List extends React.Component{
return (
<div className={className}>
{
this.props.rows.map((row, row_index) => {
var class_name = 'list__item'
if (row.type){
class_name += ' list__item--'+row.type;
}
if (this.props.middle_column){
class_name += " list__item--has-middle-column";
}
if (this.props.thumbnail){
class_name += " list__item--has-thumbnail";
}
if (this.props.details){
class_name += " list__item--has-details";
}
this.props.rows.map((item, index) => {
return (
<div
className={class_name}
key={row_index}
onClick={e => this.handleClick(e, row.uri)}
onContextMenu={e => this.handleContextMenu(e,row)}>
<div className="list__item__column list__item__column--right">
{
(this.props.right_column ? this.props.right_column.map((item, index) => {
return (
<span className={'list__item__column__item list__item__column__item--'+item.replace('.','_')} key={index}>
{this.renderValue(row, item)}
</span>
)
}) : null)
}
{this.props.nocontext ? null : <ContextMenuTrigger className="list__item__column__item list__item__column__item--context-menu-trigger subtle" onTrigger={e => this.handleContextMenu(e, row)} />}
</div>
<div className="list__item__column list__item__column--name">
{this.props.thumbnail ? <Thumbnail className="list__item__column__item list__item__column__item--thumbnail" images={(row.images ? row.images : null)} size="small" /> : null}
<div className="list__item__column__item list__item__column__item--name">
{row.name !== undefined ? this.renderValue(row, 'name') : <span className="grey-text">{row.uri}</span>}
</div>
{this.props.details ?<ul className="list__item__column__item list__item__column__item--details details">
{
this.props.details.map((item, index) => {
var value = this.renderValue(row, item);
if (!value){
return null;
}
return (
<li className={'details__item details__item--'+item.replace('.','_')} key={index}>
{value}
</li>
)
})
}
</ul> : null}
</div>
{this.props.middle_column ? <div className="list__item__column list__item__column--middle">
{
(this.props.middle_column ? this.props.middle_column.map((item, index) => {
return (
<span className={'list__item__column__item list__item__column__item--'+item.replace('.','_')} key={index}>
{this.renderValue(row, item)}
</span>
)
}) : null)
}
</div> : null}
</div>
)
<ListItem
key={index}
item={item}
lastfmActions={this.props.lastfmActions}
history={this.props.history}
link_prefix={this.props.link_prefix}
handleContextMenu={e => this.props.handleContextMenu(e, item)}
thumbnail={this.props.thumbnail}
details={this.props.details}
/>
);
})
}
</div>
@ -178,7 +60,8 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = (dispatch) => {
return {
uiActions: bindActionCreators(uiActions, dispatch)
uiActions: bindActionCreators(uiActions, dispatch),
lastfmActions: bindActionCreators(lastfmActions, dispatch)
}
}

182
src/js/components/ListItem.js Executable file
View File

@ -0,0 +1,182 @@
import React from 'react';
import ArtistSentence from './ArtistSentence';
import Dater from './Dater';
import URILink from './URILink';
import ContextMenuTrigger from './ContextMenuTrigger';
import Icon from './Icon';
import Thumbnail from './Thumbnail';
import Popularity from './Popularity';
import * as helpers from '../helpers';
export default class ListItem extends React.Component{
constructor(props){
super(props);
}
componentDidMount(){
if (this.props.item){
var item = this.props.item;
} else {
return;
}
// If the item that has just been mounted doesn't have images,
// try fetching them from LastFM
if (!item.images && this.props.lastfmActions){
switch (helpers.uriType(item.uri)){
case 'artist':
this.props.lastfmActions.getArtist(item.uri, item.name);
break;
case 'album':
if (item.artists && item.artists.length > 0){
this.props.lastfmActions.getAlbum(item.uri, item.artists[0].name, item.name, (item.mbid ? item.mbid : null));
}
break;
}
}
}
handleClick(e){
// make sure we haven't clicked a nested link (ie Artist name)
if (e.target.tagName.toLowerCase() !== 'a'){
e.preventDefault();
this.props.history.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(this.props.item.uri));
helpers.scrollTo();
}
}
handleMouseDown(e){
// make sure we haven't clicked a nested link (ie Artist name)
if (e.target.tagName.toLowerCase() !== 'a'){
e.preventDefault();
this.props.history.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(this.props.item.uri));
helpers.scrollTo();
}
}
handleContextMenu(e){
if (this.props.handleContextMenu){
e.preventDefault();
this.props.handleContextMenu(e,this.props.item);
}
}
renderValue(key_string){
var key = key_string.split('.');
var value = Object.assign(this.props.item);
for (var i = 0; i < key.length; i++){
if (value[key[i]] === undefined){
return null
} else if (typeof(value[key[i]]) === 'string' && value[key[i]].replace(' ','') == ''){
return null
} else {
value = value[key[i]]
}
}
if (key_string === 'tracks_total' || key_string === 'tracks_uris.length') return <span>{value} tracks</span>
if (key_string === 'followers') return <span>{value.toLocaleString()} followers</span>
if (key_string === 'added_at') return <span>Added <Dater type="ago" data={value} /> ago</span>
if (key_string === 'owner') return <URILink type="user" uri={value.uri}>{value.id}</URILink>
if (key_string === 'popularity') return <Popularity full popularity={value} />
if (key[0] === 'artists') return <ArtistSentence artists={value} />
if (value === true) return <Icon name="check" />
if (typeof(value) === 'number') return <span>{value.toLocaleString()}</span>
return <span>{value}</span>
}
render(){
var item = this.props.item;
if (!item){
return null;
}
var class_name = 'list__item'
if (item.type){
class_name += ' list__item--'+item.type;
}
if (this.props.middle_column){
class_name += " list__item--has-middle-column";
}
if (this.props.thumbnail){
class_name += " list__item--has-thumbnail";
}
if (this.props.details){
class_name += " list__item--has-details";
}
return (
<div
className={class_name}
onClick={e => this.handleClick(e)}
onContextMenu={e => this.handleContextMenu(e)}>
<div className="list__item__column list__item__column--right">
{
(this.props.right_column ? this.props.right_column.map((column, index) => {
return (
<span className={'list__item__column__item list__item__column__item--'+column.replace('.','_')} key={index}>
{this.renderValue(column, item)}
</span>
)
}) : null)
}
{this.props.nocontext ? null : <ContextMenuTrigger className="list__item__column__item list__item__column__item--context-menu-trigger subtle" onTrigger={e => this.handleContextMenu(e)} />}
</div>
<div className="list__item__column list__item__column--name">
{this.props.thumbnail ? <Thumbnail className="list__item__column__item list__item__column__item--thumbnail" images={(item.images ? item.images : null)} size="small" /> : null}
<div className="list__item__column__item list__item__column__item--name">
{item.name !== undefined ? this.renderValue('name') : <span className="grey-text">{item.uri}</span>}
</div>
{this.props.details ?<ul className="list__item__column__item list__item__column__item--details details">
{
this.props.details.map((detail, index) => {
var value = this.renderValue(detail);
if (!value){
return null;
}
return (
<li className={'details__item details__item--'+detail.replace('.','_')} key={index}>
{value}
</li>
)
})
}
</ul> : null}
</div>
{this.props.middle_column ? <div className="list__item__column list__item__column--middle">
{
(this.props.middle_column ? this.props.middle_column.map((column, index) => {
return (
<span className={'list__item__column__item list__item__column__item--'+column.replace('.','_')} key={index}>
{this.renderValue(column)}
</span>
)
}) : null)
}
</div> : null}
</div>
);
}
}

View File

@ -63,7 +63,7 @@ class Album extends React.Component{
// We have just received our full album or our album artists
if ((!this.props.album && nextProps.album) || (!this.props.album.artists && nextProps.album.artists)){
if (this.props.album.wiki === undefined){
if (nextProps.album.wiki === undefined && nextProps.artists.length > 0){
this.props.lastfmActions.getAlbum(nextProps.album.uri, nextProps.album.artists[0].name, nextProps.album.name);
}
}