Browse Source

UI: Implement update multiple attributes widget settings forms

pull/6545/head
Igor Kulikov 4 years ago
parent
commit
38b4d6b659
  1. 6
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  2. 53
      ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.html
  3. 40
      ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.scss
  4. 128
      ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.ts
  5. 246
      ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html
  6. 250
      ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts
  7. 94
      ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.html
  8. 117
      ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.ts
  9. 23
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  10. 2
      ui-ngx/src/app/shared/components/material-icon-select.component.html
  11. 5
      ui-ngx/src/app/shared/components/material-icon-select.component.ts
  12. 60
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
application/src/main/data/json/system/widget_bundles/input_widgets.json

File diff suppressed because one or more lines are too long

53
ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.html

@ -0,0 +1,53 @@
<!--
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="datakey-select-option" fxFlex [formGroup]="selectOptionFormGroup" [(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]="selectOptionHtml()"></div>
</div>
</mat-panel-title>
<span fxFlex></span>
<button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeSelectOption.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.input-widgets.option-value</mat-label>
<input required matInput formControlName="value">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.option-label</mat-label>
<input matInput formControlName="label">
</mat-form-field>
</section>
</section>
</div>
</ng-template>
</mat-expansion-panel>

40
ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.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;
&.datakey-select-option {
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 {
&.datakey-select-option {
.mat-expansion-panel-body {
padding: 0 8px 8px;
}
}
}
}

128
ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.ts

@ -0,0 +1,128 @@
///
/// 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,
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 { DomSanitizer, SafeHtml } from '@angular/platform-browser';
export interface DataKeySelectOption {
value: string;
label?: string;
}
export function dataKeySelectOptionValidator(control: AbstractControl) {
const selectOption: DataKeySelectOption = control.value;
if (!selectOption || !selectOption.value) {
return {
dataKeySelectOption: true
};
}
return null;
}
@Component({
selector: 'tb-datakey-select-option',
templateUrl: './datakey-select-option.component.html',
styleUrls: ['./datakey-select-option.component.scss', './../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DataKeySelectOptionComponent),
multi: true
}
]
})
export class DataKeySelectOptionComponent extends PageComponent implements OnInit, ControlValueAccessor {
@Input()
disabled: boolean;
@Input()
expanded = false;
@Output()
removeSelectOption = new EventEmitter();
private modelValue: DataKeySelectOption;
private propagateChange = null;
public selectOptionFormGroup: FormGroup;
constructor(protected store: Store<AppState>,
private translate: TranslateService,
private domSanitizer: DomSanitizer,
private fb: FormBuilder) {
super(store);
}
ngOnInit(): void {
this.selectOptionFormGroup = this.fb.group({
value: [null, [Validators.required]],
label: [null, []]
});
this.selectOptionFormGroup.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.selectOptionFormGroup.disable({emitEvent: false});
} else {
this.selectOptionFormGroup.enable({emitEvent: false});
}
}
writeValue(value: DataKeySelectOption): void {
this.modelValue = value;
this.selectOptionFormGroup.patchValue(
value, {emitEvent: false}
);
}
selectOptionHtml(): SafeHtml {
const selectOption: DataKeySelectOption = this.selectOptionFormGroup.value;
const value = selectOption?.value || 'Undefined';
const label = selectOption?.label || '';
return this.domSanitizer.bypassSecurityTrustHtml(`${value} ${label ? '(<small>' + label + '</small>)' : ''}`);
}
private updateModel() {
const value: DataKeySelectOption = this.selectOptionFormGroup.value;
this.modelValue = value;
this.propagateChange(this.modelValue);
}
}

246
ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html

