/* * 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, Inject, Input, Output, } from '@angular/core'; import {assertDefined} from 'common/assert_utils'; import {DiffType} from 'viewers/common/diff_type'; import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node'; import {UiPropertyTreeNode} from 'viewers/common/ui_property_tree_node'; import {nodeInnerItemStyles} from 'viewers/components/styles/node.styles'; @Component({ selector: 'tree-node', template: `
`, styles: [nodeInnerItemStyles], }) export class TreeNodeComponent { @Input() node?: UiHierarchyTreeNode | UiPropertyTreeNode; @Input() isLeaf?: boolean; @Input() flattened?: boolean; @Input() isExpanded?: boolean; @Input() isPinned = false; @Input() isInPinnedSection = false; @Input() isSelected = false; @Input() showStateIcon?: string; @Output() readonly toggleTreeChange = new EventEmitter(); @Output() readonly rectShowStateChange = new EventEmitter(); @Output() readonly expandTreeChange = new EventEmitter(); @Output() readonly pinNodeChange = new EventEmitter(); collapseDiffClass = ''; private el: HTMLElement; private treeWrapper: HTMLElement | undefined; private readonly gutterOffset = -13; constructor(@Inject(ElementRef) public elementRef: ElementRef) { this.el = elementRef.nativeElement; } ngAfterViewInit() { this.treeWrapper = this.getTreeWrapper(); } ngOnChanges() { if (!this.isInPinnedSection && this.isSelected) { this.expandTreeChange.emit(); } this.collapseDiffClass = this.updateCollapseDiffClass(); if (!this.isInPinnedSection && this.isSelected && !this.isNodeInView()) { this.el.scrollIntoView({block: 'center', inline: 'nearest'}); } } isNodeInView(): boolean { if (!this.treeWrapper) { return false; } const rect = this.el.getBoundingClientRect(); const parentRect = this.treeWrapper.getBoundingClientRect(); return rect.top >= parentRect.top && rect.bottom <= parentRect.bottom; } getTreeWrapper(): HTMLElement | undefined { let parent = this.el; while ( !parent.className.includes('tree-wrapper') && parent?.parentElement ) { parent = parent.parentElement; } if (!parent.className.includes('tree-wrapper')) { return undefined; } return parent; } isPropertyTreeNode(): boolean { return this.node instanceof UiPropertyTreeNode; } showPinNodeIcon(): boolean { return this.node instanceof UiHierarchyTreeNode && !this.node.isRoot(); } toggleTree(event: MouseEvent) { event.stopPropagation(); this.toggleTreeChange.emit(); } toggleRectShowState(event: MouseEvent) { event.stopPropagation(); this.rectShowStateChange.emit(); } showChevron(): boolean { return !this.isLeaf && !this.flattened && !this.isInPinnedSection; } expandTree(event: MouseEvent) { event.stopPropagation(); this.expandTreeChange.emit(); } pinNode(event: MouseEvent) { event.stopPropagation(); this.pinNodeChange.emit(assertDefined(this.node) as UiHierarchyTreeNode); } updateCollapseDiffClass(): string { if (this.isExpanded) { return ''; } const childrenDiffClasses = this.getAllDiffTypesOfChildren( assertDefined(this.node), ); childrenDiffClasses.delete(DiffType.NONE); if (childrenDiffClasses.size === 0) { return ''; } if (childrenDiffClasses.size === 1) { const diffType = assertDefined(childrenDiffClasses.values().next().value); return diffType; } return DiffType.MODIFIED; } getShowStateIconStyle() { const nodeMargin = this.flattened ? 0 : Number(this.el.style.marginLeft.split('px')[0]); return { marginLeft: nodeMargin + this.gutterOffset + 'px', }; } showCopyButton(): boolean { return ( this.node instanceof UiPropertyTreeNode && (this.node.isRoot() || !this.showChevron()) ); } getCopyText(): string { const node = assertDefined(this.node) as UiPropertyTreeNode; if (this.showChevron()) { return node.name; } return `${node.name}: ${node.formattedValue()}`; } private getAllDiffTypesOfChildren( node: UiHierarchyTreeNode | UiPropertyTreeNode, ): Set { const classes = new Set(); for (const child of node.getAllChildren()) { classes.add(child.getDiff()); for (const diffClass of this.getAllDiffTypesOfChildren(child)) { classes.add(diffClass); } } return classes; } }