1
0
mirror of https://github.com/SuperBFG7/ympd synced 2024-06-25 22:23:16 +00:00

New layout for browse card

First steps for playlist view
This commit is contained in:
jcorporation 2018-05-29 00:05:56 +02:00
parent 9d1d406026
commit 6bb24e72f8
5 changed files with 256 additions and 66 deletions

View File

@ -101,7 +101,7 @@ tbody {
float: right !important;
}
#queue-buttons {
#queue-buttons, #browsePlaylistsButtons {
margin-bottom:20px;
}

View File

@ -193,13 +193,74 @@
<button onclick="gotoPage('next',this,event)" id="queuePaginationBottomNext" title="Next Page" type="button" class="btn btn-secondary input-group-append">&raquo;</button>
</div>
</div>
</div>
</div>
<div class="card hide" id="cardBrowse">
<div class="card-header" id="panel-heading-browse">Browse</div>
<div class="card-body">
<div class="card-header" id="panel-heading-browse">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a class="nav-link text-dark" href="#/browse/filesystem/0/" id="cardBrowseNavFilesystem">Filesystem</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="#/browse/playlists/0" id="cardBrowseNavPlaylists">Playlists</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="#/browse/database/0" id="cardBrowseNavDatabase">Database</a>
</li>
</ul>
</div>
<div class="card-body hide" id="cardBrowsePlaylists">
<div class="btn-toolbar collapse show" id="browsePlaylistsButtons" role="toolbar">
<div id="browsePlaylistsPaginationTop" class="btn-group mr-2">
<button onclick="gotoPage('prev',this,event)" id="browsePlaylistsPaginationTopPrev" title="Previous Page" type="button" class="btn btn-secondary">&laquo;</button>
<div class="input-group-append">
<button id="browsePlaylistsPaginationTopPage" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">Page 1 / 1</button>
<div class="dropdown-menu bg-dark px-2" id="browsePlaylistsPaginationTopPages">
</div>
</div>
<button onclick="gotoPage('next',this,event)" id="browsePlaylistsPaginationTopNext" title="Next Page" type="button" class="btn btn-secondary input-group-append">&raquo;</button>
</div>
</div>
<div class="table-responsive-md">
<table id="browsePlaylistsList" class="table table-hover table-sm">
<col class="tblnum"/>
<col class="tbltitle"/>
<col class="tbllastmodified"/>
<col class="tblaction"/>
<thead>
<tr>
<th></th>
<th>Playlist</th>
<th>Last modified</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="btn-toolbar" id="browsePlaylistsButtonsBottom" role="toolbar">
<div class="btn-group mr-2">
<button type="button" class="btn btn-secondary" onclick="scrollToTop()" title="To top">
<span class="material-icons">keyboard_arrow_up</span>
</button>
</div>
<div id="browsePlaylistsPaginationBottom" class="btn-group mr-2 dropup">
<button onclick="gotoPage('prev',this,event)" id="browsePlaylistsPaginationBottomPrev" title="Previous Page" type="button" class="btn btn-secondary">&laquo;</button>
<div class="input-group-append">
<button id="browsePlaylistsPaginationBottomPage" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">Page 1 / 1</button>
<div class="dropdown-menu bg-dark px-2" id="browsePlaylistsPaginationBottomPages">
</div>
</div>
<button onclick="gotoPage('next',this,event)" id="browsePlaylistsPaginationBottomNext" title="Next Page" type="button" class="btn btn-secondary input-group-append">&raquo;</button>
</div>
</div>
</div>
<div class="card-body hide" id="cardBrowseDatabase">
</div>
<div class="card-body hide" id="cardBrowseFilesystem">
<ol id="browseBreadcrumb" class="breadcrumb">
</ol>
@ -211,7 +272,7 @@
</div>
<div class="table-responsive-md">
<table id="browseList" class="table table-hover table-sm">
<table id="browseFilesystemList" class="table table-hover table-sm">
<col class="tblnum"/>
<col class="tbltitle"/>
<col class="tblartist"/>
@ -240,7 +301,7 @@
</div>
<div class="card hide" id="cardSearch">
<div class="card-header" id="pnael-heading-search">Search</div>
<div class="card-header" id="panel-heading-search">Search</div>
<div class="card-body">
<div class="table-responsive-md">
<table id="searchList" class="table table-hover table-sm">
@ -272,7 +333,7 @@
<div class="d-flex flex-fill navbar-nav" id="navbar-bottom">
<div class="nav-item flex-fill text-center" id="navPlayback"><a class="nav-link" href="#/playing/">Playback</a></div>
<div class="nav-item flex-fill text-center" id="navQueue"><a class="nav-link" href="#/queue/0">Queue</a></div>
<div class="nav-item flex-fill text-center" id="navBrowse"><a class="nav-link" href="#/browse/0/">Browse</a></div>
<div class="nav-item flex-fill text-center" id="navBrowse"><a class="nav-link" href="#/browse/filesystem/0/">Browse</a></div>
</div>
</nav>
</footer>
@ -381,12 +442,12 @@
</button>
</div>
<div class="modal-body">
<h4><a style="color:#28a745" href="https://github.com/jcorporation/ympd"><span class="material-icons">play_circle_outline</span> myMPD</a>&nbsp;&ndash;&nbsp;<small>MPD Web GUI - written in C, utilizing Websockets and Bootstrap/JS</small></h4>
<h4><a class="text-success" href="https://github.com/jcorporation/ympd"><span class="material-icons">play_circle_outline</span> myMPD</a>&nbsp;&ndash;&nbsp;<small>MPD Web GUI - written in C, utilizing Websockets and Bootstrap/JS</small></h4>
<p>myMPD is a lightweight MPD web client that runs without a dedicated webserver or interpreter. It's tuned for minimal resource usage and requires only very litte dependencies. myMPD is a fork of <a style="color:#28a745" href="http://www.ympd.org">ympd</a>.</p>
<ul>
<li>Version: 2.1.1</li>
<li>Homepage: <a style="color:#28a745" target="_blank" href="https://github.com/jcorporation/mympd">https://github.com/jcorporation/mympd</a></li>
<li>Autor: Juergen Mang &lt;<a style="color:#28a745" href="mailto:mail@jcgames.de">mail@jcgames.de</a>&gt;</li>
<li>Homepage: <a class="text-success" target="_blank" href="https://github.com/jcorporation/mympd">https://github.com/jcorporation/mympd</a></li>
<li>Autor: Juergen Mang &lt;<a class="text-success" href="mailto:mail@jcgames.de">mail@jcgames.de</a>&gt;</li>
</ul>
<hr/>
<h5>Database Statistics</h5>

