7 changed files with 362 additions and 3 deletions
@ -0,0 +1,69 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
|
||||
|
<div class="tb-json-input" tb-toast toastTarget="{{ toastTargetId }}"> |
||||
|
<form *ngIf="attributeUpdateFormGroup" |
||||
|
fxLayout="column" |
||||
|
class="tb-json-input__form" |
||||
|
[formGroup]="attributeUpdateFormGroup" |
||||
|
(ngSubmit)="save()"> |
||||
|
<div fxLayout="column" fxLayoutGap="10px" fxFlex *ngIf="entityDetected && isValidParameter && dataKeyDetected"> |
||||
|
<fieldset fxFlex> |
||||
|
<tb-json-object-edit |
||||
|
[editorStyle]="{minHeight: '100px'}" |
||||
|
fillHeight="true" |
||||
|
[required]="settings.attributeRequired" |
||||
|
label="{{ settings.showLabel ? labelValue : '' }}" |
||||
|
formControlName="currentValue" |
||||
|
(focusin)="isFocused = true;" |
||||
|
(focusout)="isFocused = false;" |
||||
|
></tb-json-object-edit> |
||||
|
</fieldset> |
||||
|
<div class="tb-json-input-form__actions" fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="20px"> |
||||
|
<button mat-button color="primary" |
||||
|
type="button" |
||||
|
[disabled]="!attributeUpdateFormGroup.dirty" |
||||
|
(click)="discard()" |
||||
|
matTooltip="{{ 'widgets.input-widgets.discard-changes' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
{{ "action.undo" | translate }} |
||||
|
</button> |
||||
|
<button mat-button mat-raised-button color="primary" |
||||
|
type="submit" |
||||
|
[disabled]="attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty"> |
||||
|
{{ "action.save" | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div fxLayout="column" fxLayoutAlign="center center" fxFlex *ngIf="!entityDetected || !dataKeyDetected || !isValidParameter"> |
||||
|
<div class="tb-json-input__error" |
||||
|
*ngIf="!entityDetected"> |
||||
|
{{ 'widgets.input-widgets.no-entity-selected' | translate }} |
||||
|
</div> |
||||
|
<div class="tb-json-input__error" |
||||
|
*ngIf="entityDetected && !dataKeyDetected"> |
||||
|
{{ 'widgets.input-widgets.no-datakey-selected' | translate }} |
||||
|
</div> |
||||
|
<div class="tb-json-input__error" |
||||
|
*ngIf="dataKeyDetected && !isValidParameter"> |
||||
|
{{ errorMessage | translate }} |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
@ -0,0 +1,36 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
.tb-json-input { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
padding: 5px; |
||||
|
|
||||
|
&__form { |
||||
|
overflow: auto; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
&__error { |
||||
|
text-align: center; |
||||
|
font-size: 18px; |
||||
|
color: #a0a0a0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-toast { |
||||
|
font-size: 14px!important; |
||||
|
} |
||||
@ -0,0 +1,227 @@ |
|||||
|
///
|
||||
|
/// 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 } 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; |
||||
|
|
||||
|
entityDetected = false; |
||||
|
dataKeyDetected = false; |
||||
|
isValidParameter = false; |
||||
|
errorMessage: string; |
||||
|
|
||||
|
isFocused: boolean; |
||||
|
originalValue: any; |
||||
|
attributeUpdateFormGroup: FormGroup; |
||||
|
|
||||
|
toastTargetId = 'json-input-widget' + this.utils.guid(); |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
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() { |
||||
|
if (this.datasource?.type === DatasourceType.entity) { |
||||
|
this.entityDetected = true; |
||||
|
if (this.datasource.dataKeys.length) { |
||||
|
this.dataKeyDetected = true; |
||||
|
|
||||
|
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.isValidParameter = true; |
||||
|
} else { |
||||
|
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.isValidParameter = true; |
||||
|
} else { |
||||
|
this.errorMessage = 'widgets.input-widgets.no-timeseries-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<DatasourceData>) { |
||||
|
if (this.isValidParameter) { |
||||
|
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<any>; |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue