Merge pull request #376 from vector-im/bwindels/filter-and-highlight-logviewer
add highlight and filter support to logviewer
This commit is contained in:
commit
563847bba9
2 changed files with 112 additions and 1 deletions
|
@ -172,13 +172,33 @@
|
||||||
color: HighlightText;
|
color: HighlightText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline .item.highlighted {
|
||||||
|
background-color: fuchsia;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#highlight {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav form {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav><button id="openFile">Open log file</button></nav>
|
<nav>
|
||||||
|
<button id="openFile">Open log file</button>
|
||||||
|
<form id="highlightForm"><input type="text" id="highlight" placeholder="Highlight a search term"></form>
|
||||||
|
<button id="collapseAll">Collapse all</button>
|
||||||
|
<button id="hideCollapsed">Hide collapsed root items</button>
|
||||||
|
<button id="hideHighlightedSiblings">Hide siblings of highlighted</button>
|
||||||
|
<button id="showAll">Show all</button>
|
||||||
|
</nav>
|
||||||
<main></main>
|
<main></main>
|
||||||
<aside></aside>
|
<aside></aside>
|
||||||
<script type="module" src="main.js"></script>
|
<script type="module" src="main.js"></script>
|
||||||
|
|
|
@ -166,6 +166,7 @@ async function loadFile() {
|
||||||
main.replaceChildren(fragment);
|
main.replaceChildren(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: make this use processRecursively
|
||||||
function preprocessRecursively(item, parentElement, refsMap, path) {
|
function preprocessRecursively(item, parentElement, refsMap, path) {
|
||||||
item.s = (parentElement?.s || 0) + item.s;
|
item.s = (parentElement?.s || 0) + item.s;
|
||||||
if (itemRefSource(item)) {
|
if (itemRefSource(item)) {
|
||||||
|
@ -297,3 +298,93 @@ function itemToNode(item) {
|
||||||
}
|
}
|
||||||
return li;
|
return li;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const highlightForm = document.getElementById("highlightForm");
|
||||||
|
|
||||||
|
highlightForm.addEventListener("submit", evt => {
|
||||||
|
evt.preventDefault();
|
||||||
|
const query = document.getElementById("highlight").value;
|
||||||
|
if (query) {
|
||||||
|
processRecursively(rootItem, item => {
|
||||||
|
let domNode = document.getElementById(item.id);
|
||||||
|
if (itemMatchesFilter(item, query)) {
|
||||||
|
domNode.classList.add("highlighted");
|
||||||
|
domNode = domNode.parentElement;
|
||||||
|
while (domNode.nodeName !== "SECTION") {
|
||||||
|
if (domNode.nodeName === "LI") {
|
||||||
|
domNode.classList.add("expanded");
|
||||||
|
}
|
||||||
|
domNode = domNode.parentElement;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
domNode.classList.remove("highlighted");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (const node of document.querySelectorAll(".highlighted")) {
|
||||||
|
node.classList.remove("highlighted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function itemMatchesFilter(item, query) {
|
||||||
|
if (itemError(item)) {
|
||||||
|
if (valueMatchesQuery(itemError(item), query)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valueMatchesQuery(itemValues(item), query);
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueMatchesQuery(value, query) {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return value.includes(query);
|
||||||
|
} else if (typeof value === "object" && value !== null) {
|
||||||
|
for (const key in value) {
|
||||||
|
if (value.hasOwnProperty(key) && valueMatchesQuery(value[key], query)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof value === "number") {
|
||||||
|
return value.toString().includes(query);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processRecursively(item, callback, parentItem) {
|
||||||
|
if (item.id) {
|
||||||
|
callback(item, parentItem);
|
||||||
|
}
|
||||||
|
if (itemChildren(item)) {
|
||||||
|
for (let i = 0; i < itemChildren(item).length; i += 1) {
|
||||||
|
// do it in advance for a child as we don't want to do it for the rootItem
|
||||||
|
const child = itemChildren(item)[i];
|
||||||
|
processRecursively(child, callback, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("collapseAll").addEventListener("click", () => {
|
||||||
|
for (const node of document.querySelectorAll(".expanded")) {
|
||||||
|
node.classList.remove("expanded");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.getElementById("hideCollapsed").addEventListener("click", () => {
|
||||||
|
for (const node of document.querySelectorAll("section > div.timeline > ol > li:not(.expanded)")) {
|
||||||
|
node.closest("section").classList.add("hidden");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.getElementById("hideHighlightedSiblings").addEventListener("click", () => {
|
||||||
|
for (const node of document.querySelectorAll(".highlighted")) {
|
||||||
|
const list = node.closest("ol");
|
||||||
|
const siblings = Array.from(list.querySelectorAll("li > div > a:not(.highlighted)")).map(n => n.closest("li"));
|
||||||
|
for (const sibling of siblings) {
|
||||||
|
sibling.classList.add("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.getElementById("showAll").addEventListener("click", () => {
|
||||||
|
for (const node of document.querySelectorAll(".hidden")) {
|
||||||
|
node.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
});
|
Reference in a new issue