/*
* 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: `
{{ showStateIcon }}
{{ isExpanded ? 'arrow_drop_down' : 'chevron_right' }}
push_pin
more_horiz
content_copy
`,
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;
}
}