• 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 */
16import {CdkVirtualScrollViewport} from '@angular/cdk/scrolling';
17import {
18  Component,
19  ElementRef,
20  HostListener,
21  Inject,
22  Input,
23  ViewChild,
24} from '@angular/core';
25import {MatSelectChange} from '@angular/material/select';
26import {TraceType} from 'trace/trace_type';
27import {CollapsibleSections} from 'viewers/common/collapsible_sections';
28import {CollapsibleSectionType} from 'viewers/common/collapsible_section_type';
29import {TimestampClickDetail, ViewerEvents} from 'viewers/common/viewer_events';
30import {timeButtonStyle} from 'viewers/components/styles/clickable_property.styles';
31import {currentElementStyle} from 'viewers/components/styles/current_element.styles';
32import {selectedElementStyle} from 'viewers/components/styles/selected_element.styles';
33import {viewerCardStyle} from 'viewers/components/styles/viewer_card.styles';
34import {UiData, UiDataEntry} from './ui_data';
35
36@Component({
37  selector: 'viewer-transactions',
38  template: `
39    <div class="card-grid">
40      <collapsed-sections
41        [class.empty]="sections.areAllSectionsExpanded()"
42        [sections]="sections"
43        (sectionChange)="sections.onCollapseStateChange($event, false)">
44      </collapsed-sections>
45      <div class="log-view entries">
46        <div class="filters">
47          <div class="time"></div>
48          <div class="id transaction-id">
49            <select-with-filter
50              label="TX ID"
51              [options]="uiData.allTransactionIds"
52              outerFilterWidth="125"
53              innerFilterWidth="125"
54              (selectChange)="onTransactionIdFilterChanged($event)">
55            </select-with-filter>
56          </div>
57          <div class="vsyncid">
58            <select-with-filter
59              label="VSYNC ID"
60              [options]="uiData.allVSyncIds"
61              outerFilterWidth="110"
62              innerFilterWidth="90"
63              (selectChange)="onVSyncIdFilterChanged($event)">
64            </select-with-filter>
65          </div>
66          <div class="pid">
67            <select-with-filter
68              label="PID"
69              [options]="uiData.allPids"
70              (selectChange)="onPidFilterChanged($event)">
71            </select-with-filter>
72          </div>
73          <div class="uid">
74            <select-with-filter
75              label="UID"
76              [options]="uiData.allUids"
77              (selectChange)="onUidFilterChanged($event)">
78            </select-with-filter>
79          </div>
80          <div class="type">
81            <select-with-filter
82              label="Type"
83              innerFilterWidth="175"
84              [options]="uiData.allTypes"
85              (selectChange)="onTypeFilterChanged($event)">
86            </select-with-filter>
87          </div>
88          <div class="id layer-or-display-id">
89            <select-with-filter
90              label="LAYER/DISP ID"
91              outerFilterWidth="125"
92              innerFilterWidth="100"
93              [options]="uiData.allLayerAndDisplayIds"
94              (selectChange)="onLayerIdFilterChanged($event)">
95            </select-with-filter>
96          </div>
97          <div class="what">
98            <select-with-filter
99              label="Search text"
100              outerFilterWidth="250"
101              innerFilterWidth="250"
102              [options]="uiData.allFlags"
103              flex="2 0 250px"
104              (selectChange)="onWhatFilterChanged($event)">
105            </select-with-filter>
106          </div>
107
108          <button
109            color="primary"
110            mat-stroked-button
111            class="go-to-current-time"
112            (click)="onGoToCurrentTimeClick()">
113            Go to Current Time
114          </button>
115        </div>
116
117        <cdk-virtual-scroll-viewport
118          transactionsVirtualScroll
119          class="scroll"
120          [scrollItems]="uiData.entries">
121          <div
122            *cdkVirtualFor="let entry of uiData.entries; let i = index"
123            class="entry"
124            [attr.item-id]="i"
125            [class.current]="isCurrentEntry(i)"
126            [class.selected]="isSelectedEntry(i)"
127            (click)="onEntryClicked(i)">
128            <div class="time">
129              <button
130                mat-button
131                color="primary"
132                (click)="onTimestampClicked(entry)">
133                {{ entry.time.formattedValue() }}
134              </button>
135            </div>
136            <div class="id transaction-id">
137              <span class="mat-body-1">{{ entry.transactionId }}</span>
138            </div>
139            <div class="vsyncid">
140              <span class="mat-body-1">{{ entry.vsyncId }}</span>
141            </div>
142            <div class="pid">
143              <span class="mat-body-1">{{ entry.pid }}</span>
144            </div>
145            <div class="uid">
146              <span class="mat-body-1">{{ entry.uid }}</span>
147            </div>
148            <div class="type">
149              <span class="mat-body-1">{{ entry.type }}</span>
150            </div>
151            <div class="id layer-or-display-id">
152              <span class="mat-body-1">{{ entry.layerOrDisplayId }}</span>
153            </div>
154            <div class="what">
155              <span class="mat-body-1">{{ entry.what }}</span>
156            </div>
157          </div>
158        </cdk-virtual-scroll-viewport>
159      </div>
160
161      <properties-view
162        *ngIf="uiData.currentPropertiesTree"
163        class="properties-view"
164        [title]="propertiesTitle"
165        [showFilter]="false"
166        [userOptions]="uiData.propertiesUserOptions"
167        [propertiesTree]="uiData.currentPropertiesTree"
168        [traceType]="${TraceType.TRANSACTIONS}"
169        [isProtoDump]="false"
170        (collapseButtonClicked)="sections.onCollapseStateChange(CollapsibleSectionType.PROPERTIES, true)"
171        [class.collapsed]="sections.isSectionCollapsed(CollapsibleSectionType.PROPERTIES)"></properties-view>
172    </div>
173  `,
174  styles: [
175    `
176      .properties-view {
177        flex: 1;
178      }
179
180      .entries .filters {
181        display: flex;
182        flex-direction: row;
183      }
184
185      .entries .scroll {
186        flex: 1;
187        height: 100%;
188      }
189
190      .scroll .entry {
191        display: flex;
192        flex-direction: row;
193      }
194
195      .filters div,
196      .entries div {
197        padding: 4px;
198      }
199
200      .time {
201        flex: 0 1 250px;
202      }
203
204      .id {
205        flex: none;
206        width: 125px;
207      }
208
209      .vsyncid {
210        flex: none;
211        width: 110px;
212      }
213
214      .pid {
215        flex: none;
216        width: 75px;
217      }
218
219      .uid {
220        flex: none;
221        width: 75px;
222      }
223
224      .type {
225        width: 200px;
226      }
227
228      .what {
229        flex: 2 0 250px;
230      }
231
232      .filters .what {
233        margin-right: 16px;
234      }
235
236      .go-to-current-time {
237        flex: none;
238        margin-top: 4px;
239        font-size: 12px;
240        height: 65%;
241        width: fit-content;
242      }
243    `,
244    selectedElementStyle,
245    currentElementStyle,
246    timeButtonStyle,
247    viewerCardStyle,
248  ],
249})
250class ViewerTransactionsComponent {
251  objectKeys = Object.keys;
252  uiData: UiData = UiData.EMPTY;
253  private lastClicked = '';
254  propertiesTitle = 'PROPERTIES - PROTO DUMP';
255  CollapsibleSectionType = CollapsibleSectionType;
256  sections = new CollapsibleSections([
257    {
258      type: CollapsibleSectionType.PROPERTIES,
259      label: this.propertiesTitle,
260      isCollapsed: false,
261    },
262  ]);
263
264  @ViewChild(CdkVirtualScrollViewport)
265  scrollComponent?: CdkVirtualScrollViewport;
266
267  constructor(@Inject(ElementRef) private elementRef: ElementRef) {}
268
269  @Input()
270  set inputData(data: UiData) {
271    this.uiData = data;
272    if (
273      this.uiData.scrollToIndex !== undefined &&
274      this.scrollComponent &&
275      this.lastClicked !==
276        this.uiData.entries[this.uiData.scrollToIndex].time.formattedValue()
277    ) {
278      this.scrollComponent.scrollToIndex(this.uiData.scrollToIndex);
279    }
280  }
281
282  onVSyncIdFilterChanged(event: MatSelectChange) {
283    this.emitEvent(ViewerEvents.VSyncIdFilterChanged, event.value);
284  }
285
286  onPidFilterChanged(event: MatSelectChange) {
287    this.emitEvent(ViewerEvents.PidFilterChanged, event.value);
288  }
289
290  onUidFilterChanged(event: MatSelectChange) {
291    this.emitEvent(ViewerEvents.UidFilterChanged, event.value);
292  }
293
294  onTypeFilterChanged(event: MatSelectChange) {
295    this.emitEvent(ViewerEvents.TypeFilterChanged, event.value);
296  }
297
298  onLayerIdFilterChanged(event: MatSelectChange) {
299    this.emitEvent(ViewerEvents.LayerIdFilterChanged, event.value);
300  }
301
302  onWhatFilterChanged(event: MatSelectChange) {
303    this.emitEvent(ViewerEvents.WhatFilterChanged, event.value);
304  }
305
306  onTransactionIdFilterChanged(event: MatSelectChange) {
307    this.emitEvent(ViewerEvents.TransactionIdFilterChanged, event.value);
308  }
309
310  onEntryClicked(index: number) {
311    this.emitEvent(ViewerEvents.LogClicked, index);
312  }
313
314  onGoToCurrentTimeClick() {
315    if (this.uiData.currentEntryIndex !== undefined && this.scrollComponent) {
316      this.scrollComponent.scrollToIndex(this.uiData.currentEntryIndex);
317    }
318  }
319
320  onTimestampClicked(entry: UiDataEntry) {
321    this.emitEvent(
322      ViewerEvents.TimestampClick,
323      new TimestampClickDetail(entry.time.getValue(), entry.traceIndex),
324    );
325  }
326
327  @HostListener('document:keydown', ['$event'])
328  async handleKeyboardEvent(event: KeyboardEvent) {
329    const index =
330      this.uiData.selectedEntryIndex ?? this.uiData.currentEntryIndex;
331    if (index === undefined) {
332      return;
333    }
334    if (event.key === 'ArrowDown' && index < this.uiData.entries.length - 1) {
335      event.preventDefault();
336      this.emitEvent(ViewerEvents.LogChangedByKeyPress, index + 1);
337    }
338
339    if (event.key === 'ArrowUp' && index > 0) {
340      event.preventDefault();
341      this.emitEvent(ViewerEvents.LogChangedByKeyPress, index - 1);
342    }
343  }
344
345  isCurrentEntry(index: number): boolean {
346    return index === this.uiData.currentEntryIndex;
347  }
348
349  isSelectedEntry(index: number): boolean {
350    return index === this.uiData.selectedEntryIndex;
351  }
352
353  private emitEvent(event: string, data: any) {
354    const customEvent = new CustomEvent(event, {
355      bubbles: true,
356      detail: data,
357    });
358    this.elementRef.nativeElement.dispatchEvent(customEvent);
359  }
360}
361
362export {ViewerTransactionsComponent};
363