29 changed files with 1136 additions and 79 deletions
@ -0,0 +1,71 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2025 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. |
|||
|
|||
--> |
|||
<div [formGroup]="limitForm" class="tb-form-table-row overflow-hidden items-start"> |
|||
<div class="flex-shrink capitalize" [class]="isPanelView ? 'basis-10' : 'basis-1/5'"> |
|||
{{ labelKey | translate}} |
|||
</div> |
|||
<div class="flex-grow flex-shrink flex flex-col gap-2 gt-sm:flex-row gt-sm:gap-3" [class]="isPanelView ? 'basis-1/2' : 'basis-40%'"> |
|||
<mat-form-field class="tb-inline-field flex-1" appearance="outline" subscriptSizing="dynamic"> |
|||
<mat-select formControlName="type"> |
|||
<mat-option *ngFor="let type of ValueSourceTypes" [value]="type"> |
|||
{{ ValueSourceTypeTranslation.get(type) | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<tb-entity-alias-input |
|||
required |
|||
class="flex-1" |
|||
*ngIf="limitForm.get('type').value === ValueSourceType.entity" |
|||
[aliasController]="aliasController" |
|||
formControlName="entityAlias"> |
|||
</tb-entity-alias-input> |
|||
</div> |
|||
<div class="flex-grow flex-shrink" [class]="isPanelView ? 'basis-1/2' : 'basis-40%'"> |
|||
<mat-form-field *ngIf="limitForm.get('type').value === ValueSourceType.constant" |
|||
appearance="outline" class="tb-inline-field !w-full number" subscriptSizing="dynamic" |
|||
[class.tb-suffix-absolute]="!limitForm.get('value').value"> |
|||
<input matInput formControlName="value" type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}"> |
|||
</mat-form-field> |
|||
<tb-data-key-input |
|||
required |
|||
*ngIf="limitForm.get('type').value === ValueSourceType.latestKey" |
|||
requiredText="widgets.chart.chart-axis.key-required" |
|||
[datasourceType]="datasource?.type" |
|||
[entityAliasId]="datasource?.entityAliasId" |
|||
[deviceId]="datasource?.deviceId" |
|||
[aliasController]="aliasController" |
|||
[dataKeyType]="datasource?.type === DatasourceType.function ? DataKeyType.function : null" |
|||
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" |
|||
[callbacks]="callbacks" |
|||
[editable]="false" |
|||
formControlName="value"> |
|||
</tb-data-key-input> |
|||
<tb-data-key-input |
|||
required |
|||
*ngIf="limitForm.get('type').value === ValueSourceType.entity" |
|||
requiredText="widgets.chart.chart-axis.entity-key-required" |
|||
[datasourceType]="DatasourceType.entity" |
|||
[aliasController]="aliasController" |
|||
[entityAlias]="limitForm.get('entityAlias').value" |
|||
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" |
|||
[callbacks]="callbacks" |
|||
[editable]="false" |
|||
formControlName="value"> |
|||
</tb-data-key-input> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,15 @@ |
|||
/** |
|||
* Copyright © 2016-2025 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. |
|||
*/ |
|||
@ -0,0 +1,204 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 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, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; |
|||
import { |
|||
ControlValueAccessor, NG_VALIDATORS, |
|||
NG_VALUE_ACCESSOR, |
|||
UntypedFormBuilder, |
|||
UntypedFormGroup, ValidationErrors, Validator, |
|||
Validators, |
|||
} from '@angular/forms'; |
|||
import { ValueSourceType, ValueSourceTypes, ValueSourceTypeTranslation } from '@shared/models/widget-settings.models'; |
|||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
|||
import { AxisLimitConfig } from '@home/components/widget/lib/chart/time-series-chart.models'; |
|||
import { Datasource, DatasourceType, } from '@shared/models/widget.models'; |
|||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; |
|||
import { IAliasController } from '@core/api/widget-api.models'; |
|||
import { DataKeysCallbacks } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; |
|||
import { isEqual } from '@core/utils'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-axis-scale-row', |
|||
templateUrl: './axis-scale-row.component.html', |
|||
styleUrl: './axis-scale-row.component.scss', |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => AxisScaleRowComponent), |
|||
multi: true |
|||
}, |
|||
{ |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => AxisScaleRowComponent), |
|||
multi: true |
|||
}, |
|||
] |
|||
}) |
|||
export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Validator { |
|||
|
|||
@Input() |
|||
isPanelView = false; |
|||
|
|||
@Input() |
|||
aliasController: IAliasController; |
|||
|
|||
@Input() |
|||
callbacks: DataKeysCallbacks; |
|||
|
|||
@Input() |
|||
datasource: Datasource; |
|||
|
|||
@Input() |
|||
labelKey: string; |
|||
|
|||
ValueSourceType = ValueSourceType; |
|||
|
|||
DataKeyType = DataKeyType; |
|||
|
|||
DatasourceType = DatasourceType; |
|||
|
|||
ValueSourceTypeTranslation = ValueSourceTypeTranslation; |
|||
|
|||
ValueSourceTypes = ValueSourceTypes; |
|||
|
|||
limitForm: UntypedFormGroup; |
|||
|
|||
private propagateChanges: (value: any) => void = () => {}; |
|||
|
|||
private modelValue: AxisLimitConfig | null = null; |
|||
|
|||
constructor(private fb: UntypedFormBuilder, |
|||
private destroyRef: DestroyRef) { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.limitForm = this.fb.group({ |
|||
type: [ValueSourceType.constant], |
|||
value: [null], |
|||
entityAlias: [null] |
|||
}); |
|||
this.subscribeToTypeChanges(); |
|||
|
|||
this.limitForm.valueChanges |
|||
.pipe(takeUntilDestroyed(this.destroyRef)) |
|||
.subscribe(value => { |
|||
this.updateValidators(); |
|||
this.updateView(value); |
|||
}); |
|||
} |
|||
|
|||
writeValue(value: AxisLimitConfig) { |
|||
this.modelValue = value; |
|||
|
|||
if (!this.limitForm) { |
|||
return; |
|||
} |
|||
|
|||
if (value) { |
|||
this.limitForm.patchValue({ |
|||
type: value.type ?? ValueSourceType.constant, |
|||
value: value.value ?? null, |
|||
entityAlias: value.entityAlias ?? null |
|||
}, |
|||
{ emitEvent: false } |
|||
); |
|||
} else { |
|||
this.limitForm.patchValue({ |
|||
type: ValueSourceType.constant, |
|||
value: null, |
|||
entityAlias: null |
|||
}, |
|||
{ emitEvent: false } |
|||
); |
|||
} |
|||
|
|||
this.updateValidators(); |
|||
} |
|||
|
|||
registerOnChange(fn: any) { |
|||
this.propagateChanges = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any) { |
|||
} |
|||
|
|||
validate(): ValidationErrors | null { |
|||
return this.limitForm.valid ? null : { |
|||
axisLimitForm: { |
|||
valid: false, |
|||
errors: this.getFormErrors() |
|||
} |
|||
}; |
|||
} |
|||
|
|||
private getFormErrors(): any { |
|||
const errors: any = {}; |
|||
Object.keys(this.limitForm.controls).forEach(key => { |
|||
const control = this.limitForm.get(key); |
|||
if (control && control.errors) { |
|||
errors[key] = control.errors; |
|||
} |
|||
}); |
|||
return errors; |
|||
} |
|||
|
|||
private updateValidators() { |
|||
const axisTypeControl = this.limitForm.get('type'); |
|||
const axisValueControl = this.limitForm.get('value'); |
|||
const axisEntityAliasControl = this.limitForm.get('entityAlias'); |
|||
|
|||
if (axisTypeControl && axisValueControl && axisEntityAliasControl) { |
|||
const type = axisTypeControl.value; |
|||
if (type === ValueSourceType.latestKey || type === ValueSourceType.entity) { |
|||
axisValueControl.setValidators([Validators.required]); |
|||
} else { |
|||
axisValueControl.clearValidators(); |
|||
} |
|||
|
|||
if (type === ValueSourceType.entity) { |
|||
axisEntityAliasControl.setValidators([Validators.required]); |
|||
} else { |
|||
axisEntityAliasControl.clearValidators(); |
|||
} |
|||
|
|||
axisValueControl.updateValueAndValidity({ emitEvent: false }); |
|||
axisEntityAliasControl.updateValueAndValidity({ emitEvent: false }); |
|||
} |
|||
} |
|||
|
|||
private subscribeToTypeChanges() { |
|||
this.limitForm.controls.type.valueChanges |
|||
.pipe(takeUntilDestroyed(this.destroyRef)) |
|||
.subscribe(() => { |
|||
const axisValueControl = this.limitForm.get('value'); |
|||
const axisEntityAliasControl = this.limitForm.get('entityAlias'); |
|||
if (axisValueControl) { |
|||
axisValueControl.setValue(null, { emitEvent: true }); |
|||
} |
|||
if (axisEntityAliasControl) { |
|||
axisEntityAliasControl.setValue(null, { emitEvent: true }); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private updateView(value: AxisLimitConfig) { |
|||
if (!isEqual(this.modelValue, value)) { |
|||
this.modelValue = value; |
|||
this.propagateChanges(value); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue