Feat: interface for advanced search

This commit is contained in:
jcorporation 2018-11-27 13:05:51 +00:00
parent 63039b15ec
commit 9c0d84d2ad
5 changed files with 204 additions and 20 deletions

View File

@ -321,3 +321,7 @@ div.key {
heigth: 20px;
text-align: center;
}
ol#searchCrumb {
padding: .5rem;
}

View File

@ -142,7 +142,7 @@
</div>
</div>
</div>
<form id="searchqueue">
<form id="searchqueue" class="flex-grow-1">
<div class="input-group mr-2">
<input type="text" class="form-control" placeholder="Search Queue" id="searchqueuestr"/>
<div class="input-group-append">
@ -164,9 +164,9 @@
</div>
<button data-href='{"cmd": "gotoPage", "options": ["next"]}' id="QueueCurrentPaginationTopNext" title="Next Page" type="button" class="btn btn-secondary input-group-append">&raquo;</button>
</div>
<div class="btn-group mr-2 featTags">
<div class="btn-group featTags">
<button id="QueueCurrentColsBtn" class="btn btn-secondary dropdown-toggle material-icons" type="button" data-toggle="dropdown">settings</button>
<div class="dropdown-menu bg-dark px-2" id="QueueCurrentColsDropdown"><form></form>
<div class="dropdown-menu bg-dark px-2 dropdown-menu-right" id="QueueCurrentColsDropdown"><form></form>
<button data-href='{"cmd": "saveCols", "options": ["QueueCurrent"]}' class="btn btn-success btn-block btn-sm mt-2">Apply</button>
</div>
</div>
@ -517,17 +517,26 @@
</div>
<div class="card-body">
<div class="btn-toolbar card-toolbar" id="SearchButtons">
<form id="search">
<form id="search" class="flex-grow-1">
<div class="input-group mr-2">
<input type="text" class="form-control" placeholder="Search" id="searchstr"/>
<div class="input-group-append">
<div class="input-group-prepend">
<button title="Select tags to search" class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">
<span class="material-icons">search</span>
<span id="searchtagsdesc">Any Tag</span>
</button>
<div class="dropdown-menu bg-dark dropdown-menu-right px-2" id="searchtags">
<div class="dropdown-menu bg-dark px-2" id="searchtags">
</div>
</div>
<div class="input-group-prepend featAdvsearch">
<select class="form-control" id="searchMatch">
<option value="contains">contains</option>
<option value="==">==</option>
<option value="=~">=~</option>
<option value="!=">!=</option>
<option value="!~">!~</option>
</select>
</div>
<input type="text" class="form-control" placeholder="Search" id="searchstr"/>
</div>
</form>
<div class="input-group mr-2">
@ -550,15 +559,17 @@
</div>
<button data-href='{"cmd": "gotoPage", "options": ["next"]}' id="SearchPaginationTopNext" title="Next Page" type="button" class="btn btn-secondary input-group-append">&raquo;</button>
</div>
<div class="btn-group mr-2 featTags">
<div class="btn-group featTags">
<button id="SearchColsBtn" class="btn btn-secondary dropdown-toggle material-icons" type="button" data-toggle="dropdown">settings</button>
<div class="dropdown-menu bg-dark px-2" id="SearchColsDropdown"><form></form>
<div class="dropdown-menu bg-dark px-2 dropdown-menu-right" id="SearchColsDropdown"><form></form>
<button data-href='{"cmd": "saveCols", "options": ["Search"]}' class="btn btn-success btn-block btn-sm mt-2">Apply</button>
</div>
</div>
</div>
<ol class="FeatAdvsearch breadcrumb" id="searchCrumb"></ol>
<div class="table-responsive-md">
<table id="SearchList" class="table table-hover table-sm">
<table id="SearchList" class="table table-hover table-sm" data-sort="">
<thead>
<tr>
<th></th>

View File

