@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1 lines • 96 kB
Source Map (JSON)
{"version":3,"file":"c8y-ngx-components-alarm-event-selector.mjs","sources":["../../alarm-event-selector/alarm-event-attributes-form/alarm-event-attributes-form.service.ts","../../alarm-event-selector/alarm-event-attributes-form/alarm-event-attributes-form.component.ts","../../alarm-event-selector/alarm-event-attributes-form/alarm-event-attributes-form.component.html","../../alarm-event-selector/alarm-event-selector.model.ts","../../alarm-event-selector/alarm-event-selector.service.ts","../../alarm-event-selector/alarm-event-selector-list-item/alarm-event-selector-list-item.component.ts","../../alarm-event-selector/alarm-event-selector-list-item/alarm-event-selector-list-item.component.html","../../alarm-event-selector/custom-alarm-event-form/custom-alarm-event-form.component.ts","../../alarm-event-selector/custom-alarm-event-form/custom-alarm-event-form.component.html","../../alarm-event-selector/pipes/includes-alarm.pipe.ts","../../alarm-event-selector/alarm-event-selector.component.ts","../../alarm-event-selector/alarm-event-selector.component.html","../../alarm-event-selector/alarm-event-selector-modal/alarm-event-selector-modal.component.ts","../../alarm-event-selector/alarm-event-selector-modal/alarm-event-selector-modal.component.html","../../alarm-event-selector/alarm-event-selector-modal/alarm-event-selector-modal.service.ts","../../alarm-event-selector/alarm-event-selection-list/alarm-event-selection-list.component.ts","../../alarm-event-selector/alarm-event-selection-list/alarm-event-selection-list.component.html","../../alarm-event-selector/alarm-event-selector.module.ts","../../alarm-event-selector/c8y-ngx-components-alarm-event-selector.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { AbstractControl, ValidationErrors } from '@angular/forms';\nimport { AlarmOrEvent } from '../alarm-event-selector.model';\n\n@Injectable({ providedIn: 'root' })\nexport class AlarmEventAttributesFormService {\n duplicateTypeValidator(selectedItems: AlarmOrEvent[]) {\n return (control: AbstractControl): ValidationErrors | null => {\n if (!control.value || !selectedItems?.length) {\n return null;\n }\n const isDuplicate = selectedItems.some(\n item => item.filters?.type?.toLowerCase() === control.value.toLowerCase()\n );\n return isDuplicate ? { duplicateType: true } : null;\n };\n }\n}\n","import { Component, forwardRef, Input, OnInit } from '@angular/core';\nimport {\n AbstractControl,\n ControlValueAccessor,\n FormBuilder,\n FormControl,\n FormGroup,\n NG_VALIDATORS,\n NG_VALUE_ACCESSOR,\n ValidationErrors,\n Validator,\n Validators\n} from '@angular/forms';\nimport { take } from 'rxjs/operators';\nimport { AlarmEventAttributesFormService } from './alarm-event-attributes-form.service';\nimport {\n AlarmOrEvent,\n OmitSelectorProperties,\n SelectedDatapoint,\n TimelineType\n} from '../alarm-event-selector.model';\nimport { IIdentified } from '@c8y/client';\nimport { KPIDetails } from '@c8y/ngx-components/datapoint-selector';\nimport { AlarmEventAttributeForm } from './alarm-event-attributes-form.model';\n\n@Component({\n selector: 'c8y-alarm-event-attributes-form',\n templateUrl: './alarm-event-attributes-form.component.html',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => AlarmEventAttributesFormComponent),\n multi: true\n },\n {\n provide: NG_VALIDATORS,\n useExisting: forwardRef(() => AlarmEventAttributesFormComponent),\n multi: true\n }\n ],\n standalone: false\n})\nexport class AlarmEventAttributesFormComponent implements ControlValueAccessor, Validator, OnInit {\n @Input() timelineType: TimelineType;\n @Input() omitProperties: OmitSelectorProperties = {};\n @Input() selectedItems: AlarmOrEvent[] = [];\n @Input() target: IIdentified;\n @Input() datapoints: KPIDetails[] = [];\n formGroup: FormGroup<AlarmEventAttributeForm>;\n\n constructor(\n private formBuilder: FormBuilder,\n private alarmEventAttributesFormService: AlarmEventAttributesFormService\n ) {}\n\n ngOnInit() {\n this.formGroup = this.formBuilder.group({\n filters: this.formBuilder.group({\n type: new FormControl<string>('', [\n Validators.required,\n this.alarmEventAttributesFormService.duplicateTypeValidator(this.selectedItems)\n ])\n }),\n timelineType: new FormControl<TimelineType>('' as TimelineType),\n selectedDatapoint: new FormControl<null | SelectedDatapoint>(null)\n });\n\n if (!this.omitProperties.label) {\n this.formGroup.addControl('label', new FormControl('', Validators.required));\n }\n if (this.datapoints?.length > 0) {\n this.filterKpis();\n }\n }\n\n validate(_control: AbstractControl): ValidationErrors {\n return this.formGroup?.valid ? null : { formInvalid: {} };\n }\n\n writeValue(obj: any): void {\n this.formGroup.patchValue(obj);\n }\n\n registerOnChange(fn: any): void {\n this.formGroup.valueChanges.subscribe(fn);\n }\n\n registerOnTouched(fn: any): void {\n this.formGroup.valueChanges.pipe(take(1)).subscribe(fn);\n }\n\n setDisabledState(isDisabled: boolean): void {\n isDisabled ? this.formGroup.disable() : this.formGroup.enable();\n }\n\n changeDatapointSelection(event: Event) {\n const selectElement = event.target as HTMLSelectElement;\n const selectedIndex = selectElement.selectedIndex;\n const selectedDatapoint = this.datapoints[selectedIndex];\n const trimmedDatapoint = {\n target: selectedDatapoint.__target.id as string,\n fragment: selectedDatapoint.fragment,\n series: selectedDatapoint.series\n };\n\n this.formGroup.patchValue({ selectedDatapoint: trimmedDatapoint });\n }\n\n trackByFn(_index: number, item: KPIDetails) {\n return `${item.fragment}-${item.series}-${item.__target?.id}`;\n }\n\n private filterKpis() {\n if (this.target && this.target.id) {\n this.datapoints = this.datapoints.filter(dp => dp.__target?.id === this.target.id);\n }\n }\n}\n","<div [formGroup]=\"formGroup\">\n <c8y-form-group\n class=\"form-group-sm\"\n *ngIf=\"formGroup.controls.label\"\n >\n <label class=\"m-0\">{{ 'Label' | translate }}</label>\n <input\n class=\"form-control flex-grow\"\n name=\"label\"\n formControlName=\"label\"\n [placeholder]=\"\n 'e.g. {{ example }}'\n | translate\n : {\n example: timelineType === 'ALARM' ? 'Alarm unavailable' : 'Location update'\n }\n \"\n />\n </c8y-form-group>\n\n <div formGroupName=\"filters\">\n <c8y-form-group class=\"form-group-sm\">\n <label class=\"m-0\">{{ 'Type' | translate }}</label>\n <input\n class=\"form-control flex-grow\"\n name=\"type\"\n formControlName=\"type\"\n [placeholder]=\"\n 'e.g. {{ example }}'\n | translate\n : {\n example:\n timelineType === 'ALARM' ? 'c8y_UnavailabilityAlarm' : 'c8y_LocationUpdate'\n }\n \"\n />\n <c8y-messages>\n <c8y-message\n name=\"duplicateType\"\n [text]=\"'Type already used.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <c8y-form-group\n class=\"form-group-sm\"\n *ngIf=\"datapoints?.length > 0\"\n >\n <label>{{ 'Select data point' | translate }}</label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control input-sm\"\n (change)=\"changeDatapointSelection($event)\"\n >\n <option\n title=\"{{ 'Widget configuration' | translate }}\"\n *ngFor=\"let datapoint of datapoints; trackBy: trackByFn\"\n [ngValue]=\"{\n target: datapoint.__target.id,\n fragment: datapoint.fragment,\n series: datapoint.series\n }\"\n [selected]=\"\n datapoint.__target.id === formGroup.value.selectedDatapoint?.target &&\n datapoint.fragment === formGroup.value.selectedDatapoint?.fragment &&\n datapoint.series === formGroup.value.selectedDatapoint?.series\n \"\n >\n {{ datapoint?.label }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n</div>\n","import { IIdentified } from '@c8y/client';\nimport { gettext } from '@c8y/ngx-components';\n\nexport type TimelineType = 'ALARM' | 'EVENT';\n\ntype AlarmOrEventBase = {\n timelineType: TimelineType;\n color: string;\n __active?: boolean;\n label: string;\n filters: {\n type: string;\n };\n __target: IIdentified;\n};\n\nexport type AlarmDetails = AlarmOrEventBase & {\n timelineType: 'ALARM';\n filters: {\n type: string;\n };\n selectedDatapoint?: SelectedDatapoint;\n};\n\nexport type EventDetails = AlarmOrEventBase & {\n timelineType: 'EVENT';\n filters: {\n type: string;\n };\n selectedDatapoint?: SelectedDatapoint;\n};\n\nexport type SelectedDatapoint = {\n target?: string;\n series?: string;\n fragment?: string;\n};\n\nexport type AlarmOrEvent = AlarmDetails | EventDetails;\n\nexport type TimelineTypeTexts = {\n listTitle: string;\n emptyStateIcon: string;\n emptyStateTitle: string;\n emptyStateSubtitle: string;\n addButtonLabel: string;\n addCustomItemButtonLabel: string;\n selectorTitle: string;\n availableItemsTitle: string;\n assetWithNoItemsEmptyStateSubtitle: string;\n largeNumberOfItemsInfo: string;\n selectedItemsTitle: string;\n noSelectedItemsTitle: string;\n recentItemsWarningTitle: string;\n recentItemsWarningText: string;\n addCustomText: string;\n};\n\nexport const EVENT_TEXTS: TimelineTypeTexts = {\n listTitle: gettext('Events'),\n emptyStateIcon: 'online1',\n emptyStateTitle: gettext('No events to display.'),\n emptyStateSubtitle: gettext('Add your first event.'),\n addButtonLabel: gettext('Add event'),\n addCustomItemButtonLabel: gettext('Add custom event'),\n selectorTitle: gettext('Events selector'),\n availableItemsTitle: gettext('Available events'),\n assetWithNoItemsEmptyStateSubtitle: gettext('Select an asset with events from the list.'),\n largeNumberOfItemsInfo: gettext(\n 'Due to the large number, only a subset of events are displayed. Use search to narrow down the number of results.'\n ),\n selectedItemsTitle: gettext('Selected events'),\n noSelectedItemsTitle: gettext('No events selected.'),\n recentItemsWarningTitle: gettext('The list below may not be complete.'),\n recentItemsWarningText: gettext(\n 'Recent events are displayed below. Past events might not be shown.'\n ),\n addCustomText: gettext('Optionally you can add a custom event.')\n};\n\nexport const ALARM_TEXTS: TimelineTypeTexts = {\n listTitle: gettext('Alarms'),\n emptyStateIcon: 'c8y-alarm',\n emptyStateTitle: gettext('No alarms to display.'),\n emptyStateSubtitle: gettext('Add your first alarm.'),\n addButtonLabel: gettext('Add alarm'),\n addCustomItemButtonLabel: gettext('Add custom alarm'),\n selectorTitle: gettext('Alarms selector'),\n availableItemsTitle: gettext('Available alarms'),\n assetWithNoItemsEmptyStateSubtitle: gettext('Select an asset with alarms from the list.'),\n largeNumberOfItemsInfo: gettext(\n 'Due to the large number, only a subset of alarms are displayed. Use search to narrow down the number of results.'\n ),\n selectedItemsTitle: gettext('Selected alarms'),\n noSelectedItemsTitle: gettext('No alarms selected.'),\n recentItemsWarningTitle: gettext('The list below may not be complete.'),\n recentItemsWarningText: gettext(\n 'Recent alarms are displayed below. Past alarms might not be shown.'\n ),\n addCustomText: gettext('Optionally you can add a custom alarm.')\n};\n\n/**\n * The configuration for the alarms-events selector modal if some properties should be omitted.\n */\nexport type OmitSelectorProperties = {\n color?: boolean;\n label?: boolean;\n};\n","import { Injectable } from '@angular/core';\nimport { AlarmService, EventService, IAlarm, IEvent, IIdentified } from '@c8y/client';\nimport { ColorService } from '@c8y/ngx-components';\nimport { uniqBy } from 'lodash-es';\nimport {\n ALARM_TEXTS,\n AlarmDetails,\n AlarmOrEvent,\n EVENT_TEXTS,\n EventDetails,\n TimelineType,\n TimelineTypeTexts\n} from './alarm-event-selector.model';\n\n@Injectable({ providedIn: 'root' })\nexport class AlarmEventSelectorService {\n private timelineTypeTextsMap: Map<TimelineType, TimelineTypeTexts> = new Map([\n ['ALARM', ALARM_TEXTS],\n ['EVENT', EVENT_TEXTS]\n ]);\n\n constructor(\n private alarmService: AlarmService,\n private eventsService: EventService,\n private color: ColorService\n ) {}\n\n /**\n * This method returns the texts for the timeline type.\n * @param timelineType The timeline type.\n * @returns The texts for the timeline type.\n */\n timelineTypeTexts(timelineType: TimelineType): TimelineTypeTexts {\n return this.timelineTypeTextsMap.get(timelineType)!;\n }\n\n /**\n * This method returns all alarms or events of the platform based on the timeline type\n * @param timelineType The timeline type.\n * @returns The alarms or events of the asset.\n */\n async getItems(timelineType: TimelineType): Promise<AlarmOrEvent[]> {\n const filters = {\n pageSize: 1000\n };\n\n return timelineType === 'ALARM'\n ? await this.getAlarmsOfAsset(filters)\n : await this.getEventsOfAsset(filters);\n }\n\n /**\n * This method returns the items of the asset based on the timeline type.\n * @param parentReference The parent reference.\n * @param timelineType The timeline type.\n * @returns The alarms or events of the asset.\n */\n async getAlarmsOrEvents(\n parentReference: IIdentified,\n timelineType: TimelineType\n ): Promise<AlarmOrEvent[]> {\n const filters = { source: parentReference.id, pageSize: 1000 };\n\n return timelineType === 'ALARM'\n ? await this.getAlarmsOfAsset(filters)\n : await this.getEventsOfAsset(filters);\n }\n\n async getUniqueAlarmsOnly(data: IAlarm[]): Promise<AlarmDetails[]> {\n return Promise.all(\n uniqBy(data, 'type').map(async (alarm: IAlarm) => this.createItem('ALARM', alarm))\n );\n }\n\n async getUniqueEventsOnly(data: IEvent[]): Promise<EventDetails[]> {\n return Promise.all(\n uniqBy(data, 'type').map(async (event: IEvent) => this.createItem('EVENT', event))\n );\n }\n\n async createItem(timelineType: 'ALARM' & TimelineType, item: IAlarm): Promise<AlarmDetails>;\n async createItem(timelineType: 'EVENT' & TimelineType, item: IEvent): Promise<EventDetails>;\n async createItem(timelineType: TimelineType, item: IAlarm | IEvent): Promise<any> {\n const color = await this.color.generateColor(item.type);\n if (item.source && !item.source.name) {\n item.source.name = `Device ${item.source.id}`;\n }\n return {\n timelineType,\n color,\n label: item.type,\n filters: {\n type: item.type\n },\n __target: item.source\n };\n }\n\n private async getAlarmsOfAsset(filters: {\n pageSize: number;\n dateTo?: string;\n dateFrom?: string;\n source?: string | number;\n }): Promise<AlarmDetails[]> {\n const res = await this.alarmService.list({\n ...filters,\n withSourceAssets: !!filters.source,\n withSourceDevices: !!filters.source,\n pageSize: 1000\n });\n const alarms = await this.getUniqueAlarmsOnly(res.data);\n return alarms;\n }\n\n private async getEventsOfAsset(filters: {\n pageSize: number;\n dateTo?: string;\n dateFrom?: string;\n source?: string | number;\n }): Promise<EventDetails[]> {\n const res = await this.eventsService.list(filters);\n const events = this.getUniqueEventsOnly(res.data);\n return events;\n }\n}\n","import {\n Component,\n EventEmitter,\n forwardRef,\n Input,\n OnDestroy,\n OnInit,\n Output\n} from '@angular/core';\nimport {\n AbstractControl,\n ControlValueAccessor,\n FormBuilder,\n FormGroup,\n NG_VALIDATORS,\n NG_VALUE_ACCESSOR,\n ValidationErrors,\n Validator\n} from '@angular/forms';\nimport { AlarmOrEvent, OmitSelectorProperties, TimelineType } from '../alarm-event-selector.model';\nimport { map, take, takeUntil } from 'rxjs/operators';\nimport { Observable, Subject } from 'rxjs';\nimport { gettext } from '@c8y/ngx-components';\nimport { KPIDetails } from '@c8y/ngx-components/datapoint-selector';\n@Component({\n selector: 'c8y-alarm-event-selector-list-item',\n templateUrl: './alarm-event-selector-list-item.component.html',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => AlarmEventSelectorListItemComponent),\n multi: true\n },\n {\n provide: NG_VALIDATORS,\n useExisting: forwardRef(() => AlarmEventSelectorListItemComponent),\n multi: true\n }\n ],\n standalone: false\n})\nexport class AlarmEventSelectorListItemComponent\n implements OnInit, ControlValueAccessor, Validator, OnDestroy\n{\n @Input() timelineType: TimelineType;\n @Input() datapoints: KPIDetails[] | undefined;\n @Input() highlightText: string;\n @Input() showAddRemoveButton = true;\n @Input() isSelected = false;\n @Input() optionToRemove = false;\n @Input() showActiveToggle = false;\n @Input() allowItemEdit = false;\n @Input() hideSource = false;\n @Input() displayAsSwitch = false;\n @Input() omitProperties: OmitSelectorProperties = {};\n\n colorPickerTitle: string;\n @Output() added = new EventEmitter<AlarmOrEvent>();\n @Output() removed = new EventEmitter<AlarmOrEvent>();\n\n formGroup: FormGroup;\n valid$: Observable<boolean>;\n private destroy$ = new Subject<void>();\n\n constructor(private formBuilder: FormBuilder) {\n this.formGroup = this.formBuilder.group({\n details: [],\n color: [],\n __active: [],\n __target: []\n });\n this.valid$ = this.formGroup.statusChanges.pipe(\n takeUntil(this.destroy$),\n map(val => val === 'VALID')\n );\n }\n\n ngOnInit() {\n this.colorPickerTitle = this.allowItemEdit ? gettext('Change color') : '';\n }\n\n ngOnDestroy() {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n toggleActive() {\n this.formGroup.controls.__active.setValue(!this.formGroup.value.__active);\n }\n\n validate(_control: AbstractControl): ValidationErrors {\n return this.formGroup?.valid ? null : { formInvalid: {} };\n }\n\n writeValue(obj: any): void {\n this.formGroup.patchValue({ ...obj, details: obj });\n }\n\n registerOnChange(fn: any): void {\n this.formGroup.valueChanges\n .pipe(\n map(tmp => this.transformFormValue(tmp)),\n takeUntil(this.destroy$)\n )\n .subscribe(fn);\n }\n\n registerOnTouched(fn: any): void {\n this.formGroup.valueChanges.pipe(take(1)).subscribe(fn);\n }\n\n setDisabledState(isDisabled: boolean): void {\n isDisabled ? this.formGroup.disable() : this.formGroup.enable();\n }\n\n addOrRemoveItem() {\n const value = this.transformFormValue(this.formGroup.value);\n if (this.isSelected) {\n this.removed.emit(value);\n } else {\n this.added.emit(value);\n }\n }\n\n remove() {\n this.removed.emit(this.transformFormValue(this.formGroup.value));\n }\n\n private transformFormValue(formValue: any) {\n const obj = Object.assign({}, formValue.details || {}, formValue);\n delete obj.details;\n return obj;\n }\n}\n","<c8y-li\n class=\"c8y-list__item__collapse--container-small\"\n [formGroup]=\"formGroup\"\n #li\n>\n <c8y-li-drag-handle><ng-content select=\"c8y-li-drag-handle\"></ng-content></c8y-li-drag-handle>\n <c8y-li-icon\n class=\"a-s-center p-r-4\"\n *ngIf=\"showAddRemoveButton\"\n >\n <button\n class=\"btn btn-clean text-primary\"\n [title]=\"'Remove' | translate\"\n *ngIf=\"isSelected\"\n (click)=\"addOrRemoveItem()\"\n >\n <i\n class=\"text-danger\"\n c8yIcon=\"minus-circle\"\n ></i>\n </button>\n <button\n class=\"btn btn-clean text-primary\"\n [title]=\"'Select' | translate\"\n *ngIf=\"!isSelected\"\n (click)=\"addOrRemoveItem()\"\n [disabled]=\"(valid$ | async) === false\"\n >\n <i\n class=\"text-primary\"\n c8yIcon=\"plus-circle\"\n ></i>\n </button>\n </c8y-li-icon>\n\n <c8y-li-checkbox\n class=\"a-s-center m-t-4 p-r-0\"\n *ngIf=\"showActiveToggle\"\n [displayAsSwitch]=\"displayAsSwitch\"\n formControlName=\"__active\"\n (click)=\"$event.stopPropagation()\"\n ></c8y-li-checkbox>\n\n <div class=\"d-flex a-i-center p-l-0\">\n <div\n class=\"c8y-list__item__colorpicker p-t-0 p-b-0 p-l-4\"\n [title]=\"colorPickerTitle | translate\"\n >\n <div\n class=\"c8y-colorpicker\"\n [ngClass]=\"{\n 'c8y-colorpicker--alarm': timelineType === 'ALARM',\n 'c8y-colorpicker--event': timelineType === 'EVENT'\n }\"\n >\n <input\n [ngStyle]=\"{ 'pointer-events': allowItemEdit && !omitProperties.color ? 'auto' : 'none' }\"\n type=\"color\"\n formControlName=\"color\"\n (click)=\"$event.stopPropagation()\"\n />\n <span\n class=\"circle-icon-wrapper circle-icon-wrapper--medium\"\n [ngStyle]=\"{ 'background-color': formGroup.value.color }\"\n >\n <i\n class=\"stroked-icon\"\n [c8yIcon]=\"timelineType === 'EVENT' ? 'online1' : 'bell'\"\n ></i>\n </span>\n </div>\n </div>\n <button\n class=\"btn-clean text-truncate\"\n [title]=\"formGroup.value.details?.label\"\n (click)=\"\n allowItemEdit\n ? (li.collapsed = !li.collapsed)\n : showActiveToggle\n ? toggleActive()\n : addOrRemoveItem()\n \"\n >\n <span class=\"text-truncate\">\n <c8y-highlight\n [text]=\"\n omitProperties.label\n ? formGroup.value.details?.filters?.type\n : formGroup.value.details?.label\n \"\n [pattern]=\"highlightText\"\n [shouldTrimPattern]=\"true\"\n ></c8y-highlight>\n </span>\n <small\n class=\"text-truncate text-muted icon-flex\"\n *ngIf=\"formGroup.value.__target && !hideSource\"\n >\n <i c8yIcon=\"exchange\"></i>\n <span class=\"text-truncate\">{{ formGroup.value.__target.name }}</span>\n </small>\n </button>\n\n <button\n class=\"btn-dot btn-dot--danger m-l-auto\"\n title=\"{{ 'Invalid entries' | translate }}\"\n [popover]=\"'Some entries are invalid. Check the required input fields.' | translate\"\n container=\"body\"\n *ngIf=\"(valid$ | async) === false && li.collapsed\"\n [outsideClick]=\"true\"\n >\n <i c8yIcon=\"exclamation-circle\"></i>\n </button>\n </div>\n\n <c8y-li-action\n [icon]=\"'minus-circle'\"\n *ngIf=\"optionToRemove\"\n [label]=\"'Remove from list' | translate\"\n (click)=\"remove()\"\n ></c8y-li-action>\n <c8y-li-collapse *ngIf=\"allowItemEdit\">\n <div class=\"data-point-details\">\n <c8y-alarm-event-attributes-form\n formControlName=\"details\"\n [timelineType]=\"timelineType\"\n [omitProperties]=\"omitProperties\"\n [datapoints]=\"datapoints\"\n [target]=\"formGroup.value.__target\"\n ></c8y-alarm-event-attributes-form>\n </div>\n </c8y-li-collapse>\n</c8y-li>\n","import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';\nimport { FormBuilder, FormGroup } from '@angular/forms';\nimport { AlarmOrEvent, OmitSelectorProperties, TimelineType } from '../alarm-event-selector.model';\nimport { map, startWith, takeUntil } from 'rxjs/operators';\nimport { Observable, Subject } from 'rxjs';\nimport { IIdentified } from '@c8y/client';\n\n@Component({\n selector: 'c8y-custom-alarm-event-form',\n templateUrl: './custom-alarm-event-form.component.html',\n standalone: false\n})\nexport class CustomAlarmEventFormComponent implements OnInit, OnDestroy {\n @Input() timelineType: TimelineType;\n @Input() target: IIdentified;\n @Input() omitProperties: OmitSelectorProperties = {};\n @Input() selectedItems: AlarmOrEvent[] = [];\n @Input() defaultColor: string;\n @Output() added = new EventEmitter<AlarmOrEvent>();\n @Output() cancel = new EventEmitter<void>();\n\n formGroup: FormGroup;\n valid$: Observable<boolean>;\n private destroy$ = new Subject<void>();\n\n constructor(private formBuilder: FormBuilder) {\n this.formGroup = this.formBuilder.group({\n details: [],\n color: [],\n __active: [],\n __target: []\n });\n\n this.valid$ = this.formGroup.statusChanges.pipe(\n takeUntil(this.destroy$),\n map(val => val === 'VALID'),\n startWith(false)\n );\n }\n\n ngOnInit() {\n this.formGroup.patchValue({\n color: this.defaultColor,\n __target: this.target,\n details: { timelineType: this.timelineType }\n });\n }\n\n ngOnDestroy() {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n add() {\n if (this.formGroup.valid) {\n const formValue = this.transformFormValue(this.formGroup.value);\n this.added.emit(formValue);\n }\n }\n\n private transformFormValue(formValue: any) {\n const obj = Object.assign({}, formValue.details || {}, formValue);\n delete obj.details;\n return obj;\n }\n}\n","<form [formGroup]=\"formGroup\" class=\"p-16\">\n <div\n *ngIf=\"!omitProperties.color\"\n class=\"form-group d-flex a-i-center gap-8\"\n [title]=\"'Change color' | translate\"\n >\n <label class=\"m-0\">{{ 'Color' | translate }}</label>\n <div class=\"c8y-colorpicker\"\n [ngClass]=\"{\n 'c8y-colorpicker--event': timelineType === 'EVENT',\n 'c8y-colorpicker--alarm': timelineType !== 'EVENT'\n }\"\n >\n <input\n type=\"color\"\n formControlName=\"color\"\n (click)=\"$event.stopPropagation()\"\n />\n <span [style.background-color]=\"formGroup.value.color\">\n <i [c8yIcon]=\"timelineType === 'EVENT' ? 'online1' : 'bell'\"></i>\n </span>\n </div>\n </div>\n\n <c8y-alarm-event-attributes-form\n formControlName=\"details\"\n [timelineType]=\"timelineType\"\n [omitProperties]=\"omitProperties\"\n [selectedItems]=\"selectedItems\"\n ></c8y-alarm-event-attributes-form>\n\n <div class=\"d-flex p-t-16\">\n <button class=\"btn btn-default btn-sm\" (click)=\"cancel.emit()\">\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"(valid$ | async) === false\"\n (click)=\"add()\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Select' | translate }}\n </button>\n </div>\n</form>\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { AlarmDetails, EventDetails } from '../alarm-event-selector.model';\n\n@Pipe({\n name: 'includesAlarmOrEvent',\n standalone: false\n})\nexport class IncludesAlarmOrEventPipe implements PipeTransform {\n transform<T extends AlarmDetails | EventDetails>(itemList: T[], item?: T): boolean {\n if (!Array.isArray(itemList) || !item) {\n return false;\n }\n return itemList.some(\n tmp =>\n tmp.label === item.label &&\n tmp.filters.type === item.filters.type &&\n tmp.__target?.id === item.__target?.id\n );\n }\n}\n","import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';\nimport { NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { IIdentified } from '@c8y/client';\nimport { AssetSelectionChangeEvent } from '@c8y/ngx-components/assets-navigator';\nimport { BehaviorSubject, combineLatest, Observable } from 'rxjs';\nimport {\n debounceTime,\n distinctUntilChanged,\n map,\n shareReplay,\n switchMap,\n tap\n} from 'rxjs/operators';\nimport {\n AlarmOrEvent,\n OmitSelectorProperties,\n TimelineType,\n TimelineTypeTexts\n} from './alarm-event-selector.model';\nimport { AlarmEventSelectorService } from './alarm-event-selector.service';\n\n@Component({\n selector: 'c8y-alarm-event-selector',\n templateUrl: './alarm-event-selector.component.html',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n multi: true,\n useExisting: forwardRef(() => AlarmEventSelectorComponent)\n }\n ],\n standalone: false\n})\nexport class AlarmEventSelectorComponent implements OnInit {\n @Input() timelineType: TimelineType = 'ALARM';\n @Input() contextAsset: IIdentified;\n @Input() allowChangingContext = true;\n @Input() selectedItems = new Array<AlarmOrEvent>();\n @Input() allowSearch = true;\n @Input() groupsSelectable = true;\n @Input() hideSource = false;\n @Input() omitProperties: OmitSelectorProperties = {};\n\n @Output() selectionChange = new EventEmitter<AlarmOrEvent[]>();\n filterString = '';\n maxNumberOfItems = 50;\n\n loadingItems$ = new BehaviorSubject(false);\n assetSelection = new BehaviorSubject<IIdentified>({});\n items$: Observable<AlarmOrEvent[]>;\n filteredItems$: Observable<AlarmOrEvent[]>;\n filterStringChanges$: Observable<string>;\n timelineTypeTexts: TimelineTypeTexts;\n isExpanded = false;\n defaultColor =\n getComputedStyle(document.documentElement).getPropertyValue('--brand-primary') ||\n getComputedStyle(document.documentElement).getPropertyValue('--c8y-brand-primary') ||\n '#1776BF';\n private filterString$ = new BehaviorSubject('');\n\n constructor(private alarmEventSelectorService: AlarmEventSelectorService) {}\n\n ngOnInit(): void {\n this.timelineTypeTexts = this.alarmEventSelectorService.timelineTypeTexts(this.timelineType);\n this.setupObservables();\n if (this.contextAsset) {\n this.selectAsset(this.contextAsset);\n }\n }\n\n itemAdded(item: AlarmOrEvent): void {\n item.__active = true;\n this.selectedItems = [...this.selectedItems, item];\n this.emitCurrentSelection();\n }\n\n itemRemoved(alarm: AlarmOrEvent): void {\n this.selectedItems = this.selectedItems.filter(\n tmp =>\n tmp.label !== alarm.label ||\n tmp.filters.type !== alarm.filters.type ||\n tmp.__target?.id !== alarm.__target?.id\n );\n this.emitCurrentSelection();\n }\n\n assetSelectionChanged(evt: AssetSelectionChangeEvent): void {\n if (evt.items) {\n return this.selectAsset(evt.items.length ? evt.items[0] : evt.items);\n }\n // reset selection\n this.assetSelection.next(null);\n }\n\n trackByFn(_index: number, item: AlarmOrEvent): string {\n return `${item.filters.type}-${item.__target?.id}`;\n }\n\n filterStringChanged(newValue = ''): void {\n this.filterString$.next(newValue);\n this.filterString = newValue;\n }\n\n private setLoadingState(state: boolean) {\n queueMicrotask(() => this.loadingItems$.next(state));\n }\n\n private setupObservables(): void {\n this.items$ = this.assetSelection.pipe(\n tap(() => this.setLoadingState(true)),\n switchMap((asset: IIdentified) =>\n asset?.id\n ? this.alarmEventSelectorService.getAlarmsOrEvents(asset, this.timelineType)\n : this.alarmEventSelectorService.getItems(this.timelineType)\n ),\n tap(() => this.setLoadingState(false)),\n map(items => {\n if (this.omitProperties.color) {\n items.forEach(i => (i.color = this.defaultColor));\n }\n return items;\n }),\n shareReplay(1)\n );\n\n this.filterStringChanges$ = this.filterString$.pipe(\n distinctUntilChanged(),\n debounceTime(500),\n shareReplay(1)\n );\n\n this.filteredItems$ = combineLatest([this.filterStringChanges$, this.items$]).pipe(\n map(([filterString, items]) => {\n if (!filterString) {\n return items;\n }\n const lowerCaseFilterString = filterString.toLowerCase();\n return items.filter(item => this.includesFilterString(item, lowerCaseFilterString));\n }),\n map(filtered => filtered.slice(0, this.maxNumberOfItems))\n );\n }\n\n private selectAsset(asset: IIdentified) {\n this.assetSelection.next(asset);\n this.filterStringChanged();\n }\n\n private emitCurrentSelection() {\n this.selectionChange.emit(this.selectedItems);\n }\n\n private includesFilterString(alarm: AlarmOrEvent, lowerCaseFilterString: string): boolean {\n const label = alarm.label?.toLowerCase();\n if (label && label.includes(lowerCaseFilterString)) {\n return true;\n }\n\n const type = alarm.filters.type?.toLowerCase();\n if (type && type.includes(lowerCaseFilterString)) {\n return true;\n }\n\n return false;\n }\n}\n","<div\n class=\"d-grid grid__row--1 fit-h\"\n [ngClass]=\"{\n 'grid__col--3-6-3--md': allowChangingContext,\n 'grid__col--8-4--md': !allowChangingContext\n }\"\n>\n <div\n class=\"d-flex d-col p-relative bg-level-1\"\n *ngIf=\"allowChangingContext\"\n >\n <c8y-asset-selector-miller\n class=\"d-contents\"\n [(ngModel)]=\"contextAsset\"\n [asset]=\"contextAsset\"\n (onSelected)=\"assetSelectionChanged($event)\"\n [container]=\"''\"\n [config]=\"{\n view: 'miller',\n groupsSelectable: groupsSelectable,\n columnHeaders: true,\n showChildDevices: true,\n showUnassignedDevices: true,\n singleColumn: true,\n search: allowSearch,\n showFilter: true\n }\"\n ></c8y-asset-selector-miller>\n </div>\n <!-- center column -->\n <div class=\"inner-scroll bg-component\" data-cy=\"c8y-alarm-event-selector--inner-column\">\n <ng-template #noDeviceEmptyState>\n <div class=\"p-16\">\n <c8y-ui-empty-state\n [icon]=\"timelineTypeTexts.emptyStateIcon\"\n [title]=\"timelineTypeTexts.emptyStateTitle | translate\"\n [subtitle]=\"timelineTypeTexts.assetWithNoItemsEmptyStateSubtitle | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n </div>\n </ng-template>\n\n <ng-template #loadingData>\n <div class=\"p-16 text-center\">\n <c8y-loading></c8y-loading>\n </div>\n </ng-template>\n\n <div\n class=\"bg-inherit\"\n *ngIf=\"assetSelection | async as asset; else noDeviceEmptyState\"\n >\n <div class=\"p-l-16 p-r-16 p-t-8 p-b-8 sticky-top bg-inherit separator-bottom\">\n <p\n class=\"text-medium text-truncate\"\n [title]=\"timelineTypeTexts.availableItemsTitle | translate\"\n >\n {{ timelineTypeTexts.availableItemsTitle | translate }}\n </p>\n <div class=\"d-flex\">\n <div\n class=\"input-group input-group-search m-t-4\"\n id=\"search\"\n *ngIf=\"!(loadingItems$ | async)\"\n >\n <input\n class=\"form-control\"\n placeholder=\"Filter…\"\n type=\"search\"\n [ngModel]=\"filterString\"\n (ngModelChange)=\"filterStringChanged($event)\"\n />\n <span class=\"input-group-addon\">\n <i\n c8yIcon=\"search\"\n *ngIf=\"!filterString; else clearFilterString\"\n ></i>\n <ng-template #clearFilterString>\n <i\n class=\"text-muted\"\n c8yIcon=\"times\"\n *ngIf=\"filterString\"\n (click)=\"filterStringChanged()\"\n ></i>\n </ng-template>\n </span>\n </div>\n </div>\n </div>\n\n <ng-container *ngIf=\"filteredItems$ | async as filteredItems; else loadingData\">\n <ng-container *ngIf=\"!(loadingItems$ | async); else loadingData\">\n <ng-container *ngIf=\"items$ | async as items\">\n <div class=\"p-16 bg-level-2 separator-bottom\">\n <div>\n <p>\n <i\n class=\"text-info m-r-4\"\n c8yIcon=\"info-circle\"\n ></i>\n <strong>{{ timelineTypeTexts.recentItemsWarningTitle | translate }}</strong>\n </p>\n <p>\n {{ timelineTypeTexts.recentItemsWarningText | translate }}\n {{ timelineTypeTexts.addCustomText | translate }}\n </p>\n </div>\n <div class=\"p-t-16\">\n <button\n class=\"btn btn-default btn-sm\"\n aria-controls=\"collapseCustomItemForm\"\n [attr.aria-expanded]=\"isExpanded\"\n (click)=\"isExpanded = !isExpanded\"\n >\n {{ timelineTypeTexts.addCustomItemButtonLabel | translate }}\n </button>\n <div\n class=\"collapse\"\n id=\"collapseCustomItemForm\"\n [collapse]=\"!isExpanded\"\n [isAnimated]=\"true\"\n >\n <div [style.min-height]=\"'230px'\">\n <c8y-custom-alarm-event-form\n class=\"d-block\"\n *ngIf=\"isExpanded\"\n [timelineType]=\"timelineType\"\n [target]=\"assetSelection | async\"\n [omitProperties]=\"omitProperties\"\n [defaultColor]=\"defaultColor\"\n (added)=\"itemAdded($event); isExpanded = false\"\n (cancel)=\"isExpanded = false\"\n [selectedItems]=\"selectedItems\"\n ></c8y-custom-alarm-event-form>\n </div>\n </div>\n </div>\n </div>\n\n <div\n class=\"p-16\"\n *ngIf=\"!filteredItems.length\"\n >\n <c8y-ui-empty-state\n [icon]=\"timelineTypeTexts.emptyStateIcon\"\n [title]=\"timelineTypeTexts.emptyStateTitle | translate\"\n [subtitle]=\"\n items.length\n ? ('Try another filter term.' | translate)\n : (timelineTypeTexts.assetWithNoItemsEmptyStateSubtitle | translate)\n \"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n </div>\n\n <c8y-list-group>\n <c8y-list-item\n class=\"sticky-top\"\n style=\"top: 72px\"\n *ngIf=\"items.length > maxNumberOfItems && filteredItems.length >= maxNumberOfItems\"\n >\n <div class=\"alert alert-warning m-b-0\">\n {{ timelineTypeTexts.largeNumberOfItemsInfo | translate }}\n </div>\n </c8y-list-item>\n <c8y-alarm-event-selector-list-item\n class=\"d-contents\"\n [ngModel]=\"item\"\n [isSelected]=\"selectedItems | includesAlarmOrEvent: item\"\n (added)=\"itemAdded($event)\"\n (removed)=\"itemRemoved($event)\"\n [highlightText]=\"filterStringChanges$ | async\"\n [timelineType]=\"timelineType\"\n [hideSource]=\"hideSource\"\n *ngFor=\"let item of filteredItems; trackBy: trackByFn\"\n ></c8y-alarm-event-selector-list-item>\n </c8y-list-group>\n </ng-container>\n </ng-container>\n </ng-container>\n </div>\n </div>\n\n <!-- last column -->\n <div class=\"inner-scroll bg-level-1\">\n <p\n class=\"text-medium m-b-4 p-l-16 p-r-16 p-t-8 p-b-8 separator-bottom sticky-top text-truncate\"\n [title]=\"timelineTypeTexts.selectedItemsTitle | translate\"\n >\n {{ timelineTypeTexts.selectedItemsTitle | translate }}\n </p>\n <div class=\"d-flex flex-wrap gap-8 p-l-16 p-r-16 p-b-16\">\n <c8y-ui-empty-state\n [icon]=\"timelineTypeTexts.emptyStateIcon\"\n [title]=\"timelineTypeTexts.noSelectedItemsTitle | translate\"\n *ngIf=\"!selectedItems || !selectedItems.length\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n\n <div\n [ngClass]=\"{\n 'c8y-alarm-pill': timelineType === 'ALARM',\n 'c8y-event-pill': timelineType === 'EVENT'\n }\"\n *ngFor=\"let selectedItem of selectedItems\"\n >\n <button\n [title]=\"'Remove' | translate\"\n type=\"button\"\n [ngClass]=\"{\n 'c8y-alarm-pill__btn': timelineType === 'ALARM',\n 'c8y-event-pill__btn': timelineType === 'EVENT'\n }\"\n (click)=\"itemRemoved(selectedItem)\"\n >\n <i\n class=\"icon-14\"\n c8yIcon=\"remove\"\n ></i>\n </button>\n <div\n class=\"c8y-datapoint-pill__label\"\n [title]=\"selectedItem.label || selectedItem.filters.type\"\n >\n <span\n class=\"circle-icon-wrapper circle-icon-wrapper--small m-r-4\"\n [style.background-color]=\"selectedItem.color || defaultColor\"\n >\n <i\n class=\"stroked-icon\"\n [c8yIcon]=\"timelineType === 'ALARM' ? 'bell' : 'online1'\"\n ></i>\n </span>\n <span class=\"text-truncate\">\n <span class=\"text-truncate\">{{ selectedItem.label || selectedItem.filters.type }}</span>\n <small\n class=\"text-muted text-10\"\n *ngIf=\"!hideSource && selectedItem?.__target?.name\"\n >\n {{ selectedItem?.__target?.name }}\n </small>\n </span>\n </div>\n </div>\n </div>\n </div>\n</div>\n","import { Component } from '@angular/core';\nimport { IIdentified } from '@c8y/client';\nimport { BsModalRef } from 'ngx-bootstrap/modal';\nimport { AlarmOrEvent, OmitSelectorProperties, TimelineType } from '../alarm-event-selector.model';\n\n@Component({\n selector: 'c8y-alarm-event-selector-modal',\n templateUrl: './alarm-event-selector-modal.component.html',\n standalone: false\n})\nexport class AlarmEventSelectorModalComponent {\n selectType: TimelineType = 'ALARM';\n contextAsset: IIdentified;\n allowChangingContext = true;\n allowSearch = true;\n selectedItems = new Array<AlarmOrEvent>();\n title: string;\n groupsSelectable = true;\n hideSource = false;\n saveButtonLabel: string;\n omitProperties: OmitSelectorProperties = {};\n readonly result: Promise<AlarmOrEvent[]> = new Promise((resolve, reject) => {\n this.save = resolve;\n this.cancel = reject;\n });\n\n private save: (value: AlarmOrEvent[]) => void;\n private cancel: (reason?: any) => void;\n\n constructor(private bsModal: BsModalRef) {}\n\n saveChanges(): void {\n this.bsModal.hide();\n this.save(this.selectedItems);\n }\n\n close() {\n this.bsModal.hide();\n this.cancel();\n }\n\n selectionChange(selection: Array<AlarmOrEvent>) {\n this.selectedItems = selection;\n }\n}\n","<div class=\"modal-header separator\">\n <h4 class=\"text-medium\">{{ title | translate }}</h4>\n</div>\n<div class=\"modal-inner-scroll modal-inner-scroll--fixed\">\n <c8y-alarm-event-selector\n [selectedItems]=\"selectedItems\"\n [contextAsset]=\"contextAsset\"\n [timelineType]=\"selectType\"\n [groupsSelectable]=\"groupsSelectable\"\n [hideSource]=\"hideSource\"\n [allowChangingContext]=\"allowChangingContext\"\n [omitProperties]=\"omitProperties\"\n (selectionChange)=\"selectionChange($event)\"\n ></c8y-alarm-event-selector>\n</div>\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n [title]=\"'Cancel' | translate\"\n class=\"btn btn-default\"\n (click)=\"close()\"\n translate\n >\n Cancel\n </button>\n <button\n [title]=\"saveButtonLabel | translate\"\n class=\"btn btn-primary\"\n [disabled]=\"!this.selectedItems?.length\"\n (click)=\"saveChanges()\"\n >\n {{ saveButtonLabel | translate }}\n </button>\n</div>\n","import { Injectable } from '@angular/core';\nimport { AlarmOrEvent } from '../alarm-event-selector.model';\nimport { AlarmEventSelectorModalComponent } from './alarm-event-selector-modal.component';\nimport { BsModalService } from 'ngx-bootstrap/modal';\nimport { AlarmEventSelectorModalOptions } from './alarm-event-selector-modal.model';\n\n/**\n * Service to open the alarm event selector modal.\n */\n@Injectable({ providedIn: 'root' })\nexport class AlarmEventSelectorModalService {\n constructor(private modal: BsModalService) {}\n /**\n * Opents the alarm or event selector modal.\n * @param initialState Initial state of the modal.\n * @returns Promise that resolves with the selected alarms or events.\n */\n selectItems(initialState: Partial<AlarmEventSelectorModalOptions> = {}): Promise<AlarmOrEvent[]> {\n const modal = this.modal.show(AlarmEventSelectorModalComponent, {\n ignoreBackdropClick: true,\n keyboard: false,\n initialState,\n class: 'modal-lg'\n });\n const content = modal.content as AlarmEventSelectorModalComponent;\n return content.result;\n }\n}\n","import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';\nimport {\n Component,\n ContentChild,\n forwardRef,\n Input,\n OnDestroy,\n OnInit,\n Optional\n} from '@angular/core';\nimport {\n AbstractControl,\n ControlValueAccessor,\n FormArray,\n FormBuilder,\n NG_VALIDATORS,\n NG_VALUE_ACCESSOR,\n ValidationErrors,\n Validator\n} from '@angular/forms';\nimport { ContextRouteService, EmptyStateComponent, ViewContext } from '@c8y/ngx-components';\nimport { WidgetConfigComponent } from '@c8y/ngx-components/context-dashboard';\nimport { debounceTime, map, take, takeUntil } from 'rxjs/operators';\nimport { AlarmEventSelectorModalOptions } from '../alarm-event-selector-modal/alarm-event-selector-modal.model';\nimport { AlarmEventSelectorModalService } from '../alarm-event-selector-modal/alarm-event-selector-modal.service';\nimport {\n AlarmOrEvent,\n OmitSelectorProperties,\n TimelineType,\n TimelineTypeTexts\n} from '../alarm-event-selector.model';\nimport { AlarmEventSelectorService } from '../alarm-event-selector.service';\nimport { Subject } from 'rxjs';\nimport { ActivatedRoute } from '@angular/router';\n\n@Component({\n selector: 'c8y-alarm-event-selection-list',\n templateUrl: './alarm-event-selection-list.component.html',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n multi: true,\n useExisting: forwardRef(() => AlarmEventSelectionListComponent)\n },\n {\n provide: NG_VALIDATORS,\n useExisting: forwardRef(() => AlarmEventSelectionListComponent),\n multi: true\n }\n ],\n standalone: false\n})\nexport class AlarmEventSelectionListComponent\n implements ControlValueAccessor, Validator, OnInit, OnDestroy\n{\n @Input() timelineType: TimelineType = 'ALARM';\n @Input() canRemove = true;\n @Input() canEdit = true;\n @Input() canDragAndDrop = true;\n @Input() title: string;\n @Input() addButtonLabel: string;\n @Input() hideSource = false;\n @Input() inline = false; // TODO: @janhommes we should rename this to `isDropdownContained` or similar to make it more clear\n @Input() activeToggleAsSwitch = true;\n @Input() omitProperties: OmitSelectorProperties = { color: false, label: false };\n @Input() datapoints = [];\n @ContentChild(EmptyStateComponent) emptyState: EmptyStateComponent;\n\n /**\n * The configuration for the alarms-events selector modal.\n */\n @Input() config: Partial<AlarmEventSelectorModalOptions> = {};\n\n formArray: FormArray;\n timelineTypeTexts: TimelineTypeTexts;\n contextSourceId: number | string | null;\n\n private destroy$ = new Subject<void>();\n\n constructor(\n private alarmEventModalService: AlarmEventSelectorModalService,\n private alarmEventSelectService: AlarmEventSelectorService,\n private formBuilder: FormBuilder,\n @Optional() private activatedRoute: ActivatedRoute,\n @Optional() private contextRouteService: ContextRouteService,\n @Optional() private widgetComponent: WidgetConfigComponent\n ) {\n this.formArray = this.formBuilder.array([]);\n }\n\n ngOnInit(): void {\n this.timelineTypeTexts = this.alarmEventSelectService.timelineTypeTexts(this.timelineType);\n this.title ??= this.timelineTypeTexts.listTitle;\n this.addButtonLabel ??= this.timelineTypeTexts.addButtonLabel;\n this.contextSourceId = this.initializeContextSourceId();\n const context = this.widgetComponent?.context;\n if (context?.id) {\n const { name, id, c8y_IsDevice } = context;\n this.config.contextAsset = { name, id, c8y_IsDevice };\n }\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n registerOnTouched(fn: any): void {\n this.formArray.valueChanges.pipe(take(1)).subscribe(fn);\n }\n\n validate(_control: AbstractControl): ValidationErrors {\n return this.formArray.valid ? null : { formInvalid: {} };\n }\n\n writeValue(val: AlarmOrEvent[]): void {\n this.formArray.clear();\n if (val?.length) {\n val.forEach(val => {\n const formgroup = this.formBuilder.group({ details: [] });\n formgroup.patchValue({ details: val });\n this.formArray.push(formgroup);\n });\n }\n }\n\n registerOnChange(fn: any): void {\n this.formArray.valueChanges\n .pipe(\n debounceTime(100), // debounce to avoid emitting of this.formArray.clear()\n map(res => this.transformValue(res)),\n takeUntil(this.destroy$)\n )\n .subscribe(fn);\n }\n\n add() {\n const allowChangingContext