UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

526 lines (521 loc) 150 kB
import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle, DragDropModule } from '@angular/cdk/drag-drop'; import * as i0 from '@angular/core'; import { Injectable, forwardRef, Input, Component, EventEmitter, ContentChild, Output, Pipe, Optional, NgModule } from '@angular/core'; import * as i2$1 from '@c8y/ngx-components'; import { C8yValidators, C8yTranslateDirective, FormGroupComponent, RequiredInputPlaceholderDirective, MessagesComponent, MessageDirective, C8yTranslatePipe, MAX_PAGE_SIZE, IconDirective, ListItemComponent, ListItemDragHandleComponent, ListItemCheckboxComponent, HighlightComponent, ListItemActionComponent, ListItemCollapseComponent, TypeaheadComponent, ForOfDirective, ListItemIconComponent, EmptyStateComponent, LoadingComponent, ListGroupComponent, CoreModule } from '@c8y/ngx-components'; import { MillerViewComponent, AssetSelectorModule } from '@c8y/ngx-components/assets-navigator'; import { CollapseModule } from 'ngx-bootstrap/collapse'; import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; import * as i1$1 from 'ngx-bootstrap/modal'; import { ModalModule } from 'ngx-bootstrap/modal'; import { PopoverDirective, PopoverModule } from 'ngx-bootstrap/popover'; import { TooltipDirective, TooltipModule } from 'ngx-bootstrap/tooltip'; import * as i2 from '@angular/forms'; import { Validators, FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import { map, take, filter, startWith, tap, switchMap, shareReplay, distinctUntilChanged, debounceTime } from 'rxjs/operators'; import { gettext } from '@c8y/ngx-components/gettext'; import { NgIf, NgFor, NgClass, NgTemplateOutlet, KeyValuePipe, NgSwitch, NgSwitchCase, NgSwitchDefault, AsyncPipe } from '@angular/common'; import * as i4 from '@c8y/ngx-components/context-dashboard'; import { pipe, BehaviorSubject, from, combineLatest } from 'rxjs'; import * as i1 from '@c8y/client'; import { sortBy, get, uniq } from 'lodash-es'; const AXIS_TYPES = [ { val: undefined, text: gettext('Auto') }, { val: 'left', text: gettext('Left') }, { val: 'right', text: gettext('Right') } ]; const CHART_LINE_TYPES = [ { val: 'line', text: gettext('Line') }, { val: 'points', text: gettext('Points') }, { val: 'linePoints', text: gettext('Line and points') }, { val: 'bars', text: gettext('Bars') }, { val: 'step-before', text: gettext('Step before') }, { val: 'step-after', text: gettext('Step after') } ]; const CHART_RENDER_TYPES = [ { val: 'min', text: gettext('Minimum') }, { val: 'max', text: gettext('Maximum') }, { val: 'area', text: gettext('Minimum and maximum') } ]; class DatapointAttributesFormValidationService { constructor(formBuilder) { this.formBuilder = formBuilder; } getDefaultFormGroup(fieldsToRemove = []) { const formFields = { __active: [true, []], __target: this.getTargetFormGroup(), __template: [undefined, []], color: ['', this.getColorValidators()], label: ['', this.getLabelValidators()], description: ['', this.getDescriptionValidators()], fragment: ['', this.getFragmentValidators()], series: ['', this.getSeriesValidators()], range: this.getMinMaxFormGroup(), unit: [undefined, this.getUnitValidators()], target: [undefined, this.getTargetValidators()], redRange: this.getMinMaxFormGroup(), yellowRange: this.getMinMaxFormGroup(), chart: this.getChartFormGroup(), display: this.getDisplayFormGroup() }; if (fieldsToRemove.length) { for (const field of fieldsToRemove) { delete formFields[field]; } } return this.formBuilder.group(formFields, { validators: this.getOverallValidators() }); } convertToBackendFormat(formDataStructure, showChart) { if (!formDataStructure) { return {}; } const { __active, __target, __template, color, label, description, fragment, series, range, unit, target, redRange, yellowRange, chart, display } = formDataStructure; const obj = { __active, __target, __template, color, label, description, fragment, series, min: range?.min, max: range?.max, unit, target, redRangeMin: redRange?.min, redRangeMax: redRange?.max, yellowRangeMin: yellowRange?.min, yellowRangeMax: yellowRange?.max, renderType: showChart ? chart?.renderType : display?.renderType, lineType: chart?.lineType, yAxisType: chart?.yAxisType }; return obj; } convertToFormGroupFormat(backendDataStructure) { if (!backendDataStructure) { return {}; } const { __active, __target, __template, color, label, description, fragment, series, min, max, unit, target, redRangeMin, redRangeMax, yellowRangeMin, yellowRangeMax, renderType, lineType, yAxisType } = backendDataStructure; const obj = { __active, __target, __template, color, label, description, fragment, series, range: { min: this.convertStringToNumber(min), max: this.convertStringToNumber(max) }, unit, target: this.convertStringToNumber(target), redRange: { min: this.convertStringToNumber(redRangeMin), max: this.convertStringToNumber(redRangeMax) }, yellowRange: { min: this.convertStringToNumber(yellowRangeMin), max: this.convertStringToNumber(yellowRangeMax) }, chart: renderType || lineType || yAxisType ? { renderType, lineType, yAxisType } : undefined, display: renderType ? { renderType } : undefined }; return obj; } getColorValidators() { return [Validators.required, Validators.minLength(4)]; } getLabelValidators() { return [Validators.required, Validators.minLength(1), Validators.maxLength(120)]; } getDescriptionValidators() { return []; } getFragmentValidators() { return [ Validators.required, Validators.minLength(1), Validators.maxLength(120), Validators.pattern(/^[^.]*$/) ]; } getSeriesValidators() { return [ Validators.required, Validators.minLength(1), Validators.maxLength(120), (control) => { const forbidden = control.value?.includes('.') || ''; return forbidden ? { noPeriods: { message: gettext('Series cannot contain periods.') } } : null; } ]; } getMinMaxValidators() { return [C8yValidators.minMaxValidator(), C8yValidators.requireBothMinAndMax()]; } getUnitValidators() { return []; } getTargetValidators() { return []; } getOverallValidators() { return [ C8yValidators.withinScale('redRange.min'), C8yValidators.withinScale('redRange.max'), C8yValidators.withinScale('yellowRange.min'), C8yValidators.withinScale('yellowRange.max'), C8yValidators.withinScale('target') ]; } getMinMaxFormGroup() { return this.formBuilder.group({ min: [undefined, []], max: [undefined, []] }, { validators: this.getMinMaxValidators() }); } getChartFormGroup() { return this.formBuilder.group({ renderType: [CHART_RENDER_TYPES[0].val, []], lineType: [CHART_LINE_TYPES[0].val, []], yAxisType: [AXIS_TYPES[0].val, []] }); } getDisplayFormGroup() { return this.formBuilder.group({ renderType: [CHART_RENDER_TYPES[0].val, []] }); } getTargetFormGroup() { return this.formBuilder.group({ id: [undefined, []], name: [undefined, []] }); } convertStringToNumber(possibleString) { if (typeof possibleString === 'string') { try { return Number.parseFloat(possibleString); } catch { return undefined; } } else { return possibleString; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DatapointAttributesFormValidationService, deps: [{ token: i2.FormBuilder }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DatapointAttributesFormValidationService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DatapointAttributesFormValidationService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i2.FormBuilder }] }); class DatapointAttributesFormComponent { constructor(formValidations) { this.formValidations = formValidations; this.selectableChartRenderTypes = []; this.selectableChartLineTypes = []; this.selectableAxisTypes = []; this.showTarget = true; this.showRange = true; this.showYellowRange = true; this.showRedRange = true; this.showChart = true; this.showFormIfTemplateWasSelected = false; this.rawValue = {}; this.CHART_RENDER_TYPES = Array.from(CHART_RENDER_TYPES); this.CHART_LINE_TYPES = Array.from(CHART_LINE_TYPES); this.AXIS_TYPES = Array.from(AXIS_TYPES); this.customValidationErrorMessages = {}; this.shouldForceInitialValidation = true; this.formGroup = this.formValidations.getDefaultFormGroup(); this.setSubForms(); } ngOnInit() { this.initializeFormVisibility(); this.filterChartTypes(); } validate(_control) { if (this.formGroup?.get('series')?.errors?.noPeriods) { return this.formGroup?.get('series')?.errors; } return this.formGroup?.valid ? null : { formInvalid: {} }; } writeValue(obj) { this.rawValue = obj || {}; if (obj) { this.formGroup.patchValue(this.formValidations.convertToFormGroupFormat(obj), { emitEvent: false }); } if (this.shouldForceInitialValidation) { queueMicrotask(() => { this.formGroup.updateValueAndValidity(); }); this.shouldForceInitialValidation = false; } } registerOnChange(fn) { this.formGroup.valueChanges .pipe(map(formValue => this.formValidations.convertToBackendFormat(formValue, this.showChart)), map(formValue => Object.assign(this.rawValue, formValue))) .subscribe(fn); } registerOnTouched(fn) { this.formGroup.valueChanges.pipe(take(1)).subscribe(fn); } setDisabledState(isDisabled) { if (this.formGroup?.disabled === isDisabled) { return; } isDisabled ? this.formGroup.disable() : this.formGroup.enable(); } setSubForms() { if (!this.formGroup) { this.range = this.yellowRange = this.redRange = this.chart = undefined; return; } this.range = this.formGroup.get('range'); this.yellowRange = this.formGroup.get('yellowRange'); this.redRange = this.formGroup.get('redRange'); this.chart = this.formGroup.get('chart'); this.display = this.formGroup.get('display'); } initializeFormVisibility() { this.showChartForm = this.chart && this.showChart; const hasLineTypes = !!this.selectableChartLineTypes?.length; const hasAxisTypes = !!this.selectableAxisTypes?.length; const hasRenderTypes = this.selectableChartRenderTypes?.length > 0 || this.selectableChartRenderTypes === undefined; this.showOnlyDisplayForm = !this.showChartForm && !hasLineTypes && !hasAxisTypes && hasRenderTypes; } filterChartTypes() { this.filterRenderTypes(); this.filterLineTypes(); this.filterAxisTypes(); } filterRenderTypes() { if (!!this.selectableChartRenderTypes?.length) { this.CHART_RENDER_TYPES = this.CHART_RENDER_TYPES.filter(renderType => this.selectableChartRenderTypes.includes(renderType.val)); } } filterLineTypes() { if (!!this.selectableChartLineTypes?.length) { this.CHART_LINE_TYPES = this.CHART_LINE_TYPES.filter(lineType => this.selectableChartLineTypes.includes(lineType.val)); } } filterAxisTypes() { if (!!this.selectableAxisTypes?.length) { this.AXIS_TYPES = this.AXIS_TYPES.filter(axisType => this.selectableAxisTypes.includes(axisType.val)); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DatapointAttributesFormComponent, deps: [{ token: DatapointAttributesFormValidationService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: DatapointAttributesFormComponent, isStandalone: true, selector: "c8y-datapoint-attributes-form", inputs: { selectableChartRenderTypes: "selectableChartRenderTypes", selectableChartLineTypes: "selectableChartLineTypes", selectableAxisTypes: "selectableAxisTypes", showTarget: "showTarget", showRange: "showRange", showYellowRange: "showYellowRange", showRedRange: "showRedRange", showChart: "showChart", showFormIfTemplateWasSelected: "showFormIfTemplateWasSelected" }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DatapointAttributesFormComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => DatapointAttributesFormComponent), multi: true } ], ngImport: i0, template: "<div [formGroup]=\"formGroup\">\n <ng-container *ngIf=\"!rawValue?.__template || showFormIfTemplateWasSelected\">\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"formGroup.controls?.label || formGroup.controls?.unit || formGroup.controls?.target\"\n >\n <legend translate>Details</legend>\n <div class=\"row\">\n <div\n class=\"col-md-6\"\n *ngIf=\"formGroup.controls?.label\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label translate>Label</label>\n <input\n class=\"form-control\"\n name=\"label\"\n formControlName=\"label\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 'Temperature' }\"\n />\n <c8y-messages\n [show]=\"formGroup.controls.label.touched && formGroup.controls.label.errors\"\n >\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['label'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div\n class=\"col-md-6\"\n *ngIf=\"formGroup.controls?.unit\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label translate>Unit</label>\n <input\n class=\"form-control\"\n name=\"unit\"\n formControlName=\"unit\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: '\u00BAC' }\"\n />\n <c8y-messages\n [show]=\"formGroup.controls.unit.touched && formGroup.controls.unit.errors\"\n >\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['unit'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div\n class=\"col-md-6\"\n *ngIf=\"formGroup.controls?.target && showTarget\"\n >\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error':\n (range?.touched || formGroup.controls.target.touched) &&\n formGroup.controls?.target?.errors\n }\"\n >\n <label translate>Target</label>\n <input\n class=\"form-control\"\n name=\"target\"\n type=\"number\"\n formControlName=\"target\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 25 }\"\n />\n <c8y-messages\n [show]=\"\n (range?.touched || formGroup.controls.target.touched) &&\n formGroup.controls.target.errors\n \"\n >\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['target'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"range && showRange\"\n >\n <legend translate>Range</legend>\n <div\n class=\"row\"\n formGroupName=\"range\"\n >\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{ 'has-error': range?.touched && range?.controls?.min?.errors }\"\n >\n <label translate>Min</label>\n <input\n class=\"form-control\"\n name=\"min\"\n type=\"number\"\n formControlName=\"min\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 0 }\"\n />\n <c8y-messages [show]=\"range?.touched && range.controls?.min?.errors\"></c8y-messages>\n </c8y-form-group>\n </div>\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{ 'has-error': range?.touched && range?.controls?.max?.errors }\"\n >\n <label translate>Max</label>\n <input\n class=\"form-control\"\n name=\"max\"\n type=\"number\"\n formControlName=\"max\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 100 }\"\n />\n <c8y-messages [show]=\"range?.touched && range.controls?.max?.errors\">\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['max'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n\n <fieldset\n class=\"c8y-fieldset\"\n formGroupName=\"yellowRange\"\n *ngIf=\"yellowRange && showYellowRange\"\n >\n <legend translate>Yellow range</legend>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error':\n (range?.touched || yellowRange?.touched) && yellowRange?.controls?.min?.errors\n }\"\n >\n <label translate>Min</label>\n <input\n class=\"form-control\"\n name=\"min\"\n type=\"number\"\n formControlName=\"min\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 50 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || yellowRange?.touched) && yellowRange.controls?.min?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error':\n (range?.touched || yellowRange?.touched) && yellowRange?.controls?.max?.errors\n }\"\n >\n <label translate>Max</label>\n <input\n class=\"form-control\"\n name=\"max\"\n type=\"number\"\n formControlName=\"max\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 75 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || yellowRange?.touched) && yellowRange.controls?.max?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n\n <fieldset\n class=\"c8y-fieldset\"\n formGroupName=\"redRange\"\n *ngIf=\"redRange && showRedRange\"\n >\n <legend translate>Red range</legend>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error': (range?.touched || redRange?.touched) && redRange?.controls?.min?.errors\n }\"\n >\n <label translate>Min</label>\n <input\n class=\"form-control\"\n name=\"min\"\n type=\"number\"\n formControlName=\"min\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 75 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || redRange?.touched) && redRange.controls?.min?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error': (range?.touched || redRange?.touched) && redRange?.controls?.max?.errors\n }\"\n >\n <label translate>Max</label>\n <input\n class=\"form-control\"\n name=\"max\"\n type=\"number\"\n formControlName=\"max\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 100 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || redRange?.touched) && redRange.controls?.max?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n </ng-container>\n\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"showChartForm\"\n formGroupName=\"chart\"\n >\n <legend translate>Chart</legend>\n <div class=\"tight-grid\">\n <div\n class=\"col-xs-6 col-sm-4\"\n *ngIf=\"selectableChartRenderTypes?.length !== 0\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <ng-container *ngTemplateOutlet=\"displayHelpButton\"></ng-container>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"renderType\"\n >\n <option\n *ngFor=\"let type of CHART_RENDER_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n <div\n class=\"col-xs-6 col-sm-4\"\n *ngIf=\"selectableChartLineTypes?.length !== 0\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label\n for=\"chartType\"\n translate\n >\n Chart type\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"lineType\"\n >\n <option\n *ngFor=\"let type of CHART_LINE_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n <span></span>\n </div>\n </c8y-form-group>\n </div>\n <div\n class=\"col-xs-6 col-sm-4\"\n *ngIf=\"selectableAxisTypes?.length !== 0\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label\n for=\"yAxis\"\n translate\n >\n Y-axis\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"yAxisType\"\n >\n <option\n *ngFor=\"let type of AXIS_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n <span></span>\n </div>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"showOnlyDisplayForm\"\n formGroupName=\"display\"\n >\n <legend>\n <ng-container *ngTemplateOutlet=\"displayHelpButton\"></ng-container>\n </legend>\n <div class=\"tight-grid\">\n <div class=\"col-xs-6 col-sm-4\">\n <c8y-form-group class=\"form-group-sm\">\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"renderType\"\n >\n <option\n *ngFor=\"let type of CHART_RENDER_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n</div>\n\n<ng-template #displayHelpButton>\n <label>\n {{ 'Display' | translate }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ 'Value displayed when data is aggregated' | translate }}\"\n triggers=\"focus\"\n type=\"button\"\n placement=\"right\"\n ></button>\n </label>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DatapointAttributesFormComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-datapoint-attributes-form', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DatapointAttributesFormComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => DatapointAttributesFormComponent), multi: true } ], imports: [ FormsModule, ReactiveFormsModule, NgIf, C8yTranslateDirective, FormGroupComponent, RequiredInputPlaceholderDirective, MessagesComponent, NgFor, MessageDirective, NgClass, NgTemplateOutlet, PopoverDirective, C8yTranslatePipe, KeyValuePipe ], template: "<div [formGroup]=\"formGroup\">\n <ng-container *ngIf=\"!rawValue?.__template || showFormIfTemplateWasSelected\">\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"formGroup.controls?.label || formGroup.controls?.unit || formGroup.controls?.target\"\n >\n <legend translate>Details</legend>\n <div class=\"row\">\n <div\n class=\"col-md-6\"\n *ngIf=\"formGroup.controls?.label\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label translate>Label</label>\n <input\n class=\"form-control\"\n name=\"label\"\n formControlName=\"label\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 'Temperature' }\"\n />\n <c8y-messages\n [show]=\"formGroup.controls.label.touched && formGroup.controls.label.errors\"\n >\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['label'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div\n class=\"col-md-6\"\n *ngIf=\"formGroup.controls?.unit\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label translate>Unit</label>\n <input\n class=\"form-control\"\n name=\"unit\"\n formControlName=\"unit\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: '\u00BAC' }\"\n />\n <c8y-messages\n [show]=\"formGroup.controls.unit.touched && formGroup.controls.unit.errors\"\n >\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['unit'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div\n class=\"col-md-6\"\n *ngIf=\"formGroup.controls?.target && showTarget\"\n >\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error':\n (range?.touched || formGroup.controls.target.touched) &&\n formGroup.controls?.target?.errors\n }\"\n >\n <label translate>Target</label>\n <input\n class=\"form-control\"\n name=\"target\"\n type=\"number\"\n formControlName=\"target\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 25 }\"\n />\n <c8y-messages\n [show]=\"\n (range?.touched || formGroup.controls.target.touched) &&\n formGroup.controls.target.errors\n \"\n >\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['target'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"range && showRange\"\n >\n <legend translate>Range</legend>\n <div\n class=\"row\"\n formGroupName=\"range\"\n >\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{ 'has-error': range?.touched && range?.controls?.min?.errors }\"\n >\n <label translate>Min</label>\n <input\n class=\"form-control\"\n name=\"min\"\n type=\"number\"\n formControlName=\"min\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 0 }\"\n />\n <c8y-messages [show]=\"range?.touched && range.controls?.min?.errors\"></c8y-messages>\n </c8y-form-group>\n </div>\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{ 'has-error': range?.touched && range?.controls?.max?.errors }\"\n >\n <label translate>Max</label>\n <input\n class=\"form-control\"\n name=\"max\"\n type=\"number\"\n formControlName=\"max\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 100 }\"\n />\n <c8y-messages [show]=\"range?.touched && range.controls?.max?.errors\">\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['max'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n\n <fieldset\n class=\"c8y-fieldset\"\n formGroupName=\"yellowRange\"\n *ngIf=\"yellowRange && showYellowRange\"\n >\n <legend translate>Yellow range</legend>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error':\n (range?.touched || yellowRange?.touched) && yellowRange?.controls?.min?.errors\n }\"\n >\n <label translate>Min</label>\n <input\n class=\"form-control\"\n name=\"min\"\n type=\"number\"\n formControlName=\"min\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 50 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || yellowRange?.touched) && yellowRange.controls?.min?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error':\n (range?.touched || yellowRange?.touched) && yellowRange?.controls?.max?.errors\n }\"\n >\n <label translate>Max</label>\n <input\n class=\"form-control\"\n name=\"max\"\n type=\"number\"\n formControlName=\"max\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 75 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || yellowRange?.touched) && yellowRange.controls?.max?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n\n <fieldset\n class=\"c8y-fieldset\"\n formGroupName=\"redRange\"\n *ngIf=\"redRange && showRedRange\"\n >\n <legend translate>Red range</legend>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error': (range?.touched || redRange?.touched) && redRange?.controls?.min?.errors\n }\"\n >\n <label translate>Min</label>\n <input\n class=\"form-control\"\n name=\"min\"\n type=\"number\"\n formControlName=\"min\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 75 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || redRange?.touched) && redRange.controls?.min?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error': (range?.touched || redRange?.touched) && redRange?.controls?.max?.errors\n }\"\n >\n <label translate>Max</label>\n <input\n class=\"form-control\"\n name=\"max\"\n type=\"number\"\n formControlName=\"max\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 100 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || redRange?.touched) && redRange.controls?.max?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n </ng-container>\n\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"showChartForm\"\n formGroupName=\"chart\"\n >\n <legend translate>Chart</legend>\n <div class=\"tight-grid\">\n <div\n class=\"col-xs-6 col-sm-4\"\n *ngIf=\"selectableChartRenderTypes?.length !== 0\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <ng-container *ngTemplateOutlet=\"displayHelpButton\"></ng-container>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"renderType\"\n >\n <option\n *ngFor=\"let type of CHART_RENDER_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n <div\n class=\"col-xs-6 col-sm-4\"\n *ngIf=\"selectableChartLineTypes?.length !== 0\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label\n for=\"chartType\"\n translate\n >\n Chart type\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"lineType\"\n >\n <option\n *ngFor=\"let type of CHART_LINE_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n <span></span>\n </div>\n </c8y-form-group>\n </div>\n <div\n class=\"col-xs-6 col-sm-4\"\n *ngIf=\"selectableAxisTypes?.length !== 0\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label\n for=\"yAxis\"\n translate\n >\n Y-axis\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"yAxisType\"\n >\n <option\n *ngFor=\"let type of AXIS_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n <span></span>\n </div>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"showOnlyDisplayForm\"\n formGroupName=\"display\"\n >\n <legend>\n <ng-container *ngTemplateOutlet=\"displayHelpButton\"></ng-container>\n </legend>\n <div class=\"tight-grid\">\n <div class=\"col-xs-6 col-sm-4\">\n <c8y-form-group class=\"form-group-sm\">\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"renderType\"\n >\n <option\n *ngFor=\"let type of CHART_RENDER_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n</div>\n\n<ng-template #displayHelpButton>\n <label>\n {{ 'Display' | translate }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ 'Value displayed when data is aggregated' | translate }}\"\n triggers=\"focus\"\n type=\"button\"\n placement=\"right\"\n ></button>\n </label>\n</ng-template>\n" }] }], ctorParameters: () => [{ type: DatapointAttributesFormValidationService }], propDecorators: { selectableChartRenderTypes: [{ type: Input }], selectableChartLineTypes: [{ type: Input }], selectableAxisTypes: [{ type: Input }], showTarget: [{ type: Input }], showRange: [{ type: Input }], showYellowRange: [{ type: Input }], showRedRange: [{ type: Input }], showChart: [{ type: Input }], showFormIfTemplateWasSelected: [{ type: Input }] } }); const DATAPOINT_LIBRARY_FRAGMENT = 'c8y_Kpi'; class DatapointLibraryService { constructor(inventory, appState, measurements, color) { this.inventory = inventory; this.appState = appState; this.measurements = measurements; this.color = color; this.appState.currentUser.pipe(filter(user => !user)).subscribe(() => { this.cache = undefined; }); } async getAllDatapointLibraryEntriesCached(forceCacheRenew = false) { if (forceCacheRenew) { this.cache = undefined; } if (!this.cache) { this.cache = this.getAllDatapointLibraryEntries(); } return this.cache; } async getFirstDatapointLibraryPage() { const filterObj = { currentPage: 1, pageSize: 50, fragmentType: DATAPOINT_LIBRARY_FRAGMENT, withTotalPages: true }; return (await this.inventory.list(filterObj)); } async getAllDatapointLibraryItemsCached() { if (!this.cache) { this.cache = this.getAllDatapointLibraryEntries(); } const res = await this.cache; return res.map(tmp => tmp[DATAPOINT_LIBRARY_FRAGMENT]); } async updateDatapoints(datapoints, skipUpdatingTarget = false) { if (!Array.isArray(datapoints)) { return datapoints; } const currentTargetsPromise = !skipUpdatingTarget ? this.getCurrentVersionOfTargetsFromDatapoints(datapoints) : Promise.resolve([]); const [currentTemplates, currentTargets] = await Promise.all([ this.getCurrentTemplatesFromDatapoints(datapoints), currentTargetsPromise ]); const currentTemplateVersions = currentTemplates .map(tmp => this.mapDatapointLibraryEntry(tmp)) .filter(tmp => !!tmp); for (const datapoint of datapoints) { const { fragment, series, __active, __target, color, label, __template } = datapoint; const foundCurrentTemplateVersion = currentTemplateVersions.find(tmp => tmp.__template === datapoint.__template); if (foundCurrentTemplateVersion) { Object.assign(datapoint, foundCurrentTemplateVersion); Object.assign(datapoint, { fragment, series, __active, __target, color, label, __template }); } const foundCurrentTarget = currentTargets.find(target => target.id === __target?.id); if (foundCurrentTarget) { const { id, name } = foundCurrentTarget; datapoint.__target = { id, name }; } } return datapoints; } async getDatapointsOfAsset(parentReference, ignoreDatapointTemplates, datapointTemplatesOnly = false) { const [kpiResponse, details] = await Promise.all([ (ignoreDatapointTemplates ? Promise.resolve(null) : this.inventory.assetKPIsList(parentReference, { pageSize: MAX_PAGE_SIZE })), this.inventory.getMeasurementsAndSeries(parentReference) ]); const kpis = kpiResponse && kpiResponse.data ? kpiResponse.data : []; const sortedDetails = sortBy(details, ['fragment', 'series']); return await this.combineFragmentSeriesTuplesWithDetails(sortedDetails, parentReference, kpis, datapointTemplatesOnly); } /** * Requests the last measurement with the given fragment and series to extract it's unit. * If the source attribute is provided, it will check the last measurement for this specific source. * @returns found unit or an empty string instead */ async guessUnitOfDatapoint(fragment, series, source) { const measurementfilter = { valueFragmentSeries: series, valueFragmentType: fragment, pageSize: 1, revert: true, dateFrom: '1970-01-01' }; if (source?.id) { measurementfilter.source = source?.id; } try { const { data: lastMeasurements } = await this.measurements.list(measurementfilter); const measurement = lastMeasurements[0]; if (measurement) { const pathToUnit = `${fragment}.${series}.unit`; const unit = get(measurement, pathToUnit); if (unit?.length && typeof unit === 'string') { return unit; } } } catch { // nothing to do } return ''; } async combineFragmentSeriesTuplesWithDetails(tuples, target, kpis, datapointTemplatesOnly = false) { const datapoints = tuples .map(tuple => { const foundDatapointLibraryEntry = kpis.find(kpi => kpi[DATAPOINT_LIBRARY_FRAGMENT] && kpi[DATAPOINT_LIBRARY_FRAGMENT].fragment === tuple.fragment && kpi[DATAPOINT_LIBRARY_FRAGMENT].series === tuple.series); if (!foundDatapointLibraryEntry && datapointTemplatesOnly) { return null; } const datapoint = this.mapDatapointLibraryEntry(foundDatapointLibraryEntry) || tuple; if (!datapoint.label) { datapoint.label = `${datapoint.fragment} → ${datapoint.series}`; } if (!datapoint.unit?.length) { datapoint.unit = ''; } datapoint.__target = target; return datapoint; }) .filter(Boolean); await this.assignColorToDatapoints(datapoints); return datapoints; } async assignColorToDatapoints(datapoints) { const