Show all available track info in the 'Show Track Info...' popup.

Add icon to top right corner of album image for activating the info popup.

Fixes #227.
This commit is contained in:
jcass 2017-01-29 11:17:48 +02:00
parent 01ae68ebc6
commit c240ad9ff8
9 changed files with 202 additions and 127 deletions

View File

@ -108,8 +108,8 @@ v2.4.0 (UNRELEASED)
- Now shows server name/IP address and port number at the bottom of the navigation pane. (Addresses: `#67 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/67>`_). - Now shows server name/IP address and port number at the bottom of the navigation pane. (Addresses: `#67 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/67>`_).
- Add ability to insert a track anywhere in the current queue. (Addresses: `#75 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/75>`_). - Add ability to insert a track anywhere in the current queue. (Addresses: `#75 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/75>`_).
- Add 'Show Track Info' popup which can be activated from any context menu. The popup includes the URI of the track, - Add 'Show Track Info' popup which can be activated from any context menu or by clicking the 'info' icon next to the album cover on the
which can be inserted into various lists elsewhere in the player. 'Now Playing' pane. The popup includes the URI of the track, which can be inserted into various lists elsewhere in the player.
- Updated icon set for font-awesome 4.7.0. - Updated icon set for font-awesome 4.7.0.
- Added 'Refresh' button for refreshing libraries. (Addresses: `#75 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/75>`_). - Added 'Refresh' button for refreshing libraries. (Addresses: `#75 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/75>`_).
@ -123,6 +123,7 @@ v2.4.0 (UNRELEASED)
- Now initializes the GUI properly, even if the user is offline or the Mopidy server cannot be reached. - Now initializes the GUI properly, even if the user is offline or the Mopidy server cannot be reached.
- Fixed `Alarm Clock <https://pypi.python.org/pypi/Mopidy-AlarmClock/>`_ detection. - Fixed `Alarm Clock <https://pypi.python.org/pypi/Mopidy-AlarmClock/>`_ detection.
- When browsing the library using the local 'File' extension, only playable audio files will have context menu icons. - When browsing the library using the local 'File' extension, only playable audio files will have context menu icons.
- Show all available track information in the 'Show Track Info...' popup. (Fixes: `#227 <https://github.com/pimusicbox/mopidy-musicbox-webclient/issues/227>`_).
v2.3.0 (2016-05-15) v2.3.0 (2016-05-15)
------------------- -------------------

View File

