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 {Color} from 'app/colors'; 25import {PersistentStore} from 'common/persistent_store'; 26import {Analytics} from 'logging/analytics'; 27import {TraceType} from 'trace/trace_type'; 28import {RectShowState} from 'viewers/common/rect_show_state'; 29import {TableProperties} from 'viewers/common/table_properties'; 30import {TextFilter} from 'viewers/common/text_filter'; 31import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node'; 32import {UiTreeUtils} from 'viewers/common/ui_tree_utils'; 33import {UserOptions} from 'viewers/common/user_options'; 34import {ViewerEvents} from 'viewers/common/viewer_events'; 35import {nodeStyles} from 'viewers/components/styles/node.styles'; 36import {viewerCardInnerStyle} from './styles/viewer_card.styles'; 37 38@Component({ 39 selector: 'hierarchy-view', 40 template: ` 41 <div class="view-header"> 42 <div class="title-section"> 43 <collapsible-section-title 44 class="hierarchy-title" 45 title="HIERARCHY" 46 (collapseButtonClicked)="collapseButtonClicked.emit()"></collapsible-section-title> 47 <search-box 48 formFieldClass="applied-field" 49 [textFilter]="textFilter" 50 (filterChange)="onFilterChange($event)"></search-box> 51 </div> 52 <user-options 53 class="view-controls" 54 [userOptions]="userOptions" 55 [eventType]="ViewerEvents.HierarchyUserOptionsChange" 56 [traceType]="dependencies[0]" 57 [logCallback]="Analytics.Navigation.logHierarchySettingsChanged"> 58 </user-options> 59 <ng-container *ngIf="tree && tree.getWarnings().length > 0"> 60 <span 61 *ngFor="let warning of tree.getWarnings()" 62 class="mat-body-1 warning" 63 [matTooltip]="warning.getMessage()" 64 [matTooltipDisabled]="disableTooltip(warningEl)"> 65 <mat-icon class="warning-icon"> warning </mat-icon> 66 <span class="warning-message" #warningEl>{{warning.getMessage()}}</span> 67 </span> 68 </ng-container> 69 <properties-table 70 *ngIf="tableProperties" 71 class="properties-table" 72 [properties]="tableProperties"></properties-table> 73 <div *ngIf="pinnedItems.length > 0" class="pinned-items"> 74 <tree-node 75 *ngFor="let pinnedItem of pinnedItems" 76 class="node full-opacity" 77 [class]="pinnedItem.getDiff()" 78 [class.selected]="isHighlighted(pinnedItem, highlightedItem)" 79 [class.clickable]="true" 80 [node]="pinnedItem" 81 [isPinned]="true" 82 [isInPinnedSection]="true" 83 [isSelected]="isHighlighted(pinnedItem, highlightedItem)" 84 (pinNodeChange)="onPinnedItemChange($event)" 85 (click)="onPinnedNodeClick($event, pinnedItem)"></tree-node> 86 </div> 87 </div> 88 <mat-divider></mat-divider> 89 <span class="mat-body-1 placeholder-text" *ngIf="showPlaceholderText()"> {{ placeholderText }} </span> 90 <div class="hierarchy-content tree-wrapper"> 91 <tree-view 92 *ngIf="tree" 93 [isFlattened]="isFlattened()" 94 [node]="tree" 95 [useStoredExpandedState]="true" 96 [itemsClickable]="true" 97 [highlightedItem]="highlightedItem" 98 [pinnedItems]="pinnedItems" 99 [rectIdToShowState]="rectIdToShowState" 100 (highlightedChange)="onHighlightedItemChange($event)" 101 (pinnedItemChange)="onPinnedItemChange($event)" 102 (selectedTreeChange)="onSelectedTreeChange($event)"></tree-view> 103 104 <div class="subtrees"> 105 <tree-view 106 *ngFor="let subtree of subtrees; trackBy: trackById" 107 class="subtree" 108 [node]="subtree" 109 [isFlattened]="isFlattened()" 110 [useStoredExpandedState]="true" 111 [highlightedItem]="highlightedItem" 112 [pinnedItems]="pinnedItems" 113 [itemsClickable]="true" 114 [rectIdToShowState]="rectIdToShowState" 115 (highlightedChange)="onHighlightedItemChange($event)" 116 (pinnedItemChange)="onPinnedItemChange($event)" 117 (selectedTreeChange)="onSelectedTreeChange($event)"></tree-view> 118 </div> 119 </div> 120 `, 121 styles: [ 122 ` 123 .view-header { 124 display: flex; 125 flex-direction: column; 126 } 127 128 .properties-table { 129 padding-top: 5px; 130 } 131 132 .hierarchy-content { 133 height: 100%; 134 overflow: auto; 135 padding: 0px 12px; 136 } 137 138 .pinned-items { 139 width: 100%; 140 box-sizing: border-box; 141 border: 2px solid ${Color.PINNED_ITEM_BORDER}; 142 } 143 144 tree-view { 145 overflow: auto; 146 } 147 148 .warning { 149 display: flex; 150 align-items: center; 151 padding: 2px 12px; 152 background-color: var(--warning-background-color); 153 } 154 .warning-message { 155 padding-inline-start: 2px; 156 white-space: nowrap; 157 overflow: hidden; 158 text-overflow: ellipsis; 159 width: 100%; 160 } 161 .warning-icon { 162 font-size: 18px; 163 min-width: 18px; 164 height: 18px; 165 } 166 `, 167 nodeStyles, 168 viewerCardInnerStyle, 169 ], 170}) 171export class HierarchyComponent { 172 isHighlighted = UiTreeUtils.isHighlighted; 173 ViewerEvents = ViewerEvents; 174 Analytics = Analytics; 175 176 @Input() tree: UiHierarchyTreeNode | undefined; 177 @Input() subtrees: UiHierarchyTreeNode[] = []; 178 @Input() tableProperties: TableProperties | undefined; 179 @Input() dependencies: TraceType[] = []; 180 @Input() highlightedItem = ''; 181 @Input() pinnedItems: UiHierarchyTreeNode[] = []; 182 @Input() store: PersistentStore | undefined; 183 @Input() userOptions: UserOptions = {}; 184 @Input() rectIdToShowState?: Map<string, RectShowState>; 185 @Input() placeholderText = 'No entry found.'; 186 @Input() textFilter: TextFilter | undefined; 187 188 @Output() collapseButtonClicked = new EventEmitter(); 189 190 constructor(@Inject(ElementRef) private elementRef: ElementRef) {} 191 192 trackById(index: number, child: UiHierarchyTreeNode): string { 193 return child.id; 194 } 195 196 isFlattened(): boolean { 197 return this.userOptions['flat']?.enabled; 198 } 199 200 showPlaceholderText(): boolean { 201 return !this.tree && (this.subtrees?.length ?? 0) === 0; 202 } 203 204 onPinnedNodeClick(event: MouseEvent, pinnedItem: UiHierarchyTreeNode) { 205 event.preventDefault(); 206 if (window.getSelection()?.type === 'range') { 207 return; 208 } 209 this.onHighlightedItemChange(pinnedItem); 210 } 211 212 onFilterChange(detail: TextFilter) { 213 const event = new CustomEvent(ViewerEvents.HierarchyFilterChange, { 214 bubbles: true, 215 detail, 216 }); 217 this.elementRef.nativeElement.dispatchEvent(event); 218 } 219 220 onHighlightedItemChange(node: UiHierarchyTreeNode) { 221 const event = new CustomEvent(ViewerEvents.HighlightedNodeChange, { 222 bubbles: true, 223 detail: {node}, 224 }); 225 this.elementRef.nativeElement.dispatchEvent(event); 226 } 227 228 onPinnedItemChange(item: UiHierarchyTreeNode) { 229 const event = new CustomEvent(ViewerEvents.HierarchyPinnedChange, { 230 bubbles: true, 231 detail: {pinnedItem: item}, 232 }); 233 this.elementRef.nativeElement.dispatchEvent(event); 234 } 235 236 disableTooltip(el: HTMLElement) { 237 return el.scrollWidth === el.clientWidth; 238 } 239} 240