Create playlist and add tracks in one action, fixes #723
This commit is contained in:
@ -310,7 +310,7 @@ export class App extends React.Component {
|
||||
|
||||
<Route path="/queue/radio" component={EditRadio} />
|
||||
<Route path="/queue/add-uri" component={AddToQueue} />
|
||||
<Route path="/playlist/create" component={CreatePlaylist} />
|
||||
<Route path="/playlist/create/:uris?" component={CreatePlaylist} />
|
||||
<Route path="/playlist/:uri/edit" component={EditPlaylist} />
|
||||
|
||||
<Route>
|
||||
|
||||
@ -783,8 +783,12 @@ class ContextMenu extends React.Component {
|
||||
const {
|
||||
playlists: allPlaylists,
|
||||
loading_progress,
|
||||
menu: {
|
||||
uris,
|
||||
} = {},
|
||||
} = this.props;
|
||||
|
||||
const encodedUris = uris && uris.length > 0 ? encodeUri(uris.join(',')) : '';
|
||||
let list = null;
|
||||
if (submenu === 'add-to-playlist') {
|
||||
let playlists = compact(allPlaylists.map((playlist) => {
|
||||
@ -842,6 +846,16 @@ class ContextMenu extends React.Component {
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="context-menu__item">
|
||||
<Link className="context-menu__item__link" to={`playlist/create/${encodedUris}`}>
|
||||
<span className="context-menu__item__label">
|
||||
<Icon name="add" />
|
||||
<span>
|
||||
<I18n path="context_menu.add_to_playlist.new_playlist" />
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
{loading_progress ? (
|
||||
<div className="context-menu__item context-menu__item--loader">
|
||||
<Loader className="context-menu__item" mini loading />
|
||||
|
||||
@ -175,6 +175,7 @@ sidebar:
|
||||
context_menu:
|
||||
add_to_playlist:
|
||||
title: Add to playlist
|
||||
new_playlist: Create playlist
|
||||
no_playlists: No writable playlists
|
||||
play_next: Play next
|
||||
play_top_tracks: Play top tracks
|
||||
@ -511,6 +512,7 @@ modal:
|
||||
edit_playlist:
|
||||
title: Edit playlist
|
||||
title_create: Create playlist
|
||||
subtitle: 'And add %{count} track%{plural}'
|
||||
name_required: Name is required
|
||||
name: Name
|
||||
description: Description
|
||||
|
||||
@ -403,16 +403,12 @@ export function savePlaylist(uri, name, description = '', is_public = false, is_
|
||||
}
|
||||
}
|
||||
|
||||
export function createPlaylist(scheme, name, description = '', is_public = false, is_collaborative = false) {
|
||||
switch (scheme) {
|
||||
export function createPlaylist(playlist) {
|
||||
switch (playlist.scheme) {
|
||||
case 'spotify':
|
||||
if (description === '') {
|
||||
description = null;
|
||||
}
|
||||
return spotifyActions.createPlaylist(name, description, is_public, is_collaborative);
|
||||
|
||||
return spotifyActions.createPlaylist(playlist);
|
||||
default:
|
||||
return mopidyActions.createPlaylist(name, scheme);
|
||||
return mopidyActions.createPlaylist(playlist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -422,11 +422,10 @@ export function getImages(uris) {
|
||||
};
|
||||
}
|
||||
|
||||
export function createPlaylist(name, scheme) {
|
||||
export function createPlaylist(playlist) {
|
||||
return {
|
||||
type: 'MOPIDY_CREATE_PLAYLIST',
|
||||
name,
|
||||
scheme,
|
||||
playlist,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1346,7 +1346,11 @@ const MopidyMiddleware = (function () {
|
||||
break;
|
||||
|
||||
case 'MOPIDY_CREATE_PLAYLIST':
|
||||
request(store, 'playlists.create', { name: action.name, uri_scheme: action.scheme })
|
||||
const data = {
|
||||
name: action.playlist.name,
|
||||
uri_scheme: action.playlist.scheme,
|
||||
};
|
||||
request(store, 'playlists.create', data)
|
||||
.then((response) => {
|
||||
const playlist = formatPlaylist({
|
||||
...response,
|
||||
@ -1358,6 +1362,12 @@ const MopidyMiddleware = (function () {
|
||||
store.dispatch(uiActions.createNotification({
|
||||
content: i18n('actions.created', { name: i18n('playlist.title') }),
|
||||
}));
|
||||
if (action.playlist.tracks_uris) {
|
||||
store.dispatch(coreActions.addTracksToPlaylist(
|
||||
playlist.uri,
|
||||
action.playlist.tracks_uris,
|
||||
));
|
||||
}
|
||||
store.dispatch(coreActions.addToLibrary(
|
||||
getProvider('playlists', 'm3u:')?.uri,
|
||||
playlist,
|
||||
|
||||
@ -22,7 +22,8 @@ import {
|
||||
injectSortId,
|
||||
} from '../../util/format';
|
||||
import URILink from '../../components/URILink';
|
||||
import { getItem, providers, getProvider } from '../../util/selectors';
|
||||
import { i18n } from '../../locale';
|
||||
import { getItem, getProvider } from '../../util/selectors';
|
||||
|
||||
const coreActions = require('../core/actions');
|
||||
const uiActions = require('../ui/actions');
|
||||
@ -1140,13 +1141,13 @@ export function getAlbum(uri, { full, forceRefetch } = {}) {
|
||||
* ======================================================================================
|
||||
* */
|
||||
|
||||
export function createPlaylist(name, description, is_public, is_collaborative) {
|
||||
export function createPlaylist(playlist) {
|
||||
return (dispatch, getState) => {
|
||||
const data = {
|
||||
name,
|
||||
description,
|
||||
public: is_public,
|
||||
collaborative: is_collaborative,
|
||||
name: playlist.name,
|
||||
description: playlist.description || '',
|
||||
public: playlist.public,
|
||||
collaborative: playlist.collaborative,
|
||||
};
|
||||
const {
|
||||
spotify: {
|
||||
@ -1176,7 +1177,12 @@ export function createPlaylist(name, description, is_public, is_collaborative) {
|
||||
response,
|
||||
));
|
||||
|
||||
dispatch(uiActions.createNotification({ content: 'Created playlist' }));
|
||||
dispatch(uiActions.createNotification({
|
||||
content: i18n('actions.created', { name: i18n('playlist.title') }),
|
||||
}));
|
||||
if (playlist.tracks_uris) {
|
||||
dispatch(coreActions.addTracksToPlaylist(response.uri, playlist.tracks_uris));
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
dispatch(coreActions.handleException(
|
||||
|
||||
@ -1,49 +1,49 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Modal from './Modal';
|
||||
import * as coreActions from '../../services/core/actions';
|
||||
import * as uiActions from '../../services/ui/actions';
|
||||
import { i18n, I18n } from '../../locale';
|
||||
import { decodeUri } from '../../util/format';
|
||||
import TextField from '../../components/Fields/TextField';
|
||||
import Button from '../../components/Button';
|
||||
|
||||
class CreatePlaylist extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: '',
|
||||
description: '',
|
||||
scheme: 'm3u',
|
||||
is_public: true,
|
||||
is_collaborative: false,
|
||||
};
|
||||
}
|
||||
const CreatePlaylist = () => {
|
||||
const { uris } = useParams();
|
||||
const tracks_uris = uris ? decodeUri(uris).split(',') : undefined;
|
||||
const dispatch = useDispatch();
|
||||
const [playlist, setPlaylist] = useState({
|
||||
scheme: 'm3u',
|
||||
name: '',
|
||||
description: '',
|
||||
collaborative: false,
|
||||
public: false,
|
||||
});
|
||||
const spotifyAvailable = useSelector((state) => state.spotify.access_token);
|
||||
useEffect(
|
||||
() => {
|
||||
dispatch(uiActions.setWindowTitle(i18n('modal.edit_playlist.title_create')));
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
componentDidMount() {
|
||||
this.props.uiActions.setWindowTitle(i18n('modal.edit_playlist.title_create'));
|
||||
}
|
||||
|
||||
createPlaylist(e) {
|
||||
const onChange = (updates) => setPlaylist({ ...playlist, ...updates });
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.state.name || this.state.name == '') {
|
||||
this.setState({ error: i18n('modal.edit_playlist.name_required') });
|
||||
if (!playlist.name || playlist.name === '') {
|
||||
return false;
|
||||
}
|
||||
this.props.coreActions.createPlaylist(
|
||||
this.state.scheme,
|
||||
this.state.name,
|
||||
this.state.description,
|
||||
this.state.is_public,
|
||||
this.state.is_collaborative,
|
||||
dispatch(
|
||||
coreActions.createPlaylist({ ...playlist, tracks_uris }),
|
||||
);
|
||||
window.history.back();
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
renderFields() {
|
||||
switch (this.state.scheme) {
|
||||
const fields = () => {
|
||||
switch (playlist.scheme) {
|
||||
case 'spotify':
|
||||
return (
|
||||
<div>
|
||||
@ -52,10 +52,9 @@ class CreatePlaylist extends React.Component {
|
||||
<I18n path="modal.edit_playlist.name" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => this.setState({ name: e.target.value })}
|
||||
value={this.state.name}
|
||||
<TextField
|
||||
onChange={(value) => onChange({ name: value })}
|
||||
value={playlist.name}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -65,11 +64,9 @@ class CreatePlaylist extends React.Component {
|
||||
<I18n path="modal.edit_playlist.description" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => this.setState({ description: e.target.value })}
|
||||
|
||||
value={this.state.description}
|
||||
<TextField
|
||||
onChange={(value) => onChange({ description: value })}
|
||||
value={playlist.description}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -82,9 +79,9 @@ class CreatePlaylist extends React.Component {
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="is_public"
|
||||
checked={this.state.is_public}
|
||||
onChange={(e) => this.setState({ is_public: !this.state.is_public })}
|
||||
name="public"
|
||||
checked={playlist.public}
|
||||
onChange={() => onChange({ public: !playlist.public })}
|
||||
/>
|
||||
<span className="label">
|
||||
<I18n path="modal.edit_playlist.options.public" />
|
||||
@ -93,9 +90,9 @@ class CreatePlaylist extends React.Component {
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="is_collaborative"
|
||||
checked={this.state.is_collaborative}
|
||||
onChange={(e) => this.setState({ is_collaborative: !this.state.is_collaborative })}
|
||||
name="collaborative"
|
||||
checked={playlist.collaborative}
|
||||
onChange={() => onChange({ collaborative: !playlist.collaborative })}
|
||||
/>
|
||||
<span className="label">
|
||||
<I18n path="modal.edit_playlist.options.collaborative" />
|
||||
@ -114,10 +111,9 @@ class CreatePlaylist extends React.Component {
|
||||
<I18n path="modal.edit_playlist.name" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => this.setState({ name: e.target.value })}
|
||||
value={this.state.name}
|
||||
<TextField
|
||||
onChange={(value) => onChange({ name: value })}
|
||||
value={playlist.name}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -126,75 +122,67 @@ class CreatePlaylist extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const { spotify_available } = this.props;
|
||||
|
||||
return (
|
||||
<Modal className="modal--create-playlist">
|
||||
<h1>
|
||||
<I18n path="modal.edit_playlist.title_create" />
|
||||
</h1>
|
||||
<form onSubmit={(e) => this.createPlaylist(e)}>
|
||||
|
||||
<div className="field radio white">
|
||||
<div className="name">
|
||||
<I18n path="modal.edit_playlist.provider" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="scheme"
|
||||
value="m3u"
|
||||
checked={this.state.scheme === 'm3u'}
|
||||
onChange={(e) => this.setState({ scheme: e.target.value })}
|
||||
/>
|
||||
<span className="label">
|
||||
<I18n path="services.mopidy.title" />
|
||||
</span>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="scheme"
|
||||
value="spotify"
|
||||
disabled={!spotify_available}
|
||||
checked={this.state.scheme === 'spotify'}
|
||||
onChange={(e) => this.setState({ scheme: e.target.value })}
|
||||
/>
|
||||
<span className="label">
|
||||
<I18n path="services.spotify.title" />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
return (
|
||||
<Modal className="modal--create-playlist">
|
||||
<h1>
|
||||
<I18n path="modal.edit_playlist.title_create" />
|
||||
</h1>
|
||||
{tracks_uris && (
|
||||
<h2 className="mid_grey-text">
|
||||
<I18n
|
||||
path="modal.edit_playlist.subtitle"
|
||||
count={tracks_uris.length}
|
||||
plural={tracks_uris.length > 1 ? 's' : ''}
|
||||
/>
|
||||
</h2>
|
||||
)}
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="field radio white">
|
||||
<div className="name">
|
||||
<I18n path="modal.edit_playlist.provider" />
|
||||
</div>
|
||||
|
||||
{this.renderFields()}
|
||||
|
||||
<div className="actions centered-text">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
submit
|
||||
tracking={{ category: 'CreatePlaylist', action: 'Submit' }}
|
||||
>
|
||||
<I18n path="modal.edit_playlist.create_playlist" />
|
||||
</Button>
|
||||
<div className="input">
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="scheme"
|
||||
value="m3u"
|
||||
checked={playlist.scheme === 'm3u'}
|
||||
onChange={(e) => onChange({ scheme: e.target.value })}
|
||||
/>
|
||||
<span className="label">
|
||||
<I18n path="services.mopidy.title" />
|
||||
</span>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="scheme"
|
||||
value="spotify"
|
||||
disabled={!spotifyAvailable}
|
||||
checked={playlist.scheme === 'spotify'}
|
||||
onChange={(e) => onChange({ scheme: e.target.value })}
|
||||
/>
|
||||
<span className="label">
|
||||
<I18n path="services.spotify.title" />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{fields()}
|
||||
<div className="actions centered-text">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
submit
|
||||
tracking={{ category: 'CreatePlaylist', action: 'Submit' }}
|
||||
>
|
||||
<I18n path="modal.edit_playlist.create_playlist" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
spotify_available: state.spotify.access_token,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
coreActions: bindActionCreators(coreActions, dispatch),
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CreatePlaylist);
|
||||
export default CreatePlaylist;
|
||||
|
||||
Reference in New Issue
Block a user