| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import {Result} from './launcher_internals.mojom-webui.js'; |
| import {getTemplate} from './results_table.html.js'; |
| |
| export interface LauncherResultsTableElement { |
| $: { |
| 'headerRow': HTMLTableRowElement, |
| 'resultsSection': HTMLTableSectionElement, |
| 'displayScoreHeader': HTMLTableCellElement, |
| }; |
| } |
| |
| export class LauncherResultsTableElement extends PolymerElement { |
| static get is() { |
| return 'launcher-results-table'; |
| } |
| |
| static get template() { |
| return getTemplate(); |
| } |
| |
| // Current results keyed by result id. |
| private results: Map<string, Result> = new Map(); |
| |
| // Extra header cells, keyed by their text content. These are placed into the |
| // header row in insertion order. |
| private headerCells: Map<string, HTMLTableCellElement> = new Map(); |
| |
| // The result property used to sort the table. 'Display score' is the default |
| // key, and this will change whenever the user clicks on a new header to sort |
| // by. |
| private sortKey: string = 'Display score'; |
| |
| // The IDs of results that are currently selected. This is used to persist |
| // formatting when the table is sorted. |
| private selectedIds: Set<string> = new Set(); |
| |
| override connectedCallback() { |
| super.connectedCallback(); |
| this.$.displayScoreHeader.addEventListener( |
| 'click', |
| () => this.sortTable('Display score', /*resultsChanged=*/ false)); |
| } |
| |
| clearResults() { |
| this.results.clear(); |
| this.$.resultsSection.innerHTML = ''; |
| for (const cell of this.headerCells.values()) { |
| this.$.headerRow.removeChild(cell); |
| } |
| this.headerCells.clear(); |
| } |
| |
| addResults(newResults: Result[]) { |
| for (const result of newResults) { |
| this.results.set(result.id, result); |
| this.addHeaders(Object.keys(result.rankerScores)); |
| } |
| this.sortTable(this.sortKey, /*resultsChanged=*/ true); |
| } |
| |
| // Appends any new headers to the end of the header row. All new headers |
| // should support sort-on-click. |
| private addHeaders(newHeaders: string[]) { |
| for (const header of newHeaders) { |
| if (this.headerCells.has(header)) { |
| continue; |
| } |
| const newCell = this.$.headerRow.insertCell(); |
| newCell.textContent = header; |
| newCell.className = 'sort-header'; |
| newCell.addEventListener( |
| 'click', () => this.sortTable(header, /*resultsChanged=*/ false)); |
| this.headerCells.set(header, newCell); |
| } |
| } |
| |
| // Repopulates the table with results sorted by the current key in descending |
| // order. |
| private sortTable(sortKey: string, resultsChanged: boolean) { |
| if (!resultsChanged && this.sortKey === sortKey) { |
| return; |
| } |
| this.sortKey = sortKey; |
| |
| const sortedResults = Array.from(this.results.values()); |
| if (this.sortKey === 'Display score') { |
| sortedResults.sort((a, b) => b.score - a.score); |
| } else { |
| const getSortValue = (result: Result): number => { |
| const value = result.rankerScores[this.sortKey]; |
| return value === undefined ? 0 : value; |
| }; |
| sortedResults.sort((a, b) => getSortValue(b) - getSortValue(a)); |
| } |
| |
| // Clear and repopulate the results table. |
| this.$.resultsSection.innerHTML = ''; |
| for (const result of sortedResults) { |
| const newRow = this.$.resultsSection.insertRow(); |
| newRow.addEventListener('click', (e: Event) => this.toggleRowSelected(e)); |
| if (this.selectedIds.has(result.id)) { |
| newRow.classList.add('selected'); |
| } |
| [result.id, |
| result.title, |
| result.description, |
| result.resultType, |
| result.metricsType, |
| result.displayType, |
| result.score.toString(), |
| ...this.flattenScores(result.rankerScores), |
| ].forEach(field => { |
| const newCell = newRow.insertCell(); |
| newCell.textContent = field; |
| }); |
| } |
| } |
| |
| // Converts ranker scores into an array of scores in string form and ordered |
| // according to the current headers. |
| private flattenScores(inputScores: {[key: string]: number}): string[] { |
| const outputScores = []; |
| for (const header of this.headerCells.keys()) { |
| const score = inputScores[header]; |
| outputScores.push(score === undefined ? '' : score.toString()); |
| } |
| return outputScores; |
| } |
| |
| // Toggles selection in the class list of the targeted row. |
| private toggleRowSelected(event: Event) { |
| const row = (event.target as HTMLElement).closest('tr'); |
| if (row == null || row.cells.length === 0) { |
| return; |
| } |
| |
| const id = row.cells[0]!.textContent!; |
| if (row.classList.contains('selected')) { |
| row.classList.remove('selected'); |
| this.selectedIds.delete(id); |
| } else { |
| row.classList.add('selected'); |
| this.selectedIds.add(id); |
| } |
| } |
| } |
| |
| customElements.define( |
| LauncherResultsTableElement.is, LauncherResultsTableElement); |