Files
Iris/src/js/views/modals/EditRadio.js
2020-03-12 21:32:50 +13:00

269 lines
7.6 KiB
JavaScript
Executable File

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Modal from './Modal';
import Icon from '../../components/Icon';
import * as coreActions from '../../services/core/actions';
import * as uiActions from '../../services/ui/actions';
import * as mopidyActions from '../../services/mopidy/actions';
import * as spotifyActions from '../../services/spotify/actions';
import * as pusherActions from '../../services/pusher/actions';
import {
uriSource,
uriType,
} from '../../util/helpers';
class EditRadio extends React.Component {
constructor(props) {
super(props);
this.state = {
enabled: false,
seeds: [],
uri: '',
error_message: null,
};
}
componentDidMount() {
this.props.uiActions.setWindowTitle('Edit radio');
if (this.props.radio && this.props.radio.enabled) {
this.loadRadio(this.props.radio);
}
}
componentDidUpdate = ({ radio: prev_radio }) => {
const { radio } = this.props;
if (!prev_radio && radio) this.loadRadio(radio);
}
loadRadio(radio) {
const seeds = [...radio.seed_tracks, ...radio.seed_artists, ...radio.seed_genres];
this.setState({ seeds, enabled: radio.enabled });
}
handleStart(e) {
e.preventDefault();
let valid_seeds = true;
const seeds = this.mapSeeds();
for (let i = 0; i < seeds.length; i++) {
if (seeds[i].unresolved !== undefined) {
valid_seeds = false;
continue;
}
}
if (valid_seeds) {
this.props.pusherActions.startRadio(this.state.seeds);
window.history.back();
} else {
this.setState({ error_message: 'Invalid seed URI(s)' });
}
}
handleUpdate(e) {
e.preventDefault();
let valid_seeds = true;
const seeds = this.mapSeeds();
for (let i = 0; i < seeds.length; i++) {
if (seeds[i].unresolved !== undefined) {
valid_seeds = false;
continue;
}
}
if (valid_seeds) {
this.props.pusherActions.updateRadio(this.state.seeds);
window.history.back();
} else {
this.setState({ error_message: 'Invalid seed URI(s)' });
}
}
handleStop(e) {
e.preventDefault();
this.props.pusherActions.stopRadio();
this.props.uiActions.closeModal();
}
addSeed(e) {
e.preventDefault();
if (this.state.uri == '') {
this.setState({ error_message: 'Cannot be empty' });
return;
}
this.setState({ error_message: null });
const seeds = Object.assign([], this.state.seeds);
let uris = this.state.uri.split(',');
if (uris.length >= 5) {
uris = uris.slice(0, 5);
this.setState({ error_message: 'More than 5 seeds provided, ignoring rest' });
}
for (let i = 0; i < uris.length; i++) {
if (uriSource(uris[i]) !== 'spotify') {
this.setState({ error_message: 'Non-Spotify URIs not supported' });
return;
} if (seeds.indexOf(uris[i]) > -1) {
this.setState({ error_message: 'URI already added' });
} else {
seeds.push(uris[i]);
}
// Resolve
switch (uriType(uris[i])) {
case 'track':
this.props.spotifyActions.getTrack(uris[i]);
break;
case 'artist':
this.props.spotifyActions.getArtist(uris[i]);
break;
}
}
// commit to state
this.setState({
seeds,
uri: '',
});
}
removeSeed(uri) {
const seeds = [];
for (let i = 0; i < this.state.seeds.length; i++) {
if (this.state.seeds[i] != uri) {
seeds.push(this.state.seeds[i]);
}
}
this.setState({ seeds });
}
mapSeeds() {
const seeds = [];
if (this.state.seeds) {
for (let i = 0; i < this.state.seeds.length && i < 5; i++) {
const uri = this.state.seeds[i];
if (uri) {
if (uriType(uri) == 'artist') {
if (this.props.artists && this.props.artists.hasOwnProperty(uri)) {
seeds.push(this.props.artists[uri]);
} else {
seeds.push({
unresolved: true,
uri,
});
}
} else if (uriType(uri) == 'track') {
if (this.props.tracks && this.props.tracks.hasOwnProperty(uri)) {
seeds.push(this.props.tracks[uri]);
} else {
seeds.push({
unresolved: true,
uri,
});
}
}
}
}
}
return seeds;
}
renderSeeds() {
const seeds = this.mapSeeds();
if (seeds.length > 0) {
return (
<div>
<div className="list">
{
seeds.map((seed, index) => (
<div className="list__item" key={seed.uri}>
{seed.unresolved ? <span className="mid_grey-text">{seed.uri}</span> : <span>{seed.name}</span> }
{!seed.unresolved ? (
<span className="mid_grey-text">
&nbsp;(
{uriType(seed.uri)}
)
</span>
) : null}
<span className="button discrete remove-uri no-hover" onClick={(e) => this.removeSeed(seed.uri)}>
<Icon name="delete" />
Remove
</span>
</div>
))
}
</div>
</div>
);
}
return (
<div className="no-results">No seeds</div>
);
}
render() {
return (
<Modal className="modal--edit-radio">
<h1>Radio</h1>
<h2 className="mid_grey-text">Add and remove seeds to shape the sound of your radio. Radio uses Spotify's recommendations engine to suggest tracks similar to your seeds.</h2>
<form onSubmit={(e) => { (this.state.enabled ? this.handleUpdate(e) : this.handleStart(e)); }}>
{this.renderSeeds()}
<div className="field text">
<div className="name">URI(s)</div>
<div className="input">
<input
type="text"
onChange={(e) => this.setState({ uri: e.target.value, error_message: null })}
value={this.state.uri}
/>
<span className="button discrete add-uri no-hover" onClick={(e) => this.addSeed(e)}>
<Icon name="add" />
Add
</span>
{this.state.error_message ? <span className="description error">{this.state.error_message}</span> : null}
</div>
</div>
<div className="actions centered-text">
{this.state.enabled ? <span className="button button--destructive button--large" onClick={(e) => this.handleStop(e)}>Stop</span> : null}
{this.state.enabled ? <button className="button button--primary button--large" onClick={(e) => this.handleUpdate(e)}>Save</button> : <button className="button button--primary button--large" onClick={(e) => this.handleStart(e)}>Start</button>}
</div>
</form>
</Modal>
);
}
}
const mapStateToProps = (state, ownProps) => ({
mopidy_connected: state.mopidy.connected,
radio: state.core.radio,
artists: state.core.artists,
tracks: state.core.tracks,
});
const mapDispatchToProps = (dispatch) => ({
coreActions: bindActionCreators(coreActions, dispatch),
uiActions: bindActionCreators(uiActions, dispatch),
mopidyActions: bindActionCreators(mopidyActions, dispatch),
pusherActions: bindActionCreators(pusherActions, dispatch),
spotifyActions: bindActionCreators(spotifyActions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(EditRadio);