diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index a7d2e9ffae..512719c513 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -337,6 +337,7 @@ import * as RuleChainSelectComponent from '@shared/components/rule-chain/rule-ch import { IModulesMap } from '@modules/common/modules-map.models'; import { TimezoneComponent } from '@shared/components/time/timezone.component'; import { TimezonePanelComponent } from '@shared/components/time/timezone-panel.component'; +import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; declare const System; @@ -470,6 +471,7 @@ class ModulesMap implements IModulesMap { '@shared/components/time/timezone-select.component': TimezoneSelectComponent, '@shared/components/time/timezone.component': TimezoneComponent, '@shared/components/time/timezone-panel.component': TimezonePanelComponent, + '@shared/components/time/datapoints-limit': DatapointsLimitComponent, '@shared/components/value-input.component': ValueInputComponent, '@shared/components/dashboard-autocomplete.component': DashboardAutocompleteComponent, '@shared/components/entity/entity-subtype-autocomplete.component': EntitySubTypeAutocompleteComponent, diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.html b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.html new file mode 100644 index 0000000000..8e2a5ac459 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.html @@ -0,0 +1,30 @@ + +
+ + + + + + +
diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss new file mode 100644 index 0000000000..c2ffa0bb34 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../scss/constants"; + +.limit-slider-container { + width: 100%; + .limit-slider-value { + margin-left: 16px; + min-width: 25px; + max-width: 106px; + } + mat-form-field input[type=number] { + text-align: center; + } +} + +@media #{$mat-gt-sm} { + .limit-slider-container { + > label { + margin-right: 16px; + width: min-content; + max-width: 40%; + } + } +} diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts new file mode 100644 index 0000000000..21aff5a647 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.ts @@ -0,0 +1,138 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR, UntypedFormGroup, Validators } from '@angular/forms'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { getTimezoneInfo } from '@shared/models/time/time.models'; +import { TimeService } from '@core/services/time.service'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'tb-datapoints-limit', + templateUrl: './datapoints-limit.component.html', + styleUrls: ['./datapoints-limit.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DatapointsLimitComponent), + multi: true + }] +}) +export class DatapointsLimitComponent implements ControlValueAccessor, OnInit, OnDestroy { + + datapointsLimitFormGroup: UntypedFormGroup; + + modelValue: number | null; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + const newVal = coerceBooleanProperty(value); + if (this.requiredValue !== newVal) { + this.requiredValue = newVal; + this.updateValidators(); + } + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder, + private timeService: TimeService) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.datapointsLimitFormGroup = this.fb.group({ + limit: [null] + }); + this.datapointsLimitFormGroup.get('limit').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.updateView(value); + }); + } + + updateValidators() { + if (this.datapointsLimitFormGroup) { + this.datapointsLimitFormGroup.get('limit').setValidators(this.required + ? [Validators.required, Validators.min(this.minDatapointsLimit()), + Validators.max(this.maxDatapointsLimit())] + : []); + this.datapointsLimitFormGroup.get('limit').updateValueAndValidity(); + } + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.datapointsLimitFormGroup.disable({emitEvent: false}); + } else { + this.datapointsLimitFormGroup.enable({emitEvent: false}); + } + } + + private checkLimit(limit?: number): number { + if (!limit || limit < this.minDatapointsLimit()) { + return this.minDatapointsLimit(); + } else if (limit > this.maxDatapointsLimit()) { + return this.maxDatapointsLimit(); + } + return limit; + } + + writeValue(value: number | null): void { + this.modelValue = this.checkLimit(value); + this.datapointsLimitFormGroup.patchValue( + { limit: this.modelValue }, {emitEvent: false} + ); + } + + updateView(value: number | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + minDatapointsLimit() { + return this.timeService.getMinDatapointsLimit(); + } + + maxDatapointsLimit() { + return this.timeService.getMaxDatapointsLimit(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html index e36996197c..333c70f24f 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -173,19 +173,11 @@ {{ 'timewindow.hide' | translate }} -
- - - - - - -
+ + + + diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index ba7ee3e58c..cb16d7694a 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -191,7 +191,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On }), aggregation: this.fb.group({ type: [ isDefined(aggregation?.type) ? this.timewindow.aggregation.type : null ], - limit: [ isDefined(aggregation?.limit) ? this.checkLimit(this.timewindow.aggregation.limit) : null ] + limit: [ isDefined(aggregation?.limit) ? this.timewindow.aggregation.limit : null ] }), timezone: [ isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null ], hideAggregation: [ isDefinedAndNotNull(this.timewindow.hideAggregation) @@ -292,15 +292,6 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.destroy$.complete(); } - private checkLimit(limit?: number): number { - if (!limit || limit < this.minDatapointsLimit()) { - return this.minDatapointsLimit(); - } else if (limit > this.maxDatapointsLimit()) { - return this.maxDatapointsLimit(); - } - return limit; - } - private updateValidators(aggType: AggregationType) { if (aggType !== AggregationType.NONE) { this.timewindowForm.get('aggregation.limit').clearValidators(); diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss index 0aca6157a6..2379910a79 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-form.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import "../../../../scss/constants"; :host { background-color: #fff; @@ -32,32 +31,10 @@ tb-timeinterval, tb-quick-time-interval, - tb-datetime-period { + tb-datetime-period, + tb-datapoints-limit { flex: 1; } } } - - .limit-slider-container { - width: 100%; - .limit-slider-value { - margin-left: 16px; - min-width: 25px; - max-width: 106px; - } - mat-form-field input[type=number] { - text-align: center; - } - } - - @media #{$mat-gt-sm} { - .limit-slider-container { - > label { - margin-right: 16px; - width: min-content; - max-width: 40%; - } - } - } - } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 0b93857a81..e631eff949 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -137,19 +137,9 @@
{{ 'aggregation.limit' | translate }}
-
- - - - - - -
+ +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 8d51d2ec00..7b0286383a 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -275,7 +275,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O disabled: hideAggregation }], limit: [{ - value: isDefined(aggregation?.limit) ? this.checkLimit(aggregation.limit) : null, + value: isDefined(aggregation?.limit) ? aggregation.limit : null, disabled: hideAggInterval }, []] }), @@ -303,15 +303,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O this.destroy$.complete(); } - private checkLimit(limit?: number): number { - if (!limit || limit < this.minDatapointsLimit()) { - return this.minDatapointsLimit(); - } else if (limit > this.maxDatapointsLimit()) { - return this.maxDatapointsLimit(); - } - return limit; - } - private updateValidators(aggType: AggregationType) { if (aggType !== AggregationType.NONE) { this.timewindowForm.get('aggregation.limit').clearValidators(); diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 5afaf80fdb..130e272c21 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -228,6 +228,7 @@ import { ScadaSymbolInputComponent } from '@shared/components/image/scada-symbol import { CountryAutocompleteComponent } from '@shared/components/country-autocomplete.component'; import { CountryData } from '@shared/models/country.models'; import { SvgXmlComponent } from '@shared/components/svg-xml.component'; +import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -317,6 +318,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TimezoneComponent, TimezonePanelComponent, QuickTimeIntervalComponent, + DatapointsLimitComponent, DashboardSelectComponent, DashboardSelectPanelComponent, DatetimePeriodComponent, @@ -529,6 +531,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TimezoneComponent, TimezonePanelComponent, QuickTimeIntervalComponent, + DatapointsLimitComponent, DashboardSelectComponent, DatetimePeriodComponent, DatetimeComponent,