/// /// Copyright © 2016-2021 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, Input, OnInit } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { WidgetContext } from '@home/models/widget-component.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; import { Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models'; import { IWidgetSubscription } from '@core/api/widget-api.models'; import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { AttributeService } from '@core/http/attribute.service'; import { AttributeData, AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; import { EntityId } from '@shared/models/id/entity-id'; import { EntityType } from '@shared/models/entity-type.models'; import { createLabelFromDatasource, isDefinedAndNotNull } from '@core/utils'; import { Observable } from 'rxjs'; enum JsonInputWidgetMode { ATTRIBUTE = 'ATTRIBUTE', TIME_SERIES = 'TIME_SERIES', } interface JsonInputWidgetSettings { widgetTitle: string; widgetMode: JsonInputWidgetMode; attributeScope?: AttributeScope; showLabel: boolean; labelValue?: string; attributeRequired: boolean; showResultMessage: boolean; } @Component({ selector: 'tb-json-input-widget ', templateUrl: './json-input-widget.component.html', styleUrls: ['./json-input-widget.component.scss'] }) export class JsonInputWidgetComponent extends PageComponent implements OnInit { @Input() ctx: WidgetContext; public settings: JsonInputWidgetSettings; private widgetConfig: WidgetConfig; private subscription: IWidgetSubscription; private datasource: Datasource; labelValue: string; datasourceDetected = false; errorMessage: string; isFocused: boolean; originalValue: any; attributeUpdateFormGroup: FormGroup; toastTargetId = 'json-input-widget' + this.utils.guid(); constructor(protected store: Store, private utils: UtilsService, private fb: FormBuilder, private attributeService: AttributeService, private translate: TranslateService) { super(store); } ngOnInit(): void { this.ctx.$scope.jsonInputWidget = this; this.settings = this.ctx.settings; this.widgetConfig = this.ctx.widgetConfig; this.subscription = this.ctx.defaultSubscription; this.datasource = this.subscription.datasources[0]; this.initializeConfig(); this.validateDatasources(); this.buildForm(); this.ctx.updateWidgetParams(); } private initializeConfig() { if (this.settings.widgetTitle && this.settings.widgetTitle.length) { const title = createLabelFromDatasource(this.datasource, this.settings.widgetTitle); this.ctx.widgetTitle = this.utils.customTranslation(title, title); } else { this.ctx.widgetTitle = this.ctx.widgetConfig.title; } if (this.settings.labelValue && this.settings.labelValue.length) { const label = createLabelFromDatasource(this.datasource, this.settings.labelValue); this.labelValue = this.utils.customTranslation(label, label); } else { this.labelValue = this.translate.instant('widgets.input-widgets.value'); } } private validateDatasources() { this.datasourceDetected = isDefinedAndNotNull(this.datasource); if (!this.datasourceDetected) { return; } if (this.datasource.type === DatasourceType.entity) { if (this.settings.widgetMode === JsonInputWidgetMode.ATTRIBUTE) { if (this.datasource.dataKeys[0].type === DataKeyType.attribute) { if (this.settings.attributeScope !== AttributeScope.SERVER_SCOPE && this.datasource.entityType !== EntityType.DEVICE) { this.errorMessage = 'widgets.input-widgets.not-allowed-entity'; } } else { this.errorMessage = 'widgets.input-widgets.no-attribute-selected'; } } else { if (this.datasource.dataKeys[0].type !== DataKeyType.timeseries) { this.errorMessage = 'widgets.input-widgets.no-timeseries-selected'; } } } else { this.errorMessage = 'widgets.input-widgets.no-entity-selected'; } } private buildForm() { const validators: ValidatorFn[] = []; if (this.settings.attributeRequired) { validators.push(Validators.required); } this.attributeUpdateFormGroup = this.fb.group({ currentValue: [{}, validators] }); this.attributeUpdateFormGroup.valueChanges.subscribe(() => { this.ctx.detectChanges(); }); } private updateWidgetData(data: Array) { if (!this.errorMessage) { let value = {}; if (data[0].data[0][1] !== '') { try { value = JSON.parse(data[0].data[0][1]); } catch (e) { value = data[0].data[0][1]; } } this.originalValue = value; if (!this.isFocused) { this.attributeUpdateFormGroup.get('currentValue').patchValue(this.originalValue); this.ctx.detectChanges(); } } } public onDataUpdated() { this.updateWidgetData(this.subscription.data); } public save() { this.isFocused = false; const attributeToSave: AttributeData = { key: this.datasource.dataKeys[0].name, value: this.attributeUpdateFormGroup.get('currentValue').value }; const entityId: EntityId = { entityType: this.datasource.entityType, id: this.datasource.entityId }; let saveAttributeObservable: Observable; if (this.settings.widgetMode === JsonInputWidgetMode.ATTRIBUTE) { saveAttributeObservable = this.attributeService.saveEntityAttributes( entityId, this.settings.attributeScope, [attributeToSave], {} ); } else { saveAttributeObservable = this.attributeService.saveEntityTimeseries( entityId, LatestTelemetry.LATEST_TELEMETRY, [attributeToSave], {} ); } saveAttributeObservable.subscribe( () => { this.attributeUpdateFormGroup.markAsPristine(); this.ctx.detectChanges(); if (this.settings.showResultMessage) { this.ctx.showSuccessToast(this.translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', this.toastTargetId); } }, () => { if (this.settings.showResultMessage) { this.ctx.showErrorToast(this.translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', this.toastTargetId); } }); } public discard() { this.attributeUpdateFormGroup.reset({currentValue: this.originalValue}, {emitEvent: false}); this.attributeUpdateFormGroup.markAsPristine(); this.isFocused = false; } }