Merge pull request #193 from jcass77/enhance/tracklist_popup_overhaul

Tracklist popup menu overhaul
This commit is contained in:
John Cass 2016-04-27 10:12:28 +02:00
commit fd7ad26b52
21 changed files with 1221 additions and 806 deletions

View File

@ -7,5 +7,6 @@
"indent": [2, 4, {"SwitchCase": 1}],
"no-undef": 0, // TODO: Set this to '2' once Javascript has been modularised.
"no-unused-vars": 0, // TODO: Set this to '2' once Javascript has been modularised.
"camelcase": 1,
}
}

View File

@ -79,12 +79,20 @@ v2.3.0 (UNRELEASED)
(Addresses: `#130 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/130>`_).
- Upgrade Media Progress Timer to version 3.0.0.
- Now retrieves album cover and artist images using MusicBrainzID, if available.
- New configuration parameter ``on_track_click`` can be used to customize the action that is performed when the
user clicks on a track in a list. Valid options are: ``PLAY_NOW``, ``PLAY_NEXT``, ``ADD_THIS_BOTTOM``,
``ADD_ALL_BOTTOM``, ``PLAY_ALL`` (default), and ``DYNAMIC`` (repeats last action).
(Addresses: `#133 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/133>`_).
- Optimized updating of 'now playing' icons in tracklists.
(Addresses: `#184 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/184>`_).
**Fixes**
- Don't create Mopidy models manually. (Fixes: `#172 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/172>`_).
- Context menu is now available for all tracks in browse pane. (Fixes: `#126 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/126>`_).
- last.fm artist image lookups should now always return the correct image for similarly named artists.
- Ensure that browsed tracks are always added to the queue using the track URI rather than the track's position in the folder.
(Fixes: `#124 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/124>`_).
v2.2.0 (2016-03-01)
-------------------

View File

@ -22,6 +22,13 @@ class Extension(ext.Extension):
schema['musicbox'] = config.Boolean(optional=True)
schema['websocket_host'] = config.Hostname(optional=True)
schema['websocket_port'] = config.Port(optional=True)
schema['on_track_click'] = config.String(optional=True,
choices=['PLAY_NOW',
'PLAY_NEXT',
'ADD_THIS_BOTTOM',
'ADD_ALL_BOTTOM',
'PLAY_ALL',
'DYNAMIC'])
return schema
def setup(self, registry):

View File

@ -3,3 +3,4 @@ enabled = true
musicbox = false
websocket_host =
websocket_port =
on_track_click = PLAY_ALL

View File

