From 9bb95211c27dc8c99b6c4ecf1d5b12dbc0bf4192 Mon Sep 17 00:00:00 2001 From: jcass Date: Sun, 11 Dec 2016 14:26:33 +0200 Subject: [PATCH] Add feature to allow insertion of track URI at any point in the queue. Fixes #75. --- README.rst | 3 + mopidy_musicbox_webclient/static/index.html | 52 +++++++- .../static/js/controls.js | 120 ++++++++++++++---- mopidy_musicbox_webclient/static/js/gui.js | 19 ++- mopidy_musicbox_webclient/static/mb.appcache | 2 +- tests/js/dummy_tracklist.js | 2 +- tests/js/test_controls.js | 37 ++++-- 7 files changed, 184 insertions(+), 51 deletions(-) diff --git a/README.rst b/README.rst index a329e75c..477ebba2 100644 --- a/README.rst +++ b/README.rst @@ -106,12 +106,15 @@ Changelog v2.4.0 (UNRELEASED) ------------------- +- Add ability to insert a track anywhere in the current queue. (Addresses: `#75 `_). + **Fixes** - Only show 'Show Album' or 'Show Artist' options in popup menus if URI's for those resources are available. (Fixes: `#213 `_). - Now shows correct hostname information in loader popup. (Fixes: `#209 `_). - Now shows server name/IP address and port number at the bottom of the navigation pane. (Fixes: `#67 `_). +- Reset 'Now Playing' info when the last track in the tracklist is deleted. Fixes an issue where info of the last song played would be displayed even after the queue had been cleared. v2.3.0 (2016-05-15) ------------------- diff --git a/mopidy_musicbox_webclient/static/index.html b/mopidy_musicbox_webclient/static/index.html index eee32484..f15c80ae 100644 --- a/mopidy_musicbox_webclient/static/index.html +++ b/mopidy_musicbox_webclient/static/index.html @@ -111,14 +111,14 @@

- Album cover + Album cover
Close

 

- Album artist + Album artist
@@ -160,6 +160,9 @@

