Browse Source

Ability to define component form for advanced widget settings and widget data keys

pull/6545/head
Igor Kulikov 5 years ago
parent
commit
c2f4724ca2
  1. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts
  2. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts
  3. 5
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  4. 1
      ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html
  5. 1
      ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts
  6. 5
      ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html
  7. 13
      ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts
  8. 4
      ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts
  9. 36
      ui-ngx/src/app/modules/home/components/widget/lib/settings/qrcode-widget-settings.component.html
  10. 67
      ui-ngx/src/app/modules/home/components/widget/lib/settings/qrcode-widget-settings.component.ts
  11. 42
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  12. 2
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  13. 7
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  14. 7
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  15. 24
      ui-ngx/src/app/modules/home/components/widget/widget-settings.component.html
  16. 22
      ui-ngx/src/app/modules/home/components/widget/widget-settings.component.scss
  17. 201
      ui-ngx/src/app/modules/home/components/widget/widget-settings.component.ts
  18. 6
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  19. 12
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html
  20. 127
      ui-ngx/src/app/shared/models/widget.models.ts
  21. 11
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts

@ -91,7 +91,9 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
actionSources,
isDataEnabled,
settingsSchema,
dataKeySettingsSchema
dataKeySettingsSchema,
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective
};
this.widgetFormGroup = this.fb.group({

4
ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts

@ -112,7 +112,9 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan
actionSources,
isDataEnabled,
settingsSchema,
dataKeySettingsSchema
dataKeySettingsSchema,
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective
};
this.widgetFormGroup.reset({widgetConfig: this.widgetConfig});
}

