UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

521 lines 104 kB
import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common'; import { Component, effect, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ActivatedRoute, ActivationEnd, NavigationEnd, NavigationStart, Router } from '@angular/router'; import { aggregationType } from '@c8y/client'; import { INTERVAL_TITLES, IntervalPickerComponent } from '@c8y/ngx-components/interval-picker'; import { BsDropdownDirective, BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import { interval, Subject } from 'rxjs'; import { distinctUntilChanged, filter, map, takeUntil } from 'rxjs/operators'; import { ActionBarService } from '../../action-bar'; import { ActionBarItemComponent } from '../../action-bar/action-bar-item.component'; import { AGGREGATION_LIMITS } from '../../aggregation/aggregation.model'; import { AggregationService } from '../../aggregation/aggregation.service'; import { DatePipe } from '../../common/date.pipe'; import { IconDirective } from '../../common/icon.directive'; import { DateTimePickerComponent } from '../../date-time-picker'; import { FormGroupComponent } from '../../forms/form-group.component'; import { MessageDirective } from '../../forms/message.directive'; import { MessagesComponent } from '../../forms/messages.component'; import { gettext } from '../../i18n'; import { C8yTranslateDirective } from '../../i18n/c8y-translate.directive'; import { C8yTranslatePipe } from '../../i18n/c8y-translate.pipe'; import { DashboardChildActionComponent } from '../dashboard-child-action.component'; import { DashboardChildComponent } from '../dashboard-child.component'; import { WidgetsDashboardEventService } from '../widgets-dashboard-event.service'; import { AggregationPickerComponent } from './aggregation-picker/aggregation-picker.component'; import { RealtimeControlComponent } from './realtime-control/realtime-control.component'; import { WidgetTimeContextDateRangeService } from './widget-time-context-date-range.service'; import { WidgetTimeContextHelperService } from './widget-time-context-helper.service'; import { WidgetTimeContextQueryService } from './widget-time-context-query.service'; import * as i0 from "@angular/core"; import * as i1 from "../widgets-dashboard-event.service"; import * as i2 from "../dashboard-child.component"; import * as i3 from "@angular/forms"; import * as i4 from "./widget-time-context-query.service"; import * as i5 from "./widget-time-context-helper.service"; import * as i6 from "@angular/router"; import * as i7 from "../../action-bar"; import * as i8 from "../../aggregation/aggregation.service"; import * as i9 from "./widget-time-context-date-range.service"; import * as i10 from "ngx-bootstrap/dropdown"; import * as i11 from "ngx-bootstrap/tooltip"; export class WidgetTimeContextComponent { /** * @ignore only DI. */ constructor(widgetEventService, dashboardChild, formBuilder, queryService, helperService, router, actionBarService, aggregationService, route, widgetTimeContextDateRangeService) { this.widgetEventService = widgetEventService; this.dashboardChild = dashboardChild; this.formBuilder = formBuilder; this.queryService = queryService; this.helperService = helperService; this.router = router; this.actionBarService = actionBarService; this.aggregationService = aggregationService; this.route = route; this.widgetTimeContextDateRangeService = widgetTimeContextDateRangeService; this.INTERVAL_TITLES = INTERVAL_TITLES; this.DATE_FORMAT = 'short'; /** * Indicates if the component can decouple or not. */ this.canDecouple = true; this.hidden = false; /** * Emits each change as an array of dates [from, to]. */ this.dateContextChange = new EventEmitter(); /** * Indicates if the time context is bound to the global scope. */ this.isCoupled = true; this.decoupleTimeContextLabel = gettext('Decouple time context'); this.coupleTimeContextLabel = gettext('Couple time context'); this.disabledAggregations = {}; this.DEFAULT_INTERVAL = 'days'; this.ACTION_BAR_PRIORITY = 7; this.ACTION_BAR_GROUP_ID = 'timecontext'; this.REALTIME_INTERVAL = 1000; this.destroy$ = new Subject(); effect(() => { const data = this.widgetTimeContextDateRangeService.timeContextChange(); if (data) { const updatedFormData = { date: [new Date(data.dateFrom), new Date(data.dateTo)], interval: data.interval, realtime: false, aggregation: this.form.value.aggregation }; this.sliderChange = true; this.dateContextChange.emit({ ...updatedFormData, sliderChange: true }); this.stopRealtime(); this.updateFormValues(updatedFormData); } }); } /** * @ignore Subscribing to the global context. */ ngOnInit() { this.actionBarInGroupPriority = this.helperService.getActionBarPriority(this.displaySettings); const initialContext = this.getInitialContext() || this.getDefaultContext(); initialContext.aggregation = this.calculateAggregation(initialContext.date, initialContext.aggregation); this.form = this.createForm(initialContext); this.dateContextChange.emit({ date: initialContext.date, realtime: initialContext.realtime, aggregation: initialContext.aggregation }); if (this.isCoupled) { this.queryService.setDateContextQueryParams(initialContext); } this.subscribeToGlobalContext(); this.subscribeToQueryParamsChange(); this.subscribeToRouterEvents(); this.subscribeToIntervalChange(); this.subscribeToRealtimeChange(); this.subscribeToAggregationChange(); if (initialContext.realtime) { this.onRealtimeValueChange(initialContext.realtime); this.startRealtime(); } } /** * @ignore Adding custom actions. */ ngAfterViewInit() { if (this.canDecouple) { this.dashboardChild.addActions([this.action]); } this.route.queryParams .pipe(distinctUntilChanged(), map(params => params['globalContextAutoRefresh'] === 'true'), takeUntil(this.destroy$)) .subscribe(this.handleAutoRefreshChange.bind(this)); } /** * Toggles the coupling on or off. */ toggleDecoupling() { this.isCoupled = !this.isCoupled; const lastEventValue = this.widgetEventService.getLastValue('TIME_CONTEXT'); const { realtime, aggregation, interval } = lastEventValue; let date; if (interval) { date = this.helperService.getDateTimeContextByInterval(interval); } else { date = lastEventValue.dateTimeContext; } this.updateFormValues({ date, interval, realtime, aggregation }); if (this.isCoupled) { this.subscribeToGlobalContext(); this.dateContextChange.emit({ date, realtime, aggregation }); } else { this.unsubscribeFromGlobalContext(); } } /** * Applies form value to global or local date context. */ applyDatetimeContext() { this.update({ date: [ new Date(this.form.value.temporaryUserSelectedFromDate), new Date(this.form.value.temporaryUserSelectedToDate) ], interval: null, realtime: this.form.value.realtime, aggregation: this.form.value.aggregation }); } /** * Resets form to initial value and update context. */ reset() { this.stopRealtime(); this.update(this.getDefaultContext()); } /** * @ignore unsubscribing. */ ngOnDestroy() { this.unsubscribeFromGlobalContext(); this.clearQueryParamsIfNeeded(); this.widgetEventService.setRealtimeTimeContextSetting(); this.destroy$.next(); this.destroy$.complete(); } subscribeToIntervalChange() { this.form.controls.currentDateContextInterval.valueChanges .pipe(takeUntil(this.destroy$)) .subscribe(interval => { let date; if (interval === 'custom') { date = [ new Date(this.form.controls.currentDateContextFromDate.value), new Date(this.form.controls.currentDateContextToDate.value) ]; } else { date = this.helperService.getDateTimeContextByInterval(interval); this.dropdown.isOpen = false; } this.update({ date, interval, realtime: this.form.value.realtime, aggregation: this.form.value.aggregation }); }); } subscribeToRealtimeChange() { this.form.controls.realtime.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(realtime => { this.onRealtimeValueChange(realtime); if (realtime) { this.startRealtime(); } else { this.stopRealtime(); } }); } subscribeToAggregationChange() { this.form.controls.aggregation.valueChanges .pipe(takeUntil(this.destroy$)) .subscribe(aggregation => { if (this.sliderChange) { this.sliderChange = false; return; } this.update({ date: [ new Date(this.form.value.currentDateContextFromDate), new Date(this.form.value.currentDateContextToDate) ], interval: this.form.value.currentDateContextInterval, realtime: this.form.value.realtime, aggregation }); }); } createForm(context) { return this.formBuilder.group({ temporaryUserSelectedFromDate: context.date[0].toISOString(), temporaryUserSelectedToDate: context.date[1].toISOString(), currentDateContextFromDate: context.date[0].toISOString(), currentDateContextToDate: context.date[1].toISOString(), currentDateContextInterval: context.interval || 'custom', realtime: context.realtime, aggregation: context.aggregation }); } /** * Fires a new WidgetChangeEvent either on the local change emitter or on the global one. * @param widgetTimeContextState New widget time context value.*/ update({ date, interval, realtime, aggregation }) { const validAggregation = this.calculateAggregation(date, aggregation); if (this.isCoupled) { const eventData = interval && interval !== 'custom' ? { interval, realtime, aggregation: validAggregation } : { dateTimeContext: date, realtime, aggregation: validAggregation }; this.widgetEventService.emit({ type: 'TIME_CONTEXT', data: eventData }); } else { this.updateFormValues({ date, interval, realtime, aggregation: validAggregation }); this.dateContextChange.emit({ date, realtime, aggregation: validAggregation }); } } subscribeToGlobalContext() { const event$ = this.widgetEventService.getObservable('TIME_CONTEXT'); this.subscription = event$.subscribe((context) => { let date; const { realtime, interval, aggregation } = context; if (interval) { date = this.helperService.getDateTimeContextByInterval(context.interval); } else { date = context.dateTimeContext; } this.dateContextChange.emit({ date, sliderChange: false, interval, realtime, aggregation }); this.updateFormValues({ date, interval, realtime, aggregation }); this.queryService.setDateContextQueryParams({ interval, date, realtime, aggregation }); }); } updateFormValues({ date, interval, realtime, aggregation }) { this.form.patchValue({ temporaryUserSelectedFromDate: date[0].toISOString(), temporaryUserSelectedToDate: date[1].toISOString(), currentDateContextFromDate: date[0].toISOString(), currentDateContextToDate: date[1].toISOString(), realtime, currentDateContextInterval: interval || 'custom', aggregation: aggregation || null }, { emitEvent: false }); } unsubscribeFromGlobalContext() { if (this.subscription) { this.subscription.unsubscribe(); } } getInitialContext() { const dateTimeContextFromQueryParams = this.queryService.dateTimeContextFromQueryParams(); if (dateTimeContextFromQueryParams) { return { ...dateTimeContextFromQueryParams, realtime: dateTimeContextFromQueryParams.realtime ?? false, aggregation: dateTimeContextFromQueryParams.realtime ? null : dateTimeContextFromQueryParams.aggregation || null }; } // get value from last value of events service const lastEventValue = this.widgetEventService.getLastValue('TIME_CONTEXT'); const realtime = lastEventValue?.realtime ?? false; if (lastEventValue && lastEventValue.dateTimeContext) { return { date: lastEventValue.dateTimeContext, interval: 'custom', realtime, aggregation: realtime ? null : lastEventValue.aggregation }; } if (lastEventValue && lastEventValue.interval) { return { date: this.helperService.getDateTimeContextByInterval(lastEventValue.interval), interval: lastEventValue.interval, realtime, aggregation: realtime ? null : lastEventValue.aggregation }; } return null; } subscribeToQueryParamsChange() { this.queryService .queryParamsChange$() .pipe(takeUntil(this.destroy$)) .subscribe(({ dateContextFrom, dateContextTo, dateContextInterval, dateContextRealtime, dateContextAggregation }) => { const realtime = dateContextRealtime ?? this.form.value.realtime; if (dateContextInterval) { this.widgetEventService.emit({ type: 'TIME_CONTEXT', data: { interval: dateContextInterval, realtime, aggregation: dateContextAggregation } }); } else { const dateContext = [ new Date(dateContextFrom), new Date(dateContextTo) ]; this.widgetEventService.emit({ type: 'TIME_CONTEXT', data: { dateTimeContext: dateContext, realtime, aggregation: dateContextAggregation } }); } }); } clearQueryParamsIfNeeded() { // If navigation is in progress, router will take care of clearing query params. This way we avoid unnecessary manipulation of browser history if (this.navigationInProgress) { return; } // check if any other WidgetTimeContextComponent action in action bar exists const anyWidgetTimeContextActionLeft = Array.from(this.actionBarService.state).some(action => action.groupId === this.ACTION_BAR_GROUP_ID); if (!anyWidgetTimeContextActionLeft) { this.queryService.clearQueryParams(); } } subscribeToRouterEvents() { this.router.events .pipe(filter(e => e instanceof NavigationStart || e instanceof NavigationEnd || e instanceof ActivationEnd), takeUntil(this.destroy$)) .subscribe(e => { this.navigationInProgress = e instanceof NavigationStart; }); } getDefaultContext() { return { date: this.helperService.getDateTimeContextByInterval(this.DEFAULT_INTERVAL), interval: this.DEFAULT_INTERVAL, realtime: false, aggregation: aggregationType.MINUTELY }; } startRealtime() { this.disableDateRangeAndAggregation(); this.realtimeSubscription = interval(this.REALTIME_INTERVAL) .pipe(takeUntil(this.destroy$)) .subscribe(() => { const newDateFrom = new Date(new Date(this.form.value.currentDateContextFromDate).valueOf() + this.REALTIME_INTERVAL); const newDateTo = new Date(new Date(this.form.value.currentDateContextToDate).valueOf() + this.REALTIME_INTERVAL); this.updateFormValues({ date: [newDateFrom, newDateTo], interval: this.form.value.currentDateContextInterval, realtime: true, aggregation: null }); }); } handleAutoRefreshChange(isEnabled) { this.isAutoRefreshEnabled = isEnabled; isEnabled ? this.onDisableAutoRefresh() : this.enableDateRangeAndAggregation(); } onDisableAutoRefresh() { this.form.controls.aggregation.setValue(null); this.disableDateRangeAndAggregation(); } disableDateRangeAndAggregation() { this.form.controls.temporaryUserSelectedFromDate.disable(); this.form.controls.temporaryUserSelectedToDate.disable(); this.form.controls.aggregation.disable(); } enableDateRangeAndAggregation() { if (this.form.controls.realtime.value || this.isAutoRefreshEnabled) { return; } this.form.controls.temporaryUserSelectedFromDate.enable(); this.form.controls.temporaryUserSelectedToDate.enable(); this.form.controls.aggregation.enable(); } stopRealtime() { this.realtimeSubscription?.unsubscribe(); this.enableDateRangeAndAggregation(); } onRealtimeValueChange(realtime) { let dateTimeContext; if (this.form.value.currentDateContextInterval !== 'custom') { dateTimeContext = this.helperService.getDateTimeContextByInterval(this.form.value.currentDateContextInterval); } else { const currentTimeSpanInMs = new Date(this.form.value.currentDateContextToDate).valueOf() - new Date(this.form.value.currentDateContextFromDate).valueOf(); const dateTo = new Date(); const dateFrom = new Date(dateTo.valueOf() - currentTimeSpanInMs); dateTimeContext = [dateFrom, dateTo]; } this.update({ date: dateTimeContext, interval: this.form.value.currentDateContextInterval, realtime, aggregation: null }); } calculateAggregation([dateFrom, dateTo], aggregation) { this.disabledAggregations = this.aggregationService.getDisabledAggregationOptions(dateFrom, dateTo); const timeRangeInMs = dateTo.valueOf() - dateFrom.valueOf(); const isProperAggregation = !this.disabledAggregations[aggregation] || aggregation === null; if (isProperAggregation) { return aggregation; } if (timeRangeInMs >= AGGREGATION_LIMITS.DAILY_LIMIT) { return aggregationType.DAILY; } else if (timeRangeInMs >= AGGREGATION_LIMITS.HOURLY_LIMIT) { return aggregationType.HOURLY; } else if (timeRangeInMs >= AGGREGATION_LIMITS.MINUTELY_LIMIT) { return aggregationType.MINUTELY; } else { return null; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: WidgetTimeContextComponent, deps: [{ token: i1.WidgetsDashboardEventService }, { token: i2.DashboardChildComponent }, { token: i3.FormBuilder }, { token: i4.WidgetTimeContextQueryService }, { token: i5.WidgetTimeContextHelperService }, { token: i6.Router }, { token: i7.ActionBarService }, { token: i8.AggregationService }, { token: i6.ActivatedRoute }, { token: i9.WidgetTimeContextDateRangeService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: WidgetTimeContextComponent, isStandalone: true, selector: "c8y-widget-time-context", inputs: { canDecouple: "canDecouple", displaySettings: "displaySettings", hidden: "hidden" }, outputs: { dateContextChange: "dateContextChange" }, host: { classAttribute: "d-flex a-i-center gap-4" }, viewQueries: [{ propertyName: "action", first: true, predicate: DashboardChildActionComponent, descendants: true }, { propertyName: "dropdown", first: true, predicate: BsDropdownDirective, descendants: true }], ngImport: i0, template: "<c8y-action-bar-item\n *ngIf=\"isCoupled && !hidden\"\n [priority]=\"ACTION_BAR_PRIORITY\"\n [groupId]=\"ACTION_BAR_GROUP_ID\"\n [inGroupPriority]=\"actionBarInGroupPriority\"\n [placement]=\"'left'\"\n>\n <ng-container\n [ngTemplateOutlet]=\"dateTimePicker\"\n [ngTemplateOutletContext]=\"{\n date: [form.value.currentDateContextFromDate, form.value.currentDateContextToDate]\n }\"\n ></ng-container>\n</c8y-action-bar-item>\n\n<ng-container\n *ngIf=\"!isCoupled\"\n [ngTemplateOutlet]=\"dateTimePicker\"\n [ngTemplateOutletContext]=\"{\n date: [form.value.currentDateContextFromDate, form.value.currentDateContextToDate]\n }\"\n></ng-container>\n\n<ng-template\n #dateTimePicker\n let-date=\"date\"\n>\n <form\n class=\"d-flex gap-16 p-l-xs-16 p-r-xs-16 m-t-xs-8 m-b-xs-8\"\n [formGroup]=\"form\"\n >\n <ng-container *ngIf=\"displaySettings.globalTimeContext\">\n <div>\n <div\n class=\"dropdown flex-grow\"\n #dropdown=\"bs-dropdown\"\n dropdown\n [insideClick]=\"true\"\n *ngIf=\"date\"\n >\n <button\n class=\"dropdown-toggle form-control l-h-tight d-flex a-i-center\"\n attr.aria-label=\"{{ date[0] | c8yDate: DATE_FORMAT }} \u2014 {{\n date[1] | c8yDate: DATE_FORMAT\n }}\"\n tooltip=\"{{ date[0] | c8yDate: DATE_FORMAT }} \u2014 {{ date[1] | c8yDate: DATE_FORMAT }}\"\n placement=\"top\"\n container=\"body\"\n data-cy=\"widget-time-context--date-picker-dropdown-button\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n dropdownToggle\n >\n <i\n class=\"m-r-4\"\n c8yIcon=\"schedule1\"\n ></i>\n <div class=\"d-col text-left fit-w\">\n <span\n class=\"text-12\"\n data-cy=\"widget-time-context--selected-interval\"\n >\n {{ INTERVAL_TITLES[form.controls.currentDateContextInterval.value] | translate }}\n </span>\n <span\n class=\"text-10 text-muted text-truncate\"\n data-cy=\"widget-time-context--selected-time-range\"\n >\n {{ date[0] | c8yDate: DATE_FORMAT }} \u2014 {{ date[1] | c8yDate: DATE_FORMAT }}\n </span>\n </div>\n <span class=\"caret m-r-16 m-l-4\"></span>\n </button>\n\n <ul\n class=\"dropdown-menu dropdown-menu--date-range\"\n *dropdownMenu\n >\n <c8y-interval-picker\n class=\"d-contents\"\n formControlName=\"currentDateContextInterval\"\n ></c8y-interval-picker>\n\n <ng-container *ngIf=\"form.controls.currentDateContextInterval.value === 'custom'\">\n <div class=\"p-l-16 p-r-16\">\n <c8y-form-group\n [ngClass]=\"form.controls.temporaryUserSelectedFromDate.errors ? 'has-error' : ''\"\n >\n <label\n [title]=\"'From`date`' | translate\"\n for=\"temporaryUserSelectedFromDate\"\n translate\n >\n From`date`\n </label>\n <c8y-date-time-picker\n id=\"temporaryUserSelectedFromDate\"\n [maxDate]=\"form.value.temporaryUserSelectedToDate\"\n [placeholder]=\"'From`date`' | translate\"\n [formControl]=\"form.controls.temporaryUserSelectedFromDate\"\n [ngClass]=\"\n form.controls.temporaryUserSelectedFromDate.errors ? 'has-error' : ''\n \"\n ></c8y-date-time-picker>\n <c8y-messages [show]=\"form.controls.temporaryUserSelectedFromDate.errors\">\n <c8y-message\n name=\"dateAfterRangeMax\"\n [text]=\"'This date is after the latest allowed date.' | translate\"\n ></c8y-message>\n <c8y-message\n name=\"invalidDateTime\"\n [text]=\"'This date is invalid.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <c8y-form-group\n [ngClass]=\"form.controls.temporaryUserSelectedToDate.errors ? 'has-error' : ''\"\n >\n <label\n [title]=\"'To`date`' | translate\"\n for=\"temporaryUserSelectedToDate\"\n translate\n >\n To`date`\n </label>\n <c8y-date-time-picker\n id=\"temporaryUserSelectedToDate\"\n [minDate]=\"form.value.temporaryUserSelectedFromDate\"\n [placeholder]=\"'To`date`' | translate\"\n [formControl]=\"form.controls.temporaryUserSelectedToDate\"\n [ngClass]=\"form.controls.temporaryUserSelectedToDate.errors ? 'has-error' : ''\"\n ></c8y-date-time-picker>\n <c8y-messages [show]=\"form.controls.temporaryUserSelectedToDate.errors\">\n <c8y-message\n name=\"dateBeforeRangeMin\"\n [text]=\"'This date is before the earliest allowed date.' | translate\"\n ></c8y-message>\n <c8y-message\n name=\"invalidDateTime\"\n [text]=\"'This date is invalid.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"p-16 d-flex gap-8 separator-top\">\n <button\n class=\"btn btn-default btn-sm flex-grow\"\n title=\"{{ 'Reset' | translate }}\"\n type=\"button\"\n (click)=\"reset(); dropdown.isOpen = false\"\n [disabled]=\"form.value.realtime || isAutoRefreshEnabled\"\n translate\n >\n Reset\n </button>\n\n <button\n class=\"btn btn-primary btn-sm flex-grow\"\n title=\"{{ 'Apply' | translate }}\"\n type=\"button\"\n (click)=\"applyDatetimeContext(); dropdown.isOpen = false\"\n [disabled]=\"\n (form.pristine && form.untouched) || form.invalid || form.value.realtime || isAutoRefreshEnabled\n \"\n translate\n >\n Apply\n </button>\n </div>\n </ng-container>\n </ul>\n </div>\n </div>\n </ng-container>\n\n <div class=\"input-group w-auto\">\n <c8y-realtime-control\n class=\"form-control p-0 flex-no-grow w-auto\"\n *ngIf=\"displaySettings.globalRealtimeContext\"\n formControlName=\"realtime\"\n ></c8y-realtime-control>\n\n <c8y-aggregation-picker\n *ngIf=\"displaySettings.globalAggregationContext\"\n formControlName=\"aggregation\"\n [disabledAggregations]=\"disabledAggregations\"\n ></c8y-aggregation-picker>\n </div>\n </form>\n</ng-template>\n\n<c8y-dashboard-child-action>\n <button\n type=\"button\"\n (click)=\"toggleDecoupling()\"\n >\n <i [c8yIcon]=\"isCoupled ? 'schedule1' : 'today'\"></i>\n <span class=\"m-l-4\">\n {{ (isCoupled ? decoupleTimeContextLabel : coupleTimeContextLabel) | translate }}\n </span>\n </button>\n</c8y-dashboard-child-action>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: BsDropdownModule }, { kind: "directive", type: i10.BsDropdownMenuDirective, selector: "[bsDropdownMenu],[dropdownMenu]", exportAs: ["bs-dropdown-menu"] }, { kind: "directive", type: i10.BsDropdownToggleDirective, selector: "[bsDropdownToggle],[dropdownToggle]", exportAs: ["bs-dropdown-toggle"] }, { kind: "directive", type: i10.BsDropdownDirective, selector: "[bsDropdown], [dropdown]", inputs: ["placement", "triggers", "container", "dropup", "autoClose", "isAnimated", "insideClick", "isDisabled", "isOpen"], outputs: ["isOpenChange", "onShown", "onHidden"], exportAs: ["bs-dropdown"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i11.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: IntervalPickerComponent, selector: "c8y-interval-picker", inputs: ["INTERVALS"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "component", type: RealtimeControlComponent, selector: "c8y-realtime-control" }, { kind: "component", type: AggregationPickerComponent, selector: "c8y-aggregation-picker", inputs: ["disabledAggregations"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }, { kind: "component", type: DashboardChildActionComponent, selector: "c8y-dashboard-child-action" }, { kind: "component", type: DateTimePickerComponent, selector: "c8y-date-time-picker", inputs: ["minDate", "maxDate", "placeholder", "dateInputFormat", "adaptivePosition", "size", "dateType", "config"], outputs: ["onDateSelected"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: WidgetTimeContextComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-widget-time-context', host: { class: 'd-flex a-i-center gap-4' }, standalone: true, imports: [ NgIf, ActionBarItemComponent, NgTemplateOutlet, FormsModule, ReactiveFormsModule, BsDropdownModule, TooltipModule, IconDirective, IntervalPickerComponent, FormGroupComponent, NgClass, C8yTranslateDirective, MessagesComponent, MessageDirective, RealtimeControlComponent, AggregationPickerComponent, C8yTranslatePipe, DatePipe, DashboardChildActionComponent, DateTimePickerComponent ], template: "<c8y-action-bar-item\n *ngIf=\"isCoupled && !hidden\"\n [priority]=\"ACTION_BAR_PRIORITY\"\n [groupId]=\"ACTION_BAR_GROUP_ID\"\n [inGroupPriority]=\"actionBarInGroupPriority\"\n [placement]=\"'left'\"\n>\n <ng-container\n [ngTemplateOutlet]=\"dateTimePicker\"\n [ngTemplateOutletContext]=\"{\n date: [form.value.currentDateContextFromDate, form.value.currentDateContextToDate]\n }\"\n ></ng-container>\n</c8y-action-bar-item>\n\n<ng-container\n *ngIf=\"!isCoupled\"\n [ngTemplateOutlet]=\"dateTimePicker\"\n [ngTemplateOutletContext]=\"{\n date: [form.value.currentDateContextFromDate, form.value.currentDateContextToDate]\n }\"\n></ng-container>\n\n<ng-template\n #dateTimePicker\n let-date=\"date\"\n>\n <form\n class=\"d-flex gap-16 p-l-xs-16 p-r-xs-16 m-t-xs-8 m-b-xs-8\"\n [formGroup]=\"form\"\n >\n <ng-container *ngIf=\"displaySettings.globalTimeContext\">\n <div>\n <div\n class=\"dropdown flex-grow\"\n #dropdown=\"bs-dropdown\"\n dropdown\n [insideClick]=\"true\"\n *ngIf=\"date\"\n >\n <button\n class=\"dropdown-toggle form-control l-h-tight d-flex a-i-center\"\n attr.aria-label=\"{{ date[0] | c8yDate: DATE_FORMAT }} \u2014 {{\n date[1] | c8yDate: DATE_FORMAT\n }}\"\n tooltip=\"{{ date[0] | c8yDate: DATE_FORMAT }} \u2014 {{ date[1] | c8yDate: DATE_FORMAT }}\"\n placement=\"top\"\n container=\"body\"\n data-cy=\"widget-time-context--date-picker-dropdown-button\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n dropdownToggle\n >\n <i\n class=\"m-r-4\"\n c8yIcon=\"schedule1\"\n ></i>\n <div class=\"d-col text-left fit-w\">\n <span\n class=\"text-12\"\n data-cy=\"widget-time-context--selected-interval\"\n >\n {{ INTERVAL_TITLES[form.controls.currentDateContextInterval.value] | translate }}\n </span>\n <span\n class=\"text-10 text-muted text-truncate\"\n data-cy=\"widget-time-context--selected-time-range\"\n >\n {{ date[0] | c8yDate: DATE_FORMAT }} \u2014 {{ date[1] | c8yDate: DATE_FORMAT }}\n </span>\n </div>\n <span class=\"caret m-r-16 m-l-4\"></span>\n </button>\n\n <ul\n class=\"dropdown-menu dropdown-menu--date-range\"\n *dropdownMenu\n >\n <c8y-interval-picker\n class=\"d-contents\"\n formControlName=\"currentDateContextInterval\"\n ></c8y-interval-picker>\n\n <ng-container *ngIf=\"form.controls.currentDateContextInterval.value === 'custom'\">\n <div class=\"p-l-16 p-r-16\">\n <c8y-form-group\n [ngClass]=\"form.controls.temporaryUserSelectedFromDate.errors ? 'has-error' : ''\"\n >\n <label\n [title]=\"'From`date`' | translate\"\n for=\"temporaryUserSelectedFromDate\"\n translate\n >\n From`date`\n </label>\n <c8y-date-time-picker\n id=\"temporaryUserSelectedFromDate\"\n [maxDate]=\"form.value.temporaryUserSelectedToDate\"\n [placeholder]=\"'From`date`' | translate\"\n [formControl]=\"form.controls.temporaryUserSelectedFromDate\"\n [ngClass]=\"\n form.controls.temporaryUserSelectedFromDate.errors ? 'has-error' : ''\n \"\n ></c8y-date-time-picker>\n <c8y-messages [show]=\"form.controls.temporaryUserSelectedFromDate.errors\">\n <c8y-message\n name=\"dateAfterRangeMax\"\n [text]=\"'This date is after the latest allowed date.' | translate\"\n ></c8y-message>\n <c8y-message\n name=\"invalidDateTime\"\n [text]=\"'This date is invalid.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <c8y-form-group\n [ngClass]=\"form.controls.temporaryUserSelectedToDate.errors ? 'has-error' : ''\"\n >\n <label\n [title]=\"'To`date`' | translate\"\n for=\"temporaryUserSelectedToDate\"\n translate\n >\n To`date`\n </label>\n <c8y-date-time-picker\n id=\"temporaryUserSelectedToDate\"\n [minDate]=\"form.value.temporaryUserSelectedFromDate\"\n [placeholder]=\"'To`date`' | translate\"\n [formControl]=\"form.controls.temporaryUserSelectedToDate\"\n [ngClass]=\"form.controls.temporaryUserSelectedToDate.errors ? 'has-error' : ''\"\n ></c8y-date-time-picker>\n <c8y-messages [show]=\"form.controls.temporaryUserSelectedToDate.errors\">\n <c8y-message\n name=\"dateBeforeRangeMin\"\n [text]=\"'This date is before the earliest allowed date.' | translate\"\n ></c8y-message>\n <c8y-message\n name=\"invalidDateTime\"\n [text]=\"'This date is invalid.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"p-16 d-flex gap-8 separator-top\">\n <button\n class=\"btn btn-default btn-sm flex-grow\"\n title=\"{{ 'Reset' | translate }}\"\n type=\"button\"\n (click)=\"reset(); dropdown.isOpen = false\"\n [disabled]=\"form.value.realtime || isAutoRefreshEnabled\"\n translate\n >\n Reset\n </button>\n\n <button\n class=\"btn btn-primary btn-sm flex-grow\"\n title=\"{{ 'Apply' | translate }}\"\n type=\"button\"\n (click)=\"applyDatetimeContext(); dropdown.isOpen = false\"\n [disabled]=\"\n (form.pristine && form.untouched) || form.invalid || form.value.realtime || isAutoRefreshEnabled\n \"\n translate\n >\n Apply\n </button>\n </div>\n </ng-container>\n </ul>\n </div>\n </div>\n </ng-container>\n\n <div class=\"input-group w-auto\">\n <c8y-realtime-control\n class=\"form-control p-0 flex-no-grow w-auto\"\n *ngIf=\"displaySettings.globalRealtimeContext\"\n formControlName=\"realtime\"\n ></c8y-realtime-control>\n\n <c8y-aggregation-picker\n *ngIf=\"displaySettings.globalAggregationContext\"\n formControlName=\"aggregation\"\n [disabledAggregations]=\"disabledAggregations\"\n ></c8y-aggregation-picker>\n </div>\n </form>\n</ng-template>\n\n<c8y-dashboard-child-action>\n <button\n type=\"button\"\n (click)=\"toggleDecoupling()\"\n >\n <i [c8yIcon]=\"isCoupled ? 'schedule1' : 'today'\"></i>\n <span class=\"m-l-4\">\n {{ (isCoupled ? decoupleTimeContextLabel : coupleTimeContextLabel) | translate }}\n </span>\n </button>\n</c8y-dashboard-child-action>\n" }] }], ctorParameters: () => [{ type: i1.WidgetsDashboardEventService }, { type: i2.DashboardChildComponent }, { type: i3.FormBuilder }, { type: i4.WidgetTimeContextQueryService }, { type: i5.WidgetTimeContextHelperService }, { type: i6.Router }, { type: i7.ActionBarService }, { type: i8.AggregationService }, { type: i6.ActivatedRoute }, { type: i9.WidgetTimeContextDateRangeService }], propDecorators: { canDecouple: [{ type: Input }], displaySettings: [{ type: Input }], hidden: [{ type: Input }], dateContextChange: [{ type: Output }], action: [{ type: ViewChild, args: [DashboardChildActionComponent] }], dropdown: [{ type: ViewChild, args: [BsDropdownDirective] }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2lkZ2V0LXRpbWUtY29udGV4dC5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9jb3JlL2Rhc2hib2FyZC93aWdldC10aW1lLWNvbnRleHQvd2lkZ2V0LXRpbWUtY29udGV4dC5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi8uLi9jb3JlL2Rhc2hib2FyZC93aWdldC10aW1lLWNvbnRleHQvd2lkZ2V0LXRpbWUtY29udGV4dC5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ2xFLE9BQU8sRUFFTCxTQUFTLEVBQ1QsTUFBTSxFQUNOLFlBQVksRUFDWixLQUFLLEVBR0wsTUFBTSxFQUNOLFNBQVMsRUFDVixNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQy9FLE9BQU8sRUFDTCxjQUFjLEVBQ2QsYUFBYSxFQUNiLGFBQWEsRUFDYixlQUFlLEVBQ2YsTUFBTSxFQUNQLE1BQU0saUJBQWlCLENBQUM7QUFDekIsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEVBRUwsZUFBZSxFQUNmLHVCQUF1QixFQUN4QixNQUFNLHFDQUFxQyxDQUFDO0FBQzdDLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQy9FLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUN0RCxPQUFPLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBZ0IsTUFBTSxNQUFNLENBQUM7QUFDdkQsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENB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