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 {Component, ElementRef, Inject, Input, ViewChild} from '@angular/core'; 18import {MatSelectChange} from '@angular/material/select'; 19import {Events} from './events'; 20import {UiData} from './ui_data'; 21 22@Component({ 23 selector: 'viewer-protolog', 24 template: ` 25 <div class="card-grid container"> 26 <div class="filters"> 27 <div class="log-level"> 28 <mat-form-field appearance="fill"> 29 <mat-label>Log level</mat-label> 30 <mat-select (selectionChange)="onLogLevelsChange($event)" multiple> 31 <mat-option *ngFor="let level of uiData.allLogLevels" [value]="level"> 32 {{ level }} 33 </mat-option> 34 </mat-select> 35 </mat-form-field> 36 </div> 37 <div class="tag"> 38 <mat-form-field appearance="fill"> 39 <mat-label>Tags</mat-label> 40 <mat-select (selectionChange)="onTagsChange($event)" multiple> 41 <mat-option *ngFor="let tag of uiData.allTags" [value]="tag"> 42 {{ tag }} 43 </mat-option> 44 </mat-select> 45 </mat-form-field> 46 </div> 47 <div class="source-file"> 48 <mat-form-field appearance="fill"> 49 <mat-label>Source files</mat-label> 50 <mat-select (selectionChange)="onSourceFilesChange($event)" multiple> 51 <mat-option *ngFor="let file of uiData.allSourceFiles" [value]="file"> 52 {{ file }} 53 </mat-option> 54 </mat-select> 55 </mat-form-field> 56 </div> 57 <div class="text"> 58 <mat-form-field appearance="fill"> 59 <mat-label>Search text</mat-label> 60 <input matInput [(ngModel)]="searchString" (input)="onSearchStringChange()" /> 61 </mat-form-field> 62 </div> 63 </div> 64 <cdk-virtual-scroll-viewport itemSize="16" class="scroll-messages"> 65 <div 66 *cdkVirtualFor="let message of uiData.messages; let i = index" 67 class="message" 68 [class.current-message]="isCurrentMessage(i)"> 69 <div class="time"> 70 <span class="mat-body-1">{{ message.time }}</span> 71 </div> 72 <div class="log-level"> 73 <span class="mat-body-1">{{ message.level }}</span> 74 </div> 75 <div class="tag"> 76 <span class="mat-body-1">{{ message.tag }}</span> 77 </div> 78 <div class="source-file"> 79 <span class="mat-body-1">{{ message.at }}</span> 80 </div> 81 <div class="text"> 82 <span class="mat-body-1">{{ message.text }}</span> 83 </div> 84 </div> 85 </cdk-virtual-scroll-viewport> 86 </div> 87 `, 88 styles: [ 89 ` 90 .container { 91 padding: 16px; 92 box-sizing: border-box; 93 display: flex; 94 flex-direction: column; 95 } 96 97 .filters { 98 display: flex; 99 flex-direction: row; 100 margin-top: 16px; 101 } 102 103 .scroll-messages { 104 height: 100%; 105 flex: 1; 106 } 107 108 .message { 109 display: flex; 110 flex-direction: row; 111 overflow-wrap: anywhere; 112 } 113 114 .message.current-message { 115 background-color: #365179; 116 color: white; 117 } 118 119 .time { 120 flex: 2; 121 } 122 123 .log-level { 124 flex: 1; 125 } 126 127 .filters .log-level { 128 flex: 3; 129 } 130 131 .tag { 132 flex: 2; 133 } 134 135 .source-file { 136 flex: 4; 137 } 138 139 .text { 140 flex: 10; 141 } 142 143 .filters div { 144 margin: 4px; 145 } 146 147 .message div { 148 margin: 4px; 149 } 150 151 mat-form-field { 152 width: 100%; 153 } 154 `, 155 ], 156}) 157export class ViewerProtologComponent { 158 constructor(@Inject(ElementRef) elementRef: ElementRef) { 159 this.elementRef = elementRef; 160 } 161 162 @Input() 163 set inputData(data: UiData) { 164 this.uiData = data; 165 if (this.uiData.currentMessageIndex !== undefined && this.scrollComponent) { 166 this.scrollComponent.scrollToIndex(this.uiData.currentMessageIndex); 167 } 168 } 169 170 onLogLevelsChange(event: MatSelectChange) { 171 this.emitEvent(Events.LogLevelsFilterChanged, event.value); 172 } 173 174 onTagsChange(event: MatSelectChange) { 175 this.emitEvent(Events.TagsFilterChanged, event.value); 176 } 177 178 onSourceFilesChange(event: MatSelectChange) { 179 this.emitEvent(Events.SourceFilesFilterChanged, event.value); 180 } 181 182 onSearchStringChange() { 183 this.emitEvent(Events.SearchStringFilterChanged, this.searchString); 184 } 185 186 isCurrentMessage(index: number): boolean { 187 return index === this.uiData.currentMessageIndex; 188 } 189 190 private emitEvent(event: string, data: any) { 191 const customEvent = new CustomEvent(event, { 192 bubbles: true, 193 detail: data, 194 }); 195 this.elementRef.nativeElement.dispatchEvent(customEvent); 196 } 197 198 @ViewChild(CdkVirtualScrollViewport) scrollComponent!: CdkVirtualScrollViewport; 199 200 uiData: UiData = UiData.EMPTY; 201 private searchString = ''; 202 private elementRef: ElementRef; 203} 204