Update Main Winscope UI.

Update to match figma designs for the viewer section - app toolbar, tabs
for different viewers, etc.

Bug: b/244715720
Test: npm run test:all. also upload any trace to see what it looks like
Change-Id: I85b64e4a660fd30c6d71eb8ef3cb3b45c37a2943
This commit is contained in:
Priyanka Patel
2022-09-02 13:50:44 +00:00
parent e391056ae2
commit a5607fc269
16 changed files with 405 additions and 368 deletions

View File

@@ -19,6 +19,7 @@ import { HttpClientModule } from "@angular/common/http";
import { MatSliderModule } from "@angular/material/slider";
import { MatTooltipModule } from "@angular/material/tooltip";
import { MatToolbarModule } from "@angular/material/toolbar";
import { MatTabsModule } from "@angular/material/tabs";
import { AppComponent } from "./components/app.component";
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
@@ -31,7 +32,6 @@ import { UploadTracesComponent } from "./components/upload_traces.component";
import { HierarchyComponent } from "viewers/components/hierarchy.component";
import { PropertiesComponent } from "viewers/components/properties.component";
import { RectsComponent } from "viewers/components/rects/rects.component";
import { TraceViewHeaderComponent } from "./components/trace_view_header.component";
import { TraceViewComponent } from "./components/trace_view.component";
import { TreeComponent } from "viewers/components/tree.component";
import { TreeNodeComponent } from "viewers/components/tree_node.component";
@@ -53,7 +53,6 @@ import { TransformMatrixComponent } from "viewers/components/transform_matrix.co
HierarchyComponent,
PropertiesComponent,
RectsComponent,
TraceViewHeaderComponent,
TraceViewComponent,
TreeComponent,
TreeNodeComponent,
@@ -83,7 +82,8 @@ import { TransformMatrixComponent } from "viewers/components/transform_matrix.co
MatSliderModule,
MatRadioModule,
MatTooltipModule,
MatToolbarModule
MatToolbarModule,
MatTabsModule
],
bootstrap: [AppComponent]
})

View File

