/* * Copyright (C) 2022 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 { ChangeDetectorRef, Component, EventEmitter, Inject, Input, NgZone, Output, } from '@angular/core'; import {MatSelect, MatSelectChange} from '@angular/material/select'; import {assertDefined} from 'common/assert_utils'; import {globalConfig} from 'common/global_config'; import {Store} from 'common/store/store'; import { CheckboxConfiguration, SelectionConfiguration, TraceConfigurationMap, updateConfigsFromStore, } from 'trace_collection/ui/ui_trace_configuration'; import {userOptionStyle} from 'viewers/components/styles/user_option.styles'; @Component({ selector: 'trace-config', template: `

{{title}}

{{ this.traceConfig[traceKey].name }}

{{ this.traceConfig[traceKey].name }} configuration

{{ checkboxConfig.name }}
{{ selectionConfig.name }} {{ option }} {{selectionConfig.desc}}
`, styles: [ ` .checkboxes { display: flex; flex-direction: column; flex-wrap: wrap; } .enable-config-opt, .selection-config-opt { display: flex; flex-direction: row; flex-wrap: wrap; gap: 10px; } .config-selection-with-desc { display: flex; flex-direction: column; } .wide-field { width: 100%; } .config-panel { position: absolute; left: 0px; top: 100px; } `, userOptionStyle, ], }) export class TraceConfigComponent { changeDetectionWorker: number | undefined; @Input() title: string | undefined; @Input() traceConfigStoreKey: string | undefined; @Input() traceConfig: TraceConfigurationMap | undefined; @Input() storage: Store | undefined; @Output() readonly traceConfigChange = new EventEmitter(); private tooltipsWithStablePosition = new Set(); constructor( @Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef, @Inject(NgZone) private ngZone: NgZone, ) {} ngOnInit() { this.traceConfig = updateConfigsFromStore( assertDefined( JSON.parse(JSON.stringify(assertDefined(this.traceConfig))), () => 'component initialized without config', ), assertDefined(this.storage), assertDefined(this.traceConfigStoreKey), ); if (globalConfig.MODE !== 'KARMA_TEST') { this.changeDetectionWorker = window.setInterval( () => this.changeDetectorRef.detectChanges(), 200, ); } this.traceConfigChange.emit(this.traceConfig); } ngOnDestroy() { window.clearInterval(this.changeDetectionWorker); } getTraceCheckboxContainerHeight(): string { const config = assertDefined(this.traceConfig); return Math.ceil(Object.keys(config).length / 3) * 24 + 'px'; } getSortedTraceKeys(): string[] { const config = assertDefined(this.traceConfig); return Object.keys(config).sort((a, b) => { return config[a].name < config[b].name ? -1 : 1; }); } getSortedConfigKeys(): string[] { const advancedConfigs: string[] = []; Object.keys(assertDefined(this.traceConfig)).forEach((traceKey: string) => { const c = assertDefined(this.traceConfig)[traceKey].config; if (c.checkboxConfigs.length > 0 || c.selectionConfigs.length > 0) { advancedConfigs.push(traceKey); } }); return advancedConfigs.sort(); } getSortedConfigs( configs: CheckboxConfiguration[] | SelectionConfiguration[], ): CheckboxConfiguration[] | SelectionConfiguration[] { return configs.sort((a, b) => { return a.name < b.name ? -1 : 1; }); } onSelectOptionHover(event: MouseEvent, option: string) { if (this.tooltipsWithStablePosition.has(option)) { return; } this.ngZone.run(() => { (event.target as HTMLElement).dispatchEvent(new Event('mouseleave')); this.tooltipsWithStablePosition.add(option); this.changeDetectorRef.detectChanges(); (event.target as HTMLElement).dispatchEvent(new Event('mouseenter')); }); } disableOptionTooltip(option: string, optionText: HTMLElement): boolean { const optionEl = assertDefined(optionText.parentElement); return ( !this.tooltipsWithStablePosition.has(option) || optionEl.offsetWidth >= optionText.offsetWidth ); } onSelectChange(event: MatSelectChange, config: SelectionConfiguration) { config.value = event.value; if (!event.source.multiple) { event.source.close(); } this.onTraceConfigChange(); } onNoneButtonClick(select: MatSelect, config: SelectionConfiguration) { if (config.value.length > 0) { select.value = ''; config.value = ''; this.onTraceConfigChange(); } } onAllButtonClick(select: MatSelect, config: SelectionConfiguration) { if (config.value.length !== config.options.length) { config.value = config.options; select.value = config.options; } else { config.value = []; select.value = []; } this.onTraceConfigChange(); } onOptionClick(select: MatSelect, option: string, configName: string) { if (select.value === option) { const selectElement = assertDefined( document.querySelector( `mat-select[label="${configName}"]`, ), ); selectElement.blur(); } } onTraceConfigChange() { this.traceConfigChange.emit(this.traceConfig); } isMultipleSelect(config: SelectionConfiguration): boolean { return Array.isArray(config.value); } }