Merge branch 'enqueue'
This commit is contained in:
@ -91,13 +91,15 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
|
||||
mem.iris.send_message(self.connection_id, response)
|
||||
else:
|
||||
response = {
|
||||
'error': 'Method "'+message['method']+'" does not exist',
|
||||
'status': 0,
|
||||
'message': 'Method "'+message['method']+'" does not exist',
|
||||
'request_id': request_id
|
||||
}
|
||||
mem.iris.send_message(self.connection_id, response)
|
||||
else:
|
||||
response = {
|
||||
'error': 'Method key missing from request',
|
||||
'status': 0,
|
||||
'message': 'Method key missing from request',
|
||||
'request_id': request_id
|
||||
}
|
||||
mem.iris.send_message(self.connection_id, response)
|
||||
|
||||
@ -139,7 +139,7 @@ class ContextMenu extends React.Component{
|
||||
return (helpers.uriSource(this.props.menu.items[0].uri) == 'spotify')
|
||||
}
|
||||
|
||||
toggleInLibrary(in_library){
|
||||
toggleInLibrary(e, in_library){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
if (in_library){
|
||||
this.props.spotifyActions.following(this.props.menu.items[0].uri, 'DELETE')
|
||||
@ -148,13 +148,13 @@ class ContextMenu extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
playQueueItem(){
|
||||
playQueueItem(e){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
var tracks = this.props.menu.items;
|
||||
this.props.mopidyActions.changeTrack( tracks[0].tlid )
|
||||
}
|
||||
|
||||
removeFromQueue(){
|
||||
removeFromQueue(e){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
var tracks = this.props.menu.items;
|
||||
var tracks_tlids = [];
|
||||
@ -164,37 +164,37 @@ class ContextMenu extends React.Component{
|
||||
this.props.mopidyActions.removeTracks( tracks_tlids );
|
||||
}
|
||||
|
||||
playURIs(){
|
||||
playURIs(e){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
this.props.mopidyActions.playURIs(this.props.menu.uris, this.props.menu.tracklist_uri)
|
||||
}
|
||||
|
||||
playURIsNext(){
|
||||
playPlaylist(e){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
this.props.mopidyActions.enqueueURIsNext(this.props.menu.uris, this.props.menu.tracklist_uri)
|
||||
this.props.mopidyActions.playPlaylist(this.props.menu.uris[0])
|
||||
}
|
||||
|
||||
addToQueue(){
|
||||
addToQueue(e, next = false){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
this.props.mopidyActions.enqueueURIs(this.props.menu.uris, this.props.menu.tracklist_uri)
|
||||
this.props.mopidyActions.enqueueURIs(this.props.menu.uris, this.props.menu.tracklist_uri, next)
|
||||
}
|
||||
|
||||
addTracksToPlaylist(playlist_uri){
|
||||
addTracksToPlaylist(e, playlist_uri){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
this.props.uiActions.addTracksToPlaylist(playlist_uri, this.props.menu.uris)
|
||||
}
|
||||
|
||||
removeFromPlaylist(){
|
||||
removeFromPlaylist(e){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
this.props.uiActions.removeTracksFromPlaylist(this.props.menu.tracklist_uri, this.props.menu.indexes)
|
||||
}
|
||||
|
||||
startRadio(){
|
||||
startRadio(e){
|
||||
this.props.uiActions.hideContextMenu()
|
||||
this.props.pusherActions.startRadio(this.props.menu.uris)
|
||||
}
|
||||
|
||||
goToArtist(){
|
||||
goToArtist(e){
|
||||
if (!this.props.menu.items || this.props.menu.items.length <= 0 || !this.props.menu.items[0].artists || this.props.menu.items[0].artists.length <= 0){
|
||||
return null
|
||||
} else {
|
||||
@ -203,7 +203,7 @@ class ContextMenu extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
goToUser(){
|
||||
goToUser(e){
|
||||
if (!this.props.menu.items || this.props.menu.items.length <= 0){
|
||||
return null
|
||||
} else {
|
||||
@ -223,7 +223,7 @@ class ContextMenu extends React.Component{
|
||||
this.props.uiActions.hideContextMenu()
|
||||
}
|
||||
|
||||
closeAndDeselectTracks(){
|
||||
closeAndDeselectTracks(e){
|
||||
this.props.uiActions.hideContextMenu();
|
||||
// TODO
|
||||
}
|
||||
@ -331,6 +331,14 @@ class ContextMenu extends React.Component{
|
||||
</span>
|
||||
)
|
||||
|
||||
var play_playlist = (
|
||||
<span className="menu-item-wrapper">
|
||||
<a className="menu-item" onClick={e => this.playPlaylist(e)}>
|
||||
<span className="label">Play</span>
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
|
||||
var play_queue_item = (
|
||||
<span className="menu-item-wrapper">
|
||||
<a className="menu-item" onClick={e => this.playQueueItem(e)}>
|
||||
@ -341,7 +349,7 @@ class ContextMenu extends React.Component{
|
||||
|
||||
var play_uris_next = (
|
||||
<span className="menu-item-wrapper">
|
||||
<a className="menu-item" onClick={e => this.playURIsNext(e)}>
|
||||
<a className="menu-item" onClick={e => this.addToQueue(e, true)}>
|
||||
<span className="label">Play next</span>
|
||||
</a>
|
||||
</span>
|
||||
@ -367,7 +375,7 @@ class ContextMenu extends React.Component{
|
||||
|
||||
var toggle_in_library = (
|
||||
<span className="menu-item-wrapper">
|
||||
<a className="menu-item" onClick={e => this.toggleInLibrary(context.in_library)}>
|
||||
<a className="menu-item" onClick={e => this.toggleInLibrary(e, context.in_library)}>
|
||||
<span className="label">
|
||||
{context.in_library ? 'Remove from library' : 'Add to library'}
|
||||
</span>
|
||||
@ -451,7 +459,7 @@ class ContextMenu extends React.Component{
|
||||
case 'playlist':
|
||||
return (
|
||||
<div>
|
||||
{play_uris}
|
||||
{play_playlist}
|
||||
{this.canBeInLibrary() ? toggle_in_library : null}
|
||||
{context.source == 'spotify' ? go_to_user : null}
|
||||
{copy_uris}
|
||||
|
||||
@ -74,12 +74,15 @@ class DebugInfo extends React.Component{
|
||||
<div className="item">
|
||||
Notifications: {this.props.ui.notifications ? Object.keys(this.props.ui.notifications).length : '0'}
|
||||
</div>
|
||||
<div className="item">
|
||||
Processes: {this.props.ui.processes ? Object.keys(this.props.ui.processes).length : '0'}
|
||||
</div>
|
||||
<br />
|
||||
<div className="item">
|
||||
_testMode: {window._testMode ? 'on' : 'off'}
|
||||
</div>
|
||||
<div className="item">
|
||||
Touch: {helpers.isTouchDevice() ? 'yes' : 'no'}
|
||||
Touch: {helpers.isTouchDevice() ? 'on' : 'off'}
|
||||
</div>
|
||||
{this.renderLoadQueue()}
|
||||
</div>
|
||||
|
||||
@ -11,25 +11,65 @@ export default class Notifications extends React.Component{
|
||||
renderNotifications(){
|
||||
if (!this.props.notifications || this.props.notifications.length <= 0) return null
|
||||
|
||||
// we only care about the last notification
|
||||
var notification = this.props.notifications[this.props.notifications.length-1]
|
||||
return (
|
||||
<span>
|
||||
{
|
||||
this.props.notifications.map(notification => {
|
||||
switch (notification.type){
|
||||
case 'shortcut':
|
||||
return (
|
||||
<div className="shortcut-notification" key={notification.key}>
|
||||
<FontAwesome name={notification.content} />
|
||||
</div>
|
||||
)
|
||||
|
||||
if (notification.is_shortcut){
|
||||
return (
|
||||
<div className="shortcut-notification">
|
||||
<FontAwesome name={notification.type} />
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className={notification.type+" notification"}>
|
||||
<FontAwesome name="close" className="close-button" onClick={ e => this.props.uiActions.removeNotification(notification.id) } />
|
||||
{ notification.content }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
default:
|
||||
return (
|
||||
<div className={notification.type+" notification"} key={notification.key}>
|
||||
<FontAwesome name="close" className="close-button" onClick={ e => this.props.uiActions.removeNotification(notification.key) } />
|
||||
{ notification.content }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
renderProcesses(){
|
||||
if (!this.props.processes || this.props.processes.length <= 0) return null
|
||||
var processes = this.props.processes
|
||||
var items = []
|
||||
|
||||
for (var key in processes){
|
||||
if (processes.hasOwnProperty(key)){
|
||||
if (processes[key].cancelling){
|
||||
items.push(
|
||||
<div className="process notification cancelling" key={key}>
|
||||
Cancelling
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
items.push(
|
||||
<div className="process notification" key={key}>
|
||||
<FontAwesome name="close" className="close-button" onClick={ e => this.props.uiActions.cancelProcess(key) } />
|
||||
{ processes[key].content }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{items}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
// do we want the loading of everything to be displayed?
|
||||
// not likely...
|
||||
renderLoader(){
|
||||
if (!this.props.load_queue){
|
||||
return null
|
||||
@ -59,7 +99,7 @@ export default class Notifications extends React.Component{
|
||||
return (
|
||||
<div className="notifications">
|
||||
{this.renderNotifications()}
|
||||
{this.renderLoader()}
|
||||
{this.renderProcesses()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -266,7 +266,7 @@ export let asURIs = function(items){
|
||||
* Merge duplicated items in an array
|
||||
*
|
||||
* @param list Array the unclean array
|
||||
* @param key string = the unique key (uri, tlid, etc)
|
||||
* @param key string = the unique key (id, uri, tlid, etc)
|
||||
**/
|
||||
export let mergeDuplicates = function(list, key){
|
||||
var clean_list = [];
|
||||
|
||||
@ -57,24 +57,30 @@ export function playURIs( uris, from_uri = null ){
|
||||
}
|
||||
}
|
||||
|
||||
export function enqueueURIsNext( uris, from_uri = null ){
|
||||
if( typeof(uris) !== 'object' ) uris = [uris]
|
||||
return {
|
||||
type: 'MOPIDY_ENQUEUE_URIS_NEXT',
|
||||
uris: uris,
|
||||
from_uri: from_uri
|
||||
}
|
||||
}
|
||||
|
||||
export function enqueueURIs( uris, from_uri = null, at_position = false ){
|
||||
export function enqueueURIs( uris, from_uri = null, next = false, at_position = null ){
|
||||
return {
|
||||
type: 'MOPIDY_ENQUEUE_URIS',
|
||||
uris: uris,
|
||||
at_position: at_position,
|
||||
next: next,
|
||||
from_uri: from_uri
|
||||
}
|
||||
}
|
||||
|
||||
export function playPlaylist(uri){
|
||||
return {
|
||||
type: 'MOPIDY_PLAY_PLAYLIST',
|
||||
uri: uri
|
||||
}
|
||||
}
|
||||
|
||||
export function playAlbum(uri){
|
||||
return {
|
||||
type: 'MOPIDY_PLAY_ALBUM',
|
||||
uri: uri
|
||||
}
|
||||
}
|
||||
|
||||
export function removeTracks( tlids ){
|
||||
return {
|
||||
type: 'MOPIDY_INSTRUCT',
|
||||
|
||||
@ -4,8 +4,9 @@ import * as helpers from '../../helpers'
|
||||
|
||||
var mopidyActions = require('./actions.js')
|
||||
var uiActions = require('../ui/actions.js')
|
||||
var lastfmActions = require('../lastfm/actions.js')
|
||||
var spotifyActions = require('../spotify/actions.js')
|
||||
var pusherActions = require('../pusher/actions.js')
|
||||
var lastfmActions = require('../lastfm/actions.js')
|
||||
|
||||
const MopidyMiddleware = (function(){
|
||||
|
||||
@ -219,38 +220,46 @@ const MopidyMiddleware = (function(){
|
||||
icon: (store.getState().ui.current_track ? helpers.getTrackIcon( store.getState().ui.current_track ) : false)
|
||||
}
|
||||
store.dispatch( pusherActions.deliverBroadcast(data) )
|
||||
break;
|
||||
|
||||
case 'MOPIDY_ENQUEUE_URIS':
|
||||
|
||||
// create batches of 20 uris to load
|
||||
var all_uris = Object.assign([], action.uris)
|
||||
var batched_uris = []
|
||||
while (all_uris.length > 0){
|
||||
batched_uris.push( all_uris.splice(0,20) )
|
||||
}
|
||||
|
||||
// run each batch
|
||||
for (var i = 0; i < batched_uris.length; i++){
|
||||
var value = { uris: batched_uris[i] }
|
||||
if (action.at_position){
|
||||
value.at_position = action.at_position
|
||||
}
|
||||
|
||||
instruct( socket, store, 'tracklist.add', value )
|
||||
.then( response => {
|
||||
var tlids = []
|
||||
for (var i = 0; i < response.length; i++){
|
||||
tlids.push(response[i].tlid)
|
||||
}
|
||||
store.dispatch( pusherActions.addQueueMetadata(tlids, action.from_uri) )
|
||||
console.info('Added '+tlids.length+' URI(s) to queue')
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
case 'MOPIDY_ENQUEUE_URIS_NEXT':
|
||||
case 'MOPIDY_PLAY_PLAYLIST':
|
||||
if (helpers.uriSource(action.uri) == 'spotify'){
|
||||
|
||||
// playlist already in index
|
||||
if (store.getState().ui.playlists.hasOwnProperty(action.uri)){
|
||||
|
||||
// make sure we didn't get this playlist from Mopidy-Spotify
|
||||
// if we did, we'd have a cached version on server so no need to fetch
|
||||
if (!store.getState().ui.playlists[action.uri].is_mopidy){
|
||||
store.dispatch(uiActions.startProcess('MOPIDY_ENQUEUE_URIS', 'Fetching tracks'))
|
||||
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri))
|
||||
break
|
||||
}
|
||||
|
||||
// not loaded, so we need to fetch
|
||||
} else {
|
||||
store.dispatch(uiActions.startProcess('MOPIDY_ENQUEUE_URIS', 'Fetching tracks'))
|
||||
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// default to load it as per usual
|
||||
store.dispatch(mopidyActions.playURIs([action.uri], action.uri))
|
||||
|
||||
break
|
||||
|
||||
case 'SPOTIFY_ALL_PLAYLIST_TRACKS_LOADED_FOR_PLAYING':
|
||||
var uris = []
|
||||
for (var i = 0; i < action.tracks.length; i++){
|
||||
uris.push(action.tracks[i].track.uri)
|
||||
}
|
||||
store.dispatch(mopidyActions.playURIs(uris, action.uri))
|
||||
break
|
||||
|
||||
case 'MOPIDY_ENQUEUE_URIS':
|
||||
var uris_added = 0
|
||||
var remaining_uris = Object.assign([], action.uris)
|
||||
var current_track = store.getState().ui.current_track
|
||||
var current_tracklist = store.getState().ui.current_tracklist
|
||||
var current_track_index = -1
|
||||
@ -264,18 +273,64 @@ const MopidyMiddleware = (function(){
|
||||
}
|
||||
}
|
||||
|
||||
var at_position = null
|
||||
if( current_track_index > -1 ) at_position = current_track_index + 1
|
||||
let process_batch = function(){
|
||||
|
||||
instruct( socket, store, 'tracklist.add', { uris: action.uris, at_position: at_position } )
|
||||
.then( response => {
|
||||
var tlids = []
|
||||
for (var i = 0; i < response.length; i++){
|
||||
tlids.push(response[i].tlid)
|
||||
// process is not running
|
||||
if (store.getState().ui.processes && store.getState().ui.processes[action.type]){
|
||||
if (store.getState().ui.processes[action.type].cancelling){
|
||||
// recognise as cancelled
|
||||
store.dispatch(uiActions.stopProcess(action.type))
|
||||
return false
|
||||
}
|
||||
store.dispatch( pusherActions.addQueueMetadata(tlids, action.from_uri) )
|
||||
store.dispatch( uiActions.createNotification('Added '+tlids.length+' URI(s) to queue') )
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
// update our process details
|
||||
store.dispatch(uiActions.startProcess(action.type, 'Adding '+remaining_uris.length+' URI(s)'))
|
||||
|
||||
var params = {uris: remaining_uris.splice(0,5)}
|
||||
if (action.next && current_track_index > -1){
|
||||
params.at_position = current_track_index + uris_added + 1
|
||||
} else if (action.at_position){
|
||||
params.at_position = action.at_position
|
||||
}
|
||||
|
||||
instruct(socket, store, 'tracklist.add', params)
|
||||
.then( response => {
|
||||
|
||||
// append our counter
|
||||
uris_added += response.length
|
||||
|
||||
// add metadata to queue
|
||||
var tlids = []
|
||||
for (var i = 0; i < response.length; i++){
|
||||
tlids.push(response[i].tlid)
|
||||
}
|
||||
store.dispatch(pusherActions.addQueueMetadata(tlids, action.from_uri))
|
||||
|
||||
// still more URIs? run again in 100ms
|
||||
// this gives our server time to handle other requests
|
||||
// crude, but prevents locking the server
|
||||
if (remaining_uris.length > 0){
|
||||
setTimeout(
|
||||
function(){
|
||||
process_batch()
|
||||
},
|
||||
100
|
||||
)
|
||||
|
||||
// all done
|
||||
} else {
|
||||
store.dispatch(uiActions.stopProcess(action.type))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// start processing
|
||||
store.dispatch(uiActions.startProcess(action.type, 'Adding '+remaining_uris.length+' URI(s)'))
|
||||
process_batch()
|
||||
|
||||
break
|
||||
|
||||
case 'MOPIDY_PLAY_URIS':
|
||||
@ -285,29 +340,37 @@ const MopidyMiddleware = (function(){
|
||||
store.dispatch( pusherActions.stopRadio() )
|
||||
}
|
||||
|
||||
// add our first track
|
||||
instruct( socket, store, 'tracklist.add', { uri: action.uris[0], at_position: 0 } )
|
||||
.then( response => {
|
||||
var first_uri = action.uris[0]
|
||||
|
||||
// treat empty response as a failed lookup
|
||||
if( !response || response.length <= 0 ){
|
||||
store.dispatch( uiActions.createNotification('Failed to load URI(s)', 'bad') )
|
||||
console.error(action)
|
||||
}else{
|
||||
// play it
|
||||
store.dispatch( mopidyActions.changeTrack( response[0].tlid ) );
|
||||
// add our first track
|
||||
instruct(socket, store, 'tracklist.add', { uri: first_uri, at_position: 0 })
|
||||
.then(response => {
|
||||
|
||||
// play it (only if we got a successful lookup)
|
||||
if (response.length > 0){
|
||||
store.dispatch(mopidyActions.changeTrack(response[0].tlid));
|
||||
|
||||
var tlids = []
|
||||
for (var i = 0; i < response.length; i++){
|
||||
tlids.push(response[i].tlid)
|
||||
}
|
||||
store.dispatch( pusherActions.addQueueMetadata(tlids, action.from_uri) )
|
||||
store.dispatch(pusherActions.addQueueMetadata(tlids, action.from_uri))
|
||||
} else {
|
||||
store.dispatch(uiActions.createNotification('Failed to add some URI(s)', 'bad'))
|
||||
}
|
||||
|
||||
// add the rest of our uris (if any)
|
||||
action.uris.shift();
|
||||
if( action.uris.length > 0 ){
|
||||
store.dispatch( mopidyActions.enqueueURIs( action.uris, action.from_uri, 1 ) )
|
||||
}
|
||||
// add the rest of our uris (if any)
|
||||
action.uris.shift();
|
||||
if( action.uris.length > 0 ){
|
||||
|
||||
// wait 100ms so the server can trigger track_changed etc
|
||||
// this means our UI feels snappier as the first track shows up quickly
|
||||
setTimeout(
|
||||
function(){
|
||||
store.dispatch(mopidyActions.enqueueURIs( action.uris, action.from_uri, 1 ))
|
||||
},
|
||||
100
|
||||
)
|
||||
}
|
||||
})
|
||||
break;
|
||||
@ -453,7 +516,8 @@ const MopidyMiddleware = (function(){
|
||||
type: 'playlist',
|
||||
name: response.name,
|
||||
uri: response.uri,
|
||||
source: (source == 'spotify' ? 'local' : source),
|
||||
source: source,
|
||||
is_mopidy: true,
|
||||
last_modified: response.last_modified,
|
||||
tracks_total: ( response.tracks ? response.tracks.length : 0 )
|
||||
}
|
||||
@ -477,6 +541,7 @@ const MopidyMiddleware = (function(){
|
||||
response,
|
||||
{
|
||||
type: 'playlist',
|
||||
is_mopidy: true,
|
||||
tracks: ( response.tracks ? response.tracks : [] ),
|
||||
tracks_total: ( response.tracks ? response.tracks.length : [] )
|
||||
}
|
||||
@ -701,6 +766,7 @@ const MopidyMiddleware = (function(){
|
||||
var album = Object.assign(
|
||||
{},
|
||||
{
|
||||
is_mopidy: true,
|
||||
artists: response[uri][0].artists,
|
||||
tracks: response[uri],
|
||||
tracks_total: response[uri].length
|
||||
@ -729,6 +795,7 @@ const MopidyMiddleware = (function(){
|
||||
{ images: [] },
|
||||
response[0].album,
|
||||
{
|
||||
is_mopidy: true,
|
||||
artists: response[0].artists,
|
||||
tracks: response,
|
||||
tracks_total: response.length
|
||||
@ -832,6 +899,7 @@ const MopidyMiddleware = (function(){
|
||||
{},
|
||||
(response ? response[0].artists[0] : {}),
|
||||
{
|
||||
is_mopidy: true,
|
||||
albums_uris: helpers.asURIs(albums),
|
||||
tracks: response.slice(0,10)
|
||||
}
|
||||
|
||||
@ -890,7 +890,7 @@ export function createPlaylist( name, is_public ){
|
||||
}
|
||||
}
|
||||
|
||||
export function savePlaylist( uri, name, is_public ){
|
||||
export function savePlaylist(uri, name, is_public){
|
||||
return (dispatch, getState) => {
|
||||
|
||||
sendRequest( dispatch, getState, 'users/'+ getState().spotify.me.id +'/playlists/'+ helpers.getFromUri('playlistid',uri), 'PUT', { name: name, public: is_public } )
|
||||
@ -906,7 +906,7 @@ export function savePlaylist( uri, name, is_public ){
|
||||
}
|
||||
}
|
||||
|
||||
export function getPlaylist( uri ){
|
||||
export function getPlaylist(uri){
|
||||
return (dispatch, getState) => {
|
||||
|
||||
// get the main playlist object
|
||||
@ -919,6 +919,7 @@ export function getPlaylist( uri ){
|
||||
description = response.description
|
||||
description = description.split('<a href="spotify:artist:').join('<a href="#'+global.baseURL+'artist/spotify:artist:')
|
||||
description = description.split('<a href="spotify:album:').join('<a href="#'+global.baseURL+'album/spotify:album:')
|
||||
description = description.split('<a href="spotify:user:').join('<a href="#'+global.baseURL+'user/spotify:user:')
|
||||
}
|
||||
|
||||
var playlist = Object.assign(
|
||||
@ -942,7 +943,44 @@ export function getPlaylist( uri ){
|
||||
}
|
||||
}
|
||||
|
||||
function loadNextPlaylistsBatch( dispatch, getState, playlists, lastResponse ){
|
||||
/**
|
||||
* Get all tracks for a playlist
|
||||
*
|
||||
* Recursively get .next until we have all tracks
|
||||
**/
|
||||
function loadNextPlaylistTracksBatch(dispatch, getState, uri, tracks, lastResponse){
|
||||
if( lastResponse.next ){
|
||||
sendRequest(dispatch, getState, lastResponse.next)
|
||||
.then( response => {
|
||||
tracks = [...tracks, ...response.items]
|
||||
loadNextPlaylistTracksBatch(dispatch, getState, uri, tracks, response)
|
||||
});
|
||||
}else{
|
||||
dispatch({
|
||||
type: 'SPOTIFY_ALL_PLAYLIST_TRACKS_LOADED_FOR_PLAYING',
|
||||
uri: uri,
|
||||
tracks: tracks
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getAllPlaylistTracks(uri){
|
||||
return (dispatch, getState) => {
|
||||
sendRequest(dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'/tracks?market='+getState().spotify.country)
|
||||
.then( response => {
|
||||
loadNextPlaylistTracksBatch(dispatch, getState, uri, response.items, response)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all of our playlists in one foul swoop
|
||||
*
|
||||
* Recursively gets playlists until no more .next value. Fails when user has
|
||||
* a lot of playlists, and we hit the API limits
|
||||
**/
|
||||
function loadNextPlaylistsBatch(dispatch, getState, playlists, lastResponse){
|
||||
if( lastResponse.next ){
|
||||
sendRequest( dispatch, getState, lastResponse.next )
|
||||
.then( response => {
|
||||
@ -966,7 +1004,7 @@ export function getAllLibraryPlaylists(){
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleFollowingPlaylist( uri, method ){
|
||||
export function toggleFollowingPlaylist(uri, method){
|
||||
if( method == 'PUT' ) var new_state = 1
|
||||
if( method == 'DELETE' ) var new_state = 0
|
||||
|
||||
|
||||
@ -224,22 +224,21 @@ export function createBrowserNotification( data ){
|
||||
}
|
||||
}
|
||||
|
||||
export function createNotification(content, type = 'default', is_shortcut = false){
|
||||
export function createNotification(content, type = 'default', key = helpers.generateGuid()){
|
||||
return {
|
||||
type: 'CREATE_NOTIFICATION',
|
||||
notification: {
|
||||
id: helpers.generateGuid(),
|
||||
key: key,
|
||||
type: type,
|
||||
content: content,
|
||||
is_shortcut: is_shortcut
|
||||
content: content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function removeNotification( id ){
|
||||
export function removeNotification(key){
|
||||
return {
|
||||
type: 'REMOVE_NOTIFICATION',
|
||||
id: id
|
||||
key: key
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,3 +261,25 @@ export function stopLoading(key){
|
||||
key: key
|
||||
}
|
||||
}
|
||||
|
||||
export function startProcess(key,content){
|
||||
return {
|
||||
type: 'START_PROCESS',
|
||||
key: key,
|
||||
content: content
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelProcess(key){
|
||||
return {
|
||||
type: 'CANCEL_PROCESS',
|
||||
key: key
|
||||
}
|
||||
}
|
||||
|
||||
export function stopProcess(key){
|
||||
return {
|
||||
type: 'STOP_PROCESS',
|
||||
key: key
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,9 +271,9 @@ const UIMiddleware = (function(){
|
||||
// start a timeout to remove this notification
|
||||
var timeout = setTimeout(
|
||||
function(){
|
||||
store.dispatch(uiActions.removeNotification(action.notification.id))
|
||||
store.dispatch(uiActions.removeNotification(action.notification.key))
|
||||
},
|
||||
3000
|
||||
(action.notification.type == 'shortcut' ? 1000 : 3000)
|
||||
)
|
||||
|
||||
next(action)
|
||||
|
||||
@ -711,15 +711,16 @@ export default function reducer(ui = {}, action){
|
||||
|
||||
case 'CREATE_NOTIFICATION':
|
||||
var notifications = [...ui.notifications, action.notification]
|
||||
notifications = helpers.mergeDuplicates(notifications,'key')
|
||||
return Object.assign({}, ui, { notifications: notifications })
|
||||
|
||||
case 'REMOVE_NOTIFICATION':
|
||||
var notifications = Object.assign([], ui.notifications)
|
||||
|
||||
function getByID( notification ){
|
||||
return notification.id === action.id
|
||||
function getByKey( notification ){
|
||||
return notification.key === action.key
|
||||
}
|
||||
var index = notifications.findIndex(getByID)
|
||||
var index = notifications.findIndex(getByKey)
|
||||
if( index > -1 ) notifications.splice(index, 1)
|
||||
|
||||
return Object.assign({}, ui, { notifications: notifications })
|
||||
@ -727,7 +728,7 @@ export default function reducer(ui = {}, action){
|
||||
|
||||
|
||||
/**
|
||||
* Loader
|
||||
* Loading and processes
|
||||
**/
|
||||
|
||||
case 'START_LOADING':
|
||||
@ -742,6 +743,34 @@ export default function reducer(ui = {}, action){
|
||||
}
|
||||
return Object.assign({}, ui, {load_queue: load_queue})
|
||||
|
||||
case 'START_PROCESS':
|
||||
var processes = Object.assign({}, (ui.processes ? ui.processes : []))
|
||||
processes[action.key] = {
|
||||
key: action.key,
|
||||
content: action.content
|
||||
}
|
||||
return Object.assign({}, ui, {processes: processes})
|
||||
|
||||
case 'CANCEL_PROCESS':
|
||||
var processes = Object.assign({}, (ui.processes ? ui.processes : {}))
|
||||
if (processes[action.key]){
|
||||
processes[action.key] = Object.assign(
|
||||
{},
|
||||
processes[action.key],
|
||||
{
|
||||
cancelling: true
|
||||
}
|
||||
)
|
||||
}
|
||||
return Object.assign({}, ui, {processes: processes})
|
||||
|
||||
case 'STOP_PROCESS':
|
||||
var processes = Object.assign({}, (ui.processes ? ui.processes : {}))
|
||||
if (processes[action.key]){
|
||||
delete processes[action.key]
|
||||
}
|
||||
return Object.assign({}, ui, {processes: processes})
|
||||
|
||||
|
||||
default:
|
||||
return ui
|
||||
|
||||
@ -106,10 +106,10 @@ class App extends React.Component{
|
||||
case 32: // spacebar
|
||||
if (this.props.play_state == 'playing'){
|
||||
this.props.mopidyActions.pause()
|
||||
this.props.uiActions.createNotification(null, 'pause', true)
|
||||
this.props.uiActions.createNotification('pause', 'shortcut', 'shortcut')
|
||||
}else{
|
||||
this.props.mopidyActions.play()
|
||||
this.props.uiActions.createNotification(null, 'play', true)
|
||||
this.props.uiActions.createNotification('play', 'shortcut', 'shortcut')
|
||||
}
|
||||
break;
|
||||
|
||||
@ -158,6 +158,7 @@ class App extends React.Component{
|
||||
<Notifications
|
||||
uiActions={this.props.uiActions}
|
||||
notifications={this.props.notifications}
|
||||
processes={this.props.processes}
|
||||
load_queue={this.props.load_queue} />
|
||||
{this.props.debug_info ? <DebugInfo /> : null}
|
||||
</div>
|
||||
@ -174,6 +175,7 @@ class App extends React.Component{
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
notifications: (state.ui.notifications ? state.ui.notifications : []),
|
||||
processes: (state.ui.processes ? state.ui.processes : {}),
|
||||
load_queue: (state.ui.load_queue ? state.ui.load_queue : {}),
|
||||
mopidy_connected: state.mopidy.connected,
|
||||
spotify_authorized: state.spotify.authorized,
|
||||
|
||||
@ -103,7 +103,9 @@ class Debug extends React.Component{
|
||||
<div className="field">
|
||||
<div className="name"></div>
|
||||
<div className="input">
|
||||
<a className="button secondary" onClick={e => this.props.uiActions.createNotification('Test '+Math.floor(Date.now() / 1000))}>Create notification</a>
|
||||
<a className="button secondary" onClick={e => this.props.uiActions.createNotification('Test notification')}>Create notification</a>
|
||||
<a className="button secondary" onClick={e => this.props.uiActions.startProcess('test_process', 'Test process')}>Start process</a>
|
||||
<a className="button secondary" onClick={e => this.props.uiActions.stopProcess('test_process')}>Stop process</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -70,8 +70,7 @@ class Playlist extends React.Component{
|
||||
|
||||
play(){
|
||||
ReactGA.event({ category: 'Playlist', action: 'Play', label: this.props.playlist.uri })
|
||||
var tracks_uris = helpers.asURIs(this.props.playlist.tracks)
|
||||
this.props.mopidyActions.playURIs(tracks_uris, this.props.params.uri)
|
||||
this.props.mopidyActions.playPlaylist(this.props.playlist.uri)
|
||||
}
|
||||
|
||||
follow(){
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
background: $red;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
&.process {
|
||||
position: relative;
|
||||
padding-right: 18px;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user