1/*
2 * Copyright (C) 2023 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 */
16
17import {Component, ElementRef, Inject, Input} from '@angular/core';
18import {TraceType} from 'trace/trace_type';
19import {Transition} from 'trace/transition';
20import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
21import {CollapsibleSections} from 'viewers/common/collapsible_sections';
22import {CollapsibleSectionType} from 'viewers/common/collapsible_section_type';
23import {TimestampClickDetail, ViewerEvents} from 'viewers/common/viewer_events';
24import {timeButtonStyle} from 'viewers/components/styles/clickable_property.styles';
25import {selectedElementStyle} from 'viewers/components/styles/selected_element.styles';
26import {viewerCardStyle} from 'viewers/components/styles/viewer_card.styles';
27import {UiData} from './ui_data';
28
29@Component({
30  selector: 'viewer-transitions',
31  template: `
32    <div class="card-grid container">
33      <collapsed-sections
34        [class.empty]="sections.areAllSectionsExpanded()"
35        [sections]="sections"
36        (sectionChange)="sections.onCollapseStateChange($event, false)">
37      </collapsed-sections>
38      <div class="log-view entries">
39        <div class="table-header table-row">
40          <div class="id mat-body-2">Id</div>
41          <div class="type mat-body-2">Type</div>
42          <div class="send-time mat-body-2">Send Time</div>
43          <div class="dispatch-time mat-body-2">Dispatch Time</div>
44          <div class="duration mat-body-2">Duration</div>
45          <div class="status mat-body-2">Status</div>
46        </div>
47        <cdk-virtual-scroll-viewport itemSize="53" class="scroll">
48          <div
49            *cdkVirtualFor="let transition of uiData.entries; let i = index"
50            class="entry table-row"
51            [class.selected]="isSelectedTransition(transition)"
52            (click)="onTransitionClicked(transition)">
53            <div class="id">
54              <span class="mat-body-1">{{ transition.id }}</span>
55            </div>
56            <div class="type">
57              <span class="mat-body-1">{{ transition.type }}</span>
58            </div>
59            <div class="send-time time">
60              <button
61                mat-button
62                color="primary"
63                *ngIf="transition.sendTime"
64                (click)="onSendTimeClicked(transition)">
65                {{ transition.sendTime.formattedValue() }}
66              </button>
67              <span *ngIf="!transition.sendTime" class="mat-body-1"> n/a </span>
68            </div>
69            <div class="dispatch-time time">
70              <button
71                mat-button
72                color="primary"
73                *ngIf="transition.dispatchTime"
74                (click)="onDispatchTimeClicked(transition)">
75                {{ transition.dispatchTime.formattedValue() }}
76              </button>
77              <span *ngIf="!transition.dispatchTime" class="mat-body-1"> n/a </span>
78            </div>
79            <div class="duration">
80              <span *ngIf="transition.duration" class="mat-body-1">{{ transition.duration }}</span>
81              <span *ngIf="!transition.duration" class="mat-body-1"> n/a </span>
82            </div>
83            <div class="status">
84              <div *ngIf="transition.merged">
85                <span class="mat-body-1">MERGED</span>
86                <mat-icon aria-hidden="false" fontIcon="merge" matTooltip="merged" icon-gray>
87                </mat-icon>
88              </div>
89
90              <div *ngIf="transition.aborted && !transition.merged">
91                <span class="mat-body-1">ABORTED</span>
92                <mat-icon
93                  aria-hidden="false"
94                  fontIcon="close"
95                  matTooltip="aborted"
96                  style="color: red"
97                  icon-red></mat-icon>
98              </div>
99
100              <div *ngIf="transition.played && !transition.aborted && !transition.merged">
101                <span class="mat-body-1">PLAYED</span>
102                <mat-icon
103                  aria-hidden="false"
104                  fontIcon="check"
105                  matTooltip="played"
106                  style="color: green"
107                  *ngIf="transition.played && !transition.aborted && !transition.merged"></mat-icon>
108              </div>
109            </div>
110          </div>
111        </cdk-virtual-scroll-viewport>
112      </div>
113
114      <properties-view
115        class="properties-view"
116        [title]="propertiesTitle"
117        [showFilter]="false"
118        [propertiesTree]="uiData.selectedTransition"
119        [traceType]="${TraceType.TRANSITION}"
120        [isProtoDump]="false"
121        placeholderText="No selected transition."
122        (collapseButtonClicked)="sections.onCollapseStateChange(CollapsibleSectionType.PROPERTIES, true)"
123        [class.collapsed]="sections.isSectionCollapsed(CollapsibleSectionType.PROPERTIES)"></properties-view>
124    </div>
125  `,
126  styles: [
127    `
128      .container {
129        display: flex;
130        flex-grow: 1;
131        flex-direction: row;
132      }
133
134      .entries {
135        flex: 3;
136        display: flex;
137        flex-direction: column;
138        padding: 16px;
139      }
140
141      .entries .scroll {
142        height: 100%;
143      }
144
145      .entries .table-header {
146        flex: 1;
147      }
148
149      .table-row {
150        display: flex;
151        flex-direction: row;
152        cursor: pointer;
153        border-bottom: solid 1px rgba(0, 0, 0, 0.12);
154      }
155
156      .table-header.table-row {
157        font-weight: bold;
158        border-bottom: solid 1px rgba(0, 0, 0, 0.5);
159      }
160
161      .table-row > div {
162        padding: 16px;
163      }
164
165      .table-row .id {
166        flex: 1;
167      }
168
169      .table-row .type {
170        flex: 2;
171      }
172
173      .table-row .dispatch-time {
174        flex: 4;
175      }
176
177      .table-row .send-time {
178        flex: 4;
179      }
180
181      .table-row .duration {
182        flex: 3;
183      }
184
185      .table-row .status {
186        flex: 2;
187      }
188
189      .status > div {
190        display: flex;
191        justify-content: center;
192        align-items: center;
193        gap: 5px;
194      }
195
196      .transition-timeline .row svg rect {
197        cursor: pointer;
198      }
199
200      .label {
201        width: 300px;
202        padding: 1rem;
203      }
204
205      .lines {
206        flex-grow: 1;
207        padding: 0.5rem;
208      }
209      .properties-view {
210        flex: 1;
211      }
212    `,
213    selectedElementStyle,
214    timeButtonStyle,
215    viewerCardStyle,
216  ],
217})
218export class ViewerTransitionsComponent {
219  propertiesTitle = 'SELECTED TRANSITION';
220  CollapsibleSectionType = CollapsibleSectionType;
221  sections = new CollapsibleSections([
222    {
223      type: CollapsibleSectionType.PROPERTIES,
224      label: this.propertiesTitle,
225      isCollapsed: false,
226    },
227  ]);
228
229  constructor(@Inject(ElementRef) private elementRef: ElementRef) {}
230
231  @Input()
232  set inputData(data: UiData) {
233    this.uiData = data;
234  }
235
236  onTransitionClicked(transition: Transition): void {
237    this.emitEvent(ViewerEvents.TransitionSelected, transition.propertiesTree);
238  }
239
240  isSelectedTransition(transition: Transition): boolean {
241    return (
242      transition.id ===
243        this.uiData.selectedTransition
244          ?.getChildByName('wmData')
245          ?.getChildByName('id')
246          ?.getValue() ||
247      transition.id ===
248        this.uiData.selectedTransition
249          ?.getChildByName('shellData')
250          ?.getChildByName('id')
251          ?.getValue()
252    );
253  }
254
255  onDispatchTimeClicked(transition: Transition) {
256    this.emitEvent(
257      ViewerEvents.TimestampClick,
258      new TimestampClickDetail(
259        transition.dispatchTime?.getValue(),
260        transition.traceIndex,
261      ),
262    );
263  }
264
265  onSendTimeClicked(transition: Transition) {
266    this.emitEvent(
267      ViewerEvents.TimestampClick,
268      new TimestampClickDetail(transition.sendTime?.getValue(), undefined),
269    );
270  }
271
272  emitEvent(event: string, data: PropertyTreeNode | TimestampClickDetail) {
273    const customEvent = new CustomEvent(event, {
274      bubbles: true,
275      detail: data,
276    });
277    this.elementRef.nativeElement.dispatchEvent(customEvent);
278  }
279
280  uiData: UiData = UiData.EMPTY;
281}
282