@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
526 lines (521 loc) • 150 kB
JavaScript
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