@c8y/ngx-components 1023.65.2 → 1023.66.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/context-dashboard/index.d.ts.map +1 -1
  2. package/fesm2022/c8y-ngx-components-context-dashboard.mjs +4 -0
  3. package/fesm2022/c8y-ngx-components-context-dashboard.mjs.map +1 -1
  4. package/fesm2022/c8y-ngx-components-global-context.mjs +38 -9
  5. package/fesm2022/c8y-ngx-components-global-context.mjs.map +1 -1
  6. package/fesm2022/c8y-ngx-components-widgets-definitions-kpi.mjs +11 -0
  7. package/fesm2022/c8y-ngx-components-widgets-definitions-kpi.mjs.map +1 -1
  8. package/fesm2022/c8y-ngx-components-widgets-implementations-alarms.mjs +2 -2
  9. package/fesm2022/c8y-ngx-components-widgets-implementations-alarms.mjs.map +1 -1
  10. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-list.mjs +2 -2
  11. package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-list.mjs.map +1 -1
  12. package/fesm2022/c8y-ngx-components-widgets-implementations-kpi.mjs +245 -142
  13. package/fesm2022/c8y-ngx-components-widgets-implementations-kpi.mjs.map +1 -1
  14. package/global-context/index.d.ts +4 -1
  15. package/global-context/index.d.ts.map +1 -1
  16. package/index.d.ts +2 -0
  17. package/index.d.ts.map +1 -1
  18. package/locales/de.po +6 -0
  19. package/locales/es.po +6 -0
  20. package/locales/fr.po +6 -0
  21. package/locales/ja_JP.po +6 -0
  22. package/locales/ko.po +6 -0
  23. package/locales/locales.pot +6 -0
  24. package/locales/nl.po +6 -0
  25. package/locales/pl.po +6 -0
  26. package/locales/pt_BR.po +6 -0
  27. package/locales/zh_CN.po +6 -0
  28. package/locales/zh_TW.po +6 -0
  29. package/package.json +1 -1
  30. package/widgets/definitions/kpi/index.d.ts +1 -0
  31. package/widgets/definitions/kpi/index.d.ts.map +1 -1
  32. package/widgets/implementations/alarms/index.d.ts +2 -0
  33. package/widgets/implementations/alarms/index.d.ts.map +1 -1
  34. package/widgets/implementations/kpi/index.d.ts +75 -52
  35. package/widgets/implementations/kpi/index.d.ts.map +1 -1
@@ -1,18 +1,21 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Input, Optional, Component, ViewChild } from '@angular/core';
2
+ import { signal, inject, DestroyRef, Input, ChangeDetectionStrategy, Component, ViewChild } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3
4
  import * as i1 from '@angular/forms';
4
- import { Validators, NgForm, ControlContainer } from '@angular/forms';
5
- import * as i3 from '@c8y/ngx-components';
6
- import { CoreModule, MeasurementRealtimeService, C8yValidators } from '@c8y/ngx-components';
5
+ import { Validators, NgForm, ControlContainer, ReactiveFormsModule } from '@angular/forms';
6
+ import { DashboardChildComponent, MeasurementRealtimeService, C8yTranslateDirective, IconDirective, LoadingComponent, EmptyStateComponent, DatePipe, C8yTranslatePipe, C8yValidators, FormGroupComponent, MessagesComponent, GuideDocsComponent, GuideHrefDirective } from '@c8y/ngx-components';
7
7
  import * as i2 from '@c8y/ngx-components/context-dashboard';
8
+ import { ContextDashboardComponent } from '@c8y/ngx-components/context-dashboard';
8
9
  import { DatapointSelectorModule } from '@c8y/ngx-components/datapoint-selector';
9
- import { NEVER, combineLatest, Subject } from 'rxjs';
10
- import { map, startWith, distinctUntilChanged, tap, filter, pairwise, debounceTime, takeUntil } from 'rxjs/operators';
11
- import * as i4 from '@c8y/ngx-components/icon-selector';
10
+ import * as i3 from '@c8y/ngx-components/icon-selector';
12
11
  import { IconSelectorModule } from '@c8y/ngx-components/icon-selector';
13
- import * as i5 from 'ngx-bootstrap/popover';
12
+ import * as i4 from 'ngx-bootstrap/popover';
14
13
  import { PopoverModule } from 'ngx-bootstrap/popover';
15
- import * as i3$1 from '@angular/common';
14
+ import { share, pairwise, map, startWith, distinctUntilChanged, filter, switchMap, tap, debounceTime } from 'rxjs/operators';
15
+ import { GLOBAL_CONTEXT_DISPLAY_MODE, PRESET_NAME, WidgetConfigMigrationService, REFRESH_OPTION, GlobalContextConnectorComponent, LocalControlsComponent } from '@c8y/ngx-components/global-context';
16
+ import { merge, isEqual } from 'lodash-es';
17
+ import { NEVER, BehaviorSubject, Subject, combineLatest, merge as merge$1 } from 'rxjs';
18
+ import { NgClass, NgStyle, AsyncPipe, DecimalPipe } from '@angular/common';
16
19
 
17
20
  var ColorClass;
18
21
  (function (ColorClass) {
@@ -20,102 +23,202 @@ var ColorClass;
20
23
  ColorClass["warning"] = "text-warning";
21
24
  ColorClass["unknown"] = "";
22
25
  })(ColorClass || (ColorClass = {}));
