2019-09-05 15:36:11 +12:00
|
|
|
import React from 'react';
|
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
|
import { bindActionCreators } from 'redux';
|
|
|
|
|
|
|
|
|
|
import Link from '../components/Link';
|
|
|
|
|
import Icon from '../components/Icon';
|
|
|
|
|
import Parallax from '../components/Parallax';
|
|
|
|
|
import TrackList from '../components/TrackList';
|
|
|
|
|
import Dater from '../components/Dater';
|
|
|
|
|
import ArtistSentence from '../components/ArtistSentence';
|
|
|
|
|
import Thumbnail from '../components/Thumbnail';
|
|
|
|
|
import Header from '../components/Header';
|
|
|
|
|
import URILink from '../components/URILink';
|
|
|
|
|
import LazyLoadListener from '../components/LazyLoadListener';
|
|
|
|
|
|
|
|
|
|
import * as helpers from '../helpers';
|
|
|
|
|
import * as coreActions from '../services/core/actions';
|
|
|
|
|
import * as uiActions from '../services/ui/actions';
|
|
|
|
|
import * as pusherActions from '../services/pusher/actions';
|
|
|
|
|
import * as spotifyActions from '../services/spotify/actions';
|
|
|
|
|
import * as mopidyActions from '../services/mopidy/actions';
|
2019-01-15 16:57:50 +13:00
|
|
|
|
|
|
|
|
class Queue extends React.Component {
|
2019-08-13 16:53:39 +12:00
|
|
|
constructor(props) {
|
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
|
limit: 50,
|
2019-09-05 15:36:11 +12:00
|
|
|
per_page: 50,
|
2019-08-13 16:53:39 +12:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentWillMount() {
|
|
|
|
|
// Before we mount, restore any limit defined in our location state
|
2019-09-05 15:36:11 +12:00
|
|
|
const state = this.props.location.state ? this.props.location.state : {};
|
2019-08-13 16:53:39 +12:00
|
|
|
if (state.limit) {
|
|
|
|
|
this.setState({
|
2019-09-05 15:36:11 +12:00
|
|
|
limit: state.limit,
|
2019-08-13 16:53:39 +12:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
2019-09-05 15:36:11 +12:00
|
|
|
this.props.uiActions.setWindowTitle('Now playing');
|
2019-08-13 16:53:39 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shouldComponentUpdate(nextProps) {
|
|
|
|
|
return nextProps !== this.props;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentWillReceiveProps(nextProps) {
|
2019-09-27 16:23:33 +12:00
|
|
|
const { added_from_uri: next_added_from_uri } = nextProps;
|
|
|
|
|
const { coreActions, added_from_uri } = this.props;
|
2019-08-13 16:53:39 +12:00
|
|
|
|
2019-09-27 16:23:33 +12:00
|
|
|
if (next_added_from_uri && next_added_from_uri !== added_from_uri) {
|
|
|
|
|
const item_type = helpers.uriType(next_added_from_uri);
|
2019-08-13 16:53:39 +12:00
|
|
|
switch (item_type) {
|
2019-09-05 15:36:11 +12:00
|
|
|
case 'album':
|
2019-09-27 16:23:33 +12:00
|
|
|
coreActions.loadAlbum(next_added_from_uri);
|
2019-08-13 16:53:39 +12:00
|
|
|
break;
|
2019-09-05 15:36:11 +12:00
|
|
|
case 'artist':
|
2019-09-27 16:23:33 +12:00
|
|
|
coreActions.loadArtist(next_added_from_uri);
|
2019-08-13 16:53:39 +12:00
|
|
|
break;
|
2019-09-05 15:36:11 +12:00
|
|
|
case 'playlist':
|
2019-09-27 16:23:33 +12:00
|
|
|
coreActions.loadPlaylist(next_added_from_uri);
|
2019-08-13 16:53:39 +12:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadMore() {
|
2019-09-27 16:23:33 +12:00
|
|
|
const { limit, per_page } = this.state;
|
|
|
|
|
const { location } = this.props;
|
|
|
|
|
const new_limit = limit + per_page;
|
2019-08-13 16:53:39 +12:00
|
|
|
|
|
|
|
|
this.setState({ limit: new_limit });
|
|
|
|
|
|
|
|
|
|
// Set our pagination to location state
|
2019-09-27 16:23:33 +12:00
|
|
|
const state = tlocation && location.state
|
|
|
|
|
? location.state
|
2019-09-05 15:36:11 +12:00
|
|
|
: {};
|
2019-08-13 16:53:39 +12:00
|
|
|
state.limit = new_limit;
|
2019-09-27 16:23:33 +12:00
|
|
|
history.replace({ state });
|
2019-08-13 16:53:39 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeTracks(track_indexes) {
|
2019-09-27 16:23:33 +12:00
|
|
|
const { queue_tracks, mopidyActions } = this.props;
|
2019-09-05 15:36:11 +12:00
|
|
|
const tlids = [];
|
|
|
|
|
for (let i = 0; i < track_indexes.length; i++) {
|
2019-09-27 16:23:33 +12:00
|
|
|
const track = queue_tracks[track_indexes[i]];
|
2019-08-13 16:53:39 +12:00
|
|
|
if (track.tlid !== undefined) {
|
|
|
|
|
tlids.push(track.tlid);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tlids.length > 0) {
|
2019-09-27 16:23:33 +12:00
|
|
|
mopidyActions.removeTracks(tlids);
|
2019-08-13 16:53:39 +12:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
playTrack(track) {
|
|
|
|
|
this.props.mopidyActions.changeTrack(track.tlid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
playTracks(tracks) {
|
|
|
|
|
this.props.mopidyActions.changeTrack(tracks[0].tlid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reorderTracks(indexes, index) {
|
|
|
|
|
this.props.mopidyActions.reorderTracklist(indexes, index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderQueueStats() {
|
2019-09-27 16:23:33 +12:00
|
|
|
const { current_tracklist } = this.props;
|
2019-09-05 15:36:11 +12:00
|
|
|
const total_time = 0;
|
2019-08-13 16:53:39 +12:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="queue-stats mid_grey-text">
|
2019-09-10 06:59:44 +12:00
|
|
|
<span>
|
2019-09-27 16:23:33 +12:00
|
|
|
{`${current_tracklist.length} tracks`}
|
2019-09-05 15:36:11 +12:00
|
|
|
</span>
|
2019-08-13 16:53:39 +12:00
|
|
|
|
|
2019-09-27 16:23:33 +12:00
|
|
|
{current_tracklist.length > 0 ? (
|
|
|
|
|
<Dater type="total-time" data={current_tracklist} />
|
2019-08-13 16:53:39 +12:00
|
|
|
) : (
|
|
|
|
|
<span>0 mins</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderArtwork(image) {
|
2019-09-27 16:23:33 +12:00
|
|
|
const {
|
|
|
|
|
radio_enabled,
|
|
|
|
|
current_track,
|
|
|
|
|
} = this.props;
|
|
|
|
|
|
2019-08-13 16:53:39 +12:00
|
|
|
if (!image) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={`current-track__artwork ${
|
2019-09-27 16:23:33 +12:00
|
|
|
radio_enabled
|
2019-09-05 15:36:11 +12:00
|
|
|
? 'current-track__artwork--radio-enabled'
|
|
|
|
|
: ''
|
2019-08-13 16:53:39 +12:00
|
|
|
}`}
|
|
|
|
|
>
|
2019-09-27 16:23:33 +12:00
|
|
|
{radio_enabled ? (
|
2019-08-13 16:53:39 +12:00
|
|
|
<img
|
|
|
|
|
className="radio-overlay"
|
|
|
|
|
src="/iris/assets/radio-overlay.png"
|
2019-09-27 16:23:33 +12:00
|
|
|
alt=""
|
2019-08-13 16:53:39 +12:00
|
|
|
/>
|
|
|
|
|
) : null}
|
2019-09-27 16:23:33 +12:00
|
|
|
<Thumbnail glow circle={radio_enabled} />
|
2019-08-13 16:53:39 +12:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-05 15:36:11 +12:00
|
|
|
let uri = null;
|
2019-09-27 16:23:33 +12:00
|
|
|
if (current_track.album && current_track.album.uri) {
|
|
|
|
|
uri = current_track.album.uri;
|
2019-08-13 16:53:39 +12:00
|
|
|
}
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={`current-track__artwork ${
|
2019-09-27 16:23:33 +12:00
|
|
|
radio_enabled
|
2019-09-05 15:36:11 +12:00
|
|
|
? 'current-track__artwork--radio-enabled'
|
|
|
|
|
: ''
|
2019-08-13 16:53:39 +12:00
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<URILink type="album" uri={uri}>
|
2019-09-27 16:23:33 +12:00
|
|
|
{radio_enabled ? (
|
2019-08-13 16:53:39 +12:00
|
|
|
<img
|
|
|
|
|
className="radio-overlay"
|
|
|
|
|
src="/iris/assets/radio-overlay.png"
|
2019-09-27 16:23:33 +12:00
|
|
|
alt=""
|
2019-08-13 16:53:39 +12:00
|
|
|
/>
|
|
|
|
|
) : null}
|
2019-09-27 16:23:33 +12:00
|
|
|
<Thumbnail glow image={image} circle={radio_enabled} />
|
2019-08-13 16:53:39 +12:00
|
|
|
</URILink>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderAddedFrom() {
|
|
|
|
|
const { added_from_uri } = this.props;
|
|
|
|
|
if (!added_from_uri) return null;
|
|
|
|
|
|
|
|
|
|
const item_type = helpers.uriType(added_from_uri);
|
2019-09-05 15:36:11 +12:00
|
|
|
const item_library = this.props[`${item_type}s`];
|
2019-08-13 16:53:39 +12:00
|
|
|
if (!item_library) return null;
|
|
|
|
|
|
|
|
|
|
const item = item_library[added_from_uri];
|
|
|
|
|
if (!item) return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="current-track__added-from">
|
|
|
|
|
<URILink
|
|
|
|
|
type={item_type}
|
|
|
|
|
uri={item.uri}
|
|
|
|
|
className="current-track__added-from__thumbnail"
|
|
|
|
|
>
|
|
|
|
|
<Thumbnail
|
|
|
|
|
images={item.images}
|
|
|
|
|
size="small"
|
2019-09-27 16:23:33 +12:00
|
|
|
circle={item_type === 'artist'}
|
2019-08-13 16:53:39 +12:00
|
|
|
/>
|
|
|
|
|
</URILink>
|
|
|
|
|
<div className="current-track__added-from__text">
|
2019-09-27 16:23:33 +12:00
|
|
|
{'Playing from '}
|
2019-08-13 16:53:39 +12:00
|
|
|
<URILink type={item_type} uri={item.uri}>
|
|
|
|
|
{item.name}
|
|
|
|
|
</URILink>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
const { current_track, queue_tracks } = this.props;
|
2019-09-27 16:23:33 +12:00
|
|
|
const { limit } = this.state;
|
2019-08-13 16:53:39 +12:00
|
|
|
const total_queue_tracks = queue_tracks.length;
|
2019-09-27 16:23:33 +12:00
|
|
|
const tracks = queue_tracks.slice(0, limit);
|
2019-08-13 16:53:39 +12:00
|
|
|
|
2019-09-05 15:36:11 +12:00
|
|
|
let current_track_image = null;
|
2019-08-13 16:53:39 +12:00
|
|
|
if (current_track && this.props.current_track_uri) {
|
|
|
|
|
if (current_track.images !== undefined && current_track.images) {
|
|
|
|
|
current_track_image = current_track.images.large;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-05 15:36:11 +12:00
|
|
|
const options = (
|
2019-08-13 16:53:39 +12:00
|
|
|
<span>
|
|
|
|
|
{this.props.spotify_enabled ? (
|
2019-09-05 15:36:11 +12:00
|
|
|
<Link className="button button--no-hover" to="/queue/radio">
|
2019-08-13 16:53:39 +12:00
|
|
|
<Icon name="radio" />
|
|
|
|
|
Radio
|
|
|
|
|
{this.props.radio && this.props.radio.enabled ? (
|
|
|
|
|
<span className="flag blue">On</span>
|
|
|
|
|
) : null}
|
|
|
|
|
</Link>
|
|
|
|
|
) : null}
|
2019-09-05 15:36:11 +12:00
|
|
|
<Link className="button button--no-hover" to="/queue/history">
|
2019-08-13 16:53:39 +12:00
|
|
|
<Icon name="history" />
|
|
|
|
|
History
|
|
|
|
|
</Link>
|
2019-09-05 15:36:11 +12:00
|
|
|
<Link className="button button--no-hover" to="/queue/add-uri">
|
2019-08-13 16:53:39 +12:00
|
|
|
<Icon name="playlist_add" />
|
|
|
|
|
Add URI
|
|
|
|
|
</Link>
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="view queue-view preserve-3d">
|
|
|
|
|
<Header options={options} uiActions={this.props.uiActions}>
|
|
|
|
|
<Icon name="play_arrow" type="material" />
|
|
|
|
|
Now playing
|
|
|
|
|
</Header>
|
2019-08-21 17:02:38 +12:00
|
|
|
<Parallax blur image={current_track_image} />
|
2019-08-13 16:53:39 +12:00
|
|
|
<div className="content-wrapper">
|
|
|
|
|
<div className="current-track">
|
|
|
|
|
{this.renderArtwork(current_track_image)}
|
|
|
|
|
<div className="current-track__details">
|
|
|
|
|
<div className="current-track__title">
|
|
|
|
|
{current_track ? (
|
|
|
|
|
<URILink type="track" uri={current_track.uri}>
|
|
|
|
|
{current_track.name}
|
|
|
|
|
</URILink>
|
|
|
|
|
) : (
|
|
|
|
|
<span>-</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{current_track ? (
|
|
|
|
|
<ArtistSentence
|
|
|
|
|
className="current-track__artists"
|
|
|
|
|
artists={current_track.artists}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<ArtistSentence className="current-track__artists" />
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{this.renderAddedFrom()}
|
2019-09-27 16:23:33 +12:00
|
|
|
|
|
|
|
|
<div className="current-track__queue-details">
|
|
|
|
|
<ul className="details">
|
|
|
|
|
<li>{`${queue_tracks.length} tracks`}</li>
|
|
|
|
|
<li><Dater type="total-time" data={queue_tracks} /></li>
|
|
|
|
|
<li>
|
|
|
|
|
<a
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
this.props.mopidyActions.clearTracklist();
|
|
|
|
|
this.props.uiActions.hideContextMenu();
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Icon name="delete_sweep" />
|
|
|
|
|
Clear queue
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
2019-08-13 16:53:39 +12:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<section className="list-wrapper">
|
|
|
|
|
<TrackList
|
|
|
|
|
show_source_icon
|
|
|
|
|
track_context="queue"
|
|
|
|
|
className="queue-track-list"
|
|
|
|
|
tracks={tracks}
|
2019-09-05 15:36:11 +12:00
|
|
|
removeTracks={(track_indexes) => this.removeTracks(track_indexes)}
|
|
|
|
|
playTracks={(tracks) => this.playTracks(tracks)}
|
|
|
|
|
playTrack={(track) => this.playTrack(track)}
|
|
|
|
|
reorderTracks={(indexes, index) => this.reorderTracks(indexes, index)}
|
2019-08-13 16:53:39 +12:00
|
|
|
/>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<LazyLoadListener
|
|
|
|
|
loadKey={
|
2019-09-27 16:23:33 +12:00
|
|
|
total_queue_tracks > limit
|
|
|
|
|
? limit
|
2019-08-13 16:53:39 +12:00
|
|
|
: total_queue_tracks
|
|
|
|
|
}
|
2019-09-27 16:23:33 +12:00
|
|
|
showLoader={limit < total_queue_tracks}
|
2019-08-13 16:53:39 +12:00
|
|
|
loadMore={() => this.loadMore()}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2016-10-03 14:44:27 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mapStateToProps = (state, ownProps) => {
|
2019-09-05 15:36:11 +12:00
|
|
|
let { current_track } = state.core;
|
|
|
|
|
const queue_tracks = [];
|
2019-08-13 16:53:39 +12:00
|
|
|
|
|
|
|
|
if (state.core.queue && state.core.tracks) {
|
2019-09-05 15:36:11 +12:00
|
|
|
for (const queue_track of state.core.queue) {
|
|
|
|
|
let track = { ...queue_track };
|
2019-08-13 16:53:39 +12:00
|
|
|
|
|
|
|
|
// 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)) {
|
2019-09-05 15:36:11 +12:00
|
|
|
track = { ...state.core.tracks[track.uri], ...track, playing: current_track && current_track.tlid == track.tlid };
|
2019-08-13 16:53:39 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now merge in our queue metadata
|
2019-09-05 15:36:11 +12:00
|
|
|
if (state.core.queue_metadata[`tlid_${track.tlid}`] !== undefined) {
|
|
|
|
|
track = {
|
|
|
|
|
|
|
|
|
|
...track,
|
|
|
|
|
...state.core.queue_metadata[`tlid_${track.tlid}`],
|
|
|
|
|
};
|
2019-08-13 16:53:39 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Siphon off this track if it's a full representation of our current track (by tlid)
|
|
|
|
|
if (current_track && current_track.uri == track.uri) {
|
|
|
|
|
current_track = track;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now add our compiled track for our tracklist
|
|
|
|
|
queue_tracks.push(track);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
theme: state.ui.theme,
|
|
|
|
|
spotify_enabled: state.spotify.enabled,
|
|
|
|
|
radio: state.core.radio,
|
2019-09-05 15:36:11 +12:00
|
|
|
radio_enabled: !!(state.core.radio && state.core.radio.enabled),
|
2019-08-13 16:53:39 +12:00
|
|
|
artists: state.core.artists,
|
|
|
|
|
albums: state.core.albums,
|
|
|
|
|
playlists: state.core.playlists,
|
2019-09-05 15:36:11 +12:00
|
|
|
queue_tracks,
|
2019-08-13 16:53:39 +12:00
|
|
|
current_track_uri: state.core.current_track_uri,
|
2019-09-05 15:36:11 +12:00
|
|
|
current_track,
|
2019-08-13 16:53:39 +12:00
|
|
|
added_from_uri:
|
|
|
|
|
current_track && current_track.added_from
|
|
|
|
|
? current_track.added_from
|
2019-09-05 15:36:11 +12:00
|
|
|
: null,
|
2019-08-13 16:53:39 +12:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2019-09-05 15:36:11 +12:00
|
|
|
const mapDispatchToProps = (dispatch) => ({
|
|
|
|
|
coreActions: bindActionCreators(coreActions, dispatch),
|
|
|
|
|
uiActions: bindActionCreators(uiActions, dispatch),
|
|
|
|
|
pusherActions: bindActionCreators(pusherActions, dispatch),
|
|
|
|
|
spotifyActions: bindActionCreators(spotifyActions, dispatch),
|
|
|
|
|
mopidyActions: bindActionCreators(mopidyActions, dispatch),
|
|
|
|
|
});
|
2019-08-13 16:53:39 +12:00
|
|
|
|
|
|
|
|
export default connect(
|
|
|
|
|
mapStateToProps,
|
2019-09-05 15:36:11 +12:00
|
|
|
mapDispatchToProps,
|
2019-08-13 16:53:39 +12:00
|
|
|
)(Queue);
|