Artists

  • Play
  • +
  • + Insert Track After This One +
  • Remove from Queue
  • @@ -178,22 +181,53 @@

    Artists

    +
    +
    +

    Insert a Track to Play Next + +

    + + +
    +
    +
    + +
    +
    +

    Add a Track to End of Queue + +

    + + +
    +
    +
    +

    Save Current Queue to a Playlist + onkeypress="return controls.checkDefaultButtonClick(event.keyCode, '#popupSave');" type="text"/>

    -
    -
    - +
    @@ -347,6 +381,12 @@

    Browse

    Play Queue

    + + diff --git a/mopidy_musicbox_webclient/static/js/controls.js b/mopidy_musicbox_webclient/static/js/controls.js index f1a032a3..0a4623e1 100644 --- a/mopidy_musicbox_webclient/static/js/controls.js +++ b/mopidy_musicbox_webclient/static/js/controls.js @@ -17,8 +17,10 @@ * Adds tracks to current tracklist and starts playback if necessary. * * @param {string} action - The action to perform. Valid actions are: - * PLAY_NOW: add the track at 'trackIndex' and start playback. - * PLAY_NEXT: insert track after currently playing track. + * PLAY_NOW: add the track at the current queue position and + * start playback immediately. + * PLAY_NEXT: insert track after the reference track, if 'index' + * is provided, or after the current track otherwise. * ADD_THIS_BOTTOM: add track to bottom of tracklist. * ADD_ALL_BOTTOM: add all tracks in in the list to bottom of * tracklist. @@ -32,17 +34,19 @@ * @param {string} playlistUri - (Optional) The URI of the playlist containing the tracks * to be played. If no URI is provided then the 'list' attribute * of the popup DIV is assumed to contain the playlist URI. + * @param {string} index - (Optional) The tracklist index of the reference track that the + * action should be performed on. Defaults to the index of the currently + * playing track. */ - playTracks: function (action, mopidy, trackUri, playlistUri) { - $('#popupTracks').popup('close') - toast('Loading...') + playTracks: function (action, mopidy, trackUri, playlistUri, index) { + toast('Updating queue...') - trackUri = trackUri || $('#popupTracks').data('track') + trackUri = trackUri || $('#popupTracks').data('track') || $('#popupQueue').data('track') if (typeof trackUri === 'undefined') { throw new Error('No track URI provided for playback.') } - playlistUri = playlistUri || $('#popupTracks').data('list') + playlistUri = playlistUri || $('#popupTracks').data('list') || $('#popupQueue').data('list') if (typeof playlistUri === 'undefined') { throw new Error('No playlist URI provided for playback.') } @@ -58,17 +62,15 @@ switch (action) { case PLAY_NOW: case PLAY_NEXT: - // Find track that is currently playing. - mopidy.tracklist.index().then(function (currentIndex) { - // Add browsed track just below it. - mopidy.tracklist.add({at_position: currentIndex + 1, uris: trackUris}).then(function (tlTracks) { - if (action === PLAY_NOW) { // Start playback immediately. - mopidy.playback.stop().then(function () { - mopidy.playback.play({tlid: tlTracks[0].tlid}) - }) - } + if (action === PLAY_NOW || typeof index === 'undefined' || index === '') { + // Use current track as reference point for insertion. + mopidy.tracklist.index().then(function (currentIndex) { + controls._addTrackAtIndex(action, mopidy, trackUris, currentIndex) }) - }) + } else { + // Use provided index for insertion. + controls._addTrackAtIndex(action, mopidy, trackUris, index) + } break case ADD_THIS_BOTTOM: case ADD_ALL_BOTTOM: @@ -95,6 +97,9 @@ updatePlayIcons('', '', controls.getIconForAction(action)) } } + + $('#popupTracks').popup('close') + $('#popupQueue').popup('close') }, /* Getter function for 'action' variable. Also checks config settings and cookies if required. */ @@ -153,6 +158,29 @@ return trackUris }, + _addTrackAtIndex: function (action, mopidy, trackUris, index) { + var pos + if (index !== null) { + if (action === PLAY_NOW) { + // Insert at provided index. + pos = index + } else if (action === PLAY_NEXT) { + // Insert after provided index. + pos = index + 1 + } + } else { + // Insert at top of queue + pos = 0 + } + mopidy.tracklist.add({at_position: pos, uris: trackUris}).then(function (tlTracks) { + if (action === PLAY_NOW) { // Start playback immediately. + mopidy.playback.stop().then(function () { + mopidy.playback.play({tlid: tlTracks[0].tlid}) + }) + } + }) + }, + /** ****************************************************** * play an uri from the queue *********************************************************/ @@ -165,39 +193,81 @@ playQueueTrack: function (tlid) { // Stop directly, for user feedback mopidy.playback.stop() - $('#popupQueue').popup('close') toast('Loading...') tlid = tlid || $('#popupQueue').data('tlid') mopidy.playback.play({'tlid': parseInt(tlid)}) + $('#popupQueue').popup('close') }, /** ********************************* * remove a track from the queue * ***********************************/ removeTrack: function (tlid) { - $('#popupQueue').popup('close') toast('Deleting...') tlid = tlid || $('#popupQueue').data('tlid') mopidy.tracklist.remove({'tlid': [parseInt(tlid)]}) + $('#popupQueue').popup('close') }, clearQueue: function () { - mopidy.tracklist.clear().then( - resetSong() - ) + mopidy.tracklist.clear() return false }, - savePressed: function (key) { + checkDefaultButtonClick: function (key, parentElement) { + // Click the default button on parentElement when the user presses the enter key. if (key === 13) { - controls.saveQueue() - return false + $(parentElement).find('button' + '[data-default-btn="true"]').click() } return true }, + showInsertTrackPopup: function (tlid) { + $('#insertTrackInput').val('') + tlid = tlid || $('#popupQueue').data('tlid') + if (typeof tlid !== 'undefined' && tlid !== '') { + // Store the tlid of the track after which we want to perform the insert + $('#popupInsertTrack').data('tlid', $('#popupQueue').data('tlid')) + $('#popupInsertTrack').one('popupafterclose', function (event, ui) { + // Ensure that popup attributes are reset when the popup is closed. + $(this).removeData('tlid') + }) + } + $('#popupQueue').popup('close') + $('#popupInsertTrack').popup('open') + }, + + insertTrack: function (trackUri) { + if (typeof trackUri !== 'undefined' && trackUri !== '') { + var tlid = $('#popupInsertTrack').data('tlid') + if (typeof tlid !== 'undefined' && tlid !== '') { + mopidy.tracklist.index({tlid: parseInt(tlid)}).then(function (index) { + controls.playTracks(PLAY_NEXT, mopidy, trackUri, 'undefined', index) + }) + } else { + // No tlid provided, insert after current track. + controls.playTracks(PLAY_NEXT, mopidy, trackUri, 'undefined') + } + $('#popupInsertTrack').popup('close') + } + return false + }, + + showAddTrackPopup: function () { + $('#addTrackInput').val('') + $('#popupAddTrack').popup('open') + }, + + addTrack: function (trackUri) { + if (typeof trackUri !== 'undefined' && trackUri !== '') { + controls.playTracks(ADD_THIS_BOTTOM, mopidy, trackUri, 'undefined') + $('#popupAddTrack').popup('close') + } + return false + }, + showSavePopup: function () { mopidy.tracklist.getTracks().then(function (tracks) { if (tracks.length > 0) { diff --git a/mopidy_musicbox_webclient/static/js/gui.js b/mopidy_musicbox_webclient/static/js/gui.js index dec8810a..ea283ef0 100644 --- a/mopidy_musicbox_webclient/static/js/gui.js +++ b/mopidy_musicbox_webclient/static/js/gui.js @@ -148,13 +148,6 @@ function setSongInfo (data) { /** **************** * display popups * ******************/ -function closePopups () { - $('#popupTracks').popup('close') - $('#artistpopup').popup('close') - $('#coverpopup').popup('close') - $('#popupQueue').popup('close') -} - function popupTracks (e, listuri, trackuri, tlid) { if (!e) { e = window.event @@ -205,6 +198,7 @@ function popupTracks (e, listuri, trackuri, tlid) { popupName = '#popupTracks' } + // Set playlist, trackUri, and tlid of clicked item. if (typeof tlid !== 'undefined' && tlid !== '') { $(popupName).data('list', listuri).data('track', trackuri).data('tlid', tlid).popup('open', { x: e.pageX, @@ -217,6 +211,11 @@ function popupTracks (e, listuri, trackuri, tlid) { }) } + $(popupName).one('popupafterclose', function (event, ui) { + // Ensure that popup attributes are reset when the popup is closed. + $(this).removeData('list').removeData('track').removeData('tlid') + }) + return false } @@ -297,6 +296,12 @@ function initSocketevents () { mopidy.on('event:tracklistChanged', function (data) { library.getCurrentPlaylist() + mopidy.tracklist.getTracks().then(function (tracks) { + if (tracks.length === 0) { + // Last track in queue was deleted, reset UI. + resetSong() + } + }) }) mopidy.on('event:seeked', function (data) { diff --git a/mopidy_musicbox_webclient/static/mb.appcache b/mopidy_musicbox_webclient/static/mb.appcache index e0a2b3f0..08834284 100644 --- a/mopidy_musicbox_webclient/static/mb.appcache +++ b/mopidy_musicbox_webclient/static/mb.appcache @@ -1,6 +1,6 @@ CACHE MANIFEST -# 2016-12-11:v1 +# 2016-12-11:v2 NETWORK: * diff --git a/tests/js/dummy_tracklist.js b/tests/js/dummy_tracklist.js index 0401dba6..ee096ff1 100644 --- a/tests/js/dummy_tracklist.js +++ b/tests/js/dummy_tracklist.js @@ -93,7 +93,7 @@ // Always just assume that the second track is playing return $.when(1) } else { - return $.when(0) + return $.when(null) } } for (var i = 0; i < this._tlTracks.length; i++) { diff --git a/tests/js/test_controls.js b/tests/js/test_controls.js index 23a6825a..7a4e95ff 100644 --- a/tests/js/test_controls.js +++ b/tests/js/test_controls.js @@ -14,7 +14,7 @@ describe('controls', function () { var div_element var QUEUE_TRACKS = [ // Simulate an existing queue with three tracks loaded. {uri: 'track:tlTrackMock1'}, - {uri: 'track:tlTrackMock2'}, + {uri: 'track:tlTrackMock2'}, // <-- Currently playing track {uri: 'track:tlTrackMock3'} ] var NEW_TRACKS = [ // Simulate the user browsing to a folder with three tracks inside it. @@ -104,7 +104,7 @@ describe('controls', function () { it('PLAY_NOW, PLAY_NEXT, and ADD_THIS_BOTTOM should only add one track to the tracklist', function () { controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri) - assert(addSpy.calledWithMatch({at_position: 2, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NOW did not add correct track') + assert(addSpy.calledWithMatch({at_position: 1, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NOW did not add correct track') addSpy.reset() mopidy.tracklist.clear() @@ -133,21 +133,36 @@ describe('controls', function () { assert(addSpy.calledWithMatch({uris: getUris(NEW_TRACKS)}), 'ADD_ALL_BOTTOM did not add correct tracks') }) - it('PLAY_NOW and PLAY_NEXT should insert track after currently playing track', function () { + it('PLAY_NEXT should insert track after currently playing track by default', function () { + controls.playTracks(PLAY_NEXT, mopidy, NEW_TRACKS[0].uri) + assert(addSpy.calledWithMatch({at_position: 2, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NEXT did not insert track at correct position') + }) + + it('PLAY_NEXT should insert track after reference track index, if provided', function () { + controls.playTracks(PLAY_NEXT, mopidy, NEW_TRACKS[0].uri, 'undefined', 0) + assert(addSpy.calledWithMatch({at_position: 1, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NEXT did not insert track at correct position') + }) + + it('PLAY_NEXT should insert track even if queue is empty', function () { + mopidy.tracklist.clear() + controls.playTracks(PLAY_NEXT, mopidy, NEW_TRACKS[0].uri) + assert(addSpy.calledWithMatch({at_position: 0, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NEXT did not insert track at correct position') + }) + + it('PLAY_NOW should always insert track at current index', function () { controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri) - assert(addSpy.calledWithMatch({at_position: 2, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NOW did not insert track at correct position') + assert(addSpy.calledWithMatch({at_position: 1, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NOW did not insert track at correct position') addSpy.reset() mopidy.tracklist.clear() - mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)}) - controls.playTracks(PLAY_NEXT, mopidy, NEW_TRACKS[0].uri) - assert(addSpy.calledWithMatch({at_position: 2, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NEXT did not insert track at correct position') + controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri) + assert(addSpy.calledWithMatch({at_position: 0, uris: [NEW_TRACKS[0].uri]}), 'PLAY_NOW did not insert track at correct position') }) it('only PLAY_NOW and PLAY_ALL should trigger playback', function () { - controls.playTracks(PLAY_NOW, mopidy, 2) - assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[2].tlid}), 'PLAY_NOW did not start playback of correct track') + controls.playTracks(PLAY_NOW, mopidy) + assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[1].tlid}), 'PLAY_NOW did not start playback of correct track') mopidy.playback.play.reset() mopidy.tracklist.clear() @@ -182,12 +197,12 @@ describe('controls', function () { it('should store last action in cookie if on-track-click mode is set to "DYNAMIC"', function () { $(document.body).data('on-track-click', 'DYNAMIC') var cookieStub = sinon.stub($, 'cookie') - controls.playTracks(PLAY_NOW, mopidy, 2) + controls.playTracks(PLAY_NOW, mopidy) assert(cookieStub.calledWithMatch('onTrackClick', PLAY_NOW, {expires: 365})) cookieStub.reset() $(document.body).data('on-track-click', 'PLAY_NOW') - controls.playTracks(PLAY_NOW, mopidy, 2) + controls.playTracks(PLAY_NOW, mopidy) assert(cookieStub.notCalled) cookieStub.restore() })