Browse Source

UI: Add GPIO widgets settings forms.

pull/6545/head
Igor Kulikov 4 years ago
parent
commit
aebe4c5b20
  1. 12
      application/src/main/data/json/system/widget_bundles/gpio_widgets.json
  2. 1
      ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html
  3. 2
      ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts
  4. 1
      ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html
  5. 4
      ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts
  6. 1
      ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts
  7. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.html
  8. 25
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.ts
  9. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.html
  10. 29
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.ts
  11. 65
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts
  12. 96
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.html
  13. 145
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.ts
  14. 73
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.html
  15. 40
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.scss
  16. 150
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.ts
  17. 55
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.html
  18. 114
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.ts
  19. 21
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  20. 2
      ui-ngx/src/app/shared/components/json-content.component.html
  21. 9
      ui-ngx/src/app/shared/components/json-content.component.ts
  22. 7
      ui-ngx/src/app/shared/models/widget.models.ts
  23. 20
      ui-ngx/src/assets/locale/locale.constant-en_US.json

12
application/src/main/data/json/system/widget_bundles/gpio_widgets.json

File diff suppressed because one or more lines are too long

1
ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html

@ -34,6 +34,7 @@
[dataKeySettingsDirective]="data.dataKeySettingsDirective"
[entityAliasId]="data.entityAliasId"
[dashboard]="data.dashboard"
[aliasController]="data.aliasController"
[widget]="data.widget"
[showPostProcessing]="data.showPostProcessing"
[callbacks]="data.callbacks"

2
ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts

@ -26,12 +26,14 @@ import { DataKey, Widget } from '@shared/models/widget.models';
import { DataKeysCallbacks } from './data-keys.component.models';
import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component';
import { Dashboard } from '@shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models';
export interface DataKeyConfigDialogData {
dataKey: DataKey;
dataKeySettingsSchema: any;
dataKeySettingsDirective: string;
dashboard: Dashboard;
aliasController: IAliasController;
widget: Widget;
entityAliasId?: string;
showPostProcessing?: boolean;

1
ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html

@ -101,6 +101,7 @@
<tb-widget-settings
[settingsDirective]="dataKeySettingsDirective"
[dashboard]="dashboard"
[aliasController]="aliasController"
[widget]="widget"
formControlName="settings">
</tb-widget-settings>

4
ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts

@ -42,6 +42,7 @@ import { JsFuncComponent } from '@shared/components/js-func.component';
import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models';
import { WidgetService } from '@core/http/widget.service';
import { Dashboard } from '@shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models';
@Component({
selector: 'tb-data-key-config',
@ -73,6 +74,9 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
@Input()
dashboard: Dashboard;
@Input()
aliasController: IAliasController;
@Input()
widget: Widget;

1
ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts

@ -422,6 +422,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
dataKeySettingsSchema: this.datakeySettingsSchema,
dataKeySettingsDirective: this.dataKeySettingsDirective,
dashboard: this.dashboard,
aliasController: this.aliasController,
widget: this.widget,
entityAliasId: this.entityAliasId,
showPostProcessing: this.widgetType !== widgetType.alarm,

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.html

@ -25,7 +25,7 @@
<div fxLayout="column">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="labelDrop($event)">
<div cdkDrag class="tb-draggable" *ngFor="let labelControl of labelsFormArray().controls; trackBy: trackByLabel;
<div cdkDrag class="tb-draggable" *ngFor="let labelControl of labelsFormArray().controls; trackBy: trackByLabelControl;
let $index = index; last as isLast;"
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-label-widget-label [formControl]="labelControl"

25
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-settings.component.ts

@ -48,25 +48,32 @@ export class LabelWidgetSettingsComponent extends WidgetSettingsComponent {
}
protected onSettingsSet(settings: WidgetSettings) {
this.labelWidgetSettingsForm = this.fb.group({
backgroundImageUrl: [settings.backgroundImageUrl, [Validators.required]],
labels: this.prepareLabelsFormArray(settings.labels)
});
}
protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) {
settingsForm.setControl('labels', this.prepareLabelsFormArray(settings.labels), {emitEvent: false});
}
private prepareLabelsFormArray(labels: LabelWidgetLabel[] | undefined): FormArray {
const labelsControls: Array<AbstractControl> = [];
if (settings.labels) {
settings.labels.forEach((label) => {
if (labels) {
labels.forEach((label) => {
labelsControls.push(this.fb.control(label, [Validators.required]));
});
}
this.labelWidgetSettingsForm = this.fb.group({
backgroundImageUrl: [settings.backgroundImageUrl, [Validators.required]],
labels: this.fb.array(labelsControls)
});
return this.fb.array(labelsControls);
}
labelsFormArray(): FormArray {
return this.labelWidgetSettingsForm.get('labels') as FormArray;
}
public trackByLabel(index: number, labelControl: AbstractControl): any {
const label: LabelWidgetLabel = labelControl.value;
return label;
public trackByLabelControl(index: number, labelControl: AbstractControl): any {
return labelControl;
}
public removeLabel(index: number) {

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.html

@ -204,7 +204,7 @@
<div fxLayout="column">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="highlightDrop($event)">
<div cdkDrag class="tb-draggable" *ngFor="let highlightControl of highlightsFormArray().controls; trackBy: trackByHighlight;
<div cdkDrag class="tb-draggable" *ngFor="let highlightControl of highlightsFormArray().controls; trackBy: trackByHighlightControl;
let $index = index; last as isLast;"
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-gauge-highlight [formControl]="highlightControl"

29
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.ts

@ -19,7 +19,7 @@ import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { GaugeHighlight } from '@home/components/widget/lib/settings/gauge/gauge-highlight.component';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
export class AnalogueGaugeWidgetSettingsComponent extends WidgetSettingsComponent {
@ -102,12 +102,6 @@ export class AnalogueGaugeWidgetSettingsComponent extends WidgetSettingsComponen
}
protected onSettingsSet(settings: WidgetSettings) {
const highlightsControls: Array<AbstractControl> = [];
if (settings.highlights) {
settings.highlights.forEach((highlight) => {
highlightsControls.push(this.fb.control(highlight, [Validators.required]));
});
}
this.analogueGaugeWidgetSettingsForm = this.fb.group({
// Radial gauge settings
@ -155,7 +149,7 @@ export class AnalogueGaugeWidgetSettingsComponent extends WidgetSettingsComponen
// Highlights settings
highlightsWidth: [settings.highlightsWidth, [Validators.min(0)]],
highlights: this.fb.array(highlightsControls),
highlights: this.prepareHighlightsFormArray(settings.highlights),
// Animation settings
animation: [settings.animation, []],
@ -214,13 +208,26 @@ export class AnalogueGaugeWidgetSettingsComponent extends WidgetSettingsComponen
this.analogueGaugeWidgetSettingsForm.get('animationRule').updateValueAndValidity({emitEvent});
}
protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) {
settingsForm.setControl('highlights', this.prepareHighlightsFormArray(settings.highlights), {emitEvent: false});
}
private prepareHighlightsFormArray(highlights: GaugeHighlight[] | undefined): FormArray {
const highlightsControls: Array<AbstractControl> = [];
if (highlights) {
highlights.forEach((highlight) => {
highlightsControls.push(this.fb.control(highlight, [Validators.required]));
});
}
return this.fb.array(highlightsControls);
}
highlightsFormArray(): FormArray {
return this.analogueGaugeWidgetSettingsForm.get('highlights') as FormArray;
}
public trackByHighlight(index: number, highlightControl: AbstractControl): any {
const highlight: GaugeHighlight = highlightControl.value;
return highlight;
public trackByHighlightControl(index: number, highlightControl: AbstractControl): any {
return highlightControl;
}
public removeHighlight(index: number) {

65
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts

@ -21,7 +21,6 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { GaugeHighlight } from '@home/components/widget/lib/settings/gauge/gauge-highlight.component';
import {
FixedColorLevel,
fixedColorLevelValidator
@ -108,26 +107,6 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
}
protected onSettingsSet(settings: WidgetSettings) {
const levelColorsControls: Array<AbstractControl> = [];
const fixedLevelColorsControls: Array<AbstractControl> = [];
const ticksValueControls: Array<AbstractControl> = [];
if (settings.levelColors) {
settings.levelColors.forEach((levelColor) => {
levelColorsControls.push(this.fb.control(levelColor, [Validators.required]));
});
}
if (settings.fixedLevelColors) {
settings.fixedLevelColors.forEach((fixedLevelColor) => {
fixedLevelColorsControls.push(this.fb.control(fixedLevelColor, [fixedColorLevelValidator]));
});
}
if (settings.ticksValue) {
settings.ticksValue.forEach((tickValue) => {
ticksValueControls.push(this.fb.control(tickValue, [Validators.required]));
});
}
this.digitalGaugeWidgetSettingsForm = this.fb.group({
// Common gauge settings
@ -146,8 +125,8 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
// Gauge bar colors settings
gaugeColor: [settings.gaugeColor, []],
useFixedLevelColor: [settings.useFixedLevelColor, []],
levelColors: this.fb.array(levelColorsControls),
fixedLevelColors: this.fb.array(fixedLevelColorsControls),
levelColors: this.prepareLevelColorFormArray(settings.levelColors),
fixedLevelColors: this.prepareFixedLevelColorFormArray(settings.fixedLevelColors),
// Title settings
showTitle: [settings.showTitle, []],
@ -173,7 +152,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
showTicks: [settings.showTicks, []],
tickWidth: [settings.tickWidth, [Validators.min(0)]],
colorTicks: [settings.colorTicks, []],
ticksValue: this.fb.array(ticksValueControls),
ticksValue: this.prepareTicksValueFormArray(settings.ticksValue),
// Animation settings
animation: [settings.animation, []],
@ -275,12 +254,48 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
this.digitalGaugeWidgetSettingsForm.get('animationRule').updateValueAndValidity({emitEvent});
}
protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) {
settingsForm.setControl('levelColors', this.prepareLevelColorFormArray(settings.levelColors), {emitEvent: false});
settingsForm.setControl('fixedLevelColors', this.prepareFixedLevelColorFormArray(settings.fixedLevelColors), {emitEvent: false});
settingsForm.setControl('ticksValue', this.prepareTicksValueFormArray(settings.ticksValue), {emitEvent: false});
}
private prepareLevelColorFormArray(levelColors: string[] | undefined): FormArray {
const levelColorsControls: Array<AbstractControl> = [];
if (levelColors) {
levelColors.forEach((levelColor) => {
levelColorsControls.push(this.fb.control(levelColor, [Validators.required]));
});
}
return this.fb.array(levelColorsControls);
}
private prepareFixedLevelColorFormArray(fixedLevelColors: FixedColorLevel[] | undefined): FormArray {
const fixedLevelColorsControls: Array<AbstractControl> = [];
if (fixedLevelColors) {
fixedLevelColors.forEach((fixedLevelColor) => {
fixedLevelColorsControls.push(this.fb.control(fixedLevelColor, [fixedColorLevelValidator]));
});
}
return this.fb.array(fixedLevelColorsControls);
}
private prepareTicksValueFormArray(ticksValue: ValueSourceProperty[] | undefined): FormArray {
const ticksValueControls: Array<AbstractControl> = [];
if (ticksValue) {
ticksValue.forEach((tickValue) => {
ticksValueControls.push(this.fb.control(tickValue, [Validators.required]));
});
}
return this.fb.array(ticksValueControls);
}
levelColorsFormArray(): FormArray {
return this.digitalGaugeWidgetSettingsForm.get('levelColors') as FormArray;
}
public trackByLevelColor(index: number, levelColorControl: AbstractControl): any {
return index;
return levelColorControl;
}
public removeLevelColor(index: number) {

96
ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.html

@ -0,0 +1,96 @@
<!--
Copyright © 2016-2022 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.
-->
<section class="tb-widget-settings" [formGroup]="gpioControlWidgetSettingsForm" fxLayout="column">
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gpio.panel-settings</legend>
<tb-color-input
required
formControlName="switchPanelBackgroundColor"
icon="format_color_fill"
label="{{ 'widgets.gpio.background-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gpio.gpio-switches</legend>
<div fxLayout="column">
<div class="tb-control-list">
<div *ngFor="let gpioItemControl of gpioListFormArray().controls; trackBy: trackByGpioItem;
let $index = index; last as isLast;"
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-gpio-item [formControl]="gpioItemControl"
[expanded]="gpioItemControl.new"
(removeGpioItem)="removeGpioItem($index)">
</tb-gpio-item>
</div>
</div>
<div *ngIf="!gpioListFormArray().controls.length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">widgets.gpio.no-gpio-switches</span>
</div>
<div style="padding-top: 16px;">
<button mat-raised-button color="primary"
type="button"
(click)="addGpioItem()">
<span translate>widgets.gpio.add-gpio-switch</span>
</button>
</div>
</div>
</fieldset>
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.rpc.rpc-settings</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.rpc.request-timeout</mat-label>
<input required matInput type="number" min="0" formControlName="requestTimeout">
</mat-form-field>
<fieldset class="fields-group" formGroupName="gpioStatusRequest">
<legend class="group-title" translate>widgets.gpio.gpio-status-request</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gpio.method-name</mat-label>
<input required matInput formControlName="method">
</mat-form-field>
<tb-json-content
required
label="{{ 'widgets.gpio.method-body' | translate }}"
[contentType]="contentTypes.JSON"
validateOnChange
formControlName="paramsBody">
</tb-json-content>
</fieldset>
<fieldset class="fields-group" formGroupName="gpioStatusChangeRequest">
<legend class="group-title" translate>widgets.gpio.gpio-status-change-request</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gpio.method-name</mat-label>
<input required matInput formControlName="method">
</mat-form-field>
<tb-json-content
required
label="{{ 'widgets.gpio.method-body' | translate }}"
[contentType]="contentTypes.JSON"
validateOnChange
formControlName="paramsBody">
</tb-json-content>
</fieldset>
<tb-js-func formControlName="parseGpioStatusFunction"
required
[globalVariables]="functionScopeVariables"
[functionArgs]="['body', 'pin']"
functionTitle="{{ 'widgets.gpio.parse-gpio-status-function' | translate }}"
helpId="widget/lib/rpc/parse_gpio_status_fn">
</tb-js-func>
</fieldset>
</section>

145
ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component.ts

@ -0,0 +1,145 @@
///
/// Copyright © 2016-2022 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 } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { GpioItem, gpioItemValidator } from '@home/components/widget/lib/settings/gpio/gpio-item.component';
import { ContentType } from '@shared/models/constants';
@Component({
selector: 'tb-gpio-control-widget-settings',
templateUrl: './gpio-control-widget-settings.component.html',
styleUrls: ['./../widget-settings.scss']
})
export class GpioControlWidgetSettingsComponent extends WidgetSettingsComponent {
gpioControlWidgetSettingsForm: FormGroup;
contentTypes = ContentType;
constructor(protected store: Store<AppState>,
private fb: FormBuilder) {
super(store);
}
protected settingsForm(): FormGroup {
return this.gpioControlWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {
switchPanelBackgroundColor: '#008a00',
gpioList: [],
requestTimeout: 500,
gpioStatusRequest: {
method: 'getGpioStatus',
paramsBody: '{}'
},
gpioStatusChangeRequest: {
method: 'setGpioStatus',
paramsBody: '{\n "pin": "{$pin}",\n "enabled": "{$enabled}"\n}'
},
parseGpioStatusFunction: 'return body[pin] === true;'
};
}
protected onSettingsSet(settings: WidgetSettings) {
this.gpioControlWidgetSettingsForm = this.fb.group({
// Panel settings
switchPanelBackgroundColor: [settings.switchPanelBackgroundColor, [Validators.required]],
// --> GPIO switches
gpioList: this.prepareGpioListFormArray(settings.gpioList),
// RPC settings
requestTimeout: [settings.requestTimeout, [Validators.min(0), Validators.required]],
// --> GPIO status request
gpioStatusRequest: this.fb.group({
method: [settings.gpioStatusRequest?.method, [Validators.required]],
paramsBody: [settings.gpioStatusRequest?.paramsBody, [Validators.required]]
}, {validators: [Validators.required]}),
// --> GPIO status change request
gpioStatusChangeRequest: this.fb.group({
method: [settings.gpioStatusChangeRequest?.method, [Validators.required]],
paramsBody: [settings.gpioStatusChangeRequest?.paramsBody, [Validators.required]]
}, {validators: [Validators.required]}),
parseGpioStatusFunction: [settings.parseGpioStatusFunction, [Validators.required]]
});
}
protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) {
settingsForm.setControl('gpioList', this.prepareGpioListFormArray(settings.gpioList), {emitEvent: false});
}
private prepareGpioListFormArray(gpioList: GpioItem[] | undefined): FormArray {
const gpioListControls: Array<AbstractControl> = [];
if (gpioList) {
gpioList.forEach((gpioItem) => {
gpioListControls.push(this.fb.control(gpioItem, [gpioItemValidator(false)]));
});
}
return this.fb.array(gpioListControls, [(control: AbstractControl) => {
const gpioItems = control.value;
if (!gpioItems || !gpioItems.length) {
return {
gpioItems: true
};
}
return null;
}]);
}
gpioListFormArray(): FormArray {
return this.gpioControlWidgetSettingsForm.get('gpioList') as FormArray;
}
public trackByGpioItem(index: number, gpioItemControl: AbstractControl): any {
return gpioItemControl;
}
public removeGpioItem(index: number) {
(this.gpioControlWidgetSettingsForm.get('gpioList') as FormArray).removeAt(index);
}
public addGpioItem() {
const gpioItem: GpioItem = {
pin: null,
label: null,
row: null,
col: null
};
const gpioListArray = this.gpioControlWidgetSettingsForm.get('gpioList') as FormArray;
const gpioItemControl = this.fb.control(gpioItem, [gpioItemValidator(false)]);
(gpioItemControl as any).new = true;
gpioListArray.push(gpioItemControl);
this.gpioControlWidgetSettingsForm.updateValueAndValidity();
if (!this.gpioControlWidgetSettingsForm.valid) {
this.onSettingsChanged(this.gpioControlWidgetSettingsForm.value);
}
}
}

73
ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.html

@ -0,0 +1,73 @@
<!--
Copyright © 2016-2022 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.
-->
<mat-expansion-panel class="gpio-item" fxFlex [formGroup]="gpioItemFormGroup" [(expanded)]="expanded">
<mat-expansion-panel-header>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-panel-title>
<div fxLayout="row" fxFlex fxLayoutAlign="start center">
<div [innerHTML]="gpioItemHtml()"></div>
<div *ngIf="hasColor" class="tb-color-preview" style="margin-left: 6px;">
<div class="tb-color-result" [ngStyle]="{background: gpioItemFormGroup.get('color').value}"></div>
</div>
</div>
</mat-panel-title>
<span fxFlex></span>
<button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeGpioItem.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div fxLayout="column" fxLayoutGap="0.5em">
<mat-divider></mat-divider>
<section class="tb-widget-settings" fxLayout="column">
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gpio.pin</mat-label>
<input required matInput type="number" min="1" step="1" formControlName="pin">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gpio.label</mat-label>
<input required matInput formControlName="label">
</mat-form-field>
</section>
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gpio.row</mat-label>
<input required matInput type="number" min="0" step="1" formControlName="row">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.gpio.column</mat-label>
<input required matInput type="number" min="0" max="1" step="1" formControlName="col">
</mat-form-field>
</section>
<tb-color-input
*ngIf="hasColor"
required
formControlName="color"
icon="format_color_fill"
label="{{ 'widgets.gpio.color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
</section>
</div>
</ng-template>
</mat-expansion-panel>

40
ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.scss

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2022 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.
*/
:host {
display: block;
.mat-expansion-panel {
box-shadow: none;
&.gpio-item {
border: 1px groove rgba(0, 0, 0, .25);
.mat-expansion-panel-header {
padding: 0 24px 0 8px;
&.mat-expanded {
height: 48px;
}
}
}
}
}
:host ::ng-deep {
.mat-expansion-panel {
&.gpio-item {
.mat-expansion-panel-body {
padding: 0 8px 8px;
}
}
}
}

150
ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.ts

@ -0,0 +1,150 @@
///
/// Copyright © 2016-2022 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, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALUE_ACCESSOR, ValidationErrors, ValidatorFn,
Validators
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { isNumber } from '@core/utils';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
export interface GpioItem {
pin: number;
label: string;
row: number;
col: number;
color?: string;
}
export function gpioItemValidator(hasColor: boolean): ValidatorFn {
return (control: AbstractControl) => {
const gpioItem: GpioItem = control.value;
if (!gpioItem
|| !isNumber(gpioItem.pin) || gpioItem.pin < 1
|| !isNumber(gpioItem.row) || gpioItem.row < 0
|| !isNumber(gpioItem.col) || gpioItem.col < 0 || gpioItem.col > 1
|| !gpioItem.label
|| (hasColor && !gpioItem.color)
) {
return {
gpioItem: true
};
}
return null;
};
}
@Component({
selector: 'tb-gpio-item',
templateUrl: './gpio-item.component.html',
styleUrls: ['./gpio-item.component.scss', './../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => GpioItemComponent),
multi: true
}
]
})
export class GpioItemComponent extends PageComponent implements OnInit, ControlValueAccessor {
@Input()
disabled: boolean;
@Input()
expanded = false;
@Input()
hasColor = false;
@Output()
removeGpioItem = new EventEmitter();
private modelValue: GpioItem;
private propagateChange = null;
public gpioItemFormGroup: FormGroup;
constructor(protected store: Store<AppState>,
private translate: TranslateService,
private domSanitizer: DomSanitizer,
private fb: FormBuilder) {
super(store);
}
ngOnInit(): void {
this.gpioItemFormGroup = this.fb.group({
pin: [null, [Validators.required, Validators.min(1)]],
label: [null, [Validators.required]],
row: [null, [Validators.required, Validators.min(0)]],
col: [null, [Validators.required, Validators.min(0), Validators.max(1)]]
});
if (this.hasColor) {
this.gpioItemFormGroup.addControl('color', this.fb.control(null, [Validators.required]));
}
this.gpioItemFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.gpioItemFormGroup.disable({emitEvent: false});
} else {
this.gpioItemFormGroup.enable({emitEvent: false});
}
}
writeValue(value: GpioItem): void {
this.modelValue = value;
this.gpioItemFormGroup.patchValue(
value, {emitEvent: false}
);
}
gpioItemHtml(): SafeHtml {
const value: GpioItem = this.gpioItemFormGroup.value;
const pin = isNumber(value.pin) && value.pin > 0 ? value.pin : 'Undefined';
const row = isNumber(value.row) && value.row > -1 ? value.row : 'Undefined';
const col = isNumber(value.col) && value.col > -1 ? value.col : 'Undefined';
const label = value.label || 'Undefined';
return this.domSanitizer.bypassSecurityTrustHtml(`${label} (<small>pin:</small>${pin}) - [<small>row:</small>${row}:<small>col:</small>${col}]`);
}
private updateModel() {
const value: GpioItem = this.gpioItemFormGroup.value;
this.modelValue = value;
this.propagateChange(this.modelValue);
}
}

55
ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.html

@ -0,0 +1,55 @@
<!--
Copyright © 2016-2022 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.
-->
<section class="tb-widget-settings" [formGroup]="gpioPanelWidgetSettingsForm" fxLayout="column">
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gpio.panel-settings</legend>
<tb-color-input
required
formControlName="ledPanelBackgroundColor"
icon="format_color_fill"
label="{{ 'widgets.gpio.background-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.gpio.gpio-leds</legend>
<div fxLayout="column">
<div class="tb-control-list">
<div *ngFor="let gpioItemControl of gpioListFormArray().controls; trackBy: trackByGpioItem;
let $index = index; last as isLast;"
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-gpio-item [formControl]="gpioItemControl"
[hasColor]="true"
[expanded]="gpioItemControl.new"
(removeGpioItem)="removeGpioItem($index)">
</tb-gpio-item>
</div>
</div>
<div *ngIf="!gpioListFormArray().controls.length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">widgets.gpio.no-gpio-leds</span>
</div>
<div style="padding-top: 16px;">
<button mat-raised-button color="primary"
type="button"
(click)="addGpioItem()">
<span translate>widgets.gpio.add-gpio-led</span>
</button>
</div>
</div>
</fieldset>
</fieldset>
</section>

114
ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component.ts

@ -0,0 +1,114 @@
///
/// Copyright © 2016-2022 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 } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { GpioItem, gpioItemValidator } from '@home/components/widget/lib/settings/gpio/gpio-item.component';
@Component({
selector: 'tb-gpio-panel-widget-settings',
templateUrl: './gpio-panel-widget-settings.component.html',
styleUrls: ['./../widget-settings.scss']
})
export class GpioPanelWidgetSettingsComponent extends WidgetSettingsComponent {
gpioPanelWidgetSettingsForm: FormGroup;
constructor(protected store: Store<AppState>,
private fb: FormBuilder) {
super(store);
}
protected settingsForm(): FormGroup {
return this.gpioPanelWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {
ledPanelBackgroundColor: '#008a00',
gpioList: []
};
}
protected onSettingsSet(settings: WidgetSettings) {
this.gpioPanelWidgetSettingsForm = this.fb.group({
// Panel settings
ledPanelBackgroundColor: [settings.ledPanelBackgroundColor, [Validators.required]],
// --> GPIO leds
gpioList: this.prepareGpioListFormArray(settings.gpioList),
});
}
protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) {
settingsForm.setControl('gpioList', this.prepareGpioListFormArray(settings.gpioList), {emitEvent: false});
}
private prepareGpioListFormArray(gpioList: GpioItem[] | undefined): FormArray {
const gpioListControls: Array<AbstractControl> = [];
if (gpioList) {
gpioList.forEach((gpioItem) => {
gpioListControls.push(this.fb.control(gpioItem, [gpioItemValidator(true)]));
});
}
return this.fb.array(gpioListControls, [(control: AbstractControl) => {
const gpioItems = control.value;
if (!gpioItems || !gpioItems.length) {
return {
gpioItems: true
};
}
return null;
}]);
}
gpioListFormArray(): FormArray {
return this.gpioPanelWidgetSettingsForm.get('gpioList') as FormArray;
}
public trackByGpioItem(index: number, gpioItemControl: AbstractControl): any {
return gpioItemControl;
}
public removeGpioItem(index: number) {
(this.gpioPanelWidgetSettingsForm.get('gpioList') as FormArray).removeAt(index);
}
public addGpioItem() {
const gpioItem: GpioItem = {
pin: null,
label: null,
row: null,
col: null,
color: null
};
const gpioListArray = this.gpioPanelWidgetSettingsForm.get('gpioList') as FormArray;
const gpioItemControl = this.fb.control(gpioItem, [gpioItemValidator(true)]);
(gpioItemControl as any).new = true;
gpioListArray.push(gpioItemControl);
this.gpioPanelWidgetSettingsForm.updateValueAndValidity();
if (!this.gpioPanelWidgetSettingsForm.valid) {
this.onSettingsChanged(this.gpioPanelWidgetSettingsForm.value);
}
}
}

21
ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts

@ -156,6 +156,13 @@ import {
import {
GatewayEventsWidgetSettingsComponent
} from '@home/components/widget/lib/settings/gateway/gateway-events-widget-settings.component';
import { GpioItemComponent } from '@home/components/widget/lib/settings/gpio/gpio-item.component';
import {
GpioControlWidgetSettingsComponent
} from '@home/components/widget/lib/settings/gpio/gpio-control-widget-settings.component';
import {
GpioPanelWidgetSettingsComponent
} from '@home/components/widget/lib/settings/gpio/gpio-panel-widget-settings.component';
@NgModule({
declarations: [
@ -213,7 +220,10 @@ import {
EdgeQuickOverviewWidgetSettingsComponent,
GatewayConfigWidgetSettingsComponent,
GatewayConfigSingleDeviceWidgetSettingsComponent,
GatewayEventsWidgetSettingsComponent
GatewayEventsWidgetSettingsComponent,
GpioItemComponent,
GpioControlWidgetSettingsComponent,
GpioPanelWidgetSettingsComponent
],
imports: [
CommonModule,
@ -275,7 +285,10 @@ import {
EdgeQuickOverviewWidgetSettingsComponent,
GatewayConfigWidgetSettingsComponent,
GatewayConfigSingleDeviceWidgetSettingsComponent,
GatewayEventsWidgetSettingsComponent
GatewayEventsWidgetSettingsComponent,
GpioItemComponent,
GpioControlWidgetSettingsComponent,
GpioPanelWidgetSettingsComponent
]
})
export class WidgetSettingsModule {
@ -323,5 +336,7 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-edge-quick-overview-widget-settings': EdgeQuickOverviewWidgetSettingsComponent,
'tb-gateway-config-widget-settings': GatewayConfigWidgetSettingsComponent,
'tb-gateway-config-single-device-widget-settings': GatewayConfigSingleDeviceWidgetSettingsComponent,
'tb-gateway-events-widget-settings': GatewayEventsWidgetSettingsComponent
'tb-gateway-events-widget-settings': GatewayEventsWidgetSettingsComponent,
'tb-gpio-control-widget-settings': GpioControlWidgetSettingsComponent,
'tb-gpio-panel-widget-settings': GpioPanelWidgetSettingsComponent
};

2
ui-ngx/src/app/shared/components/json-content.component.html

@ -19,7 +19,7 @@
tb-fullscreen
[fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column">
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-json-content-toolbar">
<label class="tb-title no-padding" [ngClass]="{'tb-error': !contentValid}">{{ label }}</label>
<label class="tb-title no-padding" [ngClass]="{'tb-error': !contentValid || required && !contentBody, 'tb-required': required}">{{ label }}</label>
<span fxFlex></span>
<button type="button"
mat-button *ngIf="!readonly && !disabled" class="tidy" (click)="beautifyJSON()">

9
ui-ngx/src/app/shared/components/json-content.component.ts

@ -106,6 +106,15 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
this.validateOnChangeValue = coerceBooleanProperty(value);
}
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
fullscreen = false;
contentBody: string;

7
ui-ngx/src/app/shared/models/widget.models.ts

@ -699,7 +699,9 @@ export abstract class WidgetSettingsComponent extends PageComponent implements
}
protected updateSettings(settings: WidgetSettings) {
this.settingsForm().reset(this.prepareInputSettings(settings), {emitEvent: false});
settings = this.prepareInputSettings(settings);
this.settingsForm().reset(settings, {emitEvent: false});
this.doUpdateSettings(this.settingsForm(), settings);
this.updateValidators(false);
}
@ -719,6 +721,9 @@ export abstract class WidgetSettingsComponent extends PageComponent implements
}
}
protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) {
}
protected prepareInputSettings(settings: WidgetSettings): WidgetSettings {
return settings;
}

20
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3524,6 +3524,26 @@
"no-tick-values": "No tick values configured",
"add-tick-value": "Add tick value"
},
"gpio": {
"pin": "Pin",
"label": "Label",
"row": "Row",
"column": "Column",
"color": "Color",
"panel-settings": "Panel settings",
"background-color": "Background color",
"gpio-switches": "GPIO switches",
"no-gpio-switches": "No GPIO switches configured",
"add-gpio-switch": "Add GPIO switch",
"gpio-status-request": "GPIO status request",
"method-name": "Method name",
"method-body": "Method body",
"gpio-status-change-request": "GPIO status change request",
"parse-gpio-status-function": "Parse gpio status function",
"gpio-leds": "GPIO leds",
"no-gpio-leds": "No GPIO leds configured",
"add-gpio-led": "Add GPIO led"
},
"html-card": {
"html": "HTML",
"css": "CSS"

Loading…
Cancel
Save