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>

File diff suppressed because it is too large Load Diff

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())
}
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,37 +606,55 @@ $(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 () {
var eachTlid = $(this).attr('tlid')
if (typeof eachTlid !== 'undefined') {
eachTlid = parseInt(eachTlid)
}
if (this.id === getjQueryID(target, uri) && eachTlid === tlid) {
$(this).addClass('currenttrack')
} else {
$(this).removeClass('currenttrack')
}
})
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 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 () {
if (this.id === getjQueryID(target, uri)) {
$(this).addClass('currenttrack2')
} else {
$(this).removeClass('currenttrack2')
$(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'