@ -291,6 +291,12 @@ span.hostInfo {
vertical-align: middle; vertical-align: middle;
} }
.info-table input {
color: #555;
border: none;
font-size: 1em;
}
.albumdivider h1, .table li h1 { .albumdivider h1, .table li h1 {
font-size: 120% !important; font-size: 120% !important;
} }
@ -361,6 +367,21 @@ span.hostInfo {
font-size: initial; font-size: initial;
} }
.infoBtn {
top: 0;
width: 90%;
position: absolute;
}
.infoBtn i {
font-size: 1.33em;
color: #ddd;
background: white;
border-radius: 50%;
height: 1em;
width: 1em;
}
.backnav { .backnav {
background-color: #ccc !important; background-color: #ccc !important;
} }
@ -401,11 +422,17 @@ span.hostInfo {
/************ /************
* Popups * * Popups *
************/ ************/
#modalalbum a, #modalartist a { #modalalbum a, #modalartist a, #modalname a {
color: #444; color: #444;
text-decoration: none; text-decoration: none;
} }
#modalinfo {
position: relative;
display: inline-block;
padding-top: .5em;
}
.popupArtistLi, .popupArtistLi,
.popupAlbumLi { .popupAlbumLi {
display: none display: none

View File

@ -130,13 +130,13 @@
<li data-icon="playAll" data-iconshadow="false"> <li data-icon="playAll" data-iconshadow="false">
<a href="#" onclick="return controls.playTracks(PLAY_ALL, mopidy);">Play All</a> <a href="#" onclick="return controls.playTracks(PLAY_ALL, mopidy);">Play All</a>
</li> </li>
<li data-icon="playNext" class="addqueue"> <li data-icon="playNext">
<a href="#" onclick="return controls.playTracks(PLAY_NEXT, mopidy);">Play Track Next</a> <a href="#" onclick="return controls.playTracks(PLAY_NEXT, mopidy);">Play Track Next</a>
</li> </li>
<li data-icon="add" class="addqueue"> <li data-icon="add">
<a href="#" onclick="return controls.playTracks(ADD_THIS_BOTTOM, mopidy);">Add Track to Bottom of Queue</a> <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"> <li data-icon="addAll">
<a href="#" onclick="return controls.playTracks(ADD_ALL_BOTTOM, mopidy);">Add All to Bottom of Queue</a> <a href="#" onclick="return controls.playTracks(ADD_ALL_BOTTOM, mopidy);">Add All to Bottom of Queue</a>
</li> </li>
<li class="popupAlbumLi"> <li class="popupAlbumLi">
@ -151,7 +151,7 @@
<ul data-icon="false" data-inset="false" data-role="listview" class="popupArtistsLv"></ul> <ul data-icon="false" data-inset="false" data-role="listview" class="popupArtistsLv"></ul>
</div> </div>
<li> <li>
<a href="#" onclick="return controls.showInfoPopup('#popupTracks', mopidy);">Show Track Info...</span></a> <a href="#" onclick="return controls.showInfoPopup('', '#popupTracks', mopidy);">Show Track Info...</span></a>
</li> </li>
</ul> </ul>
</div> </div>
@ -181,7 +181,7 @@
<ul data-icon="false" data-inset="false" data-role="listview" class="popupArtistsLv"></ul> <ul data-icon="false" data-inset="false" data-role="listview" class="popupArtistsLv"></ul>
</div> </div>
<li> <li>
<a href="#" onclick="return controls.showInfoPopup('#popupQueue', mopidy);">Show Track Info...</span></a> <a href="#" onclick="return controls.showInfoPopup('', '#popupQueue', mopidy);">Show Track Info...</span></a>
</li> </li>
</ul> </ul>
</div> </div>
@ -260,36 +260,7 @@
<th data-priority="persist"></th> <th data-priority="persist"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody></tbody>
<tr>
<td class="label">Name:</td>
<td id="name-cell"></td>
</tr>
<tr id="album-row">
<td class="label">Album:</td>
<td id="album-cell"></td>
</tr>
<tr id="artist-row">
<td class="label">Artist(s):</td>
<td id="artist-cell"></td>
</tr>
<tr id="track-no-row">
<td class="label">Track #:</td>
<td id="track-no-cell"></td>
</tr>
<tr id="length-row">
<td class="label">Length:</td>
<td id="length-cell"></td>
</tr>
<tr id="bitrate-row">
<td class="label">Bitrate:</td>
<td id="bitrate-cell"></td>
</tr>
<tr>
<td class="label label-center">URI:</td>
<td><input type="text" id="uri-cell"></input></td>
</tr>
</tbody>
</table> </table>
</div><!--/show track info--> </div><!--/show track info-->
@ -363,10 +334,12 @@
<div id="nowPlayingpane" data-role="content" class="pane"> <div id="nowPlayingpane" data-role="content" class="pane">
<img id="albumCoverImg" src="images/default_cover.png" alt="Album cover"/> <div id="modalinfo">
<img id="albumCoverImg" src="images/default_cover.png" alt="Album cover"/>
</div>
<div class="nowPlaying-artistInfo"> <div class="nowPlaying-artistInfo">
<h3 id="modalname"></h3> <h3 id="modalname"></h3>
<p class="artistAlbumLine"><span id="modalartist"></span> - <span id="modalalbum"></span></p> <p class="artistAlbumLine"><span id="modalartist"></span> - <span id="modalalbum"></span></p>
</div> </div>

View File

@ -338,74 +338,130 @@
}) })
}, },
showInfoPopup: function (popupId, mopidy) { showInfoPopup: function (uri, popupId, mopidy) {
showLoading(true) showLoading(true)
var uri = $(popupId).data('track') var trackUri = uri || $(popupId).data('track')
$(popupId).popup('close') $(popupId).popup('close')
mopidy.library.lookup({'uris': [uri]}).then(function (resultDict) { $('#popupShowInfo tbody').empty()
mopidy.library.lookup({'uris': [trackUri]}).then(function (resultDict) {
var uri = Object.keys(resultDict)[0] var uri = Object.keys(resultDict)[0]
var track = resultDict[uri][0] var track = resultDict[uri][0]
var html = ''
var rowTemplate = '<tr><td class="label">{label}:</td><td id="{label}-cell">{text}</td></tr>'
var row = {'label': '', 'text': ''}
row.label = 'Name'
if (track.name) { if (track.name) {
$('#popupShowInfo #name-cell').text(track.name) row.text = track.name
} else { } else {
$('#popupShowInfo #name-cell').text('(Not available)') row.text = '(Not available)'
} }
html += stringFromTemplate(rowTemplate, row)
row.label = 'Album'
if (track.album && track.album.name) { if (track.album && track.album.name) {
$('#popupShowInfo #album-cell').text(track.album.name) row.text = track.album.name
} else { } else {
$('#popupShowInfo #album-cell').text('(Not available)') row.text = '(Not available)'
} }
var artistNames = '' html += stringFromTemplate(rowTemplate, row)
if (track.artists && track.artists.length > 0) {
for (var i = 0; i < track.artists.length; i++) { var artists = artistsToString(track.artists)
if (i > 0) { // Fallback to album artists.
artistNames = artistNames + ', ' if (artists.length === 0 && track.album && track.album.artists) {
} artists = artistsToString(track.album.artists)
artistNames = artistNames + track.artists[i].name
}
} }
// Fallback to album artists. if (artists.length > 0) {
if (artistNames.length === 0 && track.album && track.album.artists && track.album.artists.length > 0) { if (track.artists && track.artists.length > 1 || track.album && track.album.artists && track.album.artists.length > 1) {
for (i = 0; i < track.album.artists.length; i++) { row.label = 'Artists'
if (i > 0) { } else {
artistNames = artistNames + ', ' row.label = 'Artist'
}
artistNames = artistNames + track.album.artists[i].name
} }
row.text = artists
html += stringFromTemplate(rowTemplate, row)
} }
if (artistNames.length > 0) {
$('#popupShowInfo #artist-cell').text(artistNames) var composers = artistsToString(track.composers)
$('#popupShowInfo #artist-row').show() if (composers.length > 0) {
} else { if (track.composers.length > 1) {
$('#popupShowInfo #artist-row').hide() row.label = 'Composers'
} else {
row.label = 'Composer'
}
row.text = composers
html += stringFromTemplate(rowTemplate, row)
} }
var performers = artistsToString(track.performers)
if (performers.length > 0) {
if (track.performers.length > 1) {
row.label = 'Performers'
} else {
row.label = 'Performer'
}
row.text = performers
html += stringFromTemplate(rowTemplate, row)
}
if (track.genre) {
row = {'label': 'Genre', 'text': track.genre}
html += stringFromTemplate(rowTemplate, row)
}
if (track.track_no) { if (track.track_no) {
$('#popupShowInfo #track-no-cell').text(track.track_no) row = {'label': 'Track #', 'text': track.track_no}
$('#popupShowInfo #track-no-row').show() html += stringFromTemplate(rowTemplate, row)
} else {
$('#popupShowInfo #track-no-row').hide()
} }
if (track.disc_no) {
row = {'label': 'Disc #', 'text': track.disc_no}
html += stringFromTemplate(rowTemplate, row)
}
if (track.date) {
row = {'label': 'Date', 'text': new Date(track.date)}
html += stringFromTemplate(rowTemplate, row)
}
if (track.length) { if (track.length) {
$('#popupShowInfo #length-cell').text(timeFromSeconds(track.length / 1000)) row = {'label': 'Length', 'text': timeFromSeconds(track.length / 1000)}
$('#popupShowInfo #length-row').show() html += stringFromTemplate(rowTemplate, row)
} else {
$('#popupShowInfo #length-row').hide()
} }
if (track.bitrate) { if (track.bitrate) {
$('#popupShowInfo #bitrate-cell').text(track.bitrate) row = {'label': 'Bitrate', 'text': track.bitrate}
$('#popupShowInfo #bitrate-row').show() html += stringFromTemplate(rowTemplate, row)
} else {
$('#popupShowInfo #bitrate-row').hide()
} }
$('#popupShowInfo #uri-cell').val(uri)
if (track.comment) {
row = {'label': 'Comment', 'text': track.comment}
html += stringFromTemplate(rowTemplate, row)
}
if (track.musicbrainz_id) {
row = {'label': 'MusicBrainz ID', 'text': track.musicbrainz_id}
html += stringFromTemplate(rowTemplate, row)
}
if (track.last_modified) {
row = {'label': 'Modified', 'text': track.last_modified}
html += stringFromTemplate(rowTemplate, row)
}
rowTemplate = '<tr><td class="label label-center">{label}:</td><td><input type="text" id="uri-input" value="{text}"></input></td></tr>'
row = {'label': 'URI', 'text': uri}
html += stringFromTemplate(rowTemplate, row)
$('#popupShowInfo tbody').append(html)
showLoading(false) showLoading(false)
$('#popupShowInfo').popup('open') $('#popupShowInfo').popup('open')
if (!isMobile) { if (!isMobile) {
// Set focus and select URI text on desktop systems (don't want the keyboard to pop up automatically on mobile devices) // Set focus and select URI text on desktop systems (don't want the keyboard to pop up automatically on mobile devices)
$('#popupShowInfo #uri-cell').focus() $('#popupShowInfo #uri-input').focus()
$('#popupShowInfo #uri-cell').select() $('#popupShowInfo #uri-input').select()
} }
}, console.error) }, console.error)
return false return false

View File

@ -204,12 +204,14 @@ function getAlbum (pl) {
function artistsToString (artists, max) { function artistsToString (artists, max) {
var result = '' var result = ''
max = max || 3 max = max || 3
for (var i = 0; i < artists.length && i < max; i++) { if (artists && artists.length > 0) {
if (artists[i].name) { for (var i = 0; i < artists.length && i < max; i++) {
if (i > 0) { if (artists[i].name) {
result += ', ' if (i > 0) {
result += ', '
}
result += artists[i].name
} }
result += artists[i].name
} }
} }
return result return result
@ -578,6 +580,13 @@ function isSpotifyStarredPlaylist (playlist) {
return (starredRegex.test(playlist.uri) && playlist.name === 'Starred') return (starredRegex.test(playlist.uri) && playlist.name === 'Starred')
} }
// Returns a string where {x} in template is replaced by tokens[x].
function stringFromTemplate (template, tokens) {
return template.replace(/{[^}]+}/g, function (match) {
return tokens[match.slice(1, -1)]
})
}
/** /**
* Converts a URI to a jQuery-safe identifier. jQuery identifiers need to be * Converts a URI to a jQuery-safe identifier. jQuery identifiers need to be
* unique per page and cannot contain special characters. * unique per page and cannot contain special characters.

View File

@ -67,9 +67,9 @@ function resizeMb () {
*/ */
} }
function setSongTitle (title, refresh_ui) { function setSongTitle (track, refresh_ui) {
songdata.track.name = title songdata.track.name = track.name
$('#modalname').html(title) $('#modalname').html('<a href="#" onclick="return controls.showInfoPopup(\'' + track.uri + '\', \'\', mopidy);">' + track.name + '</span></a>')
if (refresh_ui) { if (refresh_ui) {
resizeMb() resizeMb()
} }
@ -98,7 +98,7 @@ function setSongInfo (data) {
songdata = data songdata = data
setSongTitle(data.track.name, false) setSongTitle(data.track, false)
songlength = Infinity songlength = Infinity
if (!data.track.length || data.track.length === 0) { if (!data.track.length || data.track.length === 0) {
@ -131,6 +131,12 @@ function setSongInfo (data) {
$('#modalalbum').html('') $('#modalalbum').html('')
} }
images.setAlbumImage(data.track.uri, '#infocover, #albumCoverImg', mopidy) images.setAlbumImage(data.track.uri, '#infocover, #albumCoverImg', mopidy)
if (data.track.uri) {
// Add 'Show Info' icon to album image
$('#modalinfo').append(
'<a href="#" class="infoBtn" onclick="return controls.showInfoPopup(\'' + data.track.uri + '\', \'undefined\', mopidy);">' +
'<i class="fa fa-info-circle"></i></a>')
}
$('#modalartist').html(arttmp) $('#modalartist').html(arttmp)
@ -190,10 +196,8 @@ function popupTracks (e, listuri, trackuri, tlid) {
var divid = hash[0].substr(1) var divid = hash[0].substr(1)
var popupName = '' var popupName = ''
if (divid === 'current') { if (divid === 'current') {
$('.addqueue').hide()
popupName = '#popupQueue' popupName = '#popupQueue'
} else { } else {
$('.addqueue').show()
popupName = '#popupTracks' popupName = '#popupTracks'
} }
@ -316,7 +320,8 @@ function initSocketevents () {
}) })
mopidy.on('event:streamTitleChanged', function (data) { mopidy.on('event:streamTitleChanged', function (data) {
setSongTitle(data.title, true) // Update all track info.
mopidy.playback.getCurrentTlTrack().then(processCurrenttrack, console.error)
}) })
} }

