Files
Iris/src/js/views/Queue.js

415 lines
12 KiB
JavaScript
Raw Normal View History

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 LinksSentence from '../components/LinksSentence';
2019-09-05 15:36:11 +12:00
import Thumbnail from '../components/Thumbnail';
import Header from '../components/Header';
import URILink from '../components/URILink';
import LazyLoadListener from '../components/LazyLoadListener';
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';
import {
getFromUri,
uriType,
} from '../util/helpers';
import { i18n, I18n } from '../locale';
import Button from '../components/Button';
import { indexToArray } from '../util/arrays';
2019-01-15 16:57:50 +13:00
const Artwork = ({
image,
album_uri,
}) => {
if (!image) {
return (
<div className="current-track__artwork">
<Thumbnail glow type="track" />
</div>
);
}
return (
<div className="current-track__artwork">
<Thumbnail glow image={image} type="track">
<Link to="/kiosk-mode" className="thumbnail__actions__item">
<Icon name="expand" type="fontawesome" />
</Link>
<URILink uri={album_uri} className="thumbnail__actions__item">
<Icon name="album" />
</URILink>
</Thumbnail>
</div>
);
}
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
};
}
componentDidMount() {
const {
uiActions: {
setWindowTitle,
},
location: {
limit,
} = {},
} = this.props;
if (limit) this.setState({ limit });
setWindowTitle(i18n('now_playing.title'));
2019-08-13 16:53:39 +12:00
}
shouldComponentUpdate(nextProps) {
return nextProps !== this.props;
}
componentDidUpdate = ({
added_from_uri: prev_added_from_uri,
}) => {
2020-03-12 11:45:02 +13:00
const {
coreActions: {
loadItem,
2020-03-12 11:45:02 +13:00
},
added_from_uri,
} = this.props;
2019-08-13 16:53:39 +12:00
if (added_from_uri && added_from_uri !== prev_added_from_uri) {
loadItem(added_from_uri, { full: false });
2019-08-13 16:53:39 +12:00
}
}
loadMore = () => {
const { limit, per_page } = this.state;
const { location, history } = 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
const state = location && location.state
? location.state
2019-09-05 15:36:11 +12:00
: {};
2019-08-13 16:53:39 +12:00
state.limit = new_limit;
history.replace({ state });
2019-08-13 16:53:39 +12:00
}
removeTracks = (track_indexes) => {
const {
queue_tracks,
mopidyActions: {
removeTracks: doRemoveTracks,
},
} = this.props;
2019-09-05 15:36:11 +12:00
const tlids = [];
for (let i = 0; i < track_indexes.length; i++) {
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) {
doRemoveTracks(tlids);
2019-08-13 16:53:39 +12:00
}
}
playTrack = (track) => {
const { mopidyActions: { changeTrack } } = this.props;
changeTrack(track.tlid);
2019-08-13 16:53:39 +12:00
}
playTracks = (tracks) => {
const { mopidyActions: { changeTrack } } = this.props;
changeTrack(tracks[0].tlid);
2019-08-13 16:53:39 +12:00
}
reorderTracks = (indexes, index) => {
const { mopidyActions: { reorderTracklist } } = this.props;
reorderTracklist(indexes, index);
2019-08-13 16:53:39 +12:00
}
renderAddedFrom = () => {
const {
items,
added_from_uri,
} = this.props;
2019-08-13 16:53:39 +12:00
if (!added_from_uri) return null;
const uri_type = uriType(added_from_uri);
let addedFromItems = [];
// Radio nests it's seed URIs in an encoded URI format
switch (uri_type){
case 'radio':
addedFromItems = indexToArray(items, getFromUri('seeds', added_from_uri));
break;
case 'search':
addedFromItems = [{
uri: added_from_uri,
name: `"${getFromUri('searchterm', added_from_uri)}" search`,
}];
break;
default:
addedFromItems = indexToArray(items, [added_from_uri]);
break;
}
if (!addedFromItems.length) return null;
2019-08-13 16:53:39 +12:00
return (
<div className="current-track__added-from">
{addedFromItems[0].images && (
<URILink
uri={addedFromItems[0].uri}
className="current-track__added-from__thumbnail"
>
<Thumbnail
images={addedFromItems[0].images}
size="small"
circle={uriType(addedFromItems[0].uri) === 'artist'}
type="artist"
/>
</URILink>
)}
2019-08-13 16:53:39 +12:00
<div className="current-track__added-from__text">
{'Playing from '}
<LinksSentence items={addedFromItems} />
{uri_type === 'radio' && (
<span className="flag flag--blue">
{i18n('now_playing.current_track.radio')}
</span>
)}
2019-08-13 16:53:39 +12:00
</div>
</div>
);
}
render = () => {
const {
current_track,
queue_tracks,
2020-08-20 00:36:23 -04:00
stream_title,
theme,
current_track_uri,
spotify_enabled,
uiActions,
mopidyActions: {
clearTracklist,
shuffleTracklist,
},
} = this.props;
const { limit } = this.state;
2019-08-13 16:53:39 +12:00
const total_queue_tracks = queue_tracks.length;
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;
if (current_track && current_track_uri) {
2019-08-13 16:53:39 +12:00
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 = (
<>
{spotify_enabled && (
<Button noHover discrete to="/queue/radio">
2019-08-13 16:53:39 +12:00
<Icon name="radio" />
<I18n path="now_playing.context_actions.radio" />
</Button>
)}
<Button noHover discrete to="/queue/history">
2019-08-13 16:53:39 +12:00
<Icon name="history" />
<I18n path="now_playing.context_actions.history" />
</Button>
<Button noHover discrete to="/queue/add-uri">
2019-08-13 16:53:39 +12:00
<Icon name="playlist_add" />
<I18n path="actions.add" />
</Button>
</>
2019-08-13 16:53:39 +12:00
);
return (
<div className="view queue-view preserve-3d">
<Header options={options} uiActions={uiActions}>
2019-08-13 16:53:39 +12:00
<Icon name="play_arrow" type="material" />
<I18n path="now_playing.title" />
2019-08-13 16:53:39 +12:00
</Header>
{theme === 'dark' && <Parallax image={current_track_image} blur />}
2019-08-13 16:53:39 +12:00
<div className="content-wrapper">
<div className="current-track">
<Artwork
image={current_track_image}
album_uri={current_track && current_track.album && current_track.album.uri}
/>
2019-08-13 16:53:39 +12:00
<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>
2020-08-20 00:36:23 -04:00
{
(current_track && current_track.artists && (
<LinksSentence
className="current-track__artists"
items={current_track.artists}
/>
))
|| (stream_title && (
<span className="current-track__artists links-sentence">{stream_title}</span>
))
|| <LinksSentence className="current-track__artists" />
}
2019-08-13 16:53:39 +12:00
{this.renderAddedFrom()}
<div className="current-track__queue-details">
<ul className="details">
<li>{`${queue_tracks.length} tracks`}</li>
<li><Dater type="total-time" data={queue_tracks} /></li>
{queue_tracks.length > 0 && (
<li>
<a onClick={shuffleTracklist}>
<Icon name="shuffle" />
<I18n path="now_playing.current_track.shuffle" />
</a>
</li>
)}
{queue_tracks.length > 0 && (
<li>
<a onClick={clearTracklist}>
<Icon name="delete_sweep" />
<I18n path="now_playing.current_track.clear" />
</a>
</li>
)}
</ul>
</div>
2019-08-13 16:53:39 +12:00
</div>
</div>
<section className="list-wrapper">
<TrackList
uri="iris:queue"
2019-08-13 16:53:39 +12:00
show_source_icon
track_context="queue"
className="queue-track-list"
tracks={tracks}
removeTracks={this.removeTracks}
playTracks={this.playTracks}
playTrack={this.playTrack}
reorderTracks={this.reorderTracks}
2019-08-13 16:53:39 +12:00
/>
</section>
<LazyLoadListener
loadKey={
total_queue_tracks > limit
? limit
2019-08-13 16:53:39 +12:00
: total_queue_tracks
}
showLoader={limit < total_queue_tracks}
loadMore={this.loadMore}
2019-08-13 16:53:39 +12:00
/>
</div>
</div>
);
}
2016-10-03 14:44:27 +13:00
}
const mapStateToProps = (state) => {
const {
current_track: core_current_track,
stream_title,
items,
queue,
queue_metadata,
} = state.core;
2019-09-05 15:36:11 +12:00
const queue_tracks = [];
let current_track = {};
2019-08-13 16:53:39 +12:00
if (queue && items) {
for (const queue_track of queue) {
let track = {
...queue_track,
playing: core_current_track && core_current_track.tlid == queue_track.tlid,
};
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 (items[track.uri]) {
track = {
...items[track.uri],
...track,
};
2019-08-13 16:53:39 +12:00
}
// Now merge in our queue metadata
if (queue_metadata[`tlid_${track.tlid}`] !== undefined) {
2019-09-05 15:36:11 +12:00
track = {
...track,
...queue_metadata[`tlid_${track.tlid}`],
2019-09-05 15:36:11 +12:00
};
2019-08-13 16:53:39 +12:00
}
// Siphon off this track if it's a full representation of our current track
if (track.playing) {
current_track = track;
2019-08-13 16:53:39 +12:00
}
// 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),
items,
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,
2020-08-20 00:36:23 -04:00
stream_title,
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);