forked from rosindex/rosindex
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a Tabulator-based combined package search and list (#438)
- Loading branch information
Showing
8 changed files
with
421 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
|
||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width initial-scale=1" /> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
|
||
<title>{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}</title> | ||
<meta name="description" content="{{ site.description }}"> | ||
|
||
{% if page.canonical_url and page.canonical_url != page.url %} | ||
<link rel="canonical" href="{{ page.canonical_url | replace: 'index.html', '' }}"> | ||
<meta http-equiv="refresh" content="0; url={{ page.canonical_url | replace: 'index.html', '' }}" /> | ||
<script> | ||
window.location.href = "{{ page.canonical_url | replace: 'index.html','' }}"; | ||
</script> | ||
{% else %} | ||
<link rel="canonical" href="{{ page.url | replace: 'index.html', '' | prepend: site.baseurl | prepend: site.url }}"> | ||
{% endif %} | ||
|
||
<link rel="icon" sizes="any" type="image/svg+xml" href="{{ site.baseurl }}/assets/rosindex_logo.svg"> | ||
|
||
{% if page.css_uris %} | ||
{% for css_uri in page.css_uris %} | ||
<link rel="stylesheet" href="{{ css_uri }}"> | ||
{% endfor %} | ||
{% endif %} | ||
|
||
<link rel="stylesheet" type="text/css" href="{{ site.baseurl}}/bootstrap/css/bootstrap.min.css"/> | ||
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.baseurl }}"> | ||
{% comment %}<link rel="stylesheet" type="text/css" media="screen" href="{{ "/css/toc.css" | prepend: site.baseurl}}">{% endcomment %} | ||
|
||
{% if page.script_uris %} | ||
{% for script_uri in page.script_uris %} | ||
<script type="text/javascript" src="{{ script_uri }}"></script> | ||
{% endfor %} | ||
{% endif %} | ||
|
||
<script type="text/javascript" src={{ "/js/jquery.js" | prepend: site.baseurl }}></script> | ||
<script src={{ "/bootstrap/js/bootstrap.min.js" | prepend: site.baseurl }} type="text/javascript"></script> | ||
<script src={{ "/js/jquery-cookie.js" | prepend: site.baseurl }} type="text/javascript"></script> | ||
{% include_cached google_analytics.html %} | ||
<script type="text/javascript" src={{ "/js/toc.js" | prepend: site.baseurl }}></script> | ||
|
||
<script src={{ "/js/distro_switch.js" | prepend: site.baseurl }}></script> | ||
</head> | ||
|
||
<body style="height: 100vh;"> | ||
|
||
{% include_cached header.html %} | ||
|
||
<div class="page-content" style="height: var(--content-height);"> | ||
<div class="wrapper" style="height:100%;"> | ||
{{ content }} | ||
</div> | ||
</div> | ||
|
||
{% include_cached footer.html %} | ||
|
||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
--- | ||
layout: noscroll-default | ||
--- | ||
<div class="container-fluid" style="padding-top:10px; height:100%;"> | ||
<div class="container-fluid" style="height:100%;"> | ||
<div class="row" style="height:var(--distro-height);"> | ||
{% include distro_switch.html %} | ||
</div> | ||
<div class="row" style="height:var(--search-height);"> | ||
<div class="panel panel-default" style="height:100%;"> | ||
<div class="panel-heading" style="height:100%;"> | ||
<div class="row" style="height:100%;"> | ||
<div class="col-xs-6"> | ||
<div class="input-group"> | ||
<span class="input-group-addon"> | ||
<input type="checkbox" id="search-enable-box"/> | ||
<span class="hidden-xs"> Enable search </span> | ||
</span> | ||
<input id="search-query" type="text" class="form-control" | ||
autocomplete="off" placeholder="Search packages"> | ||
<span class="input-group-btn"> | ||
<button id="search-button" title="Search packages" class="btn btn-default"> | ||
<span class="glyphicon glyphicon-search"></span> | ||
</button> | ||
</span> | ||
</div> | ||
</div> | ||
<div class="col-xs-6"> | ||
<p style="line-height:35px"><span class="hidden-xs">Showing </span> | ||
<span id="table-filtered-count">0</span> of | ||
<span id="table-unfiltered-count">0</span> packages | ||
</p> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<div class="row" style="height:calc(100% - var(--search-height) - var(--distro-height))"> | ||
<div id="packages-table"></div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<!-- See https://github.com/olifolkerd/tabulator/issues/4419 for issue with Tabulator post-5.6.0 --> | ||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/tabulator.min.css" rel="stylesheet"> | ||
<!-- <script src={{ "/js/tabulator.js" | prepend: site.baseurl }}></script> --> | ||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/tabulator.min.js"></script> | ||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js"></script> | ||
<script src="{{site.baseurl}}/js/lunr.js" type="text/javascript" charset="utf-8"></script> | ||
<script type="text/javascript"> | ||
var gData_promises = {}; | ||
var gIndex_promises = {}; | ||
var gTable = null; | ||
var gDistro = "{{ site.distros[0] }}"; | ||
|
||
function addFilter(table, field, type, value) { | ||
// Add a new table filter, removing existing | ||
removeFilterByField(table, field); | ||
table.addFilter(field, type, value); | ||
} | ||
|
||
function removeFilterByField(table, field) { | ||
// remove all filters on a specified field, if any | ||
for (const existingFilter of table.getFilters()) { | ||
if ('field' in existingFilter && existingFilter['field'] == field) { | ||
table.removeFilter(existingFilter.field, existingFilter.type, existingFilter.value); | ||
} | ||
} | ||
} | ||
|
||
function getData(distro) { | ||
// start and return a promise to get distro json data | ||
if (distro in gData_promises) { | ||
return gData_promises[distro]; | ||
} | ||
gData_promises[distro] = | ||
fetch(`{{site.baseurl}}/search/packages/data.${distro}.json`) | ||
.then((response) => response.json()); | ||
|
||
return gData_promises[distro]; | ||
} | ||
|
||
function getIndex(distro) { | ||
console.log(`getIndex for ${distro}`); | ||
// start and return a promise to get distro json index | ||
if (distro in gIndex_promises) { | ||
return gIndex_promises[distro]; | ||
} | ||
gIndex_promises[distro] = | ||
fetch(`{{site.baseurl}}/search/packages/index.${distro}.json`) | ||
.then((response) => response.json()) | ||
.then((indexData) => { | ||
return lunr.Index.load(indexData); | ||
}); | ||
|
||
return gIndex_promises[distro]; | ||
} | ||
|
||
function getWindowSearchQuery() { | ||
return new URL(window.location.toString()).searchParams.get('pkgs'); | ||
} | ||
|
||
// Populate the search input with querystring parameter | ||
function setWindowSearchQuery(query) { | ||
var url = new URL(window.location.toString()); | ||
url.searchParams.set('pkgs', query); | ||
window.history.pushState({}, '', url); | ||
} | ||
|
||
function copySearchQueryToUI() { | ||
var query = getWindowSearchQuery() || ''; | ||
$('#search-query').val(query); | ||
$("#search-enable-box").prop("checked", !!query); | ||
} | ||
|
||
function copyUIToSearchQuery() { | ||
if ($("#search-enable-box").prop("checked")) { | ||
setWindowSearchQuery($('#search-query').val()); | ||
} else { | ||
setWindowSearchQuery(''); | ||
} | ||
} | ||
|
||
// Returns a promise resolving to a search array of id hits. | ||
function getSearchArray(distro, query) { | ||
if (!query) { | ||
return Promise.resolve(null); | ||
} | ||
return getIndex(distro).then(searchIndex => { | ||
var searchResults = searchIndex.search(query); | ||
searchArray = searchResults.map((result) => parseInt(result.ref)); | ||
console.log(`Search returned ${searchArray.length} results`); | ||
// Tabulator filters don't seem to work with zero entries | ||
searchArray = searchArray.length ? searchArray : [-1]; | ||
return searchArray; | ||
}); | ||
} | ||
|
||
function doSearch(table, distro, query) { | ||
console.log(`doSearch query="${query}"`); | ||
getSearchArray(distro, query) | ||
.then(searchArray => { | ||
if (searchArray) { | ||
addFilter(table, 'id', 'in', searchArray) | ||
} else { | ||
removeFilterByField(table, 'id'); | ||
} | ||
}) | ||
} | ||
|
||
function makeTable(distro, data, searchArray) { | ||
const CALENDAR = "\u{1F4C5}"; | ||
const STAR = "\u2605"; | ||
const LEFT_ARROW = "\u2B05" | ||
const RIGHT_ARROW = "\u27A1" | ||
|
||
var initialFilter = (searchArray === undefined || searchArray === null) ? | ||
[] : [{field: 'id', type: 'in', value: searchArray}]; | ||
|
||
const table = new Tabulator("#packages-table", { | ||
maxHeight:"100%", | ||
// height: "400px", | ||
data:data, //assign data to table | ||
layout:"fitColumns", //fit columns to width of table (optional) | ||
rowHeight:30, // Workaround for https://github.com/olifolkerd/tabulator/issues/4419 | ||
initialFilter: initialFilter, | ||
columns:[ //Define Table Columns | ||
{ title:"Name", field:"url", width:200, | ||
formatter:"link", formatterParams:{labelField:"name", urlPrefix:"{{site.baseurl}}"}}, | ||
{ title:"Description", field:"description", minWidth:300}, | ||
{ title:STAR, field:"released", width:40, formatter:"tickCross", headerTooltip:"release status", hozAlign:"center", | ||
formatterParams: {allowTruthy: true, allowEmpty: true}}, | ||
{ title:CALENDAR, field: "last_commit_time", width:80, headerHozAlign:"center", headerTooltip:"last commit date", | ||
sorter:"date", sorterParams:{format:"yyyy-MM-dd"}}, | ||
{ title:LEFT_ARROW, field:"pkg_deps", width:40, headerTooltip:"package dependency count"}, | ||
{ title:RIGHT_ARROW, field:"dependants", width:40, headerTooltip:"package used by count"}, | ||
{ title:"Authors", field:"authors", width:120}, | ||
{ title:"Maintainers", field:"maintainers", width:120}, | ||
{ title:"Repo", field:"repo_name", width:100, | ||
formatter:"link", formatterParams:{labelField:"repo_name", url:(cell) => | ||
{ return `{{site.baseurl}}/r/${cell.getValue()}/#${distro}`}}}, | ||
{ title:"Org", field:"org", width:100}, | ||
], | ||
}); | ||
return table; | ||
} | ||
|
||
// Returns a promise that resolves to a Tabulator table | ||
function showTable(distro, query) { | ||
console.log(`showTable distro: ${distro}, query: ${query}`); | ||
var searchPromise = query ? getSearchArray(distro, query) : Promise.resolve(null); | ||
return Promise.all([getData(distro), searchPromise]).then(values => { | ||
var data = values[0]; | ||
var searchArray = values[1]; | ||
const table = makeTable(distro, data, searchArray); | ||
$("#table-filtered-count").text(data.length); | ||
$("#table-unfiltered-count").text(data.length); | ||
table.on("dataFiltered", (filters, rows) => { | ||
$("#table-filtered-count").text(rows.length); | ||
$("#table-unfiltered-count").text(table.getDataCount()); | ||
}); | ||
table.on("tableBuilt", () => { | ||
$("#table-filtered-count").text(table.getDataCount('active')); | ||
$("#table-unfiltered-count").text(table.getDataCount()); | ||
}); | ||
return table; | ||
}); | ||
} | ||
|
||
$(function() { | ||
console.log('document ready'); | ||
copySearchQueryToUI(); | ||
gDistro = setupDistroSwitch("{{ site.distros[0] }}"); | ||
var query = getWindowSearchQuery(); | ||
showTable(gDistro, query) | ||
.then(table => gTable = table); | ||
|
||
window.addEventListener('hashchange', (event) => { | ||
// Reload the table if the distro changes | ||
var url = new URL(document.location.toString()); | ||
console.log(`hash change url: ${url.href}`); | ||
if (url.hash) { | ||
gDistro = url.hash.substring(1); | ||
// TODO: change cookie? | ||
} else { | ||
gDistro = "{{ site.distros[0] }}"; | ||
} | ||
console.log("overriding in search_packages via anchor "+gDistro+" distro"); | ||
var query = getWindowSearchQuery(); | ||
showTable(gDistro, query) | ||
.then(table => gTable = table); | ||
}); | ||
|
||
$('#search-query').on('focus', (e) => { | ||
// Pre-load index to speed up search response | ||
getIndex(gDistro); | ||
}); | ||
|
||
$('#search-query').bind('keypress', (e) => { | ||
if (e.which == 13) { | ||
$("#search-enable-box").prop("checked", true); | ||
copyUIToSearchQuery(); | ||
var query = getWindowSearchQuery() || ''; | ||
doSearch(gTable, gDistro, query) | ||
} | ||
}); | ||
|
||
$("#search-enable-box").on('click', (e) => { | ||
element = e.currentTarget; | ||
console.log(`#search-enable click ${element.checked}`); | ||
query = $("#search-query").val(); | ||
if (element.checked && query) { | ||
copyUIToSearchQuery(); | ||
doSearch(gTable, gDistro, query); | ||
} else { | ||
setWindowSearchQuery(''); | ||
removeFilterByField(gTable, 'id'); | ||
} | ||
}); | ||
|
||
$("#search-button").on('click', (e) => { | ||
query = $("#search-query").val(); | ||
if (query) { | ||
$("#search-enable-box").prop("checked", true); | ||
copyUIToSearchQuery(); | ||
doSearch(gTable, gDistro, query); | ||
} | ||
}); | ||
|
||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.