blob: 9a1e832a936ed5230f655c636fb8a6544a1aa5bd [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ChangeDetectorRef,
Component,
EventEmitter,
Inject,
Input,
NgZone,
Output,
} from '@angular/core';
import {TracePipeline} from 'app/trace_pipeline';
import {ProgressListener} from 'messaging/progress_listener';
import {Trace} from 'trace/trace';
import {TRACE_INFO} from 'trace/trace_info';
import {TraceTypeUtils} from 'trace/trace_type';
import {LoadProgressComponent} from './load_progress_component';
@Component({
selector: 'upload-traces',
template: `
<mat-card class="upload-card">
<div class="card-header">
<mat-card-title class="title">Upload Traces</mat-card-title>
<div
*ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() > 0"
class="trace-actions-container">
<button
color="primary"
mat-raised-button
class="load-btn"
matTooltip="Upload trace with an associated viewer to visualize"
[matTooltipDisabled]="hasLoadedFilesWithViewers()"
[disabled]="!hasLoadedFilesWithViewers()"
(click)="onViewTracesButtonClick()">
View traces
</button>
<button class="download-btn" color="primary" mat-stroked-button (click)="downloadTracesClick.emit()">
Download all
</button>
<button color="primary" mat-stroked-button for="fileDropRef" (click)="fileDropRef.click()">
Upload another file
</button>
<button
class="clear-all-btn"
color="primary"
mat-stroked-button
(click)="onClearButtonClick()">
Clear all
</button>
</div>
</div>
<mat-card-content
class="drop-box"
ref="drop-box"
(dragleave)="onFileDragOut($event)"
(dragover)="onFileDragIn($event)"
(drop)="onFileDrop($event)"
(click)="fileDropRef.click()">
<input
id="fileDropRef"
hidden
type="file"
multiple
onclick="this.value = null"
#fileDropRef
(change)="onInputFiles($event)" />
<load-progress
*ngIf="isLoadingFiles"
[progressPercentage]="progressPercentage"
[message]="progressMessage">
</load-progress>
<mat-list
*ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() > 0"
class="uploaded-files">
<mat-list-item [class.no-visualization]="!canVisualizeTrace(trace)" [class.trace-error]="trace.isCorrupted()" *ngFor="let trace of tracePipeline.getTraces()">
<mat-icon
matListIcon
[style]="{color: TRACE_INFO[trace.type].color}">
{{ TRACE_INFO[trace.type].icon }}
</mat-icon>
<p matLine>{{ TRACE_INFO[trace.type].name }}</p>
<p matLine *ngFor="let descriptor of trace.getDescriptors()">{{ descriptor }}</p>
<mat-icon class="warning-icon" *ngIf="!canVisualizeTrace(trace)" [matTooltip]="cannotVisualizeTraceTooltip(trace)">
warning
</mat-icon>
<mat-icon class="error-icon" *ngIf="trace.isCorrupted()" [matTooltip]="traceErrorTooltip(trace)">
error
</mat-icon>
<button color="primary" mat-icon-button (click)="onRemoveTrace($event, trace)">
<mat-icon>close</mat-icon>
</button>
</mat-list-item>
</mat-list>
<div *ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() === 0" class="drop-info">
<p class="mat-body-3 icon">
<mat-icon inline fontIcon="upload"></mat-icon>
</p>
<p class="mat-body-1">Drag your .winscope file(s) or click to upload</p>
</div>
</mat-card-content>
</mat-card>
`,
styles: [
`
.upload-card {
height: 100%;
display: flex;
flex-direction: column;
overflow: auto;
margin: 10px;
padding-top: 0px;
}
.card-header {
justify-content: space-between;
align-items: center;
display: flex;
flex-direction: row;
}
.title {
padding-top: 16px;
text-align: center;
}
.trace-actions-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
padding: 4px 0px;
}
.drop-box {
display: flex;
flex-direction: column;
overflow: auto;
border: 2px dashed var(--border-color);
cursor: pointer;
}
.uploaded-files {
flex: 400px;
padding: 0;
}
.drop-info {
flex: 400px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
pointer-events: none;
}
.drop-info p {
opacity: 0.6;
font-size: 1.2rem;
}
.drop-info .icon {
font-size: 3rem;
margin: 0;
}
.div-progress {
display: flex;
height: 100%;
flex-direction: column;
justify-content: center;
align-content: center;
align-items: center;
}
.div-progress p {
opacity: 0.6;
}
.div-progress mat-icon {
font-size: 3rem;
width: unset;
height: unset;
}
.div-progress mat-progress-bar {
max-width: 250px;
}
mat-card-content {
flex-grow: 1;
}
.no-visualization {
background-color: var(--warning-background-color);
}
.trace-error {
background-color: var(--error-background-color);
}
.info-icon, .warning-icon {
flex-shrink: 0;
}
`,
],
})
export class UploadTracesComponent implements ProgressListener {
TRACE_INFO = TRACE_INFO;
isLoadingFiles = false;
progressMessage = '';
progressPercentage?: number;
lastUiProgressUpdateTimeMs?: number;
@Input() tracePipeline: TracePipeline | undefined;
@Output() filesUploaded = new EventEmitter<File[]>();
@Output() viewTracesButtonClick = new EventEmitter<void>();
@Output() downloadTracesClick = new EventEmitter<void>();
constructor(
@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef,
@Inject(NgZone) private ngZone: NgZone,
) {}
ngOnInit() {
this.tracePipeline?.clear();
}
onProgressUpdate(
message: string | undefined,
progressPercentage: number | undefined,
) {
if (
!LoadProgressComponent.canUpdateComponent(this.lastUiProgressUpdateTimeMs)
) {
return;
}
this.isLoadingFiles = true;
this.progressMessage = message ? message : 'Loading...';
this.progressPercentage = progressPercentage;
this.lastUiProgressUpdateTimeMs = Date.now();
this.changeDetectorRef.detectChanges();
}
onOperationFinished() {
this.isLoadingFiles = false;
this.lastUiProgressUpdateTimeMs = undefined;
this.changeDetectorRef.detectChanges();
}
onInputFiles(event: Event) {
const files = this.getInputFiles(event);
this.filesUploaded.emit(files);
}
onViewTracesButtonClick() {
this.viewTracesButtonClick.emit();
}
onClearButtonClick() {
this.tracePipeline?.clear();
this.onOperationFinished();
}
onFileDragIn(e: DragEvent) {
e.preventDefault();
e.stopPropagation();
}
onFileDragOut(e: DragEvent) {
e.preventDefault();
e.stopPropagation();
}
onFileDrop(e: DragEvent) {
e.preventDefault();
e.stopPropagation();
const droppedFiles = e.dataTransfer?.files;
if (!droppedFiles) return;
this.filesUploaded.emit(Array.from(droppedFiles));
}
onRemoveTrace(event: MouseEvent, trace: Trace<object>) {
event.preventDefault();
event.stopPropagation();
this.tracePipeline?.removeTrace(trace);
this.onOperationFinished();
}
hasLoadedFilesWithViewers(): boolean {
return this.ngZone.run(() => {
let hasFilesWithViewers = false;
this.tracePipeline?.getTraces().forEachTrace((trace) => {
if (
!trace.isCorrupted() &&
TraceTypeUtils.isTraceTypeWithViewer(trace.type)
) {
hasFilesWithViewers = true;
}
});
return hasFilesWithViewers;
});
}
canVisualizeTrace(trace: Trace<object>): boolean {
return TraceTypeUtils.isTraceTypeWithViewer(trace.type);
}
cannotVisualizeTraceTooltip(trace: Trace<object>): string {
return TraceTypeUtils.getReasonForNoTraceVisualization(trace.type);
}
traceErrorTooltip(trace: Trace<object>): string {
const reason = trace.getCorruptedReason() ?? 'Trace is corrupted.';
return 'Cannot visualize trace. ' + reason;
}
private getInputFiles(event: Event): File[] {
const files: FileList | null = (event?.target as HTMLInputElement)?.files;
if (!files || !files[0]) {
return [];
}
return Array.from(files);
}
}