1/*
2 * Copyright (C) 2024 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  Component,
18  ElementRef,
19  EventEmitter,
20  Inject,
21  Input,
22  Output,
23} from '@angular/core';
24import {PersistentStore} from 'common/persistent_store';
25import {Analytics} from 'logging/analytics';
26import {TraceType} from 'trace/trace_type';
27import {CollapsibleSectionType} from 'viewers/common/collapsible_section_type';
28import {CuratedProperties} from 'viewers/common/curated_properties';
29import {UiPropertyTreeNode} from 'viewers/common/ui_property_tree_node';
30import {UserOptions} from 'viewers/common/user_options';
31import {ViewerEvents} from 'viewers/common/viewer_events';
32import {nodeStyles} from 'viewers/components/styles/node.styles';
33import {searchBoxStyle} from './styles/search_box.styles';
34import {viewerCardInnerStyle} from './styles/viewer_card.styles';
35
36@Component({
37  selector: 'properties-view',
38  template: `
39    <div class="view-header">
40      <div class="title-section">
41       <collapsible-section-title
42          class="properties-title"
43          [class.padded-title]="!hasUserOptions()"
44          [title]="title"
45          (collapseButtonClicked)="collapseButtonClicked.emit()"></collapsible-section-title>
46
47        <mat-form-field *ngIf="showFilter" class="search-box" (keydown.enter)="$event.target.blur()">
48          <mat-label>Search</mat-label>
49
50          <input matInput [(ngModel)]="filterString" (ngModelChange)="filterTree()" name="filter" />
51        </mat-form-field>
52      </div>
53
54      <user-options
55        *ngIf="hasUserOptions()"
56        class="view-controls"
57        [userOptions]="userOptions"
58        [eventType]="ViewerEvents.PropertiesUserOptionsChange"
59        [traceType]="traceType"
60        [logCallback]="Analytics.Navigation.logPropertiesSettingsChanged">
61      </user-options>
62    </div>
63
64    <mat-divider *ngIf="hasUserOptions()"></mat-divider>
65
66    <ng-container *ngIf="showViewCaptureFormat()">
67      <view-capture-property-groups
68        class="property-groups"
69        [properties]="curatedProperties"></view-capture-property-groups>
70
71      <mat-divider *ngIf="showPropertiesTree()"></mat-divider>
72    </ng-container>
73
74    <div *ngIf="showPropertiesTree()" class="properties-content">
75      <div class="tree-wrapper">
76        <tree-view
77          [node]="propertiesTree"
78          [useStoredExpandedState]="!!store"
79          [itemsClickable]="true"
80          [highlightedItem]="highlightedProperty"
81          (highlightedChange)="onHighlightedPropertyChange($event)"></tree-view>
82      </div>
83    </div>
84
85    <span class="mat-body-1 placeholder-text" *ngIf="!showPropertiesTree() && placeholderText"> {{ placeholderText }} </span>
86  `,
87  styles: [
88    `
89      .view-header {
90        display: flex;
91        flex-direction: column;
92      }
93
94      .padded-title {
95        padding-bottom: 8px;
96      }
97
98      .property-groups {
99        overflow-y: auto;
100      }
101
102      .properties-content {
103        flex: 1;
104        display: flex;
105        flex-direction: column;
106        overflow-y: auto;
107        padding: 0px 12px;
108      }
109
110      .placeholder-text {
111        padding: 8px 12px;
112      }
113    `,
114    nodeStyles,
115    searchBoxStyle,
116    viewerCardInnerStyle,
117  ],
118})
119export class PropertiesComponent {
120  Analytics = Analytics;
121  CollapsibleSectionType = CollapsibleSectionType;
122  filterString = '';
123  ViewerEvents = ViewerEvents;
124
125  @Input() title = 'PROPERTIES';
126  @Input() showFilter = true;
127  @Input() userOptions: UserOptions = {};
128  @Input() placeholderText = '';
129  @Input() propertiesTree: UiPropertyTreeNode | undefined;
130  @Input() highlightedProperty = '';
131  @Input() curatedProperties: CuratedProperties | undefined;
132  @Input() displayPropertyGroups = false;
133  @Input() isProtoDump = false;
134  @Input() traceType: TraceType | undefined;
135  @Input() store: PersistentStore | undefined;
136
137  @Output() collapseButtonClicked = new EventEmitter();
138
139  constructor(@Inject(ElementRef) private elementRef: ElementRef) {}
140
141  filterTree() {
142    const event = new CustomEvent(ViewerEvents.PropertiesFilterChange, {
143      bubbles: true,
144      detail: {filterString: this.filterString},
145    });
146    this.elementRef.nativeElement.dispatchEvent(event);
147  }
148
149  onHighlightedPropertyChange(newNode: UiPropertyTreeNode) {
150    const event = new CustomEvent(ViewerEvents.HighlightedPropertyChange, {
151      bubbles: true,
152      detail: {id: newNode.id},
153    });
154    this.elementRef.nativeElement.dispatchEvent(event);
155  }
156
157  hasUserOptions() {
158    return Object.keys(this.userOptions).length > 0;
159  }
160
161  showViewCaptureFormat(): boolean {
162    return (
163      this.traceType === TraceType.VIEW_CAPTURE &&
164      this.filterString === '' &&
165      // Todo: Highlight Inline in formatted ViewCapture Properties Component.
166      !this.userOptions['showDiff']?.enabled &&
167      this.curatedProperties !== undefined
168    );
169  }
170
171  showPropertiesTree(): boolean {
172    return !!this.propertiesTree && !this.showViewCaptureFormat();
173  }
174}
175