• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {Component, ElementRef, EventEmitter, Inject, Input, Output} from '@angular/core';
18import {TRACE_INFO} from 'app/trace_info';
19import {PersistentStore} from 'common/persistent_store';
20import {View, Viewer, ViewType} from 'viewers/viewer';
21
22interface Tab extends View {
23  addedToDom: boolean;
24}
25
26@Component({
27  selector: 'trace-view',
28  template: `
29    <div class="overlay">
30      <div class="draggable-container" cdkDrag cdkDragBoundary=".overlay">
31        <!--
32        TODO:
33        this draggable div is a temporary hack. We should remove the div and move the cdkDrag
34        directives into the overlay view (e.g. ViewerScreenReocordingComponent) as soon as the new
35        Angular's directive composition API is available
36        (https://github.com/angular/angular/issues/8785).
37         -->
38      </div>
39    </div>
40    <div class="header-items-wrapper">
41      <nav mat-tab-nav-bar class="tabs-navigation-bar">
42        <a
43          *ngFor="let tab of tabs"
44          mat-tab-link
45          [active]="isCurrentActiveTab(tab)"
46          (click)="onTabClick(tab)"
47          class="tab">
48          <mat-icon
49            class="icon"
50            [matTooltip]="TRACE_INFO[tab.traceType].name"
51            [style]="{color: TRACE_INFO[tab.traceType].color, marginRight: '0.5rem'}">
52            {{ TRACE_INFO[tab.traceType].icon }}
53          </mat-icon>
54          <p>
55            {{ tab.title }}
56          </p>
57        </a>
58      </nav>
59      <button
60        color="primary"
61        mat-button
62        class="save-button"
63        (click)="downloadTracesButtonClick.emit()">
64        Download all traces
65      </button>
66    </div>
67    <mat-divider></mat-divider>
68    <div class="trace-view-content"></div>
69  `,
70  styles: [
71    `
72      .overlay {
73        z-index: 10;
74        position: fixed;
75        top: 0px;
76        left: 0px;
77        width: 100%;
78        height: 100%;
79        pointer-events: none;
80      }
81
82      .overlay .draggable-container {
83        position: absolute;
84        right: 0;
85        top: 20vh;
86      }
87
88      .header-items-wrapper {
89        display: flex;
90        flex-direction: row;
91        justify-content: space-between;
92      }
93
94      .tabs-navigation-bar {
95        height: 100%;
96      }
97
98      .trace-view-content {
99        height: 100%;
100        overflow: auto;
101      }
102    `,
103  ],
104})
105export class TraceViewComponent {
106  @Input() viewers!: Viewer[];
107  @Input() store!: PersistentStore;
108  @Output() downloadTracesButtonClick = new EventEmitter<void>();
109  @Output() activeViewChanged = new EventEmitter<View>();
110
111  TRACE_INFO = TRACE_INFO;
112
113  private elementRef: ElementRef;
114
115  tabs: Tab[] = [];
116  private currentActiveTab: undefined | Tab;
117
118  constructor(@Inject(ElementRef) elementRef: ElementRef) {
119    this.elementRef = elementRef;
120  }
121
122  ngOnChanges() {
123    this.renderViewsTab();
124    this.renderViewsOverlay();
125  }
126
127  onTabClick(tab: Tab) {
128    this.showTab(tab);
129  }
130
131  private renderViewsTab() {
132    this.tabs = this.viewers
133      .map((viewer) => viewer.getViews())
134      .flat()
135      .filter((view) => view.type === ViewType.TAB)
136      .map((view) => {
137        return {
138          type: view.type,
139          htmlElement: view.htmlElement,
140          title: view.title,
141          addedToDom: false,
142          dependencies: view.dependencies,
143          traceType: view.traceType,
144        };
145      });
146
147    this.tabs.forEach((tab) => {
148      // TODO: setting "store" this way is a hack.
149      //       Store should be part of View's interface.
150      (tab.htmlElement as any).store = this.store;
151    });
152
153    if (this.tabs.length > 0) {
154      this.showTab(this.tabs[0]);
155    }
156  }
157
158  private renderViewsOverlay() {
159    const views: View[] = this.viewers
160      .map((viewer) => viewer.getViews())
161      .flat()
162      .filter((view) => view.type === ViewType.OVERLAY);
163
164    if (views.length > 1) {
165      throw new Error(
166        'Only one overlay view is supported. To allow more overlay views, either create more than' +
167          ' one draggable containers in this component or move the cdkDrag directives into the' +
168          " overlay view when the new Angular's directive composition API is available" +
169          ' (https://github.com/angular/angular/issues/8785).'
170      );
171    }
172
173    views.forEach((view) => {
174      view.htmlElement.style.pointerEvents = 'all';
175      const container = this.elementRef.nativeElement.querySelector(
176        '.overlay .draggable-container'
177      )!;
178      container.appendChild(view.htmlElement);
179    });
180  }
181
182  private showTab(tab: Tab) {
183    if (this.currentActiveTab) {
184      this.currentActiveTab.htmlElement.style.display = 'none';
185    }
186
187    if (!tab.addedToDom) {
188      // Workaround for b/255966194:
189      // make sure that the first time a tab content is rendered
190      // (added to the DOM) it has style.display == "". This fixes the
191      // initialization/rendering issues with cdk-virtual-scroll-viewport
192      // components inside the tab contents.
193      const traceViewContent = this.elementRef.nativeElement.querySelector('.trace-view-content')!;
194      traceViewContent.appendChild(tab.htmlElement);
195      tab.addedToDom = true;
196    } else {
197      tab.htmlElement.style.display = '';
198    }
199
200    this.currentActiveTab = tab;
201    this.activeViewChanged.emit(tab);
202  }
203
204  isCurrentActiveTab(tab: Tab) {
205    return tab === this.currentActiveTab;
206  }
207}
208