Merging playlists by uri; Hide context on scroll

This commit is contained in:
James Barnsley
2016-11-13 14:41:08 +13:00
parent 003b1d6569
commit 57311f1b6a
8 changed files with 163 additions and 79 deletions

View File

@ -13,6 +13,90 @@ class ContextMenu extends React.Component{
constructor(props) {
super(props);
this.handleScroll = this.handleScroll.bind(this);
}
componentDidMount(){
window.addEventListener("scroll", this.handleScroll, false);
}
componentWillUnmount(){
window.removeEventListener("scroll", this.handleScroll, false);
}
handleScroll(){
if( this.props.context_menu.show ){
this.props.uiActions.hideContextMenu();
}
}
playQueueItem(){
var selectedTracks = this.props.context_menu.data.selected_tracks;
this.props.mopidyActions.changeTrack( selectedTracks[0].tlid );
this.props.uiActions.hideContextMenu();
}
removeFromQueue(){
var selected_tracks = this.props.context_menu.data.selected_tracks;
var selected_tracks_tlids = [];
for( var i = 0; i < selected_tracks.length; i++ ){
selected_tracks_tlids.push( selected_tracks[i].tlid );
}
this.props.mopidyActions.removeTracks( selected_tracks_tlids );
this.props.uiActions.hideContextMenu();
}
playItems(){
var selected_tracks = this.props.context_menu.data.selected_tracks;
var selected_tracks_uris = [];
for( var i = 0; i < selected_tracks.length; i++ ){
selected_tracks_uris.push( selected_tracks[i].uri );
}
this.props.mopidyActions.playTracks(selected_tracks_uris);
this.props.uiActions.hideContextMenu();
}
playItemsNext(){
var selected_tracks = this.props.context_menu.data.selected_tracks;
var selected_tracks_uris = [];
for( var i = 0; i < selected_tracks.length; i++ ){
selected_tracks_uris.push( selected_tracks[i].uri );
}
var current_track = this.props.current_track
var current_track_index = -1
for( var i = 0; i < this.props.current_tracklist.length; i++ ){
if( this.props.current_tracklist[i].tlid == this.props.current_track.tlid ){
current_track_index = i
break
}
}
var at_position = null
if( current_track_index > -1 ) at_position = current_track_index + 1
this.props.mopidyActions.enqueueTracks(selected_tracks_uris, at_position);
this.props.uiActions.hideContextMenu();
}
addToQueue(){
var selected_tracks = this.props.context_menu.data.selected_tracks;
var selected_tracks_uris = [];
for( var i = 0; i < selected_tracks.length; i++ ){
selected_tracks_uris.push( selected_tracks[i].uri );
}
this.props.mopidyActions.enqueueTracks(selected_tracks_uris);
this.props.uiActions.hideContextMenu();
}
addToPlaylist(){
this.props.uiActions.openModal( 'AddToPlaylistModal', { track_indexes: this.props.context_menu.data.selected_tracks_indexes })
this.props.uiActions.hideContextMenu();
}
removeFromPlaylist(){
this.props.uiActions.removeTracksFromPlaylist( this.props.playlist.uri, this.props.context_menu.data.selected_tracks_indexes )
this.props.uiActions.hideContextMenu();
}
renderPlaylistSubmenu(){
@ -96,75 +180,6 @@ class ContextMenu extends React.Component{
);
}
playQueueItem(){
var selectedTracks = this.props.context_menu.data.selected_tracks;
this.props.mopidyActions.changeTrack( selectedTracks[0].tlid );
this.props.uiActions.hideContextMenu();
}
removeFromQueue(){
var selected_tracks = this.props.context_menu.data.selected_tracks;
var selected_tracks_tlids = [];
for( var i = 0; i < selected_tracks.length; i++ ){
selected_tracks_tlids.push( selected_tracks[i].tlid );
}
this.props.mopidyActions.removeTracks( selected_tracks_tlids );
this.props.uiActions.hideContextMenu();
}
playItems(){
var selected_tracks = this.props.context_menu.data.selected_tracks;
var selected_tracks_uris = [];
for( var i = 0; i < selected_tracks.length; i++ ){
selected_tracks_uris.push( selected_tracks[i].uri );
}
this.props.mopidyActions.playTracks(selected_tracks_uris);
this.props.uiActions.hideContextMenu();
}
playItemsNext(){
var selected_tracks = this.props.context_menu.data.selected_tracks;
var selected_tracks_uris = [];
for( var i = 0; i < selected_tracks.length; i++ ){
selected_tracks_uris.push( selected_tracks[i].uri );
}
var current_track = this.props.current_track
var current_track_index = -1
for( var i = 0; i < this.props.current_tracklist.length; i++ ){
if( this.props.current_tracklist[i].tlid == this.props.current_track.tlid ){
current_track_index = i
break
}
}
var at_position = null
if( current_track_index > -1 ) at_position = current_track_index + 1
this.props.mopidyActions.enqueueTracks(selected_tracks_uris, at_position);
this.props.uiActions.hideContextMenu();
}
addToQueue(){
var selected_tracks = this.props.context_menu.data.selected_tracks;
var selected_tracks_uris = [];
for( var i = 0; i < selected_tracks.length; i++ ){
selected_tracks_uris.push( selected_tracks[i].uri );
}
this.props.mopidyActions.enqueueTracks(selected_tracks_uris);
this.props.uiActions.hideContextMenu();
}
addToPlaylist(){
this.props.uiActions.openModal( 'AddToPlaylistModal', { track_indexes: this.props.context_menu.data.selected_tracks_indexes })
this.props.uiActions.hideContextMenu();
}
removeFromPlaylist(){
this.props.uiActions.removeTracksFromPlaylist( this.props.playlist.uri, this.props.context_menu.data.selected_tracks_indexes )
this.props.uiActions.hideContextMenu();
}
render(){
if( !this.props.context_menu.show ) return null;

View File

@ -154,4 +154,29 @@ export let uriType = function( uri ){
}
return null;
}
/**
* Merge duplicated items in an array
*
* @param list Array the unclean array
* @param key string = the unique key (uri, tlid, etc)
**/
export let mergeDuplicates = function(list, key){
var clean_list = [];
var keyed_list = {};
for( var i in list ){
var item = list[i]
if( item[key] in keyed_list ){
item = Object.assign({}, keyed_list[item[key]], item)
}
keyed_list[item[key]] = item;
}
for( i in keyed_list ){
clean_list.push(keyed_list[i]);
}
return clean_list;
}

View File

@ -248,6 +248,28 @@ const MopidyMiddleware = (function(){
})
break;
case 'MOPIDY_ADD_PLAYLIST_TRACKS':
instruct( socket, store, 'playlists.lookup', { uri: action.playlist_uri })
.then( response => {
var tracks = [];
for( var i = 0; i < action.tracks_uris.length; i++ ){
tracks.push({
__model__: "Track",
uri: action.tracks_uris[i]
});
}
var playlist = Object.assign({}, response)
playlist.tracks = [...playlist.tracks, ...tracks]
instruct( socket, store, 'playlists.save', { playlist: playlist } )
.then( response => {
store.dispatch({ type: 'PLAYLIST_TRACKS_ADDED', tracks_uris: action.tracks_uris });
})
});
break
case 'MOPIDY_REMOVE_PLAYLIST_TRACKS':
// reverse order our indexes (otherwise removing from top will affect the keys following)

View File

@ -588,6 +588,19 @@ export function toggleFollowingPlaylist( uri, method ){
}
}
export function addTracksToPlaylist( playlist_uri, tracks_uris ){
return (dispatch, getState) => {
sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',playlist_uri) + '/playlists/'+ helpers.getFromUri('playlistid',playlist_uri) + '/tracks', 'POST', { uris: tracks_uris } )
.then( response => {
dispatch({
type: 'PLAYLIST_TRACKS_ADDED',
tracks_uris: tracks_uris,
snapshot_id: response.snapshot_id
});
});
}
}
export function deleteTracksFromPlaylist( uri, snapshot_id, tracks_indexes ){
return (dispatch, getState) => {
sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) + '/playlists/'+ helpers.getFromUri('playlistid',uri) + '/tracks', 'DELETE', { snapshot_id: snapshot_id, positions: tracks_indexes } )