View File

@ -126,19 +126,12 @@
$('#searchtracks').show() $('#searchtracks').show()
} }
// Returns a string where {x} in template is replaced by tokens[x]. // 'Show more' template
function theme (template, tokens) { var showMoreTemplate = '<li onclick="$(this).hide().siblings().show(); return false;"><a>Show {count} more</a></li>'
return template.replace(/{[^}]+}/g, function (match) {
return tokens[match.slice(1, -1)]
})
}
// 'Show more' pattern
var showMorePattern = '<li onclick="$(this).hide().siblings().show(); return false;"><a>Show {count} more</a></li>'
// Artist results // Artist results
var child = '' var child = ''
var pattern = '<li><a href="#" onclick="return library.showArtist(this.id)" id={id}><i class="{class}"></i> <strong>{name}</strong></a></li>' var template = '<li><a href="#" onclick="return library.showArtist(this.id)" id={id}><i class="{class}"></i> <strong>{name}</strong></a></li>'
var tokens var tokens
for (i = 0; i < results.artists.length; i++) { for (i = 0; i < results.artists.length; i++) {
@ -150,11 +143,11 @@
// Add 'Show all' item after a certain number of hits. // Add 'Show all' item after a certain number of hits.
if (i === 4 && results.artists.length > 5) { if (i === 4 && results.artists.length > 5) {
child += theme(showMorePattern, {'count': results.artists.length - i}) child += stringFromTemplate(showMoreTemplate, {'count': results.artists.length - i})
pattern = pattern.replace('<li>', '<li class="overflow">') template = template.replace('<li>', '<li class="overflow">')
} }
child += theme(pattern, tokens) child += stringFromTemplate(template, tokens)
} }
// Inject list items, refresh listview and hide superfluous items. // Inject list items, refresh listview and hide superfluous items.
@ -162,10 +155,10 @@
// Album results // Album results
child = '' child = ''
pattern = '<li><a href="#" onclick="return library.showAlbum(this.id)" id="{albumId}">' template = '<li><a href="#" onclick="return library.showAlbum(this.id)" id="{albumId}">'
pattern += '<h5 data-role="heading"><i class="{class}"></i> {albumName}</h5>' template += '<h5 data-role="heading"><i class="{class}"></i> {albumName}</h5>'
pattern += '<p data-role="desc">{artistName}</p>' template += '<p data-role="desc">{artistName}</p>'
pattern += '</a></li>' template += '</a></li>'
for (i = 0; i < results.albums.length; i++) { for (i = 0; i < results.albums.length; i++) {
tokens = { tokens = {
@ -187,11 +180,11 @@
} }
// Add 'Show all' item after a certain number of hits. // Add 'Show all' item after a certain number of hits.
if (i === 4 && results.albums.length > 5) { if (i === 4 && results.albums.length > 5) {
child += theme(showMorePattern, {'count': results.albums.length - i}) child += stringFromTemplate(showMoreTemplate, {'count': results.albums.length - i})
pattern = pattern.replace('<li>', '<li class="overflow">') template = template.replace('<li>', '<li class="overflow">')
} }
child += theme(pattern, tokens) child += stringFromTemplate(template, tokens)
} }
// Inject list items, refresh listview and hide superfluous items. // Inject list items, refresh listview and hide superfluous items.
$(SEARCH_ALBUM_TABLE).html(child).listview('refresh').find('.overflow').hide() $(SEARCH_ALBUM_TABLE).html(child).listview('refresh').find('.overflow').hide()

