blob: 19904b68b4b173bea9c3669ef1b401c09bfdf509 [file] [log] [blame]
// 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);
}
}