26
+
23
27
  class KpiWidgetViewComponent {
24
- constructor(measurementRealtime, dashboard) {
25
- this.measurementRealtime = measurementRealtime;
26
- this.dashboard = dashboard;
28
+ constructor() {
27
29
  this.config = { datapoints: [] };
30
+ this.displayMode = signal(GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD, ...(ngDevMode ? [{ debugName: "displayMode" }] : []));
31
+ this.contextConfig = signal({}, ...(ngDevMode ? [{ debugName: "contextConfig" }] : []));
32
+ this.isLinkedToGlobal = signal(undefined, ...(ngDevMode ? [{ debugName: "isLinkedToGlobal" }] : []));
33
+ this.widgetControls = signal(PRESET_NAME.KPI, ...(ngDevMode ? [{ debugName: "widgetControls" }] : []));
34
+ this.isHistoryMode = signal(false, ...(ngDevMode ? [{ debugName: "isHistoryMode" }] : []));
28
35
  this.state$ = NEVER;
29
- // used to differentiate between loading state and empty state
30
- this.noDataInitiallyInDB = false;
36
+ this.noDataInitiallyInDB = signal(false, ...(ngDevMode ? [{ debugName: "noDataInitiallyInDB" }] : []));
37
+ this.GLOBAL_CONTEXT_DISPLAY_MODE = GLOBAL_CONTEXT_DISPLAY_MODE;
38
+ this.dashboardChild = inject(DashboardChildComponent, { optional: true });
39
+ this.dashboard = inject(ContextDashboardComponent, { optional: true });
40
+ this.measurementRealtime = inject(MeasurementRealtimeService);
41
+ this.widgetConfigMigrationService = inject(WidgetConfigMigrationService);
42
+ this.destroyRef = inject(DestroyRef);
43
+ this.context$ = new BehaviorSubject(null);
44
+ this.refresh$ = new Subject();
45
+ this.lastDatapoint = null;
46
+ }
47
+ ngOnInit() {
48
+ this.config = merge(this.config, this.widgetConfigMigrationService.migrateWidgetConfig(this.config));
49
+ this.syncDisplayState();
50
+ this.buildStatePipeline();
51
+ if (!this.isDashboardMode()) {
52
+ this.emitContext(this.config);
53
+ }
31
54
  }
32
- async ngOnInit() {
33
- const datapoints = this.config.datapoints || [];
34
- const datapoint = datapoints.find(tmp => tmp?.__active);
35
- if (!datapoint) {
55
+ ngOnChanges(changes) {
56
+ const cfg = changes.config?.currentValue;
57
+ if (!cfg) {
36
58
  return;
37
59
  }
38
- this.state$ = this.setupObservable(datapoint);
39
- }
40
- setupObservable(datapoint) {
41
- this.assignContextFromContextDashboard(datapoint);
42
- const latestMeasurement$ = this.getLatestMeasurement$(datapoint);
43
- const lastTwoValues$ = this.getLastTwoValuesOfObservable$(latestMeasurement$);
44
- const previousValue$ = lastTwoValues$.pipe(map(([previousVal]) => previousVal), startWith(undefined));
45
- const unit$ = latestMeasurement$.pipe(map(latestMeasurementValue => datapoint.unit || latestMeasurementValue.unit || ''), startWith(''), distinctUntilChanged());
60
+ this.config = cfg;
61
+ this.syncDisplayState();
62
+ if (this.isOnRealDashboard()) {
63
+ return;
64
+ }
65
+ if (this.isDashboardPreviewWaitingForContext()) {
66
+ return;
67
+ }
68
+ this.emitContext(this.config, this.hasDatapointChanged());
69
+ }
70
+ onContextChange(event) {
71
+ this.contextConfig.set(event.context);
72
+ this.emitContext(event.context);
73
+ }
74
+ onRefresh() {
75
+ this.refresh$.next();
76
+ }
77
+ getDashboardChild() {
78
+ return this.dashboardChild;
79
+ }
80
+ setupObservable(datapoint, context) {
81
+ const isHistory = context.refreshOption === REFRESH_OPTION.HISTORY;
82
+ const isPaused = !isHistory && context.isAutoRefreshEnabled === false;
83
+ this.isHistoryMode.set(isHistory);
84
+ this.noDataInitiallyInDB.set(false);
85
+ let source$;
86
+ if (isPaused) {
87
+ source$ = this.getHistoryMeasurement$(datapoint, {});
88
+ }
89
+ else if (isHistory) {
90
+ source$ = this.getHistoryMeasurement$(datapoint, context);
91
+ }
92
+ else {
93
+ source$ = this.getLiveMeasurement$(datapoint);
94
+ }
95
+ const shared$ = source$.pipe(share());
96
+ const lastTwo$ = shared$.pipe(pairwise());
46
97
  return combineLatest([
47
- latestMeasurement$,
48
- previousValue$,
49
- this.getTrendOfLatestMeasurements$(lastTwoValues$),
50
- unit$,
51
- this.getColorClass$(latestMeasurement$, datapoint)
52
- ]).pipe(map(([latestMeasurement, previousValue, trend, unit, colorClass]) => {
53
- return {
54
- latestMeasurement,
55
- previousValue,
56
- trend,
57
- unit,
58
- colorClass
59
- };
60
- }));
61
- }
62
- getLatestMeasurement$(datapoint) {
98
+ shared$,
99
+ lastTwo$.pipe(map(([prev]) => prev), startWith(undefined)),
100
+ this.getTrend$(lastTwo$),
101
+ shared$.pipe(map(m => datapoint.unit || m.unit || ''), startWith(''), distinctUntilChanged()),
102
+ this.getColorClass$(shared$, datapoint)
103
+ ]).pipe(map(([latestMeasurement, previousValue, trend, unit, colorClass]) => ({
104
+ latestMeasurement,
105
+ previousValue,
106
+ trend,
107
+ unit,
108
+ colorClass
109
+ })));
110
+ }
111
+ buildStatePipeline() {
112
+ const readState = () => ({ ctx: this.context$.value, dp: this.findActiveDatapoint() });
113
+ this.state$ = merge$1(this.context$.pipe(map(readState), distinctUntilChanged(isEqual)), this.refresh$.pipe(map(readState))).pipe(filter((s) => !!s.ctx && !!s.dp), switchMap(({ ctx, dp }) => {
114
+ this.assignContextFromContextDashboard(dp);
115
+ return this.setupObservable(dp, ctx).pipe(startWith(null));
116
+ }), takeUntilDestroyed(this.destroyRef));
117
+ }
118
+ syncDisplayState() {
119
+ const newMode = this.resolveDisplayMode();
120
+ if (this.displayMode() !== newMode) {
121
+ this.displayMode.set(newMode);
122
+ }
123
+ const newCtx = this.extractContext(this.config);
124
+ if (!isEqual(this.contextConfig(), newCtx)) {
125
+ this.contextConfig.set(newCtx);
126
+ }
127
+ }
128
+ emitContext(source, force = false) {
129
+ const ctx = this.extractContext(source);
130
+ if (force || !isEqual(this.context$.value, ctx)) {
131
+ this.context$.next(ctx);
132
+ }
133
+ }
134
+ isDashboardMode() {
135
+ return this.resolveDisplayMode() === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD;
136
+ }
137
+ isOnRealDashboard() {
138
+ return this.isDashboardMode() && !!this.dashboardChild;
139
+ }
140
+ isDashboardPreviewWaitingForContext() {
141
+ return this.isDashboardMode() && !this.config.isGlobalContextReady;
142
+ }
143
+ resolveDisplayMode() {
144
+ return (this.config?.displayMode || GLOBAL_CONTEXT_DISPLAY_MODE.CONFIG);
145
+ }
146
+ extractContext(source) {
147
+ return {
148
+ dateTimeContext: source.dateTimeContext,
149
+ isAutoRefreshEnabled: source.isAutoRefreshEnabled,
150
+ refreshInterval: source.refreshInterval,
151
+ refreshOption: source.refreshOption
152
+ };
153
+ }
154
+ findActiveDatapoint() {
155
+ return this.config?.datapoints?.find(dp => dp?.__active);
156
+ }
157
+ hasDatapointChanged() {
158
+ const dp = this.findActiveDatapoint();
159
+ if (!dp) {
160
+ return false;
161
+ }
162
+ const prev = this.lastDatapoint;
163
+ this.lastDatapoint = dp;
164
+ return (!prev ||
165
+ dp.fragment !== prev.fragment ||
166
+ dp.series !== prev.series ||
167
+ dp.__target?.id !== prev.__target?.id ||
168
+ dp.unit !== prev.unit ||
169
+ dp.redRangeMin !== prev.redRangeMin ||
170
+ dp.redRangeMax !== prev.redRangeMax ||
171
+ dp.yellowRangeMin !== prev.yellowRangeMin ||
172
+ dp.yellowRangeMax !== prev.yellowRangeMax);
173
+ }
174
+ getLiveMeasurement$(datapoint) {
63
175
  return this.measurementRealtime
64
- .latestValueOfSpecificMeasurement$(datapoint.fragment, datapoint.series, datapoint.__target,
65
- // we only need the last two values in case we want to show a trend
66
- this.config.showTrend ? 2 : 1,
67
- // null will be emitted in case no measurement was found initially
68
- true)
69
- .pipe(tap(measurement => {
70
- if (!measurement) {
71
- this.noDataInitiallyInDB = true;
72
- }
73
- }), filter(measurement => !!measurement), map(measurement => {
74
- return {
75
- unit: measurement[datapoint.fragment][datapoint.series].unit,
76
- value: measurement[datapoint.fragment][datapoint.series].value,
77
- date: measurement.time
78
- };
79
- }));
80
- }
81
- getColorClass$(measurementAndDatapointCombination$, datapoint) {
82
- return measurementAndDatapointCombination$.pipe(map(latestMeasurementValue => {
83
- if (this.inRangeOf(datapoint, latestMeasurementValue.value, 'redRangeMin', 'redRangeMax')) {
176
+ .latestValueOfSpecificMeasurement$(datapoint.fragment, datapoint.series, datapoint.__target, this.config.showTrend ? 2 : 1, true)
177
+ .pipe(tap(m => {
178
+ if (!m)
179
+ this.noDataInitiallyInDB.set(true);
180
+ }), filter(m => !!m), map(m => this.toMeasurementValue(m, datapoint)));
181
+ }
182
+ getHistoryMeasurement$(datapoint, context) {
183
+ return this.measurementRealtime
184
+ .lastMeasurement$(datapoint.fragment, datapoint.series, datapoint.__target, 1, true, context.dateTimeContext?.dateFrom, context.dateTimeContext?.dateTo)
185
+ .pipe(tap(m => {
186
+ if (!m)
187
+ this.noDataInitiallyInDB.set(true);
188
+ }), filter(m => !!m), map(m => this.toMeasurementValue(m, datapoint)));
189
+ }
190
+ toMeasurementValue(m, dp) {
191
+ return {
192
+ unit: m[dp.fragment][dp.series].unit,
193
+ value: m[dp.fragment][dp.series].value,
194
+ date: m.time
195
+ };
196
+ }
197
+ getColorClass$(measurement$, datapoint) {
198
+ return measurement$.pipe(map(m => {
199
+ if (this.inRange(datapoint, m.value, 'redRangeMin', 'redRangeMax')) {
84
200
  return ColorClass.danger;
85
201
  }
86
- if (this.inRangeOf(datapoint, latestMeasurementValue.value, 'yellowRangeMin', 'yellowRangeMax')) {
202
+ if (this.inRange(datapoint, m.value, 'yellowRangeMin', 'yellowRangeMax')) {
87
203
  return ColorClass.warning;
88
204
  }
89
205
  return ColorClass.unknown;
90
206
  }), startWith(ColorClass.unknown), distinctUntilChanged());
91
207
  }
92
- getLastTwoValuesOfObservable$(input$) {
93
- return input$.pipe(pairwise());
94
- }
95
- getTrendOfLatestMeasurements$(latestMeasurement$) {
96
- return latestMeasurement$.pipe(map(res => {
97
- if (res.length === 2) {
98
- const oldValue = res[0].value;
99
- const newValue = res[1].value;
100
- if (oldValue < newValue) {
101
- return '45deg';
102
- }
103
- if (oldValue > newValue) {
104
- return '135deg';
105
- }
106
- }
208
+ getTrend$(lastTwo$) {
209
+ return lastTwo$.pipe(map(([prev, curr]) => {
210
+ if (prev.value < curr.value)
211
+ return '45deg';
212
+ if (prev.value > curr.value)
213
+ return '135deg';
107
214
  return '90deg';
108
215
  }), startWith('90deg'), distinctUntilChanged());
109
216
  }
110
- inRangeOf(datapoint, measurementValue, minAttribute, maxAttribute) {
111
- if (typeof datapoint[minAttribute] === 'number' &&
112
- typeof datapoint[maxAttribute] === 'number') {
113
- if (measurementValue >= datapoint[minAttribute] &&
114
- measurementValue < datapoint[maxAttribute]) {
115
- return true;
116
- }
117
- }
118
- return false;
217
+ inRange(dp, value, minKey, maxKey) {
218
+ return (typeof dp[minKey] === 'number' &&
219
+ typeof dp[maxKey] === 'number' &&
220
+ value >= dp[minKey] &&
221
+ value < dp[maxKey]);
119
222
  }
120
223
  assignContextFromContextDashboard(datapoint) {
121
224
  if (!this.dashboard?.isDeviceTypeDashboard) {
@@ -123,42 +226,46 @@ class KpiWidgetViewComponent {
123
226
  }
124
227
  const context = this.dashboard?.context;
125
228
  if (context?.id) {
126
- const { name, id } = context;
127
- datapoint.__target = { name, id };
229
+ datapoint.__target = { name: context.name, id: context.id };
128
230
  }
129
231
  }
130
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: KpiWidgetViewComponent, deps: [{ token: i3.MeasurementRealtimeService }, { token: i2.ContextDashboardComponent, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
131
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: KpiWidgetViewComponent, isStandalone: true, selector: "c8y-kpi-widget-view", inputs: { config: "config" }, providers: [MeasurementRealtimeService], ngImport: i0, template: "<div\n class=\"kpi-widget__container d-flex d-col fit-h fit-w a-i-center j-c-center\"\n *ngIf=\"state$ | async as lastState; else noMeasurementFound\"\n>\n <div class=\"d-flex a-i-center j-c-center fit-w\">\n <div\n class=\"m-r-16 flex-no-shrink text-muted\"\n [ngClass]=\"lastState.colorClass\"\n *ngIf=\"config.icon && config.showIcon\"\n >\n <i class=\"icon-32\" [c8yIcon]=\"config.icon\"></i>\n </div>\n <div class=\"text-truncate\">\n <span\n class=\"text-truncate text-medium\"\n [ngClass]=\"lastState.colorClass\"\n [ngStyle]=\"{ 'font-size': (config.fontSize || '36') + 'px' }\"\n title=\"{{\n lastState.colorClass === 'text-danger'\n ? ('Within red range:' | translate)\n : lastState.colorClass === 'text-warning'\n ? ('Within yellow range:' | translate)\n : ''\n }} {{\n lastState.latestMeasurement.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')\n }} {{ lastState.unit || '' }}\"\n >\n {{\n lastState.latestMeasurement.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')\n }}\n <small class=\"text-regular\">{{ lastState.unit || '' }}</small>\n </span>\n </div>\n <div\n class=\"dot dot-info dot-30 m-l-16 flex-no-shrink\"\n *ngIf=\"config?.showTrend && lastState.previousValue as previousValue\"\n >\n <i\n class=\"icon-20\"\n [title]=\"\n ('Previous value' | translate) +\n ': ' +\n (previousValue.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')) +\n ' (' +\n (previousValue.date | date: 'medium') +\n ')'\n \"\n c8yIcon=\"arrow-dotted-up\"\n [ngStyle]=\"{ transform: 'rotate(' + lastState.trend + ')' }\"\n ></i>\n </div>\n </div>\n <div class=\"d-flex j-c-center\">\n <p *ngIf=\"config?.showTimestamp\" class=\"icon-flex text-center text-muted small\">\n <i c8yIcon=\"calendar\"></i>\n {{ lastState.latestMeasurement.date | date: 'medium' }}\n </p>\n </div>\n</div>\n\n<ng-template #noMeasurementFound>\n <div class=\"d-flex fit-h fit-w j-c-center a-i-center\">\n <c8y-ui-empty-state\n *ngIf=\"noDataInitiallyInDB\"\n class=\"fit-w\"\n [icon]=\"'line-chart'\"\n [title]=\"'No measurement to display.' | translate\"\n [subtitle]=\"'Waiting for measurements to be created.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n <c8y-loading *ngIf=\"!noDataInitiallyInDB\"></c8y-loading>\n </div>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CoreModule }, { kind: "component", type: i3.EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: i3.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i3$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i3.LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "pipe", type: i3.C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.DecimalPipe, name: "number" }, { kind: "pipe", type: i3$1.DatePipe, name: "date" }] }); }
232
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: KpiWidgetViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
233
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: KpiWidgetViewComponent, isStandalone: true, selector: "c8y-kpi-widget-view", inputs: { config: "config" }, host: { classAttribute: "d-col fit-h" }, providers: [MeasurementRealtimeService], usesOnChanges: true, ngImport: i0, template: "@if (displayMode() === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD && getDashboardChild()) {\n <c8y-global-context-connector\n [controls]=\"widgetControls()\"\n [config]=\"contextConfig()\"\n [dashboardChild]=\"getDashboardChild()\"\n [linked]=\"isLinkedToGlobal()\"\n [emitRefresh]=\"false\"\n (configChange)=\"onContextChange($event)\"\n (refresh)=\"onRefresh()\"\n ></c8y-global-context-connector>\n} @else if (displayMode() !== GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {\n <c8y-local-controls\n [controls]=\"widgetControls()\"\n [displayMode]=\"displayMode()\"\n [config]=\"contextConfig()\"\n [emitRefresh]=\"false\"\n (configChange)=\"onContextChange($event)\"\n (refresh)=\"onRefresh()\"\n ></c8y-local-controls>\n}\n\n@if (state$ | async; as lastState) {\n <div class=\"kpi-widget__container d-flex d-col flex-grow fit-w a-i-center j-c-center\">\n <div class=\"d-flex a-i-center j-c-center fit-w\">\n @if (config.icon && config.showIcon) {\n <div\n class=\"m-r-16 flex-no-shrink text-muted\"\n [ngClass]=\"lastState.colorClass\"\n >\n <i\n class=\"icon-32\"\n [c8yIcon]=\"config.icon\"\n ></i>\n </div>\n }\n <div class=\"text-truncate\">\n <span\n class=\"text-truncate text-medium\"\n [ngStyle]=\"{ 'font-size': (config.fontSize || '36') + 'px' }\"\n title=\"{{\n lastState.colorClass === 'text-danger'\n ? ('Within red range:' | translate)\n : lastState.colorClass === 'text-warning'\n ? ('Within yellow range:' | translate)\n : ''\n }} {{\n lastState.latestMeasurement.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')\n }} {{ lastState.unit || '' }}\"\n [ngClass]=\"lastState.colorClass\"\n >\n {{\n lastState.latestMeasurement.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')\n }}\n <small class=\"text-regular\">{{ lastState.unit || '' }}</small>\n </span>\n </div>\n @if (config?.showTrend && lastState.previousValue; as previousValue) {\n <div class=\"dot dot-info dot-30 m-l-16 flex-no-shrink\">\n <i\n class=\"icon-20\"\n c8yIcon=\"arrow-dotted-up\"\n [ngStyle]=\"{ transform: 'rotate(' + lastState.trend + ')' }\"\n [title]=\"\n ('Previous value' | translate) +\n ': ' +\n (previousValue.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')) +\n ' (' +\n (previousValue.date | c8yDate: 'medium') +\n ')'\n \"\n ></i>\n </div>\n }\n </div>\n <div class=\"d-flex d-col a-i-center\">\n @if (config?.showTimestamp) {\n <p class=\"icon-flex text-center text-muted small m-b-0\">\n <i c8yIcon=\"calendar\"></i>\n {{ lastState.latestMeasurement.date | c8yDate: 'medium' }}\n </p>\n }\n @if (isHistoryMode()) {\n <p class=\"text-center text-muted small m-b-0\">\n <span translate>Last measurement in selected time range</span>\n </p>\n }\n </div>\n </div>\n} @else {\n <div class=\"d-flex flex-grow fit-w j-c-center a-i-center\">\n @let noDataSubtitleLive = 'Waiting for measurements to be created.' | translate;\n @let noDataSubtitleHistory = 'No data available for the selected time period.' | translate;\n @if (noDataInitiallyInDB()) {\n <c8y-ui-empty-state\n [icon]=\"'line-chart'\"\n [title]=\"'No measurement to display.' | translate\"\n [subtitle]=\"isHistoryMode() ? noDataSubtitleHistory : noDataSubtitleLive\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n } @else {\n <c8y-loading></c8y-loading>\n }\n </div>\n}\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "component", type: GlobalContextConnectorComponent, selector: "c8y-global-context-connector", inputs: ["controls", "config", "isLoading", "dashboardChild", "linked", "emitRefresh"], outputs: ["configChange", "refresh", "linkedChange"] }, { kind: "component", type: LocalControlsComponent, selector: "c8y-local-controls", inputs: ["controls", "displayMode", "config", "isLoading", "disabled", "emitRefresh"], outputs: ["configChange", "refresh"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
132
234
  }
133
235
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: KpiWidgetViewComponent, decorators: [{
134
236
  type: Component,
135
- args: [{ selector: 'c8y-kpi-widget-view', standalone: true, imports: [CoreModule], providers: [MeasurementRealtimeService], template: "<div\n class=\"kpi-widget__container d-flex d-col fit-h fit-w a-i-center j-c-center\"\n *ngIf=\"state$ | async as lastState; else noMeasurementFound\"\n>\n <div class=\"d-flex a-i-center j-c-center fit-w\">\n <div\n class=\"m-r-16 flex-no-shrink text-muted\"\n [ngClass]=\"lastState.colorClass\"\n *ngIf=\"config.icon && config.showIcon\"\n >\n <i class=\"icon-32\" [c8yIcon]=\"config.icon\"></i>\n </div>\n <div class=\"text-truncate\">\n <span\n class=\"text-truncate text-medium\"\n [ngClass]=\"lastState.colorClass\"\n [ngStyle]=\"{ 'font-size': (config.fontSize || '36') + 'px' }\"\n title=\"{{\n lastState.colorClass === 'text-danger'\n ? ('Within red range:' | translate)\n : lastState.colorClass === 'text-warning'\n ? ('Within yellow range:' | translate)\n : ''\n }} {{\n lastState.latestMeasurement.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')\n }} {{ lastState.unit || '' }}\"\n >\n {{\n lastState.latestMeasurement.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')\n }}\n <small class=\"text-regular\">{{ lastState.unit || '' }}</small>\n </span>\n </div>\n <div\n class=\"dot dot-info dot-30 m-l-16 flex-no-shrink\"\n *ngIf=\"config?.showTrend && lastState.previousValue as previousValue\"\n >\n <i\n class=\"icon-20\"\n [title]=\"\n ('Previous value' | translate) +\n ': ' +\n (previousValue.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')) +\n ' (' +\n (previousValue.date | date: 'medium') +\n ')'\n \"\n c8yIcon=\"arrow-dotted-up\"\n [ngStyle]=\"{ transform: 'rotate(' + lastState.trend + ')' }\"\n ></i>\n </div>\n </div>\n <div class=\"d-flex j-c-center\">\n <p *ngIf=\"config?.showTimestamp\" class=\"icon-flex text-center text-muted small\">\n <i c8yIcon=\"calendar\"></i>\n {{ lastState.latestMeasurement.date | date: 'medium' }}\n </p>\n </div>\n</div>\n\n<ng-template #noMeasurementFound>\n <div class=\"d-flex fit-h fit-w j-c-center a-i-center\">\n <c8y-ui-empty-state\n *ngIf=\"noDataInitiallyInDB\"\n class=\"fit-w\"\n [icon]=\"'line-chart'\"\n [title]=\"'No measurement to display.' | translate\"\n [subtitle]=\"'Waiting for measurements to be created.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n <c8y-loading *ngIf=\"!noDataInitiallyInDB\"></c8y-loading>\n </div>\n</ng-template>\n" }]
136
- }], ctorParameters: () => [{ type: i3.MeasurementRealtimeService }, { type: i2.ContextDashboardComponent, decorators: [{
137
- type: Optional
138
- }] }], propDecorators: { config: [{
237
+ args: [{ selector: 'c8y-kpi-widget-view', standalone: true, imports: [
238
+ AsyncPipe,
239
+ DatePipe,
240
+ DecimalPipe,
241
+ NgClass,
242
+ NgStyle,
243
+ C8yTranslatePipe,
244
+ C8yTranslateDirective,
245
+ IconDirective,
246
+ LoadingComponent,
247
+ EmptyStateComponent,
248
+ GlobalContextConnectorComponent,
249
+ LocalControlsComponent
250
+ ], providers: [MeasurementRealtimeService], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'd-col fit-h' }, template: "@if (displayMode() === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD && getDashboardChild()) {\n <c8y-global-context-connector\n [controls]=\"widgetControls()\"\n [config]=\"contextConfig()\"\n [dashboardChild]=\"getDashboardChild()\"\n [linked]=\"isLinkedToGlobal()\"\n [emitRefresh]=\"false\"\n (configChange)=\"onContextChange($event)\"\n (refresh)=\"onRefresh()\"\n ></c8y-global-context-connector>\n} @else if (displayMode() !== GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {\n <c8y-local-controls\n [controls]=\"widgetControls()\"\n [displayMode]=\"displayMode()\"\n [config]=\"contextConfig()\"\n [emitRefresh]=\"false\"\n (configChange)=\"onContextChange($event)\"\n (refresh)=\"onRefresh()\"\n ></c8y-local-controls>\n}\n\n@if (state$ | async; as lastState) {\n <div class=\"kpi-widget__container d-flex d-col flex-grow fit-w a-i-center j-c-center\">\n <div class=\"d-flex a-i-center j-c-center fit-w\">\n @if (config.icon && config.showIcon) {\n <div\n class=\"m-r-16 flex-no-shrink text-muted\"\n [ngClass]=\"lastState.colorClass\"\n >\n <i\n class=\"icon-32\"\n [c8yIcon]=\"config.icon\"\n ></i>\n </div>\n }\n <div class=\"text-truncate\">\n <span\n class=\"text-truncate text-medium\"\n [ngStyle]=\"{ 'font-size': (config.fontSize || '36') + 'px' }\"\n title=\"{{\n lastState.colorClass === 'text-danger'\n ? ('Within red range:' | translate)\n : lastState.colorClass === 'text-warning'\n ? ('Within yellow range:' | translate)\n : ''\n }} {{\n lastState.latestMeasurement.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')\n }} {{ lastState.unit || '' }}\"\n [ngClass]=\"lastState.colorClass\"\n >\n {{\n lastState.latestMeasurement.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')\n }}\n <small class=\"text-regular\">{{ lastState.unit || '' }}</small>\n </span>\n </div>\n @if (config?.showTrend && lastState.previousValue; as previousValue) {\n <div class=\"dot dot-info dot-30 m-l-16 flex-no-shrink\">\n <i\n class=\"icon-20\"\n c8yIcon=\"arrow-dotted-up\"\n [ngStyle]=\"{ transform: 'rotate(' + lastState.trend + ')' }\"\n [title]=\"\n ('Previous value' | translate) +\n ': ' +\n (previousValue.value\n | number\n : '1.' +\n (config.numberOfDecimalPlaces || '0') +\n '-' +\n (config.numberOfDecimalPlaces || '0')) +\n ' (' +\n (previousValue.date | c8yDate: 'medium') +\n ')'\n \"\n ></i>\n </div>\n }\n </div>\n <div class=\"d-flex d-col a-i-center\">\n @if (config?.showTimestamp) {\n <p class=\"icon-flex text-center text-muted small m-b-0\">\n <i c8yIcon=\"calendar\"></i>\n {{ lastState.latestMeasurement.date | c8yDate: 'medium' }}\n </p>\n }\n @if (isHistoryMode()) {\n <p class=\"text-center text-muted small m-b-0\">\n <span translate>Last measurement in selected time range</span>\n </p>\n }\n </div>\n </div>\n} @else {\n <div class=\"d-flex flex-grow fit-w j-c-center a-i-center\">\n @let noDataSubtitleLive = 'Waiting for measurements to be created.' | translate;\n @let noDataSubtitleHistory = 'No data available for the selected time period.' | translate;\n @if (noDataInitiallyInDB()) {\n <c8y-ui-empty-state\n [icon]=\"'line-chart'\"\n [title]=\"'No measurement to display.' | translate\"\n [subtitle]=\"isHistoryMode() ? noDataSubtitleHistory : noDataSubtitleLive\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n } @else {\n <c8y-loading></c8y-loading>\n }\n </div>\n}\n" }]
251
+ }], propDecorators: { config: [{
139
252
  type: Input
140
253
  }] } });
141
254
 
142
255
  function exactlyASingleDatapointActive() {
143
256
  return (control) => {
144
257
  const datapoints = control.value;
145
- if (!datapoints || !datapoints.length) {
146
- return null;
147
- }
148
- const activeDatapoints = datapoints.filter(datapoint => datapoint.__active);
149
- if (activeDatapoints.length === 1) {
258
+ if (!datapoints?.length) {
150
259
  return null;
151
260
  }
152
- return { exactlyOneDatapointNeedsToBeActive: true };
261
+ return datapoints.filter(dp => dp.__active).length === 1
262
+ ? null
263
+ : { exactlyOneDatapointNeedsToBeActive: true };
153
264
  };
154
265
  }
155
266
  class KpiWidgetConfigComponent {
156
267
  set previewMapSet(template) {
157
- if (template) {
158
- this.widgetConfigService.setPreview(template);
159
- return;
160
- }
161
- this.widgetConfigService.setPreview(null);
268
+ this.widgetConfigService.setPreview(template || null);
162
269
  }
163
270
  constructor(formBuilder, form, widgetConfig, widgetConfigService) {
164
271
  this.formBuilder = formBuilder;
@@ -170,8 +277,7 @@ class KpiWidgetConfigComponent {
170
277
  showRedRange: true,
171
278
  showYellowRange: true
172
279
  };
173
- this.availableIcons = [];
174
- this.destroy$ = new Subject();
280
+ this.destroyRef = inject(DestroyRef);
175
281
  this.limits = {
176
282
  fontSizeMax: 72,
177
283
  fontSizeMin: 18,
@@ -191,60 +297,50 @@ class KpiWidgetConfigComponent {
191
297
  this.formGroup.controls.datapoints.patchValue(this.config.datapoints || []);
192
298
  }
193
299
  }
194
- async ngOnInit() {
300
+ ngOnInit() {
195
301
  if (this.widgetConfig.context?.id) {
196
- this.datapointSelectionConfig.contextAsset = this.widgetConfig?.context;
302
+ this.datapointSelectionConfig.contextAsset = this.widgetConfig.context;
197
303
  }
198
304
  this.previewConfig = { ...this.config };
199
305
  this.initForm();
200
306
  if (this.config?.datapoints) {
201
307
  this.formGroup.patchValue({ datapoints: this.config.datapoints });
202
- if (this.config.datapoints.length > 0) {
203
- this.previewActiveDatapoint = this.config.datapoints.find(dp => dp.__active);
204
- }
308
+ this.previewActiveDatapoint = this.config.datapoints.find(dp => dp.__active);
205
309
  }
206
- }
207
- ngOnDestroy() {
208
- this.destroy$.next();
209
- this.destroy$.complete();
210
- }
211
- applyLimits(value, min, max) {
212
- if (value < min) {
213
- return min;
214
- }
215
- if (value > max) {
216
- return max;
217
- }
218
- return value;
310
+ this.widgetConfigService.currentConfig$
311
+ .pipe(filter(c => !!c), takeUntilDestroyed(this.destroyRef))
312
+ .subscribe(c => {
313
+ this.previewConfig = { ...this.previewConfig, ...c, ...this.formGroup.value };
314
+ });
219
315
  }
220
316
  initForm() {
221
317
  this.formGroup = this.createForm();
222
318
  this.form.form.addControl('config', this.formGroup);
223
319
  this.formGroup.patchValue(this.config);
224
320
  this.formGroup.valueChanges
225
- .pipe(debounceTime(100), takeUntil(this.destroy$))
321
+ .pipe(debounceTime(100), takeUntilDestroyed(this.destroyRef))
226
322
  .subscribe(formValue => {
227
323
  if (formValue.datapoints) {
228
324
  this.previewActiveDatapoint = formValue.datapoints.find(dp => dp.__active);
229
325
  }
230
- const previewLimitedValue = this.createPreviewLimitedValue(formValue);
231
326
  if (this.formGroup.valid) {
232
- // don't apply invalid values to preview to avoid errors
233
- // e.g. invalid value of numberOfDecimalPlaces provided to number pipe
234
- this.previewConfig = { ...this.config, ...previewLimitedValue };
327
+ this.previewConfig = { ...this.config, ...this.applyLimitsToPreview(formValue) };
235
328
  }
236
329
  Object.assign(this.config, formValue);
237
330
  });
238
331
  }
239
- createPreviewLimitedValue(formValue) {
240
- const previewValue = { ...formValue };
241
- if (previewValue.numberOfDecimalPlaces !== undefined) {
242
- previewValue.numberOfDecimalPlaces = this.applyLimits(previewValue.numberOfDecimalPlaces, this.limits.numberOfDecimalPlacesMin, this.limits.numberOfDecimalPlacesMax);
332
+ applyLimitsToPreview(formValue) {
333
+ const result = { ...formValue };
334
+ if (result.numberOfDecimalPlaces !== undefined) {
335
+ result.numberOfDecimalPlaces = this.clamp(result.numberOfDecimalPlaces, this.limits.numberOfDecimalPlacesMin, this.limits.numberOfDecimalPlacesMax);
243
336
  }
244
- if (previewValue.fontSize !== undefined) {
245
- previewValue.fontSize = this.applyLimits(previewValue.fontSize, this.limits.fontSizeMin, this.limits.fontSizeMax);
337
+ if (result.fontSize !== undefined) {
338
+ result.fontSize = this.clamp(result.fontSize, this.limits.fontSizeMin, this.limits.fontSizeMax);
246
339
  }
247
- return previewValue;
340
+ return result;
341
+ }
342
+ clamp(value, min, max) {
343
+ return Math.max(min, Math.min(max, value));
248
344
  }
249
345
  createForm() {
250
346
  return this.formBuilder.group({
@@ -277,17 +373,24 @@ class KpiWidgetConfigComponent {
277
373
  });
278
374
  }
279
375
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: KpiWidgetConfigComponent, deps: [{ token: i1.FormBuilder }, { token: i1.NgForm }, { token: i2.WidgetConfigComponent }, { token: i2.WidgetConfigService }], target: i0.ɵɵFactoryTarget.Component }); }
280
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: KpiWidgetConfigComponent, isStandalone: true, selector: "c8y-kpi-widget-config", inputs: { config: "config" }, viewQueries: [{ propertyName: "previewMapSet", first: true, predicate: ["kpiPreview"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<form [formGroup]=\"formGroup\">\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Layout' | translate }}</legend>\n <div class=\"d-flex a-i-center gap-8\">\n <div class=\"form-group form-group-sm m-b-16\">\n <label translate>Icon</label>\n <c8y-icon-selector-wrapper\n [iconSize]=\"16\"\n name=\"icon\"\n formControlName=\"icon\"\n ></c8y-icon-selector-wrapper>\n </div>\n <c8y-form-group class=\"form-group-sm m-b-16 flex-grow\">\n <label\n [title]=\"'Font size of measurement value (px)' | translate\"\n translate\n >\n Font size of measurement value (px)\n </label>\n <input\n class=\"form-control\"\n name=\"fontSize\"\n type=\"number\"\n formControlName=\"fontSize\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 36 }\"\n />\n <c8y-messages\n [show]=\"formGroup.controls?.fontSize?.touched && formGroup?.controls?.fontSize?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Display' | translate }}</legend>\n <div class=\"d-flex gap-16 flex-wrap\">\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show timestamp' | translate\"\n >\n <input\n name=\"showTimestamp\"\n type=\"checkbox\"\n formControlName=\"showTimestamp\"\n />\n <span></span>\n <span translate>Show timestamp</span>\n </label>\n </c8y-form-group>\n\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show icon' | translate\"\n >\n <input\n name=\"showIcon\"\n type=\"checkbox\"\n formControlName=\"showIcon\"\n />\n <span></span>\n <span translate>Show icon</span>\n </label>\n </c8y-form-group>\n\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show trend icon' | translate\"\n >\n <input\n name=\"showTrend\"\n type=\"checkbox\"\n formControlName=\"showTrend\"\n />\n <span></span>\n <span translate>Show trend icon</span>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'Indicates the trend between the last two measurement values.' | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n </c8y-form-group>\n </div>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend translate>Number of decimal places</legend>\n <c8y-form-group class=\"form-group-sm m-b-20\">\n <input\n class=\"form-control\"\n name=\"numberOfDecimalPlaces\"\n type=\"number\"\n formControlName=\"numberOfDecimalPlaces\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 1 }\"\n />\n <c8y-messages\n [show]=\"\n formGroup.controls?.numberOfDecimalPlaces?.touched &&\n formGroup?.controls?.numberOfDecimalPlaces?.errors\n \"\n ></c8y-messages>\n </c8y-form-group>\n </fieldset>\n</form>\n\n<ng-template #kpiPreview>\n @if (formGroup && formGroup.value) {\n @if (formGroup.value.datapoints?.length > 0 && previewActiveDatapoint) {\n <div style=\"height: 300px\">\n @if (previewConfig) {\n <c8y-kpi-widget-view [config]=\"previewConfig\"></c8y-kpi-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=\"kpi-widget--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/#kpi\">user documentation</a>.\n </small>\n </p>\n </c8y-ui-empty-state>\n </div>\n }\n }\n</ng-template>\n", dependencies: [{ 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: "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.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][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: "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"] }, { 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: "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: DatapointSelectorModule }, { kind: "ngmodule", type: IconSelectorModule }, { kind: "component", type: i4.IconSelectorWrapperComponent, selector: "c8y-icon-selector-wrapper", inputs: ["canRemoveIcon", "selectedIcon", "iconSize"], outputs: ["onSelect"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i5.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "component", type: KpiWidgetViewComponent, selector: "c8y-kpi-widget-view", inputs: ["config"] }, { kind: "pipe", type: i3.C8yTranslatePipe, name: "translate" }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] }); }
376
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: KpiWidgetConfigComponent, isStandalone: true, selector: "c8y-kpi-widget-config", inputs: { config: "config" }, viewQueries: [{ propertyName: "previewMapSet", first: true, predicate: ["kpiPreview"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<form [formGroup]=\"formGroup\">\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Layout' | translate }}</legend>\n <div class=\"d-flex a-i-center gap-8\">\n <div class=\"form-group form-group-sm m-b-16\">\n <label translate>Icon</label>\n <c8y-icon-selector-wrapper\n [iconSize]=\"16\"\n name=\"icon\"\n formControlName=\"icon\"\n ></c8y-icon-selector-wrapper>\n </div>\n <c8y-form-group class=\"form-group-sm m-b-16 flex-grow\">\n <label\n [title]=\"'Font size of measurement value (px)' | translate\"\n translate\n >\n Font size of measurement value (px)\n </label>\n <input\n class=\"form-control\"\n name=\"fontSize\"\n type=\"number\"\n formControlName=\"fontSize\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 36 }\"\n />\n <c8y-messages\n [show]=\"formGroup.controls?.fontSize?.touched && formGroup?.controls?.fontSize?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Display' | translate }}</legend>\n <div class=\"d-flex gap-16 flex-wrap\">\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show timestamp' | translate\"\n >\n <input\n name=\"showTimestamp\"\n type=\"checkbox\"\n formControlName=\"showTimestamp\"\n />\n <span></span>\n <span translate>Show timestamp</span>\n </label>\n </c8y-form-group>\n\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show icon' | translate\"\n >\n <input\n name=\"showIcon\"\n type=\"checkbox\"\n formControlName=\"showIcon\"\n />\n <span></span>\n <span translate>Show icon</span>\n </label>\n </c8y-form-group>\n\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show trend icon' | translate\"\n >\n <input\n name=\"showTrend\"\n type=\"checkbox\"\n formControlName=\"showTrend\"\n />\n <span></span>\n <span translate>Show trend icon</span>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'Indicates the trend between the last two measurement values.' | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n </c8y-form-group>\n </div>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend translate>Number of decimal places</legend>\n <c8y-form-group class=\"form-group-sm m-b-20\">\n <input\n class=\"form-control\"\n name=\"numberOfDecimalPlaces\"\n type=\"number\"\n formControlName=\"numberOfDecimalPlaces\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 1 }\"\n />\n <c8y-messages\n [show]=\"\n formGroup.controls?.numberOfDecimalPlaces?.touched &&\n formGroup?.controls?.numberOfDecimalPlaces?.errors\n \"\n ></c8y-messages>\n </c8y-form-group>\n </fieldset>\n</form>\n\n<ng-template #kpiPreview>\n @if (formGroup && formGroup.value) {\n @if (formGroup.value.datapoints?.length > 0 && previewActiveDatapoint) {\n <div style=\"height: 300px\">\n <c8y-kpi-widget-view [config]=\"previewConfig\"></c8y-kpi-widget-view>\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=\"kpi-widget--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/#kpi\">user documentation</a>.\n </small>\n </p>\n </c8y-ui-empty-state>\n </div>\n }\n }\n</ng-template>\n", dependencies: [{ 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.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][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: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "component", type: GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "ngmodule", type: DatapointSelectorModule }, { kind: "ngmodule", type: IconSelectorModule }, { kind: "component", type: i3.IconSelectorWrapperComponent, selector: "c8y-icon-selector-wrapper", inputs: ["canRemoveIcon", "selectedIcon", "iconSize"], outputs: ["onSelect"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i4.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "component", type: KpiWidgetViewComponent, selector: "c8y-kpi-widget-view", inputs: ["config"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
281
377
  }
282
378
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: KpiWidgetConfigComponent, decorators: [{
283
379
  type: Component,
284
380
  args: [{ selector: 'c8y-kpi-widget-config', standalone: true, imports: [
285
- CoreModule,
381
+ ReactiveFormsModule,
382
+ C8yTranslatePipe,
383
+ C8yTranslateDirective,
384
+ FormGroupComponent,
385
+ MessagesComponent,
386
+ EmptyStateComponent,
387
+ GuideDocsComponent,
388
+ GuideHrefDirective,
286
389
  DatapointSelectorModule,
287
390
  IconSelectorModule,
288
391
  PopoverModule,
289
392
  KpiWidgetViewComponent
290
- ], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], template: "<form [formGroup]=\"formGroup\">\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Layout' | translate }}</legend>\n <div class=\"d-flex a-i-center gap-8\">\n <div class=\"form-group form-group-sm m-b-16\">\n <label translate>Icon</label>\n <c8y-icon-selector-wrapper\n [iconSize]=\"16\"\n name=\"icon\"\n formControlName=\"icon\"\n ></c8y-icon-selector-wrapper>\n </div>\n <c8y-form-group class=\"form-group-sm m-b-16 flex-grow\">\n <label\n [title]=\"'Font size of measurement value (px)' | translate\"\n translate\n >\n Font size of measurement value (px)\n </label>\n <input\n class=\"form-control\"\n name=\"fontSize\"\n type=\"number\"\n formControlName=\"fontSize\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 36 }\"\n />\n <c8y-messages\n [show]=\"formGroup.controls?.fontSize?.touched && formGroup?.controls?.fontSize?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Display' | translate }}</legend>\n <div class=\"d-flex gap-16 flex-wrap\">\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show timestamp' | translate\"\n >\n <input\n name=\"showTimestamp\"\n type=\"checkbox\"\n formControlName=\"showTimestamp\"\n />\n <span></span>\n <span translate>Show timestamp</span>\n </label>\n </c8y-form-group>\n\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show icon' | translate\"\n >\n <input\n name=\"showIcon\"\n type=\"checkbox\"\n formControlName=\"showIcon\"\n />\n <span></span>\n <span translate>Show icon</span>\n </label>\n </c8y-form-group>\n\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show trend icon' | translate\"\n >\n <input\n name=\"showTrend\"\n type=\"checkbox\"\n formControlName=\"showTrend\"\n />\n <span></span>\n <span translate>Show trend icon</span>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'Indicates the trend between the last two measurement values.' | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n </c8y-form-group>\n </div>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend translate>Number of decimal places</legend>\n <c8y-form-group class=\"form-group-sm m-b-20\">\n <input\n class=\"form-control\"\n name=\"numberOfDecimalPlaces\"\n type=\"number\"\n formControlName=\"numberOfDecimalPlaces\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 1 }\"\n />\n <c8y-messages\n [show]=\"\n formGroup.controls?.numberOfDecimalPlaces?.touched &&\n formGroup?.controls?.numberOfDecimalPlaces?.errors\n \"\n ></c8y-messages>\n </c8y-form-group>\n </fieldset>\n</form>\n\n<ng-template #kpiPreview>\n @if (formGroup && formGroup.value) {\n @if (formGroup.value.datapoints?.length > 0 && previewActiveDatapoint) {\n <div style=\"height: 300px\">\n @if (previewConfig) {\n <c8y-kpi-widget-view [config]=\"previewConfig\"></c8y-kpi-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=\"kpi-widget--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/#kpi\">user documentation</a>.\n </small>\n </p>\n </c8y-ui-empty-state>\n </div>\n }\n }\n</ng-template>\n" }]
393
+ ], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form [formGroup]=\"formGroup\">\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Layout' | translate }}</legend>\n <div class=\"d-flex a-i-center gap-8\">\n <div class=\"form-group form-group-sm m-b-16\">\n <label translate>Icon</label>\n <c8y-icon-selector-wrapper\n [iconSize]=\"16\"\n name=\"icon\"\n formControlName=\"icon\"\n ></c8y-icon-selector-wrapper>\n </div>\n <c8y-form-group class=\"form-group-sm m-b-16 flex-grow\">\n <label\n [title]=\"'Font size of measurement value (px)' | translate\"\n translate\n >\n Font size of measurement value (px)\n </label>\n <input\n class=\"form-control\"\n name=\"fontSize\"\n type=\"number\"\n formControlName=\"fontSize\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 36 }\"\n />\n <c8y-messages\n [show]=\"formGroup.controls?.fontSize?.touched && formGroup?.controls?.fontSize?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Display' | translate }}</legend>\n <div class=\"d-flex gap-16 flex-wrap\">\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show timestamp' | translate\"\n >\n <input\n name=\"showTimestamp\"\n type=\"checkbox\"\n formControlName=\"showTimestamp\"\n />\n <span></span>\n <span translate>Show timestamp</span>\n </label>\n </c8y-form-group>\n\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show icon' | translate\"\n >\n <input\n name=\"showIcon\"\n type=\"checkbox\"\n formControlName=\"showIcon\"\n />\n <span></span>\n <span translate>Show icon</span>\n </label>\n </c8y-form-group>\n\n <c8y-form-group class=\"m-b-8\">\n <label\n class=\"c8y-checkbox\"\n [title]=\"'Show trend icon' | translate\"\n >\n <input\n name=\"showTrend\"\n type=\"checkbox\"\n formControlName=\"showTrend\"\n />\n <span></span>\n <span translate>Show trend icon</span>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'Indicates the trend between the last two measurement values.' | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n </c8y-form-group>\n </div>\n </fieldset>\n\n <fieldset class=\"c8y-fieldset\">\n <legend translate>Number of decimal places</legend>\n <c8y-form-group class=\"form-group-sm m-b-20\">\n <input\n class=\"form-control\"\n name=\"numberOfDecimalPlaces\"\n type=\"number\"\n formControlName=\"numberOfDecimalPlaces\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 1 }\"\n />\n <c8y-messages\n [show]=\"\n formGroup.controls?.numberOfDecimalPlaces?.touched &&\n formGroup?.controls?.numberOfDecimalPlaces?.errors\n \"\n ></c8y-messages>\n </c8y-form-group>\n </fieldset>\n</form>\n\n<ng-template #kpiPreview>\n @if (formGroup && formGroup.value) {\n @if (formGroup.value.datapoints?.length > 0 && previewActiveDatapoint) {\n <div style=\"height: 300px\">\n <c8y-kpi-widget-view [config]=\"previewConfig\"></c8y-kpi-widget-view>\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=\"kpi-widget--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/#kpi\">user documentation</a>.\n </small>\n </p>\n </c8y-ui-empty-state>\n </div>\n }\n }\n</ng-template>\n" }]
291
394
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i1.NgForm }, { type: i2.WidgetConfigComponent }, { type: i2.WidgetConfigService }], propDecorators: { previewMapSet: [{
292
395
  type: ViewChild,
293
396
  args: ['kpiPreview']
@@ -299,5 +402,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
299
402
  * Generated bundle index. Do not edit.
300
403
  */
301
404
 
302
- export { KpiWidgetConfigComponent, KpiWidgetViewComponent, exactlyASingleDatapointActive };
405
+ export { ColorClass, KpiWidgetConfigComponent, KpiWidgetViewComponent, exactlyASingleDatapointActive };
303
406
  //# sourceMappingURL=c8y-ngx-components-widgets-implementations-kpi.mjs.map