implement proper match highlighting in vm search feature

This commit is contained in:
Arthur Lu 2024-08-25 03:09:41 +00:00
parent 07bf4b0967
commit 746136724f

View File

@ -62,7 +62,7 @@ class InstanceCard extends HTMLElement {
this.vmid = data.vmid; this.vmid = data.vmid;
this.name = data.name; this.name = data.name;
this.node = data.node; this.node = data.node;
this.searchQuery = data.searchQuery; this.searchQueryResult = data.searchQueryResult;
this.update(); this.update();
} }
@ -71,18 +71,36 @@ class InstanceCard extends HTMLElement {
vmidParagraph.innerText = this.vmid; vmidParagraph.innerText = this.vmid;
const nameParagraph = this.shadowRoot.querySelector("#instance-name"); const nameParagraph = this.shadowRoot.querySelector("#instance-name");
if (this.searchQuery) { if (this.searchQueryResult.alignment) {
const regExpEscape = v => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); let i = 0; // name index
const escapedQuery = regExpEscape(this.searchQuery); let c = 0; // alignment index
const searchRegExp = new RegExp(`(${escapedQuery})`, "gi"); const alignment = this.searchQueryResult.alignment;
const nameParts = this.name.split(searchRegExp); while (i < this.name.length && c < alignment.length) {
for (let i = 0; i < nameParts.length; i++) { if (alignment[c] === "M") {
const part = document.createElement("span"); const part = document.createElement("span");
part.innerText = nameParts[i]; part.innerText = this.name[i];
if (nameParts[i].toLowerCase() === this.searchQuery.toLowerCase()) {
part.style = "color: var(--lightbg-text-color); background-color: var(--highlight-color);"; part.style = "color: var(--lightbg-text-color); background-color: var(--highlight-color);";
nameParagraph.append(part);
i++;
c++;
}
else if (alignment[c] === "I") {
const part = document.createElement("span");
part.innerText = this.name[i];
nameParagraph.append(part);
i++;
c++;
}
else if (alignment[c] === "D") {
c++;
}
else if (alignment[c] === "X") {
const part = document.createElement("span");
part.innerText = this.name[i];
nameParagraph.append(part);
i++;
c++;
} }
nameParagraph.append(part);
} }
} }
else { else {
@ -282,25 +300,23 @@ async function populateInstances () {
const searchQuery = document.querySelector("#search").value || null; const searchQuery = document.querySelector("#search").value || null;
let criteria; let criteria;
if (!searchQuery) { if (!searchQuery) {
criteria = (a, b) => { criteria = (item, query = null) => {
return (a.vmid > b.vmid) ? 1 : -1; return { score: item.vmid, alignment: null };
}; };
} }
else if (searchCriteria === "exact") { else if (searchCriteria === "exact") {
criteria = (a, b) => { criteria = (item, query) => {
const aInc = a.name.toLowerCase().includes(searchQuery.toLowerCase()); const substrInc = item.includes(query);
const bInc = b.name.toLowerCase().includes(searchQuery.toLowerCase()); if (substrInc) {
if (aInc && bInc) { const substrStartIndex = item.indexOf(query);
return a.vmid > b.vmid ? 1 : -1; const queryLength = query.length;
} const remaining = item.length - substrInc - queryLength;
else if (aInc && !bInc) { const alignment = `${"X".repeat(substrStartIndex)}${"M".repeat(queryLength)}${"X".repeat(remaining)}`;
return -1; return { score: 1, alignment };
}
else if (!aInc && bInc) {
return 1;
} }
else { else {
return a.vmid > b.vmid ? 1 : -1; const alignment = `${"X".repeat(item.length)}`;
return { score: 0, alignment };
} }
}; };
} }
@ -308,34 +324,43 @@ async function populateInstances () {
const penalties = { const penalties = {
m: 0, m: 0,
x: 1, x: 1,
o: 1, o: 0,
e: 1 e: 1
}; };
criteria = (a, b) => { criteria = (item, query) => {
// lower is better // lower is better
const aAlign = wfAlign(a.name.toLowerCase(), searchQuery.toLowerCase(), penalties); const { score, CIGAR } = wfAlign(query, item, penalties, true);
const aScore = aAlign.score / a.name.length; return { score: score / item.length, alignment: CIGAR };
const bAlign = wfAlign(b.name.toLowerCase(), searchQuery.toLowerCase(), penalties);
const bScore = bAlign.score / b.name.length;
if (aScore === bScore) {
return a.vmid > b.vmid ? 1 : -1;
}
else {
return aScore - bScore;
}
}; };
} }
instances.sort(criteria); sortInstances(criteria, searchQuery);
const instanceContainer = document.querySelector("#instance-container"); const instanceContainer = document.querySelector("#instance-container");
instanceContainer.innerHTML = ""; instanceContainer.innerHTML = "";
for (let i = 0; i < instances.length; i++) { for (let i = 0; i < instances.length; i++) {
const newInstance = document.createElement("instance-card"); const newInstance = document.createElement("instance-card");
instances[i].searchQuery = searchQuery;
newInstance.data = instances[i]; newInstance.data = instances[i];
instanceContainer.append(newInstance); instanceContainer.append(newInstance);
} }
} }
function sortInstances (criteria, searchQuery) {
for (let i = 0; i < instances.length; i++) {
const { score, alignment } = criteria(instances[i].name.toLowerCase(), searchQuery ? searchQuery.toLowerCase() : "");
instances[i].searchQueryResult = { score, alignment };
}
const sortCriteria = (a, b) => {
const aScore = a.searchQueryResult.score;
const bScore = b.searchQueryResult.score;
if (aScore === bScore) {
return a.vmid > b.vmid ? 1 : -1;
}
else {
return aScore - bScore;
}
};
instances.sort(sortCriteria);
}
async function handleInstanceAdd () { async function handleInstanceAdd () {
const header = "Create New Instance"; const header = "Create New Instance";