Upgrading lists
This commit is contained in:
@ -21,7 +21,7 @@ const Grid = memo(({
|
||||
item={item}
|
||||
getLink={getLink}
|
||||
itemHeight={itemHeight || '300px'}
|
||||
isFirstInGrid={index === 0}
|
||||
isFirst={index === 0}
|
||||
setItemHeight={setItemHeight}
|
||||
/>
|
||||
))
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
import handleViewport from 'react-in-viewport';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
@ -69,7 +69,7 @@ const GridItemComponent = ({
|
||||
show_source_icon,
|
||||
grid_glow_enabled,
|
||||
spotify_available,
|
||||
isFirstInGrid,
|
||||
isFirst,
|
||||
uiActions: {
|
||||
showContextMenu,
|
||||
},
|
||||
@ -90,7 +90,7 @@ const GridItemComponent = ({
|
||||
// Listen for changes to our height, and pass it up to our Grid. This is then used to build the
|
||||
// placeholder elements when out of viewport. We only care about the first item because this
|
||||
// represents the same heights for everything else (in almost all circumstances).
|
||||
if (isFirstInGrid && forwardedRef.current) {
|
||||
if (isFirst && forwardedRef.current) {
|
||||
const { current: { clientHeight } } = forwardedRef;
|
||||
if (clientHeight !== itemHeight) {
|
||||
setItemHeight(clientHeight);
|
||||
@ -134,7 +134,7 @@ const GridItemComponent = ({
|
||||
|
||||
return (
|
||||
<span className={`grid__item grid__item--${item.type}`} ref={forwardedRef}>
|
||||
{inViewport || isFirstInGrid ? (
|
||||
{inViewport || isFirst ? (
|
||||
<Link
|
||||
to={to}
|
||||
onClick={scrollTo}
|
||||
|
||||
@ -1,52 +1,39 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { withRouter } from 'react-router';
|
||||
import ListItem from './ListItem';
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import * as lastfmActions from '../services/lastfm/actions';
|
||||
import * as discogsActions from '../services/discogs/actions';
|
||||
import React, { useState } from 'react';
|
||||
import { ListItem } from './ListItem';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
class List extends React.Component {
|
||||
handleContextMenu(e, item) {
|
||||
const { handleContextMenu } = this.props;
|
||||
const List = ({
|
||||
items,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
if (!items || !items.length) return null;
|
||||
const [itemHeight, setItemHeight] = useState(0);
|
||||
|
||||
if (handleContextMenu) {
|
||||
e.preventDefault();
|
||||
handleContextMenu(e, item);
|
||||
}
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const {
|
||||
rows,
|
||||
className,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
if (!rows) return null;
|
||||
|
||||
return (
|
||||
<div className={`list ${className}`}>
|
||||
return (
|
||||
<div className={`list ${className}`}>
|
||||
<ErrorBoundary>
|
||||
{
|
||||
rows.map((item, index) => (
|
||||
items.map((item, index) => (
|
||||
<ListItem
|
||||
key={index}
|
||||
key={`${index}_${item.uri || item.name}`}
|
||||
item={item}
|
||||
handleContextMenu={(e) => this.handleContextMenu(e, item)}
|
||||
itemHeight={itemHeight || '300px'}
|
||||
isFirst={index === 0}
|
||||
setItemHeight={setItemHeight}
|
||||
{...rest}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
))
|
||||
}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
List,
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
lastfmActions: bindActionCreators(lastfmActions, dispatch),
|
||||
discogsActions: bindActionCreators(discogsActions, dispatch),
|
||||
});
|
||||
|
||||
export default withRouter(connect(mapDispatchToProps)(List));
|
||||
export {
|
||||
List,
|
||||
};
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import React from 'react';
|
||||
import handleViewport from 'react-in-viewport';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import LinksSentence from './LinksSentence';
|
||||
import { dater } from './Dater';
|
||||
import { nice_number } from './NiceNumber';
|
||||
@ -7,101 +9,27 @@ import ContextMenuTrigger from './ContextMenuTrigger';
|
||||
import Icon from './Icon';
|
||||
import Thumbnail from './Thumbnail';
|
||||
import Popularity from './Popularity';
|
||||
import {
|
||||
uriType,
|
||||
scrollTo,
|
||||
} from '../util/helpers';
|
||||
import Link from './Link';
|
||||
import { I18n } from '../locale';
|
||||
import { encodeUri } from '../util/format';
|
||||
|
||||
export default class ListItem extends React.Component {
|
||||
componentDidMount() {
|
||||
const { item, mopidyActions, discogsActions } = this.props;
|
||||
if (!item) return;
|
||||
import * as uiActions from '../services/ui/actions';
|
||||
import * as lastfmActions from '../services/lastfm/actions';
|
||||
import * as mopidyActions from '../services/mopidy/actions';
|
||||
import * as spotifyActions from '../services/spotify/actions';
|
||||
|
||||
// If the item that has just been mounted doesn't have images,
|
||||
// try fetching them from LastFM
|
||||
if (!item.images) {
|
||||
switch (uriType(item.uri)) {
|
||||
case 'artist':
|
||||
if (discogsActions) {
|
||||
//discogsActions.getArtistImages(item.uri, item);
|
||||
}
|
||||
break;
|
||||
const getValue = (item = {}, name = '') => {
|
||||
const { [name]: value } = item;
|
||||
|
||||
case 'album':
|
||||
if (mopidyActions) {
|
||||
mopidyActions.getImages([item.uri]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
const {
|
||||
history,
|
||||
link_prefix = '',
|
||||
link,
|
||||
item,
|
||||
} = this.props;
|
||||
|
||||
if (e.target.tagName.toLowerCase() !== 'a') {
|
||||
e.preventDefault();
|
||||
history.push(link ? link(item) : `${link_prefix}${encodeUri(item.uri)}`);
|
||||
scrollTo();
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDown = (e) => {
|
||||
const {
|
||||
history,
|
||||
link_prefix = '',
|
||||
link,
|
||||
item,
|
||||
} = this.props;
|
||||
|
||||
if (e.target.tagName.toLowerCase() !== 'a') {
|
||||
e.preventDefault();
|
||||
history.push(link ? link(item) : `${link_prefix}${encodeUri(item.uri)}`);
|
||||
scrollTo();
|
||||
}
|
||||
}
|
||||
|
||||
handleContextMenu = (e) => {
|
||||
const {
|
||||
handleContextMenu,
|
||||
item,
|
||||
} = this.props;
|
||||
|
||||
if (handleContextMenu) {
|
||||
e.preventDefault();
|
||||
handleContextMenu(e, item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* THIS WHOLE BLOCK NEEDS A REVISIT.
|
||||
* Surely there is a cleaner way to pull values? Perhaps simplify it like our new sortItems?
|
||||
*/
|
||||
renderValue = (key) => {
|
||||
const {
|
||||
item: {
|
||||
[key]: value,
|
||||
...item
|
||||
} = {},
|
||||
} = this.props;
|
||||
|
||||
if (key === 'tracks') {
|
||||
switch (name) {
|
||||
case 'tracks': {
|
||||
const {
|
||||
tracks_total: total,
|
||||
} = item;
|
||||
if (!total && !value) return null;
|
||||
return <I18n path="specs.tracks" count={nice_number(total || value.length)} />;
|
||||
}
|
||||
if (key === 'artists') {
|
||||
case 'artists': {
|
||||
const {
|
||||
artists_total: total,
|
||||
artists_uris: array,
|
||||
@ -110,7 +38,7 @@ export default class ListItem extends React.Component {
|
||||
if (!total && !array) return null;
|
||||
return <I18n path="specs.artists" count={nice_number(total || array.length)} />;
|
||||
}
|
||||
if (key === 'albums') {
|
||||
case 'albums': {
|
||||
const {
|
||||
albums_total: total,
|
||||
albums_uris: array,
|
||||
@ -118,74 +46,124 @@ export default class ListItem extends React.Component {
|
||||
if (!total && !array) return null;
|
||||
return <I18n path="specs.albums" count={total || array.length} />;
|
||||
}
|
||||
|
||||
// All options beyond here don't play well with null/undefined
|
||||
if (!value) return null;
|
||||
|
||||
if (key === 'followers') {
|
||||
return <I18n path="specs.followers" count={nice_number(value)} />;
|
||||
}
|
||||
if (key === 'listeners') {
|
||||
return <I18n path="specs.listeners" count={nice_number(value)} />;
|
||||
}
|
||||
if (key === 'added_at') {
|
||||
return <I18n path="specs.added_ago" time={dater('ago', value)} />;
|
||||
}
|
||||
if (key === 'last_modified') {
|
||||
return <I18n path="specs.updated_ago" time={dater('ago', value)} />;
|
||||
}
|
||||
if (key === 'owner') {
|
||||
return <URILink type="user" uri={value.uri}>{value.id}</URILink>;
|
||||
}
|
||||
if (key === 'popularity') {
|
||||
return <Popularity full popularity={value} />;
|
||||
}
|
||||
|
||||
if (value === true) return <Icon name="check" />;
|
||||
if (typeof (value) === 'number') return <span>{value.toLocaleString()}</span>;
|
||||
return value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const {
|
||||
item,
|
||||
middle_column,
|
||||
right_column,
|
||||
thumbnail,
|
||||
details,
|
||||
nocontext,
|
||||
} = this.props;
|
||||
if (!item) {
|
||||
return null;
|
||||
// All options beyond here don't play well with null/undefined
|
||||
if (!value) return null;
|
||||
|
||||
switch (name) {
|
||||
case 'followers':
|
||||
return <I18n path="specs.followers" count={nice_number(value)} />;
|
||||
case 'listeners':
|
||||
return <I18n path="specs.listeners" count={nice_number(value)} />;
|
||||
case 'added_at':
|
||||
return <I18n path="specs.added_ago" time={dater('ago', value)} />;
|
||||
case 'last_modified':
|
||||
return <I18n path="specs.updated_ago" time={dater('ago', value)} />;
|
||||
case 'owner':
|
||||
return <URILink type="user" uri={value.uri}>{value.id}</URILink>;
|
||||
case 'popularity':
|
||||
return <Popularity full popularity={value} />;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (value === true) return <Icon name="check" />;
|
||||
if (typeof (value) === 'number') return <span>{value.toLocaleString()}</span>;
|
||||
return value;
|
||||
};
|
||||
|
||||
const ListItemComponent = ({
|
||||
item,
|
||||
middle_column,
|
||||
right_column,
|
||||
thumbnail,
|
||||
details,
|
||||
nocontext,
|
||||
forwardedRef,
|
||||
getLink,
|
||||
isFirst,
|
||||
inViewport,
|
||||
itemHeight,
|
||||
setItemHeight,
|
||||
}) => {
|
||||
if (!item) return null;
|
||||
|
||||
// Listen for changes to our height, and pass it up to our Grid. This is then used to build the
|
||||
// placeholder elements when out of viewport. We only care about the first item because this
|
||||
// represents the same heights for everything else (in almost all circumstances).
|
||||
if (isFirst && forwardedRef.current) {
|
||||
const { current: { clientHeight } } = forwardedRef;
|
||||
if (clientHeight !== itemHeight) {
|
||||
console.debug({ clientHeight, itemHeight })
|
||||
setItemHeight(clientHeight);
|
||||
}
|
||||
}
|
||||
|
||||
let class_name = 'list__item';
|
||||
if (item.type) class_name += ` list__item--${item.type}`;
|
||||
if (item.loading) class_name += ' list__item--loading';
|
||||
if (middle_column) class_name += ' list__item--has-middle-column';
|
||||
if (thumbnail) class_name += ' list__item--has-thumbnail';
|
||||
if (details) class_name += ' list__item--has-details';
|
||||
let class_name = 'list__item';
|
||||
if (item.type) class_name += ` list__item--${item.type}`;
|
||||
if (item.loading) class_name += ' list__item--loading';
|
||||
if (middle_column) class_name += ' list__item--has-middle-column';
|
||||
if (thumbnail) class_name += ' list__item--has-thumbnail';
|
||||
if (details) class_name += ' list__item--has-details';
|
||||
|
||||
// Return our placeholder as soon as possible, avoiding additional hooks and code
|
||||
if (!inViewport && !isFirst) {
|
||||
return (
|
||||
<div
|
||||
className={class_name}
|
||||
onClick={this.handleClick}
|
||||
onContextMenu={this.handleContextMenu}
|
||||
>
|
||||
<div className={class_name} ref={forwardedRef}>
|
||||
<div style={{ height: itemHeight }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Build link
|
||||
let to = '';
|
||||
if (getLink) {
|
||||
to = getLink(item);
|
||||
} else if (item.link) {
|
||||
to = item.link;
|
||||
} else {
|
||||
to = `/${item.type}/${encodeUri(item.uri)}`;
|
||||
}
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const onContextMenu = (e) => {
|
||||
e.preventDefault();
|
||||
dispatch(
|
||||
uiActions.showContextMenu({
|
||||
e,
|
||||
context: item.type,
|
||||
uris: [item.uri],
|
||||
items: [item],
|
||||
tracklist_uri: item.uri, // not needed?
|
||||
}),
|
||||
);
|
||||
};
|
||||
const grid_glow_enabled = useSelector((state) => state.ui.grid_glow_enabled);
|
||||
const spotify_available = useSelector((state) => state.spotify.access_token);
|
||||
|
||||
return (
|
||||
<div className={class_name} onContextMenu={onContextMenu} ref={forwardedRef}>
|
||||
<Link to={to} className="list__item__inner">
|
||||
{
|
||||
right_column && !nocontext && (
|
||||
<div className="list__item__column list__item__column--right">
|
||||
{
|
||||
right_column.map((column, index) => (
|
||||
<span className={`list__item__column__item list__item__column__item--${column.replace('.', '_')}`} key={index}>
|
||||
{this.renderValue(column, item)}
|
||||
right_column.map((column) => (
|
||||
<span
|
||||
className={`list__item__column__item list__item__column__item--${column.replace('.', '_')}`}
|
||||
key={`${column}`}
|
||||
>
|
||||
{getValue(item, column)}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
))
|
||||
}
|
||||
{!nocontext && (
|
||||
<ContextMenuTrigger
|
||||
className="list__item__column__item list__item__column__item--context-menu-trigger subtle"
|
||||
onTrigger={this.handleContextMenu}
|
||||
onTrigger={onContextMenu}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -202,7 +180,7 @@ export default class ListItem extends React.Component {
|
||||
<div className="list__item__column__item list__item__column__item--name">
|
||||
{
|
||||
item.name !== undefined
|
||||
? this.renderValue('name')
|
||||
? getValue(item, 'name')
|
||||
: <span className="grey-text">{item.uri}</span>
|
||||
}
|
||||
</div>
|
||||
@ -210,19 +188,19 @@ export default class ListItem extends React.Component {
|
||||
{details ? (
|
||||
<ul className="list__item__column__item list__item__column__item--details details">
|
||||
{
|
||||
details.map((detail, index) => {
|
||||
const value = this.renderValue(detail);
|
||||
if (!value) return null;
|
||||
return (
|
||||
details.map((detail) => {
|
||||
const value = getValue(item, detail);
|
||||
if (!value) return null;
|
||||
return (
|
||||
<li
|
||||
className={`details__item details__item--${detail.replace('.', '_')}`}
|
||||
key={index}
|
||||
key={detail}
|
||||
>
|
||||
{value}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
) : null}
|
||||
</div>
|
||||
@ -230,18 +208,28 @@ export default class ListItem extends React.Component {
|
||||
{middle_column && (
|
||||
<div className="list__item__column list__item__column--middle">
|
||||
{
|
||||
middle_column.map((column, index) => (
|
||||
middle_column.map((column) => (
|
||||
<span
|
||||
className={`list__item__column__item list__item__column__item--${column.replace('.', '_')}`}
|
||||
key={index}
|
||||
key={column}
|
||||
>
|
||||
{this.renderValue(column)}
|
||||
{getValue(item, column)}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ListItem = handleViewport(ListItemComponent);
|
||||
|
||||
export {
|
||||
ListItem,
|
||||
};
|
||||
|
||||
export default {
|
||||
ListItem,
|
||||
};
|
||||
|
||||
@ -377,39 +377,41 @@ export default class Track extends React.Component {
|
||||
onTouchStart={this.handleTouchStart}
|
||||
onTouchEnd={this.handleTouchEnd}
|
||||
>
|
||||
<div className="list__item__column list__item__column--name">
|
||||
<div className="list__item__column__item--name">
|
||||
{track.name ? track.name : <span className="mid_grey-text">{track.uri}</span>}
|
||||
{track.playing && <Icon className={`js--${play_state}`} name="playing" type="css" />}
|
||||
<div className="list__item__inner">
|
||||
<div className="list__item__column list__item__column--name">
|
||||
<div className="list__item__column__item--name">
|
||||
{track.name ? track.name : <span className="mid_grey-text">{track.uri}</span>}
|
||||
{track.playing && <Icon className={`js--${play_state}`} name="playing" type="css" />}
|
||||
</div>
|
||||
{track_details && (
|
||||
<ul className="list__item__column__item--details">
|
||||
{track_details}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
{track_details && (
|
||||
<ul className="list__item__column__item--details">
|
||||
{track_details}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
{track_middle_column}
|
||||
<div className="list__item__column list__item__column--right">
|
||||
{drag_zone}
|
||||
{track.is_explicit && <span className="flag flag--dark">EXPLICIT</span>}
|
||||
{(track_context === 'album' || track_context === 'artist') && track.track_number && (
|
||||
<span className="mid_grey-text list__item__column__item list__item__column__item--track-number">
|
||||
<span>
|
||||
<I18n path="track.title" />
|
||||
|
||||
{track_middle_column}
|
||||
<div className="list__item__column list__item__column--right">
|
||||
{drag_zone}
|
||||
{track.is_explicit && <span className="flag flag--dark">EXPLICIT</span>}
|
||||
{(track_context === 'album' || track_context === 'artist') && track.track_number && (
|
||||
<span className="mid_grey-text list__item__column__item list__item__column__item--track-number">
|
||||
<span>
|
||||
<I18n path="track.title" />
|
||||
|
||||
</span>
|
||||
{track.track_number}
|
||||
</span>
|
||||
{track.track_number}
|
||||
)}
|
||||
<span className="list__item__column__item list__item__column__item--duration">
|
||||
{track.duration ? <Dater type="length" data={track.duration} /> : '-'}
|
||||
</span>
|
||||
)}
|
||||
<span className="list__item__column__item list__item__column__item--duration">
|
||||
{track.duration ? <Dater type="length" data={track.duration} /> : '-'}
|
||||
</span>
|
||||
{show_source_icon && (
|
||||
<span className="list__item__column__item list__item__column__item--source">
|
||||
<Icon type="fontawesome" name={sourceIcon(track.uri)} fixedWidth />
|
||||
</span>
|
||||
)}
|
||||
<ContextMenuTrigger className="list__item__column__item--context-menu-trigger subtle" onTrigger={(e) => this.props.handleContextMenu(e)} />
|
||||
{show_source_icon && (
|
||||
<span className="list__item__column__item list__item__column__item--source">
|
||||
<Icon type="fontawesome" name={sourceIcon(track.uri)} fixedWidth />
|
||||
</span>
|
||||
)}
|
||||
<ContextMenuTrigger className="list__item__column__item--context-menu-trigger subtle" onTrigger={(e) => this.props.handleContextMenu(e)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import List from '../../components/List';
|
||||
import Header from '../../components/Header';
|
||||
import DropdownField from '../../components/Fields/DropdownField';
|
||||
import FilterField from '../../components/Fields/FilterField';
|
||||
import { Grid } from '../../components/Grid';
|
||||
import { List } from '../../components/List';
|
||||
import Icon from '../../components/Icon';
|
||||
import * as coreActions from '../../services/core/actions';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
@ -179,13 +179,11 @@ class LibraryAlbums extends React.Component {
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<List
|
||||
handleContextMenu={this.handleContextMenu}
|
||||
rows={albums}
|
||||
items={albums}
|
||||
details={['followers', 'listeners']}
|
||||
right_column={['source', 'albums']}
|
||||
className="artists"
|
||||
thumbnail
|
||||
details={['artists']}
|
||||
right_column={['added_at', 'tracks']}
|
||||
className="albums"
|
||||
link_prefix="/album/"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -2,10 +2,10 @@ import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import Header from '../../components/Header';
|
||||
import List from '../../components/List';
|
||||
import DropdownField from '../../components/Fields/DropdownField';
|
||||
import FilterField from '../../components/Fields/FilterField';
|
||||
import { Grid } from '../../components/Grid';
|
||||
import { List } from '../../components/List';
|
||||
import Icon from '../../components/Icon';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
import * as coreActions from '../../services/core/actions';
|
||||
@ -165,13 +165,11 @@ class LibraryArtists extends React.Component {
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<List
|
||||
handleContextMenu={(e, item) => this.handleContextMenu(e, item)}
|
||||
rows={artists}
|
||||
thumbnail
|
||||
items={artists}
|
||||
details={['followers', 'listeners']}
|
||||
right_column={['source', 'albums']}
|
||||
className="artists"
|
||||
link_prefix="/artist/"
|
||||
thumbnail
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -3,13 +3,12 @@ import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import Loader from '../../components/Loader';
|
||||
import Header from '../../components/Header';
|
||||
import List from '../../components/List';
|
||||
import TrackList from '../../components/TrackList';
|
||||
import { Grid } from '../../components/Grid';
|
||||
import { List } from '../../components/List';
|
||||
import DropdownField from '../../components/Fields/DropdownField';
|
||||
import FilterField from '../../components/Fields/FilterField';
|
||||
import Icon from '../../components/Icon';
|
||||
import URILink from '../../components/URILink';
|
||||
import ErrorBoundary from '../../components/ErrorBoundary';
|
||||
import LazyLoadListener from '../../components/LazyLoadListener';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
@ -43,9 +42,9 @@ const Subdirectories = ({ items, view }) => {
|
||||
if (view === 'list') {
|
||||
return (
|
||||
<List
|
||||
rows={items}
|
||||
items={items}
|
||||
className="library-local-directory-list"
|
||||
link={link}
|
||||
getLink={link}
|
||||
nocontext
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import Button from '../../components/Button';
|
||||
import { Grid } from '../../components/Grid';
|
||||
import List from '../../components/List';
|
||||
import { List } from '../../components/List';
|
||||
import DropdownField from '../../components/Fields/DropdownField';
|
||||
import Header from '../../components/Header';
|
||||
import FilterField from '../../components/Fields/FilterField';
|
||||
import LazyLoadListener from '../../components/LazyLoadListener';
|
||||
import Icon from '../../components/Icon';
|
||||
import * as coreActions from '../../services/core/actions';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
@ -152,13 +150,10 @@ class LibraryPlaylists extends React.Component {
|
||||
return (
|
||||
<section className="content-wrapper">
|
||||
<List
|
||||
handleContextMenu={(e, item) => this.handleContextMenu(e, item)}
|
||||
rows={playlists}
|
||||
items={playlists}
|
||||
thumbnail
|
||||
details={['owner', 'tracks', 'last_modified']}
|
||||
right_column={['source']}
|
||||
className="playlists"
|
||||
link_prefix="/playlist/"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
@ -32,8 +31,6 @@ class LibraryTracks extends React.Component {
|
||||
|
||||
this.state = {
|
||||
filter: '',
|
||||
limit: 50,
|
||||
per_page: 50,
|
||||
};
|
||||
}
|
||||
|
||||
@ -47,13 +44,6 @@ class LibraryTracks extends React.Component {
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
// Restore any limit defined in our location state
|
||||
if (state.limit) {
|
||||
this.setState({
|
||||
limit: state.limit,
|
||||
});
|
||||
}
|
||||
|
||||
setWindowTitle(i18n('library.tracks.title'));
|
||||
this.getMopidyLibrary();
|
||||
this.getSpotifyLibrary();
|
||||
@ -126,23 +116,6 @@ class LibraryTracks extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
loadMore = () => {
|
||||
const {
|
||||
limit,
|
||||
per_page,
|
||||
} = this.state;
|
||||
const {
|
||||
location: {
|
||||
state,
|
||||
},
|
||||
history,
|
||||
} = this.props;
|
||||
|
||||
const new_limit = limit + per_page;
|
||||
this.setState({ limit: new_limit });
|
||||
history.replace({ state: { ...state, limit: new_limit } });
|
||||
}
|
||||
|
||||
setSort = (value) => {
|
||||
const {
|
||||
sort,
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
|
||||
.list {
|
||||
margin: 0 -20px 0 -20px;
|
||||
|
||||
&__item {
|
||||
@include clearfix();
|
||||
@include animate(0.1s);
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
@ -10,28 +10,6 @@
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin: 0 -20px 0 -20px;
|
||||
padding: 16px 16px 16px 20px;
|
||||
border-radius: 3px;
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
|
||||
&:before {
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: colour('blue');
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:not(:first-child){
|
||||
border-top: 1px solid rgba(128,128,128,0.05);
|
||||
|
||||
@ -40,6 +18,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__inner {
|
||||
@include animate(0.1s);
|
||||
padding: 16px 16px 16px 20px;
|
||||
border-radius: 3px;
|
||||
align-content: center;
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
border-bottom: none !important;
|
||||
|
||||
&:before {
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: colour('blue');
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:root .notouch:not(.dragging) &:not(.list__item--no-interaction){
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user