/* * Copyright (C) 2022 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 { Component, ElementRef, Inject, Input, SimpleChanges, } from '@angular/core'; import {assertDefined} from 'common/assert_utils'; import {FunctionUtils} from 'common/function_utils'; import {PersistentStore} from 'common/persistent_store'; import {Analytics} from 'logging/analytics'; import { TabbedViewSwitched, WinscopeEvent, WinscopeEventType, } from 'messaging/winscope_event'; import { EmitEvent, WinscopeEventEmitter, } from 'messaging/winscope_event_emitter'; import {WinscopeEventListener} from 'messaging/winscope_event_listener'; import {TRACE_INFO} from 'trace/trace_info'; import {View, Viewer, ViewType} from 'viewers/viewer'; interface Tab { view: View; addedToDom: boolean; } @Component({ selector: 'trace-view', template: `
`, styles: [ ` .tab.active { opacity: 100%; } .header-items-wrapper { display: flex; flex-direction: row; justify-content: space-between; } .tabs-navigation-bar { height: 100%; border-bottom: 0px; } .trace-view-content { height: 100%; overflow: auto; background-color: var(--trace-view-background-color); } .tab { overflow-x: hidden; text-overflow: ellipsis; } .tab:not(.last):after { content: ''; position: absolute; right: 0; height: 60%; width: 1px; background-color: #C4C0C0; } `, ], }) export class TraceViewComponent implements WinscopeEventEmitter, WinscopeEventListener { @Input() viewers: Viewer[] = []; @Input() store: PersistentStore | undefined; TRACE_INFO = TRACE_INFO; tabs: Tab[] = []; private elementRef: ElementRef; private currentActiveTab: undefined | Tab; private emitAppEvent: EmitEvent = FunctionUtils.DO_NOTHING_ASYNC; constructor(@Inject(ElementRef) elementRef: ElementRef) { this.elementRef = elementRef; } ngOnChanges(changes: SimpleChanges) { this.renderViewsTab(changes['viewers']?.firstChange ?? false); this.renderViewsOverlay(); } async onTabClick(tab: Tab) { await this.showTab(tab, false); } async onWinscopeEvent(event: WinscopeEvent) { await event.visit( WinscopeEventType.TABBED_VIEW_SWITCH_REQUEST, async (event) => { const tab = this.tabs.find((tab) => tab.view.traces.some((trace) => trace === event.newActiveTrace), ); await this.showTab(assertDefined(tab), false); }, ); } setEmitEvent(callback: EmitEvent) { this.emitAppEvent = callback; } isCurrentActiveTab(tab: Tab) { return tab === this.currentActiveTab; } private renderViewsTab(firstToRender: boolean) { this.tabs = this.viewers .map((viewer) => viewer.getViews()) .flat() .filter((view) => view.type === ViewType.TAB) .map((view) => { return { view, addedToDom: false, }; }); this.tabs.forEach((tab) => { // TODO: setting "store" this way is a hack. // Store should be part of View's interface. (tab.view.htmlElement as any).store = this.store; }); if (this.tabs.length > 0) { this.showTab(this.tabs[0], firstToRender); } } private renderViewsOverlay() { const views: View[] = this.viewers .map((viewer) => viewer.getViews()) .flat() .filter((view) => view.type === ViewType.OVERLAY); if (views.length > 1) { throw new Error( 'Only one overlay view is supported. To allow more overlay views, either create more than' + ' one draggable containers in this component or move the cdkDrag directives into the' + " overlay view when the new Angular's directive composition API is available" + ' (https://github.com/angular/angular/issues/8785).', ); } views.forEach((view) => { view.htmlElement.style.pointerEvents = 'all'; const container = assertDefined( this.elementRef.nativeElement.querySelector('.overlay-container'), ); container.appendChild(view.htmlElement); }); } private async showTab(tab: Tab, firstToRender: boolean) { if (this.currentActiveTab) { this.currentActiveTab.view.htmlElement.style.display = 'none'; } if (!tab.addedToDom) { // Workaround for b/255966194: // make sure that the first time a tab content is rendered // (added to the DOM) it has style.display == "". This fixes the // initialization/rendering issues with cdk-virtual-scroll-viewport // components inside the tab contents. const traceViewContent = assertDefined( this.elementRef.nativeElement.querySelector('.trace-view-content'), ); traceViewContent.appendChild(tab.view.htmlElement); tab.addedToDom = true; } else { tab.view.htmlElement.style.display = ''; } this.currentActiveTab = tab; if (!firstToRender) { Analytics.Navigation.logTabSwitched( TRACE_INFO[tab.view.traces[0].type].name, ); await this.emitAppEvent(new TabbedViewSwitched(tab.view)); } } }