@ -0,0 +1,246 @@
<!--
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]="updateMultipleAttributesKeySettingsForm">
<mat-slide-toggle formControlName="dataKeyHidden" class="slide-block">
{{ 'widgets.input-widgets.hide-input-field' | translate }}
</mat-slide-toggle>
<fieldset [fxShow]="!updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value" class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.general-settings</legend>
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<mat-form-field fxFlex="50" class="mat-block">
<mat-label translate>widgets.input-widgets.datakey-type</mat-label>
<mat-select formControlName="dataKeyType">
<mat-option [value]="'server'">
{{ 'widgets.input-widgets.datakey-type-server' | translate }}
</mat-option>
<mat-option [value]="'shared'">
{{ 'widgets.input-widgets.datakey-type-shared' | translate }}
</mat-option>
<mat-option [value]="'timeseries'">
{{ 'widgets.input-widgets.datakey-type-timeseries' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="50" class="mat-block">
<mat-label translate>widgets.input-widgets.datakey-value-type</mat-label>
<mat-select formControlName="dataKeyValueType">
<mat-option [value]="'string'">
{{ 'widgets.input-widgets.datakey-value-type-string' | translate }}
</mat-option>
<mat-option [value]="'double'">
{{ 'widgets.input-widgets.datakey-value-type-double' | translate }}
</mat-option>
<mat-option [value]="'integer'">
{{ 'widgets.input-widgets.datakey-value-type-integer' | translate }}
</mat-option>
<mat-option [value]="'booleanCheckbox'">
{{ 'widgets.input-widgets.datakey-value-type-boolean-checkbox' | translate }}
</mat-option>
<mat-option [value]="'booleanSwitch'">
{{ 'widgets.input-widgets.datakey-value-type-boolean-switch' | translate }}
</mat-option>
<mat-option [value]="'dateTime'">
{{ 'widgets.input-widgets.datakey-value-type-date-time' | translate }}
</mat-option>
<mat-option [value]="'date'">
{{ 'widgets.input-widgets.datakey-value-type-date' | translate }}
</mat-option>
<mat-option [value]="'time'">
{{ 'widgets.input-widgets.datakey-value-type-time' | translate }}
</mat-option>
<mat-option [value]="'select'">
{{ 'widgets.input-widgets.datakey-value-type-select' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</section>
<mat-checkbox formControlName="required" style="margin-bottom: 8px;">
{{ 'widgets.input-widgets.value-is-required' | translate }}
</mat-checkbox>
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<mat-form-field fxFlex="50" class="mat-block">
<mat-label translate>widgets.input-widgets.ability-to-edit-attribute</mat-label>
<mat-select formControlName="isEditable">
<mat-option [value]="'editable'">
{{ 'widgets.input-widgets.ability-to-edit-attribute-editable' | translate }}
</mat-option>
<mat-option [value]="'disabled'">
{{ 'widgets.input-widgets.ability-to-edit-attribute-disabled' | translate }}
</mat-option>
<mat-option [value]="'readonly'">
{{ 'widgets.input-widgets.ability-to-edit-attribute-readonly' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field [fxShow]="updateMultipleAttributesKeySettingsForm.get('isEditable').value !== 'disabled'" fxFlex="50" class="mat-block">
<mat-label translate>widgets.input-widgets.disable-on-datakey-name</mat-label>
<input matInput formControlName="disabledOnDataKey">
</mat-form-field>
</section>
</fieldset>
<fieldset [fxShow]="!updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value &&
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'booleanSwitch'" class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.slide-toggle-settings</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.slide-toggle-label-position</mat-label>
<mat-select formControlName="slideToggleLabelPosition">
<mat-option [value]="'after'">
{{ 'widgets.input-widgets.slide-toggle-label-position-after' | translate }}
</mat-option>
<mat-option [value]="'before'">
{{ 'widgets.input-widgets.slide-toggle-label-position-before' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</fieldset>
<fieldset [fxShow]="!updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value &&
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'select'" class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.select-options</legend>
<div fxLayout="column">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="selectOptionDrop($event)">
<div cdkDrag class="tb-draggable" *ngFor="let selectOptionControl of selectOptionsFormArray().controls; trackBy: trackBySelectOption;
let $index = index; last as isLast;"
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-datakey-select-option [formControl]="selectOptionControl"
[expanded]="selectOptionControl.new"
(removeSelectOption)="removeSelectOption($index)">
</tb-datakey-select-option>
</div>
</div>
<div *ngIf="!selectOptionsFormArray().controls.length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">widgets.input-widgets.no-select-options</span>
</div>
<div style="padding-top: 16px;">
<button mat-raised-button color="primary"
type="button"
(click)="addSelectOption()">
<span translate>widgets.input-widgets.add-select-option</span>
</button>
</div>
</div>
</fieldset>
<fieldset [fxShow]="!updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value &&
(updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'integer' ||
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'double')" class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.numeric-field-settings</legend>
<section fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.step-interval</mat-label>
<input matInput type="number" min="0" formControlName="step">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.min-value</mat-label>
<input matInput type="number" formControlName="minValue">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.max-value</mat-label>
<input matInput type="number" formControlName="maxValue">
</mat-form-field>
</section>
</fieldset>
<fieldset [fxShow]="displayErrorMessagesSection()" class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.error-messages</legend>
<mat-form-field [fxShow]="updateMultipleAttributesKeySettingsForm.get('required').value" fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.required-error-message</mat-label>
<input matInput formControlName="requiredErrorMessage">
</mat-form-field>
<section [fxShow]="updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'integer' ||
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'double'" fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.min-value-error-message</mat-label>
<input matInput formControlName="minValueErrorMessage">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.max-value-error-message</mat-label>
<input matInput formControlName="maxValueErrorMessage">
</mat-form-field>
</section>
<mat-form-field [fxShow]="updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'dateTime' ||
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'date' ||
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'time'" fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.invalid-date-error-message</mat-label>
<input matInput formControlName="invalidDateErrorMessage">
</mat-form-field>
</fieldset>
<fieldset [fxShow]="!updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value" class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.icon-settings</legend>
<mat-slide-toggle formControlName="useCustomIcon" class="slide-block">
{{ 'widgets.input-widgets.use-custom-icon' | translate }}
</mat-slide-toggle>
<tb-material-icon-select [fxShow]="!updateMultipleAttributesKeySettingsForm.get('useCustomIcon').value"
label="{{ 'widgets.input-widgets.input-cell-icon' | translate }}"
iconClearButton
formControlName="icon">
</tb-material-icon-select>
<tb-image-input [fxShow]="updateMultipleAttributesKeySettingsForm.get('useCustomIcon').value"
label="{{ 'widgets.input-widgets.input-cell-icon' | translate }}"
formControlName="customIcon">
</tb-image-input>
</fieldset>
<fieldset [fxShow]="!updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value" class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.value-conversion-settings</legend>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.input-widgets.get-value-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="useGetValueFunction" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.input-widgets.use-get-value-function' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-js-func formControlName="getValueFunctionBody"
[globalVariables]="functionScopeVariables"
[functionArgs]="['value', 'ctx']"
functionTitle="{{ 'widgets.input-widgets.get-value-function' | translate }}">
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.input-widgets.set-value-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="useSetValueFunction" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.input-widgets.use-set-value-function' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-js-func formControlName="setValueFunctionBody"
[globalVariables]="functionScopeVariables"
[functionArgs]="['value', 'originValue', 'ctx']"
functionTitle="{{ 'widgets.input-widgets.set-value-function' | translate }}">
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</fieldset>
</fieldset>
</section>

250
ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts

@ -0,0 +1,250 @@
///
/// 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 {
DataKeySelectOption,
dataKeySelectOptionValidator
} from '@home/components/widget/lib/settings/input/datakey-select-option.component';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
@Component({
selector: 'tb-update-multiple-attributes-key-settings',
templateUrl: './update-multiple-attributes-key-settings.component.html',
styleUrls: ['./../widget-settings.scss']
})
export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettingsComponent {
updateMultipleAttributesKeySettingsForm: FormGroup;
constructor(protected store: Store<AppState>,
private fb: FormBuilder) {
super(store);
}
protected settingsForm(): FormGroup {
return this.updateMultipleAttributesKeySettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {
dataKeyHidden: false,
dataKeyType: 'server',
dataKeyValueType: 'string',
required: false,
isEditable: 'editable',
disabledOnDataKey: '',
slideToggleLabelPosition: 'after',
selectOptions: [],
step: 1,
minValue: null,
maxValue: null,
requiredErrorMessage: '',
minValueErrorMessage: '',
maxValueErrorMessage: '',
invalidDateErrorMessage: '',
useCustomIcon: false,
icon: '',
customIcon: '',
useGetValueFunction: false,
getValueFunctionBody: '',
useSetValueFunction: false,
setValueFunctionBody: ''
};
}
protected onSettingsSet(settings: WidgetSettings) {
this.updateMultipleAttributesKeySettingsForm = this.fb.group({
dataKeyHidden: [settings.dataKeyHidden, []],
// General settings
dataKeyType: [settings.dataKeyType, []],
dataKeyValueType: [settings.dataKeyValueType, []],
required: [settings.required, []],
isEditable: [settings.isEditable, []],
disabledOnDataKey: [settings.disabledOnDataKey, []],
// Slide toggle settings
slideToggleLabelPosition: [settings.slideToggleLabelPosition, []],
// Select options
selectOptions: this.prepareSelectOptionsFormArray(settings.selectOptions),
// Numeric field settings
step: [settings.step, [Validators.min(0)]],
minValue: [settings.minValue, []],
maxValue: [settings.maxValue, []],
// Error messages
requiredErrorMessage: [settings.requiredErrorMessage, []],
minValueErrorMessage: [settings.minValueErrorMessage, []],
maxValueErrorMessage: [settings.maxValueErrorMessage, []],
invalidDateErrorMessage: [settings.invalidDateErrorMessage, []],
// Icon settings
useCustomIcon: [settings.useCustomIcon, []],
icon: [settings.icon, []],
customIcon: [settings.customIcon, []],
// Value conversion settings
useGetValueFunction: [settings.useGetValueFunction, []],
getValueFunctionBody: [settings.getValueFunctionBody, []],
useSetValueFunction: [settings.useSetValueFunction, []],
setValueFunctionBody: [settings.setValueFunctionBody, []]
});
}
protected validatorTriggers(): string[] {
return ['dataKeyHidden', 'dataKeyValueType', 'required', 'isEditable', 'useCustomIcon', 'useGetValueFunction', 'useSetValueFunction'];
}
protected updateValidators(emitEvent: boolean) {
const dataKeyHidden: boolean = this.updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value;
const dataKeyValueType: string = this.updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value;
const required: boolean = this.updateMultipleAttributesKeySettingsForm.get('required').value;
const isEditable: string = this.updateMultipleAttributesKeySettingsForm.get('isEditable').value;
const useCustomIcon: boolean = this.updateMultipleAttributesKeySettingsForm.get('useCustomIcon').value;
const useGetValueFunction: boolean = this.updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').value;
const useSetValueFunction: boolean = this.updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').value;
this.updateMultipleAttributesKeySettingsForm.disable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').enable({emitEvent: false});
if (!dataKeyHidden) {
this.updateMultipleAttributesKeySettingsForm.get('dataKeyType').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('required').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('isEditable').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('useCustomIcon').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').enable({emitEvent: false});
if (isEditable !== 'disabled') {
this.updateMultipleAttributesKeySettingsForm.get('disabledOnDataKey').enable({emitEvent: false});
}
if (dataKeyValueType === 'booleanSwitch') {
this.updateMultipleAttributesKeySettingsForm.get('slideToggleLabelPosition').enable({emitEvent: false});
} else if (dataKeyValueType === 'select') {
this.updateMultipleAttributesKeySettingsForm.get('selectOptions').enable({emitEvent: false});
} else if (dataKeyValueType === 'integer' || dataKeyValueType === 'double') {
this.updateMultipleAttributesKeySettingsForm.get('step').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('minValue').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('maxValue').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('minValueErrorMessage').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('maxValueErrorMessage').enable({emitEvent: false});
} else if (dataKeyValueType === 'dateTime' || dataKeyValueType === 'date' || dataKeyValueType === 'time') {
this.updateMultipleAttributesKeySettingsForm.get('invalidDateErrorMessage').enable({emitEvent: false});
}
if (required) {
this.updateMultipleAttributesKeySettingsForm.get('requiredErrorMessage').enable({emitEvent: false});
}
if (useCustomIcon) {
this.updateMultipleAttributesKeySettingsForm.get('customIcon').enable({emitEvent: false});
} else {
this.updateMultipleAttributesKeySettingsForm.get('icon').enable({emitEvent: false});
}
if (useGetValueFunction) {
this.updateMultipleAttributesKeySettingsForm.get('getValueFunctionBody').enable({emitEvent: false});
}
if (useSetValueFunction) {
this.updateMultipleAttributesKeySettingsForm.get('setValueFunctionBody').enable({emitEvent: false});
}
}
this.updateMultipleAttributesKeySettingsForm.updateValueAndValidity({emitEvent: false});
}
protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) {
settingsForm.setControl('selectOptions', this.prepareSelectOptionsFormArray(settings.selectOptions), {emitEvent: false});
}
private prepareSelectOptionsFormArray(selectOptions: DataKeySelectOption[] | undefined): FormArray {
const selectOptionsControls: Array<AbstractControl> = [];
if (selectOptions) {
selectOptions.forEach((selectOption) => {
selectOptionsControls.push(this.fb.control(selectOption, [dataKeySelectOptionValidator]));
});
}
return this.fb.array(selectOptionsControls, [(control: AbstractControl) => {
const selectOptionItems = control.value;
if (!selectOptionItems || !selectOptionItems.length) {
return {
selectOptionItems: true
};
}
return null;
}]);
}
selectOptionsFormArray(): FormArray {
return this.updateMultipleAttributesKeySettingsForm.get('selectOptions') as FormArray;
}
public trackBySelectOption(index: number, selectOptionControl: AbstractControl): any {
return selectOptionControl;
}
public removeSelectOption(index: number) {
(this.updateMultipleAttributesKeySettingsForm.get('selectOptions') as FormArray).removeAt(index);
}
public addSelectOption() {
const selectOption: DataKeySelectOption = {
value: null,
label: null
};
const selectOptionsArray = this.updateMultipleAttributesKeySettingsForm.get('selectOptions') as FormArray;
const selectOptionControl = this.fb.control(selectOption, [dataKeySelectOptionValidator]);
(selectOptionControl as any).new = true;
selectOptionsArray.push(selectOptionControl);
this.updateMultipleAttributesKeySettingsForm.updateValueAndValidity();
if (!this.updateMultipleAttributesKeySettingsForm.valid) {
this.onSettingsChanged(this.updateMultipleAttributesKeySettingsForm.value);
}
}
selectOptionDrop(event: CdkDragDrop<any[]>) {
const selectOptionsArray = this.updateMultipleAttributesKeySettingsForm.get('selectOptions') as FormArray;
const selectOption = selectOptionsArray.at(event.previousIndex);
selectOptionsArray.removeAt(event.previousIndex);
selectOptionsArray.insert(event.currentIndex, selectOption);
}
displayErrorMessagesSection(): boolean {
const dataKeyHidden: boolean = this.updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value;
const required: boolean = this.updateMultipleAttributesKeySettingsForm.get('required').value;
const dataKeyValueType: string = this.updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value;
return !dataKeyHidden && (required || (['integer', 'double', 'dateTime', 'date', 'time'].includes(dataKeyValueType)));
}
}

94
ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.html

@ -0,0 +1,94 @@
<!--
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]="updateMultipleAttributesWidgetSettingsForm">
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.general-settings</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.widget-title</mat-label>
<input matInput formControlName="widgetTitle">
</mat-form-field>
<mat-slide-toggle formControlName="showResultMessage" class="slide-block">
{{ 'widgets.input-widgets.show-result-message' | translate }}
</mat-slide-toggle>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.input-widgets.action-buttons</legend>
<mat-expansion-panel class="tb-settings" [expanded]="updateMultipleAttributesWidgetSettingsForm.get('showActionButtons').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="showActionButtons" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.input-widgets.show-action-buttons' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section fxLayout="column">
<mat-checkbox formControlName="updateAllValues" style="margin-bottom: 8px;">
{{ 'widgets.input-widgets.update-all-values' | translate }}
</mat-checkbox>
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.save-button-label</mat-label>
<input matInput formControlName="saveButtonLabel">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.input-widgets.reset-button-label</mat-label>
<input matInput formControlName="resetButtonLabel">
</mat-form-field>
</section>
</section>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.group-settings</legend>
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<mat-slide-toggle fxFlex="30" formControlName="showGroupTitle" class="slide-block">
{{ 'widgets.input-widgets.show-group-title' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex="70" class="mat-block">
<mat-label translate>widgets.input-widgets.group-title</mat-label>
<input matInput formControlName="groupTitle">
</mat-form-field>
</section>
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.fields-alignment</legend>
<section fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<mat-form-field fxFlex="50" class="mat-block">
<mat-label translate>widgets.input-widgets.fields-alignment</mat-label>
<mat-select formControlName="fieldsAlignment">
<mat-option [value]="'row'">
{{ 'widgets.input-widgets.fields-alignment-row' | translate }}
</mat-option>
<mat-option [value]="'column'">
{{ 'widgets.input-widgets.fields-alignment-column' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="50" [fxShow]="updateMultipleAttributesWidgetSettingsForm.get('fieldsAlignment').value === 'row'" class="mat-block">
<mat-label translate>widgets.input-widgets.fields-in-row</mat-label>
<input matInput type="number" min="1" step="1" formControlName="fieldsInRow">
</mat-form-field>
</section>
</fieldset>
</section>

117
ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.ts

@ -0,0 +1,117 @@
///
/// 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 { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@Component({
selector: 'tb-update-multiple-attributes-widget-settings',
templateUrl: './update-multiple-attributes-widget-settings.component.html',
styleUrls: ['./../widget-settings.scss']
})
export class UpdateMultipleAttributesWidgetSettingsComponent extends WidgetSettingsComponent {
updateMultipleAttributesWidgetSettingsForm: FormGroup;
constructor(protected store: Store<AppState>,
private fb: FormBuilder) {
super(store);
}
protected settingsForm(): FormGroup {
return this.updateMultipleAttributesWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {
widgetTitle: '',
showResultMessage: true,
showActionButtons: true,
updateAllValues: false,
saveButtonLabel: '',
resetButtonLabel: '',
showGroupTitle: false,
groupTitle: '',
fieldsAlignment: 'row',
fieldsInRow: 2
};
}
protected onSettingsSet(settings: WidgetSettings) {
this.updateMultipleAttributesWidgetSettingsForm = this.fb.group({
// General settings
widgetTitle: [settings.widgetTitle, []],
showResultMessage: [settings.showResultMessage, []],
// Action button settings
showActionButtons: [settings.showActionButtons, []],
updateAllValues: [settings.updateAllValues, []],
saveButtonLabel: [settings.saveButtonLabel, []],
resetButtonLabel: [settings.resetButtonLabel, []],
// Group settings
showGroupTitle: [settings.showGroupTitle, []],
groupTitle: [settings.groupTitle, []],
// Fields alignment
fieldsAlignment: [settings.fieldsAlignment, []],
fieldsInRow: [settings.fieldsInRow, [Validators.min(1)]],
});
}
protected validatorTriggers(): string[] {
return ['showActionButtons', 'showGroupTitle', 'fieldsAlignment'];
}
protected updateValidators(emitEvent: boolean) {
const showActionButtons: boolean = this.updateMultipleAttributesWidgetSettingsForm.get('showActionButtons').value;
const showGroupTitle: boolean = this.updateMultipleAttributesWidgetSettingsForm.get('showGroupTitle').value;
const fieldsAlignment: string = this.updateMultipleAttributesWidgetSettingsForm.get('fieldsAlignment').value;
if (showActionButtons) {
this.updateMultipleAttributesWidgetSettingsForm.get('updateAllValues').enable();
this.updateMultipleAttributesWidgetSettingsForm.get('saveButtonLabel').enable();
this.updateMultipleAttributesWidgetSettingsForm.get('resetButtonLabel').enable();
} else {
this.updateMultipleAttributesWidgetSettingsForm.get('updateAllValues').disable();
this.updateMultipleAttributesWidgetSettingsForm.get('saveButtonLabel').disable();
this.updateMultipleAttributesWidgetSettingsForm.get('resetButtonLabel').disable();
}
if (showGroupTitle) {
this.updateMultipleAttributesWidgetSettingsForm.get('groupTitle').enable();
} else {
this.updateMultipleAttributesWidgetSettingsForm.get('groupTitle').disable();
}
if (fieldsAlignment === 'row') {
this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').enable();
} else {
this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').disable();
}
this.updateMultipleAttributesWidgetSettingsForm.get('updateAllValues').updateValueAndValidity({emitEvent});
this.updateMultipleAttributesWidgetSettingsForm.get('saveButtonLabel').updateValueAndValidity({emitEvent});
this.updateMultipleAttributesWidgetSettingsForm.get('resetButtonLabel').updateValueAndValidity({emitEvent});
this.updateMultipleAttributesWidgetSettingsForm.get('groupTitle').updateValueAndValidity({emitEvent});
this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').updateValueAndValidity({emitEvent});
}
}

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

@ -202,6 +202,15 @@ import {
import {
PhotoCameraInputWidgetSettingsComponent
} from '@home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component';
import {
UpdateMultipleAttributesWidgetSettingsComponent
} from '@home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component';
import {
DataKeySelectOptionComponent
} from '@home/components/widget/lib/settings/input/datakey-select-option.component';
import {
UpdateMultipleAttributesKeySettingsComponent
} from '@home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component';
@NgModule({
declarations: [
@ -275,7 +284,10 @@ import {
UpdateDateAttributeWidgetSettingsComponent,
UpdateLocationAttributeWidgetSettingsComponent,
UpdateJsonAttributeWidgetSettingsComponent,
PhotoCameraInputWidgetSettingsComponent
PhotoCameraInputWidgetSettingsComponent,
UpdateMultipleAttributesWidgetSettingsComponent,
DataKeySelectOptionComponent,
UpdateMultipleAttributesKeySettingsComponent
],
imports: [
CommonModule,
@ -353,7 +365,10 @@ import {
UpdateDateAttributeWidgetSettingsComponent,
UpdateLocationAttributeWidgetSettingsComponent,
UpdateJsonAttributeWidgetSettingsComponent,
PhotoCameraInputWidgetSettingsComponent
PhotoCameraInputWidgetSettingsComponent,
UpdateMultipleAttributesWidgetSettingsComponent,
DataKeySelectOptionComponent,
UpdateMultipleAttributesKeySettingsComponent
]
})
export class WidgetSettingsModule {
@ -415,5 +430,7 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-update-date-attribute-widget-settings': UpdateDateAttributeWidgetSettingsComponent,
'tb-update-location-attribute-widget-settings': UpdateLocationAttributeWidgetSettingsComponent,
'tb-update-json-attribute-widget-settings': UpdateJsonAttributeWidgetSettingsComponent,
'tb-photo-camera-input-widget-settings': PhotoCameraInputWidgetSettingsComponent
'tb-photo-camera-input-widget-settings': PhotoCameraInputWidgetSettingsComponent,
'tb-update-multiple-attributes-widget-settings': UpdateMultipleAttributesWidgetSettingsComponent,
'tb-update-multiple-attributes-key-settings': UpdateMultipleAttributesKeySettingsComponent
};

2
ui-ngx/src/app/shared/components/material-icon-select.component.html

@ -18,7 +18,7 @@
<div fxLayout="row" [formGroup]="materialIconFormGroup">
<mat-icon class="icon-value" (click)="openIconDialog()">{{materialIconFormGroup.get('icon').value}}</mat-icon>
<mat-form-field fxFlex>
<mat-label translate>icon.icon</mat-label>
<mat-label>{{ label }}</mat-label>
<input [required]="required" matInput formControlName="icon" (mousedown)="openIconDialog()">
<button *ngIf="iconClearButton"
type="button"

5
ui-ngx/src/app/shared/components/material-icon-select.component.ts

@ -21,6 +21,7 @@ import { AppState } from '@core/core.state';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { DialogService } from '@core/services/dialog.service';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'tb-material-icon-select',
@ -36,6 +37,9 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
})
export class MaterialIconSelectComponent extends PageComponent implements OnInit, ControlValueAccessor {
@Input()
label = this.translate.instant('icon.icon');
@Input()
disabled: boolean;
@ -68,6 +72,7 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit
constructor(protected store: Store<AppState>,
private dialogs: DialogService,
private translate: TranslateService,
private fb: FormBuilder) {
super(store);
}

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

@ -3656,7 +3656,65 @@
"image-format-webp": "WEBP",
"image-quality": "Image quality that use lossy compression such as jpeg and webp",
"max-image-width": "Maximum image width",
"max-image-height": "Maximum image height"
"max-image-height": "Maximum image height",
"action-buttons": "Action buttons",
"show-action-buttons": "Show action buttons",
"update-all-values": "Update all values, not only modified",
"save-button-label": "'SAVE' button label",
"reset-button-label": "'UNDO' button label",
"group-settings": "Group settings",
"show-group-title": "Show title for group of fields, related to different entities",
"group-title": "Group title",
"fields-alignment": "Fields alignment",
"fields-alignment-row": "Row (default)",
"fields-alignment-column": "Column",
"fields-in-row": "Number of fields in the row",
"option-value": "Value (write 'null' for create empty option)",
"option-label": "Label",
"hide-input-field": "Hide input field",
"datakey-type": "Datakey type",
"datakey-type-server": "Server attribute (default)",
"datakey-type-shared": "Shared attribute",
"datakey-type-timeseries": "Timeseries",
"datakey-value-type": "Datakey value type",
"datakey-value-type-string": "String",
"datakey-value-type-double": "Double",
"datakey-value-type-integer": "Integer",
"datakey-value-type-boolean-checkbox": "Boolean (Checkbox)",
"datakey-value-type-boolean-switch": "Boolean (Switch)",
"datakey-value-type-date-time": "Date & Time",
"datakey-value-type-date": "Date",
"datakey-value-type-time": "Time",
"datakey-value-type-select": "Select",
"value-is-required": "Value is required",
"ability-to-edit-attribute": "Ability to edit attribute",
"ability-to-edit-attribute-editable": "Editable (default)",
"ability-to-edit-attribute-disabled": "Disabled",
"ability-to-edit-attribute-readonly": "Read-only",
"disable-on-datakey-name": "Disable on false value of another datakey (specify datakey name)",
"slide-toggle-settings": "Slide toggle settings",
"slide-toggle-label-position": "Slide toggle label position",
"slide-toggle-label-position-after": "After",
"slide-toggle-label-position-before": "Before",
"select-options": "Select options",
"no-select-options": "No select options configured",
"add-select-option": "Add select option",
"numeric-field-settings": "Numeric field settings",
"step-interval": "Step interval between values",
"error-messages": "Error messages",
"min-value-error-message": "'Min value' error message",
"max-value-error-message": "'Max value' error message",
"invalid-date-error-message": "'Invalid date' error message",
"icon-settings": "Icon settings",
"use-custom-icon": "Use custom icon",
"input-cell-icon": "Icon to show before input cell",
"value-conversion-settings": "Value conversion settings",
"get-value-settings": "Get value settings",
"use-get-value-function": "Use getValue function",
"get-value-function": "getValue function",
"set-value-settings": "Set value settings",
"use-set-value-function": "Use setValue function",
"set-value-function": "setValue function"
},
"invalid-qr-code-text": "Invalid input text for QR code. Input should have a string type",
"qr-code": {

Loading…
Cancel
Save