diff --git a/application/src/main/data/json/system/scada_symbols/level_and_fan.svg b/application/src/main/data/json/system/scada_symbols/level_and_fan.svg index 0ea27397bb..7a0b0e088b 100644 --- a/application/src/main/data/json/system/scada_symbols/level_and_fan.svg +++ b/application/src/main/data/json/system/scada_symbols/level_and_fan.svg @@ -6,6 +6,8 @@ "level", "fan" ], + "widgetSizeX": 3, + "widgetSizeY": 3, "stateRenderFunction": "var showMinMaxLevel = ctx.properties.showMinMaxLevel;\nvar minLevelElement = ctx.tags.minLevel[0];\nvar maxLevelElement = ctx.tags.maxLevel[0];\nvar minLevel = ctx.properties.minLevel; \nvar maxLevel = ctx.properties.maxLevel;\n\nif (showMinMaxLevel) {\n \n var minMaxLevelFont = ctx.properties.minMaxLevelFont;\n var minMaxLevelColor = ctx.properties.minMaxLevelColor;\n \n ctx.api.text(minLevelElement, minLevel);\n ctx.api.text(maxLevelElement, maxLevel);\n \n ctx.api.font(minLevelElement, minMaxLevelFont, minMaxLevelColor);\n ctx.api.font(maxLevelElement, minMaxLevelFont, minMaxLevelColor);\n \n} else {\n minLevelElement.hide();\n maxLevelElement.hide();\n}\n\nvar disabled = ctx.values.disabled;\nvar on = ctx.values.on;\nvar level = ctx.values.level;\n\nvar onButton = ctx.tags.onButton;\nvar offButton = ctx.tags.offButton;\nvar levelUpButton = ctx.tags.levelUpButton;\nvar levelDownButton = ctx.tags.levelDownButton;\n\nvar onButtonEnabled = !disabled && !on;\nvar offButtonEnabled = !disabled && on;\nvar levelUpEnabled = !disabled && level < maxLevel;\nvar levelDownEnabled = !disabled && level > minLevel;\n\nif (onButtonEnabled) {\n ctx.api.enable(onButton);\n onButton[0].findOne('rect').attr({fill: '#12ed19'});\n} else {\n ctx.api.disable(onButton);\n onButton[0].findOne('rect').attr({fill: '#777'});\n}\n \nif (offButtonEnabled) {\n ctx.api.enable(offButton);\n offButton[0].findOne('rect').attr({fill: '#ed121f'});\n} else {\n ctx.api.disable(offButton);\n offButton[0].findOne('rect').attr({fill: '#777'});\n} \n\n\nif (levelUpEnabled) {\n ctx.api.enable(levelUpButton);\n levelUpButton[0].findOne('rect').attr({fill: '#fff'});\n} else {\n ctx.api.disable(levelUpButton);\n levelUpButton[0].findOne('rect').attr({fill: '#777'});\n}\n \nif (levelDownEnabled) {\n ctx.api.enable(levelDownButton);\n levelDownButton[0].findOne('rect').attr({fill: '#fff'});\n} else {\n ctx.api.disable(levelDownButton);\n levelDownButton[0].findOne('rect').attr({fill: '#777'});\n}", "tags": [ { diff --git a/application/src/main/data/json/system/widget_types/scada_symbol.json b/application/src/main/data/json/system/widget_types/scada_symbol.json index 616ad37cdc..5a40c26ffa 100644 --- a/application/src/main/data/json/system/widget_types/scada_symbol.json +++ b/application/src/main/data/json/system/widget_types/scada_symbol.json @@ -6,12 +6,12 @@ "description": "", "descriptor": { "type": "rpc", - "sizeX": 3.5, - "sizeY": 3.5, + "sizeX": 3, + "sizeY": 3, "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '280px',\n previewHeight: '280px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '300px',\n previewHeight: '320px',\n embedTitlePanel: true,\n targetDeviceOptional: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-scada-symbol-widget-settings", diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index ebca8ac4dd..e3c8c209c4 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -382,6 +382,12 @@ public class InstallScripts { } settings.put("scadaSymbolUrl", symbolUrl); ((ObjectNode)descriptor).put("defaultConfig", JacksonUtil.toString(defaultConfig)); + ((ObjectNode)descriptor).put("sizeX", metadata.getWidgetSizeX()); + ((ObjectNode)descriptor).put("sizeY", metadata.getWidgetSizeY()); + String controllerScript = descriptor.get("controllerScript").asText(); + controllerScript = controllerScript.replaceAll("previewWidth: '\\d*px'", "previewWidth: '" + (metadata.getWidgetSizeX() * 100) + "px'"); + controllerScript = controllerScript.replaceAll("previewHeight: '\\d*px'", "previewHeight: '" + (metadata.getWidgetSizeY() * 100 + 20) + "px'"); + ((ObjectNode)descriptor).put("controllerScript", controllerScript); var savedWidget = widgetTypeService.saveWidgetType(scadaSymbolWidget); return savedWidget.getFqn(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java b/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java index 675c47002d..e92adba01d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java @@ -283,6 +283,8 @@ public class ImageUtils { private String title; private String description; private String[] searchTags; + private int widgetSizeX; + private int widgetSizeY; public ScadaSymbolMetadataInfo(String fileName, JsonNode metaData) { if (metaData != null && metaData.has("title")) { @@ -304,6 +306,16 @@ public class ImageUtils { } else { searchTags = new String[0]; } + if (metaData != null && metaData.has("widgetSizeX")) { + widgetSizeX = metaData.get("widgetSizeX").asInt(); + } else { + widgetSizeX = 3; + } + if (metaData != null && metaData.has("widgetSizeY")) { + widgetSizeY = metaData.get("widgetSizeY").asInt(); + } else { + widgetSizeY = 3; + } } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index c69a2a4de9..8d297bd34a 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -23,7 +23,9 @@
-
+
this.dashboardWidgets.sortWidgets(), @@ -530,7 +552,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo if (autofillHeight) { this.gridsterOpts.gridType = this.isMobileSize ? GridType.Fixed : GridType.Fit; } else { - this.gridsterOpts.gridType = this.isMobileSize ? GridType.Fixed : GridType.ScrollVertical; + this.gridsterOpts.gridType = this.isMobileSize ? GridType.Fixed : this.gridType || GridType.ScrollVertical; } const mobileBreakPoint = this.isMobileSize ? 20000 : 0; this.gridsterOpts.mobileBreakpoint = mobileBreakPoint; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/target-device.component.html b/ui-ngx/src/app/modules/home/components/widget/config/target-device.component.html index ad2728484c..7adea38000 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/target-device.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/target-device.component.html @@ -24,13 +24,13 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/target-device.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/target-device.component.ts index c1d09f50b6..58893cb2a7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/target-device.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/target-device.component.ts @@ -60,6 +60,10 @@ export class TargetDeviceComponent implements ControlValueAccessor, OnInit, Vali return this.widgetConfigComponent.widgetConfigCallbacks; } + public get targetDeviceOptional(): boolean { + return this.widgetConfigComponent.modelValue?.typeParameters?.targetDeviceOptional; + } + targetDeviceType = TargetDeviceType; entityType = EntityType; @@ -103,9 +107,9 @@ export class TargetDeviceComponent implements ControlValueAccessor, OnInit, Vali ngOnInit() { this.targetDeviceFormGroup = this.fb.group({ - type: [null, !this.widgetEditMode ? [Validators.required] : []], - deviceId: [null, !this.widgetEditMode ? [Validators.required] : []], - entityAliasId: [null, !this.widgetEditMode ? [Validators.required] : []] + type: [null, (!this.widgetEditMode && !this.targetDeviceOptional) ? [Validators.required] : []], + deviceId: [null, (!this.widgetEditMode && !this.targetDeviceOptional) ? [Validators.required] : []], + entityAliasId: [null, (!this.widgetEditMode && !this.targetDeviceOptional) ? [Validators.required] : []] }); this.targetDeviceFormGroup.get('type').valueChanges.subscribe(() => { this.updateValidators(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts index 75adedc16d..45f54b80df 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts @@ -177,6 +177,8 @@ export interface ScadaSymbolMetadata { title: string; description?: string; searchTags?: string[]; + widgetSizeX: number; + widgetSizeY: number; stateRenderFunction?: string; stateRender?: ScadaSymbolStateRenderFunction; tags: ScadaSymbolTag[]; @@ -186,6 +188,8 @@ export interface ScadaSymbolMetadata { export const emptyMetadata = (): ScadaSymbolMetadata => ({ title: '', + widgetSizeX: 3, + widgetSizeY: 3, tags: [], behavior: [], properties: [] @@ -443,12 +447,14 @@ export class ScadaSymbolObject { this.stateValueSubjects[stateValueId].unsubscribe(); } this.valueActions.forEach(v => v.destroy()); - for (const tag of this.metadata.tags) { - const elements = this.context.tags[tag.tag]; - elements.forEach(element => { - element.timeline().finish(); - element.timeline(null); - }); + if (this.context) { + for (const tag of this.metadata.tags) { + const elements = this.context.tags[tag.tag]; + elements.forEach(element => { + element.timeline().finish(); + element.timeline(null); + }); + } } if (this.svgShape) { this.svgShape.remove(); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index f1801561bd..ace991c6a6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -589,6 +589,9 @@ export class WidgetComponentService { if (isUndefined(result.typeParameters.displayRpcMessageToast)) { result.typeParameters.displayRpcMessageToast = true; } + if (isUndefined(result.typeParameters.targetDeviceOptional)) { + result.typeParameters.targetDeviceOptional = false; + } if (isFunction(widgetTypeInstance.actionSources)) { result.actionSources = widgetTypeInstance.actionSources(); } else { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index 2914ed4391..f4c2eb1fa8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -962,7 +962,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe if (this.modelValue) { const config = this.modelValue.config; if (this.widgetType === widgetType.rpc && this.modelValue.isDataEnabled) { - if (!this.widgetEditMode && !targetDeviceValid(config.targetDevice)) { + if ((!this.widgetEditMode && !this.modelValue?.typeParameters.targetDeviceOptional) && !targetDeviceValid(config.targetDevice)) { return { targetDevice: { valid: false diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html index fd92d16521..573ea27af0 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html @@ -52,6 +52,23 @@
+
+
scada.widget-size
+
+
{{ 'scada.cols' | translate }}
+ + + +
{{ 'scada.rows' | translate }}
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts index 0d6e6771ce..db266f5a08 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts @@ -130,6 +130,8 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni title: [null, [Validators.required]], description: [null], searchTags: [null], + widgetSizeX: [null, [Validators.min(1), Validators.max(24), Validators.required]], + widgetSizeY: [null, [Validators.min(1), Validators.max(24), Validators.required]], stateRenderFunction: [null], tags: [null], behavior: [null], @@ -176,6 +178,8 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni title: value?.title, description: value?.description, searchTags: value?.searchTags, + widgetSizeX: value?.widgetSizeX || 3, + widgetSizeY: value?.widgetSizeY || 3, stateRenderFunction: value?.stateRenderFunction, tags: value?.tags, behavior: value?.behavior, diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html index e487201440..72792c835c 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html @@ -29,6 +29,13 @@ [showCloseDetails]="false" headerHeightPx="64" headerTitle="{{symbolData?.imageResource?.title}}"> +
+ +
diff --git a/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.ts index e5a0fa3712..3685b553ef 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.ts @@ -14,8 +14,8 @@ /// limitations under the License. /// -import { Component, OnInit } from '@angular/core'; -import { MatDialogRef } from '@angular/material/dialog'; +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -29,6 +29,12 @@ export interface SaveWidgetTypeAsDialogResult { widgetBundleId?: string; } +export interface SaveWidgetTypeAsDialogData { + dialogTitle?: string; + title?: string; + saveAsActionTitle?: string; +} + @Component({ selector: 'tb-save-widget-type-as-dialog', templateUrl: './save-widget-type-as-dialog.component.html', @@ -39,9 +45,12 @@ export class SaveWidgetTypeAsDialogComponent extends saveWidgetTypeAsFormGroup: FormGroup; bundlesScope: string; + dialogTitle = 'widget.save-widget-as'; + saveAsActionTitle = 'action.saveAs'; constructor(protected store: Store, protected router: Router, + @Inject(MAT_DIALOG_DATA) private data: SaveWidgetTypeAsDialogData, public dialogRef: MatDialogRef, public fb: FormBuilder) { super(store, router, dialogRef); @@ -52,11 +61,18 @@ export class SaveWidgetTypeAsDialogComponent extends } else { this.bundlesScope = 'system'; } + + if (this.data?.dialogTitle) { + this.dialogTitle = this.data.dialogTitle; + } + if (this.data?.saveAsActionTitle) { + this.saveAsActionTitle = this.data.saveAsActionTitle; + } } ngOnInit(): void { this.saveWidgetTypeAsFormGroup = this.fb.group({ - title: [null, [Validators.required]], + title: [this.data?.title, [Validators.required]], widgetsBundle: [null] }); } diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 01a7e98215..3646f3cdc5 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -187,6 +187,7 @@ export interface WidgetTypeParameters { defaultLatestDataKeysFunction?: (configComponent: any, configData: any) => DataKey[]; dataKeySettingsFunction?: DataKeySettingsFunction; displayRpcMessageToast?: boolean; + targetDeviceOptional?: boolean; } export interface WidgetControllerDescriptor { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index b922ff1b10..16822eb2a5 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3455,6 +3455,9 @@ "title": "Title", "description": "Description", "search-tags": "Search tags", + "widget-size": "Widget size", + "cols": "cols", + "rows": "rows", "state-render-function": "State render function", "preview": "Preview", "preview-widget-action-text": "Widget action '{{type}}' successfully invoked!", @@ -3464,6 +3467,8 @@ "browse-symbol-from-gallery": "Browse SCADA symbol from gallery", "zoom-in": "Zoom In", "zoom-out": "Zoom Out", + "create-widget": "Create widget", + "create-widget-from-symbol": "Create widget from SCADA symbol", "tag": { "tag": "Tag", "on-click-action": "On click action",