/*
* 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
0"
class="enable-config-opt">
{{ checkboxConfig.name }}
0"
class="selection-config-opt">
{{ selectionConfig.name }}
All
0"
(click)="onNoneButtonClick(matSelect, selectionConfig)"> None
{{ 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);
}
}