1
0
mirror of https://github.com/SuperBFG7/ympd synced 2024-12-27 03:10:26 +00:00

Feat: Add playlist actions

This commit is contained in:
jcorporation 2018-07-22 20:00:26 +01:00
parent c8895ebdd8
commit c403202c7d
5 changed files with 414 additions and 90 deletions

View File

@ -213,3 +213,10 @@ div.alertBoxActive {
.opacity05 { .opacity05 {
opacity:0.5; opacity:0.5;
} }
caption {
caption-side: top;
font-size: 120%;
font-weight: bold;
color: black;
}

View File

@ -195,6 +195,14 @@
<div class="card-body hide" id="cardBrowsePlaylists"> <div class="card-body hide" id="cardBrowsePlaylists">
<div class="btn-toolbar card-toolbar" id="BrowsePlaylistsButtons" role="toolbar"> <div class="btn-toolbar card-toolbar" id="BrowsePlaylistsButtons" role="toolbar">
<div class="btn-group mr-2 hide">
<button data-href="{'cmd': 'appGoto', 'options': ['Browse','Playlists','All']}" id="btnBrowsePlaylistsAll" type="button" class="btn btn-secondary">&laquo; Playlists</button>
</div>
<div class="btn-group mr-2 hide">
<button id="btnPlaylistClear" type="button" class="btn btn-secondary" data-href="{'cmd': 'playlistClear', 'options': []}" title="Clear playlist">
<span class="material-icons">clear_all</span>
</button>
</div>
<div class="btn-group mr-2"> <div class="btn-group mr-2">
<button id="BrowsePlaylistsFilter" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">Filter</button> <button id="BrowsePlaylistsFilter" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">Filter</button>
<div class="dropdown-menu bg-dark px-2 letters" id="BrowsePlaylistsFilterLetters"> <div class="dropdown-menu bg-dark px-2 letters" id="BrowsePlaylistsFilterLetters">
@ -210,9 +218,8 @@
<button data-href="{'cmd': 'gotoPage', 'options': ['next']}" id="BrowsePlaylistsPaginationTopNext" title="Next Page" type="button" class="btn btn-secondary input-group-append">&raquo;</button> <button data-href="{'cmd': 'gotoPage', 'options': ['next']}" id="BrowsePlaylistsPaginationTopNext" title="Next Page" type="button" class="btn btn-secondary input-group-append">&raquo;</button>
</div> </div>
</div> </div>
<div class="table-responsive-md"> <div class="table-responsive-md">
<table id="BrowsePlaylistsList" class="table table-hover table-sm"> <table id="BrowsePlaylistsAllList" class="table table-hover table-sm">
<col class="tblnum"/> <col class="tblnum"/>
<col class="tbltitle"/> <col class="tbltitle"/>
<col class="tbllastmodified"/> <col class="tbllastmodified"/>
@ -228,6 +235,27 @@
<tbody class="clickable"> <tbody class="clickable">
</tbody> </tbody>
</table> </table>
<table id="BrowsePlaylistsDetailList" class="table table-hover table-sm hide">
<caption>Playlist List</caption>
<col class="tblnum"/>
<col class="tbltitle"/>
<col class="tblartist"/>
<col class="tblalbum"/>
<col class="tbllength"/>
<col class="tblaction"/>
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Artist</th>
<th>Album</th>
<th>Duration</th>
<th></th>
</tr>
</thead>
<tbody class="clickable">
</tbody>
</table>
</div> </div>
<div class="btn-toolbar" id="BrowsePlaylistsButtonsBottom" role="toolbar"> <div class="btn-toolbar" id="BrowsePlaylistsButtonsBottom" role="toolbar">
<div class="btn-group mr-2"> <div class="btn-group mr-2">
@ -248,10 +276,9 @@
</div> </div>
<div class="card-body hide" id="cardBrowseDatabase"> <div class="card-body hide" id="cardBrowseDatabase">
<div class="btn-toolbar card-toolbar" id="BrowseDatabaseButtons" role="toolbar"> <div class="btn-toolbar card-toolbar" id="BrowseDatabaseButtons" role="toolbar">
<div class="btn-group mr-2"> <div class="btn-group mr-2 hide">
<button data-href="{'cmd': 'appGoto', 'options': ['Browse','Database','Artist']}" id="btnBrowseDatabaseArtist" type="button" class="btn btn-secondary hide">&laquo; Artists</button> <button data-href="{'cmd': 'appGoto', 'options': ['Browse','Database','Artist']}" id="btnBrowseDatabaseArtist" type="button" class="btn btn-secondary">&laquo; Artists</button>
</div> </div>
<div class="btn-group mr-2"> <div class="btn-group mr-2">
<button id="BrowseDatabaseFilter" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">Filter</button> <button id="BrowseDatabaseFilter" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">Filter</button>
@ -286,7 +313,7 @@
</table> </table>
</div> </div>
<div id="BrowseDatabaseAlbumCards" class="row"></div> <div id="BrowseDatabaseAlbumCards" class="row hide"></div>
<div class="btn-toolbar" id="BrowseDatabaseButtonsBottom" role="toolbar"> <div class="btn-toolbar" id="BrowseDatabaseButtonsBottom" role="toolbar">
<div class="btn-group mr-2"> <div class="btn-group mr-2">
@ -462,8 +489,8 @@
</nav> </nav>
</footer> </footer>
<!-- Modal --> <!-- Modals -->
<div class="modal" id="modalConnectionError" role="dialog"> <div class="modal fade" id="modalConnectionError" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header bg-danger text-light"> <div class="modal-header bg-danger text-light">
@ -476,7 +503,59 @@
</div> </div>
</div> </div>
<!-- Modal --> <div class="modal fade" id="modalAddToPlaylist" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><span class="material-icons title-icon">playlist_add</span> Add to playlist</h5>
</div>
<div class="modal-body">
<form id="addToPlaylistFrm">
<input type="hidden" id="addToPlaylistUri"/>
<div class="form-group input-group col-md-6 border-secondary">
<div class="input-group-prepend">
<div class="input-group-text bg-secondary text-light border-secondary">Playlist</div>
</div>
<select id="addToPlaylistPlaylist" class="form-control custom-select border-secondary">
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" data-href="{'cmd': 'addToPlaylist', 'options': []}">Save</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalRenamePlaylist" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><span class="material-icons title-icon">playlist_add</span>Rename playlist</h5>
</div>
<div class="modal-body">
<form class="needs-validation" id="renamePlaylistFrm" novalidate>
<div class="form-group">
<label for="fromInput">From</label>
<input type="text" class="form-control" id="renamePlaylistFrom" readonly>
</div>
<div class="form-group">
<label for="renamePlaylistTo">To</label>
<input type="text" class="form-control" id="renamePlaylistTo">
<div class="invalid-feedback">Invalid filename.</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" data-href="{'cmd': 'renamePlaylist', 'options': []}">Save</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalSettings" tabindex="-1" role="dialog" aria-labelledby="settingsLabel" aria-hidden="true"> <div class="modal fade" id="modalSettings" tabindex="-1" role="dialog" aria-labelledby="settingsLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -570,7 +649,6 @@
</div><!-- /.modal-dialog --> </div><!-- /.modal-dialog -->
</div><!-- /.modal --> </div><!-- /.modal -->
<!-- Modal -->
<div class="modal fade" id="modalAbout" tabindex="-1" role="dialog" aria-labelledby="settingsLabel" aria-hidden="true"> <div class="modal fade" id="modalAbout" tabindex="-1" role="dialog" aria-labelledby="settingsLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -608,7 +686,6 @@
</div><!-- /.modal-dialog --> </div><!-- /.modal-dialog -->
</div><!-- /.modal --> </div><!-- /.modal -->
<!-- Modal -->
<div class="modal fade" id="modalAddstream" tabindex="-1" role="dialog" aria-labelledby="addstreamLabel" aria-hidden="true"> <div class="modal fade" id="modalAddstream" tabindex="-1" role="dialog" aria-labelledby="addstreamLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

View File

@ -38,7 +38,12 @@ app.apps = { "Playback": { "state": "0/-/", "scrollPos": 0 },
"Browse": { "Browse": {
"active": "Database", "active": "Database",
"tabs": { "Filesystem": { "state": "0/-/", "scrollPos": 0 }, "tabs": { "Filesystem": { "state": "0/-/", "scrollPos": 0 },
"Playlists": { "state": "0/-/", "scrollPos": 0 }, "Playlists": {
"active": "All",
"views": { "All": { "state": "0/-/", "scrollPos": 0 },
"Detail": { "state": "0/-/", "scrollPos": 0 }
}
},
"Database": { "Database": {
"active": "Artist", "active": "Artist",
"views": { "Artist": { "state": "0/-/", "scrollPos": 0 }, "views": { "Artist": { "state": "0/-/", "scrollPos": 0 },
@ -75,6 +80,8 @@ var modalSettings = new Modal(document.getElementById('modalSettings'));
var modalAddstream = new Modal(document.getElementById('modalAddstream')); var modalAddstream = new Modal(document.getElementById('modalAddstream'));
var modalSavequeue = new Modal(document.getElementById('modalSavequeue')); var modalSavequeue = new Modal(document.getElementById('modalSavequeue'));
var modalSongDetails = new Modal(document.getElementById('modalSongDetails')); var modalSongDetails = new Modal(document.getElementById('modalSongDetails'));
var modalAddToPlaylist = new Modal(document.getElementById('modalAddToPlaylist'));
var modalRenamePlaylist = new Modal(document.getElementById('modalRenamePlaylist'));
var mainMenu = new Dropdown(document.getElementById('mainMenu')); var mainMenu = new Dropdown(document.getElementById('mainMenu'));
function appPrepare(scrollPos) { function appPrepare(scrollPos) {
@ -102,6 +109,11 @@ function appPrepare(scrollPos) {
} }
scrollTo(scrollPos); scrollTo(scrollPos);
} }
var list = document.getElementById(app.current.app +
(app.current.tab == undefined ? '' : app.current.tab) +
(app.current.view == undefined ? '' : app.current.view) + 'List');
if (list)
list.classList.add('opacity05');
} }
function appGoto(a,t,v,s) { function appGoto(a,t,v,s) {
@ -171,13 +183,6 @@ function appRoute() {
sendAPI({"cmd":"MPD_API_GET_CURRENT_SONG"}, songChange); sendAPI({"cmd":"MPD_API_GET_CURRENT_SONG"}, songChange);
} }
else if (app.current.app == 'Queue' ) { else if (app.current.app == 'Queue' ) {
document.getElementById('QueueList').classList.add('opacity05');
/* if (app.last.app != app.current.app) {
if (app.current.search.length < 2) {
setPagination(app.current.page);
}
}
*/
var btns = document.getElementById('searchqueuetag').getElementsByTagName('button'); var btns = document.getElementById('searchqueuetag').getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) { for (var i = 0; i < btns.length; i++) {
btns[i].classList.remove('active'); btns[i].classList.remove('active');
@ -188,23 +193,23 @@ function appRoute() {
} }
getQueue(); getQueue();
} }
else if (app.current.app == 'Browse' && app.current.tab == 'Playlists') { else if (app.current.app == 'Browse' && app.current.tab == 'Playlists' && app.current.view == 'All') {
document.getElementById('BrowsePlaylistsList').classList.add('opacity05');
sendAPI({"cmd":"MPD_API_GET_PLAYLISTS","data": {"offset": app.current.page, "filter": app.current.filter}}, parsePlaylists); sendAPI({"cmd":"MPD_API_GET_PLAYLISTS","data": {"offset": app.current.page, "filter": app.current.filter}}, parsePlaylists);
doSetFilterLetter('BrowsePlaylistsFilter'); doSetFilterLetter('BrowsePlaylistsFilter');
} }
else if (app.current.app == 'Browse' && app.current.tab == 'Playlists' && app.current.view == 'Detail') {
sendAPI({"cmd":"MPD_API_GET_PLAYLIST_LIST","data": {"offset": app.current.page, "filter": app.current.filter, "uri": app.current.search}}, parsePlaylists);
doSetFilterLetter('BrowsePlaylistsFilter');
}
else if (app.current.app == 'Browse' && app.current.tab == 'Database' && app.current.view == 'Artist') { else if (app.current.app == 'Browse' && app.current.tab == 'Database' && app.current.view == 'Artist') {
document.getElementById('BrowseDatabaseArtistList').classList.add('opacity05');
sendAPI({"cmd":"MPD_API_GET_ARTISTS","data": {"offset": app.current.page, "filter": app.current.filter}}, parseListDBtags); sendAPI({"cmd":"MPD_API_GET_ARTISTS","data": {"offset": app.current.page, "filter": app.current.filter}}, parseListDBtags);
doSetFilterLetter('BrowseDatabaseFilter'); doSetFilterLetter('BrowseDatabaseFilter');
} }
else if (app.current.app == 'Browse' && app.current.tab == 'Database' && app.current.view == 'Album') { else if (app.current.app == 'Browse' && app.current.tab == 'Database' && app.current.view == 'Album') {
document.getElementById('BrowseDatabaseAlbumCards').classList.add('opacity05');
sendAPI({"cmd":"MPD_API_GET_ARTISTALBUMS","data": {"offset": app.current.page, "filter": app.current.filter, "albumartist": app.current.search}}, parseListDBtags); sendAPI({"cmd":"MPD_API_GET_ARTISTALBUMS","data": {"offset": app.current.page, "filter": app.current.filter, "albumartist": app.current.search}}, parseListDBtags);
doSetFilterLetter('BrowseDatabaseFilter'); doSetFilterLetter('BrowseDatabaseFilter');
} }
else if (app.current.app == 'Browse' && app.current.tab == 'Filesystem') { else if (app.current.app == 'Browse' && app.current.tab == 'Filesystem') {
document.getElementById('BrowseFilesystemList').classList.add('opacity05');
sendAPI({"cmd":"MPD_API_GET_FILESYSTEM","data": {"offset": app.current.page, "path": (app.current.search ? app.current.search : "/"), "filter": app.current.filter}}, parseFilesystem); sendAPI({"cmd":"MPD_API_GET_FILESYSTEM","data": {"offset": app.current.page, "path": (app.current.search ? app.current.search : "/"), "filter": app.current.filter}}, parseFilesystem);
// Don't add all songs from root // Don't add all songs from root
if (app.current.search) if (app.current.search)
@ -238,15 +243,11 @@ function appRoute() {
} }
else if (app.current.app == 'Search') { else if (app.current.app == 'Search') {
document.getElementById('searchstr').focus(); document.getElementById('searchstr').focus();
document.getElementById('SearchList').classList.add('opacity05');
if (app.last.app != app.current.app) { if (app.last.app != app.current.app) {
if (app.current.search != '') if (app.current.search != '')
document.getElementById('SearchList').getElementsByTagName('tbody')[0].innerHTML= document.getElementById('SearchList').getElementsByTagName('tbody')[0].innerHTML=
'<tr><td><span class="material-icons">search</span></td>' + '<tr><td><span class="material-icons">search</span></td>' +
'<td colspan="5">Searching...</td></tr>'; '<td colspan="5">Searching...</td></tr>';
// else
// setPagination(app.current.page);
// document.getElementById('searchstr').value = app.current.search;
} }
if (app.current.search.length >= 2) { if (app.current.search.length >= 2) {
@ -255,7 +256,6 @@ function appRoute() {
document.getElementById('SearchList').getElementsByTagName('tbody')[0].innerHTML = ''; document.getElementById('SearchList').getElementsByTagName('tbody')[0].innerHTML = '';
document.getElementById('searchAddAllSongs').setAttribute('disabled', 'disabled'); document.getElementById('searchAddAllSongs').setAttribute('disabled', 'disabled');
document.getElementById('panel-heading-search').innerText = ''; document.getElementById('panel-heading-search').innerText = '';
// setPagination(app.current.page);
document.getElementById('SearchList').classList.remove('opacity05'); document.getElementById('SearchList').classList.remove('opacity05');
} }
@ -313,10 +313,6 @@ function appInit() {
document.getElementById('streamurl').focus(); document.getElementById('streamurl').focus();
}); });
document.getElementById('addstreamFrm').addEventListener('submit', function () {
addStream();
});
addFilterLetter('BrowseFilesystemFilterLetters'); addFilterLetter('BrowseFilesystemFilterLetters');
addFilterLetter('BrowseDatabaseFilterLetters'); addFilterLetter('BrowseDatabaseFilterLetters');
addFilterLetter('BrowsePlaylistsFilterLetters'); addFilterLetter('BrowsePlaylistsFilterLetters');
@ -386,7 +382,17 @@ function appInit() {
} }
}, false); }, false);
document.getElementById('BrowsePlaylistsList').addEventListener('click', function(event) { document.getElementById('BrowsePlaylistsAllList').addEventListener('click', function(event) {
if (event.target.nodeName == 'TD') {
appendQueue('plist', decodeURI(event.target.parentNode.getAttribute("data-uri")), event.target.parentNode.getAttribute("data-name"));
}
else if (event.target.nodeName == 'A') {
event.preventDefault();
showMenu(event.target);
}
}, false);
document.getElementById('BrowsePlaylistsDetailList').addEventListener('click', function(event) {
if (event.target.nodeName == 'TD') { if (event.target.nodeName == 'TD') {
appendQueue('plist', decodeURI(event.target.parentNode.getAttribute("data-uri")), event.target.parentNode.getAttribute("data-name")); appendQueue('plist', decodeURI(event.target.parentNode.getAttribute("data-uri")), event.target.parentNode.getAttribute("data-name"));
} }
@ -917,10 +923,31 @@ function parseFilesystem(obj) {
function parsePlaylists(obj) { function parsePlaylists(obj) {
if (app.current.app !== 'Browse' && app.current.tab !== 'Playlists') if (app.current.app !== 'Browse' && app.current.tab !== 'Playlists')
return; return;
if (app.current.view == 'All') {
document.getElementById('BrowsePlaylistsAllList').classList.remove('hide');
document.getElementById('BrowsePlaylistsDetailList').classList.add('hide');
document.getElementById('btnBrowsePlaylistsAll').parentNode.classList.add('hide');
document.getElementById('btnPlaylistClear').parentNode.classList.add('hide');
} else {
if (obj.uri.indexOf('.') > -1) {
document.getElementById('BrowsePlaylistsDetailList').setAttribute('data-ro', 'true')
document.getElementById('btnPlaylistClear').parentNode.classList.add('hide');
}
else {
document.getElementById('BrowsePlaylistsDetailList').setAttribute('data-ro', 'false');
document.getElementById('btnPlaylistClear').parentNode.classList.remove('hide');
}
document.getElementById('BrowsePlaylistsDetailList').setAttribute('data-uri', obj.uri);
document.getElementById('BrowsePlaylistsDetailList').getElementsByTagName('caption')[0].innerText = 'Playlist: ' + obj.uri;
document.getElementById('BrowsePlaylistsDetailList').classList.remove('hide');
document.getElementById('BrowsePlaylistsAllList').classList.add('hide');
document.getElementById('btnBrowsePlaylistsAll').parentNode.classList.remove('hide');
}
var nrItems = obj.data.length; var nrItems = obj.data.length;
var tbody = document.getElementById(app.current.app + app.current.tab + 'List').getElementsByTagName('tbody')[0]; var tbody = document.getElementById(app.current.app + app.current.tab + app.current.view + 'List').getElementsByTagName('tbody')[0];
var tr = tbody.getElementsByTagName('tr'); var tr = tbody.getElementsByTagName('tr');
if (app.current.view == 'All') {
for (var i = 0; i < nrItems; i++) { for (var i = 0; i < nrItems; i++) {
var uri = encodeURI(obj.data[i].uri); var uri = encodeURI(obj.data[i].uri);
if (tr[i]) if (tr[i])
@ -940,6 +967,33 @@ function parsePlaylists(obj) {
else else
tbody.append(row); tbody.append(row);
} }
}
else if (app.current.view == 'Detail') {
for (var i = 0; i < nrItems; i++) {
var uri = encodeURI(obj.data[i].uri);
if (tr[i])
if (tr[i].getAttribute('data-uri') == uri)
continue;
var songpos = obj.offset + i;
var row = document.createElement('tr');
row.setAttribute('data-type', obj.data[i].type);
row.setAttribute('data-uri', uri);
row.setAttribute('data-name', obj.data[i].name);
row.setAttribute('data-songpos', songpos);
var minutes = Math.floor(obj.data[i].duration / 60);
var seconds = obj.data[i].duration - minutes * 60;
row.innerHTML = '<td>' + (songpos + 1) + '</td>' +
'<td>' + obj.data[i].title + '</td>' +
'<td>' + obj.data[i].artist + '</td>' +
'<td>' + obj.data[i].album + '</td>' +
'<td>' + minutes + ':' + (seconds < 10 ? '0' : '') + seconds +
'</td><td><a href="#" class="material-icons color-darkgrey">playlist_add</a></td>';
if (i < tr.length)
tr[i].replaceWith(row);
else
tbody.append(row);
}
}
var tr_length=tr.length - 1; var tr_length=tr.length - 1;
for (var i = tr_length; i >= nrItems; i --) { for (var i = tr_length; i >= nrItems; i --) {
tr[i].remove(); tr[i].remove();
@ -948,9 +1002,14 @@ function parsePlaylists(obj) {
setPagination(obj.totalEntities); setPagination(obj.totalEntities);
if (nrItems == 0) if (nrItems == 0)
if (app.current.view == 'All')
tbody.innerHTML = '<tr><td><span class="material-icons">error_outline</span></td>' + tbody.innerHTML = '<tr><td><span class="material-icons">error_outline</span></td>' +
'<td colspan="5">No playlists found.</td></tr>' '<td colspan="5">No playlists found.</td></tr>';
document.getElementById(app.current.app + (app.current.tab==undefined ? '' : app.current.tab) + 'List').classList.remove('opacity05'); else
tbody.innerHTML = '<tr><td><span class="material-icons">error_outline</span></td>' +
'<td colspan="5">Empty playlist.</td></tr>';
document.getElementById(app.current.app + app.current.tab + app.current.view + 'List').classList.remove('opacity05');
} }
function parseListDBtags(obj) { function parseListDBtags(obj) {
@ -959,7 +1018,7 @@ function parseListDBtags(obj) {
if (obj.tagtype == 'AlbumArtist') { if (obj.tagtype == 'AlbumArtist') {
document.getElementById('BrowseDatabaseAlbumCards').classList.add('hide'); document.getElementById('BrowseDatabaseAlbumCards').classList.add('hide');
document.getElementById('BrowseDatabaseArtistList').classList.remove('hide'); document.getElementById('BrowseDatabaseArtistList').classList.remove('hide');
document.getElementById('btnBrowseDatabaseArtist').classList.add('hide'); document.getElementById('btnBrowseDatabaseArtist').parentNode.classList.add('hide');
var nrItems = obj.data.length; var nrItems = obj.data.length;
var tbody = document.getElementById(app.current.app + app.current.tab + app.current.view + 'List').getElementsByTagName('tbody')[0]; var tbody = document.getElementById(app.current.app + app.current.tab + app.current.view + 'List').getElementsByTagName('tbody')[0];
var tr = tbody.getElementsByTagName('tr'); var tr = tbody.getElementsByTagName('tr');
@ -994,7 +1053,7 @@ function parseListDBtags(obj) {
} else if (obj.tagtype == 'Album') { } else if (obj.tagtype == 'Album') {
document.getElementById('BrowseDatabaseAlbumCards').classList.remove('hide'); document.getElementById('BrowseDatabaseAlbumCards').classList.remove('hide');
document.getElementById('BrowseDatabaseArtistList').classList.add('hide'); document.getElementById('BrowseDatabaseArtistList').classList.add('hide');
document.getElementById('btnBrowseDatabaseArtist').classList.remove('hide'); document.getElementById('btnBrowseDatabaseArtist').parentNode.classList.remove('hide');
var nrItems = obj.data.length; var nrItems = obj.data.length;
var cardContainer = document.getElementById('BrowseDatabaseAlbumCards'); var cardContainer = document.getElementById('BrowseDatabaseAlbumCards');
var cards = cardContainer.querySelectorAll('.col-md'); var cards = cardContainer.querySelectorAll('.col-md');
@ -1178,6 +1237,74 @@ function parseSongDetails(obj) {
} }
} }
function playlistDetails(uri) {
appGoto('Browse', 'Playlists', 'Detail', '0/-/' + uri);
}
function removeFromPlaylist(uri, pos) {
sendAPI({"cmd": "MPD_API_RM_PLAYLIST_TRACK", "data": {"uri": uri, "track": pos}});
document.getElementById('BrowsePlaylistsDetailList').classList.add('opacity05');
sendAPI({"cmd": "MPD_API_GET_PLAYLIST_LIST", "data": {"offset": app.current.page, "filter": app.current.filter, "uri": app.current.search}}, parsePlaylists);
}
function playlistClear() {
var uri = document.getElementById('BrowsePlaylistsDetailList').getAttribute('data-uri');
sendAPI({"cmd": "MPD_API_PLAYLIST_CLEAR", "data": {"uri": uri}});
document.getElementById('BrowsePlaylistsDetailList').classList.add('opacity05');
sendAPI({"cmd": "MPD_API_GET_PLAYLIST_LIST", "data": {"offset": app.current.page, "filter": app.current.filter, "uri": app.current.search}}, parsePlaylists);
}
function getAllPlaylists(obj) {
var nrItems = obj.data.length;
var playlists = '';
for (var i = 0; i < nrItems; i++) {
playlists += '<option>' + obj.data[i].uri + '</option>';
}
document.getElementById('addToPlaylistPlaylist').innerHTML += playlists;
if (obj.totalEntities > obj.returnedEntities) {
obj.offset += settings.max_elements_per_page;
sendAPI({"cmd":"MPD_API_GET_PLAYLISTS","data": {"offset": obj.offset, "filter": "-"}}, getAllPlaylists);
}
}
function showAddToPlaylist(uri) {
modalAddToPlaylist.show();
document.getElementById('addToPlaylistUri').value = uri;
document.getElementById('addToPlaylistPlaylist').innerHTML = '';
sendAPI({"cmd":"MPD_API_GET_PLAYLISTS","data": {"offset": 0, "filter": "-"}}, getAllPlaylists);
}
function addToPlaylist() {
var uri = document.getElementById('addToPlaylistUri').value;
var plistEl = document.getElementById('addToPlaylistPlaylist');
var plist = plistEl.options[plistEl.selectedIndex].text;
sendAPI({"cmd": "MPD_API_ADD_TO_PLAYLIST", "data": {"uri": uri, "plist": plist}});
modalAddToPlaylist.hide();
}
function showRenamePlaylist(from) {
document.getElementById('renamePlaylistFrm').classList.remove('was-validated');
document.getElementById('renamePlaylistTo').classList.remove('is-invalid');
modalRenamePlaylist.show();
document.getElementById('renamePlaylistFrom').value = from;
document.getElementById('renamePlaylistTo').value = '';
}
function renamePlaylist() {
var from = document.getElementById('renamePlaylistFrom').value;
var to = document.getElementById('renamePlaylistTo').value;
var valid = to.replace(/\w/g,'');
if (to != '' && to != from && valid == '') {
sendAPI({"cmd": "MPD_API_PLAYLIST_RENAME", "data": {"from": from, "to": to}});
modalRenamePlaylist.hide();
sendAPI({"cmd":"MPD_API_GET_PLAYLISTS","data": {"offset": app.current.page, "filter": app.current.filter}}, parsePlaylists);
}
else {
document.getElementById('renamePlaylistTo').classList.add('is-invalid');
document.getElementById('renamePlaylistFrm').classList.add('was-validated');
}
}
function showMenu(el) { function showMenu(el) {
var type = el.getAttribute('data-type'); var type = el.getAttribute('data-type');
var uri = decodeURI(el.getAttribute('data-uri')); var uri = decodeURI(el.getAttribute('data-uri'));
@ -1201,22 +1328,32 @@ function showMenu(el) {
uri + '\',' + nextsongpos + ',\'' + name + '\']}">Add after current playing song</a>' : '') + uri + '\',' + nextsongpos + ',\'' + name + '\']}">Add after current playing song</a>' : '') +
'<a class="dropdown-item" href="#" data-href="{\'cmd\': \'replaceQueue\', \'options\': [\'' + type + '\',\'' + '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'replaceQueue\', \'options\': [\'' + type + '\',\'' +
uri + '\',\'' + name + '\']}">Replace queue</a>' + uri + '\',\'' + name + '\']}">Replace queue</a>' +
// ( type != 'plist' ? '<div class="dropdown-divider"></div><a class="dropdown-item" href="#">Add to playlist</a>' : '') + ( type != 'plist' ? '<div class="dropdown-divider"></div><a class="dropdown-item" href="#" data-href="{\'cmd\': \'showAddToPlaylist\', \'options\': [\'' + uri + '\']}">Add to playlist</a>' : '') +
( type != 'dir' ? '<div class="dropdown-divider"></div>' : '') + ( type != 'dir' ? '<div class="dropdown-divider"></div>' : '') +
( type == 'song' ? '<a class="dropdown-item" data-href="{\'cmd\': \'songDetails\', \'options\': [\'' + uri + '\']}" href="#">Songdetails</a>' : ''); ( type == 'song' ? '<a class="dropdown-item" data-href="{\'cmd\': \'songDetails\', \'options\': [\'' + uri + '\']}" href="#">Songdetails</a>' : '') +
// ( type == 'plist' ? '<a class="dropdown-item" href="#">Show playlist</a>' : ''); ( type == 'plist' ? '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'playlistDetails\', \'options\': [\'' + uri + '\']}">Show playlist</a>' : '');
} }
else if (app.current.app == 'Browse' && app.current.tab == 'Playlists') { else if (app.current.app == 'Browse' && app.current.tab == 'Playlists' && app.current.view == 'All') {
menu += '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'appendQueue\', \'options\': [\'' + type + '\',\'' + menu += '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'appendQueue\', \'options\': [\'' + type + '\',\'' +
uri + '\',\'' + name + '\']}">Append to queue</a>' + uri + '\',\'' + name + '\']}">Append to queue</a>' +
'<a class="dropdown-item" href="#" data-href="{\'cmd\': \'replaceQueue\', \'options\': [\'' + type + '\',\'' + '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'replaceQueue\', \'options\': [\'' + type + '\',\'' +
uri + '\',\'' + name + '\']}">Replace queue</a>' + uri + '\',\'' + name + '\']}">Replace queue</a>' +
'<div class="dropdown-divider"></div>' + '<div class="dropdown-divider"></div>' +
// '<a class="dropdown-item" href="#">Show playlist</a>' + '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'playlistDetails\', \'options\': [\'' + uri + '\']}">Edit playlist</a>' +
// '<a class="dropdown-item" href="#">Rename playlist</a>' + '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'showRenamePlaylist\', \'options\': [\'' + uri + '\']}">Rename playlist</a>' +
'<a class="dropdown-item" href="#" data-href="{\'cmd\': \'delPlaylist\', \'options\': [\'' + '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'delPlaylist\', \'options\': [\'' +
uri + '\',\'' + name + '\']}">Delete playlist</a>'; uri + '\',\'' + name + '\']}">Delete playlist</a>';
} }
else if (app.current.app == 'Browse' && app.current.tab == 'Playlists' && app.current.view == 'Detail') {
menu += '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'appendQueue\', \'options\': [\'' + type + '\',\'' +
uri + '\',\'' + name + '\']}">Append to queue</a>' +
'<a class="dropdown-item" href="#" data-href="{\'cmd\': \'replaceQueue\', \'options\': [\'' + type + '\',\'' +
uri + '\',\'' + name + '\']}">Replace queue</a>' +
( document.getElementById('BrowsePlaylistsDetailList').getAttribute('data-ro') == 'false' ?
'<div class="dropdown-divider"></div>' +
'<a class="dropdown-item" href="#" data-href="{\'cmd\': \'removeFromPlaylist\', \'options\': [\'' + document.getElementById('BrowsePlaylistsDetailList').getAttribute('data-uri') + '\', \'' +
el.parentNode.parentNode.getAttribute('data-songpos') + '\']}">Remove</a>' : '');
}
else if (app.current.app == 'Queue') { else if (app.current.app == 'Queue') {
menu += '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'delQueueSong\', \'options\': [\'single\',' + menu += '<a class="dropdown-item" href="#" data-href="{\'cmd\': \'delQueueSong\', \'options\': [\'single\',' +
el.parentNode.parentNode.getAttribute('data-trackid') + ']}">Remove</a>' + el.parentNode.parentNode.getAttribute('data-trackid') + ']}">Remove</a>' +
@ -1262,7 +1399,7 @@ function sendAPI(request, callback) {
if (ajaxRequest.responseText != '') { if (ajaxRequest.responseText != '') {
var obj = JSON.parse(ajaxRequest.responseText); var obj = JSON.parse(ajaxRequest.responseText);
if (obj.type == 'error') { if (obj.type == 'error') {
showNotification('Error', obj.data, obj.data, 'error'); showNotification('Error', obj.data, obj.data, 'danger');
console.log('Error: ' + obj.data); console.log('Error: ' + obj.data);
} }
else if (obj.type == 'result' && obj.data != 'ok') else if (obj.type == 'result' && obj.data != 'ok')
@ -1284,7 +1421,7 @@ function openLocalPlayer() {
function updateDB() { function updateDB() {
sendAPI({"cmd": "MPD_API_UPDATE_DB"}); sendAPI({"cmd": "MPD_API_UPDATE_DB"});
// showNotification('Updating MPD Database...', '', '', 'success'); showNotification('Updating MPD Database...', '', '', 'success');
} }
function clickPlay() { function clickPlay() {
@ -1439,7 +1576,7 @@ function showNotification(notificationTitle,notificationText,notificationHtml,no
else { else {
alertBox = document.getElementById('alertBox'); alertBox = document.getElementById('alertBox');
} }
alertBox.classList.remove('alert-success', 'alert-error'); alertBox.classList.remove('alert-success', 'alert-danger');
alertBox.classList.add('alert','alert-' + notificationType); alertBox.classList.add('alert','alert-' + notificationType);
alertBox.innerHTML = '<div><strong>' + notificationTitle + '</strong><br/>' + notificationHtml + '</div>'; alertBox.innerHTML = '<div><strong>' + notificationTitle + '</strong><br/>' + notificationHtml + '</div>';
document.getElementsByTagName('main')[0].append(alertBox); document.getElementsByTagName('main')[0].append(alertBox);
@ -1466,7 +1603,7 @@ function notificationsSupported() {
} }
function songChange(obj) { function songChange(obj) {
if (obj.type == 'error') if (obj.type == 'error' || obj.type == 'result')
return; return;
var cur_song = obj.data.title + obj.data.artist + obj.data.album + obj.data.uri + obj.data.currentsongid; var cur_song = obj.data.title + obj.data.artist + obj.data.album + obj.data.uri + obj.data.currentsongid;
if (last_song == cur_song) if (last_song == cur_song)
@ -1479,7 +1616,7 @@ function songChange(obj) {
if(typeof obj.data.artist != 'undefined' && obj.data.artist.length > 0 && obj.data.artist != '-') { if(typeof obj.data.artist != 'undefined' && obj.data.artist.length > 0 && obj.data.artist != '-') {
textNotification += obj.data.artist; textNotification += obj.data.artist;
htmlNotification += '<br/>' + obj.data.artist; htmlNotification += obj.data.artist;
pageTitle += obj.data.artist + ' - '; pageTitle += obj.data.artist + ' - ';
document.getElementById('artist').innerText = obj.data.artist; document.getElementById('artist').innerText = obj.data.artist;
} else { } else {

View File

@ -78,7 +78,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg)
switch(cmd_id) { switch(cmd_id) {
case MPD_API_UNKNOWN: case MPD_API_UNKNOWN:
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown request: %.*s\"}", msg.len, msg.p ); n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"error\", \"data\": \"Unknown request\"}");
break; break;
case MPD_API_SET_SETTINGS: case MPD_API_SET_SETTINGS:
json_scanf(msg.p, msg.len, "{ data: { notificationWeb: %d, notificationPage: %d} }", &state.a, &state.b); json_scanf(msg.p, msg.len, "{ data: { notificationWeb: %d, notificationPage: %d} }", &state.a, &state.b);
@ -140,7 +140,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg)
case MPD_API_UPDATE_DB: case MPD_API_UPDATE_DB:
uint_rc = mpd_run_update(mpd.conn, NULL); uint_rc = mpd_run_update(mpd.conn, NULL);
if (uint_rc > 0) if (uint_rc > 0)
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"Updating MPD Database...\"}"); n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
break; break;
case MPD_API_SET_PAUSE: case MPD_API_SET_PAUSE:
mpd_run_toggle_pause(mpd.conn); mpd_run_toggle_pause(mpd.conn);
@ -254,6 +254,15 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg)
free(p_charbuf2); free(p_charbuf2);
} }
break; break;
case MPD_API_PLAYLIST_RENAME:
je = json_scanf(msg.p, msg.len, "{ data: { from:%Q, to:%Q } }", &p_charbuf1, &p_charbuf2);
if (je == 2) {
mpd_run_rename(mpd.conn, p_charbuf1, p_charbuf2);
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"Renamed playlist %s to %s\"}", p_charbuf1, p_charbuf2);
free(p_charbuf1);
free(p_charbuf2);
}
break;
case MPD_API_GET_PLAYLISTS: case MPD_API_GET_PLAYLISTS:
je = json_scanf(msg.p, msg.len, "{ data: { offset:%u, filter:%Q } }", &uint_buf1, &p_charbuf1); je = json_scanf(msg.p, msg.len, "{ data: { offset:%u, filter:%Q } }", &uint_buf1, &p_charbuf1);
if (je == 2) { if (je == 2) {
@ -261,6 +270,39 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg)
free(p_charbuf1); free(p_charbuf1);
} }
break; break;
case MPD_API_GET_PLAYLIST_LIST:
je = json_scanf(msg.p, msg.len, "{ data: { uri: %Q, offset:%u, filter:%Q } }", &p_charbuf1, &uint_buf1, &p_charbuf2);
if (je == 3) {
n = mympd_put_playlist_list(mpd.buf, p_charbuf1, uint_buf1, p_charbuf2);
free(p_charbuf1);
free(p_charbuf2);
}
break;
case MPD_API_ADD_TO_PLAYLIST:
je = json_scanf(msg.p, msg.len, "{ data: { plist:%Q, uri:%Q } }", &p_charbuf1, &p_charbuf2);
if (je == 2) {
mpd_run_playlist_add(mpd.conn, p_charbuf1, p_charbuf2);
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"Added %s to playlist %s\"}", p_charbuf2, p_charbuf1);
free(p_charbuf1);
free(p_charbuf2);
}
break;
case MPD_API_PLAYLIST_CLEAR:
je = json_scanf(msg.p, msg.len, "{ data: { uri:%Q } }", &p_charbuf1);
if (je == 1) {
mpd_run_playlist_clear(mpd.conn, p_charbuf1);
free(p_charbuf1);
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
}
break;
case MPD_API_RM_PLAYLIST_TRACK:
je = json_scanf(msg.p, msg.len, "{ data: { uri:%Q, track:%u } }", &p_charbuf1, &uint_buf1);
if (je == 2) {
mpd_run_playlist_delete(mpd.conn, p_charbuf1, uint_buf1);
free(p_charbuf1);
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
}
break;
case MPD_API_GET_FILESYSTEM: case MPD_API_GET_FILESYSTEM:
je = json_scanf(msg.p, msg.len, "{ data: { offset:%u, filter:%Q, path:%Q } }", &uint_buf1, &p_charbuf1, &p_charbuf2); je = json_scanf(msg.p, msg.len, "{ data: { offset:%u, filter:%Q, path:%Q } }", &uint_buf1, &p_charbuf1, &p_charbuf2);
if (je == 3) { if (je == 3) {
@ -737,8 +779,10 @@ int mympd_put_current_song(char *buffer) {
char cover[500]; char cover[500];
song = mpd_run_current_song(mpd.conn); song = mpd_run_current_song(mpd.conn);
if(song == NULL) if (song == NULL) {
return 0; len = json_printf(&out,"{type: result, data: ok}");
return len;
}
mympd_get_cover(mpd_song_get_uri(song),cover,500); mympd_get_cover(mpd_song_get_uri(song),cover,500);
@ -1101,6 +1145,57 @@ int mympd_put_playlists(char *buffer, unsigned int offset, char *filter) {
return len; return len;
} }
int mympd_put_playlist_list(char *buffer, char *uri, unsigned int offset, char *filter) {
struct mpd_entity *entity;
unsigned int entity_count = 0;
unsigned int entities_returned = 0;
const char *entityName;
int len;
struct json_out out = JSON_OUT_BUF(buffer, MAX_SIZE);
if (!mpd_send_list_playlist_meta(mpd.conn, uri))
RETURN_ERROR_AND_RECOVER("mpd_send_list_meta");
len = json_printf(&out, "{ type: playlist_detail, data: [ ");
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
const struct mpd_song *song;
entity_count ++;
if (entity_count > offset && entity_count <= offset+MAX_ELEMENTS_PER_PAGE) {
song = mpd_entity_get_song(entity);
entityName = mympd_get_tag(song, MPD_TAG_TITLE);
if (strncmp(filter,"-",1) == 0 || strncasecmp(filter,entityName,1) == 0 ||
( strncmp(filter,"0",1) == 0 && isalpha(*entityName) == 0 )
) {
if (entities_returned ++) len += json_printf(&out,",");
len += json_printf(&out, "{type: song, uri: %Q, album: %Q, artist: %Q, duration: %d, title: %Q, name: %Q }",
mpd_song_get_uri(song),
mympd_get_tag(song, MPD_TAG_ALBUM),
mympd_get_tag(song, MPD_TAG_ARTIST),
mpd_song_get_duration(song),
entityName,
entityName
);
} else {
entity_count --;
}
}
mpd_entity_free(entity);
}
len += json_printf(&out, "], totalEntities: %d, offset: %d, returnedEntities: %d, filter: %Q, uri: %Q }",
entity_count,
offset,
entities_returned,
filter,
uri
);
if (len > MAX_SIZE)
fprintf(stderr,"Buffer truncated\n");
return len;
}
int mympd_search(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr) { int mympd_search(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr) {
struct mpd_song *song; struct mpd_song *song;
unsigned long entity_count = 0; unsigned long entity_count = 0;
@ -1150,7 +1245,8 @@ int mympd_search(char *buffer, char *mpdtagtype, unsigned int offset, char *sear
); );
} }
if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); if (len > MAX_SIZE)
fprintf(stderr,"Buffer truncated\n");
return len; return len;
} }
@ -1180,7 +1276,8 @@ int mympd_search_add(char *buffer,char *mpdtagtype, char *searchstr) {
mpd_song_free(song); mpd_song_free(song);
} }
if (len > MAX_SIZE) fprintf(stderr,"Buffer truncated\n"); if (len > MAX_SIZE)
fprintf(stderr,"Buffer truncated\n");
return len; return len;
} }

View File

@ -52,6 +52,10 @@
X(MPD_API_REPLACE_TRACK) \ X(MPD_API_REPLACE_TRACK) \
X(MPD_API_ADD_PLAYLIST) \ X(MPD_API_ADD_PLAYLIST) \
X(MPD_API_REPLACE_PLAYLIST) \ X(MPD_API_REPLACE_PLAYLIST) \
X(MPD_API_RM_PLAYLIST_TRACK) \
X(MPD_API_PLAYLIST_CLEAR) \
X(MPD_API_PLAYLIST_RENAME) \
X(MPD_API_ADD_TO_PLAYLIST) \
X(MPD_API_PLAY_TRACK) \ X(MPD_API_PLAY_TRACK) \
X(MPD_API_SAVE_QUEUE) \ X(MPD_API_SAVE_QUEUE) \
X(MPD_API_RM_TRACK) \ X(MPD_API_RM_TRACK) \
@ -75,6 +79,7 @@
X(MPD_API_SEND_SHUFFLE) \ X(MPD_API_SEND_SHUFFLE) \
X(MPD_API_GET_STATS) \ X(MPD_API_GET_STATS) \
X(MPD_API_GET_PLAYLISTS) \ X(MPD_API_GET_PLAYLISTS) \
X(MPD_API_GET_PLAYLIST_LIST) \
X(MPD_API_RM_PLAYLIST) \ X(MPD_API_RM_PLAYLIST) \
X(MPD_API_GET_ARTISTALBUMS) \ X(MPD_API_GET_ARTISTALBUMS) \
X(MPD_API_GET_ARTISTALBUMTITLES) \ X(MPD_API_GET_ARTISTALBUMTITLES) \
@ -146,6 +151,7 @@ int mympd_put_settings(char *buffer);
int mympd_put_db_tag(char *buffer, unsigned int offset, char *mpdtagtype, char *mpdsearchtagtype, char *searchstr, char *filter); int mympd_put_db_tag(char *buffer, unsigned int offset, char *mpdtagtype, char *mpdsearchtagtype, char *searchstr, char *filter);
int mympd_put_songs_in_album(char *buffer, char *albumartist, char *album); int mympd_put_songs_in_album(char *buffer, char *albumartist, char *album);
int mympd_put_playlists(char *buffer, unsigned int offset, char *filter); int mympd_put_playlists(char *buffer, unsigned int offset, char *filter);
int mympd_put_playlist_list(char *buffer, char *uri, unsigned int offset, char *filter);
int mympd_put_songdetails(char *buffer, char *uri); int mympd_put_songdetails(char *buffer, char *uri);
void mympd_disconnect(); void mympd_disconnect();
#endif #endif