View File

@ -48,6 +48,12 @@ var app = $.sammy(function() {
$('#cardSearch').addClass('hide');
$('.pagination').addClass('hide');
$('#searchqueue > input').val('');
$('#cardBrowsePlaylists').addClass('hide');
$('#cardBrowseDatabase').addClass('hide');
$('#cardBrowseFilesystem').addClass('hide');
$('#cardBrowseNavPlaylists').removeClass('active');
$('#cardBrowseNavDatabase').removeClass('active');
$('#cardBrowseNavFilesystem').removeClass('active');
pagination = 0;
browsepath = '';
}
@ -70,14 +76,39 @@ var app = $.sammy(function() {
socket.send('MPD_API_GET_QUEUE,'+pagination);
});
this.get(/\#\/browse\/(\d+)\/(.*)/, function() {
this.get(/\#\/browse\/playlists\/(\d+)/, function() {
prepare();
browsepath = this.params['splat'][1];
pagination = parseInt(this.params['splat'][0]);
current_app = 'browse';
current_app = 'browsePlaylists';
$('#navBrowse').addClass('active');
$('#cardBrowse').removeClass('hide');
$('#browseList').find("tr:gt(0)").remove();
$('#cardBrowsePlaylists').removeClass('hide');
$('#cardBrowseNavPlaylists').addClass('active');
socket.send('MPD_API_GET_PLAYLISTS,'+pagination);
});
this.get(/\#\/browse\/database\/(\d+)/, function() {
prepare();
browsepath = this.params['splat'][1];
pagination = parseInt(this.params['splat'][0]);
current_app = 'browseDatabase';
$('#navBrowse').addClass('active');
$('#cardBrowse').removeClass('hide');
$('#cardBrowseDatabase').removeClass('hide');
$('#cardBrowseNavDatabase').addClass('active');
});
this.get(/\#\/browse\/filesystem\/(\d+)\/(.*)/, function() {
prepare();
browsepath = this.params['splat'][1];
pagination = parseInt(this.params['splat'][0]);
current_app = 'browseFilesystem';
$('#navBrowse').addClass('active');
$('#cardBrowse').removeClass('hide');
$('#cardBrowseFilesystem').removeClass('hide');
$('#cardBrowseNavFilesystem').addClass('active');
$('#browseFilesystemList').find("tr:gt(0)").remove();
$('#browseBreadcrumb').empty().append("<li class=\"breadcrumb-item\"><a uri=\"\" onclick=\"set_filter('')\">root</a></li>");
socket.send('MPD_API_GET_BROWSE,'+pagination+','+(browsepath ? browsepath : "/"));
// Don't add all songs from root
@ -91,7 +122,7 @@ var app = $.sammy(function() {
} else {
add_all_songs.addClass('hide');
}
$('#panel-heading-browse').text("Browse database: /"+browsepath);
//$('#panel-heading-browse').text("Browse database: /"+browsepath);
var path_array = browsepath.split('/');
var full_path = "";
@ -243,37 +274,7 @@ function webSocketConnect() {
"<td></td><td></td></tr>"
);
}
totalPages=Math.ceil(obj.totalSongs / MAX_ELEMENTS_PER_PAGE);
if (totalPages==0) { totalPages=1; }
$('#queuePaginationTopPage').text('Page '+(pagination / MAX_ELEMENTS_PER_PAGE + 1)+' / '+totalPages);
$('#queuePaginationBottomPage').text('Page '+(pagination / MAX_ELEMENTS_PER_PAGE + 1)+' / '+totalPages);
if (totalPages > 1) {
$('#queuePaginationTopPage').removeClass('disabled');
$('#queuePaginationBottomPage').removeClass('disabled');
$('#queuePaginationTopPages').empty();
$('#queuePaginationBottomPages').empty();
for (var i=0;i<totalPages;i++) {
$('#queuePaginationTopPages').append('<button onclick="gotoPage('+(i * MAX_ELEMENTS_PER_PAGE)+',this,event)" type="button" class="mr-1 mb-1 btn btn-secondary">'+(i+1)+'</button>');
$('#queuePaginationBottomPages').append('<button onclick="gotoPage('+(i * MAX_ELEMENTS_PER_PAGE)+',this,event)" type="button" class="mr-1 mb-1 btn btn-secondary">'+(i+1)+'</button>');
}
} else {
$('#queuePaginationTopPage').addClass('disabled');
$('#queuePaginationBottomPage').addClass('disabled');
}
if(obj.totalSongs > pagination + MAX_ELEMENTS_PER_PAGE) {
$('#queuePaginationTopNext').removeClass('disabled');
$('#queuePaginationBottomNext').removeClass('disabled');
} else {
$('#queuePaginationTopNext').addClass('disabled');
$('#queuePaginationBottomNext').addClass('disabled');
}
if(pagination > 0) {
$('#queuePaginationTopPrev').removeClass('disabled');
$('#queuePaginationBottomPrev').removeClass('disabled');
} else {
$('#queuePaginationTopPrev').addClass('disabled');
$('#queuePaginationBottomPrev').addClass('disabled');
}
setPagination(obj.totalSongs);
if ( isTouch ) {
$('#queueList > tbody > tr > td:last-child').append(
@ -316,10 +317,52 @@ function webSocketConnect() {
},
});
break;
case 'playlists':
if(current_app !== 'browsePlaylists')
break;
var nrItems=0;
for (var item in obj.data) {
nrItems++;
var d = new Date(obj.data[item].last_modified * 1000);
$('#'+current_app+'List > tbody').append(
'<tr uri="' + encodeURI(obj.data[item].plist) + '">' +
'<td><span class="material-icons">list</span></td>' +
'<td><a>' + basename(obj.data[item].plist) + '</a></td>' +
'<td>'+d.toUTCString()+'</td><td></td></tr>'
);
}
setPagination(obj.totalEntities);
if ( isTouch ) {
$('#'+current_app+'List > tbody > tr > td:last-child').append(
"<a class=\"pull-right btn-group-hover color-darkgrey\" href=\"#/\" " +
"onclick=\"trash($(this).parents('tr'));\">" +
"<span class=\"material-icons\">delete</span></a>");
} else {
$('#'+current_app+'List > tbody > tr').on({
mouseover: function(){
if($(this).children().last().has("a").length == 0)
$(this).children().last().append(
"<a class=\"pull-right btn-group-hover color-darkgrey\" href=\"#/\" " +
"onclick=\"trash($(this).parents('tr'));\">" +
"<span class=\"material-icons\">delete</span></a>");
},
mouseleave: function(){
var doomed = $(this);
$(this).children().last().find("a").stop().remove();
}
});
};
$('#'+current_app+'List > tbody > tr').on({
click: function() {
socket.send("MPD_API_ADD_PLAYLIST," + decodeURI($(this).attr("uri")));
showNotification('"' + $('td:nth-last-child(3)', this).text() + '" added','','','success');
}
});
break;
case 'search':
$('#searchList').find("tr:gt(0)").remove();
case 'browse':
if(current_app !== 'browse' && current_app !== 'search')
if(current_app !== 'browseFilesystem' && current_app !== 'search')
break;
/* The use of encodeURI() below might seem useless, but it's not. It prevents
@ -383,7 +426,7 @@ function webSocketConnect() {
);
break;
case 'wrap':
if(current_app == 'browse') {
if(current_app == 'browseFilesystem') {
$('#browseNext').removeClass('disabled');
$('#browsePagination').removeClass('hide');
} else {
@ -445,10 +488,10 @@ function webSocketConnect() {
case 'dir':
pagination = 0;
browsepath = $(this).attr("uri");
$("#browse > a").attr("href", '#/browse/'+pagination+'/'+browsepath);
$('#filter > a').attr("href", '#/browse/'+pagination+'/'+browsepath);
app.setLocation('#/browse/'+pagination+'/'+browsepath);
set_filter('');
$("#browseFilesystemList > a").attr("href", '#/browse/filesystem/'+pagination+'/'+browsepath);
$('#filter > a').attr("href", '#/browse/filesystem/'+pagination+'/'+browsepath);
app.setLocation('#/browse/filesystem/'+pagination+'/'+browsepath);
set_filter('');
break;
case 'song':
socket.send("MPD_API_ADD_TRACK," + decodeURI($(this).attr("uri")));
@ -466,9 +509,9 @@ function webSocketConnect() {
click: function() {
pagination = 0;
browsepath = $(this).attr("uri");
$("#browse > a").attr("href", '#/browse/'+pagination+'/'+browsepath);
$('#filter > a').attr("href", '#/browse/'+pagination+'/'+browsepath);
app.setLocation('#/browse/'+pagination+'/'+browsepath);
$("#browseFilesystemList > a").attr("href", '#/browse/filesystem/'+pagination+'/'+browsepath);
$('#filter > a').attr("href", '#/browse/filesystem/'+pagination+'/'+browsepath);
app.setLocation('#/browse/filesystem/'+pagination+'/'+browsepath);
set_filter('');
}
});
@ -671,6 +714,44 @@ function get_appropriate_ws_url()
return pcol + u[0] + separator + "ws";
}
function setPagination(number) {
var totalPages=Math.ceil(number / MAX_ELEMENTS_PER_PAGE);
if (totalPages==0) { totalPages=1; }
$('#'+current_app+'PaginationTopPage').text('Page '+(pagination / MAX_ELEMENTS_PER_PAGE + 1)+' / '+totalPages);
$('#'+current_app+'PaginationBottomPage').text('Page '+(pagination / MAX_ELEMENTS_PER_PAGE + 1)+' / '+totalPages);
if (totalPages > 1) {
$('#'+current_app+'PaginationTopPage').removeClass('disabled');
$('#'+current_app+'PaginationBottomPage').removeClass('disabled');
$('#'+current_app+'PaginationTopPages').empty();
$('#'+current_app+'PaginationBottomPages').empty();
for (var i=0;i<totalPages;i++) {
$('#'+current_app+'PaginationTopPages').append('<button onclick="gotoPage('+(i * MAX_ELEMENTS_PER_PAGE)+',this,event)" type="button" class="mr-1 mb-1 btn btn-secondary">'+(i+1)+'</button>');
$('#'+current_app+'PaginationBottomPages').append('<button onclick="gotoPage('+(i * MAX_ELEMENTS_PER_PAGE)+',this,event)" type="button" class="mr-1 mb-1 btn btn-secondary">'+(i+1)+'</button>');
}
} else {
$('#'+current_app+'PaginationTopPage').addClass('disabled');
$('#'+current_app+'PaginationBottomPage').addClass('disabled');
}
if(number > pagination + MAX_ELEMENTS_PER_PAGE) {
$('#'+current_app+'PaginationTopNext').removeClass('disabled');
$('#'+current_app+'PaginationBottomNext').removeClass('disabled');
$('#'+current_app+'ButtonsBottom').removeClass('hide');
} else {
$('#'+current_app+'PaginationTopNext').addClass('disabled');
$('#'+current_app+'PaginationBottomNext').addClass('disabled');
$('#'+current_app+'ButtonsBottom').addClass('hide');
}
if(pagination > 0) {
$('#'+current_app+'PaginationTopPrev').removeClass('disabled');
$('#'+current_app+'PaginationBottomPrev').removeClass('disabled');
} else {
$('#'+current_app+'PaginationTopPrev').addClass('disabled');
$('#'+current_app+'PaginationBottomPrev').addClass('disabled');
}
}
function updateVolumeIcon(volume) {
if (volume == -1) {
$('#volumePrct').text('Volumecontrol disabled');
@ -884,11 +965,8 @@ $('.page-link').on('click', function (e) {
}
switch(current_app) {
case "queue":
app.setLocation('#/queue/'+pagination);
break;
case "browse":
app.setLocation('#/browse/'+pagination+'/'+browsepath);
case "browseFilesystem":
app.setLocation('#/browse/filesystem/'+pagination+'/'+browsepath);
break;
}
e.preventDefault();
@ -925,9 +1003,15 @@ function gotoPage(x,element,event) {
app.setLocation('#/queue/'+pagination);
}
break;
case "browse":
app.setLocation('#/browse/'+pagination+'/'+browsepath);
case "browseFilesystem":
app.setLocation('#/browse/filesystem/'+pagination+'/'+browsepath);
break;
case "browsePlaylists":
app.setLocation('#/browse/playlists/'+pagination);
break;
case "browseDatabase":
app.setLocation('#/browse/database/'+pagination);
break;
}
event.preventDefault();
}
@ -1071,15 +1155,15 @@ function set_filter (c) {
}
function add_filter () {
$('#filter').append('<a class="btn btn-secondary" onclick="set_filter(\'\')" href="#/browse/'+pagination+'/'+browsepath+'">All</a>');
$('#filter').append('<a class="btn btn-secondary" id="fnum" onclick="set_filter(\'num\')" href="#/browse/'+pagination+'/'+browsepath+'">#</a>');
$('#filter').append('<a class="btn btn-secondary" onclick="set_filter(\'\')" href="#/browse/filesystem/'+pagination+'/'+browsepath+'">All</a>');
$('#filter').append('<a class="btn btn-secondary" id="fnum" onclick="set_filter(\'num\')" href="#/browse/filesystem/'+pagination+'/'+browsepath+'">#</a>');
for (i = 65; i <= 90; i++) {
var c = String.fromCharCode(i);
$('#filter').append('<a class="btn btn-secondary" id="f' + c + '" onclick="set_filter(\'' + c + '\');" href="#/browse/' + pagination + '/' + browsepath + '">' + c + '</a>');
$('#filter').append('<a class="btn btn-secondary" id="f' + c + '" onclick="set_filter(\'' + c + '\');" href="#/browse/filesystem/' + pagination + '/' + browsepath + '">' + c + '</a>');
}
$('#filter').append('<a class="btn btn-secondary material-icons" id="fplist" onclick="set_filter(\'plist\')" href="#/browse/'+pagination+'/'+browsepath+'">list</a>');
$('#filter').append('<a class="btn btn-secondary material-icons" id="fplist" onclick="set_filter(\'plist\')" href="#/browse/filesystem/'+pagination+'/'+browsepath+'">list</a>');
}
function chVolume (increment) {

View File

@ -170,6 +170,10 @@ int callback_mpd(struct mg_connection *c)
if(sscanf(c->content, "MPD_API_GET_QUEUE,%u", &uint_buf))
n = mpd_put_queue(mpd.buf, uint_buf);
break;
case MPD_API_GET_PLAYLISTS:
if(sscanf(c->content, "MPD_API_GET_PLAYLISTS,%u", &uint_buf))
n = mpd_put_playlists(mpd.buf, uint_buf);
break;
case MPD_API_GET_BROWSE:
p_charbuf = strdup(c->content);
if(strcmp(strtok(p_charbuf, ","), "MPD_API_GET_BROWSE"))
@ -779,6 +783,45 @@ int mpd_put_browse(char *buffer, char *path, unsigned int offset)
return cur - buffer;
}
int mpd_put_playlists(char *buffer, unsigned int offset)
{
char *cur = buffer;
const char *end = buffer + MAX_SIZE;
struct mpd_playlist *pl;
unsigned int entity_count = 0;
if (!mpd_send_list_playlists(mpd.conn))
RETURN_ERROR_AND_RECOVER("mpd_send_lists_playlists");
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"playlists\",\"data\":[ ");
while((pl = mpd_recv_playlist(mpd.conn)) != NULL) {
entity_count++;
if(entity_count > offset && entity_count <= offset+MAX_ELEMENTS_PER_PAGE) {
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"playlist\",\"plist\":");
cur += json_emit_quoted_str(cur, end - cur, mpd_playlist_get_path(pl));
cur += json_emit_raw_str(cur, end - cur, ",\"last_modified\":");
cur += json_emit_int(cur, end - cur, mpd_playlist_get_last_modified(pl));
cur += json_emit_raw_str(cur, end - cur, "},");
}
mpd_playlist_free(pl);
}
if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(mpd.conn)) {
fprintf(stderr, "MPD mpd_send_list_playlists: %s\n", mpd_connection_get_error_message(mpd.conn));
mpd.conn_state = MPD_FAILURE;
return 0;
}
/* remove last ',' */
cur--;
cur += json_emit_raw_str(cur, end - cur, "],\"totalEntities\":");
cur += json_emit_int(cur, end - cur, entity_count);
cur += json_emit_raw_str(cur, end - cur, "}");
return cur - buffer;
}
int mpd_search(char *buffer, char *searchstr)
{
char *cur = buffer;

View File

@ -76,7 +76,8 @@
X(MPD_API_SEND_SHUFFLE) \
X(MPD_API_GET_STATS) \
X(MPD_API_SET_MIXRAMPDB) \
X(MPD_API_SET_MIXRAMPDELAY)
X(MPD_API_SET_MIXRAMPDELAY) \
X(MPD_API_GET_PLAYLISTS)
enum mpd_cmd_ids {
MPD_CMDS(GEN_ENUM)
@ -125,6 +126,7 @@ int mpd_put_state(char *buffer, int *current_song_id, int *next_song_id, unsigne
int mpd_put_outputs(char *buffer, int putnames);
int mpd_put_current_song(char *buffer);
int mpd_put_queue(char *buffer, unsigned int offset);
int mpd_put_playlists(char *buffer, unsigned int offset);
int mpd_put_browse(char *buffer, char *path, unsigned int offset);
int mpd_search(char *buffer, char *searchstr);
int mpd_search_queue(char *buffer, char *mpdtagtype, unsigned int offset, char *searchstr);