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 { 17 ChangeDetectorRef, 18 Component, 19 EventEmitter, 20 Inject, 21 Input, 22 NgZone, 23 Output, 24} from '@angular/core'; 25import {TRACE_INFO} from 'app/trace_info'; 26import {TracePipeline} from 'app/trace_pipeline'; 27import {ProgressListener} from 'interfaces/progress_listener'; 28import {Trace} from 'trace/trace'; 29import {LoadProgressComponent} from './load_progress_component'; 30 31@Component({ 32 selector: 'upload-traces', 33 template: ` 34 <mat-card class="upload-card"> 35 <mat-card-title class="title">Upload Traces</mat-card-title> 36 37 <mat-card-content 38 class="drop-box" 39 ref="drop-box" 40 (dragleave)="onFileDragOut($event)" 41 (dragover)="onFileDragIn($event)" 42 (drop)="onHandleFileDrop($event)" 43 (click)="fileDropRef.click()"> 44 <input 45 id="fileDropRef" 46 hidden 47 type="file" 48 multiple 49 onclick="this.value = null" 50 #fileDropRef 51 (change)="onInputFiles($event)" /> 52 53 <load-progress 54 *ngIf="isLoadingFiles" 55 [progressPercentage]="progressPercentage" 56 [message]="progressMessage"> 57 </load-progress> 58 59 <mat-list 60 *ngIf="!isLoadingFiles && this.tracePipeline.getTraces().getSize() > 0" 61 class="uploaded-files"> 62 <mat-list-item *ngFor="let trace of this.tracePipeline.getTraces()"> 63 <mat-icon matListIcon> 64 {{ TRACE_INFO[trace.type].icon }} 65 </mat-icon> 66 67 <p matLine>{{ TRACE_INFO[trace.type].name }}</p> 68 <p matLine *ngFor="let descriptor of trace.getDescriptors()">{{ descriptor }}</p> 69 70 <button color="primary" mat-icon-button (click)="onRemoveTrace($event, trace)"> 71 <mat-icon>close</mat-icon> 72 </button> 73 </mat-list-item> 74 </mat-list> 75 76 <div *ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() === 0" class="drop-info"> 77 <p class="mat-body-3 icon"> 78 <mat-icon inline fontIcon="upload"></mat-icon> 79 </p> 80 <p class="mat-body-1">Drag your .winscope file(s) or click to upload</p> 81 </div> 82 </mat-card-content> 83 84 <div 85 *ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() > 0" 86 class="trace-actions-container"> 87 <button 88 color="primary" 89 mat-raised-button 90 class="load-btn" 91 (click)="onViewTracesButtonClick()"> 92 View traces 93 </button> 94 95 <button color="primary" mat-stroked-button for="fileDropRef" (click)="fileDropRef.click()"> 96 Upload another file 97 </button> 98 99 <button color="primary" mat-stroked-button (click)="onClearButtonClick()">Clear all</button> 100 </div> 101 </mat-card> 102 `, 103 styles: [ 104 ` 105 .upload-card { 106 height: 100%; 107 display: flex; 108 flex-direction: column; 109 overflow: auto; 110 margin: 10px; 111 } 112 .drop-box { 113 display: flex; 114 flex-direction: column; 115 overflow: auto; 116 border: 2px dashed var(--border-color); 117 cursor: pointer; 118 } 119 .uploaded-files { 120 flex: 400px; 121 padding: 0; 122 } 123 .drop-info { 124 flex: 400px; 125 display: flex; 126 flex-direction: column; 127 justify-content: center; 128 align-items: center; 129 pointer-events: none; 130 } 131 .drop-info p { 132 opacity: 0.6; 133 font-size: 1.2rem; 134 } 135 .drop-info .icon { 136 font-size: 3rem; 137 margin: 0; 138 } 139 .trace-actions-container { 140 display: flex; 141 flex-direction: row; 142 flex-wrap: wrap; 143 gap: 10px; 144 } 145 .div-progress { 146 display: flex; 147 height: 100%; 148 flex-direction: column; 149 justify-content: center; 150 align-content: center; 151 align-items: center; 152 } 153 .div-progress p { 154 opacity: 0.6; 155 } 156 .div-progress mat-icon { 157 font-size: 3rem; 158 width: unset; 159 height: unset; 160 } 161 .div-progress mat-progress-bar { 162 max-width: 250px; 163 } 164 mat-card-content { 165 flex-grow: 1; 166 } 167 `, 168 ], 169}) 170export class UploadTracesComponent implements ProgressListener { 171 TRACE_INFO = TRACE_INFO; 172 isLoadingFiles = false; 173 progressMessage = ''; 174 progressPercentage?: number; 175 lastUiProgressUpdateTimeMs?: number; 176 177 @Input() tracePipeline!: TracePipeline; 178 @Output() filesUploaded = new EventEmitter<File[]>(); 179 @Output() viewTracesButtonClick = new EventEmitter<void>(); 180 181 constructor( 182 @Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef, 183 @Inject(NgZone) private ngZone: NgZone 184 ) {} 185 186 ngOnInit() { 187 this.tracePipeline.clear(); 188 } 189 190 onProgressUpdate(message: string | undefined, progressPercentage: number | undefined) { 191 if (!LoadProgressComponent.canUpdateComponent(this.lastUiProgressUpdateTimeMs)) { 192 return; 193 } 194 this.isLoadingFiles = true; 195 this.progressMessage = message ? message : 'Loading...'; 196 this.progressPercentage = progressPercentage; 197 this.lastUiProgressUpdateTimeMs = Date.now(); 198 this.changeDetectorRef.detectChanges(); 199 } 200 201 onOperationFinished() { 202 this.isLoadingFiles = false; 203 this.lastUiProgressUpdateTimeMs = undefined; 204 this.changeDetectorRef.detectChanges(); 205 } 206 207 onInputFiles(event: Event) { 208 const files = this.getInputFiles(event); 209 this.filesUploaded.emit(files); 210 } 211 212 onViewTracesButtonClick() { 213 this.viewTracesButtonClick.emit(); 214 } 215 216 onClearButtonClick() { 217 this.tracePipeline.clear(); 218 this.onOperationFinished(); 219 } 220 221 onFileDragIn(e: DragEvent) { 222 e.preventDefault(); 223 e.stopPropagation(); 224 } 225 226 onFileDragOut(e: DragEvent) { 227 e.preventDefault(); 228 e.stopPropagation(); 229 } 230 231 onHandleFileDrop(e: DragEvent) { 232 e.preventDefault(); 233 e.stopPropagation(); 234 const droppedFiles = e.dataTransfer?.files; 235 if (!droppedFiles) return; 236 this.filesUploaded.emit(Array.from(droppedFiles)); 237 } 238 239 onRemoveTrace(event: MouseEvent, trace: Trace<object>) { 240 event.preventDefault(); 241 event.stopPropagation(); 242 this.tracePipeline.removeTrace(trace); 243 this.onOperationFinished(); 244 } 245 246 private getInputFiles(event: Event): File[] { 247 const files: FileList | null = (event?.target as HTMLInputElement)?.files; 248 if (!files || !files[0]) { 249 return []; 250 } 251 return Array.from(files); 252 } 253} 254