View File

@ -1,6 +1,6 @@
CACHE MANIFEST CACHE MANIFEST
# 2017-01-18:v2 # 2017-01-29:v2
NETWORK: NETWORK:
* *

View File

@ -363,12 +363,21 @@ describe('controls', function () {
describe('#showInfoPopup()', function () { describe('#showInfoPopup()', function () {
var track var track
var popup = $('<div data-role="popup" id="popupShowInfo"></div>') var popup = $('<div data-role="popup" id="popupShowInfo"><table><thead><tr><th></th><th></th></tr></thead><tbody></tbody></div>')
before(function () { before(function () {
track = { track = {
'uri': QUEUE_TRACKS[0].uri, 'uri': QUEUE_TRACKS[0].uri,
'length': 61000 'length': 61000,
'artists': [
{
'uri': 'artistUri1',
'name': 'nameMock1'
}, {
'uri': 'artistUri2',
'name': 'nameMock2'
}
]
} }
var library = { var library = {
lookup: sinon.stub() lookup: sinon.stub()
@ -377,7 +386,7 @@ describe('controls', function () {
mopidy.library.lookup.returns($.when({'track:tlTrackMock1': [track]})) mopidy.library.lookup.returns($.when({'track:tlTrackMock1': [track]}))
$(document.body).append(popup) $(document.body).append(popup)
$('#popupTracks').data(track, track.uri) // Simulate selection from context menu $('#popupShowInfo').data(track, track.uri) // Simulate selection from context menu
$('#popupShowInfo').popup() // Initialize popup $('#popupShowInfo').popup() // Initialize popup
}) })
@ -386,21 +395,23 @@ describe('controls', function () {
}) })
it('should default track name', function () { it('should default track name', function () {
popup.append('<span id="name-cell"></span>') controls.showInfoPopup('', '#popupShowInfo', mopidy)
controls.showInfoPopup('#popupTracks', mopidy) assert.equal($('td:contains("Name:")').siblings('td').text(), '(Not available)')
assert.equal($('#name-cell').text(), '(Not available)')
}) })
it('should default album name', function () { it('should default album name', function () {
popup.append('<span id="album-cell"></span>') controls.showInfoPopup('', '#popupShowInfo', mopidy)
controls.showInfoPopup('#popupTracks', mopidy) assert.equal($('td:contains("Album:")').siblings('td').text(), '(Not available)')
assert.equal($('#album-cell').text(), '(Not available)')
}) })
it('should add leading zero if seconds length < 10', function () { it('should add leading zero if seconds length < 10', function () {
popup.append('<span id="length-cell"></span>') controls.showInfoPopup('', '#popupShowInfo', mopidy)
controls.showInfoPopup('#popupTracks', mopidy) assert.equal($('td:contains("Length:")').siblings('td').text(), '1:01')
assert.equal($('#length-cell').text(), '1:01') })
it('should show plural for artist name', function () {
controls.showInfoPopup('', '#popupShowInfo', mopidy)
assert.isOk($('td:contains("Artists:")'))
}) })
}) })
}) })