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 {Component, ElementRef, Inject, Input} from '@angular/core'; 17import {PersistentStore} from 'common/persistent_store'; 18import {TraceType} from 'trace/trace_type'; 19import {TableProperties} from 'viewers/common/table_properties'; 20import {HierarchyTreeNode, UiTreeNode, UiTreeUtils} from 'viewers/common/ui_tree_utils'; 21import {UserOptions} from 'viewers/common/user_options'; 22import {ViewerEvents} from 'viewers/common/viewer_events'; 23import {nodeStyles} from 'viewers/components/styles/node.styles'; 24 25@Component({ 26 selector: 'hierarchy-view', 27 template: ` 28 <div class="view-header"> 29 <div class="title-filter"> 30 <h2 class="hierarchy-title mat-title">Hierarchy</h2> 31 <mat-form-field> 32 <mat-label>Filter...</mat-label> 33 <input matInput [(ngModel)]="filterString" (ngModelChange)="filterTree()" name="filter" /> 34 </mat-form-field> 35 </div> 36 <div class="view-controls"> 37 <mat-checkbox 38 *ngFor="let option of objectKeys(userOptions)" 39 color="primary" 40 [(ngModel)]="userOptions[option].enabled" 41 (ngModelChange)="updateTree()" 42 >{{ userOptions[option].name }}</mat-checkbox 43 > 44 </div> 45 <properties-table 46 *ngIf="tableProperties" 47 class="properties-table" 48 [properties]="tableProperties"></properties-table> 49 <div *ngIf="pinnedItems.length > 0" class="pinned-items"> 50 <tree-node 51 *ngFor="let pinnedItem of pinnedItems" 52 class="node" 53 [class]="diffClass(pinnedItem)" 54 [class.selected]="isHighlighted(pinnedItem, highlightedItems)" 55 [class.clickable]="true" 56 [item]="pinnedItem" 57 [isPinned]="true" 58 [isInPinnedSection]="true" 59 (pinNodeChange)="pinnedItemChange($event)" 60 (click)="onPinnedNodeClick($event, pinnedItem)"></tree-node> 61 </div> 62 </div> 63 <mat-divider></mat-divider> 64 <div class="hierarchy-content"> 65 <tree-view 66 *ngIf="tree" 67 [isFlattened]="isFlattened()" 68 [item]="tree" 69 [dependencies]="dependencies" 70 [store]="store" 71 [useGlobalCollapsedState]="true" 72 [itemsClickable]="true" 73 [highlightedItems]="highlightedItems" 74 [pinnedItems]="pinnedItems" 75 (highlightedItemChange)="highlightedItemChange($event)" 76 (pinnedItemChange)="pinnedItemChange($event)" 77 (selectedTreeChange)="selectedTreeChange($event)"></tree-view> 78 </div> 79 `, 80 styles: [ 81 ` 82 .view-header { 83 display: flex; 84 flex-direction: column; 85 margin-bottom: 12px; 86 } 87 88 .title-filter { 89 display: flex; 90 flex-direction: row; 91 flex-wrap: wrap; 92 justify-content: space-between; 93 } 94 95 .view-controls { 96 display: flex; 97 flex-direction: row; 98 flex-wrap: wrap; 99 column-gap: 10px; 100 } 101 102 .properties-table { 103 padding-top: 5px; 104 } 105 106 .hierarchy-content { 107 height: 100%; 108 overflow: auto; 109 } 110 111 .pinned-items { 112 width: 100%; 113 box-sizing: border-box; 114 border: 2px solid yellow; 115 } 116 117 tree-view { 118 overflow: auto; 119 } 120 `, 121 nodeStyles, 122 ], 123}) 124export class HierarchyComponent { 125 objectKeys = Object.keys; 126 filterString = ''; 127 diffClass = UiTreeUtils.diffClass; 128 isHighlighted = UiTreeUtils.isHighlighted; 129 130 @Input() tree!: HierarchyTreeNode | null; 131 @Input() tableProperties?: TableProperties | null; 132 @Input() dependencies: TraceType[] = []; 133 @Input() highlightedItems: string[] = []; 134 @Input() pinnedItems: HierarchyTreeNode[] = []; 135 @Input() store!: PersistentStore; 136 @Input() userOptions: UserOptions = {}; 137 138 constructor(@Inject(ElementRef) private elementRef: ElementRef) {} 139 140 isFlattened() { 141 return this.userOptions['flat']?.enabled; 142 } 143 144 onPinnedNodeClick(event: MouseEvent, pinnedItem: HierarchyTreeNode) { 145 event.preventDefault(); 146 if (window.getSelection()?.type === 'range') { 147 return; 148 } 149 if (pinnedItem.id) this.highlightedItemChange(`${pinnedItem.id}`); 150 this.selectedTreeChange(pinnedItem); 151 } 152 153 updateTree() { 154 const event: CustomEvent = new CustomEvent(ViewerEvents.HierarchyUserOptionsChange, { 155 bubbles: true, 156 detail: {userOptions: this.userOptions}, 157 }); 158 this.elementRef.nativeElement.dispatchEvent(event); 159 } 160 161 filterTree() { 162 const event: CustomEvent = new CustomEvent(ViewerEvents.HierarchyFilterChange, { 163 bubbles: true, 164 detail: {filterString: this.filterString}, 165 }); 166 this.elementRef.nativeElement.dispatchEvent(event); 167 } 168 169 highlightedItemChange(newId: string) { 170 const event: CustomEvent = new CustomEvent(ViewerEvents.HighlightedChange, { 171 bubbles: true, 172 detail: {id: newId}, 173 }); 174 this.elementRef.nativeElement.dispatchEvent(event); 175 } 176 177 selectedTreeChange(item: UiTreeNode) { 178 if (!(item instanceof HierarchyTreeNode)) { 179 return; 180 } 181 const event: CustomEvent = new CustomEvent(ViewerEvents.SelectedTreeChange, { 182 bubbles: true, 183 detail: {selectedItem: item}, 184 }); 185 this.elementRef.nativeElement.dispatchEvent(event); 186 } 187 188 pinnedItemChange(item: UiTreeNode) { 189 if (!(item instanceof HierarchyTreeNode)) { 190 return; 191 } 192 const event: CustomEvent = new CustomEvent(ViewerEvents.HierarchyPinnedChange, { 193 bubbles: true, 194 detail: {pinnedItem: item}, 195 }); 196 this.elementRef.nativeElement.dispatchEvent(event); 197 } 198} 199