@ -276,8 +276,30 @@ function appRoute() {
else if (app.current.app == 'Search') {
var searchstrEl = document.getElementById('searchstr');
searchstrEl.focus();
if (searchstrEl.value == '' && app.current.search != '')
searchstrEl.value = app.current.search;
if (settings.featAdvsearch) {
var crumbs = '';
var elements = app.current.search.substring(1, app.current.search.length - 1).split(' AND ');
for (var i = 0; i < elements.length - 1 ; i++) {
var value = elements[i].substring(1, elements[i].length - 1);
crumbs += '<button data-filter="' + encodeURI(value) + '" class="btn btn-light mr-2">' + value + '<span class="ml-2 badge badge-secondary">&times</span></button>';
}
document.getElementById('searchCrumb').innerHTML = crumbs;
if (searchstrEl.value == '' && elements.length >= 1) {
var lastEl = elements[elements.length - 1].substring(1, elements[elements.length - 1].length - 1);
var lastElValue = lastEl.substring(lastEl.indexOf('\'') + 1, lastEl.length - 1);
if (searchstrEl.value != lastElValue)
document.getElementById('searchCrumb').innerHTML += '<button data-filter="' + encodeURI(lastEl) +'" class="btn btn-light mr-2">' + lastEl + '<span href="#" class="ml-2 badge badge-secondary">&times;</span></button>';
var match = lastEl.substring(lastEl.indexOf(' ') + 1);
match = match.substring(0, match.indexOf(' '));
if (match == '')
match = 'contains';
document.getElementById('searchMatch').value = match;
}
}
else {
if (searchstrEl.value == '' && app.current.search != '')
searchstrEl.value = app.current.search;
}
if (app.last.app != app.current.app) {
if (app.current.search != '') {
var colspan = settings['cols' + app.current.app].length;
@ -289,12 +311,33 @@ function appRoute() {
}
if (app.current.search.length >= 2) {
sendAPI({"cmd": "MPD_API_DATABASE_SEARCH", "data": { "plist": "", "offset": app.current.page, "filter": app.current.filter, "searchstr": app.current.search}}, parseSearch);
if (settings.featAdvsearch) {
var sort = document.getElementById('SearchList').getAttribute('data-sort');
var sortdesc = false;
if (sort == '') {
if (settings.tags.includes('Title'))
sort = 'Title';
else
sort = 'Filename';
document.getElementById('SearchList').setAttribute('data-sort', sort);
}
else {
if (sort.indexOf('-') == 0) {
sortdesc = true;
sort = sort.substring(1);
}
}
sendAPI({"cmd": "MPD_API_DATABASE_SEARCH_ADV", "data": { "plist": "", "offset": app.current.page, "sort": sort, "sortdesc": sortdesc, "expression": app.current.search}}, parseSearch);
}
else {
sendAPI({"cmd": "MPD_API_DATABASE_SEARCH", "data": { "plist": "", "offset": app.current.page, "filter": app.current.filter, "searchstr": app.current.search}}, parseSearch);
}
} else {
document.getElementById('SearchList').getElementsByTagName('tbody')[0].innerHTML = '';
document.getElementById('searchAddAllSongs').setAttribute('disabled', 'disabled');
document.getElementById('searchAddAllSongsBtn').setAttribute('disabled', 'disabled');
document.getElementById('panel-heading-search').innerText = '';
document.getElementById('cardFooterSearch').innerText = '';
document.getElementById('SearchList').classList.remove('opacity05');
setPagination(0);
}
@ -608,8 +651,81 @@ function appInit() {
document.getElementById('searchstr').addEventListener('keyup', function(event) {
if (event.key == 'Escape')
this.blur();
else if (event.key == 'Enter' && settings.featAdvsearch) {
if (this.value != '') {
var match = document.getElementById('searchMatch');
var li = document.createElement('button');
li.classList.add('btn', 'btn-light', 'mr-2');
li.setAttribute('data-filter', encodeURI(app.current.filter + ' ' + match.options[match.selectedIndex].value +' \'' + this.value + '\''));
li.innerHTML = app.current.filter + ' ' + match.options[match.selectedIndex].value + ' \'' + this.value + '\'<span class="ml-2 badge badge-secondary">&times;</span>';
this.value = '';
document.getElementById('searchCrumb').appendChild(li);
}
else
search(this.value);
}
else
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + this.value);
search(this.value);
}, false);
document.getElementById('searchCrumb').addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
if (event.target.nodeName == 'SPAN') {
event.target.parentNode.remove();
search('');
}
else if (event.target.nodeName == 'BUTTON') {
var value = decodeURI(event.target.getAttribute('data-filter'));
document.getElementById('searchstr').value = value.substring(value.indexOf('\'') + 1, value.length - 1);
var filter = value.substring(0, value.indexOf(' '));
selectTag('searchtags', 'searchtagsdesc', filter);
var match = value.substring(value.indexOf(' ') + 1);
match = match.substring(0, match.indexOf(' '));
document.getElementById('searchMatch').value = match;
event.target.remove();
search(document.getElementById('searchstr').value);
}
}, false);
document.getElementById('searchMatch').addEventListener('change', function(event) {
search(document.getElementById('searchstr').value);
}, false);
document.getElementById('SearchList').getElementsByTagName('tr')[0].addEventListener('click', function(event) {
if (settings.featAdvsearch) {
if (event.target.nodeName == 'TH') {
var col = event.target.getAttribute('data-col');
if (col == 'Duration')
return;
var sortcol = document.getElementById('SearchList').getAttribute('data-sort');
var sortdesc = true;
if (sortcol == col || sortcol == '-' + col) {
if (sortcol.indexOf('-') == 0) {
sortdesc = true;
sortcol = sortcol.substring(1);
}
else
sortdesc = false;
}
if (sortdesc == false) {
sortcol = '-' + col;
sortdesc = true;
}
else {
sortdesc = false;
sortcol = col;
}
var s = document.getElementById('SearchList').getElementsByClassName('sort-dir');
for (var i = 0; i < s.length; i++)
s[i].remove();
document.getElementById('SearchList').setAttribute('data-sort', sortcol);
event.target.innerHTML = col + '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>';
appRoute();
}
}
}, false);
document.getElementById('BrowseDatabaseByTagDropdown').addEventListener('click', function(event) {
@ -634,8 +750,8 @@ function appInit() {
window.addEventListener('focus', function() {
sendAPI({"cmd": "MPD_API_PLAYER_STATE"}, parseState);
}, false);
document.addEventListener('keydown', function(event) {
if (event.target.tagName == 'INPUT' || event.target.tagName == 'SELECT' ||
event.ctrlKey || event.altKey)
@ -712,6 +828,28 @@ function parseCmd(event, href) {
}
}
function search(x) {
if (settings.featAdvsearch) {
var expression = '(';
var crumbs = document.getElementById('searchCrumb').children;
for (var i = 0; i < crumbs.length; i++) {
expression += '(' + decodeURI(crumbs[i].getAttribute('data-filter')) + ')';
if (x != '') expression += ' AND ';
}
if (x != '') {
var match = document.getElementById('searchMatch');
expression += '(' + app.current.filter + ' ' + match.options[match.selectedIndex].value + ' \'' + x +'\'))';
}
else
expression += ')';
if (expression.length <= 2)
expression = '';
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + encodeURI(expression));
}
else
appGoto('Search', undefined, undefined, '0/' + app.current.filter + '/' + x);
}
function dragAndDropTable(table) {
var tableBody=document.getElementById(table).getElementsByTagName('tbody')[0];
tableBody.addEventListener('dragstart', function(event) {
@ -1059,7 +1197,7 @@ function parseSettings(obj) {
toggleBtn('btnnotifyPage', settings.notificationPage);
var features = ["featStickers", "featSmartpls", "featPlaylists", "featTags", "featLocalplayer", "featSyscmds", "featCoverimage"];
var features = ["featStickers", "featSmartpls", "featPlaylists", "featTags", "featLocalplayer", "featSyscmds", "featCoverimage", "featAdvsearch"];
for (var j = 0; j < features.length; j++) {
var Els = document.getElementsByClassName(features[j]);
@ -1221,6 +1359,14 @@ function setCols(table, className) {
}
document.getElementById(table + 'ColsDropdown').firstChild.innerHTML = tagChks;
var sort = document.getElementById('SearchList').getAttribute('data-sort');
if (sort == '') {
if (settings.featTags)
sort = 'Title';
else
sort = 'Filename';
}
if (table != 'Playback') {
var heading = '';
for (var i = 0; i < settings['cols' + table].length; i++) {
@ -1228,9 +1374,20 @@ function setCols(table, className) {
heading += '<th draggable="true" data-col="' + h + '">';
if (h == 'Track' || h == 'Pos')
h = '#';
heading += h + '</th>';
heading += h;
if (table == 'Search' && h == sort ) {
var sortdesc = false;
if (sort.indexOf('-') == 0) {
sortdesc = true;
sort = sort.substring(1);
}
heading += '<span class="sort-dir material-icons pull-right">' + (sortdesc == true ? 'arrow_drop_up' : 'arrow_drop_down') + '</span>';
}
heading += '</th>';
}
heading += '<th></th>';
if (className == undefined)
document.getElementById(table + 'List').getElementsByTagName('tr')[0].innerHTML = heading;
else {

View File

@ -56,6 +56,7 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
unsigned int uint_buf1, uint_buf2, uint_rc;
int je, int_buf1, int_rc;
float float_buf;
bool bool_buf;
char *p_charbuf1, *p_charbuf2, *p_charbuf3, *p_charbuf4;
char p_char[4];
enum mpd_cmd_ids cmd_id;
@ -594,6 +595,16 @@ void callback_mympd(struct mg_connection *nc, const struct mg_str msg) {
free(p_charbuf2);
}
break;
case MPD_API_DATABASE_SEARCH_ADV:
je = json_scanf(msg.p, msg.len, "{data: {expression:%Q, sort:%Q, sortdesc:%B, plist:%Q, offset:%u}}",
&p_charbuf1, &p_charbuf2, &bool_buf, &p_charbuf3, &uint_buf1);
if (je == 5) {
n = mympd_search_adv(mpd.buf, p_charbuf1, p_charbuf2, bool_buf, NULL, p_charbuf3, uint_buf1);
free(p_charbuf1);
free(p_charbuf2);
free(p_charbuf3);
}
break;
case MPD_API_QUEUE_SHUFFLE:
mpd_run_shuffle(mpd.conn);
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\": \"result\", \"data\": \"ok\"}");
@ -2202,12 +2213,12 @@ int mympd_search_adv(char *buffer, char *expression, char *sort, bool sortdesc,
if (mpd_search_add_expression(mpd.conn, expression) == false)
RETURN_ERROR_AND_RECOVER("mpd_search_add_expression");
if (sort != NULL && strcmp(plist, "") == 0) {
if (sort != NULL && strcmp(sort, "") != 0 && strcmp(plist, "") == 0) {
if (mpd_search_add_sort_name(mpd.conn, sort, sortdesc) == false)
RETURN_ERROR_AND_RECOVER("mpd_search_add_sort_name");
}
if (grouptag != NULL && strcmp(plist, "") == 0) {
if (grouptag != NULL && strcmp(grouptag, "") != 0 && strcmp(plist, "") == 0) {
if (mpd_search_add_group_tag(mpd.conn, mpd_tag_name_parse(grouptag)) == false)
RETURN_ERROR_AND_RECOVER("mpd_search_add_group_tag");
}

View File

@ -98,6 +98,7 @@
X(MPD_API_SMARTPLS_UPDATE_ALL) \
X(MPD_API_SMARTPLS_SAVE) \
X(MPD_API_SMARTPLS_GET) \
X(MPD_API_DATABASE_SEARCH_ADV) \
X(MPD_API_DATABASE_SEARCH) \
X(MPD_API_DATABASE_UPDATE) \
X(MPD_API_DATABASE_RESCAN) \