/* * Copyright (C) 2024 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, ElementRef, EventEmitter, HostListener, Inject, Input, Output, } from '@angular/core'; import {Color} from 'app/colors'; import {InMemoryStorage} from 'common/store/in_memory_storage'; import {PersistentStore} from 'common/store/persistent_store'; import {Analytics} from 'logging/analytics'; import {UserWarning} from 'messaging/user_warning'; import {TraceType} from 'trace/trace_type'; import {RectShowState} from 'viewers/common/rect_show_state'; import {TableProperties} from 'viewers/common/table_properties'; import {TextFilter} from 'viewers/common/text_filter'; import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node'; import {UiTreeUtils} from 'viewers/common/ui_tree_utils'; import {UserOptions} from 'viewers/common/user_options'; import {ViewerEvents} from 'viewers/common/viewer_events'; import {nodeStyles} from 'viewers/components/styles/node.styles'; import {viewerCardInnerStyle} from './styles/viewer_card.styles'; @Component({ selector: 'hierarchy-view', template: `
warning {{warning.getMessage()}}
{{ placeholderText + ' Try changing timeline position.' }}
`, styles: [ ` .view-header { display: flex; flex-direction: column; } .properties-table { padding-top: 5px; } .hierarchy-content { height: 100%; overflow: auto; padding: 0px 12px; } .pinned-items { width: 100%; box-sizing: border-box; border: 2px solid ${Color.PINNED_ITEM_BORDER}; } tree-view { overflow: auto; } `, nodeStyles, viewerCardInnerStyle, ], }) export class HierarchyComponent { isHighlighted = UiTreeUtils.isHighlighted; ViewerEvents = ViewerEvents; Analytics = Analytics; treeStorage = new InMemoryStorage(); @Input() trees: UiHierarchyTreeNode[] = []; @Input() tableProperties: TableProperties | undefined; @Input() dependencies: TraceType[] = []; @Input() highlightedItem = ''; @Input() pinnedItems: UiHierarchyTreeNode[] = []; @Input() store: PersistentStore | undefined; @Input() userOptions: UserOptions = {}; @Input() rectIdToShowState?: Map; @Input() placeholderText = 'No entry found.'; @Input() textFilter: TextFilter | undefined; @Output() collapseButtonClicked = new EventEmitter(); constructor( @Inject(ElementRef) private elementRef: ElementRef, ) {} trackById(index: number, child: UiHierarchyTreeNode): string { return child.id; } isFlattened(): boolean { return this.userOptions['flat']?.enabled; } showPlaceholderText(): boolean { return this.trees.length === 0 && !!this.placeholderText; } getWarnings(): UserWarning[] { return this.trees.flatMap((tree) => tree.getWarnings()); } onPinnedNodeClick(event: MouseEvent, pinnedItem: UiHierarchyTreeNode) { event.preventDefault(); if (window.getSelection()?.type === 'range') { return; } this.onHighlightedItemChange(pinnedItem); } onFilterChange(detail: TextFilter) { const event = new CustomEvent(ViewerEvents.HierarchyFilterChange, { bubbles: true, detail, }); this.elementRef.nativeElement.dispatchEvent(event); } onHighlightedItemChange(node: UiHierarchyTreeNode) { const event = new CustomEvent(ViewerEvents.HighlightedNodeChange, { bubbles: true, detail: {node}, }); this.elementRef.nativeElement.dispatchEvent(event); } onPinnedItemChange(item: UiHierarchyTreeNode) { const event = new CustomEvent(ViewerEvents.HierarchyPinnedChange, { bubbles: true, detail: {pinnedItem: item}, }); this.elementRef.nativeElement.dispatchEvent(event); } disableTooltip(el: HTMLElement) { return el.scrollWidth === el.clientWidth; } getPinnedItemsPadding() { const addGutter = (this.rectIdToShowState?.size ?? 0) > 0; return `0px 10.5px 0px ${addGutter ? 22.5 : 10.5}px`; } @HostListener('document:keydown', ['$event']) async handleKeyboardEvent(event: KeyboardEvent) { const domRect = this.elementRef.nativeElement.getBoundingClientRect(); const componentVisible = domRect.height > 0 && domRect.width > 0; if ( componentVisible && (event.key === 'ArrowDown' || event.key === 'ArrowUp') ) { event.preventDefault(); const details = {bubbles: true, detail: this.treeStorage}; if (event.key === 'ArrowDown') { const arrowEvent = new CustomEvent( ViewerEvents.ArrowDownPress, details, ); this.elementRef.nativeElement.dispatchEvent(arrowEvent); } else { const arrowEvent = new CustomEvent(ViewerEvents.ArrowUpPress, details); this.elementRef.nativeElement.dispatchEvent(arrowEvent); } } } }