@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
243 lines (238 loc) • 17.9 kB
JavaScript
import * as i0 from '@angular/core';
import { Input, Optional, Component, ViewChild } from '@angular/core';
import { BehaviorSubject, Subject, takeUntil } from 'rxjs';
import { filter, map, distinctUntilChanged, switchMap, shareReplay, debounceTime } from 'rxjs/operators';
import * as i1 from '@angular/forms';
import { Validators, ReactiveFormsModule } from '@angular/forms';
import * as i2 from '@c8y/ngx-components/context-dashboard';
import * as i3 from '@c8y/ngx-components';
import { DynamicComponentAlert, RangeDisplayModule, MeasurementRealtimeService, CoreModule } from '@c8y/ngx-components';
import { DatapointSelectorModule } from '@c8y/ngx-components/datapoint-selector';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { gettext } from '@c8y/ngx-components/gettext';
import { defaultWidgetIds } from '@c8y/ngx-components/widgets/definitions';
import { NgIf, NgClass, AsyncPipe } from '@angular/common';
class LinearGaugeWidgetViewComponent {
constructor(measurementRealtime, dashboard, dynamicComponent) {
this.measurementRealtime = measurementRealtime;
this.dashboard = dashboard;
this.dynamicComponent = dynamicComponent;
this.activeDatapoint$ = new BehaviorSubject(null);
const activeDatapoint = this.activeDatapoint$.pipe(filter(dp => !!dp), map(dp => this.assignContextFromContextDashboard(dp)), distinctUntilChanged());
this.rangeDisplayConfig$ = activeDatapoint.pipe(switchMap(dp => this.getRangeDisplayConfig$(dp)), shareReplay({ refCount: true, bufferSize: 1 }));
this.rangeDisplayConfig$
.pipe(map(data => this.getErrorType(data)), distinctUntilChanged(), takeUntilDestroyed())
.subscribe(inRange => this.updateAlertStatus(inRange));
}
ngOnChanges() {
const activeDp = this.config.datapoints.find(dp => dp.__active);
this.activeDatapoint$.next(activeDp);
}
getRangeDisplayConfig$(dp) {
return this.measurementRealtime
.latestValueOfSpecificMeasurement$(dp.fragment, dp.series, dp.__target, 1, true)
.pipe(map(m => {
if (!m) {
return null;
}
const date = m.time;
const measurement = m[dp.fragment][dp.series];
return {
current: measurement.value,
fractionSize: this.config.fractionSize || 1,
max: this.ensureIsNumber(dp.max),
min: this.ensureIsNumber(dp.min),
redRangeMax: this.ensureIsNumber(dp.redRangeMax),
redRangeMin: this.ensureIsNumber(dp.redRangeMin),
target: this.ensureIsNumber(dp.target),
time: date,
yellowRangeMax: this.ensureIsNumber(dp.yellowRangeMax),
yellowRangeMin: this.ensureIsNumber(dp.yellowRangeMin),
unit: measurement.unit || dp.unit,
orientation: this.getOrientation()
};
}));
}
ensureIsNumber(value) {
if (typeof value === 'number') {
return value;
}
if (typeof value === 'string' && value.trim().length > 0) {
const parsedValue = parseFloat(value);
if (!isNaN(parsedValue)) {
return parsedValue;
}
}
return undefined;
}
assignContextFromContextDashboard(datapoint) {
if (!this.dashboard?.isDeviceTypeDashboard) {
return datapoint;
}
const context = this.dashboard?.context;
if (context?.id) {
const { name, id } = context;
datapoint.__target = { name, id };
}
return datapoint;
}
getOrientation() {
return this.dynamicComponent?.componentId === defaultWidgetIds.LINEAR_GAUGE
? 'horizontal'
: 'vertical';
}
getErrorType(data) {
if (!data) {
return 'NOT_FOUND';
}
if (!this.isInRange(data)) {
return 'OUT_OF_RANGE';
}
return 'NONE';
}
isInRange(data) {
if (!Number.isFinite(data.max) || !Number.isFinite(data.min)) {
// default range is 0-100
return data.current <= 100 && data.current >= 0;
}
return data.current <= data.max && data.current >= data.min;
}
updateAlertStatus(errorType) {
if (!this.alerts) {
return;
}
this.alerts.clear();
let msg;
if (errorType === 'OUT_OF_RANGE') {
msg = gettext('Current value out of defined range.');
}
else if (errorType === 'NOT_FOUND') {
msg = gettext('Configured data point not available on the selected device.');
}
if (!msg) {
return;
}
this.alerts.addAlerts(new DynamicComponentAlert({
type: 'warning',
text: msg
}));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: LinearGaugeWidgetViewComponent, deps: [{ token: i3.MeasurementRealtimeService }, { token: i2.ContextDashboardComponent, optional: true }, { token: i3.DynamicComponentComponent, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: LinearGaugeWidgetViewComponent, isStandalone: true, selector: "c8y-linear-gauge-widget-view", inputs: { config: "config" }, providers: [MeasurementRealtimeService], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"p-l-16 p-r-16 p-b-16 fit-h d-flex d-col flex-center\"\n *ngIf=\"rangeDisplayConfig$ | async as rangeDisplayConfig\"\n [ngClass]=\"{\n 'p-t-40 j-c-center': rangeDisplayConfig.orientation === 'horizontal',\n }\"\n>\n <c8y-range-display [config]=\"rangeDisplayConfig\" [ngClass]=\"{'flex-grow': rangeDisplayConfig.orientation == 'vertical'}\"></c8y-range-display>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: RangeDisplayModule }, { kind: "component", type: i3.RangeDisplayComponent, selector: "c8y-range-display", inputs: ["config", "display"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: LinearGaugeWidgetViewComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-linear-gauge-widget-view', providers: [MeasurementRealtimeService], standalone: true, imports: [NgIf, NgClass, AsyncPipe, RangeDisplayModule], template: "<div\n class=\"p-l-16 p-r-16 p-b-16 fit-h d-flex d-col flex-center\"\n *ngIf=\"rangeDisplayConfig$ | async as rangeDisplayConfig\"\n [ngClass]=\"{\n 'p-t-40 j-c-center': rangeDisplayConfig.orientation === 'horizontal',\n }\"\n>\n <c8y-range-display [config]=\"rangeDisplayConfig\" [ngClass]=\"{'flex-grow': rangeDisplayConfig.orientation == 'vertical'}\"></c8y-range-display>\n</div>\n" }]
}], ctorParameters: () => [{ type: i3.MeasurementRealtimeService }, { type: i2.ContextDashboardComponent, decorators: [{
type: Optional
}] }, { type: i3.DynamicComponentComponent, decorators: [{
type: Optional
}] }], propDecorators: { config: [{
type: Input
}] } });
class LinearGaugeWidgetConfigComponent {
set previewMapSet(template) {
if (template) {
this.widgetConfigService.setPreview(template);
return;
}
this.widgetConfigService.setPreview(null);
}
constructor(formBuilder, form, widgetConfig, widgetConfigService) {
this.formBuilder = formBuilder;
this.form = form;
this.widgetConfig = widgetConfig;
this.widgetConfigService = widgetConfigService;
this.datapointSelectionConfig = {};
this.defaultFormOptions = {
showRedRange: true,
showYellowRange: true,
showRange: true,
showTarget: true
};
this.limits = {
numberOfDecimalPlacesMax: 10,
numberOfDecimalPlacesMin: 0
};
this.destroy$ = new Subject();
}
onBeforeSave(config) {
if (this.formGroup.valid) {
Object.assign(config, this.formGroup.value);
return true;
}
return false;
}
ngOnChanges(changes) {
if (this.formGroup && changes.config) {
this.formGroup.controls.datapoints.patchValue(this.config.datapoints || []);
}
}
ngOnInit() {
if (this.widgetConfig.context?.id) {
this.datapointSelectionConfig.contextAsset = this.widgetConfig?.context;
}
// Initialize preview config
this.previewConfig = { ...this.config };
this.initForm();
if (this.config?.datapoints) {
this.formGroup.patchValue({ datapoints: this.config.datapoints });
}
if (typeof this.config?.fractionSize === 'number' && !Number.isNaN(this.config?.fractionSize)) {
this.formGroup.patchValue({ fractionSize: this.config.fractionSize });
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
initForm() {
this.formGroup = this.createForm();
this.form.form.addControl('config', this.formGroup);
this.formGroup.patchValue(this.config);
// Update preview on form changes
this.formGroup.valueChanges
.pipe(debounceTime(100), takeUntil(this.destroy$))
.subscribe(formValue => {
if (formValue.datapoints) {
this.previewActiveDatapoint = formValue.datapoints.find(dp => dp.__active);
}
this.previewConfig = { ...this.config, ...formValue };
Object.assign(this.config, formValue);
});
}
createForm() {
return this.formBuilder.group({
fractionSize: [
2,
[
Validators.required,
Validators.min(this.limits.numberOfDecimalPlacesMin),
Validators.max(this.limits.numberOfDecimalPlacesMax)
]
],
datapoints: this.formBuilder.control(new Array(), [Validators.required])
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: LinearGaugeWidgetConfigComponent, deps: [{ token: i1.FormBuilder }, { token: i1.NgForm }, { token: i2.WidgetConfigComponent }, { token: i2.WidgetConfigService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: LinearGaugeWidgetConfigComponent, isStandalone: true, selector: "c8y-linear-gauge-widget-config", inputs: { config: "config" }, viewQueries: [{ propertyName: "previewMapSet", first: true, predicate: ["linearGaugePreview"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<form\n class=\"no-card-context\"\n [formGroup]=\"formGroup\"\n>\n <fieldset class=\"c8y-fieldset\">\n <legend translate>Decimal places</legend>\n <c8y-form-group class=\"form-group-sm m-b-16\">\n <input\n class=\"form-control\"\n name=\"fractionSize\"\n type=\"number\"\n formControlName=\"fractionSize\"\n step=\"1\"\n />\n <c8y-messages [show]=\"formGroup.controls.fractionSize.errors\"></c8y-messages>\n </c8y-form-group>\n </fieldset>\n</form>\n\n<ng-template #linearGaugePreview>\n @if (formGroup && formGroup.value) {\n @if (formGroup.value.datapoints?.length > 0 && previewActiveDatapoint) {\n <div style=\"height: 300px\">\n @if (previewConfig) {\n <c8y-linear-gauge-widget-view [config]=\"previewConfig\"></c8y-linear-gauge-widget-view>\n }\n </div>\n } @else {\n <div class=\"col-md-6 d-col a-i-start j-c-center\">\n <c8y-ui-empty-state\n [icon]=\"'c8y-data-points'\"\n [title]=\"'No data points selected' | translate\"\n [subtitle]=\"'Select data point to render content' | translate\"\n [horizontal]=\"false\"\n data-cy=\"linear-gauge--empty-state-no-data-point-selected\"\n >\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/cockpit/widgets-collection/#linear-gauge\">\n user documentation</a\n >.\n </small>\n </p>\n </c8y-ui-empty-state>\n </div>\n }\n }\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: DatapointSelectorModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.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: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CoreModule }, { kind: "component", type: i3.EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: i3.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: i3.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: i3.MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage", "additionalMessages"] }, { kind: "directive", type: i3.RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "directive", type: i3.GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "component", type: i3.GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "component", type: LinearGaugeWidgetViewComponent, selector: "c8y-linear-gauge-widget-view", inputs: ["config"] }, { kind: "pipe", type: i3.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: LinearGaugeWidgetConfigComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-linear-gauge-widget-config', standalone: true, imports: [
DatapointSelectorModule,
ReactiveFormsModule,
CoreModule,
LinearGaugeWidgetViewComponent
], template: "<form\n class=\"no-card-context\"\n [formGroup]=\"formGroup\"\n>\n <fieldset class=\"c8y-fieldset\">\n <legend translate>Decimal places</legend>\n <c8y-form-group class=\"form-group-sm m-b-16\">\n <input\n class=\"form-control\"\n name=\"fractionSize\"\n type=\"number\"\n formControlName=\"fractionSize\"\n step=\"1\"\n />\n <c8y-messages [show]=\"formGroup.controls.fractionSize.errors\"></c8y-messages>\n </c8y-form-group>\n </fieldset>\n</form>\n\n<ng-template #linearGaugePreview>\n @if (formGroup && formGroup.value) {\n @if (formGroup.value.datapoints?.length > 0 && previewActiveDatapoint) {\n <div style=\"height: 300px\">\n @if (previewConfig) {\n <c8y-linear-gauge-widget-view [config]=\"previewConfig\"></c8y-linear-gauge-widget-view>\n }\n </div>\n } @else {\n <div class=\"col-md-6 d-col a-i-start j-c-center\">\n <c8y-ui-empty-state\n [icon]=\"'c8y-data-points'\"\n [title]=\"'No data points selected' | translate\"\n [subtitle]=\"'Select data point to render content' | translate\"\n [horizontal]=\"false\"\n data-cy=\"linear-gauge--empty-state-no-data-point-selected\"\n >\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/cockpit/widgets-collection/#linear-gauge\">\n user documentation</a\n >.\n </small>\n </p>\n </c8y-ui-empty-state>\n </div>\n }\n }\n</ng-template>\n" }]
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i1.NgForm }, { type: i2.WidgetConfigComponent }, { type: i2.WidgetConfigService }], propDecorators: { previewMapSet: [{
type: ViewChild,
args: ['linearGaugePreview']
}], config: [{
type: Input
}] } });
/**
* Generated bundle index. Do not edit.
*/
export { LinearGaugeWidgetConfigComponent, LinearGaugeWidgetViewComponent };
//# sourceMappingURL=c8y-ngx-components-widgets-implementations-linear-gauge.mjs.map