// Copyright (C) 2019 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import * as m from 'mithril'; import {assertExists} from '../base/logging'; import {Actions} from '../common/actions'; import { LogBounds, LogBoundsKey, LogEntries, LogEntriesKey } from '../common/logs'; import {formatTimestamp} from '../common/time'; import {TimeSpan} from '../common/time'; import {globals} from './globals'; import {Panel} from './panel'; const ROW_H = 20; const PRIO_TO_LETTER = ['-', '-', 'V', 'D', 'I', 'W', 'E', 'F']; export class LogPanel extends Panel<{}> { private scrollContainer?: HTMLElement; private bounds?: LogBounds; private entries?: LogEntries; private visibleRowOffset = 0; private visibleRowCount = 0; recomputeVisibleRowsAndUpdate() { const scrollContainer = assertExists(this.scrollContainer); const prevOffset = this.visibleRowOffset; const prevCount = this.visibleRowCount; this.visibleRowOffset = Math.floor(scrollContainer.scrollTop / ROW_H); this.visibleRowCount = Math.ceil(scrollContainer.clientHeight / ROW_H); if (this.visibleRowOffset !== prevOffset || this.visibleRowCount !== prevCount)  { globals.dispatch(Actions.updateLogsPagination({ offset: this.visibleRowOffset, count: this.visibleRowCount, })); } } oncreate({dom}: m.CVnodeDOM) { this.scrollContainer = assertExists( dom.parentElement!.parentElement!.parentElement as HTMLElement); this.scrollContainer.addEventListener( 'scroll', this.onScroll.bind(this), {passive: true}); this.bounds = globals.trackDataStore.get(LogBoundsKey) as LogBounds; this.entries = globals.trackDataStore.get(LogEntriesKey) as LogEntries; this.recomputeVisibleRowsAndUpdate(); } onupdate(_: m.CVnodeDOM) { this.bounds = globals.trackDataStore.get(LogBoundsKey) as LogBounds; this.entries = globals.trackDataStore.get(LogEntriesKey) as LogEntries; this.recomputeVisibleRowsAndUpdate(); } onScroll() { if (this.scrollContainer === undefined) return; this.recomputeVisibleRowsAndUpdate(); globals.rafScheduler.scheduleFullRedraw(); } onRowOver(ts: number) { globals.frontendLocalState.setHoveredLogsTimestamp(ts); } onRowOut() { globals.frontendLocalState.setHoveredLogsTimestamp(-1); } private totalRows(): {isStale: boolean, total: number, offset: number, count: number} { if (!this.bounds) { return {isStale: false, total: 0, offset: 0, count: 0}; } const {total, startTs, endTs, firstRowTs, lastRowTs} = this.bounds; const vis = globals.frontendLocalState.visibleWindowTime; const leftSpan = new TimeSpan(startTs, firstRowTs); const rightSpan = new TimeSpan(lastRowTs, endTs); const isStaleLeft = !leftSpan.isInBounds(vis.start); const isStaleRight = !rightSpan.isInBounds(vis.end); const isStale = isStaleLeft || isStaleRight; const offset = Math.min(this.visibleRowOffset, total); const visCount = Math.min(total, this.visibleRowCount); return {isStale, total, count: visCount, offset}; } view(_: m.CVnode<{}>) { const {isStale, total, offset, count} = this.totalRows(); const rows: m.Children = []; if (this.entries) { const offset = this.entries.offset; const timestamps = this.entries.timestamps; const priorities = this.entries.priorities; const tags = this.entries.tags; const messages = this.entries.messages; for (let i = 0; i < this.entries.timestamps.length; i++) { const priorityLetter = PRIO_TO_LETTER[priorities[i]]; const ts = timestamps[i]; const prioClass = priorityLetter || ''; rows.push( m(`.row.${prioClass}`, { 'class': isStale ? 'stale' : '', style: {top: `${(offset + i) * ROW_H}px`}, onmouseover: this.onRowOver.bind(this, ts / 1e9), onmouseout: this.onRowOut.bind(this), }, m('.cell', formatTimestamp(ts / 1e9 - globals.state.traceTime.startSec)), m('.cell', priorityLetter || '?'), m('.cell', tags[i]), m('.cell', messages[i]), m('br'))); } } return m( '.log-panel', m('header', { 'class': isStale ? 'stale' : '', }, `Logs rows [${offset}, ${offset + count}] / ${total}`), m('.rows', {style: {height: `${total * ROW_H}px`}}, rows)); } renderCanvas() {} }