| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {html, LitElement, css} from 'lit'; |
| import {customElement, state} from 'lit/decorators.js'; |
| import * as constant from '../src/constants'; |
| import { LogHeader } from './log_header'; |
| import { LogRow } from './log_row'; |
| import {LogRowData} from '../src/log_data'; |
| import { |
| LogField, |
| LogHeadersData, |
| LogViewOptions, |
| HeaderWidthChangeHandler |
| } from '../src/fields'; |
| import {FilterExpression} from '../src/filter'; |
| import * as styles from './styles'; |
| |
| export const WRAP_LOG_TEXT_ATTR = 'wrap-log-text'; |
| |
| const logStyles = css` |
| log-header>div { |
| background-color: var(--vscode-editor-background); |
| border-bottom: 2px solid var(--vscode-panel-border); |
| padding-bottom: 5px; |
| position: sticky; |
| top: 0; |
| } |
| |
| log-header, |
| log-view-row { |
| display: table-row-group; |
| } |
| |
| log-view-row[hidden] { |
| display: none; |
| } |
| |
| log-view-row>div { |
| display: table-row; |
| } |
| |
| .log-list-cell, |
| log-header>div { |
| overflow: clip; |
| text-align: left; |
| text-overflow: ellipsis; |
| vertical-align: top; |
| white-space: nowrap; |
| display: table-cell; |
| } |
| |
| td#moniker { |
| direction: rtl; |
| } |
| |
| :host([wrap-log-text]) .msg-cell, |
| :host(:not([wrap-log-text])) .msg-cell:hover { |
| text-overflow: clip; |
| white-space: normal; |
| } |
| |
| :host(:not([wrap-log-text])) .msg-cell:hover { |
| background: var(--vscode-list-hoverBackground); |
| } |
| |
| |
| :host { |
| display: table; |
| table-layout: fixed; |
| width: 100%; |
| word-wrap: normal; |
| } |
| |
| log-view-row { |
| font-family: var(--vscode-editor-font-family); |
| font-size: var(--vscode-editor-font-size); |
| font-weight: var(--vscode-editor-font-weight); |
| } |
| |
| log-view-row div[data-severity="warn"] { |
| color: var(--vscode-list-warningForeground); |
| } |
| |
| log-view-row div[data-severity="error"] { |
| color: var(--vscode-list-errorForeground); |
| } |
| |
| log-view-row div[data-moniker="<VSCode>"] { |
| line-height: 75%; |
| opacity: 70%; |
| } |
| |
| .column-resize { |
| cursor: col-resize; |
| height: 100%; |
| position: absolute; |
| right: 0; |
| top: 0; |
| width: 5px; |
| } |
| |
| .column-resize:hover, |
| .resizing { |
| border-right: 2px solid var(--vscode-editor-foreground); |
| }`; |
| |
| @customElement('log-view') |
| export class LogList extends LitElement { |
| static styles = [ |
| styles.VSCODE_CSS, |
| logStyles |
| ]; |
| |
| @state() |
| private logRows: Array<LitElement> = []; |
| private maxWidths: Array<number>; |
| private logHeader: LogHeader; |
| public lastTimestamp: number = 0; |
| public filterResultsCount: number = 0; |
| |
| constructor( |
| private headerFields: LogHeadersData, |
| shouldWrapLogs: boolean, |
| onHeaderWidthChange: HeaderWidthChangeHandler, |
| private currentFilter: FilterExpression, |
| private options: LogViewOptions) { |
| super(); |
| this.maxWidths = new Array(Object.keys(headerFields).length).fill(0); |
| this.logHeader = new LogHeader(headerFields, onHeaderWidthChange); |
| this.logWrapping = shouldWrapLogs; |
| this.ariaColCount = `${Object.keys(headerFields).length}`; |
| } |
| |
| filterChangeHandler(event: CustomEvent) { |
| const filter: FilterExpression = event.detail.filter; |
| this.ariaColCount = `${this.logRows.length}`; |
| this.currentFilter = filter; |
| let resultCount = 0; |
| |
| if (filter.isEmpty()) { |
| for (const element of this.logRows) { |
| element.removeAttribute('hidden'); |
| resultCount++; |
| } |
| } else { |
| for (const element of this.logRows) { |
| if (filter.accepts(element.children[0])) { |
| element.removeAttribute('hidden'); |
| resultCount++; |
| } else { |
| element.setAttribute('hidden', ''); |
| } |
| } |
| } |
| |
| this.filterResultsCount = resultCount; |
| } |
| |
| render() { |
| return html` |
| ${this.logHeader} |
| ${this.logRows} |
| `; |
| } |
| |
| /** |
| * Defines whether or not to wrap the log text. |
| * |
| * @param logWrapActive if true the log text will be wrapped |
| */ |
| public set logWrapping(logWrapActive: boolean) { |
| if (logWrapActive) { |
| this.setAttribute(WRAP_LOG_TEXT_ATTR, 'true'); |
| } else { |
| this.removeAttribute(WRAP_LOG_TEXT_ATTR); |
| } |
| } |
| |
| /** |
| * Appends a log to the current list of logs |
| */ |
| public addLog(log: LogRowData) { |
| const timestampField = log.timestamp; |
| // Keep the timestamp around so we know when to start of from |
| // when we clear logs. |
| if (timestampField && timestampField > this.lastTimestamp) { |
| // ffx does not guarantee that logs come in order |
| // of timestamp. |
| this.lastTimestamp = timestampField; |
| } |
| this.addLogElement(log); |
| } |
| |
| /** |
| * Resets the state and contents of the webview to contain nothing. |
| * The persistent state is maintained as that represents user selections. |
| */ |
| public reset() { |
| this.logRows = []; |
| this.filterResultsCount = 0; |
| } |
| |
| private addLogElement(log: LogRowData) { |
| const logsToDrop = this.logRows.length - constant.MAX_LOGS; |
| for (let i = 0; i < logsToDrop; i++) { |
| this.popLogElement(); |
| } |
| this.appendLogElement(log); |
| } |
| |
| private appendLogElement(log: LogRowData) { |
| const element = new LogRow( |
| log, this.headerFields, this.options.columnFormatter); |
| this.logRows = [...this.logRows, element]; |
| element.updateComplete |
| .then(() => { |
| if (!this.filtersAllow(element.children[0])) { |
| element.setAttribute('hidden', ''); |
| } |
| this.setMaxCellWidth(element.children[0]); |
| if (!this.parentElement?.matches(':hover')) { |
| this.parentElement?.scrollTo(-1, this.scrollHeight); |
| }; |
| }) |
| .catch(e => { }); |
| } |
| |
| private setMaxCellWidth(row: Element) { |
| for (const cell in Array.from(row.children)) { |
| let id = row.children[cell].id as LogField; |
| let width = this.maxWidths[cell]; |
| if (width < row.children[cell].scrollWidth) { |
| this.maxWidths[cell] = row.children[cell].scrollWidth; |
| this.logHeader.setMaxCellWidth(id, this.maxWidths[cell]); |
| } |
| } |
| } |
| |
| private popLogElement() { |
| this.logRows = this.logRows.slice(1); |
| } |
| |
| private filtersAllow(el: Element) { |
| return this.currentFilter.accepts(el); |
| } |
| } |