@ -244,8 +244,8 @@
}
.smalldivider {
font-size: 25% !important;
height: 5px !important;
font-size: 10%;
height: 2px;
background-color: #ddd !important;
}
@ -342,6 +342,11 @@
text-decoration: none;
}
.popupArtistLi,
.popupAlbumLi {
display: none
}
.popupArtistName,
.popupTrackName,
.popupAlbumName,
@ -361,7 +366,7 @@
margin-top: 10px;
}
#controlspopupimage,
#albumCoverImg,
#coverpopupimage,
#artistpopupimage {
display: block;
@ -372,16 +377,56 @@
max-height: 90%;
}
#popupTracksLv li,
#popupQueueLv li,
#popupBrowseLv li {
border-bottom: 1px solid #aaa;
/* Override to make buttons more visible in popups.*/
#popupTracks .ui-btn-up-c,
#popupQueue .ui-btn-up-c {
background: white;
}
#popupTracksLv,
#popupQueueLv,
#popupBrowseLv li {
border: 1px solid #aaa;
/* Custom icons for popup listviews - see http://demos.jquerymobile.com/1.3.2/#CustomIcons */
.ui-icon-playAll:after,
.ui-icon-play:after,
.ui-icon-playNext:after,
.ui-icon-add:after,
.ui-icon-addAll:after,
.ui-icon-remove:after {
color: #34495e;
font-family: 'FontAwesome';
}
.ui-icon-playAll:after {
content: '\f050';
}
.ui-icon-play:after {
content: '\f04b';
}
.ui-icon-playNext:after {
content: '\f149';
}
.ui-icon-add:after {
content: '\f196';
}
.ui-icon-addAll:after {
content: '\f0fe';
}
.ui-icon-remove:after {
content: '\f00d';
}
.ui-icon-playAll,
.ui-icon-play,
.ui-icon-playNext,
.ui-icon-add,
.ui-icon-addAll,
.ui-icon-remove {
background-color: unset;
background-image: none;
font-weight: normal;
}
.popupDialog {
@ -523,7 +568,7 @@ a {
overflow: hidden;
}
#controlspopupimage {
#albumCoverImg {
max-width: 90%;
max-height: 90%;
margin-bottom: 3px;

View File

@ -36,7 +36,7 @@
</head>
<body data-websocket-url="{{websocketUrl}}" data-is-musicbox="{{isMusicBox}}" data-has-alarmclock="{{hasAlarmClock}}">
<body data-websocket-url="{{websocketUrl}}" data-is-musicbox="{{isMusicBox}}" data-has-alarmclock="{{hasAlarmClock}}" data-on-track-click="{{onTrackClick}}">
<div data-role="page" id="page" class="ui-responsive-panel" data-theme="c">
<div data-role="panel" id="panel" data-position="left" data-theme="a" data-display="reveal" data-position-fixed="true">
@ -87,7 +87,7 @@
</li>
<li data-icon="false">
<div><!-- slider for volume -->
<a href="#" onclick="doMute(); return false;"><span title="Toggle mute"><i id="mutebt" class="fa fa-volume-up"></i></span></a>
<a href="#" onclick="controls.doMute(); return false;"><span title="Toggle mute"><i id="mutebt" class="fa fa-volume-up"></i></span></a>
<label for="volumeslider" class="ui-hidden-accessible">Volume</label>
<input id="volumeslider" data-highlight="true" name="volumeslider" data-mini="true" type="range" min="0"
value="0" max="100"/>
@ -112,47 +112,28 @@
<a href="#" onclick="closePopups();"><img id="artistpopupimage" src="" alt="Album artist"/></a>
</div>
<div data-role="popup" data-transition="none" data-theme="c" id="popupBrowse">
<div data-role="collapsible-set">
<ul data-role="listview" data-icon="false" id="popupBrowseLv">
<li>
<a href="#" onclick="return playBrowsedTracks(PLAY_ALL);">Play All</a>
</li>
<li>
<a href="#" onclick="return playBrowsedTracks(PLAY_NOW);">Play <span class="popupTrackName"></span></a>
</li>
<li class="addqueue">
<a href="#" onclick="return playBrowsedTracks(PLAY_NEXT);">Play Track Next</a>
</li>
<li class="addqueue">
<a href="#" onclick="return playBrowsedTracks(ADD_THIS_BOTTOM);">Add Track to Bottom of Queue</a>
</li>
<li class="addqueue">
<a href="#" onclick="return playBrowsedTracks(ADD_ALL_BOTTOM);">Add All to Bottom of Queue</a>
</li>
</ul>
</div>
</div>
<div data-role="popup" data-transition="none" data-theme="c" id="popupTracks">
<div data-role="popup" data-transition="none" data-theme="b" id="popupTracks">
<div data-role="collapsible-set">
<ul data-role="listview" data-icon="false" id="popupTracksLv">
<li>
<a href="#" onclick="return playTrack(PLAY_NOW);">Play <span class="popupTrackName"></span></a>
<li data-icon="playAll" data-iconshadow="false">
<a href="#" onclick="return controls.playTracks(PLAY_ALL, mopidy);">Play All</a>
</li>
<li class="addqueue">
<a href="#" onclick="return playTrack(PLAY_NEXT);">Play Track Next</a>
<li data-icon="play">
<a href="#" onclick="return controls.playTracks(PLAY_NOW, mopidy);">Play <span class="popupTrackName"></span></a>
</li>
<li class="addqueue">
<a href="#" onclick="return playTrack(ADD_THIS_BOTTOM);">Add Track to Bottom of Queue</a>
<li data-icon="playNext" class="addqueue">
<a href="#" onclick="return controls.playTracks(PLAY_NEXT, mopidy);">Play Track Next</a>
</li>
<li class="addqueue">
<a href="#" onclick="return playTrack(ADD_ALL_BOTTOM);" id="liaddtobottom">Add all to Bottom of Queue</a>
<li data-icon="add" class="addqueue">
<a href="#" onclick="return controls.playTracks(ADD_THIS_BOTTOM, mopidy);">Add Track to Bottom of Queue</a>
</li>
<li>
<li data-icon="addAll" class="addqueue">
<a href="#" onclick="return controls.playTracks(ADD_ALL_BOTTOM, mopidy);">Add All to Bottom of Queue</a>
</li>
<li class="popupAlbumLi">
<a href="#" onclick="showAlbumPopup('#popupTracks')">Show Album <span class="popupAlbumName"></span></a>
</li>
<li id="popupArtistsLi">
<li class="popupArtistsLi">
<a href="#" onclick="showArtist()" class="popupArtistHref">Show Artist <span class="popupArtistName"></span>
</a>
</li>
@ -164,19 +145,19 @@
</div>
</div>
<div data-role="popup" data-transition="none" data-theme="c" id="popupQueue">
<div data-role="popup" data-transition="none" data-theme="b" id="popupQueue">
<div data-role="collapsible-set">
<ul data-role="listview" data-icon="false" id="popupQueueLv">
<li>
<a href="#" onclick="return playTrackQueue();">Play <span class="popupTrackName"></span></a>
<li data-icon="play">
<a href="#" onclick="return controls.playQueueTrack();">Play <span class="popupTrackName"></span></a>
</li>
<li>
<a href="#" onclick="return removeTrack();">Remove from Queue</a>
<li data-icon="remove">
<a href="#" onclick="return controls.removeTrack();">Remove from Queue</a>
</li>
<li>
<li class="popupAlbumLi">
<a href="#" onclick="showAlbumPopup('#popupQueue')">Show Album <span class="popupAlbumName"></span></a>
</li>
<li id="popupArtistsLi">
<li class="popupArtistsLi">
<a href="#" onclick="showArtist()" class="popupArtistHref">Show Artist <span class="popupArtistName"></span>
</a>
</li>
@ -192,9 +173,9 @@
<form>
<p>Save current queue to a playlist.
<input id="saveinput" placeholder="Playlist name" class="span2" data-clear-btn="true"
onkeypress="return savePressed(event.keyCode);" type="text"/>
onkeypress="return controls.savePressed(event.keyCode);" type="text"/>
<div data-role="controlgroup" data-type="horizontal" align="center">
<button class="btn" type="button" onclick="return saveQueue();">
<button class="btn" type="button" onclick="return controls.saveQueue();">
Ok
</button>
<button class="btn" type="button" onclick="return $('#popupSave').popup('close');">
@ -289,7 +270,7 @@
<div id="nowPlayingpane" data-role="content" class="pane">
<img id="controlspopupimage" src="images/default_cover.png" alt="Album cover"/>
<img id="albumCoverImg" src="images/default_cover.png" alt="Album cover"/>
<div class="nowPlaying-artistInfo">
<h3 id="modalname"></h3>
@ -311,7 +292,7 @@
<h4>Playlists</h4>
</div>
<div align="right" class="ui-block-b" data-role="controlgroup" data-type="horizontal">
<button class="btn" type="button" title="Refresh playlists" onclick="return refreshPlaylists();">
<button class="btn" type="button" title="Refresh playlists" onclick="return controls.refreshPlaylists();">
<i class="fa fa-refresh"></i>
</button>
</div>
@ -343,11 +324,11 @@
<h4>Play Queue</h4>
</div>
<div align="right" class="ui-block-b" data-role="controlgroup" data-type="horizontal">
<button class="btn" type="button" title="Save queue to playlist" onclick="return showSavePopup();">
<i class="fa fa-floppy-o"></i>
<button class="btn" type="button" title="Save queue to playlist" onclick="return controls.showSavePopup();">
<i class="fa fa-bookmark-o"></i>
</button>
<button class="btn" type="button" title="Clear queue" onclick="return clearQueue();">
<i class="fa fa-trash-o"></i>
<button class="btn" type="button" title="Clear queue" onclick="return controls.clearQueue();">
<i class="fa fa-times"></i>
</button>
</div>
</div>
@ -426,18 +407,18 @@
<div class="ui-block-a" style="padding: 5px">
<form>
<p>Play a specific stream/track and optionally save it to your favourites.
<button class="btn" type="button" onclick="return getCurrentlyPlaying();">
<button class="btn" type="button" onclick="return controls.getCurrentlyPlaying();">
Get currently playing
</button>
<input id="streamuriinput" placeholder="URI" class="span2" data-clear-btn="true"
onkeypress="return streamPressed(event.keyCode);" type="text"/>
<button class="btn" type="button" onclick="return playStreamUri();">
onkeypress="return controls.streamPressed(event.keyCode);" type="text"/>
<button class="btn" type="button" onclick="return controls.playStreamUri();">
Play
</button>
<input id="streamnameinput" placeholder="Name" class="span2" data-clear-btn="true"
onkeypress="return streamPressed(event.keyCode);" type="text"/>
<button class="btn" type="button" onclick="return addFavourite();">
onkeypress="return controls.streamPressed(event.keyCode);" type="text"/>
<button class="btn" type="button" onclick="return controls.addFavourite();">
Save
</button>
<br/>
@ -459,7 +440,7 @@
</div>
</div>
<div class="playicon">
<a href="#" onclick="doPlay(); return false"><span id="btplay" title="Play"><i class="fa fa-play"></i></span></a>
<a href="#" onclick="controls.doPlay(); return false"><span id="btplay" title="Play"><i class="fa fa-play"></i></span></a>
</div>
</div>
</div>
@ -467,16 +448,16 @@
<div data-role="footer" data-tap-toggle="false" data-position="fixed" id="nowPlayingFooter">
<div class="footerControls" style="padding-left: 10px;">
<div style="float: left;">
<a href="#" onclick="doPrevious(); return false"><span id="btprev" title="Previous"><i class="fa fa-fast-backward"></i></span></a>
<a href="#" onclick="doPlay(); return false"><span id="btplayNowPlaying" title="Play"><i class="fa fa-play"></i></span></a>
<a href="#" onclick="doNext(); return false"><span id="btnext" title="Next"><i class="fa fa-fast-forward"></i></span></a>
<a href="#" onclick="controls.doPrevious(); return false"><span id="btprev" title="Previous"><i class="fa fa-fast-backward"></i></span></a>
<a href="#" onclick="controls.doPlay(); return false"><span id="btplayNowPlaying" title="Play"><i class="fa fa-play"></i></span></a>
<a href="#" onclick="controls.doNext(); return false"><span id="btnext" title="Next"><i class="fa fa-fast-forward"></i></span></a>
</div>
<div style="float: right; margin-right: 10px;">
<a href="#" onclick="doRandom(); return false"><span id="randombt" title="Random"><i class="fa fa-random"></i></span></a>
<a href="#" onclick="doRepeat(); return false"><span id="repeatbt" title="Repeat"><i class="fa fa-repeat"></i></span></a>
<a href="#" onclick="doConsume(); return false"><span id="consumebt" title="Consume"><i class="fa fa-cutlery"></i></span></a>
<a href="#" onclick="doSingle(); return false"><span id="singlebt" title="Single"><i class="fa fa-dot-circle-o"></i></span></a>
<a href="#" onclick="doShuffle(); return false"><span id="shufflebt" title="Shuffle"><i class="fa fa-arrows-v"></i></span></a>
<a href="#" onclick="controls.doRandom(); return false"><span id="randombt" title="Random"><i class="fa fa-random"></i></span></a>
<a href="#" onclick="controls.doRepeat(); return false"><span id="repeatbt" title="Repeat"><i class="fa fa-repeat"></i></span></a>
<a href="#" onclick="controls.doConsume(); return false"><span id="consumebt" title="Consume"><i class="fa fa-cutlery"></i></span></a>
<a href="#" onclick="controls.doSingle(); return false"><span id="singlebt" title="Single"><i class="fa fa-dot-circle-o"></i></span></a>
<a href="#" onclick="controls.doShuffle(); return false"><span id="shufflebt" title="Shuffle"><i class="fa fa-arrows-v"></i></span></a>
</div>
</div>
</div>

View File

@ -1,256 +1,233 @@
/** *********************************
* play tracks from a browse list *
***********************************/
function playBrowsedTracks (action, trackIndex) {
$('#popupBrowse').popup('close')
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory)
} else if (typeof module === 'object' && module.exports) {
module.exports = factory()
} else {
root.controls = factory()
}
}(this, function () {
'use strict'
var controls = {
/**
* 'onClick' handler for tracks that are rendered in a list.
*
* 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.
* ADD_THIS_BOTTOM: add track to bottom of tracklist.
* ADD_ALL_BOTTOM: add all tracks in in the list to bottom of
* tracklist.
* PLAY_ALL: clear tracklist and start playback of the track
* with URI provided in 'trackUri'.
* @param {object} mopidy - The Mopidy.js object that should be used to communicate with the
* Mopidy server.
* @param {string} trackUri - (Optional) The URI of the specific track that the action should
* be performed on. If no URI is provided then the 'data' attribute
* of the popup DIV is assumed to contain the track URI.
* @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.
*/
playTracks: function (action, mopidy, trackUri, playlistUri) {
$('#popupTracks').popup('close')
toast('Loading...')
if (typeof trackIndex === 'undefined') {
trackIndex = $('#popupBrowse').data('tlid')
trackUri = trackUri || $('#popupTracks').data('track')
if (typeof trackUri === 'undefined') {
throw new Error('No track URI provided for playback.')
}
playlistUri = playlistUri || $('#popupTracks').data('list')
if (typeof playlistUri === 'undefined') {
throw new Error('No playlist URI provided for playback.')
}
action = controls.getAction(action)
if (action === PLAY_ALL) {
mopidy.tracklist.clear()
// Default for radio streams is to just add the selected URI.
if (isStreamUri(browseTracks[trackIndex].uri)) {
action = PLAY_NOW
}
var trackUris = controls._getTrackURIsForAction(action, trackUri, playlistUri)
// Add the tracks and start playback if necessary.
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})
})
}
})
})
break
case ADD_THIS_BOTTOM:
case ADD_ALL_BOTTOM:
case PLAY_ALL:
mopidy.tracklist.add({uris: trackUris}).then(function (tlTracks) {
if (action === PLAY_ALL) { // Start playback of selected track immediately.
mopidy.tracklist.filter({uri: [trackUri]}).then(function (tlTracks) {
mopidy.playback.stop().then(function () {
mopidy.playback.play({tlid: tlTracks[0].tlid})
})
})
}
})
break
default:
throw new Error('Unexpected tracklist action identifier: ' + action)
}
if (window[$(document.body).data('on-track-click')] === DYNAMIC) {
// Save last 'action' - will become default for future 'onClick' events
var previousAction = $.cookie('onTrackClick')
if (typeof previousAction === 'undefined' || action !== previousAction) {
$.cookie('onTrackClick', action, { expires: 365 })
updatePlayIcons('', '', controls.getIconForAction(action))
}
}
},
/* Getter function for 'action' variable. Also checks config settings and cookies if required. */
getAction: function (action) {
if (typeof action === 'undefined' || action.length === 0) { // Action parameter not provided, use defaults
action = window[$(document.body).data('on-track-click')]
}
if (action === DYNAMIC) { // Re-use last action stored in cookie.
action = $.cookie('onTrackClick')
if (typeof action === 'undefined') {
action = PLAY_ALL // Backwards-compatible default value.
}
}
return action
},
/* Retrieves the Font Awesome character for the given action. */
getIconForAction: function (action) {
action = controls.getAction(action)
switch (parseInt(action)) {
case PLAY_ALL:
return 'fa fa-fast-forward'
case PLAY_NOW:
return 'fa fa-play'
case PLAY_NEXT:
return 'fa fa-level-down'
case ADD_THIS_BOTTOM:
return 'fa fa-plus-square-o'
case ADD_ALL_BOTTOM:
return 'fa fa-plus-square'
default:
throw new Error('Unkown tracklist action identifier: ' + action)
}
},
/* Retrieves the relevant track URIs for the given action. */
_getTrackURIsForAction: function (action, trackUri, playlistUri) {
var trackUris = []
switch (action) {
// Fill 'trackUris', by determining which tracks should be added.
switch (parseInt(action)) {
case PLAY_NOW:
case PLAY_NEXT:
case ADD_THIS_BOTTOM:
trackUris.push(browseTracks[trackIndex].uri)
// Process single track
trackUris.push(trackUri)
break
case PLAY_ALL:
case ADD_ALL_BOTTOM:
trackUris = getUris(browseTracks)
// Process all tracks in playlist
trackUris = getTracksFromUri(playlistUri, false)
break
default:
break
}
var maybePlay = function (tlTracks) {
if (action === PLAY_NOW || action === PLAY_ALL) {
var playIndex = (action === PLAY_ALL) ? trackIndex : 0
mopidy.playback.play({'tl_track': tlTracks[playIndex]})
}
throw new Error('Unexpected tracklist action identifier: ' + action)
}
return trackUris
},
switch (action) {
case PLAY_NOW:
case PLAY_NEXT:
mopidy.tracklist.index().then(function (currentIndex) {
mopidy.tracklist.add({'at_position': currentIndex + 1, 'uris': trackUris}).then(maybePlay)
})
break
case ADD_THIS_BOTTOM:
case ADD_ALL_BOTTOM:
case PLAY_ALL:
mopidy.tracklist.add({'uris': trackUris}).then(maybePlay)
break
default:
break
}
return false
}
/** *******************************
* play an uri from a tracklist *
*********************************/
function playTrack (action) {
var hash = document.location.hash.split('?')
var divid = hash[0].substr(1)
// Search page default click behaviour adds and plays selected track only.
if (action === PLAY_NOW && divid === 'search') {
action === PLAY_NOW_SEARCH
}
$('#popupTracks').popup('close')
$('#controlspopup').popup('close')
toast('Loading...')
playlisturi = $('#popupTracks').data('list')
uri = $('#popupTracks').data('track')
var trackUris = getTracksFromUri(playlisturi)
// find track that was selected
for (var selected = 0; selected < trackUris.length; selected++) {
if (trackUris[selected] === uri) {
break
}
}
switch (action) {
case ADD_THIS_BOTTOM:
case PLAY_NEXT:
case PLAY_NOW_SEARCH:
trackUris = [trackUris[selected]]
selected = 0
}
switch (action) {
case PLAY_NOW:
case PLAY_NOW_SEARCH:
mopidy.tracklist.clear().then(
mopidy.tracklist.add({'uris': trackUris}).then(
function (tlTracks) {
mopidy.playback.play({'tl_track': tlTracks[selected]})
}
)
)
break
case PLAY_NEXT:
mopidy.tracklist.index().then(function (currentIndex) {
mopidy.tracklist.add({'at_position': currentIndex + 1, 'uris': trackUris})
})
break
case ADD_THIS_BOTTOM:
case ADD_ALL_BOTTOM:
mopidy.tracklist.add({'uris': trackUris})
break
}
return false
}
/** *
* Plays a Track given by an URI from the given playlist URI.
* @param track_uri, playlist_uri
* @returns {boolean}
*/
function playTrackByUri (track_uri, playlist_uri) {
function findAndPlayTrack (tlTracks) {
if (tlTracks.length > 0) {
// Find track that was selected
for (var selected = 0; selected < tlTracks.length; selected++) {
if (tlTracks[selected].track.uri === track_uri) {
mopidy.playback.play({'tl_track': tlTracks[selected]})
return
}
}
}
console.error('Failed to find and play selected track ', track_uri)
return
}
// Stop directly, for user feedback
mopidy.tracklist.clear()
// this is deprecated, remove when popuptracks is removed completly
$('#popupTracks').popup('close')
$('#controlspopup').popup('close')
// end of deprecated
toast('Loading...')
mopidy.tracklist.add({'uris': [playlist_uri]}).then(function (tlTracks) {
// Can fail for all sorts of reasons. If so, just add individually.
if (tlTracks.length === 0) {
var trackUris = getTracksFromUri(playlist_uri, false)
mopidy.tracklist.add({'uris': trackUris}).then(findAndPlayTrack)
} else {
findAndPlayTrack(tlTracks)
}
})
return false
}
/** ******************************************************
/** ******************************************************
* play an uri from the queue
*********************************************************/
/** *
/** *
* Plays a Track from a Playlist.
* @param uri
* @param tlid
* @returns {boolean}
*/
function playTrackQueueByTlid (uri, tlid) {
// stop directly, for user feedback
playQueueTrack: function (tlid) {
// Stop directly, for user feedback
mopidy.playback.stop()
$('#popupQueue').popup('close')
toast('Loading...')
tlid = parseInt(tlid)
mopidy.tracklist.filter({
'tlid': [tlid]
}).then(
function (tlTracks) {
if (tlTracks.length > 0) {
mopidy.playback.play({'tl_track': tlTracks[0]})
return
}
console.log('Failed to play selected track ', tlid)
}
)
return false
}
tlid = tlid || $('#popupQueue').data('tlid')
mopidy.playback.play({'tlid': parseInt(tlid)})
},
/** *
* @deprecated
* @returns {boolean}
*/
function playTrackQueue () {
uri = $('#popupQueue').data('track')
tlid = $('#popupQueue').data('tlid')
return playTrackQueueByTlid(uri, tlid)
}
/** *********************************
/** *********************************
* remove a track from the queue *
***********************************/
function removeTrack () {
removeTrack: function (tlid) {
$('#popupQueue').popup('close')
toast('Deleting...')
tlid = parseInt($('#popupQueue').data('tlid'))
console.log(tlid)
mopidy.tracklist.remove({'tlid': [tlid]})
}
tlid = tlid || $('#popupQueue').data('tlid')
mopidy.tracklist.remove({'tlid': [parseInt(tlid)]})
},
function clearQueue () {
clearQueue: function () {
mopidy.tracklist.clear().then(
resetSong()
)
return false
}
},
function savePressed (key) {
savePressed: function (key) {
if (key === 13) {
saveQueue()
controls.saveQueue()
return false
}
return true
}
},
function showSavePopup () {
showSavePopup: function () {
mopidy.tracklist.getTracks().then(function (tracks) {
if (tracks.length > 0) {
$('#saveinput').val('')
$('#popupSave').popup('open')
}
})
}
},
function saveQueue () {
saveQueue: function () {
mopidy.tracklist.getTracks().then(function (tracks) {
var playlistName = $('#saveinput').val().trim()
if (playlistName !== null && playlistName !== '') {
getPlaylistByName(playlistName, 'm3u', false).then(function (exists) {
controls.getPlaylistByName(playlistName, 'm3u', false).then(function (exists) {
if (exists) {
$('#popupSave').popup('close')
$('#popupOverwrite').popup('open')
$('#overwriteConfirmBtn').click(function () {
initSave(playlistName, tracks)
controls.initSave(playlistName, tracks)
})
} else {
initSave(playlistName, tracks)
controls.initSave(playlistName, tracks)
}
})
}
})
return false
}
},
function initSave (playlistName, tracks) {
initSave: function (playlistName, tracks) {
$('#popupOverwrite').popup('close')
$('#popupSave').popup('close')
$('#saveinput').val('')
@ -259,29 +236,29 @@ function initSave (playlistName, tracks) {
playlist.tracks = tracks
mopidy.playlists.save({'playlist': playlist}).then()
})
}
},
function refreshPlaylists () {
refreshPlaylists: function () {
mopidy.playlists.refresh().then(function () {
playlists = {}
$('#playlisttracksdiv').hide()
$('#playlistslistdiv').show()
})
return false
}
},
/** ***********
/** ***********
* Buttons *
*************/
function doShuffle () {
doShuffle: function () {
mopidy.playback.stop()
mopidy.tracklist.shuffle()
mopidy.playback.play()
}
},
/* Toggle state of play button */
function setPlayState (nwplay) {
/* Toggle state of play button */
setPlayState: function (nwplay) {
if (nwplay) {
$('#btplayNowPlaying >i').removeClass('fa-play').addClass('fa-pause')
$('#btplayNowPlaying').attr('title', 'Pause')
@ -297,10 +274,10 @@ function setPlayState (nwplay) {
syncedProgressTimer.stop()
}
play = nwplay
}
},
// play or pause
function doPlay () {
// play or pause
doPlay: function () {
toast('Please wait...', 250)
if (!play) {
mopidy.playback.play()
@ -311,118 +288,118 @@ function doPlay () {
mopidy.playback.pause()
}
}
setPlayState(!play)
}
controls.setPlayState(!play)
},
function doPrevious () {
doPrevious: function () {
toast('Playing previous track...')
mopidy.playback.previous()
}
},
function doNext () {
doNext: function () {
toast('Playing next track...')
mopidy.playback.next()
}
},
function backbt () {
backbt: function () {
history.back()
return false
}
},
/** ***********
/** ***********
* Options *
*************/
function setTracklistOption (name, new_value) {
setTracklistOption: function (name, new_value) {
if (!new_value) {
$('#' + name + 'bt').attr('style', 'color:#2489ce')
} else {
$('#' + name + 'bt').attr('style', 'color:#66DD33')
}
return new_value
}
},
function setRepeat (nwrepeat) {
setRepeat: function (nwrepeat) {
if (repeat !== nwrepeat) {
repeat = setTracklistOption('repeat', nwrepeat)
repeat = controls.setTracklistOption('repeat', nwrepeat)
}
}
},
function setRandom (nwrandom) {
setRandom: function (nwrandom) {
if (random !== nwrandom) {
random = setTracklistOption('random', nwrandom)
random = controls.setTracklistOption('random', nwrandom)
}
}
},
function setConsume (nwconsume) {
setConsume: function (nwconsume) {
if (consume !== nwconsume) {
consume = setTracklistOption('consume', nwconsume)
consume = controls.setTracklistOption('consume', nwconsume)
}
}
},
function setSingle (nwsingle) {
setSingle: function (nwsingle) {
if (single !== nwsingle) {
single = setTracklistOption('single', nwsingle)
single = controls.setTracklistOption('single', nwsingle)
}
}
},
function doRandom () {
doRandom: function () {
mopidy.tracklist.setRandom({'value': !random}).then()
}
},
function doRepeat () {
doRepeat: function () {
mopidy.tracklist.setRepeat({'value': !repeat}).then()
}
},
function doConsume () {
doConsume: function () {
mopidy.tracklist.setConsume({'value': !consume}).then()
}
},
function doSingle () {
doSingle: function () {
mopidy.tracklist.setSingle({'value': !single}).then()
}
},
/** *********************************************
/** *********************************************
* Track Slider *
* Use a timer to prevent looping of commands *
***********************************************/
function doSeekPos (value) {
doSeekPos: function (value) {
if (!positionChanging) {
positionChanging = value
mopidy.playback.seek({'time_position': Math.round(value)}).then(function () {
positionChanging = null
})
}
}
},
function setPosition (pos) {
setPosition: function (pos) {
if (!positionChanging && $('#trackslider').val() !== pos) {
syncedProgressTimer.set(pos)
}
}
},
/** *********************************************
/** *********************************************
* Volume slider *
* Use a timer to prevent looping of commands *
***********************************************/
function setVolume (value) {
setVolume: function (value) {
if (!volumeChanging && !volumeSliding && $('#volumeslider').val() !== value) {
$('#volumeslider').off('change')
$('#volumeslider').val(value).slider('refresh')
$('#volumeslider').on('change', function () { doVolume($(this).val()) })
$('#volumeslider').on('change', function () { controls.doVolume($(this).val()) })
}
}
},
function doVolume (value) {
doVolume: function (value) {
if (!volumeChanging) {
volumeChanging = value
mopidy.playback.setVolume({'volume': parseInt(volumeChanging)}).then(function () {
volumeChanging = null
})
}
}
},
function setMute (nwmute) {
setMute: function (nwmute) {
if (mute !== nwmute) {
mute = nwmute
if (mute) {
@ -431,24 +408,24 @@ function setMute (nwmute) {
$('#mutebt').attr('class', 'fa fa-volume-up')
}
}
}
},
function doMute () {
doMute: function () {
mopidy.mixer.setMute({'mute': !mute})
}
},
/** **********
/** **********
* Stream *
************/
function streamPressed (key) {
streamPressed: function (key) {
if (key === 13) {
playStreamUri()
controls.playStreamUri()
return false
}
return true
}
},
function playStreamUri (uri) {
playStreamUri: function (uri) {
// value of name is based on the passing of an uri as a parameter or not
var nwuri = uri || $('#streamuriinput').val().trim()
var service = $('#selectstreamservice').val()
@ -461,7 +438,7 @@ function playStreamUri (uri) {
mopidy.playback.stop()
// hide ios/android keyboard
document.activeElement.blur()
clearQueue()
controls.clearQueue()
$('input').blur()
mopidy.tracklist.add({'uris': [nwuri]})
mopidy.playback.play()
@ -469,9 +446,9 @@ function playStreamUri (uri) {
toast('No valid url!')
}
return false
}
},
function getCurrentlyPlaying () {
getCurrentlyPlaying: function () {
$('#streamuriinput').val(songdata.track.uri)
var name = songdata.track.name
if (songdata.track.artists) {
@ -482,18 +459,18 @@ function getCurrentlyPlaying () {
}
$('#streamnameinput').val(name)
return true
}
},
function getUriSchemes () {
getUriSchemes: function () {
uriSchemes = {}
return mopidy.getUriSchemes().then(function (schemes) {
for (var i = 0; i < schemes.length; i++) {
uriSchemes[schemes[i].toLowerCase()] = true
}
})
}
},
function getPlaylistByName (name, scheme, create) {
getPlaylistByName: function (name, scheme, create) {
var uri_scheme = scheme || ''
var uri = ''
if (uri_scheme && !uriSchemes[uri_scheme]) {
@ -514,28 +491,28 @@ function getPlaylistByName (name, scheme, create) {
console.log("Can't find playist '%s", name)
return Mopidy.when(false)
})
}
},
function getPlaylistFull (uri) {
getPlaylistFull: function (uri) {
return mopidy.playlists.lookup({'uri': uri}).then(function (pl) {
playlists[uri] = pl
return pl
})
}
},
function getFavourites () {
return getPlaylistByName(STREAMS_PLAYLIST_NAME,
getFavourites: function () {
return controls.getPlaylistByName(STREAMS_PLAYLIST_NAME,
STREAMS_PLAYLIST_SCHEME,
true).then(function (playlist) {
if (playlist) {
return getPlaylistFull(playlist.uri)
return controls.getPlaylistFull(playlist.uri)
}
return Mopidy.when(false)
})
}
},
function addToFavourites (newTracks) {
getFavourites().catch(console.error.bind(console)).then(function (favourites) {
addToFavourites: function (newTracks) {
controls.getFavourites().catch(console.error.bind(console)).then(function (favourites) {
if (favourites) {
if (favourites.tracks) {
Array.prototype.push.apply(favourites.tracks, newTracks)
@ -543,13 +520,13 @@ function addToFavourites (newTracks) {
favourites.tracks = newTracks
}
mopidy.playlists.save({'playlist': favourites}).then(function (s) {
showFavourites()
controls.showFavourites()
})
}
})
}
},
function addFavourite (uri, name) {
addFavourite: function (uri, name) {
uri = uri || $('#streamuriinput').val().trim()
name = name || $('#streamnameinput').val().trim()
mopidy.library.lookup({'uris': [uri]}).then(function (results) {
@ -559,7 +536,7 @@ function addFavourite (uri, name) {
if (name) {
newTracks[0].name = name // User overrides name.
}
addToFavourites(newTracks)
controls.addToFavourites(newTracks)
} else {
if (newTracks.length === 0) {
console.log('No tracks to add')
@ -568,25 +545,25 @@ function addFavourite (uri, name) {
}
}
})
}
},
function deleteFavourite (index) {
getFavourites().then(function (favourites) {
deleteFavourite: function (index) {
controls.getFavourites().then(function (favourites) {
if (favourites && favourites.tracks && index < favourites.tracks.length) {
var name = favourites.tracks[index].name
if (confirm("Are you sure you want to remove '" + name + "'?")) {
favourites.tracks.splice(index, 1)
mopidy.playlists.save({'playlist': favourites}).then(function (s) {
showFavourites()
controls.showFavourites()
})
}
}
})
}
},
function showFavourites () {
showFavourites: function () {
$('#streamuristable').empty()
getFavourites().then(function (favourites) {
controls.getFavourites().then(function (favourites) {
if (!favourites) {
return
}
@ -594,24 +571,24 @@ function showFavourites () {
$.cookie.json = true
if ($.cookie('streamUris')) {
tmp = '<button class="btn" style="padding: 5px; width: 100%" type="button" onclick="return upgradeStreamUrisToFavourites();">Convert StreamUris</button>'
tmp = '<button class="btn" style="padding: 5px; width: 100%" type="button" onclick="return controls.upgradeStreamUrisToFavourites();">Convert StreamUris</button>'
}
if (favourites.tracks) {
var child = ''
for (var i = 0; i < favourites.tracks.length; i++) {
child = '<li><span class="ui-icon ui-icon-delete ui-icon-shadow" style="float:right; margin: .5em; margin-top: .8em;"><a href="#" onclick="return deleteFavourite(\'' + i + '\');">&nbsp;</a></span>' +
child = '<li><span class="ui-icon ui-icon-delete ui-icon-shadow" style="float:right; margin: .5em; margin-top: .8em;"><a href="#" onclick="return controls.deleteFavourite(\'' + i + '\');">&nbsp;</a></span>' +
'<i class="fa fa-rss" style="float: left; padding: .5em; padding-top: 1em;"></i>' +
' <a style="margin-left: 20px" href="#" onclick="return playStreamUri(\'' + favourites.tracks[i].uri + '\');">'
' <a style="margin-left: 20px" href="#" onclick="return controls.playStreamUri(\'' + favourites.tracks[i].uri + '\');">'
child += '<h1>' + favourites.tracks[i].name + '</h1></a></li>'
tmp += child
}
}
$('#streamuristable').html(tmp)
})
}
},
// TODO: Remove this upgrade path in next major release.
function upgradeStreamUrisToFavourites () {
// TODO: Remove this upgrade path in next major release.
upgradeStreamUrisToFavourites: function () {
toast('Converting streamUris...')
$.cookie.json = true
var streamUris = $.cookie('streamUris') // Read the cookie.
@ -637,27 +614,31 @@ function upgradeStreamUrisToFavourites () {
}
}
}
addToFavourites(tracks)
controls.addToFavourites(tracks)
$.cookie('streamUris', null) // Delete the cookie now we're done.
console.log(tracks.length + ' streamUris added to favourites')
})
} else {
console.log('No streamUris cookie found')
}
}
},
function haltSystem () {
haltSystem: function () {
$.post('/settings/shutdown')
toast('Stopping system...', 10000)
setTimeout(function () {
window.history.back()
}, 10000)
}
},
function rebootSystem () {
rebootSystem: function () {
$.post('/settings/reboot')
toast('Rebooting...', 10000)
setTimeout(function () {
window.history.back()
}, 10000)
}
}
}
return controls
}))

View File

@ -20,7 +20,7 @@ var volumeSliding = false
var positionChanging
var initgui = true
var popupData = {}
var popupData = {} // TODO: Refactor into one shared cache
var songlength = 0
var artistshtml = ''
@ -36,12 +36,11 @@ var STREAMS_PLAYLIST_SCHEME = 'm3u'
var uriSchemes = {}
// array of cached playlists (not only user-playlists, also search, artist, album-playlists)
var playlists = {}
var playlists = {} // TODO: Refactor into one shared cache
var currentplaylist
var customTracklists = []
var customTracklists = [] // TODO: Refactor into one shared cache
var browseStack = []
var browseTracks = []
var ua = navigator.userAgent
var isMobileSafari = /Mac/.test(ua) && /Mobile/.test(ua)
@ -68,9 +67,7 @@ PLAY_NEXT = 1
ADD_THIS_BOTTOM = 2
ADD_ALL_BOTTOM = 3
PLAY_ALL = 4
PLAY_NOW_SEARCH = 5
MAX_TABLEROWS = 50
DYNAMIC = 5
// the first part of Mopidy extensions which serve radio streams
var radioExtensionsList = ['somafm', 'tunein', 'dirble', 'audioaddict']
@ -185,9 +182,9 @@ function albumTracksToTable (pl, target, uri) {
nextTrack = i < pl.length - 1 ? pl[i + 1] : undefined
track = pl[i]
popupData[track.uri] = track
renderSongLi(previousTrack, track, nextTrack, uri, '', ALBUM_TABLE, i, pl.length)
renderSongLi(previousTrack, track, nextTrack, uri, '', target, i, pl.length)
}
updatePlayIcons(songdata.track.uri, songdata.tlid)
updatePlayIcons(songdata.track.uri, songdata.tlid, controls.getIconForAction())
}
function renderSongLi (previousTrack, track, nextTrack, uri, tlid, target, currentIndex, listLength) {
@ -204,16 +201,14 @@ function renderSongLi (previousTrack, track, nextTrack, uri, tlid, target, curre
$(target).append('<li class="albumli"><a href="#"><h1><i class="' + getMediaClass(track.uri) + '"></i> ' + track.name + ' [Stream]</h1></a></li>')
return
}
// Play by tlid if available.
// TODO: Need to consolidate all of the 'play...' functions
if (tlid && target === BROWSE_TABLE) {
onClick = 'return playBrowsedTracks(PLAY_ALL, ' + tlid + ');'
} else if (tlid) {
if (target === CURRENT_PLAYLIST_TABLE && typeof tlid === 'number' && tlid >= 0) { // Current queue: Show popup menu icon. onClick plays track.
tlidParameter = '\',\'' + tlid
onClick = 'return playTrackQueueByTlid(\'' + track.uri + '\',\'' + tlid + '\');'
} else {
onClick = 'return playTrackByUri(\'' + track.uri + '\',\'' + uri + '\');'
onClick = 'return controls.playQueueTrack(' + tlid + ');'
} else { // All other tracklist: Show default action icon. onClick performs default action
onClick = 'return controls.playTracks(\'\', mopidy, \'' + track.uri + '\', \'' + uri + '\');'
}
$(target).append(
'<li class="song albumli" id="' + getjQueryID(target, track.uri) + '" tlid="' + tlid + '">' +
'<a href="#" class="moreBtn" onclick="return popupTracks(event, \'' + uri + '\',\'' + track.uri + tlidParameter + '\');">' +
@ -223,8 +218,8 @@ function renderSongLi (previousTrack, track, nextTrack, uri, tlid, target, curre
if (listLength === 1 || !hasSameAlbum(previousTrack, track) && !hasSameAlbum(track, nextTrack)) {
renderSongLiAlbumInfo(track, target)
}
// TODO: remove this hard-coded condition for 'ALBUM_TABLE'
if (target !== ALBUM_TABLE && !hasSameAlbum(previousTrack, track)) {
// TODO: remove this hard-coded conditions for 'ALBUM_TABLE' and 'BROWSE_TABLE'
if (target !== ALBUM_TABLE && target !== BROWSE_TABLE && !hasSameAlbum(previousTrack, track)) {
// Starting to render a new album in the list.
renderSongLiDivider(track, nextTrack, currentIndex, target)
}
@ -339,7 +334,7 @@ function resultsToTables (results, target, uri, onClickBack, backIsOptional) {
renderSongLi(previousTrack, track, nextTrack, uri, tlid, target, i, results.length)
}
}
updatePlayIcons(songdata.track.uri, songdata.tlid)
updatePlayIcons(songdata.track.uri, songdata.tlid, controls.getIconForAction())
}
// process updated playlist to gui
@ -397,7 +392,7 @@ function getUris (tracks) {
function getTracksFromUri (uri, full_track_data) {
var returnTracksOrUris = function (tracks) {
return (full_track_data || false) ? tracks : getUris(tracks)
return full_track_data ? tracks : getUris(tracks)
}
if (customTracklists[uri]) {
return returnTracksOrUris(customTracklists[uri])
@ -552,7 +547,7 @@ function getjQueryID (identifier, uri, includePrefix) {
} else if (identifier.charAt(0) !== '#' && includePrefix) {
identifier = '#' + identifier
}
return identifier + '-' + fixedEncodeURIComponent(uri).replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '')
return identifier + '-' + fixedEncodeURIComponent(uri).replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '') // eslint-disable-line no-useless-escape
}
// Strict URI encoding as per https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent

View File

@ -7,8 +7,8 @@
* Song Info Sreen *
********************/
function resetSong () {
setPlayState(false)
setPosition(0)
controls.setPlayState(false)
controls.setPosition(0)
var data = {}
data.tlid = -1
data.track = {}
@ -84,7 +84,7 @@ function setSongInfo (data) {
data.track.name = decodeURI(name[name.length - 1])
}
updatePlayIcons(data.track.uri, data.tlid)
updatePlayIcons(data.track.uri, data.tlid, controls.getIconForAction())
artistshtml = ''
artiststext = ''
@ -131,7 +131,7 @@ function setSongInfo (data) {
} else {
$('#modalalbum').html('')
}
images.setAlbumImage(data.track.uri, '#infocover, #controlspopupimage', mopidy)
images.setAlbumImage(data.track.uri, '#infocover, #albumCoverImg', mopidy)
$('#modalartist').html(arttmp)
@ -153,7 +153,6 @@ function closePopups () {
$('#artistpopup').popup('close')
$('#coverpopup').popup('close')
$('#popupQueue').popup('close')
$('#controlspopup').popup('close')
}
function popupTracks (e, listuri, trackuri, tlid) {
@ -163,6 +162,9 @@ function popupTracks (e, listuri, trackuri, tlid) {
$('.popupTrackName').html(popupData[trackuri].name)
if (popupData[trackuri].album && popupData[trackuri].album.name) {
$('.popupAlbumName').html(popupData[trackuri].album.name)
$('.popupAlbumLi').show()
} else {
$('.popupAlbumLi').hide()
}
var child = ''
@ -194,9 +196,6 @@ function popupTracks (e, listuri, trackuri, tlid) {
if (divid === 'current') {
$('.addqueue').hide()
popupName = '#popupQueue'
} else if (divid === 'browse') {
$('.addqueue').show()
popupName = '#popupBrowse'
} else {
$('.addqueue').show()
popupName = '#popupTracks'
@ -232,8 +231,8 @@ function initSocketevents () {
library.getCurrentPlaylist()
updateStatusOfAll()
library.getPlaylists()
getUriSchemes().then(function () {
showFavourites()
controls.getUriSchemes().then(function () {
controls.showFavourites()
})
library.getBrowseDir()
library.getSearchSchemes(searchBlacklist, mopidy)
@ -250,7 +249,7 @@ function initSocketevents () {
mopidy.on('event:trackPlaybackStarted', function (data) {
setSongInfo(data.tl_track)
setPlayState(true)
controls.setPlayState(true)
})
mopidy.on('event:playlistsLoaded', function (data) {
@ -273,21 +272,21 @@ function initSocketevents () {
})
mopidy.on('event:volumeChanged', function (data) {
setVolume(data.volume)
controls.setVolume(data.volume)
})
mopidy.on('event:muteChanged', function (data) {
setMute(data.mute)
controls.setMute(data.mute)
})
mopidy.on('event:playbackStateChanged', function (data) {
switch (data.new_state) {
case 'paused':
case 'stopped':
setPlayState(false)
controls.setPlayState(false)
break
case 'playing':
setPlayState(true)
controls.setPlayState(true)
break
}
})
@ -297,7 +296,7 @@ function initSocketevents () {
})
mopidy.on('event:seeked', function (data) {
setPosition(parseInt(data.time_position))
controls.setPosition(parseInt(data.time_position))
if (play) {
syncedProgressTimer.start()
}
@ -420,9 +419,7 @@ function locationHashChanged () {
case 'search':
$('#navsearch a').addClass($.mobile.activeBtnClass)
$('#searchinput').focus()
if (customTracklists['mbw:allresultscache'] === '') {
library.initSearch($('#searchinput').val())
}
break
case 'stream':
$('#navstream a').addClass('ui-state-active ui-state-persist ui-btn-active')
@ -519,7 +516,7 @@ $(document).ready(function (event) {
$('#songinfo').click(function () {
return switchContent('nowPlaying')
})
$('#controlspopupimage').click(function () {
$('#albumCoverImg').click(function () {
return switchContent('current')
})
$('#navToggleFullscreen').click(function () {
@ -559,15 +556,15 @@ $(document).ready(function (event) {
var actualkey = String.fromCharCode(unicode)
switch (actualkey) {
case ' ':
doPlay()
controls.doPlay()
event.preventDefault()
break
case '>':
doNext()
controls.doNext()
event.preventDefault()
break
case '<':
doPrevious()
controls.doPrevious()
event.preventDefault()
break
}
@ -586,8 +583,8 @@ $(document).ready(function (event) {
$.event.special.swipe.durationThreshold = 500
// swipe songinfo and panel
$('#normalFooter, #nowPlayingFooter').on('swiperight', doPrevious)
$('#normalFooter, #nowPlayingFooter').on('swipeleft', doNext)
$('#normalFooter, #nowPlayingFooter').on('swiperight', controls.doPrevious)
$('#normalFooter, #nowPlayingFooter').on('swipeleft', controls.doNext)
$('#nowPlayingpane, .ui-body-c, #header, #panel, .pane').on('swiperight', function (event) {
if (!$(event.target).is('#normalFooter') && !$(event.target).is('#nowPlayingFooter')) {
$('#panel').panel('open')
@ -609,38 +606,56 @@ $(document).ready(function (event) {
$('#trackslider').on('slidestop', function () {
$('#trackslider').off('change')
syncedProgressTimer.updatePosition($(this).val())
doSeekPos($(this).val())
controls.doSeekPos($(this).val())
})
$('#volumeslider').on('slidestart', function () { volumeSliding = true })
$('#volumeslider').on('slidestop', function () { volumeSliding = false })
$('#volumeslider').on('change', function () { doVolume($(this).val()) })
$('#volumeslider').on('change', function () { controls.doVolume($(this).val()) })
})
function updatePlayIcons (uri, tlid) {
function updatePlayIcons (uri, tlid, popupMenuIcon) {
// Update styles of listviews
if (arguments.length < 3) {
throw new Error('Missing parameters for "updatePlayIcons" function call.')
}
var listviews = [PLAYLIST_TABLE, SEARCH_TRACK_TABLE, ARTIST_TABLE, ALBUM_TABLE, BROWSE_TABLE]
var target = CURRENT_PLAYLIST_TABLE.substr(1)
$(CURRENT_PLAYLIST_TABLE).children('li').each(function () {
if (uri && typeof tlid === 'number' && tlid >= 0) {
$(CURRENT_PLAYLIST_TABLE).children('li.song.albumli').each(function () {
var eachTlid = $(this).attr('tlid')
if (typeof eachTlid !== 'undefined') {
eachTlid = parseInt(eachTlid)
}
if (this.id === getjQueryID(target, uri) && eachTlid === tlid) {
if (!$(this).hasClass('currenttrack')) {
$(this).addClass('currenttrack')
} else {
}
} else if ($(this).hasClass('currenttrack')) {
$(this).removeClass('currenttrack')
}
})
}
var popupElement
for (var i = 0; i < listviews.length; i++) {
target = listviews[i].substr(1)
$(listviews[i]).children('li').each(function () {
$(listviews[i]).children('li.song.albumli').each(function () {
if (uri) {
if (this.id === getjQueryID(target, uri)) {
$(this).addClass('currenttrack2')
} else {
$(this).removeClass('currenttrack2')
}
}
if (popupMenuIcon) {
popupElement = $(this).children('a.moreBtn').children('i').first()
if (!popupElement.hasClass(popupMenuIcon)) {
popupElement.removeClass()
popupElement.addClass(popupMenuIcon)
}
}
})
}
}

View File

@ -37,9 +37,6 @@
document.activeElement.blur()
$('input').blur()
delete customTracklists[URI_SCHEME + ':allresultscache']
delete customTracklists[URI_SCHEME + ':artistresultscache']
delete customTracklists[URI_SCHEME + ':albumresultscache']
delete customTracklists[URI_SCHEME + ':trackresultscache']
$('#searchartists').hide()
$('#searchalbums').hide()
@ -258,7 +255,7 @@
resultsToTables(tracks, PLAYLIST_TABLE, uri, 'return library.togglePlaylists();', true)
showLoading(false)
})
updatePlayIcons(uri)
updatePlayIcons(uri, '', controls.getIconForAction())
$('#playlistslist li a').each(function () {
$(this).removeClass('playlistactive')
if (this.id === uri) {

View File

@ -16,49 +16,49 @@ function processCurrenttrack (data) {
* process results of volume
*********************************************************/
function processVolume (data) {
setVolume(data)
controls.setVolume(data)
}
/** ******************************************************
* process results of mute
*********************************************************/
function processMute (data) {
setMute(data)
controls.setMute(data)
}
/** ******************************************************
* process results of a repeat
*********************************************************/
function processRepeat (data) {
setRepeat(data)
controls.setRepeat(data)
}
/** ******************************************************
* process results of random
*********************************************************/
function processRandom (data) {
setRandom(data)
controls.setRandom(data)
}
/** ******************************************************
* process results of consume
*********************************************************/
function processConsume (data) {
setConsume(data)
controls.setConsume(data)
}
/** ******************************************************
* process results of single
*********************************************************/
function processSingle (data) {
setSingle(data)
controls.setSingle(data)
}
/** ******************************************************
* process results of current position
*********************************************************/
function processCurrentposition (data) {
setPosition(parseInt(data))
controls.setPosition(parseInt(data))
}
/** ******************************************************
@ -66,9 +66,9 @@ function processCurrentposition (data) {
*********************************************************/
function processPlaystate (data) {
if (data === 'playing') {
setPlayState(true)
controls.setPlayState(true)
} else {
setPlayState(false)
controls.setPlayState(false)
}
}
@ -84,26 +84,24 @@ function processBrowseDir (resultArr) {
showLoading(false)
return
}
browseTracks = []
uris = []
var ref, track, previousTrack, nextTrack
var ref, previousRef, nextRef
var uri = resultArr[0].uri
var length = 0 || resultArr.length
customTracklists[BROWSE_TABLE] = []
for (var i = 0, index = 0; i < resultArr.length; i++) {
if (resultArr[i].type === 'track') {
previousRef = ref || undefined
nextRef = i < resultArr.length - 1 ? resultArr[i + 1] : undefined
ref = resultArr[i]
// TODO: consolidate usage of various arrays for caching URIs, Refs, and Tracks
popupData[ref.uri] = ref
browseTracks.push(ref)
customTracklists[BROWSE_TABLE].push(ref)
uris.push(ref.uri)
$(BROWSE_TABLE).append(
'<li class="song albumli" id="' + getjQueryID(BROWSE_TABLE, ref.uri) + '">' +
'<a href="#" class="moreBtn" onclick="return popupTracks(event, \'' + uri + '\', \'' + ref.uri + '\', \'' + index + '\');">' +
'<i class="fa fa-ellipsis-v"></i></a>' +
'<a href="#" class="browsetrack" onclick="return playBrowsedTracks(PLAY_ALL, ' + index + ');">' +
'<h1><i></i> ' + ref.name + '</h1></a></li>'
)
renderSongLi(previousRef, ref, nextRef, BROWSE_TABLE, '', BROWSE_TABLE, index, resultArr.length)
index++
} else {
var iconClass = ''
@ -119,7 +117,7 @@ function processBrowseDir (resultArr) {
}
}
updatePlayIcons(songdata.track.uri, songdata.tlid)
updatePlayIcons(songdata.track.uri, songdata.tlid, controls.getIconForAction())
if (uris.length > 0) {
mopidy.library.lookup({'uris': uris}).then(function (resultDict) {
@ -134,6 +132,7 @@ function processBrowseDir (resultArr) {
nextTrack = undefined
}
track = resultDict[ref.uri][0]
popupData[track.uri] = track // Need full track info in popups in order to display albums and artists.
if (uris.length === 1 || (previousTrack && !hasSameAlbum(previousTrack, track) && !hasSameAlbum(track, nextTrack))) {
renderSongLiAlbumInfo(track, BROWSE_TABLE)
}
@ -211,7 +210,7 @@ function processCurrentPlaylist (resultArr) {
currentplaylist = resultArr
resultsToTables(currentplaylist, CURRENT_PLAYLIST_TABLE)
mopidy.playback.getCurrentTlTrack().then(processCurrenttrack, console.error)
updatePlayIcons(songdata.track.uri, songdata.tlid)
updatePlayIcons(songdata.track.uri, songdata.tlid, controls.getIconForAction())
}
/** ******************************************************

View File

@ -1,6 +1,6 @@
CACHE MANIFEST
# 2016-04-12:v1
# 2016-04-26:v2
NETWORK:
*

View File

@ -23,8 +23,8 @@
<div data-role="content" data-theme="b">
<h3>System</h3>
<a href="#" onclick="haltSystem(); return false;" data-role="button" data-rel="dialog" data-transition="slidedown" data-theme="b">Shutdown</a>
<a href="#" onclick="rebootSystem(); return false;" data-role="button" data-rel="dialog" data-transition="slidedown" data-theme="b">Reboot</a>
<a href="#" onclick="controls.haltSystem(); return false;" data-role="button" data-rel="dialog" data-transition="slidedown" data-theme="b">Shutdown</a>
<a href="#" onclick="controls.rebootSystem(); return false;" data-role="button" data-rel="dialog" data-transition="slidedown" data-theme="b">Reboot</a>
<a href="index.html" data-role="button" data-rel="back" data-theme="a">Cancel</a>
</div>
</div>

View File

@ -37,6 +37,7 @@ class IndexHandler(tornado.web.RequestHandler):
'isMusicBox': json.dumps(webclient.is_music_box()),
'websocketUrl': webclient.get_websocket_url(self.request),
'hasAlarmClock': json.dumps(webclient.has_alarm_clock()),
'onTrackClick': webclient.get_default_click_action()
}
self.__path = path
self.__title = string.Template('MusicBox on $hostname')

View File

@ -44,3 +44,6 @@ class Webclient(object):
def is_music_box(self):
return self.ext_config.get('musicbox', False)
def get_default_click_action(self):
return self.ext_config.get('on_track_click', 'PLAY_ALL')

108
tests/js/dummy_tracklist.js Normal file
View File

@ -0,0 +1,108 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory)
} else if (typeof module === 'object' && module.exports) {
module.exports = factory()
} else {
root.DummyTracklist = factory()
}
}(this, function () {
'use strict'
/* A dummy tracklist with partial support for mocking mopidy.core.TracklistController.
*
* Returns resolved promises to simulate functionality of Mopidy.js.
*/
function DummyTracklist () {
if (!(this instanceof DummyTracklist)) {
return new DummyTracklist()
}
this._tlTracks = []
this._nextTlid = 1
return this
}
/* Add tracks to the tracklist. params.uris should contain an array of strings for the URIs to be added. */
DummyTracklist.prototype.add = function (params) {
if (!params || !params.uris) {
throw new Error('No tracks provided to add.')
}
if (params.tracks || params.uri) {
throw new Error('DummyTracklist.add does not support deprecated "tracks" and "uri" parameters.')
}
// Add tracks to end of tracklist if no position is provided
params.at_position = params.at_position || this._tlTracks.length
var tlTrack
var tlTracks = []
for (var i = 0; i < params.uris.length; i++) {
tlTrack = {
tlid: this._nextTlid++,
track: {
uri: params.uris[i]
}
}
tlTracks.push(tlTrack)
this._tlTracks.splice(params.at_position + i, 0, tlTrack)
}
return $.when(tlTracks)
}
/* Clears the tracklist */
DummyTracklist.prototype.clear = function () {
this._tlTracks = []
}
/**
* Retuns a list containing tlTracks that contain the provided
* criteria.uri or has ID criteria.tlid.
*
*/
DummyTracklist.prototype.filter = function (criteria) {
if (!criteria || (!criteria.uri && !criteria.tlid)) {
throw new Error('No URI or tracklist ID provided to filter on.')
}
var matches = []
if (criteria.uri) { // Look for matching URIs
for (var i = 0; i < criteria.uri.length; i++) {
for (var j = 0; j < this._tlTracks.length; j++) {
if (this._tlTracks[j].track.uri === criteria.uri[i]) {
matches.push(this._tlTracks[j])
}
}
}
}
if (criteria.tlid) { // Look for matching tracklist IDs
for (i = 0; i < criteria.tlid.length; i++) {
for (j = 0; j < this._tlTracks.length; j++) {
if (this._tlTracks[j].tlid === criteria.tlid[i]) {
matches.push(this._tlTracks[j])
}
}
}
}
return $.when(matches)
}
/* Retuns index of the currently 'playing' track. */
DummyTracklist.prototype.index = function (params) {
if (!params) {
if (this._tlTracks.length > 1) {
// Always just assume that the second track is playing
return $.when(1)
} else {
return $.when(0)
}
}
for (var i = 0; i < this._tlTracks.length; i++) {
if (this._tlTracks[i].tlid === params.tlid || (params.tl_track && params.tl_track.tlid === this._tlTracks[i].tlid)) {
return $.when(i)
}
}
return $.when(0)
}
return DummyTracklist
}))

265
tests/js/test_controls.js Normal file
View File

@ -0,0 +1,265 @@
var chai = require('chai')
var expect = chai.expect
var assert = chai.assert
chai.use(require('chai-string'))
chai.use(require('chai-jquery'))
var sinon = require('sinon')
var controls = require('../../mopidy_musicbox_webclient/static/js/controls.js')
var DummyTracklist = require('./dummy_tracklist.js')
describe('controls', function () {
var mopidy
var div_element
var QUEUE_TRACKS = [ // Simulate an existing queue with three tracks loaded.
{uri: 'track:tlTrackMock1'},
{uri: 'track:tlTrackMock2'},
{uri: 'track:tlTrackMock3'}
]
var NEW_TRACKS = [ // Simulate the user browsing to a folder with three tracks inside it.
{uri: 'track:trackMock1'},
{uri: 'tunein:track:trackMock2'}, // Stream
{uri: 'track:trackMock3'}
]
var addSpy
before(function () {
$(document.body).append('<div data-role="popup" id="popupTracks"></div>')
$('#popupTracks').popup() // Initialize popup
$(document.body).data('on-track-click', 'PLAY_ALL') // Set default click action
mopidy = sinon.stub(new Mopidy({callingConvention: 'by-position-or-by-name'}))
var playback = {
play: sinon.stub(),
stop: sinon.stub()
}
mopidy.playback = playback
mopidy.playback.stop.returns($.when())
// Mock the Mopidy tracklist so that we have a predictable state to test against.
mopidy.tracklist = new DummyTracklist()
addSpy = sinon.spy(mopidy.tracklist, 'add')
clearSpy = sinon.spy(mopidy.tracklist, 'clear')
})
beforeEach(function () {
mopidy.tracklist.clear()
clearSpy.reset()
mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)})
})
afterEach(function () {
mopidy.playback.play.reset()
addSpy.reset()
})
after(function () {
mopidy.tracklist.add.restore()
mopidy.tracklist.clear.restore()
})
describe('#playTracks()', function () {
it('PLAY_ALL should clear tracklist first before populating with tracks', function () {
customTracklists[BROWSE_TABLE] = NEW_TRACKS
controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[0].uri, BROWSE_TABLE)
assert(clearSpy.called)
})
it('should not clear tracklist for events other than PLAY_ALL', function () {
customTracklists[BROWSE_TABLE] = NEW_TRACKS
controls.playTracks(PLAY_NOW, mopidy, NEW_TRACKS[0].uri, BROWSE_TABLE)
assert(clearSpy.notCalled)
})
it('should raise exception if trackUri parameter is not provided and "track" data attribute is empty', function () {
assert.throw(function () { controls.playTracks('', mopidy) }, Error)
controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[0].uri, BROWSE_TABLE)
assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[0].tlid}))
})
it('should raise exception if playListUri parameter is not provided and "track" data attribute is empty', function () {
assert.throw(function () { controls.playTracks('', mopidy, NEW_TRACKS[0].uri) }, Error)
controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[0].uri, BROWSE_TABLE)
assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[0].tlid}))
})
it('should raise exception if unknown tracklist action is provided', function () {
var getTrackURIsForActionStub = sinon.stub(controls, '_getTrackURIsForAction') // Stub to bypass earlier exception
assert.throw(function () { controls.playTracks('99', mopidy, NEW_TRACKS[0].uri, BROWSE_TABLE) }, Error)
getTrackURIsForActionStub.restore()
})
it('should use "track" and "list" data attributes as fallback if parameters are not provided', function () {
$('#popupTracks').data('track', 'track:trackMock1') // Simulate 'track:trackMock1' being clicked.
$('#popupTracks').data('list', BROWSE_TABLE)
customTracklists[BROWSE_TABLE] = NEW_TRACKS
controls.playTracks(PLAY_ALL, mopidy)
assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[0].tlid}))
})
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')
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 add correct track')
addSpy.reset()
mopidy.tracklist.clear()
mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)})
controls.playTracks(ADD_THIS_BOTTOM, mopidy, NEW_TRACKS[0].uri)
assert(addSpy.calledWithMatch({uris: [NEW_TRACKS[0].uri]}), 'ADD_THIS_BOTTOM did not add correct track')
})
it('PLAY_ALL and ADD_ALL_BOTTOM should add all tracks to tracklist', function () {
controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[0].uri)
assert(addSpy.calledWithMatch({uris: getUris(NEW_TRACKS)}), 'PLAY_ALL did not add correct tracks')
addSpy.reset()
mopidy.tracklist.clear()
mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)})
controls.playTracks(ADD_ALL_BOTTOM, mopidy, NEW_TRACKS[0].uri)
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 () {
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')
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')
})
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')
mopidy.playback.play.reset()
mopidy.tracklist.clear()
mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)})
controls.playTracks(PLAY_NEXT, mopidy, NEW_TRACKS[0].uri)
assert.isFalse(mopidy.playback.play.called, 'PLAY_NEXT should not have triggered playback to start')
mopidy.playback.play.reset()
mopidy.tracklist.clear()
mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)})
controls.playTracks(ADD_THIS_BOTTOM, mopidy, NEW_TRACKS[0].uri)
assert.isFalse(mopidy.playback.play.called, 'ADD_THIS_BOTTOM should not have triggered playback to start')
mopidy.playback.play.reset()
mopidy.tracklist.clear()
mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)})
controls.playTracks(PLAY_ALL, mopidy, NEW_TRACKS[2].uri)
assert(mopidy.playback.play.calledWithMatch({tlid: mopidy.tracklist._tlTracks[2].tlid}), 'PLAY_ALL did not start playback of correct track')
mopidy.playback.play.reset()
mopidy.tracklist.clear()
mopidy.tracklist.add({uris: getUris(QUEUE_TRACKS)})
controls.playTracks(ADD_ALL_BOTTOM, mopidy, NEW_TRACKS[0].uri)
assert.isFalse(mopidy.playback.play.called, 'ADD_ALL_BOTTOM should not have triggered playback to start')
mopidy.playback.play.reset()
})
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)
assert(cookieStub.calledWithMatch('onTrackClick', PLAY_NOW, {expires: 365}))
cookieStub.reset()
$(document.body).data('on-track-click', 'PLAY_NOW')
controls.playTracks(PLAY_NOW, mopidy, 2)
assert(cookieStub.notCalled)
cookieStub.restore()
})
})
describe('#getAction()', function () {
it('should use default action if none is specified', function () {
window.MOCK_DEFAULT = 99 // Define global variable to test against.
$(document.body).data('on-track-click', 'MOCK_DEFAULT')
assert.equal(controls.getAction(), 99)
})
it('should get action from cookie if action is set to "DYNAMIC"', function () {
$(document.body).data('on-track-click', 'DYNAMIC')
var cookieStub = sinon.stub($, 'cookie')
controls.getAction()
assert(cookieStub.called)
cookieStub.restore()
})
it('should default to "PLAY_ALL" if no cookie is available for "DYNAMIC"', function () {
$(document.body).data('on-track-click', 'DYNAMIC')
$.removeCookie('onTrackClick')
assert.equal(controls.getAction(), PLAY_ALL)
})
})
describe('#getIconForAction()', function () {
it('should return correct FontAwesome class for each tracklist action', function () {
assert.equal(controls.getIconForAction(PLAY_ALL), 'fa fa-fast-forward')
assert.equal(controls.getIconForAction(PLAY_NOW), 'fa fa-play')
assert.equal(controls.getIconForAction(PLAY_NEXT), 'fa fa-level-down')
assert.equal(controls.getIconForAction(ADD_THIS_BOTTOM), 'fa fa-plus-square-o')
assert.equal(controls.getIconForAction(ADD_ALL_BOTTOM), 'fa fa-plus-square')
})
it('should raise error if unknown tracklist action is provided', function () {
assert.throw(function () { controls.getIconForAction(99) }, Error)
})
it('should handle action identifier strings in addition to integers', function () {
assert.equal(controls.getIconForAction('0'), 'fa fa-play')
})
it('should use default tracklist action if no parameter is provided', function () {
assert.equal(controls.getIconForAction(), 'fa fa-fast-forward')
})
})
describe('#_getTrackURIsForAction()', function () {
it('should return just "trackUri" for PLAY_NOW, PLAY_NEXT, and ADD_THIS_BOTTOM', function () {
assert.equal(controls._getTrackURIsForAction(PLAY_NOW, 'mockUri')[0], 'mockUri')
assert.equal(controls._getTrackURIsForAction(PLAY_NEXT, 'mockUri')[0], 'mockUri')
assert.equal(controls._getTrackURIsForAction(ADD_THIS_BOTTOM, 'mockUri')[0], 'mockUri')
})
it('should get tracks from "playlistUri" for PLAY_ALL, and ADD_ALL_BOTTOM', function () {
customTracklists[BROWSE_TABLE] = NEW_TRACKS
var tracks = controls._getTrackURIsForAction(PLAY_ALL, NEW_TRACKS[0], BROWSE_TABLE)
assert.equal(tracks.length, NEW_TRACKS.length)
for (var i = 0; i < tracks.length; i++) {
assert.equal(tracks[i], NEW_TRACKS[i].uri)
}
})
it('should raise error if unknown tracklist action is provided', function () {
assert.throw(function () { controls._getTrackURIsForAction(99) }, Error)
})
it('should handle action identifier strings in addition to integers', function () {
assert.equal(controls._getTrackURIsForAction('0', 'mockUri')[0], 'mockUri')
})
})
})

View File

@ -243,6 +243,9 @@ describe('images', function () {
getImagesSpy.reset()
setDeprecatedAlbumImageSpy.reset()
})
after(function () {
mopidy.library.getImages.restore()
})
it('should use default image if no track URI is provided', function () {
images.setAlbumImage('', img_element, mopidy)
@ -302,7 +305,7 @@ describe('images', function () {
assert.isTrue(lookupSpy.calledOnce)
expect($(img_element).prop('src')).to.endWith('mockAlbumImageUri')
lookupSpy.restore()
mopidy.library.lookup.restore()
})
it('should use default image if track.album or track.artist is not available', function () {
@ -313,7 +316,7 @@ describe('images', function () {
assert.isTrue(lookupSpy.calledOnce)
expect($(img_element).prop('src')).to.endWith(images.DEFAULT_ALBUM_URL)
lookupSpy.restore()
mopidy.library.lookup.restore()
})
it('should fall back to retrieving image from last.fm if none provided by Mopidy', function () {
@ -414,7 +417,7 @@ describe('images', function () {
getImagesSpy.reset()
})
after(function () {
getImagesSpy.restore()
mopidy.library.getImages.restore()
})
it('should use default image if no artist URI is provided', function () {

View File

@ -223,7 +223,7 @@ describe('SyncedTimer', function () {
clock.tick(1001)
syncedProgressTimer._scheduleSync(1000)
assert(clearSpy.calledWith(scheduleID))
clearSpy.restore()
window.clearTimeout.restore()
})
})
@ -358,7 +358,7 @@ describe('SyncedTimer', function () {
assert(scheduleSpy.calledWith(0))
syncedProgressTimer.stop()
scheduleSpy.restore()
syncedProgressTimer._scheduleSync.restore()
})
})
@ -392,7 +392,7 @@ describe('SyncedTimer', function () {
assert.isFalse(syncedProgressTimer._isSyncScheduled)
assert(cancelSpy.calledWith(syncedProgressTimer._scheduleID))
cancelSpy.restore()
window.clearTimeout.restore()
})
})
@ -422,7 +422,7 @@ describe('SyncedTimer', function () {
assert.isTrue(formatSpy.called)
expect(syncedProgressTimer.positionNode.nodeValue).to.equal('0:01')
formatSpy.restore()
SyncedProgressTimer.format.restore()
})
it('should set position to "" if timer has not been initialized', function () {

View File

@ -18,6 +18,7 @@ class ExtensionTests(unittest.TestCase):
assert 'enabled = true' in config
assert 'websocket_host =' in config
assert 'websocket_port =' in config
assert 'on_track_click = PLAY_ALL' in config
def test_get_config_schema(self):
ext = Extension()
@ -27,6 +28,7 @@ class ExtensionTests(unittest.TestCase):
assert 'musicbox' in schema
assert 'websocket_host' in schema
assert 'websocket_port' in schema
assert 'on_track_click' in schema
def test_setup(self):
registry = mock.Mock()

View File

@ -76,3 +76,6 @@ class WebclientTests(unittest.TestCase):
def test_is_musicbox(self):
assert not self.mmw.is_music_box()
def test_default_click_action(self):
assert self.mmw.get_default_click_action() == 'PLAY_ALL'