View File

@ -24,12 +24,22 @@ const SpotifyMiddleware = (function(){
return
}
if( !store.getState().spotify.me || store.getState().spotify.me.id != playlist.owner.id ){
alert('You can only modify tracks you own')
alert('You can only modify playlists you own')
return
}
store.dispatch( actions.deleteTracksFromPlaylist( playlist.uri, playlist.snapshot_id, action.tracks_indexes ))
break
case 'SPOTIFY_ADD_PLAYLIST_TRACKS':
if( !store.getState().spotify.authorized ){
alert('Must be logged in to Spotify to do this')
return
}
store.dispatch( actions.addTracksToPlaylist( action.playlist_uri, action.tracks_uris ))
break
// when our mopidy server current track changes
case 'MOPIDY_CURRENTTLTRACK':

View File

@ -107,7 +107,7 @@ export function addTracksToPlaylist( playlist_uri, tracks ){
case 'm3u':
return {
type: 'MOPIDY_ADD_TRACKS_TO_PLAYLIST',
type: 'MOPIDY_ADD_PLAYLIST_TRACKS',
playlist_uri: playlist_uri,
tracks_uris: tracks_uris
}

View File

@ -1,4 +1,6 @@
import * as helpers from '../../helpers'
export default function reducer(ui = {}, action){
switch (action.type) {
@ -246,15 +248,12 @@ export default function reducer(ui = {}, action){
**/
case 'MOPIDY_PLAYLISTS_LOADED':
if( !action.data ) return ui
return Object.assign({}, ui, {
playlists: [...ui.playlists, ...action.data],
});
case 'SPOTIFY_LIBRARY_PLAYLISTS_LOADED':
if( !action.data ) return ui
var playlists = [...ui.playlists, ...action.data]
playlists = helpers.mergeDuplicates(playlists, 'uri')
return Object.assign({}, ui, {
playlists: [...ui.playlists, ...action.data]
playlists: playlists
});

View File

@ -80,12 +80,12 @@ class Playlist extends React.Component{
switch( helpers.uriSource( this.props.params.uri ) ){
case 'm3u':
return <ConfirmationButton className="large tertiary" content="Delete" confirmingContent="Confirm" onConfirm={ e => this.delete() } />
return <ConfirmationButton className="large tertiary" content="Delete" confirmingContent="Are you sure?" onConfirm={ e => this.delete() } />
break
case 'spotify':
if( this.props.playlist.owner && this.props.playlist.owner.id == this.props.spotify_userid ){
return <ConfirmationButton className="large tertiary" content="Delete" confirmingContent="Are you sure?" onConfirm={ e => this.delete() } />
return <ConfirmationButton className="large tertiary" content="Delete" confirmingContent="Are you sure?" onConfirm={ e => this.unfollow() } />
}else if( this.props.playlist.following ){
return <button className="large tertiary" onClick={ e => this.unfollow() }>Unfollow</button>
}else{