UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

407 lines (401 loc) 40.7 kB
import * as i0 from '@angular/core'; import { signal, inject, DestroyRef, Input, ChangeDetectionStrategy, Component, ViewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import * as i1 from '@angular/forms'; import { Validators, NgForm, ControlContainer, ReactiveFormsModule } from '@angular/forms'; import { DashboardChildComponent, MeasurementRealtimeService, C8yTranslateDirective, IconDirective, LoadingComponent, EmptyStateComponent, DatePipe, C8yTranslatePipe, C8yValidators, FormGroupComponent, MessagesComponent, GuideDocsComponent, GuideHrefDirective } from '@c8y/ngx-components'; import * as i2 from '@c8y/ngx-components/context-dashboard'; import { ContextDashboardComponent } from '@c8y/ngx-components/context-dashboard'; import { DatapointSelectorModule } from '@c8y/ngx-components/datapoint-selector'; import * as i3 from '@c8y/ngx-components/icon-selector'; import { IconSelectorModule } from '@c8y/ngx-components/icon-selector'; import * as i4 from 'ngx-bootstrap/popover'; import { PopoverModule } from 'ngx-bootstrap/popover'; import { share, pairwise, map, startWith, distinctUntilChanged, filter, switchMap, tap, debounceTime } from 'rxjs/operators'; import { GLOBAL_CONTEXT_DISPLAY_MODE, PRESET_NAME, WidgetConfigMigrationService, REFRESH_OPTION, GlobalContextConnectorComponent, LocalControlsComponent } from '@c8y/ngx-components/global-context'; import { merge, isEqual } from 'lodash-es'; import { NEVER, BehaviorSubject, Subject, combineLatest, merge as merge$1 } from 'rxjs'; import { NgClass, NgStyle, AsyncPipe, DecimalPipe } from '@angular/common'; var ColorClass; (function (ColorClass) { ColorClass["danger"] = "text-danger"; ColorClass["warning"] = "text-warning"; ColorClass["unknown"] = ""; })(ColorClass || (ColorClass = {})); class KpiWidgetViewComponent { constructor() { this.config = { datapoints: [] }; this.displayMode = signal(GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD, ...(ngDevMode ? [{ debugName: "displayMode" }] : [])); this.contextConfig = signal({}, ...(ngDevMode ? [{ debugName: "contextConfig" }] : [])); this.isLinkedToGlobal = signal(undefined, ...(ngDevMode ? [{ debugName: "isLinkedToGlobal" }] : [])); this.widgetControls = signal(PRESET_NAME.KPI, ...(ngDevMode ? [{ debugName: "widgetControls" }] : [])); this.isHistoryMode = signal(false, ...(ngDevMode ? [{ debugName: "isHistoryMode" }] : [])); this.state$ = NEVER; this.noDataInitiallyInDB = signal(false, ...(ngDevMode ? [{ debugName: "noDataInitiallyInDB" }] : [])); this.GLOBAL_CONTEXT_DISPLAY_MODE = GLOBAL_CONTEXT_DISPLAY_MODE; this.dashboardChild = inject(DashboardChildComponent, { optional: true }); this.dashboard = inject(ContextDashboardComponent, { optional: true }); this.measurementRealtime = inject(MeasurementRealtimeService); this.widgetConfigMigrationService = inject(WidgetConfigMigrationService); this.destroyRef = inject(DestroyRef); this.context$ = new BehaviorSubject(null); this.refresh$ = new Subject(); this.lastDatapoint = null; } ngOnInit() { this.config = merge(this.config, this.widgetConfigMigrationService.migrateWidgetConfig(this.config)); this.syncDisplayState(); this.buildStatePipeline(); if (!this.isDashboardMode()) { this.emitContext(this.config); } } ngOnChanges(changes) { const cfg = changes.config?.currentValue; if (!cfg) { return; } this.config = cfg; this.syncDisplayState(); if (this.isOnRealDashboard()) { return; } if (this.isDashboardPreviewWaitingForContext()) { return; } this.emitContext(this.config, this.hasDatapointChanged()); } onContextChange(event) { this.contextConfig.set(event.context); this.emitContext(event.context); } onRefresh() { this.refresh$.next(); } getDashboardChild() { return this.dashboardChild; } setupObservable(datapoint, context) { const isHistory = context.refreshOption === REFRESH_OPTION.HISTORY; const isPaused = !isHistory && context.isAutoRefreshEnabled === false; this.isHistoryMode.set(isHistory); this.noDataInitiallyInDB.set(false); let source$; if (isPaused) { source$ = this.getHistoryMeasurement$(datapoint, {}); } else if (isHistory) { source$ = this.getHistoryMeasurement$(datapoint, context); } else { source$ = this.getLiveMeasurement$(datapoint); } const shared$ = source$.pipe(share()); const lastTwo$ = shared$.pipe(pairwise()); return combineLatest([ shared$, lastTwo$.pipe(map(([prev]) => prev), startWith(undefined)), this.getTrend$(lastTwo$), shared$.pipe(map(m => datapoint.unit || m.unit || ''), startWith(''), distinctUntilChanged()), this.getColorClass$(shared$, datapoint) ]).pipe(map(([latestMeasurement, previousValue, trend, unit, colorClass]) => ({ latestMeasurement, previousValue, trend, unit, colorClass }))); } buildStatePipeline() { const readState = () => ({ ctx: this.context$.value, dp: this.findActiveDatapoint() }); 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 }) => { this.assignContextFromContextDashboard(dp); return this.setupObservable(dp, ctx).pipe(startWith(null)); }), takeUntilDestroyed(this.destroyRef)); } syncDisplayState() { const newMode = this.resolveDisplayMode(); if (this.displayMode() !== newMode) { this.displayMode.set(newMode); } const newCtx = this.extractContext(this.config); if (!isEqual(this.contextConfig(), newCtx)) { this.contextConfig.set(newCtx); } } emitContext(source, force = false) { const ctx = this.extractContext(source); if (force || !isEqual(this.context$.value, ctx)) { this.context$.next(ctx); } } isDashboardMode() { return this.resolveDisplayMode() === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD; } isOnRealDashboard() { return this.isDashboardMode() && !!this.dashboardChild; } isDashboardPreviewWaitingForContext() { return this.isDashboardMode() && !this.config.isGlobalContextReady; } resolveDisplayMode() { return (this.config?.displayMode || GLOBAL_CONTEXT_DISPLAY_MODE.CONFIG); } extractContext(source) { return { dateTimeContext: source.dateTimeContext, isAutoRefreshEnabled: source.isAutoRefreshEnabled, refreshInterval: source.refreshInterval, refreshOption: source.refreshOption }; } findActiveDatapoint() { return this.config?.datapoints?.find(dp => dp?.__active); } hasDatapointChanged() { const dp = this.findActiveDatapoint(); if (!dp) { return false; } const prev = this.lastDatapoint; this.lastDatapoint = dp; return (!prev || dp.fragment !== prev.fragment || dp.series !== prev.series || dp.__target?.id !== prev.__target?.id || dp.unit !== prev.unit || dp.redRangeMin !== prev.redRangeMin || dp.redRangeMax !== prev.redRangeMax || dp.yellowRangeMin !== prev.yellowRangeMin || dp.yellowRangeMax !== prev.yellowRangeMax); } getLiveMeasurement$(datapoint) { return this.measurementRealtime .latestValueOfSpecificMeasurement$(datapoint.fragment, datapoint.series, datapoint.__target, this.config.showTrend ? 2 : 1, true) .pipe(tap(m => { if (!m) this.noDataInitiallyInDB.set(true); }), filter(m => !!m), map(m => this.toMeasurementValue(m, datapoint))); } getHistoryMeasurement$(datapoint, context) { return this.measurementRealtime .lastMeasurement$(datapoint.fragment, datapoint.series, datapoint.__target, 1, true, context.dateTimeContext?.dateFrom, context.dateTimeContext?.dateTo) .pipe(tap(m => { if (!m) this.noDataInitiallyInDB.set(true); }), filter(m => !!m), map(m => this.toMeasurementValue(m, datapoint))); } toMeasurementValue(m, dp) { return { unit: m[dp.fragment][dp.series].unit, value: m[dp.fragment][dp.series].value, date: m.time }; } getColorClass$(measurement$, datapoint) { return measurement$.pipe(map(m => { if (this.inRange(datapoint, m.value, 'redRangeMin', 'redRangeMax')) { return ColorClass.danger; } if (this.inRange(datapoint, m.value, 'yellowRangeMin', 'yellowRangeMax')) { return ColorClass.warning; } return ColorClass.unknown; }), startWith(ColorClass.unknown), distinctUntilChanged()); } getTrend$(lastTwo$) { return lastTwo$.pipe(map(([prev, curr]) => { if (prev.value < curr.value) return '45deg'; if (prev.value > curr.value) return '135deg'; return '90deg'; }), startWith('90deg'), distinctUntilChanged()); } inRange(dp, value, minKey, maxKey) { return (typeof dp[minKey] === 'number' && typeof dp[maxKey] === 'number' && value >= dp[minKey] && value < dp[maxKey]); } assignContextFromContextDashboard(datapoint) { if (!this.dashboard?.isDeviceTypeDashboard) { return; } const context = this.dashboard?.context; if (context?.id) { datapoint.__target = { name: context.name, id: context.id }; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: KpiWidgetViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", 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 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: KpiWidgetViewComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-kpi-widget-view', standalone: true, imports: [ AsyncPipe, DatePipe, DecimalPipe, NgClass, NgStyle, C8yTranslatePipe, C8yTranslateDirective, IconDirective, LoadingComponent, EmptyStateComponent, GlobalContextConnectorComponent, LocalControlsComponent ], 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" }] }], propDecorators: { config: [{ type: Input }] } }); function exactlyASingleDatapointActive() { return (control) => { const datapoints = control.value; if (!datapoints?.length) { return null; } return datapoints.filter(dp => dp.__active).length === 1 ? null : { exactlyOneDatapointNeedsToBeActive: true }; }; } class KpiWidgetConfigComponent { set previewMapSet(template) { this.widgetConfigService.setPreview(template || 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 }; this.destroyRef = inject(DestroyRef); this.limits = { fontSizeMax: 72, fontSizeMin: 18, numberOfDecimalPlacesMax: 10, numberOfDecimalPlacesMin: 0 }; } 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; } this.previewConfig = { ...this.config }; this.initForm(); if (this.config?.datapoints) { this.formGroup.patchValue({ datapoints: this.config.datapoints }); this.previewActiveDatapoint = this.config.datapoints.find(dp => dp.__active); } this.widgetConfigService.currentConfig$ .pipe(filter(c => !!c), takeUntilDestroyed(this.destroyRef)) .subscribe(c => { this.previewConfig = { ...this.previewConfig, ...c, ...this.formGroup.value }; }); } initForm() { this.formGroup = this.createForm(); this.form.form.addControl('config', this.formGroup); this.formGroup.patchValue(this.config); this.formGroup.valueChanges .pipe(debounceTime(100), takeUntilDestroyed(this.destroyRef)) .subscribe(formValue => { if (formValue.datapoints) { this.previewActiveDatapoint = formValue.datapoints.find(dp => dp.__active); } if (this.formGroup.valid) { this.previewConfig = { ...this.config, ...this.applyLimitsToPreview(formValue) }; } Object.assign(this.config, formValue); }); } applyLimitsToPreview(formValue) { const result = { ...formValue }; if (result.numberOfDecimalPlaces !== undefined) { result.numberOfDecimalPlaces = this.clamp(result.numberOfDecimalPlaces, this.limits.numberOfDecimalPlacesMin, this.limits.numberOfDecimalPlacesMax); } if (result.fontSize !== undefined) { result.fontSize = this.clamp(result.fontSize, this.limits.fontSizeMin, this.limits.fontSizeMax); } return result; } clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } createForm() { return this.formBuilder.group({ numberOfDecimalPlaces: [ 2, [ Validators.required, Validators.min(this.limits.numberOfDecimalPlacesMin), Validators.max(this.limits.numberOfDecimalPlacesMax), C8yValidators.integerValidator() ] ], showTimestamp: [true, []], showTrend: [true, []], showIcon: [true, []], icon: ['water', [Validators.required, Validators.minLength(1)]], fontSize: [ 36, [ Validators.required, Validators.min(this.limits.fontSizeMin), Validators.max(this.limits.fontSizeMax) ] ], datapoints: this.formBuilder.control(new Array(), [ Validators.required, Validators.minLength(1), exactlyASingleDatapointActive() ]) }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: KpiWidgetConfigComponent, 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: 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", "additionalMessages"] }, { 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 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: KpiWidgetConfigComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-kpi-widget-config', standalone: true, imports: [ ReactiveFormsModule, C8yTranslatePipe, C8yTranslateDirective, FormGroupComponent, MessagesComponent, EmptyStateComponent, GuideDocsComponent, GuideHrefDirective, DatapointSelectorModule, IconSelectorModule, PopoverModule, KpiWidgetViewComponent ], 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" }] }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i1.NgForm }, { type: i2.WidgetConfigComponent }, { type: i2.WidgetConfigService }], propDecorators: { previewMapSet: [{ type: ViewChild, args: ['kpiPreview'] }], config: [{ type: Input }] } }); /** * Generated bundle index. Do not edit. */ export { ColorClass, KpiWidgetConfigComponent, KpiWidgetViewComponent, exactlyASingleDatapointActive }; //# sourceMappingURL=c8y-ngx-components-widgets-implementations-kpi.mjs.map