All files / app/codeCharta/ui/metricChooser metricChooser.component.ts

100% Statements 47/47
100% Branches 18/18
100% Functions 12/12
100% Lines 45/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 1108x 8x   8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x                                               8x           31x 31x       31x   31x     31x   31x     31x   31x       31x 3x         7x 3x 7x 4x 3x   3x 3x 1x 2x 1x     3x 3x               15x 9x 9x   6x 6x         3x 3x 3x        
import { AfterViewInit, Component, ElementRef, HostBinding, Input, OnInit, QueryList, ViewChild, ViewChildren } from "@angular/core"
import { map, Observable } from "rxjs"
import { EdgeMetricData, NodeMetricData, CcState, PrimaryMetrics } from "../../codeCharta.model"
import { metricDataSelector } from "../../state/selectors/accumulatedData/metricData/metricData.selector"
import { attributeDescriptorsSelector } from "../../state/store/fileSettings/attributeDescriptors/attributeDescriptors.selector"
import { Store } from "@ngrx/store"
import { MatSelect, MatSelectTrigger } from "@angular/material/select"
import { MatOption } from "@angular/material/core"
import { NgIf, AsyncPipe } from "@angular/common"
import { MatFormField, MatPrefix } from "@angular/material/form-field"
import { MatInput } from "@angular/material/input"
import { FormsModule } from "@angular/forms"
import { MetricChooserValueComponent } from "./metricChooserValue/metricChooserValue.component"
import { AttributeDescriptorTooltipPipe } from "../../util/pipes/attributeDescriptorTooltip.pipe"
import { FilterMetricDataBySearchTermPipe } from "./filterMetricDataBySearchTerm.pipe"
 
type MetricChooserType = "node" | "edge"
 
@Component({
    selector: "cc-metric-chooser",
    templateUrl: "./metricChooser.component.html",
    styleUrls: ["./metricChooser.component.scss"],
    standalone: true,
    imports: [
        MatSelect,
        MatSelectTrigger,
        NgIf,
        MatFormField,
        MatPrefix,
        MatInput,
        FormsModule,
        MatOption,
        MetricChooserValueComponent,
        AsyncPipe,
        AttributeDescriptorTooltipPipe,
        FilterMetricDataBySearchTermPipe
    ]
})
export class MetricChooserComponent implements OnInit, AfterViewInit {
    @Input() metricFor?: keyof PrimaryMetrics
    @Input() icon?: string
    @Input() selectedMetricName: string
    @Input() searchPlaceholder: string
    @Input() handleMetricChanged: (newSelectedMetricName: string) => void
    @Input() type: MetricChooserType = "node"
    @Input() isDisabled = false
    @ViewChild("searchTermInput") searchTermInput: ElementRef<HTMLInputElement>
    @ViewChild("matSelect") matSelect: MatSelect
    @ViewChildren(MatOption) matOptions: QueryList<MatOption>
    searchTerm = ""
    metricData$: Observable<NodeMetricData[] | EdgeMetricData[]>
    attributeDescriptors$ = this.store.select(attributeDescriptorsSelector)
 
    @HostBinding("class.hide-metric-value")
    hideMetricSum = false
 
    constructor(private store: Store<CcState>) {}
 
    ngOnInit(): void {
        this.metricData$ = this.store
            .select(metricDataSelector)
            .pipe(map(metricData => (this.type === "node" ? metricData.nodeMetricData : metricData.edgeMetricData)))
    }
 
    ngAfterViewInit() {
        this.matOptions.changes.subscribe((options: QueryList<MatOption>) => {
            this.setFirstItemActiveOnSearch(options)
        })
    }
 
    setFirstItemActiveOnSearch(options: QueryList<MatOption>) {
        const selectedOptions = options.filter(option => option["_selected"])
        const matchingOptions = options
            .filter(option => option.value.toLowerCase().startsWith(this.searchTerm.toLowerCase()))
            .sort((a, b) => a.value.localeCompare(b.value))
        const searchTermExists = this.searchTerm.trim().length > 0
 
        setTimeout(() => {
            if (searchTermExists && selectedOptions.length === 0 && matchingOptions.length === 0) {
                this.matSelect._keyManager.setActiveItem(0)
            } else if (searchTermExists && selectedOptions.length === 0 && matchingOptions.length > 0) {
                this.matSelect._keyManager.setActiveItem(matchingOptions[0])
            }
 
            try {
                document.querySelector(".mdc-list-item--selected").scrollIntoView()
            } catch {
                // ignore
            }
        })
    }
 
    handleOpenedChanged(opened: boolean) {
        if (opened) {
            this.searchTermInput.nativeElement.focus()
            this.hideMetricSum = true
        } else {
            this.searchTerm = ""
            this.hideMetricSum = false
        }
    }
 
    handleKeyDown(event: KeyboardEvent) {
        const { key } = event
        if (key !== "ArrowDown" && key !== "ArrowUp" && key !== "Enter" && key !== "Escape") {
            event.stopPropagation()
        }
    }
}