• 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 {
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