5
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -148,6 +148,8 @@ import {
} from '@home/components/tokens';
import { DashboardStateComponent } from '@home/components/dashboard-page/dashboard-state.component';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module';
import { WidgetSettingsComponent } from '@home/components/widget/widget-settings.component';
@NgModule({
declarations:
@ -182,6 +184,7 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai
WidgetContainerComponent,
WidgetComponent,
LegendComponent,
WidgetSettingsComponent,
WidgetConfigComponent,
EntityFilterViewComponent,
EntityFilterComponent,
@ -273,6 +276,7 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai
CommonModule,
SharedModule,
SharedHomeComponentsModule,
WidgetSettingsModule,
Lwm2mProfileComponentsModule,
SnmpDeviceProfileTransportModule,
StatesControllerModule,
@ -301,6 +305,7 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai
WidgetContainerComponent,
WidgetComponent,
LegendComponent,
WidgetSettingsComponent,
WidgetConfigComponent,
EntityFilterViewComponent,
EntityFilterComponent,

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

@ -31,6 +31,7 @@
<div mat-dialog-content>
<tb-data-key-config #dataKeyConfig
[dataKeySettingsSchema]="data.dataKeySettingsSchema"
[dataKeySettingsDirective]="data.dataKeySettingsDirective"
[entityAliasId]="data.entityAliasId"
[showPostProcessing]="data.showPostProcessing"
[callbacks]="data.callbacks"

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

@ -29,6 +29,7 @@ import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.
export interface DataKeyConfigDialogData {
dataKey: DataKey;
dataKeySettingsSchema: any;
dataKeySettingsDirective: string;
entityAliasId?: string;
showPostProcessing?: boolean;
callbacks?: DataKeysCallbacks;

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

@ -98,9 +98,10 @@
</mat-tab>
<mat-tab [formGroup]="dataKeySettingsFormGroup" label="{{ 'datakey.advanced' | translate }}" *ngIf="displayAdvanced">
<div class="mat-padding" fxLayout="column">
<tb-json-form
<tb-widget-settings
[settingsDirective]="dataKeySettingsDirective"
formControlName="settings">
</tb-json-form>
</tb-widget-settings>
</div>
</mat-tab>
</mat-tab-group>

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

@ -72,6 +72,9 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
@Input()
dataKeySettingsSchema: any;
@Input()
dataKeySettingsDirective: string;
@Input()
showPostProcessing = true;
@ -121,11 +124,15 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
type: DataKeyType.alarm
});
}
if (this.dataKeySettingsSchema && this.dataKeySettingsSchema.schema) {
if (this.dataKeySettingsSchema && this.dataKeySettingsSchema.schema ||
this.dataKeySettingsDirective && this.dataKeySettingsDirective.length) {
this.displayAdvanced = true;
this.dataKeySettingsData = {
schema: this.dataKeySettingsSchema.schema,
form: this.dataKeySettingsSchema.form || ['*']
schema: this.dataKeySettingsSchema?.schema || {
type: 'object',
properties: {}
},
form: this.dataKeySettingsSchema?.form || ['*']
};
this.dataKeySettingsFormGroup = this.fb.group({
settings: [null, []]

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

@ -113,6 +113,9 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
@Input()
datakeySettingsSchema: any;
@Input()
dataKeySettingsDirective: string;
@Input()
callbacks: DataKeysCallbacks;
@ -395,6 +398,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
data: {
dataKey: deepClone(key),
dataKeySettingsSchema: this.datakeySettingsSchema,
dataKeySettingsDirective: this.dataKeySettingsDirective,
entityAliasId: this.entityAliasId,
showPostProcessing: this.widgetType !== widgetType.alarm,
callbacks: this.callbacks

36
ui-ngx/src/app/modules/home/components/widget/lib/settings/qrcode-widget-settings.component.html

@ -0,0 +1,36 @@
<!--
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 [formGroup]="qrCodeWidgetSettingsForm" fxLayout="column">
<mat-checkbox formControlName="useQrCodeTextFunction" style="padding-bottom: 16px;">
{{ 'widgets.qr-code.use-qr-code-text-function' | translate }}
</mat-checkbox>
<mat-form-field [fxShow]="qrCodeWidgetSettingsForm.get('useQrCodeTextFunction').value === false">
<mat-label translate>widgets.qr-code.qr-code-text-pattern</mat-label>
<input required matInput formControlName="qrCodeTextPattern">
<mat-error *ngIf="qrCodeWidgetSettingsForm.get('qrCodeTextPattern').hasError('required')">
{{ 'widgets.qr-code.qr-code-text-pattern-required' | translate }}
</mat-error>
</mat-form-field>
<section fxLayout="column" [fxShow]="qrCodeWidgetSettingsForm.get('useQrCodeTextFunction').value === true">
<label translate class="tb-title no-padding">widgets.qr-code.qr-code-text-function</label>
<tb-js-func formControlName="qrCodeTextFunction"
[functionArgs]="['data']"
helpId="widget/lib/qrcode/qrcode_text_fn">
</tb-js-func>
</section>
</section>

67
ui-ngx/src/app/modules/home/components/widget/lib/settings/qrcode-widget-settings.component.ts

@ -0,0 +1,67 @@
///
/// 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-qrcode-widget-settings',
templateUrl: './qrcode-widget-settings.component.html',
styleUrls: []
})
export class QrCodeWidgetSettingsComponent extends WidgetSettingsComponent {
qrCodeWidgetSettingsForm: FormGroup;
constructor(protected store: Store<AppState>,
private fb: FormBuilder) {
super(store);
}
protected settingsForm(): FormGroup {
return this.qrCodeWidgetSettingsForm;
}
protected onSettingsSet(settings: WidgetSettings) {
this.qrCodeWidgetSettingsForm = this.fb.group({
qrCodeTextPattern: [settings ? settings.qrCodeTextPattern : '${entityName}', []],
useQrCodeTextFunction: [settings ? settings.useQrCodeTextFunction : false, []],
qrCodeTextFunction: [settings ? settings.qrCodeTextFunction : 'return data[0][\'entityName\'];', []]
});
}
protected validatorTriggers(): string[] {
return ['useQrCodeTextFunction'];
}
protected updateValidators(emitEvent: boolean) {
const useQrCodeTextFunction: boolean = this.qrCodeWidgetSettingsForm.get('useQrCodeTextFunction').value;
if (useQrCodeTextFunction) {
this.qrCodeWidgetSettingsForm.get('qrCodeTextPattern').setValidators([]);
this.qrCodeWidgetSettingsForm.get('qrCodeTextFunction').setValidators([Validators.required]);
} else {
this.qrCodeWidgetSettingsForm.get('qrCodeTextPattern').setValidators([Validators.required]);
this.qrCodeWidgetSettingsForm.get('qrCodeTextFunction').setValidators([]);
}
this.qrCodeWidgetSettingsForm.get('qrCodeTextPattern').updateValueAndValidity({emitEvent});
this.qrCodeWidgetSettingsForm.get('qrCodeTextFunction').updateValueAndValidity({emitEvent});
}
}

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

@ -0,0 +1,42 @@
///
/// 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 { NgModule, Type } from '@angular/core';
import { QrCodeWidgetSettingsComponent } from '@home/components/widget/lib/settings/qrcode-widget-settings.component';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module';
import { IWidgetSettingsComponent } from '@shared/models/widget.models';
@NgModule({
declarations: [
QrCodeWidgetSettingsComponent
],
imports: [
CommonModule,
SharedModule,
SharedHomeComponentsModule
],
exports: [
QrCodeWidgetSettingsComponent
]
})
export class WidgetSettingsModule {
}
export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsComponent>} = {
'tb-qrcode-widget-settings': QrCodeWidgetSettingsComponent
};

2
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -107,6 +107,8 @@ export class WidgetComponentService {
controllerScript: this.utils.editWidgetInfo.controllerScript,
settingsSchema: this.utils.editWidgetInfo.settingsSchema,
dataKeySettingsSchema: this.utils.editWidgetInfo.dataKeySettingsSchema,
settingsDirective: this.utils.editWidgetInfo.settingsDirective,
dataKeySettingsDirective: this.utils.editWidgetInfo.dataKeySettingsDirective,
defaultConfig: this.utils.editWidgetInfo.defaultConfig
}, new WidgetTypeId('1'), new TenantId( NULL_UUID ), 'customWidgetBundle', undefined
);

7
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -190,6 +190,7 @@
[optDataKeys]="modelValue?.typeParameters?.dataKeysOptional"
[aliasController]="aliasController"
[datakeySettingsSchema]="modelValue?.dataKeySettingsSchema"
[dataKeySettingsDirective]="modelValue?.dataKeySettingsDirective"
[callbacks]="widgetConfigCallbacks"
[entityAliasId]="datasourceControl.get('entityAliasId').value"
[formControl]="datasourceControl.get('dataKeys')">
@ -283,6 +284,7 @@
[datasourceType]="alarmSourceSettings.get('type').value"
[aliasController]="aliasController"
[datakeySettingsSchema]="modelValue?.dataKeySettingsSchema"
[dataKeySettingsDirective]="modelValue?.dataKeySettingsDirective"
[callbacks]="widgetConfigCallbacks"
[entityAliasId]="alarmSourceSettings.get('entityAliasId').value"
formControlName="dataKeys">
@ -476,9 +478,10 @@
<mat-tab *ngIf="displayAdvanced()" label="{{ 'widget-config.advanced' | translate }}">
<div [formGroup]="advancedSettings" class="mat-content mat-padding tb-advanced-widget-config"
fxLayout="column">
<tb-json-form
<tb-widget-settings
[settingsDirective]="modelValue.settingsDirective"
formControlName="settings">
</tb-json-form>
</tb-widget-settings>
</div>
</mat-tab>
<mat-tab label="{{ 'widget-config.actions' | translate }}" [formGroup]="actionsSettings">

7
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -605,7 +605,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
widgetSettingsFormData.schema = deepClone(emptySettingsSchema);
widgetSettingsFormData.form = deepClone(defaultSettingsForm);
widgetSettingsFormData.groupInfoes = deepClone(emptySettingsGroupInfoes);
widgetSettingsFormData.model = {};
widgetSettingsFormData.model = settings || {};
}
this.advancedSettings.patchValue({ settings: widgetSettingsFormData }, {emitEvent: false});
}
@ -713,7 +713,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
}
public displayAdvanced(): boolean {
return !!this.modelValue && !!this.modelValue.settingsSchema && !!this.modelValue.settingsSchema.schema;
return !!this.modelValue && (!!this.modelValue.settingsSchema && !!this.modelValue.settingsSchema.schema ||
!!this.modelValue.settingsDirective && !!this.modelValue.settingsDirective.length);
}
public dndDatasourceMoved(index: number) {
@ -913,7 +914,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
valid: false
}
};
} else if (!this.advancedSettings.valid) {
} else if (!this.advancedSettings.valid || (this.displayAdvanced() && !this.modelValue.config.settings)) {
return {
advancedSettings: {
valid: false

24
ui-ngx/src/app/modules/home/components/widget/widget-settings.component.html

@ -0,0 +1,24 @@
<!--
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.
-->
<div [formGroup]="widgetSettingsFormGroup">
<ng-container #definedSettingsContent></ng-container>
<div class="tb-settings-directive-error" *ngIf="definedDirectiveError">{{definedDirectiveError}}</div>
<tb-json-form *ngIf="useJsonForm()" #jsonFormComponent
formControlName="settings">
</tb-json-form>
</div>

22
ui-ngx/src/app/modules/home/components/widget/widget-settings.component.scss

@ -0,0 +1,22 @@
/**
* 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 {
.tb-settings-directive-error {
font-size: 13px;
font-weight: 400;
color: rgb(221, 44, 0);
}
}

201
ui-ngx/src/app/modules/home/components/widget/widget-settings.component.ts

@ -0,0 +1,201 @@
///
/// 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 {
AfterViewInit,
Component, ComponentFactoryResolver,
ComponentRef,
forwardRef,
Input,
OnDestroy,
OnInit,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
IRuleNodeConfigurationComponent,
RuleNodeConfiguration,
RuleNodeDefinition
} from '@shared/models/rule-node.models';
import { Subscription } from 'rxjs';
import { RuleChainService } from '@core/http/rule-chain.service';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { TranslateService } from '@ngx-translate/core';
import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component';
import { deepClone } from '@core/utils';
import { RuleChainType } from '@shared/models/rule-chain.models';
import { JsonFormComponent } from '@shared/components/json-form/json-form.component';
import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models';
import { IWidgetSettingsComponent, WidgetSettings } from '@shared/models/widget.models';
import { widgetSettingsComponentsMap } from '@home/components/widget/lib/settings/widget-settings.module';
@Component({
selector: 'tb-widget-settings',
templateUrl: './widget-settings.component.html',
styleUrls: ['./widget-settings.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => WidgetSettingsComponent),
multi: true
}]
})
export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, OnDestroy, AfterViewInit {
@ViewChild('definedSettingsContent', {read: ViewContainerRef, static: true}) definedSettingsContainer: ViewContainerRef;
@ViewChild('jsonFormComponent') jsonFormComponent: JsonFormComponent;
@Input()
disabled: boolean;
settingsDirectiveValue: string;
@Input()
set settingsDirective(settingsDirective: string) {
if (this.settingsDirectiveValue !== settingsDirective) {
this.settingsDirectiveValue = settingsDirective;
if (this.settingsDirectiveValue) {
this.validateDefinedDirective();
}
}
}
get settingsDirective(): string {
return this.settingsDirectiveValue;
}
definedDirectiveError: string;
widgetSettingsFormGroup: FormGroup;
changeSubscription: Subscription;
private definedSettingsComponentRef: ComponentRef<IWidgetSettingsComponent>;
private definedSettingsComponent: IWidgetSettingsComponent;
private widgetSettingsFormData: JsonFormComponentData;
private propagateChange = (v: any) => { };
constructor(private translate: TranslateService,
private cfr: ComponentFactoryResolver,
private fb: FormBuilder) {
this.widgetSettingsFormGroup = this.fb.group({
settings: [null, Validators.required]
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit(): void {
}
ngOnDestroy(): void {
if (this.definedSettingsComponentRef) {
this.definedSettingsComponentRef.destroy();
}
}
ngAfterViewInit(): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.widgetSettingsFormGroup.disable({emitEvent: false});
} else {
this.widgetSettingsFormGroup.enable({emitEvent: false});
}
}
writeValue(value: JsonFormComponentData): void {
this.widgetSettingsFormData = value;
if (this.changeSubscription) {
this.changeSubscription.unsubscribe();
this.changeSubscription = null;
}
if (this.definedSettingsComponent) {
this.definedSettingsComponent.settings = this.widgetSettingsFormData.model;
this.changeSubscription = this.definedSettingsComponent.settingsChanged.subscribe((settings) => {
this.updateModel(settings);
});
} else {
this.widgetSettingsFormGroup.get('settings').patchValue(this.widgetSettingsFormData, {emitEvent: false});
this.changeSubscription = this.widgetSettingsFormGroup.get('settings').valueChanges.subscribe(
(widgetSettingsFormData: JsonFormComponentData) => {
this.updateModel(widgetSettingsFormData.model);
}
);
}
}
useDefinedDirective(): boolean {
return this.settingsDirective &&
this.settingsDirective.length && !this.definedDirectiveError;
}
useJsonForm(): boolean {
return !this.settingsDirective || !this.settingsDirective.length;
}
private updateModel(settings: WidgetSettings) {
this.widgetSettingsFormData.model = settings;
if (this.definedSettingsComponent || this.widgetSettingsFormGroup.valid) {
this.propagateChange(this.widgetSettingsFormData);
} else {
this.propagateChange(null);
}
}
private validateDefinedDirective() {
if (this.definedSettingsComponentRef) {
this.definedSettingsComponentRef.destroy();
this.definedSettingsComponentRef = null;
}
if (this.settingsDirective && this.settingsDirective.length) {
const componentType = widgetSettingsComponentsMap[this.settingsDirective];
if (!componentType) {
this.definedDirectiveError = this.translate.instant('widget-config.settings-component-not-found',
{selector: this.settingsDirective});
} else {
if (this.changeSubscription) {
this.changeSubscription.unsubscribe();
this.changeSubscription = null;
}
this.definedSettingsContainer.clear();
const factory = this.cfr.resolveComponentFactory(componentType);
this.definedSettingsComponentRef = this.definedSettingsContainer.createComponent(factory);
this.definedSettingsComponent = this.definedSettingsComponentRef.instance;
this.definedSettingsComponent.settings = this.widgetSettingsFormData?.model;
this.changeSubscription = this.definedSettingsComponent.settingsChanged.subscribe((settings) => {
this.updateModel(settings);
});
}
}
}
validate() {
if (this.useDefinedDirective()) {
this.definedSettingsComponent.validate();
}
}
}

6
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -424,6 +424,8 @@ export interface WidgetConfigComponentData {
isDataEnabled: boolean;
settingsSchema: JsonSettingsSchema;
dataKeySettingsSchema: JsonSettingsSchema;
settingsDirective: string;
dataKeySettingsDirective: string;
}
export const MissingWidgetType: WidgetInfo = {
@ -510,6 +512,8 @@ export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo {
controllerScript: widgetTypeEntity.descriptor.controllerScript,
settingsSchema: widgetTypeEntity.descriptor.settingsSchema,
dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema,
settingsDirective: widgetTypeEntity.descriptor.settingsDirective,
dataKeySettingsDirective: widgetTypeEntity.descriptor.dataKeySettingsDirective,
defaultConfig: widgetTypeEntity.descriptor.defaultConfig
};
}
@ -536,6 +540,8 @@ export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
controllerScript: widgetInfo.controllerScript,
settingsSchema: widgetInfo.settingsSchema,
dataKeySettingsSchema: widgetInfo.dataKeySettingsSchema,
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective,
defaultConfig: widgetInfo.defaultConfig
};
return {

12
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html

@ -237,6 +237,18 @@
rows="2" maxlength="255"></textarea>
<mat-hint align="end">{{descriptionInput.value?.length || 0}}/255</mat-hint>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>widget.settings-form-selector</mat-label>
<input matInput
[(ngModel)]="widget.settingsDirective"
(ngModelChange)="isDirty = true"/>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>widget.data-key-settings-form-selector</mat-label>
<input matInput
[(ngModel)]="widget.dataKeySettingsDirective"
(ngModelChange)="isDirty = true"/>
</mat-form-field>
</div>
</div>
</mat-tab>

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

@ -25,6 +25,14 @@ import { EntityId } from '@shared/models/id/entity-id';
import * as moment_ from 'moment';
import { EntityDataPageLink, EntityFilter, KeyFilter } from '@shared/models/query/query.models';
import { PopoverPlacement } from '@shared/components/popover.models';
import { PageComponent } from '@shared/components/page.component';
import { AfterViewInit, Directive, EventEmitter, Inject, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AbstractControl, FormGroup } from '@angular/forms';
import {RuleChainType} from "@shared/models/rule-chain.models";
import {Observable} from "rxjs";
import {RuleNodeConfiguration} from "@shared/models/rule-node.models";
export enum widgetType {
timeseries = 'timeseries',
@ -143,6 +151,8 @@ export interface WidgetTypeDescriptor {
controllerScript: string;
settingsSchema?: string | any;
dataKeySettingsSchema?: string | any;
settingsDirective?: string;
dataKeySettingsDirective?: string;
defaultConfig: string;
sizeX: number;
sizeY: number;
@ -159,6 +169,7 @@ export interface WidgetTypeParameters {
singleEntity?: boolean;
warnOnPageDataOverflow?: boolean;
ignoreDataUpdateOnIntervalTick?: boolean;
}
export interface WidgetControllerDescriptor {
@ -483,6 +494,10 @@ export interface WidgetComparisonSettings {
comparisonCustomIntervalValue?: number;
}
export interface WidgetSettings {
[key: string]: any;
}
export interface WidgetConfig {
title?: string;
titleIcon?: string;
@ -512,7 +527,7 @@ export interface WidgetConfig {
decimals?: number;
noDataDisplayMessage?: string;
actions?: {[actionSourceId: string]: Array<WidgetActionDescriptor>};
settings?: any;
settings?: WidgetSettings;
alarmSource?: Datasource;
alarmStatusList?: AlarmSearchStatus[];
alarmSeverityList?: AlarmSeverity[];
@ -570,3 +585,113 @@ export interface WidgetSize {
sizeX: number;
sizeY: number;
}
export interface IWidgetSettingsComponent {
settings: WidgetSettings;
settingsChanged: Observable<WidgetSettings>;
validate();
[key: string]: any;
}
@Directive()
// tslint:disable-next-line:directive-class-suffix
export abstract class WidgetSettingsComponent extends PageComponent implements
IWidgetSettingsComponent, OnInit, AfterViewInit {
settingsValue: WidgetSettings;
private settingsSet = false;
set settings(value: WidgetSettings) {
this.settingsValue = value;
if (!this.settingsSet) {
this.settingsSet = true;
this.setupSettings(value);
} else {
this.updateSettings(value);
}
}
get settings(): WidgetSettings {
return this.settingsValue;
}
settingsChangedEmitter = new EventEmitter<WidgetSettings>();
settingsChanged = this.settingsChangedEmitter.asObservable();
protected constructor(@Inject(Store) protected store: Store<AppState>) {
super(store);
}
ngOnInit() {}
ngAfterViewInit(): void {
setTimeout(() => {
if (!this.validateSettings()) {
this.settingsChangedEmitter.emit(null);
}
}, 0);
}
validate() {
this.onValidate();
}
protected setupSettings(settings: WidgetSettings) {
this.onSettingsSet(this.prepareInputSettings(settings));
this.updateValidators(false);
for (const trigger of this.validatorTriggers()) {
const path = trigger.split('.');
let control: AbstractControl = this.settingsForm();
for (const part of path) {
control = control.get(part);
}
control.valueChanges.subscribe(() => {
this.updateValidators(true, trigger);
});
}
this.settingsForm().valueChanges.subscribe((updated: WidgetSettings) => {
this.onSettingsChanged(updated);
});
}
protected updateSettings(settings: WidgetSettings) {
this.settingsForm().reset(this.prepareInputSettings(settings), {emitEvent: false});
this.updateValidators(false);
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
}
protected validatorTriggers(): string[] {
return [];
}
protected onSettingsChanged(updated: WidgetSettings) {
this.settingsValue = updated;
if (this.validateSettings()) {
this.settingsChangedEmitter.emit(this.prepareOutputSettings(updated));
} else {
this.settingsChangedEmitter.emit(null);
}
}
protected prepareInputSettings(settings: WidgetSettings): WidgetSettings {
return settings;
}
protected prepareOutputSettings(settings: WidgetSettings): WidgetSettings {
return settings;
}
protected validateSettings(): boolean {
return this.settingsForm().valid;
}
protected onValidate() {}
protected abstract settingsForm(): FormGroup;
protected abstract onSettingsSet(settings: WidgetSettings);
}

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

@ -2985,6 +2985,8 @@
"widget-settings": "Widget settings",
"description": "Description",
"image-preview": "Image preview",
"settings-form-selector": "Settings form selector",
"data-key-settings-form-selector": "Data key settings form selector",
"javascript": "Javascript",
"js": "JS",
"remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?",
@ -3151,7 +3153,8 @@
"icon-size": "Icon size",
"advanced-settings": "Advanced settings",
"data-settings": "Data settings",
"no-data-display-message": "\"No data to display\" alternative message"
"no-data-display-message": "\"No data to display\" alternative message",
"settings-component-not-found": "Settings form component not found for selector '{{selector}}'"
},
"widget-type": {
"import": "Import widget type",
@ -3266,6 +3269,12 @@
"value": "Value"
},
"invalid-qr-code-text": "Invalid input text for QR code. Input should have a string type",
"qr-code": {
"use-qr-code-text-function": "Use QR code text function",
"qr-code-text-pattern": "QR code text pattern (for ex. '${entityName} | ${keyName} - some text.')",
"qr-code-text-pattern-required": "QR code text pattern is required.",
"qr-code-text-function": "QR code text function"
},
"persistent-table": {
"rpc-id": "RPC ID",
"message-type": "Message type",

Loading…
Cancel
Save