@@ -29,6 +29,7 @@ import { AdbProxyComponent } from "./adb_proxy.component";
import { WebAdbComponent } from "./web_adb.component";
import { TraceConfigComponent } from "./trace_config.component";
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
import { TraceViewComponent } from "./trace_view.component";
describe("AppComponent", () => {
let fixture: ComponentFixture<AppComponent>;
@@ -55,7 +56,8 @@ describe("AppComponent", () => {
AdbProxyComponent,
WebAdbComponent,
TraceConfigComponent,
ViewerSurfaceFlingerComponent
ViewerSurfaceFlingerComponent,
TraceViewComponent
],
}).overrideComponent(AppComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }

View File

@@ -13,25 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Injector, Inject, ViewEncapsulation, Input } from "@angular/core";
import { Component, Injector, Inject, ViewEncapsulation, Input, ViewChild } from "@angular/core";
import { createCustomElement } from "@angular/elements";
import { TraceCoordinator } from "../trace_coordinator";
import { proxyClient, ProxyState } from "trace_collection/proxy_client";
import { PersistentStore } from "common/persistent_store";
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
import { TraceViewComponent } from "./trace_view.component";
import { Timestamp } from "common/trace/timestamp";
import { MatSliderChange } from "@angular/material/slider";
import { Viewer } from "viewers/viewer";
@Component({
selector: "app-root",
template: `
<mat-toolbar class="app-toolbar">
<span id="app-title">Winscope</span>
<button mat-raised-button *ngIf="dataLoaded" (click)="clearData()">Back to Home</button>
<span class="toolbar-wrapper">
<button mat-raised-button *ngIf="dataLoaded" (click)="toggleTimestamp()">Start/End Timestamp</button>
<button class="upload-new-btn white-btn" mat-raised-button *ngIf="dataLoaded" (click)="clearData()">Upload New</button>
</span>
</mat-toolbar>
<div class="welcome-info" *ngIf="!dataLoaded">
@@ -48,6 +48,10 @@ import { Viewer } from "viewers/viewer";
</div>
<div id="viewers" [class]="showViewers()">
<trace-view
[store]="store"
[traceCoordinator]="traceCoordinator"
></trace-view>
</div>
<div id="timescrub">
@@ -67,15 +71,32 @@ import { Viewer } from "viewers/viewer";
`,
styles: [
`
.time-slider {width: 100%}
.upload-new-btn {float: right}
.time-slider {
width: 100%
}
.upload-new-btn {
float: right;
position: relative;
vertical-align: middle;
display: inline-block;
}
.app-toolbar {
border-bottom: 1px solid var(--default-border);
box-shadow: none;
background-color: rgba(1, 1, 1, 0);
height: 56px;
vertical-align: middle;
position: relative;
display: inline-block;
}
.toolbar-wrapper {
width: 100%;
height: 100%;
vertical-align: middle;
position: relative;
display: inline-block;
align-content: center;
}
.welcome-info {
text-align: center;
font: inherit;
@@ -90,11 +111,10 @@ export class AppComponent {
traceCoordinator: TraceCoordinator;
states = ProxyState;
store: PersistentStore = new PersistentStore();
@Input() dataLoaded = false;
viewersCreated = false;
currentTimestamp?: Timestamp;
currentTimestampIndex = 0;
allTimestamps: Timestamp[] = [];
@Input() dataLoaded = false;
constructor(
@Inject(Injector) injector: Injector
@@ -108,52 +128,9 @@ export class AppComponent {
customElements.define("viewer-surface-flinger",
createCustomElement(ViewerSurfaceFlingerComponent, {injector}));
}
if (!customElements.get("trace-view")) {
customElements.define("trace-view",
createCustomElement(TraceViewComponent, {injector}));
}
}
onDataLoadedChange(dataLoaded: boolean) {
if (dataLoaded && !this.viewersCreated) {
this.allTimestamps = this.traceCoordinator.getTimestamps();
this.traceCoordinator.createViewers();
this.createViewerElements();
this.currentTimestampIndex = 0;
this.notifyCurrentTimestamp();
this.viewersCreated = true;
this.dataLoaded = dataLoaded;
}
}
createViewerElements() {
const viewersDiv = document.querySelector("div#viewers")!;
viewersDiv.innerHTML = "";
let cardCounter = 0;
this.traceCoordinator.getViewers().forEach((viewer: Viewer) => {
const traceView = document.createElement("trace-view");
(traceView as any).title = viewer.getTitle();
(traceView as any).dependencies = viewer.getDependencies();
(traceView as any).showTrace = true;
traceView.addEventListener("saveTraces", ($event: any) => {
this.traceCoordinator.saveTraces($event.detail);
});
viewersDiv.appendChild(traceView);
const traceCard = traceView.querySelector(".trace-card")!;
traceCard.id = `card-${cardCounter}`;
(traceView as any).cardId = cardCounter;
cardCounter++;
const traceCardContent = traceCard.querySelector(".trace-card-content")!;
const view = viewer.getView();
(view as any).store = this.store;
traceCardContent.appendChild(view);
});
}
updateCurrentTimestamp(event: MatSliderChange) {
public updateCurrentTimestamp(event: MatSliderChange) {
if (event.value) {
this.currentTimestampIndex = event.value;
this.notifyCurrentTimestamp();
@@ -176,7 +153,6 @@ export class AppComponent {
public clearData() {
this.dataLoaded = false;
this.viewersCreated = false;
this.traceCoordinator.clearData();
proxyClient.adbData = [];
}
@@ -185,4 +161,14 @@ export class AppComponent {
const isShown = this.dataLoaded ? "show" : "hide";
return ["viewers", isShown];
}
public onDataLoadedChange(dataLoaded: boolean) {
if (dataLoaded && !(this.traceCoordinator.getViewers().length > 0)) {
this.allTimestamps = this.traceCoordinator.getTimestamps();
this.traceCoordinator.createViewers();
this.currentTimestampIndex = 0;
this.notifyCurrentTimestamp();
this.dataLoaded = dataLoaded;
}
}
}

View File

@@ -18,7 +18,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing";
import { TraceViewComponent } from "./trace_view.component";
import { MatCardModule } from "@angular/material/card";
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from "@angular/core";
import { TraceType } from "common/trace/trace_type";
import { TraceCoordinator } from "app/trace_coordinator";
describe("TraceViewComponent", () => {
let fixture: ComponentFixture<TraceViewComponent>;
@@ -36,26 +36,55 @@ describe("TraceViewComponent", () => {
}).compileComponents();
fixture = TestBed.createComponent(TraceViewComponent);
component = fixture.componentInstance;
component.traceCoordinator = new TraceCoordinator();
component.viewerTabs = [
{
label: "Surface Flinger",
cardId: 0,
},
{
label: "Window Manager",
cardId: 1,
}
];
htmlElement = fixture.nativeElement;
component.dependencies = [TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER];
component.showTrace = true;
});
it("can be created", () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});
it("check that mat card title and contents are displayed", () => {
it("creates viewer tabs", () => {
fixture.detectChanges();
const title = htmlElement.querySelector(".trace-card-title");
expect(title).toBeTruthy();
const header = title?.querySelector("trace-view-header");
expect(header).toBeTruthy();
const tabs = htmlElement.querySelectorAll(".viewer-tab");
expect(tabs.length).toEqual(2);
expect(component.activeViewerCardId).toEqual(0);
});
it("check that card content is created", () => {
it("changes active viewer on click", async () => {
fixture.detectChanges();
const content = htmlElement.querySelector(".trace-card-content") as HTMLElement;
expect(content).toBeTruthy();
expect(component.activeViewerCardId).toEqual(0);
const tabs = htmlElement.querySelectorAll(".viewer-tab");
tabs[0].dispatchEvent(new Event("click"));
fixture.detectChanges();
await fixture.whenStable();
const firstId = component.activeViewerCardId;
tabs[1].dispatchEvent(new Event("click"));
fixture.detectChanges();
await fixture.whenStable();
const secondId = component.activeViewerCardId;
expect(firstId !== secondId).toBeTrue;
});
it("downloads all traces", async () => {
spyOn(component, "saveTraces").and.callThrough();
fixture.detectChanges();
const downloadButton: HTMLButtonElement | null = htmlElement.querySelector(".save-btn");
expect(downloadButton).toBeInstanceOf(HTMLButtonElement);
downloadButton?.dispatchEvent(new Event("click"));
fixture.detectChanges();
await fixture.whenStable();
expect(component.saveTraces).toHaveBeenCalled();
});
});

View File

@@ -16,42 +16,145 @@
import {
Component,
Input,
Output,
EventEmitter
Inject,
ElementRef,
} from "@angular/core";
import { TRACE_INFO } from "../trace_info";
import { TraceType } from "common/trace/trace_type";
import { TraceCoordinator } from "app/trace_coordinator";
import { PersistentStore } from "common/persistent_store";
import { Viewer } from "viewers/viewer";
@Component({
selector: "trace-view",
template: `
<mat-card class="trace-card">
<mat-card-header>
<mat-card-title class="trace-card-title" *ngIf="dependencies">
<trace-view-header
[title]="title"
[(showTrace)]="showTrace"
[dependencies]="dependencies"
[cardId]="cardId"
(saveTraceChange)="onSaveTraces($event)"
></trace-view-header>
</mat-card-title>
<mat-card-header class="trace-view-header">
<span class="header-items-wrapper">
<nav mat-tab-nav-bar class="viewer-nav-bar">
<a
mat-tab-link
*ngFor="let tab of viewerTabs"
[active]="isCurrentActiveCard(tab.cardId)"
(click)="showViewer(tab.cardId)"
class="viewer-tab"
>{{tab.label}}</a>
</nav>
<button
mat-raised-button
class="icon-button white-btn save-btn"
(click)="saveTraces()"
>Download all traces</button>
</span>
</mat-card-header>
<mat-card-content class="trace-card-content" [hidden]="!showTrace">
<mat-card-content class="trace-view-content">
</mat-card-content>
</mat-card>
`,
styles: [
`
:host /deep/ .trace-view-header .mat-card-header-text {
margin: 0;
}
.header-items-wrapper {
width: 100%;
vertical-align: middle;
position: relative;
display: inline-block;
}
.viewer-nav-bar {
vertical-align: middle;
display: inline-block;
}
.save-btn {
float: right;
vertical-align: middle;
border: none;
height: 100%;
margin: 0;
display: inline-block;
}
`
]
})
export class TraceViewComponent {
@Input() title!: string;
@Input() dependencies!: TraceType[];
@Input() showTrace = true;
@Input() cardId = 0;
@Output() saveTraces = new EventEmitter<TraceType[]>();
@Input() store!: PersistentStore;
@Input() traceCoordinator!: TraceCoordinator;
viewerTabs: ViewerTab[] = [];
viewersAdded = false;
activeViewerCardId = 0;
TRACE_INFO = TRACE_INFO;
constructor(
@Inject(ElementRef) private elementRef: ElementRef,
) {}
onSaveTraces(dependencies: TraceType[]) {
this.saveTraces.emit(dependencies);
ngDoCheck() {
if (this.traceCoordinator.getViewers().length > 0 && !this.viewersAdded) {
let cardCounter = 0;
this.viewerTabs = [];
this.traceCoordinator.getViewers().forEach((viewer: Viewer) => {
// create tab for viewer nav bar
const tab = {
label: viewer.getTitle(),
cardId: cardCounter,
};
this.viewerTabs.push(tab);
// add properties to view and add view to trace view card
const view = viewer.getView();
(view as any).store = this.store;
view.id = `card-${cardCounter}`;
view.style.display = this.isActiveViewerCard(cardCounter) ? "" : "none";
const traceViewContent = this.elementRef.nativeElement.querySelector(".trace-view-content")!;
traceViewContent.appendChild(view);
cardCounter++;
});
this.viewersAdded = true;
} else if (this.traceCoordinator.getViewers().length === 0 && this.viewersAdded) {
this.activeViewerCardId = 0;
this.viewersAdded = false;
}
}
public showViewer(cardId: number) {
this.changeViewerVisibility(false);
this.activeViewerCardId = cardId;
this.changeViewerVisibility(true);
}
public isCurrentActiveCard(cardId: number) {
return this.activeViewerCardId === cardId;
}
public async saveTraces() {
const zipFile = await this.traceCoordinator.saveTracesAsZip();
const zipFileName = "winscope.zip";
const a = document.createElement("a");
document.body.appendChild(a);
const url = window.URL.createObjectURL(zipFile);
a.href = url;
a.download = zipFileName;
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
private isActiveViewerCard(cardId: number) {
return this.activeViewerCardId === cardId;
}
private changeViewerVisibility(show: boolean) {
const view = document.querySelector(`#card-${this.activeViewerCardId}`);
if (view) {
(view as HTMLElement).style.display = show ? "" : "none";
(view as any).active = show;
}
}
}
interface ViewerTab {
label: string,
cardId: number
}

View File

@@ -1,115 +0,0 @@
/*
* 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 { CommonModule } from "@angular/common";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { TraceViewHeaderComponent } from "./trace_view_header.component";
import { MatIconModule } from "@angular/material/icon";
import { MatButtonModule } from "@angular/material/button";
import { TraceType } from "common/trace/trace_type";
describe("TraceViewHeaderComponent", () => {
let fixture: ComponentFixture<TraceViewHeaderComponent>;
let component: TraceViewHeaderComponent;
let htmlElement: HTMLElement;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
CommonModule,
MatIconModule,
MatButtonModule
],
declarations: [TraceViewHeaderComponent]
}).compileComponents();
fixture = TestBed.createComponent(TraceViewHeaderComponent);
component = fixture.componentInstance;
htmlElement = fixture.nativeElement;
component.dependencies = [TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER];
});
it("can be created", () => {
expect(component).toBeTruthy();
});
it("check that toggle button is displayed, expanded on default", () => {
component.showTrace = true;
fixture.detectChanges();
const toggleButton = htmlElement.querySelector(".toggle-btn");
expect(toggleButton).toBeTruthy();
const chevronIcon = toggleButton?.querySelector("mat-icon");
expect(chevronIcon).toBeTruthy;
});
it("check that toggle button icon is a right chevron when minimised ", () => {
component.showTrace = false;
fixture.detectChanges();
const toggleButton = htmlElement.querySelector(".toggle-btn");
const chevronIcon = toggleButton?.querySelector("mat-icon");
expect(chevronIcon).toBeTruthy;
});
it("check that clicking toggle button causes view to minimise", async () => {
component.showTrace = true;
fixture.detectChanges();
spyOn(component, "toggleView").and.callThrough();
const button: HTMLButtonElement | null = htmlElement.querySelector(".toggle-btn");
expect(button).toBeInstanceOf(HTMLButtonElement);
button?.dispatchEvent(new Event("click"));
await fixture.whenStable();
expect(component.toggleView).toHaveBeenCalled();
fixture.detectChanges();
expect (htmlElement.querySelector(".toggle-btn")?.querySelector("mat-icon")?.innerHTML).toContain("chevron_right");
});
it("check that dependency icons show", () => {
fixture.detectChanges();
const dependencyIcons = htmlElement.querySelectorAll(".dep-icon");
expect(dependencyIcons).toBeTruthy();
expect(dependencyIcons.length).toBe(2);
});
it("check that title is displayed", () => {
component.title = "Surface Flinger, Window Manager";
fixture.detectChanges();
const title = htmlElement.querySelector(".trace-card-title-text");
expect(title).toBeTruthy();
expect(title?.innerHTML).toContain("Surface Flinger");
expect(title?.innerHTML).toContain("Window Manager");
});
it("check that save button is displayed", () => {
fixture.detectChanges();
const saveButton = htmlElement.querySelectorAll(".save-btn");
expect(saveButton).toBeTruthy();
});
it("check that clicking save button emits", async () => {
spyOn(component, "saveTraces").and.callThrough();
spyOn(component.saveTraceChange, "emit");
const button: HTMLButtonElement | null = htmlElement.querySelector(".save-btn");
expect(button).toBeInstanceOf(HTMLButtonElement);
button?.dispatchEvent(new Event("click"));
await fixture.whenStable();
expect(component.saveTraces).toHaveBeenCalled();
expect(component.saveTraceChange.emit).toHaveBeenCalled();
});
it("check that screenshot button is displayed", () => {
fixture.detectChanges();
const screenshotButton = htmlElement.querySelectorAll("#screenshot-btn");
expect(screenshotButton).toBeTruthy();
});
});

View File

@@ -1,88 +0,0 @@
/*
* 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 {
Component,
Input,
Output,
EventEmitter
} from "@angular/core";
import { TRACE_INFO } from "../trace_info";
import { TraceType } from "common/trace/trace_type";
import html2canvas from "html2canvas";
@Component({
selector: "trace-view-header",
template: `
<button class="icon-button toggle-btn" (click)="toggleView()">
<mat-icon aria-hidden="true">
{{ showTrace ? "arrow_drop_down" : "chevron_right" }}
</mat-icon>
</button>
<mat-icon *ngFor="let dep of dependencies" aria-hidden="true" class="icon-button dep-icon">{{TRACE_INFO[dep].icon}}</mat-icon>
<span class="trace-card-title-text">
{{title}}
</span>
<button class="icon-button save-btn" (click)="saveTraces()">
<mat-icon aria-hidden="true">save_alt</mat-icon>
</button>
<button id="screenshot-btn" (click)="takeScreenshot()" class="icon-button">
<mat-icon aria-hidden="true">camera_alt</mat-icon>
</button>
`,
styles: [
".trace-card-title-text {font: inherit; display: inline-block; vertical-align: middle;}",
]
})
export class TraceViewHeaderComponent {
@Input() title?: string;
@Input() dependencies?: TraceType[];
@Input() showTrace = true;
@Input() cardId!: number ;
@Output() showTraceChange = new EventEmitter<boolean>();
@Output() saveTraceChange = new EventEmitter<TraceType[]>();
TRACE_INFO = TRACE_INFO;
toggleView() {
this.showTrace = !this.showTrace;
this.showTraceChange.emit(this.showTrace);
}
public saveTraces() {
this.saveTraceChange.emit(this.dependencies);
}
public takeScreenshot() {
const el = document.querySelector(`#card-${this.cardId}`);
if (el) {
html2canvas((el as HTMLElement)).then((canvas) => {
const uri = canvas.toDataURL();
const filename = "Winscope-Screenshot.png";
const link = document.createElement("a");
if (typeof link.download === "string") {
link.href = uri;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} else {
window.open(uri);
}
});
}
}
}

View File

@@ -23,6 +23,7 @@ import { ViewerFactory } from "viewers/viewer_factory";
import { LoadedTrace } from "app/loaded_trace";
import { TRACE_INFO } from "./trace_info";
import { TimestampUtils } from "common/trace/timestamp_utils";
import { FileUtils } from "common/utils/file_utils";
class TraceCoordinator {
private parsers: Parser[];
@@ -132,25 +133,27 @@ class TraceCoordinator {
setTraces.dataReady = false;
}
saveTraces(traceTypes: TraceType[]) {
const blobs: Blob[] = [];
traceTypes.forEach(type => {
const trace = this.findParser(type)?.getTrace();
getTraceTypeBlobMap(): Map<TraceType, Blob> {
const traceTypeBlobMap: Map<TraceType, Blob> = new Map();
this.parsers.forEach(parser => {
const type = parser.getTraceType();
const trace = parser.getTrace();
if (trace) {
blobs.push(trace);
traceTypeBlobMap.set(type, trace);
}
});
blobs.forEach((blob, idx) => {
const a = document.createElement("a");
document.body.appendChild(a);
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = (blob as any).name ?? `${TRACE_INFO[traceTypes[idx]].name}.pb`;
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
});
return traceTypeBlobMap;
}
async saveTracesAsZip(): Promise<Blob> {
const traceTypeBlobMap: Map<TraceType, Blob> = this.getTraceTypeBlobMap();
const fileNameBlobMap = new Map<string, Blob>();
for (const [traceType, blob] of traceTypeBlobMap.entries()) {
fileNameBlobMap.set(TRACE_INFO[traceType].name, blob);
}
return await FileUtils.createZipArchive(fileNameBlobMap);
}
}
export { TraceCoordinator };
export { TraceCoordinator };

View File

@@ -0,0 +1,30 @@
/*
* 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 JSZip from "jszip";
class FileUtils {
static async createZipArchive(fileNameBlobMap: Map<string, Blob>): Promise<Blob> {
const zip = new JSZip();
for (const [fileName, blob] of fileNameBlobMap.entries()) {
const traceFolder = zip.folder(fileName);
traceFolder?.file(fileName, blob);
}
const zipFile = await zip.generateAsync({type: "blob"});
return zipFile;
}
}
export {FileUtils};

View File

@@ -66,7 +66,10 @@ button {
}
.trace-card {
border: 1px solid var(--default-border);
padding: 0 !important;
margin: 0 !important;
border: none;
box-shadow: none !important;
height: 100%;
overflow: auto;
display: flex;
@@ -101,8 +104,8 @@ mat-icon {
.file-icon {
vertical-align: middle;
outline: none !important;
background: none !important;
outline: none;
background: none;
}
.card-block {
@@ -125,7 +128,7 @@ button.mat-raised-button span {
}
.input {
opacity: 0 !important;
opacity: 0;
width: 100%;
height: 100%;
top: 0;
@@ -150,9 +153,6 @@ button.mat-raised-button span {
display: none !important;
}
[hidden] {
display: none !important;
}
.icon-button {
background: none;
@@ -237,7 +237,7 @@ app-root .white-btn {
.mat-slider-thumb-label-text{font-family:"Google Sans", sans-serif}
.mat-stepper-vertical,.mat-stepper-horizontal{font-family:"Google Sans", sans-serif}
.mat-tab-group{font-family:"Google Sans", sans-serif}
.mat-tab-label,.mat-tab-link{font-family:"Google Sans", sans-serif;}
.mat-tab-label,.mat-tab-link{font-family:"Google Sans", sans-serif; font-weight: 500;}
.mat-toolbar,.mat-toolbar h1,.mat-toolbar h2,.mat-toolbar h3,.mat-toolbar h4,.mat-toolbar h5,.mat-toolbar h6{font-family:"Google Sans", sans-serif;}
.mat-tooltip{font-family:"Google Sans", sans-serif;}
.mat-list-item{font-family:"Google Sans", sans-serif}

View File

@@ -29,7 +29,7 @@ describe("Viewer SurfaceFlinger", () => {
const loadData = element(by.css(".load-btn"));
loadData.click();
const surfaceFlingerCard: ElementFinder = element(by.css(".trace-card-title-text"));
expect(surfaceFlingerCard.getText()).toContain("Surface Flinger");
const surfaceFlingerViewer: ElementFinder = element(by.css("viewer-surface-flinger"));
expect(surfaceFlingerViewer).toBeTruthy();
});
});

View File

@@ -25,7 +25,7 @@ import { TraceType } from "common/trace/trace_type";
selector: "hierarchy-view",
template: `
<mat-card-header class="view-header">
<mat-card-title class="title-filter">
<div class="title-filter">
<span class="hierarchy-title">Hierarchy</span>
<mat-form-field class="filter-field">
<mat-label>Filter...</mat-label>
@@ -36,7 +36,7 @@ import { TraceType } from "common/trace/trace_type";
name="filter"
/>
</mat-form-field>
</mat-card-title>
</div>
<div class="view-controls">
<mat-checkbox
*ngFor="let option of objectKeys(userOptions)"
@@ -84,6 +84,7 @@ import { TraceType } from "common/trace/trace_type";
styles: [
`
.view-header {
position: relative;
display: block;
width: 100%;
min-height: 3.75rem;
@@ -96,6 +97,7 @@ import { TraceType } from "common/trace/trace_type";
display: flex;
align-items: center;
width: 100%;
margin-bottom: 12px;
}
.hierarchy-title {
@@ -114,7 +116,7 @@ import { TraceType } from "common/trace/trace_type";
display: inline-block;
font-size: 12px;
font-weight: normal;
margin-left: 5px
margin-left: 5px;
}
.hierarchy-content {

View File

@@ -23,7 +23,7 @@ import { Layer } from "common/trace/flickerlib/common";
selector: "properties-view",
template: `
<mat-card-header class="view-header">
<mat-card-title class="title-filter">
<div class="title-filter">
<span class="properties-title">Properties</span>
<mat-form-field class="filter-field">
<mat-label>Filter...</mat-label>
@@ -34,7 +34,7 @@ import { Layer } from "common/trace/flickerlib/common";
name="filter"
/>
</mat-form-field>
</mat-card-title>
</div>
<div class="view-controls">
<mat-checkbox
*ngFor="let option of objectKeys(userOptions)"
@@ -80,6 +80,7 @@ import { Layer } from "common/trace/flickerlib/common";
display: flex;
align-items: center;
width: 100%;
margin-bottom: 12px;
}
.properties-title {
@@ -90,14 +91,14 @@ import { Layer } from "common/trace/flickerlib/common";
font-size: 16px;
transform: scale(0.7);
right: 0px;
position: absolute
position: absolute;
}
.view-controls {
display: inline-block;
font-size: 12px;
font-weight: normal;
margin-left: 5px
margin-left: 5px;
}
.properties-content{

View File

@@ -144,7 +144,8 @@ export class CanvasGraphics {
this.clearLabelElements();
this.rects.forEach(rect => {
const mustNotDrawInVisibleView = this.visibleView && !rect.isVisible;
const mustNotDrawInXrayViewWithoutVirtualDisplays = !this.visibleView && !this.showVirtualDisplays && rect.isDisplay && rect.isVirtual;
const mustNotDrawInXrayViewWithoutVirtualDisplays =
!this.visibleView && !this.showVirtualDisplays && rect.isDisplay && rect.isVirtual;
if (mustNotDrawInVisibleView || mustNotDrawInXrayViewWithoutVirtualDisplays) {
rectCounter++;
return;
@@ -264,7 +265,7 @@ export class CanvasGraphics {
//add rectangle label
const rectLabelDiv: HTMLElement = document.createElement("div");
this.labelElements.push(rectLabelDiv);
rectLabelDiv.className = "rect-label";
rectLabelDiv.textContent = labelText;
rectLabelDiv.style.fontSize = "10px";
if (isGrey) {
@@ -275,6 +276,7 @@ export class CanvasGraphics {
const textCanvas = document.createElement("canvas");
const labelContext = textCanvas.getContext("2d");
let labelWidth = 0;
if (labelContext?.font) {
labelContext.font = rectLabelDiv.style.font;
@@ -288,7 +290,9 @@ export class CanvasGraphics {
);
} else {
rectLabel.position.set(
endPos.x - labelWidth * this.labelXFactor, endPos.y - this.labelShift * labelWidth * this.labelXFactor, endPos.z
endPos.x - labelWidth * this.labelXFactor,
endPos.y - this.labelShift * labelWidth * this.labelXFactor,
endPos.z
);
}
@@ -355,7 +359,7 @@ export class CanvasGraphics {
}
clearLabelElements() {
this.labelElements.forEach(el => el.remove());
document.querySelectorAll(".rect-label").forEach(el => el.remove());
}
updateZoom(isZoomIn: boolean) {
@@ -414,7 +418,6 @@ export class CanvasGraphics {
private highlightedItems: Array<string> = [];
private camera: THREE.OrthographicCamera;
private rects: Rectangle[] = [];
private labelElements: HTMLElement[] = [];
private targetObjects: any[] = [];
private canvas?: HTMLCanvasElement;
}

View File

@@ -24,7 +24,9 @@ import { ViewerEvents } from "viewers/common/viewer_events";
selector: "rects-view",
template: `
<mat-card-header class="view-controls">
<mat-card-title><span>Layers</span></mat-card-title>
<div class="rects-title">
<span>Layers</span>
</div>
<div class="top-view-controls">
<div class="top-view-controls">
<mat-checkbox
@@ -89,37 +91,104 @@ import { ViewerEvents } from "viewers/common/viewer_events";
</mat-card-content>
`,
styles: [
"@import 'https://fonts.googleapis.com/icon?family=Material+Icons';",
".rects-content {position: relative}",
".canvas-container {height: 40rem; width: 100%; position: relative}",
".rects-canvas {height: 40rem; width: 100%; cursor: pointer; position: absolute; top: 0px}",
".labels-canvas {height: 40rem; width: 100%; position: absolute; top: 0px}",
".view-controls {display: inline-block; position: relative; min-height: 4rem; width: 100%;}",
".slider-view-controls {display: inline-block; position: relative; height: 3rem; width: 100%;}",
".slider {display: inline-block}",
".slider.spacing {float: right}",
".slider span, .slider mat-slider { display: block; padding-left: 0px; padding-top: 0px; font-weight: bold}",
".top-view-controls {height: 3rem; width: 100%; position: relative; display: inline-block; vertical-align: middle;}",
".zoom-container {position: relative; vertical-align: middle; float: right}",
".zoom-btn {position:relative; display: inline-flex; background: none; border: none; padding: 0}",
"mat-card-title {font-size: 16px !important; font-weight: medium; font-family: inherit;}",
":host /deep/ .mat-card-header-text {width: 100%; margin: 0;}",
"mat-radio-group {vertical-align: middle}",
"mat-radio-button {font-size: 16px; font-weight: normal}",
".mat-radio-button, .mat-radio-button-frame {transform: scale(0.8);}",
".rects-checkbox {font-size: 14px; font-weight: normal}",
"mat-icon {margin: 5px}",
"mat-checkbox {margin-left: 5px;}",
".mat-checkbox .mat-checkbox-frame { transform: scale(0.7);}",
".mat-checkbox-checked .mat-checkbox-background {transform: scale(0.7);}",
".mat-checkbox-indeterminate .mat-checkbox-background {transform: scale(0.7);}",
".slider-label {position: absolute; top: 0}",
".control-item {position: relative; display: inline-block;vertical-align: middle;align-items: center;}"
`
@import 'https://fonts.googleapis.com/icon?family=Material+Icons';
:host /deep/ .mat-card-header-text {
width: 100%;
margin: 0;
}
.rects-title {
font-size: 16px;
font-weight: medium;
font-family: inherit;
width: 100%;
margin-bottom: 12px;
}
.rects-content {
position: relative;
}
.canvas-container {
height: 40rem;
width: 100%;
position: relative;
}
.labels-canvas, .rects-canvas {
height: 40rem;
width: 100%;
position: absolute;
top: 0px;
}
.rects-canvas {
cursor: pointer;
}
.view-controls {
display: inline-block;
position: relative;
min-height: 4rem;
width: 100%;
}
.slider-view-controls, .top-view-controls {
display: inline-block;
position: relative;
height: 3rem;
width: 100%;
}
.top-view-controls {
vertical-align: middle;
}
.slider {
display: inline-block;
}
.slider-label {
position: absolute;
top: 0;
}
.slider.spacing {
float: right;
}
.slider span, .slider mat-slider {
display: block;
padding-left: 0px;
padding-top: 0px;
font-weight: bold;
}
.zoom-container {
position: relative;
vertical-align: middle;
float: right;
}
.zoom-btn {
position: relative;
display: inline-flex;
background: none;
border: none;
padding: 0;
}
.rects-checkbox {
font-size: 14px;
font-weight: normal;
margin-left: 5px;
}
mat-icon {
margin: 5px
}
.mat-checkbox .mat-checkbox-frame, .mat-checkbox-checked .mat-checkbox-background, .mat-checkbox-indeterminate .mat-checkbox-background {
transform: scale(0.7);
}
.control-item {
position: relative;
display: inline-block;
vertical-align: middle;
align-items: center;
}
`
]
})
export class RectsComponent implements OnInit, OnChanges, OnDestroy {
@Input() rects!: Rectangle[];
@Input() forceRefresh = false;
@Input() displayIds: Array<number> = [];
@Input() highlightedItems: Array<string> = [];
@@ -143,9 +212,8 @@ export class RectsComponent implements OnInit, OnChanges, OnDestroy {
if (changes["highlightedItems"]) {
this.canvasGraphics.updateHighlightedItems(this.highlightedItems);
}
if (this.rects.length > 0) {
if (this.rects.length > 0 || changes["forceRefresh"]?.currentValue) {
//change in rects so they must undergo transformation and scaling before canvas refreshed
this.canvasGraphics.clearLabelElements();
this.rects = this.rects.filter(rect => rect.isVisible || rect.isDisplay);
this.displayRects = this.rects.filter(rect => rect.isDisplay);
this.computeBounds();
@@ -217,7 +285,6 @@ export class RectsComponent implements OnInit, OnChanges, OnDestroy {
onChangeView(visible: boolean) {
this.canvasGraphics.updateVisibleView(visible);
this.canvasGraphics.clearLabelElements();
this.refreshCanvas();
}

View File

@@ -15,7 +15,8 @@
*/
import {
Component,
Input
Input,
SimpleChanges
} from "@angular/core";
import { UiData } from "./ui_data";
import { TRACE_INFO } from "app/trace_info";
@@ -32,6 +33,7 @@ import { PersistentStore } from "common/persistent_store";
[displayIds]="inputData?.displayIds ?? []"
[highlightedItems]="inputData?.highlightedItems ?? []"
[displayIds]="inputData?.displayIds ?? []"
[forceRefresh]="active"
></rects-view>
</mat-card>
<div fxLayout="row wrap" fxLayoutGap="10px grid" class="card-grid">
@@ -96,15 +98,16 @@ import { PersistentStore } from "common/persistent_store";
.rects-view {
font: inherit;
flex: none !important;
flex: none;
width: 350px;
height: 52.5rem;
margin: 0px;
border: 1px solid var(--default-border);
border-top: 1px solid var(--default-border);
border-right: 1px solid var(--default-border);
border-radius: 0;
}
.hierarchy-view, .properties-view {
.hierarchy-view {
font: inherit;
margin: 0px;
width: 50%;
@@ -112,7 +115,17 @@ import { PersistentStore } from "common/persistent_store";
border-radius: 0;
border-top: 1px solid var(--default-border);
border-right: 1px solid var(--default-border);
border-bottom: 1px solid var(--default-border);
border-left: 1px solid var(--default-border);
}
.properties-view {
font: inherit;
margin: 0px;
width: 50%;
height: 52.5rem;
border-radius: 0;
border-top: 1px solid var(--default-border);
border-left: 1px solid var(--default-border);
}
`,
]
@@ -120,6 +133,7 @@ import { PersistentStore } from "common/persistent_store";
export class ViewerSurfaceFlingerComponent {
@Input() inputData?: UiData;
@Input() store: PersistentStore = new PersistentStore();
@Input() active = false;
TRACE_INFO = TRACE_INFO;
TraceType = TraceType;
}