UI: Ability to import form properties from JSON content. Update help resources according to new widget settings forms.

This commit is contained in:
Igor Kulikov 2024-12-23 16:48:20 +02:00
parent 39abbacb20
commit f6071177ae
13 changed files with 205 additions and 97 deletions

View File

@ -37,7 +37,12 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { FormProperty, FormPropertyType, propertyValid } from '@shared/models/dynamic-form.models'; import {
cleanupFormProperties,
FormProperty,
FormPropertyType,
propertyValid
} from '@shared/models/dynamic-form.models';
import { import {
DynamicFormPropertyRowComponent DynamicFormPropertyRowComponent
} from '@home/components/widget/lib/settings/common/dynamic-form/dynamic-form-property-row.component'; } from '@home/components/widget/lib/settings/common/dynamic-form/dynamic-form-property-row.component';
@ -134,7 +139,7 @@ export class DynamicFormPropertiesComponent implements ControlValueAccessor, OnI
controls[i].patchValue(p, {emitEvent: false}); controls[i].patchValue(p, {emitEvent: false});
} }
}); });
this.propagateChange(properties); this.propagateChange(cleanupFormProperties(properties));
} }
); );
} }

View File

@ -54,8 +54,24 @@ const widgetEditorCompletions = (settingsCompletions?: TbEditorCompletions): TbE
description: 'Called when widget element is destroyed. Should be used to cleanup all resources if necessary.', description: 'Called when widget element is destroyed. Should be used to cleanup all resources if necessary.',
meta: 'function' meta: 'function'
}, },
getSettingsForm: {
description: 'Optional function returning widget settings form array as alternative to <b>Settings form</b> tab of settings section.',
meta: 'function',
return: {
description: 'An array of widget settings form properties',
type: 'Array&lt;FormProperty&gt;'
}
},
getDataKeySettingsForm: {
description: 'Optional function returning particular data key settings form array as alternative to <b>Data key settings form</b> tab of settings section.',
meta: 'function',
return: {
description: 'An array of data key settings form properties',
type: 'Array&lt;FormProperty&gt;'
}
},
getSettingsSchema: { getSettingsSchema: {
description: 'Optional function returning widget settings schema json as alternative to <b>Settings tab</b> of <a href="https://thingsboard.io/docs/user-guide/contribution/widgets-development/#settings-schema-section" target="_blank">Settings schema section</a>.', description: '<b>Deprecated</b>. Use getSettingsForm() function.',
meta: 'function', meta: 'function',
return: { return: {
description: 'An widget settings schema json', description: 'An widget settings schema json',
@ -63,7 +79,7 @@ const widgetEditorCompletions = (settingsCompletions?: TbEditorCompletions): TbE
} }
}, },
getDataKeySettingsSchema: { getDataKeySettingsSchema: {
description: 'Optional function returning particular data key settings schema json as alternative to <b>Data key settings schema</b> of <a href="https://thingsboard.io/docs/user-guide/contribution/widgets-development/#settings-schema-section" target="_blank">Settings schema section</a>.', description: '<b>Deprecated</b>. Use getDataKeySettingsForm() function.',
meta: 'function', meta: 'function',
return: { return: {
description: 'A particular data key settings schema json', description: 'A particular data key settings schema json',

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<form [formGroup]="importFormGroup" (ngSubmit)="importFromJson()"> <form [formGroup]="importFormGroup" (ngSubmit)="importFromJson()" style="width: 500px;">
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2 translate>{{ importTitle }}</h2> <h2 translate>{{ importTitle }}</h2>
<span class="flex-1"></span> <span class="flex-1"></span>
@ -30,15 +30,28 @@
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="isLoading$ | async">
<div class="flex flex-1 flex-col"> <div class="flex flex-1 flex-col">
<tb-file-input <tb-toggle-select *ngIf="enableImportFromContent"
class="flex-1"
formControlName="importType">
<tb-toggle-option value="file">{{ importFileLabel | translate }}</tb-toggle-option>
<tb-toggle-option value="content">{{ importContentLabel | translate }}</tb-toggle-option>
</tb-toggle-select>
<tb-file-input *ngIf="importFormGroup.get('importType').value === 'file'"
[contentConvertFunction]="loadDataFromJsonContent" [contentConvertFunction]="loadDataFromJsonContent"
formControlName="jsonContent" [existingFileName]="currentFileName"
(fileNameChanged)="currentFileName = $event"
formControlName="fileContent"
required required
label="{{importFileLabel | translate}}" label="{{importFileLabel | translate}}"
dropLabel="{{ 'import.drop-json-file-or' | translate }}" dropLabel="{{ 'import.drop-json-file-or' | translate }}"
accept=".json,application/json" accept=".json,application/json"
allowedExtensions="json"> allowedExtensions="json">
</tb-file-input> </tb-file-input>
<tb-json-object-edit *ngIf="importFormGroup.get('importType').value === 'content'"
formControlName="jsonContent"
jsonRequired
label="{{ importContentLabel | translate }}">
</tb-json-object-edit>
</div> </div>
</fieldset> </fieldset>
</div> </div>

View File

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; import { Component, DestroyRef, Inject, OnInit, SkipSelf } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core'; import { ErrorStateMatcher } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@ -23,10 +23,14 @@ import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDire
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component'; import { DialogComponent } from '@app/shared/components/dialog.component';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { isDefinedAndNotNull } from '@core/utils';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export interface ImportDialogData { export interface ImportDialogData {
importTitle: string; importTitle: string;
importFileLabel: string; importFileLabel: string;
enableImportFromContent?: boolean;
importContentLabel?: string;
} }
@Component({ @Component({
@ -40,9 +44,13 @@ export class ImportDialogComponent extends DialogComponent<ImportDialogComponent
importTitle: string; importTitle: string;
importFileLabel: string; importFileLabel: string;
enableImportFromContent: boolean;
importContentLabel: string;
importFormGroup: UntypedFormGroup; importFormGroup: UntypedFormGroup;
currentFileName: string;
submitted = false; submitted = false;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
@ -50,17 +58,26 @@ export class ImportDialogComponent extends DialogComponent<ImportDialogComponent
@Inject(MAT_DIALOG_DATA) public data: ImportDialogData, @Inject(MAT_DIALOG_DATA) public data: ImportDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<ImportDialogComponent>, public dialogRef: MatDialogRef<ImportDialogComponent>,
private destroyRef: DestroyRef,
private fb: UntypedFormBuilder) { private fb: UntypedFormBuilder) {
super(store, router, dialogRef); super(store, router, dialogRef);
this.importTitle = data.importTitle; this.importTitle = data.importTitle;
this.importFileLabel = data.importFileLabel; this.importFileLabel = data.importFileLabel;
this.enableImportFromContent = isDefinedAndNotNull(data.enableImportFromContent) ? data.enableImportFromContent : false;
this.importFormGroup = this.fb.group({ this.importContentLabel = data.importContentLabel;
jsonContent: [null, [Validators.required]]
});
} }
ngOnInit(): void { ngOnInit(): void {
this.importFormGroup = this.fb.group({
importType: ['file'],
fileContent: [null, [Validators.required]],
jsonContent: [null, [Validators.required]]
});
this.importFormGroup.get('importType').valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {
this.importTypeChanged();
});
} }
isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean {
@ -85,7 +102,19 @@ export class ImportDialogComponent extends DialogComponent<ImportDialogComponent
importFromJson(): void { importFromJson(): void {
this.submitted = true; this.submitted = true;
const importData = this.importFormGroup.get('jsonContent').value; const importType: 'file' | 'content' = this.importFormGroup.get('importType').value;
const importData = this.importFormGroup.get(importType === 'file' ? 'fileContent' : 'jsonContent').value;
this.dialogRef.close(importData); this.dialogRef.close(importData);
} }
private importTypeChanged() {
const importType: 'file' | 'content' = this.importFormGroup.get('importType').value;
if (importType === 'file') {
this.importFormGroup.get('fileContent').enable({emitEvent: false});
this.importFormGroup.get('jsonContent').disable({emitEvent: false});
} else {
this.importFormGroup.get('fileContent').disable({emitEvent: false});
this.importFormGroup.get('jsonContent').enable({emitEvent: false});
}
}
} }

View File

@ -125,7 +125,8 @@ export class ImportExportService {
} }
public importFormProperties(): Observable<FormProperty[]> { public importFormProperties(): Observable<FormProperty[]> {
return this.openImportDialog('dynamic-form.import-form', 'dynamic-form.form-json-file').pipe( return this.openImportDialog('dynamic-form.import-form',
'dynamic-form.json-file', true, 'dynamic-form.json-content').pipe(
map((properties: FormProperty[]) => { map((properties: FormProperty[]) => {
if (!this.validateImportedFormProperties(properties)) { if (!this.validateImportedFormProperties(properties)) {
this.store.dispatch(new ActionNotificationShow( this.store.dispatch(new ActionNotificationShow(
@ -1134,14 +1135,17 @@ export class ImportExportService {
}; };
} }
private openImportDialog(importTitle: string, importFileLabel: string): Observable<any> { private openImportDialog(importTitle: string, importFileLabel: string,
enableImportFromContent = false, importContentLabel?: string): Observable<any> {
return this.dialog.open<ImportDialogComponent, ImportDialogData, return this.dialog.open<ImportDialogComponent, ImportDialogData,
any>(ImportDialogComponent, { any>(ImportDialogComponent, {
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
importTitle, importTitle,
importFileLabel importFileLabel,
enableImportFromContent,
importContentLabel
} }
}).afterClosed().pipe( }).afterClosed().pipe(
map((importedData) => { map((importedData) => {

View File

@ -325,7 +325,7 @@ export const widgetContextCompletionsWithSettings = (settingsCompletions?: TbEdi
type: 'object' type: 'object'
}, },
settings: { settings: {
description: 'Widget settings containing widget specific properties according to the defined <a href="https://thingsboard.io/docs/user-guide/contribution/widgets-development/#settings-schema-section" target="_blank">settings json schema</a>', description: 'Widget settings containing widget specific properties according to the defined settings form.',
meta: 'property', meta: 'property',
type: 'object', type: 'object',
children: settingsCompletions children: settingsCompletions
@ -363,7 +363,7 @@ export const widgetContextCompletionsWithSettings = (settingsCompletions?: TbEdi
} }
}, },
settings: { settings: {
description: 'Widget settings containing widget specific properties according to the defined <a href="https://thingsboard.io/docs/user-guide/contribution/widgets-development/#settings-schema-section" target="_blank">settings json schema</a>', description: 'Widget settings containing widget specific properties according to the defined settings form.',
meta: 'property', meta: 'property',
type: 'object', type: 'object',
children: settingsCompletions children: settingsCompletions

View File

@ -16,8 +16,8 @@
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models'; import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models';
import { deepClone, isDefinedAndNotNull, isString } from '@core/utils'; import { deepClone, isDefinedAndNotNull, isEmptyStr, isString, isUndefinedOrNull } from '@core/utils';
import { JsonSchema, JsonSettingsSchema, JsonFormData, KeyLabelItem } from '@shared/legacy/json-form.models'; import { JsonFormData, JsonSchema, JsonSettingsSchema, KeyLabelItem } from '@shared/legacy/json-form.models';
import JsonFormUtils from '@shared/legacy/json-form-utils'; import JsonFormUtils from '@shared/legacy/json-form-utils';
import { constantColor, Font } from '@shared/models/widget-settings.models'; import { constantColor, Font } from '@shared/models/widget-settings.models';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@ -165,6 +165,63 @@ export interface FormHtmlSection extends FormPropertyBase {
export type FormProperty = FormPropertyBase & FormTextareaProperty & FormNumberProperty & FormSelectProperty & FormRadiosProperty export type FormProperty = FormPropertyBase & FormTextareaProperty & FormNumberProperty & FormSelectProperty & FormRadiosProperty
& FormDateTimeProperty & FormJavascriptProperty & FormMarkdownProperty & FormFieldSetProperty & FormArrayProperty & FormHtmlSection; & FormDateTimeProperty & FormJavascriptProperty & FormMarkdownProperty & FormFieldSetProperty & FormArrayProperty & FormHtmlSection;
export const cleanupFormProperties = (properties: FormProperty[]): FormProperty[] => {
for (const property of properties) {
cleanupFormProperty(property);
}
return properties;
}
export const cleanupFormProperty = (property: FormProperty): FormProperty => {
if (property.type !== FormPropertyType.number) {
delete property.min;
delete property.max;
delete property.step;
}
if (property.type !== FormPropertyType.textarea) {
delete property.rows;
}
if (property.type !== FormPropertyType.fieldset) {
delete property.properties;
} else if (property.properties?.length) {
property.properties = cleanupFormProperties(property.properties);
}
if (property.type !== FormPropertyType.array) {
delete property.arrayItemName;
delete property.arrayItemType;
}
if (property.type !== FormPropertyType.select) {
delete property.multiple;
delete property.allowEmptyOption;
delete property.minItems;
delete property.maxItems;
}
if (property.type !== FormPropertyType.radios) {
delete property.direction;
}
if (![FormPropertyType.select, FormPropertyType.radios].includes(property.type)) {
delete property.items;
}
if (property.type !== FormPropertyType.datetime) {
delete property.allowClear;
delete property.dateTimeType;
}
if (![FormPropertyType.javascript, FormPropertyType.markdown].includes(property.type)) {
delete property.helpId;
}
if (property.type !== FormPropertyType.htmlSection) {
delete property.htmlClassList;
delete property.htmlContent;
}
for (const key of Object.keys(property)) {
const val = property[key];
if (isUndefinedOrNull(val) || isEmptyStr(val)) {
delete property[key];
}
}
return property;
}
export enum FormPropertyContainerType { export enum FormPropertyContainerType {
field = 'field', field = 'field',
row = 'row', row = 'row',

View File

@ -40,32 +40,25 @@ The **Widget Editor** will be opened, pre-populated with the content of the defa
{:copy-code} {:copy-code}
``` ```
- Put the following JSON content inside the "Settings schema" tab of **Settings schema section**: - Import the following JSON content inside the "Settings form" tab by clicking on 'Import form from JSON' button:
```json ```json
{ [
"schema": { {
"type": "object", "id": "alarmSeverityColorFunction",
"title": "AlarmTableSettings", "name": "Alarm severity color function: f(severity)",
"properties": { "type": "javascript",
"alarmSeverityColorFunction": { "default": "if (severity == 'CRITICAL') {\n return 'red';\n} else if (severity == 'MAJOR') {\n return 'orange';\n} else return 'green';",
"title": "Alarm severity color function: f(severity)", "required": false
"type": "string", }
"default": "if(severity == 'CRITICAL') {return 'red';} else if (severity == 'MAJOR') {return 'orange';} else return 'green'; " ]
}
},
"required": []
},
"form": [
{
"key": "alarmSeverityColorFunction",
"type": "javascript"
}
]
}
{:copy-code} {:copy-code}
``` ```
- Clear all 'form selector' fields in the "Widget settings" tab.
- Turn off 'Has basic mode' switch in the "Widget settings" tab.
- Put the following JavaScript code inside the "JavaScript" section: - Put the following JavaScript code inside the "JavaScript" section:
```javascript ```javascript

View File

@ -40,35 +40,28 @@ The **Widget Editor** will open, pre-populated with default **Control** template
{:copy-code} {:copy-code}
``` ```
- Put the following JSON content inside the "Settings schema" tab of **Settings schema section**: - Import the following JSON content inside the "Settings form" tab by clicking on 'Import form from JSON' button:
```json ```json
{ [
"schema": { {
"type": "object", "id": "oneWayElseTwoWay",
"title": "Settings", "name": "Is One Way Command",
"properties": { "type": "switch",
"oneWayElseTwoWay": { "default": true
"title": "Is One Way Command", },
"type": "boolean", {
"default": true "id": "requestTimeout",
}, "name": "RPC request timeout",
"requestTimeout": { "type": "number",
"title": "RPC request timeout", "default": 500
"type": "number", }
"default": 500 ]
}
},
"required": []
},
"form": [
"oneWayElseTwoWay",
"requestTimeout"
]
}
{:copy-code} {:copy-code}
``` ```
- Clear value of 'Settings form selector' in the "Widget settings" tab.
- Put the following JavaScript code inside the "JavaScript" section: - Put the following JavaScript code inside the "JavaScript" section:
```javascript ```javascript

View File

@ -17,28 +17,23 @@ The **Widget Editor** will be opened pre-populated with the content of default *
{:copy-code} {:copy-code}
``` ```
- Put the following JSON content inside the "Settings schema" tab of **Settings schema section**: - Import the following JSON content inside the "Settings form" tab by clicking on 'Import form from JSON' button:
```json ```json
{ [
"schema": { {
"type": "object", "id": "alertContent",
"title": "Settings", "name": "Alert content",
"properties": { "type": "text",
"alertContent": { "default": "Content derived from alertContent property of widget settings.",
"title": "Alert content", "fieldClass": "flex"
"type": "string", }
"default": "Content derived from alertContent property of widget settings." ]
}
}
},
"form": [
"alertContent"
]
}
{:copy-code} {:copy-code}
``` ```
- Clear value of 'Settings form selector' in the "Widget settings" tab.
- Put the following JavaScript code inside the "JavaScript" section: - Put the following JavaScript code inside the "JavaScript" section:
```javascript ```javascript

View File

@ -10,18 +10,20 @@ Each widget function should be defined as a property of the **self** variable.
In order to implement a new widget, the following JavaScript functions should be defined *(Note: each function is optional and can be implemented according to widget specific behaviour):* In order to implement a new widget, the following JavaScript functions should be defined *(Note: each function is optional and can be implemented according to widget specific behaviour):*
|{:auto} **Function** | **Description** | | {:auto} **Function** | **Description** |
|------------------------------------|----------------------------------------------------------------------------------------| |-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ``` onInit() ``` | The first function which is called when widget is ready for initialization. Should be used to prepare widget DOM, process widget settings and initial subscription information. | | ``` onInit() ``` | The first function which is called when widget is ready for initialization. Should be used to prepare widget DOM, process widget settings and initial subscription information. |
| ``` onDataUpdated() ``` | Called when the new data is available from the widget subscription. Latest data can be accessed from the <span trigger-style="fontSize: 16px;" trigger-text="<b>defaultSubscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> object of widget context (**ctx**). | | ``` onDataUpdated() ``` | Called when the new data is available from the widget subscription. Latest data can be accessed from the <span trigger-style="fontSize: 16px;" trigger-text="<b>defaultSubscription</b>" tb-help-popup="widget/editor/widget_js_subscription_object"></span> object of widget context (**ctx**). |
| ``` onResize() ``` | Called when widget container is resized. Latest width and height can be obtained from widget context (**ctx**). | | ``` onResize() ``` | Called when widget container is resized. Latest width and height can be obtained from widget context (**ctx**). |
| ``` onEditModeChanged() ``` | Called when dashboard editing mode is changed. Latest mode is handled by isEdit property of **ctx**. | | ``` onEditModeChanged() ``` | Called when dashboard editing mode is changed. Latest mode is handled by isEdit property of **ctx**. |
| ``` onMobileModeChanged() ``` | Called when dashboard view width crosses mobile breakpoint. Latest state is handled by isMobile property of **ctx**. | | ``` onMobileModeChanged() ``` | Called when dashboard view width crosses mobile breakpoint. Latest state is handled by isMobile property of **ctx**. |
| ``` onDestroy() ``` | Called when widget element is destroyed. Should be used to cleanup all resources if necessary. | | ``` onDestroy() ``` | Called when widget element is destroyed. Should be used to cleanup all resources if necessary. |
| ``` getSettingsSchema() ``` | Optional function returning widget settings schema json as alternative to **Settings schema** of settings section. | | ``` getSettingsForm() ``` | Optional function returning widget settings form array as alternative to **Settings form** tab of settings section. |
| ``` getDataKeySettingsSchema() ``` | Optional function returning particular data key settings schema json as alternative to **Data key settings schema** tab of settings section. | | ``` getDataKeySettingsForm() ``` | Optional function returning particular data key settings form array as alternative to **Data key settings form** tab of settings section. |
| ``` typeParameters() ``` | Returns [WidgetTypeParameters{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L151) object describing widget datasource parameters. See <span trigger-style="fontSize: 16px;" trigger-text="<b>Type parameters object</b>" tb-help-popup="widget/editor/widget_js_type_parameters_object"></span> | | | ``` typeParameters() ``` | Returns [WidgetTypeParameters{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L151) object describing widget datasource parameters. See <span trigger-style="fontSize: 16px;" trigger-text="<b>Type parameters object</b>" tb-help-popup="widget/editor/widget_js_type_parameters_object"></span> | |
| ``` actionSources() ``` | Returns map describing available widget action sources ([WidgetActionSource{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L121)) used to define user actions. See <span trigger-style="fontSize: 16px;" trigger-text="<b>Action sources object</b>" tb-help-popup="widget/editor/widget_js_action_sources_object"></span> | | ``` actionSources() ``` | Returns map describing available widget action sources ([WidgetActionSource{:target="_blank"}](https://github.com/thingsboard/thingsboard/blob/2627fe51d491055d4140f16617ed543f7f5bd8f6/ui-ngx/src/app/shared/models/widget.models.ts#L121)) used to define user actions. See <span trigger-style="fontSize: 16px;" trigger-text="<b>Action sources object</b>" tb-help-popup="widget/editor/widget_js_action_sources_object"></span> |
| ~~getSettingsSchema()~~ | **Deprecated**. Use getSettingsForm() function. |
| ~~getDataKeySettingsSchema()~~ | **Deprecated**. Use getDataKeySettingsForm() function. |
<div class="divider"></div> <div class="divider"></div>

View File

@ -26,7 +26,7 @@ For [Latest values{:target="_blank"}](${siteBaseUrl}/docs${docPlatformPrefix}/us
label: 'Sin', // label of the dataKey. Used as display value (for ex. in the widget legend section) label: 'Sin', // label of the dataKey. Used as display value (for ex. in the widget legend section)
color: '#ffffff', // color of the key. Can be used by widget to set color of the key data (for ex. lines in line chart or segments in the pie chart). color: '#ffffff', // color of the key. Can be used by widget to set color of the key data (for ex. lines in line chart or segments in the pie chart).
funcBody: "", // only applicable for datasource with type "function" and "function" key type. Defines body of the function to generate simulated data. funcBody: "", // only applicable for datasource with type "function" and "function" key type. Defines body of the function to generate simulated data.
settings: {} // dataKey specific settings with structure according to the defined Data key settings json schema. See "Settings schema section". settings: {} // dataKey specific settings with structure according to the defined Data key settings form.
}, },
//... //...
] ]
@ -72,7 +72,7 @@ For [Alarm widget{:target="_blank"}](${siteBaseUrl}/docs${docPlatformPrefix}/use
type: 'alarm', // type of the dataKey. Only "alarm" in this case. type: 'alarm', // type of the dataKey. Only "alarm" in this case.
label: 'Severity', // label of the dataKey. Used as display value (for ex. as a column title in the Alarms table) label: 'Severity', // label of the dataKey. Used as display value (for ex. as a column title in the Alarms table)
color: '#ffffff', // color of the key. Can be used by widget to set color of the key data. color: '#ffffff', // color of the key. Can be used by widget to set color of the key data.
settings: {} // dataKey specific settings with structure according to the defined Data key settings json schema. See "Settings schema section". settings: {} // dataKey specific settings with structure according to the defined Data key settings form.
}, },
//... //...
] ]

View File

@ -1699,7 +1699,8 @@
"clear-form-prompt": "Are you sure you want to remove all form properties?", "clear-form-prompt": "Are you sure you want to remove all form properties?",
"import-form": "Import form from JSON", "import-form": "Import form from JSON",
"export-form": "Export form to JSON", "export-form": "Export form to JSON",
"form-json-file": "Form JSON file", "json-file": "JSON file",
"json-content": "JSON content",
"invalid-form-json-file-error": "Unable to import form from JSON: Invalid form JSON data structure." "invalid-form-json-file-error": "Unable to import form from JSON: Invalid form JSON data structure."
}, },
"asset-profile": { "asset-profile": {