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