Browse Source

UI: SCADA symbol components refactoring.

pull/11391/head
Igor Kulikov 2 years ago
parent
commit
9e8fe8ad73
  1. 1
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 13
      application/src/main/data/json/system/widget_bundles/scada_symbols.json
  3. 26
      application/src/main/data/json/system/widget_types/iot_svg.json
  4. 28
      application/src/main/data/json/system/widget_types/scada_symbol.json
  5. 2
      application/src/main/java/org/thingsboard/server/controller/ImageController.java
  6. 32
      application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java
  7. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ResourceSubType.java
  8. 17
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java
  9. 8
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  10. 100
      ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.html
  11. 157
      ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.ts
  12. 48
      ui-ngx/src/app/modules/home/components/widget/config/basic/svg/iot-svg-basic-config.component.html
  13. 95
      ui-ngx/src/app/modules/home/components/widget/config/basic/svg/iot-svg-basic-config.component.ts
  14. 4
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html
  15. 4
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts
  16. 11
      ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.html
  17. 16
      ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.scss
  18. 80
      ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.ts
  19. 20
      ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.models.ts
  20. 230
      ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts
  21. 50
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.html
  22. 11
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.scss
  23. 129
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.ts
  24. 14
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.models.ts
  25. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  26. 6
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  27. 6
      ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts
  28. 10
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.html
  29. 28
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.ts
  30. 4
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.html
  31. 50
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts
  32. 12
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.ts
  33. 3
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component.ts
  34. 56
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts
  35. 32
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts
  36. 16
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts
  37. 16
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.ts
  38. 28
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.html
  39. 36
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.ts
  40. 4
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.html
  41. 44
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.ts
  42. 2
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.html
  43. 2
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss
  44. 14
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts
  45. 114
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts
  46. 6
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts
  47. 31
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html
  48. 16
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.scss
  49. 72
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts
  50. 33
      ui-ngx/src/app/shared/components/image/gallery-image-input.component.ts
  51. 2
      ui-ngx/src/app/shared/components/image/image-gallery.component.ts
  52. 65
      ui-ngx/src/app/shared/components/image/scada-symbol-input.component.html
  53. 149
      ui-ngx/src/app/shared/components/image/scada-symbol-input.component.scss
  54. 198
      ui-ngx/src/app/shared/components/image/scada-symbol-input.component.ts
  55. 32
      ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts
  56. 8
      ui-ngx/src/app/shared/models/resource.models.ts
  57. 7
      ui-ngx/src/app/shared/shared.module.ts
  58. 4
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  59. 2
      ui-ngx/src/styles.scss

1
application/src/main/data/json/system/widget_bundles/cards.json

@ -13,7 +13,6 @@
"cards.aggregated_value_card", "cards.aggregated_value_card",
"simple_value_and_chart_card", "simple_value_and_chart_card",
"progress_bar", "progress_bar",
"iot_svg",
"cards.label_widget", "cards.label_widget",
"cards.dashboard_state_widget", "cards.dashboard_state_widget",
"cards.qr_code", "cards.qr_code",

13
application/src/main/data/json/system/widget_bundles/scada_symbols.json

@ -0,0 +1,13 @@
{
"widgetsBundle": {
"alias": "scada_symbols",
"title": "SCADA symbols",
"image": null,
"description": "Bundle with SCADA symbols",
"order": 9200,
"name": "SCADA symbols"
},
"widgetTypeFqns": [
"scada_symbol"
]
}

26
application/src/main/data/json/system/widget_types/iot_svg.json

@ -1,26 +0,0 @@
{
"fqn": "iot_svg",
"name": "IoT SVG",
"deprecated": false,
"image": null,
"description": "IoT SVG",
"descriptor": {
"type": "rpc",
"sizeX": 3.5,
"sizeY": 3.5,
"resources": [],
"templateHtml": "<tb-iot-svg-widget\n [ctx]='ctx'\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-iot-svg-widget>",
"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",
"settingsSchema": "",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "",
"hasBasicMode": true,
"basicModeDirective": "tb-iot-svg-basic-config",
"defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"IoT SVG\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"actions\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":null,\"lineHeight\":\"1.6\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"titleIcon\":\"mdi:lightbulb-outline\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"configMode\":\"basic\",\"targetDevice\":null,\"titleColor\":null,\"borderRadius\":null}"
},
"tags": [
"svg",
"scada"
]
}

28
application/src/main/data/json/system/widget_types/scada_symbol.json

File diff suppressed because one or more lines are too long

2
application/src/main/java/org/thingsboard/server/controller/ImageController.java

@ -266,7 +266,7 @@ public class ImageController extends BaseController {
@RequestParam int pageSize, @RequestParam int pageSize,
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@Parameter(description = RESOURCE_IMAGE_SUB_TYPE_DESCRIPTION, schema = @Schema(allowableValues = {"IMAGE", "IOT_SVG"})) @Parameter(description = RESOURCE_IMAGE_SUB_TYPE_DESCRIPTION, schema = @Schema(allowableValues = {"IMAGE", "SCADA_SYMBOL"}))
@RequestParam(required = false) String imageSubType, @RequestParam(required = false) String imageSubType,
@Parameter(description = RESOURCE_INCLUDE_SYSTEM_IMAGES_DESCRIPTION) @Parameter(description = RESOURCE_INCLUDE_SYSTEM_IMAGES_DESCRIPTION)
@RequestParam(required = false) boolean includeSystemImages, @RequestParam(required = false) boolean includeSystemImages,

32
application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java

@ -115,11 +115,11 @@ public class ImageControllerTest extends AbstractControllerTest {
} }
@Test @Test
public void testUploadIoTSvgImage() throws Exception { public void testUploadScadaSymbolImage() throws Exception {
String filename = "my_iot_svg_image.svg"; String filename = "my_scada_symbol_image.svg";
TbResourceInfo imageInfo = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.IOT_SVG.name(), filename, "image/svg+xml", SVG_IMAGE); TbResourceInfo imageInfo = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.SCADA_SYMBOL.name(), filename, "image/svg+xml", SVG_IMAGE);
assertThat(imageInfo.getResourceSubType()).isEqualTo(ResourceSubType.IOT_SVG); assertThat(imageInfo.getResourceSubType()).isEqualTo(ResourceSubType.SCADA_SYMBOL);
ImageDescriptor imageDescriptor = imageInfo.getDescriptor(ImageDescriptor.class); ImageDescriptor imageDescriptor = imageInfo.getDescriptor(ImageDescriptor.class);
checkSvgImageDescriptor(imageDescriptor); checkSvgImageDescriptor(imageDescriptor);
@ -224,37 +224,37 @@ public class ImageControllerTest extends AbstractControllerTest {
String systemImageName = "my_system_png_image.png"; String systemImageName = "my_system_png_image.png";
TbResourceInfo systemImage = uploadImage(HttpMethod.POST, "/api/image", systemImageName, "image/png", PNG_IMAGE); TbResourceInfo systemImage = uploadImage(HttpMethod.POST, "/api/image", systemImageName, "image/png", PNG_IMAGE);
String systemIotSvgName = "my_system_iot_svg_image.svg"; String systemScadaSymbolName = "my_system_scada_symbol_image.svg";
TbResourceInfo systemIotSvg = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.IOT_SVG.name(), systemIotSvgName, "image/svg+xml", SVG_IMAGE); TbResourceInfo systemScadaSymbol = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.SCADA_SYMBOL.name(), systemScadaSymbolName, "image/svg+xml", SVG_IMAGE);
loginTenantAdmin(); loginTenantAdmin();
String tenantImageName = "my_jpeg_image.jpg"; String tenantImageName = "my_jpeg_image.jpg";
TbResourceInfo tenantImage = uploadImage(HttpMethod.POST, "/api/image", tenantImageName, "image/jpeg", JPEG_IMAGE); TbResourceInfo tenantImage = uploadImage(HttpMethod.POST, "/api/image", tenantImageName, "image/jpeg", JPEG_IMAGE);
String tenantIotSvgName = "my_iot_svg_image.svg"; String tenantScadaSymbolName = "my_scada_symbol_image.svg";
TbResourceInfo tenantIotSvg = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.IOT_SVG.name(), tenantIotSvgName, "image/svg+xml", SVG_IMAGE); TbResourceInfo tenantScadaSymbol = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.SCADA_SYMBOL.name(), tenantScadaSymbolName, "image/svg+xml", SVG_IMAGE);
List<TbResourceInfo> tenantImages = getImages(null, false, 10); List<TbResourceInfo> tenantImages = getImages(null, false, 10);
assertThat(tenantImages).containsOnly(tenantImage); assertThat(tenantImages).containsOnly(tenantImage);
List<TbResourceInfo> tenantIotSvgs = getImages(null, ResourceSubType.IOT_SVG.name(), false, 10); List<TbResourceInfo> tenantScadaSymbols = getImages(null, ResourceSubType.SCADA_SYMBOL.name(), false, 10);
assertThat(tenantIotSvgs).containsOnly(tenantIotSvg); assertThat(tenantScadaSymbols).containsOnly(tenantScadaSymbol);
List<TbResourceInfo> allImages = getImages(null, true, 10); List<TbResourceInfo> allImages = getImages(null, true, 10);
assertThat(allImages).containsOnly(tenantImage, systemImage); assertThat(allImages).containsOnly(tenantImage, systemImage);
List<TbResourceInfo> allIotSvgs = getImages(null, ResourceSubType.IOT_SVG.name(), true, 10); List<TbResourceInfo> allScadaSymbols = getImages(null, ResourceSubType.SCADA_SYMBOL.name(), true, 10);
assertThat(allIotSvgs).containsOnly(tenantIotSvg, systemIotSvg); assertThat(allScadaSymbols).containsOnly(tenantScadaSymbol, systemScadaSymbol);
assertThat(getImages("png", true, 10)) assertThat(getImages("png", true, 10))
.containsOnly(systemImage); .containsOnly(systemImage);
assertThat(getImages("jpg", true, 10)) assertThat(getImages("jpg", true, 10))
.containsOnly(tenantImage); .containsOnly(tenantImage);
assertThat(getImages("my_system_iot", ResourceSubType.IOT_SVG.name(), true, 10)) assertThat(getImages("my_system_scada_symbol", ResourceSubType.SCADA_SYMBOL.name(), true, 10))
.containsOnly(systemIotSvg); .containsOnly(systemScadaSymbol);
assertThat(getImages("my_iot_svg", ResourceSubType.IOT_SVG.name(),true, 10)) assertThat(getImages("my_scada_symbol", ResourceSubType.SCADA_SYMBOL.name(),true, 10))
.containsOnly(tenantIotSvg); .containsOnly(tenantScadaSymbol);
} }
@Test @Test

2
common/data/src/main/java/org/thingsboard/server/common/data/ResourceSubType.java

@ -17,5 +17,5 @@ package org.thingsboard.server.common.data;
public enum ResourceSubType { public enum ResourceSubType {
IMAGE, IMAGE,
IOT_SVG SCADA_SYMBOL
} }

17
dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java

@ -104,6 +104,7 @@ public class BaseImageService extends BaseResourceService implements ImageServic
WIDGET_TYPE_BASE64_MAPPING.put("settings.markerImages", "Map marker image $index"); WIDGET_TYPE_BASE64_MAPPING.put("settings.markerImages", "Map marker image $index");
WIDGET_TYPE_BASE64_MAPPING.put("settings.background.imageUrl", "$prefix background"); WIDGET_TYPE_BASE64_MAPPING.put("settings.background.imageUrl", "$prefix background");
WIDGET_TYPE_BASE64_MAPPING.put("settings.background.imageBase64", "$prefix background"); WIDGET_TYPE_BASE64_MAPPING.put("settings.background.imageBase64", "$prefix background");
WIDGET_TYPE_BASE64_MAPPING.put("settings.scadaSymbolUrl", "$prefix SCADA symbol");
WIDGET_TYPE_BASE64_MAPPING.put("datasources.*.dataKeys.*.settings.customIcon", "$prefix custom icon"); WIDGET_TYPE_BASE64_MAPPING.put("datasources.*.dataKeys.*.settings.customIcon", "$prefix custom icon");
} }
@ -425,7 +426,7 @@ public class BaseImageService extends BaseResourceService implements ImageServic
return base64ToImageUrl(tenantId, name, data, false); return base64ToImageUrl(tenantId, name, data, false);
} }
private static final Pattern TB_IMAGE_METADATA_PATTERN = Pattern.compile("^tb-image:(.*):(.*);data:(.*);.*"); private static final Pattern TB_IMAGE_METADATA_PATTERN = Pattern.compile("^tb-image:([^:]*):([^:]*):?([^:]*)?;data:(.*);.*");
private UpdateResult base64ToImageUrl(TenantId tenantId, String name, String data, boolean strict) { private UpdateResult base64ToImageUrl(TenantId tenantId, String name, String data, boolean strict) {
if (StringUtils.isBlank(data)) { if (StringUtils.isBlank(data)) {
@ -435,11 +436,15 @@ public class BaseImageService extends BaseResourceService implements ImageServic
boolean matches = matcher.matches(); boolean matches = matcher.matches();
String mdResourceKey = null; String mdResourceKey = null;
String mdResourceName = null; String mdResourceName = null;
String mdResourceSubType = null;
String mdMediaType; String mdMediaType;
if (matches) { if (matches) {
mdResourceKey = new String(Base64.getDecoder().decode(matcher.group(1)), StandardCharsets.UTF_8); mdResourceKey = new String(Base64.getDecoder().decode(matcher.group(1)), StandardCharsets.UTF_8);
mdResourceName = new String(Base64.getDecoder().decode(matcher.group(2)), StandardCharsets.UTF_8); mdResourceName = new String(Base64.getDecoder().decode(matcher.group(2)), StandardCharsets.UTF_8);
mdMediaType = matcher.group(3); if (StringUtils.isNotBlank(matcher.group(3))) {
mdResourceSubType = new String(Base64.getDecoder().decode(matcher.group(3)), StandardCharsets.UTF_8);
};
mdMediaType = matcher.group(4);
} else if (data.startsWith(DataConstants.TB_IMAGE_PREFIX + "data:image/") || (!strict && data.startsWith("data:image/"))) { } else if (data.startsWith(DataConstants.TB_IMAGE_PREFIX + "data:image/") || (!strict && data.startsWith("data:image/"))) {
mdMediaType = StringUtils.substringBetween(data, "data:", ";base64"); mdMediaType = StringUtils.substringBetween(data, "data:", ";base64");
} else { } else {
@ -468,6 +473,11 @@ public class BaseImageService extends BaseResourceService implements ImageServic
} else { } else {
fileName = mdResourceKey; fileName = mdResourceKey;
} }
if (StringUtils.isBlank(mdResourceSubType)) {
image.setResourceSubType(ResourceSubType.IMAGE);
} else {
image.setResourceSubType(ResourceSubType.valueOf(mdResourceSubType));
}
image.setFileName(fileName); image.setFileName(fileName);
image.setDescriptor(JacksonUtil.newObjectNode().put("mediaType", mdMediaType)); image.setDescriptor(JacksonUtil.newObjectNode().put("mediaType", mdMediaType));
image.setData(imageData); image.setData(imageData);
@ -629,7 +639,8 @@ public class BaseImageService extends BaseResourceService implements ImageServic
String tbImagePrefix = ""; String tbImagePrefix = "";
if (addTbImagePrefix) { if (addTbImagePrefix) {
tbImagePrefix = "tb-image:" + Base64.getEncoder().encodeToString(imageInfo.getResourceKey().getBytes(StandardCharsets.UTF_8)) + ":" tbImagePrefix = "tb-image:" + Base64.getEncoder().encodeToString(imageInfo.getResourceKey().getBytes(StandardCharsets.UTF_8)) + ":"
+ Base64.getEncoder().encodeToString(imageInfo.getName().getBytes(StandardCharsets.UTF_8)) + ";"; + Base64.getEncoder().encodeToString(imageInfo.getName().getBytes(StandardCharsets.UTF_8)) + ":"
+ Base64.getEncoder().encodeToString(imageInfo.getResourceSubType().name().getBytes(StandardCharsets.UTF_8)) + ";";
} }
return tbImagePrefix + "data:" + descriptor.getMediaType() + ";base64," + Base64.getEncoder().encodeToString(data); return tbImagePrefix + "data:" + descriptor.getMediaType() + ";base64," + Base64.getEncoder().encodeToString(data);
} }

8
ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts

@ -129,7 +129,7 @@ import {
import { import {
RadarChartBasicConfigComponent RadarChartBasicConfigComponent
} from '@home/components/widget/config/basic/chart/radar-chart-basic-config.component'; } from '@home/components/widget/config/basic/chart/radar-chart-basic-config.component';
import { IotSvgBasicConfigComponent } from '@home/components/widget/config/basic/svg/iot-svg-basic-config.component'; import { ScadaSymbolBasicConfigComponent } from '@home/components/widget/config/basic/scada/scada-symbol-basic-config.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -173,7 +173,7 @@ import { IotSvgBasicConfigComponent } from '@home/components/widget/config/basic
BarChartBasicConfigComponent, BarChartBasicConfigComponent,
PolarAreaChartBasicConfigComponent, PolarAreaChartBasicConfigComponent,
RadarChartBasicConfigComponent, RadarChartBasicConfigComponent,
IotSvgBasicConfigComponent ScadaSymbolBasicConfigComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -219,7 +219,7 @@ import { IotSvgBasicConfigComponent } from '@home/components/widget/config/basic
BarChartBasicConfigComponent, BarChartBasicConfigComponent,
PolarAreaChartBasicConfigComponent, PolarAreaChartBasicConfigComponent,
RadarChartBasicConfigComponent, RadarChartBasicConfigComponent,
IotSvgBasicConfigComponent ScadaSymbolBasicConfigComponent
] ]
}) })
export class BasicWidgetConfigModule { export class BasicWidgetConfigModule {
@ -259,5 +259,5 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-bar-chart-basic-config': BarChartBasicConfigComponent, 'tb-bar-chart-basic-config': BarChartBasicConfigComponent,
'tb-polar-area-chart-basic-config': PolarAreaChartBasicConfigComponent, 'tb-polar-area-chart-basic-config': PolarAreaChartBasicConfigComponent,
'tb-radar-chart-basic-config': RadarChartBasicConfigComponent, 'tb-radar-chart-basic-config': RadarChartBasicConfigComponent,
'tb-iot-svg-basic-config': IotSvgBasicConfigComponent 'tb-scada-symbol-basic-config': ScadaSymbolBasicConfigComponent
}; };

100
ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.html

@ -0,0 +1,100 @@
<!--
Copyright © 2016-2024 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.
-->
<ng-container [formGroup]="scadaSymbolWidgetConfigForm">
<tb-target-device formControlName="targetDevice"></tb-target-device>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>scada.symbol</div>
<tb-scada-symbol-input required
formControlName="scadaSymbolUrl"></tb-scada-symbol-input>
</div>
<tb-scada-symbol-object-settings
formControlName="scadaSymbolObjectSettings"
[scadaSymbolUrl]="scadaSymbolWidgetConfigForm.get('scadaSymbolUrl').value"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
[callbacks]="callbacks">
<div class="tb-scada-symbol-appearance-properties">
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.title' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="titleFont"
clearButton
[previewText]="scadaSymbolWidgetConfigForm.get('title').value"
[initialPreviewStyle]="widgetConfig.config.titleStyle">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="titleColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon">
{{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select fxFlex formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
iconClearButton
[color]="scadaSymbolWidgetConfigForm.get('iconColor').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
colorClearButton
formControlName="iconColor">
</tb-color-input>
</div>
</div>
</div>
</tb-scada-symbol-object-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
<div class="tb-form-row space-between column-lt-md">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-border-radius' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-padding' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="padding" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
</ng-container>

157
ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.ts

@ -0,0 +1,157 @@
///
/// Copyright © 2016-2024 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 { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { TargetDevice, WidgetConfig, } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import {
scadaSymbolWidgetDefaultSettings,
ScadaSymbolWidgetSettings
} from '@home/components/widget/lib/scada/scada-symbol-widget.models';
import { isUndefined } from '@core/utils';
import { cssSizeToStrSize, resolveCssSize } from '@shared/models/widget-settings.models';
@Component({
selector: 'tb-scada-symbol-basic-config',
templateUrl: './scada-symbol-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class ScadaSymbolBasicConfigComponent extends BasicWidgetConfigComponent {
get targetDevice(): TargetDevice {
return this.scadaSymbolWidgetConfigForm.get('targetDevice').value;
}
scadaSymbolWidgetConfigForm: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.scadaSymbolWidgetConfigForm;
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: ScadaSymbolWidgetSettings = {...scadaSymbolWidgetDefaultSettings, ...(configData.config.settings || {})};
const iconSize = resolveCssSize(configData.config.iconSize);
this.scadaSymbolWidgetConfigForm = this.fb.group({
targetDevice: [configData.config.targetDevice, []],
scadaSymbolUrl: [settings.scadaSymbolUrl, []],
scadaSymbolObjectSettings: [settings.scadaSymbolObjectSettings, []],
showTitle: [configData.config.showTitle, []],
title: [configData.config.title, []],
titleFont: [configData.config.titleFont, []],
titleColor: [configData.config.titleColor, []],
showIcon: [configData.config.showTitleIcon, []],
iconSize: [iconSize[0], [Validators.min(0)]],
iconSizeUnit: [iconSize[1], []],
icon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []],
background: [settings.background, []],
cardButtons: [this.getCardButtons(configData.config), []],
borderRadius: [configData.config.borderRadius, []],
padding: [settings.padding, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
this.widgetConfig.config.targetDevice = config.targetDevice;
this.widgetConfig.config.showTitle = config.showTitle;
this.widgetConfig.config.title = config.title;
this.widgetConfig.config.titleFont = config.titleFont;
this.widgetConfig.config.titleColor = config.titleColor;
this.widgetConfig.config.showTitleIcon = config.showIcon;
this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit);
this.widgetConfig.config.titleIcon = config.icon;
this.widgetConfig.config.iconColor = config.iconColor;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.scadaSymbolUrl = config.scadaSymbolUrl;
this.widgetConfig.config.settings.scadaSymbolObjectSettings = config.scadaSymbolObjectSettings;
this.widgetConfig.config.settings.background = config.background;
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.borderRadius = config.borderRadius;
this.widgetConfig.config.settings.padding = config.padding;
this.widgetConfig.config.actions = config.actions;
return this.widgetConfig;
}
protected validatorTriggers(): string[] {
return ['showTitle', 'showIcon'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.scadaSymbolWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.scadaSymbolWidgetConfigForm.get('showIcon').value;
if (showTitle) {
this.scadaSymbolWidgetConfigForm.get('title').enable();
this.scadaSymbolWidgetConfigForm.get('titleFont').enable();
this.scadaSymbolWidgetConfigForm.get('titleColor').enable();
this.scadaSymbolWidgetConfigForm.get('showIcon').enable({emitEvent: false});
if (showIcon) {
this.scadaSymbolWidgetConfigForm.get('iconSize').enable();
this.scadaSymbolWidgetConfigForm.get('iconSizeUnit').enable();
this.scadaSymbolWidgetConfigForm.get('icon').enable();
this.scadaSymbolWidgetConfigForm.get('iconColor').enable();
} else {
this.scadaSymbolWidgetConfigForm.get('iconSize').disable();
this.scadaSymbolWidgetConfigForm.get('iconSizeUnit').disable();
this.scadaSymbolWidgetConfigForm.get('icon').disable();
this.scadaSymbolWidgetConfigForm.get('iconColor').disable();
}
} else {
this.scadaSymbolWidgetConfigForm.get('title').disable();
this.scadaSymbolWidgetConfigForm.get('titleFont').disable();
this.scadaSymbolWidgetConfigForm.get('titleColor').disable();
this.scadaSymbolWidgetConfigForm.get('showIcon').disable({emitEvent: false});
this.scadaSymbolWidgetConfigForm.get('iconSize').disable();
this.scadaSymbolWidgetConfigForm.get('iconSizeUnit').disable();
this.scadaSymbolWidgetConfigForm.get('icon').disable();
this.scadaSymbolWidgetConfigForm.get('iconColor').disable();
}
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.enableFullscreen = buttons.includes('fullscreen');
}
}

48
ui-ngx/src/app/modules/home/components/widget/config/basic/svg/iot-svg-basic-config.component.html

@ -1,48 +0,0 @@
<!--
Copyright © 2016-2024 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.
-->
<ng-container [formGroup]="iotSvgWidgetConfigForm">
<tb-target-device formControlName="targetDevice"></tb-target-device>
<tb-iot-svg-object-settings
formControlName="iotSvgObject"
[svgPath]="iotSvgWidgetConfigForm.get('iotSvg').value"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
[callbacks]="callbacks">
</tb-iot-svg-object-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
<div class="tb-form-row space-between column-lt-md">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-border-radius' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
</ng-container>

95
ui-ngx/src/app/modules/home/components/widget/config/basic/svg/iot-svg-basic-config.component.ts

@ -1,95 +0,0 @@
///
/// Copyright © 2016-2024 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 { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { TargetDevice, WidgetConfig, } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import {
iotSvgWidgetDefaultSettings,
IotSvgWidgetSettings
} from '@home/components/widget/lib/svg/iot-svg-widget.models';
import { isUndefined } from '@core/utils';
@Component({
selector: 'tb-iot-svg-basic-config',
templateUrl: './iot-svg-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class IotSvgBasicConfigComponent extends BasicWidgetConfigComponent {
get targetDevice(): TargetDevice {
return this.iotSvgWidgetConfigForm.get('targetDevice').value;
}
iotSvgWidgetConfigForm: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.iotSvgWidgetConfigForm;
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: IotSvgWidgetSettings = {...iotSvgWidgetDefaultSettings, ...(configData.config.settings || {})};
this.iotSvgWidgetConfigForm = this.fb.group({
targetDevice: [configData.config.targetDevice, []],
iotSvg: [settings.iotSvg, []],
iotSvgObject: [settings.iotSvgObject, []],
background: [settings.background, []],
cardButtons: [this.getCardButtons(configData.config), []],
borderRadius: [configData.config.borderRadius, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
this.widgetConfig.config.targetDevice = config.targetDevice;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.iotSvg = config.iotSvg;
this.widgetConfig.config.settings.iotSvgObject = config.iotSvgObject;
this.widgetConfig.config.settings.background = config.background;
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.borderRadius = config.borderRadius;
this.widgetConfig.config.actions = config.actions;
return this.widgetConfig;
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.enableFullscreen = buttons.includes('fullscreen');
}
}

4
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html

@ -18,7 +18,9 @@
<div class="tb-time-series-chart-panel" [style.padding]="padding" <div class="tb-time-series-chart-panel" [style.padding]="padding"
[class.overlay]="overlayEnabled" [style]="backgroundStyle$ | async"> [class.overlay]="overlayEnabled" [style]="backgroundStyle$ | async">
<div class="tb-time-series-chart-overlay" [style]="overlayStyle"></div> <div class="tb-time-series-chart-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> <ng-container *ngIf="widgetComponent.dashboardWidget.showWidgetTitlePanel">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</ng-container>
<div class="tb-time-series-chart-content" [class]="legendClass"> <div class="tb-time-series-chart-content" [class]="legendClass">
<div #chartShape class="tb-time-series-chart-shape"> <div #chartShape class="tb-time-series-chart-shape">
</div> </div>

4
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts

@ -43,6 +43,7 @@ import {
TimeSeriesChartWidgetSettings TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models'; } from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import { mergeDeep } from '@core/utils'; import { mergeDeep } from '@core/utils';
import { WidgetComponent } from '@home/components/widget/widget.component';
@Component({ @Component({
selector: 'tb-time-series-chart-widget', selector: 'tb-time-series-chart-widget',
@ -85,7 +86,8 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV
private timeSeriesChart: TbTimeSeriesChart; private timeSeriesChart: TbTimeSeriesChart;
constructor(private imagePipe: ImagePipe, constructor(public widgetComponent: WidgetComponent,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private renderer: Renderer2, private renderer: Renderer2,
private cd: ChangeDetectorRef) { private cd: ChangeDetectorRef) {

11
ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.html → ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.html

@ -15,14 +15,15 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-iot-svg-panel" [style]="backgroundStyle$ | async"> <div class="tb-scada-symbol-panel" [style.padding]="padding"
<div class="tb-iot-svg-overlay" [style]="overlayStyle"></div> [class.overlay]="overlayEnabled" [style]="backgroundStyle$ | async">
<div class="tb-scada-symbol-overlay" [style]="overlayStyle"></div>
<ng-container *ngIf="widgetComponent.dashboardWidget.showWidgetTitlePanel"> <ng-container *ngIf="widgetComponent.dashboardWidget.showWidgetTitlePanel">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> <ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</ng-container> </ng-container>
<div class="tb-iot-svg-content"> <div class="tb-scada-symbol-content">
<div #iotSvgShape class="tb-iot-svg-shape"> <div #scadaSymbolShape class="tb-scada-symbol-shape">
</div> </div>
</div> </div>
<mat-progress-bar class="tb-action-widget-progress" style="height: 4px;" color="accent" mode="indeterminate" *ngIf="iotSvgObject?.loading$ | async"></mat-progress-bar> <mat-progress-bar class="tb-action-widget-progress" style="height: 4px;" color="accent" mode="indeterminate" *ngIf="loading$ | async"></mat-progress-bar>
</div> </div>

16
ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.scss

@ -14,18 +14,20 @@
* limitations under the License. * limitations under the License.
*/ */
.tb-iot-svg-panel { .tb-scada-symbol-panel {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 8px;
padding: 20px 24px 24px 24px; &.overlay {
> div:not(.tb-iot-svg-overlay) { padding: 20px 24px 24px 24px;
}
> div:not(.tb-scada-symbol-overlay) {
z-index: 1; z-index: 1;
} }
.tb-iot-svg-overlay { .tb-scada-symbol-overlay {
position: absolute; position: absolute;
top: 12px; top: 12px;
left: 12px; left: 12px;
@ -35,11 +37,11 @@
div.tb-widget-title { div.tb-widget-title {
padding: 0; padding: 0;
} }
.tb-iot-svg-content { .tb-scada-symbol-content {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
min-height: 0; min-height: 0;
.tb-iot-svg-shape { .tb-scada-symbol-shape {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;

80
ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.ts

@ -22,36 +22,35 @@ import {
Input, Input,
OnDestroy, OnDestroy,
OnInit, OnInit,
Renderer2,
TemplateRef, TemplateRef,
ViewChild, ViewChild,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { ImagePipe } from '@shared/pipe/image.pipe'; import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { HttpClient } from '@angular/common/http'; import { ScadaSymbolObject, ScadaSymbolObjectCallbacks } from '@home/components/widget/lib/scada/scada-symbol.models';
import { IotSvgObject, IotSvgObjectCallbacks } from '@home/components/widget/lib/svg/iot-svg.models';
import { import {
iotSvgWidgetDefaultSettings, scadaSymbolWidgetDefaultSettings,
IotSvgWidgetSettings ScadaSymbolWidgetSettings
} from '@home/components/widget/lib/svg/iot-svg-widget.models'; } from '@home/components/widget/lib/scada/scada-symbol-widget.models';
import { Observable, of } from 'rxjs'; import { BehaviorSubject, Observable, of } from 'rxjs';
import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models'; import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models';
import { ImageService } from '@core/http/image.service'; import { ImageService } from '@core/http/image.service';
import { WidgetComponent } from '@home/components/widget/widget.component'; import { WidgetComponent } from '@home/components/widget/widget.component';
import { isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { isDefinedAndNotNull, mergeDeep } from '@core/utils';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { catchError, share } from 'rxjs/operators';
@Component({ @Component({
selector: 'tb-iot-svg-widget', selector: 'tb-scada-symbol-widget',
templateUrl: './iot-svg-widget.component.html', templateUrl: './scada-symbol-widget.component.html',
styleUrls: ['../action/action-widget.scss', './iot-svg-widget.component.scss'], styleUrls: ['../action/action-widget.scss', './scada-symbol-widget.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class IotSvgWidgetComponent implements OnInit, AfterViewInit, OnDestroy, IotSvgObjectCallbacks { export class ScadaSymbolWidgetComponent implements OnInit, AfterViewInit, OnDestroy, ScadaSymbolObjectCallbacks {
@ViewChild('iotSvgShape', {static: false}) @ViewChild('scadaSymbolShape', {static: false})
iotSvgShape: ElementRef<HTMLElement>; scadaSymbolShape: ElementRef<HTMLElement>;
@Input() @Input()
ctx: WidgetContext; ctx: WidgetContext;
@ -59,47 +58,57 @@ export class IotSvgWidgetComponent implements OnInit, AfterViewInit, OnDestroy,
@Input() @Input()
widgetTitlePanel: TemplateRef<any>; widgetTitlePanel: TemplateRef<any>;
private settings: IotSvgWidgetSettings; private loadingSubject = new BehaviorSubject(false);
private svgContent$: Observable<string>; private settings: ScadaSymbolWidgetSettings;
private scadaSymbolContent$: Observable<string>;
backgroundStyle$: Observable<ComponentStyle>; backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {}; overlayStyle: ComponentStyle = {};
iotSvgObject: IotSvgObject; overlayEnabled: boolean;
padding: string;
loading$ = this.loadingSubject.asObservable().pipe(share());
scadaSymbolObject: ScadaSymbolObject;
constructor(public widgetComponent: WidgetComponent, constructor(public widgetComponent: WidgetComponent,
protected imagePipe: ImagePipe, protected imagePipe: ImagePipe,
protected sanitizer: DomSanitizer, protected sanitizer: DomSanitizer,
private imageService: ImageService, private imageService: ImageService,
protected cd: ChangeDetectorRef, protected cd: ChangeDetectorRef) {
private http: HttpClient) {
} }
ngOnInit(): void { ngOnInit(): void {
this.ctx.$scope.actionWidget = this; this.ctx.$scope.actionWidget = this;
this.settings = mergeDeep({} as IotSvgWidgetSettings, iotSvgWidgetDefaultSettings, this.ctx.settings || {}); this.settings = mergeDeep({} as ScadaSymbolWidgetSettings, scadaSymbolWidgetDefaultSettings, this.ctx.settings || {});
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
this.overlayStyle = overlayStyle(this.settings.background.overlay); this.overlayStyle = overlayStyle(this.settings.background.overlay);
this.overlayEnabled = this.settings.background.overlay.enabled;
if (this.settings.iotSvgContent) { this.padding = this.overlayEnabled ? undefined : this.settings.padding;
this.svgContent$ = of(this.settings.iotSvgContent);
} else if (this.settings.iotSvgUrl) { if (this.settings.scadaSymbolContent) {
this.svgContent$ = this.imageService.getImageString(this.settings.iotSvgUrl); this.scadaSymbolContent$ = of(this.settings.scadaSymbolContent);
} else if (this.settings.scadaSymbolUrl) {
this.scadaSymbolContent$ = this.imageService.getImageString(this.settings.scadaSymbolUrl)
.pipe(catchError(() => of('<svg></svg>')));
} else { } else {
this.svgContent$ = this.http.get(this.settings.iotSvg, {responseType: 'text'}); this.scadaSymbolContent$ = of('<svg></svg>');
} }
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
this.svgContent$.subscribe((content) => { this.scadaSymbolContent$.subscribe((content) => {
this.initObject(this.iotSvgShape.nativeElement, content); this.initObject(this.scadaSymbolShape.nativeElement, content);
}); });
} }
ngOnDestroy() { ngOnDestroy() {
if (this.iotSvgObject) { if (this.scadaSymbolObject) {
this.iotSvgObject.destroy(); this.scadaSymbolObject.destroy();
} }
this.loadingSubject.complete();
this.loadingSubject.unsubscribe();
} }
public onInit() { public onInit() {
@ -108,19 +117,24 @@ export class IotSvgWidgetComponent implements OnInit, AfterViewInit, OnDestroy,
this.cd.detectChanges(); this.cd.detectChanges();
} }
onSvgObjectError(error: string) { onScadaSymbolObjectLoadingState(loading: boolean) {
this.loadingSubject.next(loading);
}
onScadaSymbolObjectError(error: string) {
this.ctx.showErrorToast(error, 'bottom', 'center', this.ctx.toastTargetId, true); this.ctx.showErrorToast(error, 'bottom', 'center', this.ctx.toastTargetId, true);
} }
onSvgObjectMessage(message: string) { onScadaSymbolObjectMessage(message: string) {
this.ctx.showSuccessToast(message, 3000, 'bottom', 'center', this.ctx.toastTargetId, true); this.ctx.showSuccessToast(message, 3000, 'bottom', 'center', this.ctx.toastTargetId, true);
} }
private initObject(rootElement: HTMLElement, private initObject(rootElement: HTMLElement,
svgContent: string) { content: string) {
const simulated = this.ctx.utilsService.widgetEditMode || const simulated = this.ctx.utilsService.widgetEditMode ||
this.ctx.isPreview || (isDefinedAndNotNull(this.settings.simulated) ? this.settings.simulated : false); this.ctx.isPreview || (isDefinedAndNotNull(this.settings.simulated) ? this.settings.simulated : false);
this.iotSvgObject = new IotSvgObject(rootElement, this.ctx, svgContent, this.settings.iotSvgObject, this, simulated); this.scadaSymbolObject = new ScadaSymbolObject(rootElement, this.ctx, content,
this.settings.scadaSymbolObjectSettings, this, simulated);
} }
} }

20
ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts → ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.models.ts

@ -14,21 +14,20 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { IotSvgObjectSettings } from '@home/components/widget/lib/svg/iot-svg.models'; import { ScadaSymbolObjectSettings } from '@home/components/widget/lib/scada/scada-symbol.models';
import { BackgroundSettings, BackgroundType } from '@shared/models/widget-settings.models'; import { BackgroundSettings, BackgroundType } from '@shared/models/widget-settings.models';
export interface IotSvgWidgetSettings { export interface ScadaSymbolWidgetSettings {
iotSvg?: string; scadaSymbolUrl?: string;
iotSvgUrl?: string; scadaSymbolContent?: string;
iotSvgContent?: string;
simulated?: boolean; simulated?: boolean;
iotSvgObject: IotSvgObjectSettings; scadaSymbolObjectSettings: ScadaSymbolObjectSettings;
background: BackgroundSettings; background: BackgroundSettings;
padding: string;
} }
export const iotSvgWidgetDefaultSettings: IotSvgWidgetSettings = { export const scadaSymbolWidgetDefaultSettings: ScadaSymbolWidgetSettings = {
iotSvg: '/assets/widget/svg/drawing.svg', scadaSymbolObjectSettings: {
iotSvgObject: {
behavior: {}, behavior: {},
properties: {} properties: {}
}, },
@ -40,5 +39,6 @@ export const iotSvgWidgetDefaultSettings: IotSvgWidgetSettings = {
color: 'rgba(255,255,255,0.72)', color: 'rgba(255,255,255,0.72)',
blur: 3 blur: 3
} }
} },
padding: '12px'
}; };

230
ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts → ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts

@ -33,8 +33,7 @@ import {
mergeDeep, mergeDeep,
parseFunction parseFunction
} from '@core/utils'; } from '@core/utils';
import { BehaviorSubject, forkJoin, Observable, Observer } from 'rxjs'; import { BehaviorSubject, forkJoin, Observable, Observer, Subject } from 'rxjs';
import { share } from 'rxjs/operators';
import { ValueAction, ValueGetter, ValueSetter } from '@home/components/widget/lib/action/action-widget.models'; import { ValueAction, ValueGetter, ValueSetter } from '@home/components/widget/lib/action/action-widget.models';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { ColorProcessor, constantColor, Font } from '@shared/models/widget-settings.models'; import { ColorProcessor, constantColor, Font } from '@shared/models/widget-settings.models';
@ -42,8 +41,9 @@ import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { WidgetAction, WidgetActionType, widgetActionTypeTranslationMap } from '@shared/models/widget.models'; import { WidgetAction, WidgetActionType, widgetActionTypeTranslationMap } from '@shared/models/widget.models';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { takeUntil } from 'rxjs/operators';
export interface IotSvgApi { export interface ScadaSymbolApi {
formatValue: (value: any, dec?: number, units?: string, showZeroDecimals?: boolean) => string | undefined; formatValue: (value: any, dec?: number, units?: string, showZeroDecimals?: boolean) => string | undefined;
text: (element: Element | Element[], text: string) => void; text: (element: Element | Element[], text: string) => void;
font: (element: Element | Element[], font: Font, color: string) => void; font: (element: Element | Element[], font: Font, color: string) => void;
@ -54,57 +54,57 @@ export interface IotSvgApi {
setValue: (valueId: string, value: any) => void; setValue: (valueId: string, value: any) => void;
} }
export interface IotSvgContext { export interface ScadaSymbolContext {
api: IotSvgApi; api: ScadaSymbolApi;
tags: {[id: string]: Element[]}; tags: {[id: string]: Element[]};
values: {[id: string]: any}; values: {[id: string]: any};
properties: {[id: string]: any}; properties: {[id: string]: any};
} }
export type IotSvgStateRenderFunction = (ctx: IotSvgContext, svg: Svg) => void; export type ScadaSymbolStateRenderFunction = (ctx: ScadaSymbolContext, svg: Svg) => void;
export type IotSvgTagStateRenderFunction = (ctx: IotSvgContext, element: Element) => void; export type ScadaSymbolTagStateRenderFunction = (ctx: ScadaSymbolContext, element: Element) => void;
export type IotSvgActionTrigger = 'click'; export type ScadaSymbolActionTrigger = 'click';
export type IotSvgActionFunction = (ctx: IotSvgContext, element: Element, event: Event) => void; export type ScadaSymbolActionFunction = (ctx: ScadaSymbolContext, element: Element, event: Event) => void;
export interface IotSvgAction { export interface ScadaSymbolAction {
actionFunction?: string; actionFunction?: string;
action?: IotSvgActionFunction; action?: ScadaSymbolActionFunction;
} }
export interface IotSvgTag { export interface ScadaSymbolTag {
tag: string; tag: string;
stateRenderFunction?: string; stateRenderFunction?: string;
stateRender?: IotSvgTagStateRenderFunction; stateRender?: ScadaSymbolTagStateRenderFunction;
actions?: {[trigger: string]: IotSvgAction}; actions?: {[trigger: string]: ScadaSymbolAction};
} }
export enum IotSvgBehaviorType { export enum ScadaSymbolBehaviorType {
value = 'value', value = 'value',
action = 'action', action = 'action',
widgetAction = 'widgetAction' widgetAction = 'widgetAction'
} }
export const iotSvgBehaviorTypes = Object.keys(IotSvgBehaviorType) as IotSvgBehaviorType[]; export const scadaSymbolBehaviorTypes = Object.keys(ScadaSymbolBehaviorType) as ScadaSymbolBehaviorType[];
export const iotSvgBehaviorTypeTranslations = new Map<IotSvgBehaviorType, string>( export const scadaSymbolBehaviorTypeTranslations = new Map<ScadaSymbolBehaviorType, string>(
[ [
[IotSvgBehaviorType.value, 'scada.behavior.type-value'], [ScadaSymbolBehaviorType.value, 'scada.behavior.type-value'],
[IotSvgBehaviorType.action, 'scada.behavior.type-action'], [ScadaSymbolBehaviorType.action, 'scada.behavior.type-action'],
[IotSvgBehaviorType.widgetAction, 'scada.behavior.type-widget-action'] [ScadaSymbolBehaviorType.widgetAction, 'scada.behavior.type-widget-action']
] ]
); );
export interface IotSvgBehaviorBase { export interface ScadaSymbolBehaviorBase {
id: string; id: string;
name: string; name: string;
hint?: string; hint?: string;
type: IotSvgBehaviorType; type: ScadaSymbolBehaviorType;
} }
export interface IotSvgBehaviorValue extends IotSvgBehaviorBase { export interface ScadaSymbolBehaviorValue extends ScadaSymbolBehaviorBase {
valueType: ValueType; valueType: ValueType;
defaultValue: any; defaultValue: any;
trueLabel?: string; trueLabel?: string;
@ -112,16 +112,16 @@ export interface IotSvgBehaviorValue extends IotSvgBehaviorBase {
stateLabel?: string; stateLabel?: string;
} }
export interface IotSvgBehaviorAction extends IotSvgBehaviorBase { export interface ScadaSymbolBehaviorAction extends ScadaSymbolBehaviorBase {
valueType: ValueType; valueType: ValueType;
valueToDataType: ValueToDataType; valueToDataType: ValueToDataType;
constantValue: any; constantValue: any;
valueToDataFunction: string; valueToDataFunction: string;
} }
export type IotSvgBehavior = IotSvgBehaviorValue & IotSvgBehaviorAction; export type ScadaSymbolBehavior = ScadaSymbolBehaviorValue & ScadaSymbolBehaviorAction;
export enum IotSvgPropertyType { export enum ScadaSymbolPropertyType {
text = 'text', text = 'text',
number = 'number', number = 'number',
switch = 'switch', switch = 'switch',
@ -131,30 +131,30 @@ export enum IotSvgPropertyType {
units = 'units' units = 'units'
} }
export const iotSvgPropertyTypes = Object.keys(IotSvgPropertyType) as IotSvgPropertyType[]; export const scadaSymbolPropertyTypes = Object.keys(ScadaSymbolPropertyType) as ScadaSymbolPropertyType[];
export const iotSvgPropertyTypeTranslations = new Map<IotSvgPropertyType, string>( export const scadaSymbolPropertyTypeTranslations = new Map<ScadaSymbolPropertyType, string>(
[ [
[IotSvgPropertyType.text, 'scada.property.type-text'], [ScadaSymbolPropertyType.text, 'scada.property.type-text'],
[IotSvgPropertyType.number, 'scada.property.type-number'], [ScadaSymbolPropertyType.number, 'scada.property.type-number'],
[IotSvgPropertyType.switch, 'scada.property.type-switch'], [ScadaSymbolPropertyType.switch, 'scada.property.type-switch'],
[IotSvgPropertyType.color, 'scada.property.type-color'], [ScadaSymbolPropertyType.color, 'scada.property.type-color'],
[IotSvgPropertyType.color_settings, 'scada.property.type-color-settings'], [ScadaSymbolPropertyType.color_settings, 'scada.property.type-color-settings'],
[IotSvgPropertyType.font, 'scada.property.type-font'], [ScadaSymbolPropertyType.font, 'scada.property.type-font'],
[IotSvgPropertyType.units, 'scada.property.type-units'] [ScadaSymbolPropertyType.units, 'scada.property.type-units']
] ]
); );
export const iotSvgPropertyRowClasses = export const scadaSymbolPropertyRowClasses =
['column', 'column-xs', 'column-lt-md', 'align-start', 'no-border', 'no-gap', 'no-padding', 'same-padding']; ['column', 'column-xs', 'column-lt-md', 'align-start', 'no-border', 'no-gap', 'no-padding', 'same-padding'];
export const iotSvgPropertyFieldClasses = export const scadaSymbolPropertyFieldClasses =
['medium-width', 'flex', 'flex-xs', 'flex-lt-md']; ['medium-width', 'flex', 'flex-xs', 'flex-lt-md'];
export interface IotSvgPropertyBase { export interface ScadaSymbolPropertyBase {
id: string; id: string;
name: string; name: string;
type: IotSvgPropertyType; type: ScadaSymbolPropertyType;
default: any; default: any;
required?: boolean; required?: boolean;
subLabel?: string; subLabel?: string;
@ -165,24 +165,24 @@ export interface IotSvgPropertyBase {
fieldClass?: string; fieldClass?: string;
} }
export interface IotSvgNumberProperty extends IotSvgPropertyBase { export interface ScadaSymbolNumberProperty extends ScadaSymbolPropertyBase {
min?: number; min?: number;
max?: number; max?: number;
step?: number; step?: number;
} }
export type IotSvgProperty = IotSvgPropertyBase & IotSvgNumberProperty; export type ScadaSymbolProperty = ScadaSymbolPropertyBase & ScadaSymbolNumberProperty;
export interface IotSvgMetadata { export interface ScadaSymbolMetadata {
title: string; title: string;
stateRenderFunction?: string; stateRenderFunction?: string;
stateRender?: IotSvgStateRenderFunction; stateRender?: ScadaSymbolStateRenderFunction;
tags: IotSvgTag[]; tags: ScadaSymbolTag[];
behavior: IotSvgBehavior[]; behavior: ScadaSymbolBehavior[];
properties: IotSvgProperty[]; properties: ScadaSymbolProperty[];
} }
export const emptyMetadata = (): IotSvgMetadata => ({ export const emptyMetadata = (): ScadaSymbolMetadata => ({
title: '', title: '',
tags: [], tags: [],
behavior: [], behavior: [],
@ -190,16 +190,16 @@ export const emptyMetadata = (): IotSvgMetadata => ({
}); });
export const parseIotSvgMetadataFromContent = (svgContent: string): IotSvgMetadata => { export const parseScadaSymbolMetadataFromContent = (svgContent: string): ScadaSymbolMetadata => {
try { try {
const svgDoc = new DOMParser().parseFromString(svgContent, 'image/svg+xml'); const svgDoc = new DOMParser().parseFromString(svgContent, 'image/svg+xml');
return parseIotSvgMetadataFromDom(svgDoc); return parseScadaSymbolMetadataFromDom(svgDoc);
} catch (_e) { } catch (_e) {
return emptyMetadata(); return emptyMetadata();
} }
}; };
const parseIotSvgMetadataFromDom = (svgDoc: Document): IotSvgMetadata => { const parseScadaSymbolMetadataFromDom = (svgDoc: Document): ScadaSymbolMetadata => {
try { try {
const elements = svgDoc.getElementsByTagName('tb:metadata'); const elements = svgDoc.getElementsByTagName('tb:metadata');
if (elements.length) { if (elements.length) {
@ -213,13 +213,13 @@ const parseIotSvgMetadataFromDom = (svgDoc: Document): IotSvgMetadata => {
} }
}; };
export const updateIotSvgMetadataInContent = (svgContent: string, metadata: IotSvgMetadata): string => { export const updateScadaSymbolMetadataInContent = (svgContent: string, metadata: ScadaSymbolMetadata): string => {
const svgDoc = new DOMParser().parseFromString(svgContent, 'image/svg+xml'); const svgDoc = new DOMParser().parseFromString(svgContent, 'image/svg+xml');
updateIotSvgMetadataInDom(svgDoc, metadata); updateScadaSymbolMetadataInDom(svgDoc, metadata);
return svgDoc.documentElement.outerHTML; return svgDoc.documentElement.outerHTML;
}; };
const updateIotSvgMetadataInDom = (svgDoc: Document, metadata: IotSvgMetadata) => { const updateScadaSymbolMetadataInDom = (svgDoc: Document, metadata: ScadaSymbolMetadata) => {
svgDoc.documentElement.setAttribute('xmlns:tb', 'https://thingsboard.io/svg'); svgDoc.documentElement.setAttribute('xmlns:tb', 'https://thingsboard.io/svg');
let metadataElement: Node; let metadataElement: Node;
const elements = svgDoc.getElementsByTagName('tb:metadata'); const elements = svgDoc.getElementsByTagName('tb:metadata');
@ -238,13 +238,13 @@ const updateIotSvgMetadataInDom = (svgDoc: Document, metadata: IotSvgMetadata) =
const svgPartsRegex = /(<svg .*?>)(.*)<\/svg>/gms; const svgPartsRegex = /(<svg .*?>)(.*)<\/svg>/gms;
const tbMetadataRegex = /<tb:metadata>.*<\/tb:metadata>/gs; const tbMetadataRegex = /<tb:metadata>.*<\/tb:metadata>/gs;
export interface IoTSvgContentData { export interface ScadaSymbolContentData {
svgRootNode: string; svgRootNode: string;
innerSvg: string; innerSvg: string;
} }
export const iotSvgContentData = (svgContent: string): IoTSvgContentData => { export const scadaSymbolContentData = (svgContent: string): ScadaSymbolContentData => {
const result: IoTSvgContentData = { const result: ScadaSymbolContentData = {
svgRootNode: '', svgRootNode: '',
innerSvg: '' innerSvg: ''
}; };
@ -268,7 +268,7 @@ export const iotSvgContentData = (svgContent: string): IoTSvgContentData => {
return result; return result;
}; };
const defaultGetValueSettings = (get: IotSvgBehaviorValue): GetValueSettings<any> => ({ const defaultGetValueSettings = (get: ScadaSymbolBehaviorValue): GetValueSettings<any> => ({
action: GetValueAction.DO_NOTHING, action: GetValueAction.DO_NOTHING,
defaultValue: get.defaultValue, defaultValue: get.defaultValue,
executeRpc: { executeRpc: {
@ -291,7 +291,7 @@ const defaultGetValueSettings = (get: IotSvgBehaviorValue): GetValueSettings<any
} }
}); });
const defaultSetValueSettings = (set: IotSvgBehaviorAction): SetValueSettings => ({ const defaultSetValueSettings = (set: ScadaSymbolBehaviorAction): SetValueSettings => ({
action: SetValueAction.EXECUTE_RPC, action: SetValueAction.EXECUTE_RPC,
executeRpc: { executeRpc: {
method: 'setState', method: 'setState',
@ -314,7 +314,7 @@ const defaultSetValueSettings = (set: IotSvgBehaviorAction): SetValueSettings =>
} }
}); });
const defaultWidgetActionSettings = (widgetAction: IotSvgBehavior): WidgetAction => ({ const defaultWidgetActionSettings = (widgetAction: ScadaSymbolBehavior): WidgetAction => ({
type: WidgetActionType.updateDashboardState, type: WidgetActionType.updateDashboardState,
targetDashboardStateId: null, targetDashboardStateId: null,
openRightLayout: false, openRightLayout: false,
@ -322,17 +322,17 @@ const defaultWidgetActionSettings = (widgetAction: IotSvgBehavior): WidgetAction
stateEntityParamName: null stateEntityParamName: null
}); });
export const defaultIotSvgObjectSettings = (metadata: IotSvgMetadata): IotSvgObjectSettings => { export const defaultScadaSymbolObjectSettings = (metadata: ScadaSymbolMetadata): ScadaSymbolObjectSettings => {
const settings: IotSvgObjectSettings = { const settings: ScadaSymbolObjectSettings = {
behavior: {}, behavior: {},
properties: {} properties: {}
}; };
for (const behavior of metadata.behavior) { for (const behavior of metadata.behavior) {
if (behavior.type === IotSvgBehaviorType.value) { if (behavior.type === ScadaSymbolBehaviorType.value) {
settings.behavior[behavior.id] = defaultGetValueSettings(behavior as IotSvgBehaviorValue); settings.behavior[behavior.id] = defaultGetValueSettings(behavior as ScadaSymbolBehaviorValue);
} else if (behavior.type === IotSvgBehaviorType.action) { } else if (behavior.type === ScadaSymbolBehaviorType.action) {
settings.behavior[behavior.id] = defaultSetValueSettings(behavior as IotSvgBehaviorAction); settings.behavior[behavior.id] = defaultSetValueSettings(behavior as ScadaSymbolBehaviorAction);
} else if (behavior.type === IotSvgBehaviorType.widgetAction) { } else if (behavior.type === ScadaSymbolBehaviorType.widgetAction) {
settings.behavior[behavior.id] = defaultWidgetActionSettings(behavior); settings.behavior[behavior.id] = defaultWidgetActionSettings(behavior);
} }
} }
@ -342,7 +342,7 @@ export const defaultIotSvgObjectSettings = (metadata: IotSvgMetadata): IotSvgObj
return settings; return settings;
}; };
export type IotSvgObjectSettings = { export type ScadaSymbolObjectSettings = {
behavior: {[id: string]: any}; behavior: {[id: string]: any};
properties: {[id: string]: any}; properties: {[id: string]: any};
}; };
@ -350,21 +350,21 @@ export type IotSvgObjectSettings = {
const parseError = (ctx: WidgetContext, err: any): string => const parseError = (ctx: WidgetContext, err: any): string =>
ctx.$injector.get(UtilsService).parseException(err).message || 'Unknown Error'; ctx.$injector.get(UtilsService).parseException(err).message || 'Unknown Error';
export interface IotSvgObjectCallbacks { export interface ScadaSymbolObjectCallbacks {
onSvgObjectError: (error: string) => void; onScadaSymbolObjectLoadingState: (loading: boolean) => void;
onSvgObjectMessage: (message: string) => void; onScadaSymbolObjectError: (error: string) => void;
onScadaSymbolObjectMessage: (message: string) => void;
} }
export class IotSvgObject { export class ScadaSymbolObject {
private metadata: IotSvgMetadata; private metadata: ScadaSymbolMetadata;
private settings: IotSvgObjectSettings; private settings: ScadaSymbolObjectSettings;
private context: IotSvgContext; private context: ScadaSymbolContext;
private svgShape: Svg; private svgShape: Svg;
private box: Box; private box: Box;
private loadingSubject = new BehaviorSubject(false);
private valueGetters: ValueGetter<any>[] = []; private valueGetters: ValueGetter<any>[] = [];
private valueActions: ValueAction[] = []; private valueActions: ValueAction[] = [];
private valueSetters: {[behaviorId: string]: ValueSetter<any>} = {}; private valueSetters: {[behaviorId: string]: ValueSetter<any>} = {};
@ -372,32 +372,34 @@ export class IotSvgObject {
private stateValueSubjects: {[id: string]: BehaviorSubject<any>} = {}; private stateValueSubjects: {[id: string]: BehaviorSubject<any>} = {};
private readonly shapeResize$: ResizeObserver; private readonly shapeResize$: ResizeObserver;
private readonly destroy$ = new Subject<void>();
private scale = 1; private scale = 1;
private performInit = true; private performInit = true;
loading$ = this.loadingSubject.asObservable().pipe(share());
constructor(private rootElement: HTMLElement, constructor(private rootElement: HTMLElement,
private ctx: WidgetContext, private ctx: WidgetContext,
private svgContent: string, private svgContent: string,
private inputSettings: IotSvgObjectSettings, private inputSettings: ScadaSymbolObjectSettings,
private callbacks: IotSvgObjectCallbacks, private callbacks: ScadaSymbolObjectCallbacks,
private simulated: boolean) { private simulated: boolean) {
this.shapeResize$ = new ResizeObserver(() => { this.shapeResize$ = new ResizeObserver(() => {
this.resize(); this.resize();
}); });
const doc: XMLDocument = new DOMParser().parseFromString(this.svgContent, 'image/svg+xml'); const doc: XMLDocument = new DOMParser().parseFromString(this.svgContent, 'image/svg+xml');
this.metadata = parseIotSvgMetadataFromDom(doc); this.metadata = parseScadaSymbolMetadataFromDom(doc);
const defaults = defaultIotSvgObjectSettings(this.metadata); const defaults = defaultScadaSymbolObjectSettings(this.metadata);
this.settings = mergeDeep<IotSvgObjectSettings>({} as IotSvgObjectSettings, this.settings = mergeDeep<ScadaSymbolObjectSettings>({} as ScadaSymbolObjectSettings,
defaults, this.inputSettings || {} as IotSvgObjectSettings); defaults, this.inputSettings || {} as ScadaSymbolObjectSettings);
this.prepareMetadata(); this.prepareMetadata();
this.prepareSvgShape(doc); this.prepareSvgShape(doc);
this.shapeResize$.observe(this.rootElement); this.shapeResize$.observe(this.rootElement);
} }
public destroy() { public destroy() {
this.destroy$.next();
this.destroy$.complete();
if (this.shapeResize$) { if (this.shapeResize$) {
this.shapeResize$.disconnect(); this.shapeResize$.disconnect();
} }
@ -406,8 +408,6 @@ export class IotSvgObject {
this.stateValueSubjects[stateValueId].unsubscribe(); this.stateValueSubjects[stateValueId].unsubscribe();
} }
this.valueActions.forEach(v => v.destroy()); this.valueActions.forEach(v => v.destroy());
this.loadingSubject.complete();
this.loadingSubject.unsubscribe();
for (const tag of this.metadata.tags) { for (const tag of this.metadata.tags) {
const elements = this.context.tags[tag.tag]; const elements = this.context.tags[tag.tag];
elements.forEach(element => { elements.forEach(element => {
@ -490,15 +490,15 @@ export class IotSvgObject {
} }
} }
for (const behavior of this.metadata.behavior) { for (const behavior of this.metadata.behavior) {
if (behavior.type === IotSvgBehaviorType.value) { if (behavior.type === ScadaSymbolBehaviorType.value) {
const getBehavior = behavior as IotSvgBehaviorValue; const getBehavior = behavior as ScadaSymbolBehaviorValue;
let getValueSettings: GetValueSettings<any> = this.settings.behavior[getBehavior.id]; let getValueSettings: GetValueSettings<any> = this.settings.behavior[getBehavior.id];
getValueSettings = {...getValueSettings, actionLabel: getValueSettings = {...getValueSettings, actionLabel:
this.ctx.utilsService.customTranslation(getBehavior.name, getBehavior.name)}; this.ctx.utilsService.customTranslation(getBehavior.name, getBehavior.name)};
const stateValueSubject = new BehaviorSubject<any>(getValueSettings.defaultValue); const stateValueSubject = new BehaviorSubject<any>(getValueSettings.defaultValue);
this.stateValueSubjects[getBehavior.id] = stateValueSubject; this.stateValueSubjects[getBehavior.id] = stateValueSubject;
this.context.values[getBehavior.id] = getValueSettings.defaultValue; this.context.values[getBehavior.id] = getValueSettings.defaultValue;
stateValueSubject.subscribe((value) => { stateValueSubject.pipe(takeUntil(this.destroy$)).subscribe((value) => {
this.onStateValueChanged(getBehavior.id, value); this.onStateValueChanged(getBehavior.id, value);
}); });
const valueGetter = const valueGetter =
@ -511,15 +511,15 @@ export class IotSvgObject {
}, this.simulated); }, this.simulated);
this.valueGetters.push(valueGetter); this.valueGetters.push(valueGetter);
this.valueActions.push(valueGetter); this.valueActions.push(valueGetter);
} else if (behavior.type === IotSvgBehaviorType.action) { } else if (behavior.type === ScadaSymbolBehaviorType.action) {
const setBehavior = behavior as IotSvgBehaviorAction; const setBehavior = behavior as ScadaSymbolBehaviorAction;
let setValueSettings: SetValueSettings = this.settings.behavior[setBehavior.id]; let setValueSettings: SetValueSettings = this.settings.behavior[setBehavior.id];
setValueSettings = {...setValueSettings, actionLabel: setValueSettings = {...setValueSettings, actionLabel:
this.ctx.utilsService.customTranslation(setBehavior.name, setBehavior.name)}; this.ctx.utilsService.customTranslation(setBehavior.name, setBehavior.name)};
const valueSetter = ValueSetter.fromSettings<any>(this.ctx, setValueSettings, this.simulated); const valueSetter = ValueSetter.fromSettings<any>(this.ctx, setValueSettings, this.simulated);
this.valueSetters[setBehavior.id] = valueSetter; this.valueSetters[setBehavior.id] = valueSetter;
this.valueActions.push(valueSetter); this.valueActions.push(valueSetter);
} else if (behavior.type === IotSvgBehaviorType.widgetAction) { } else if (behavior.type === ScadaSymbolBehaviorType.widgetAction) {
// TODO: // TODO:
} }
} }
@ -529,45 +529,49 @@ export class IotSvgObject {
this.valueGetters.forEach(valueGetter => { this.valueGetters.forEach(valueGetter => {
getValueObservables.push(valueGetter.getValue()); getValueObservables.push(valueGetter.getValue());
}); });
this.loadingSubject.next(true); this.onLoadingState(true);
forkJoin(getValueObservables).subscribe( forkJoin(getValueObservables).pipe(takeUntil(this.destroy$)).subscribe(
{ {
next: () => { next: () => {
this.loadingSubject.next(false); this.onLoadingState(false);
}, },
error: () => { error: () => {
this.loadingSubject.next(false); this.onLoadingState(false);
} }
} }
); );
} }
} }
private onLoadingState(loading: boolean) {
this.callbacks.onScadaSymbolObjectLoadingState(loading);
}
private onError(error: string) { private onError(error: string) {
this.callbacks.onSvgObjectError(error); this.callbacks.onScadaSymbolObjectError(error);
} }
private onMessage(message: string) { private onMessage(message: string) {
this.callbacks.onSvgObjectMessage(message); this.callbacks.onScadaSymbolObjectMessage(message);
} }
private callAction(event: Event, behaviorId: string, value?: any, observer?: Partial<Observer<void>>) { private callAction(event: Event, behaviorId: string, value?: any, observer?: Partial<Observer<void>>) {
const behavior = this.metadata.behavior.find(b => b.id === behaviorId); const behavior = this.metadata.behavior.find(b => b.id === behaviorId);
if (behavior) { if (behavior) {
if (behavior.type === IotSvgBehaviorType.action) { if (behavior.type === ScadaSymbolBehaviorType.action) {
const valueSetter = this.valueSetters[behaviorId]; const valueSetter = this.valueSetters[behaviorId];
if (valueSetter) { if (valueSetter) {
this.loadingSubject.next(true); this.onLoadingState(true);
valueSetter.setValue(value).subscribe( valueSetter.setValue(value).pipe(takeUntil(this.destroy$)).subscribe(
{ {
next: () => { next: () => {
if (observer?.next) { if (observer?.next) {
observer.next(); observer.next();
} }
this.loadingSubject.next(false); this.onLoadingState(false);
}, },
error: (err) => { error: (err) => {
this.loadingSubject.next(false); this.onLoadingState(false);
if (observer?.error) { if (observer?.error) {
observer.error(err); observer.error(err);
} }
@ -577,7 +581,7 @@ export class IotSvgObject {
} }
); );
} }
} else if (behavior.type === IotSvgBehaviorType.widgetAction) { } else if (behavior.type === ScadaSymbolBehaviorType.widgetAction) {
const widgetAction: WidgetAction = this.settings.behavior[behavior.id]; const widgetAction: WidgetAction = this.settings.behavior[behavior.id];
if (this.simulated) { if (this.simulated) {
const translatedType = this.ctx.translate.instant(widgetActionTypeTranslationMap.get(widgetAction.type)); const translatedType = this.ctx.translate.instant(widgetActionTypeTranslationMap.get(widgetAction.type));
@ -598,8 +602,10 @@ export class IotSvgObject {
const targetWidth = this.rootElement.getBoundingClientRect().width; const targetWidth = this.rootElement.getBoundingClientRect().width;
const targetHeight = this.rootElement.getBoundingClientRect().height; const targetHeight = this.rootElement.getBoundingClientRect().height;
if (targetWidth && targetHeight) { if (targetWidth && targetHeight) {
const svgAspect = this.box.width / this.box.height;
const shapeAspect = targetWidth / targetHeight;
let scale: number; let scale: number;
if (targetWidth < targetHeight) { if (svgAspect > shapeAspect) {
scale = targetWidth / this.box.width; scale = targetWidth / this.box.width;
} else { } else {
scale = targetHeight / this.box.height; scale = targetHeight / this.box.height;
@ -617,7 +623,7 @@ export class IotSvgObject {
} }
private onValue(id: string, value: any) { private onValue(id: string, value: any) {
const valueBehavior = this.metadata.behavior.find(b => b.id === id) as IotSvgBehaviorValue; const valueBehavior = this.metadata.behavior.find(b => b.id === id) as ScadaSymbolBehaviorValue;
value = this.normalizeValue(value, valueBehavior.valueType); value = this.normalizeValue(value, valueBehavior.valueType);
this.setValue(valueBehavior.id, value); this.setValue(valueBehavior.id, value);
} }
@ -725,7 +731,7 @@ export class IotSvgObject {
return Array.isArray(element) ? element : [element]; return Array.isArray(element) ? element : [element];
} }
private getProperty(id: string): IotSvgProperty { private getProperty(id: string): ScadaSymbolProperty {
return this.metadata.properties.find(p => p.id === id); return this.metadata.properties.find(p => p.id === id);
} }
@ -734,9 +740,9 @@ export class IotSvgObject {
if (property) { if (property) {
const value = this.settings.properties[id]; const value = this.settings.properties[id];
if (isDefinedAndNotNull(value)) { if (isDefinedAndNotNull(value)) {
if (property.type === IotSvgPropertyType.color_settings) { if (property.type === ScadaSymbolPropertyType.color_settings) {
return ColorProcessor.fromSettings(value); return ColorProcessor.fromSettings(value);
} else if (property.type === IotSvgPropertyType.text) { } else if (property.type === ScadaSymbolPropertyType.text) {
const result = this.ctx.utilsService.customTranslation(value, value); const result = this.ctx.utilsService.customTranslation(value, value);
const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo(); const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo();
return createLabelFromSubscriptionEntityInfo(entityInfo, result); return createLabelFromSubscriptionEntityInfo(entityInfo, result);
@ -744,13 +750,13 @@ export class IotSvgObject {
return value; return value;
} else { } else {
switch (property.type) { switch (property.type) {
case IotSvgPropertyType.text: case ScadaSymbolPropertyType.text:
return ''; return '';
case IotSvgPropertyType.number: case ScadaSymbolPropertyType.number:
return 0; return 0;
case IotSvgPropertyType.color: case ScadaSymbolPropertyType.color:
return '#000'; return '#000';
case IotSvgPropertyType.color_settings: case ScadaSymbolPropertyType.color_settings:
return ColorProcessor.fromSettings(constantColor('#000')); return ColorProcessor.fromSettings(constantColor('#000'));
} }
} }

50
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.html → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.html

@ -15,44 +15,44 @@
limitations under the License. limitations under the License.
--> -->
<ng-container [formGroup]="iotSvgObjectSettingsFormGroup"> <ng-container [formGroup]="scadaSymbolObjectSettingsFormGroup">
<div class="iot-svg-object-title">{{ metadata?.title | customTranslate }}</div>
<div *ngIf="metadata?.behavior?.length" class="tb-form-panel" formGroupName="behavior"> <div *ngIf="metadata?.behavior?.length" class="tb-form-panel" formGroupName="behavior">
<div class="tb-form-panel-title" translate>scada.behavior.behavior</div> <div class="tb-form-panel-title" translate>scada.behavior.behavior</div>
<div *ngFor="let behaviour of metadata.behavior" class="tb-form-row"> <div *ngFor="let behavior of metadata.behavior" class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{ behaviour.hint | customTranslate }}">{{ behaviour.name | customTranslate }}</div> <div class="fixed-title-width" tb-hint-tooltip-icon="{{ behavior.hint | customTranslate }}">{{ behavior.name | customTranslate }}</div>
<tb-get-value-action-settings *ngIf="behaviour.type === IotSvgBehaviorType.value" <tb-get-value-action-settings *ngIf="behavior.type === ScadaSymbolBehaviorType.value"
fxFlex fxFlex
panelTitle="{{ behaviour.name | customTranslate }}" panelTitle="{{ behavior.name | customTranslate }}"
[valueType]="behaviour.valueType" [valueType]="behavior.valueType"
[trueLabel]="behaviour.trueLabel | customTranslate" [trueLabel]="behavior.trueLabel | customTranslate"
[falseLabel]="behaviour.falseLabel | customTranslate" [falseLabel]="behavior.falseLabel | customTranslate"
[stateLabel]="behaviour.stateLabel | customTranslate" [stateLabel]="behavior.stateLabel | customTranslate"
[aliasController]="aliasController" [aliasController]="aliasController"
[targetDevice]="targetDevice" [targetDevice]="targetDevice"
[widgetType]="widgetType" [widgetType]="widgetType"
formControlName="{{ behaviour.id }}"> formControlName="{{ behavior.id }}">
</tb-get-value-action-settings> </tb-get-value-action-settings>
<tb-set-value-action-settings *ngIf="behaviour.type === IotSvgBehaviorType.action" <tb-set-value-action-settings *ngIf="behavior.type === ScadaSymbolBehaviorType.action"
fxFlex fxFlex
panelTitle="{{ behaviour.name | customTranslate }}" panelTitle="{{ behavior.name | customTranslate }}"
[valueType]="behaviour.valueType" [valueType]="behavior.valueType"
[aliasController]="aliasController" [aliasController]="aliasController"
[targetDevice]="targetDevice" [targetDevice]="targetDevice"
[widgetType]="widgetType" [widgetType]="widgetType"
formControlName="{{ behaviour.id }}"> formControlName="{{ behavior.id }}">
</tb-set-value-action-settings> </tb-set-value-action-settings>
<tb-widget-action-settings *ngIf="behaviour.type === IotSvgBehaviorType.widgetAction" <tb-widget-action-settings *ngIf="behavior.type === ScadaSymbolBehaviorType.widgetAction"
fxFlex fxFlex
panelTitle="{{ behaviour.name | customTranslate }}" panelTitle="{{ behavior.name | customTranslate }}"
[callbacks]="callbacks" [callbacks]="callbacks"
[widgetType]="widgetType" [widgetType]="widgetType"
formControlName="{{ behaviour.id }}"> formControlName="{{ behavior.id }}">
</tb-widget-action-settings> </tb-widget-action-settings>
</div> </div>
</div> </div>
<div *ngIf="propertyRows?.length" class="tb-form-panel" formGroupName="properties"> <div [fxShow]="propertyRows?.length || appearanceProperties.children.length" class="tb-form-panel" formGroupName="properties">
<div class="tb-form-panel-title" translate>widget-config.appearance</div> <div class="tb-form-panel-title" translate>widget-config.appearance</div>
<div #appearanceProperties><ng-content select=".tb-scada-symbol-appearance-properties"></ng-content></div>
<div *ngFor="let propertyRow of propertyRows" class="tb-form-row space-between" [class]="propertyRow.rowClass"> <div *ngFor="let propertyRow of propertyRows" class="tb-form-row space-between" [class]="propertyRow.rowClass">
<mat-slide-toggle *ngIf="propertyRow.switch" class="mat-slide fixed-title-width" formControlName="{{ propertyRow.switch.id }}"> <mat-slide-toggle *ngIf="propertyRow.switch" class="mat-slide fixed-title-width" formControlName="{{ propertyRow.switch.id }}">
{{ propertyRow.label | customTranslate }} {{ propertyRow.label | customTranslate }}
@ -61,35 +61,35 @@
<div fxLayout="row" fxFlex fxLayoutAlign="end center" fxLayoutGap="8px"> <div fxLayout="row" fxFlex fxLayoutAlign="end center" fxLayoutGap="8px">
<ng-container *ngFor="let property of propertyRow.properties"> <ng-container *ngFor="let property of propertyRow.properties">
<div *ngIf="property.subLabel" class="tb-small-label">{{ property.subLabel | customTranslate }}</div> <div *ngIf="property.subLabel" class="tb-small-label">{{ property.subLabel | customTranslate }}</div>
<mat-form-field *ngIf="property.type === IotSvgPropertyType.text" [class]="property.fieldClass" appearance="outline" subscriptSizing="dynamic"> <mat-form-field *ngIf="property.type === ScadaSymbolPropertyType.text" [class]="property.fieldClass" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="{{ property.id }}" [required]="property.required" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="{{ property.id }}" [required]="property.required" placeholder="{{ 'widget-config.set' | translate }}">
<div fxHide.lt-md matSuffix *ngIf="property.fieldSuffix">{{ property.fieldSuffix | customTranslate }}</div> <div fxHide.lt-md matSuffix *ngIf="property.fieldSuffix">{{ property.fieldSuffix | customTranslate }}</div>
</mat-form-field> </mat-form-field>
<tb-color-input *ngIf="property.type === IotSvgPropertyType.color" <tb-color-input *ngIf="property.type === ScadaSymbolPropertyType.color"
[required]="property.required" [required]="property.required"
[class]="property.fieldClass" [class]="property.fieldClass"
asBoxInput asBoxInput
colorClearButton colorClearButton
formControlName="{{ property.id }}"> formControlName="{{ property.id }}">
</tb-color-input> </tb-color-input>
<tb-color-settings *ngIf="property.type === IotSvgPropertyType.color_settings" <tb-color-settings *ngIf="property.type === ScadaSymbolPropertyType.color_settings"
[class]="property.fieldClass" [class]="property.fieldClass"
formControlName="{{ property.id }}" formControlName="{{ property.id }}"
settingsKey="{{ property.name | customTranslate }}"> settingsKey="{{ property.name | customTranslate }}">
</tb-color-settings> </tb-color-settings>
<mat-form-field *ngIf="property.type === IotSvgPropertyType.number" [class]="property.fieldClass" appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field *ngIf="property.type === ScadaSymbolPropertyType.number" [class]="property.fieldClass" appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="{{ property.id }}" [required]="property.required" <input matInput formControlName="{{ property.id }}" [required]="property.required"
[min]="property.min" [max]="property.max" [step]="property.step" [min]="property.min" [max]="property.max" [step]="property.step"
type="number" placeholder="{{ 'widget-config.set' | translate }}"> type="number" placeholder="{{ 'widget-config.set' | translate }}">
<div fxHide.lt-md matSuffix *ngIf="property.fieldSuffix">{{ property.fieldSuffix | customTranslate }}</div> <div fxHide.lt-md matSuffix *ngIf="property.fieldSuffix">{{ property.fieldSuffix | customTranslate }}</div>
</mat-form-field> </mat-form-field>
<tb-font-settings *ngIf="property.type === IotSvgPropertyType.font" <tb-font-settings *ngIf="property.type === ScadaSymbolPropertyType.font"
[class]="property.fieldClass" [class]="property.fieldClass"
formControlName="{{ property.id }}" formControlName="{{ property.id }}"
clearButton clearButton
disabledLineHeight> disabledLineHeight>
</tb-font-settings> </tb-font-settings>
<tb-unit-input *ngIf="property.type === IotSvgPropertyType.units" <tb-unit-input *ngIf="property.type === ScadaSymbolPropertyType.units"
[class]="property.fieldClass" [class]="property.fieldClass"
[required]="property.required" [required]="property.required"
formControlName="{{ property.id }}"></tb-unit-input> formControlName="{{ property.id }}"></tb-unit-input>

11
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.scss

@ -13,11 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
:host { .tb-scada-symbol-appearance-properties {
.iot-svg-object-title { gap: 16px;
font-size: 18px; display: flex;
font-style: normal; flex-direction: column;
font-weight: 500;
line-height: 24px;
}
} }

129
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.ts

@ -14,7 +14,16 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { ChangeDetectorRef, Component, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import {
ChangeDetectorRef,
Component,
forwardRef,
Input,
OnChanges,
OnInit,
SimpleChanges,
ViewEncapsulation
} from '@angular/core';
import { import {
ControlValueAccessor, ControlValueAccessor,
NG_VALIDATORS, NG_VALIDATORS,
@ -29,59 +38,60 @@ import {
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { import {
defaultIotSvgObjectSettings, defaultScadaSymbolObjectSettings,
IotSvgBehaviorType, parseScadaSymbolMetadataFromContent,
IotSvgMetadata, ScadaSymbolBehaviorType,
IotSvgObjectSettings, ScadaSymbolMetadata,
IotSvgPropertyType, ScadaSymbolObjectSettings,
parseIotSvgMetadataFromContent ScadaSymbolPropertyType
} from '@home/components/widget/lib/svg/iot-svg.models'; } from '@home/components/widget/lib/scada/scada-symbol.models';
import { HttpClient } from '@angular/common/http';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { TargetDevice, widgetType } from '@shared/models/widget.models'; import { TargetDevice, widgetType } from '@shared/models/widget.models';
import { isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { isDefinedAndNotNull, mergeDeep } from '@core/utils';
import { import {
IotSvgPropertyRow, ScadaSymbolPropertyRow,
toPropertyRows toPropertyRows
} from '@home/components/widget/lib/settings/common/svg/iot-svg-object-settings.models'; } from '@home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.models';
import { merge, Observable, of, Subscription } from 'rxjs'; import { merge, Observable, of, Subscription } from 'rxjs';
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models'; import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
import { ImageService } from '@core/http/image.service'; import { ImageService } from '@core/http/image.service';
import { map } from 'rxjs/operators';
@Component({ @Component({
selector: 'tb-iot-svg-object-settings', selector: 'tb-scada-symbol-object-settings',
templateUrl: './iot-svg-object-settings.component.html', templateUrl: './scada-symbol-object-settings.component.html',
styleUrls: ['./iot-svg-object-settings.component.scss', './../../widget-settings.scss'], styleUrls: ['./scada-symbol-object-settings.component.scss', './../../widget-settings.scss'],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => IotSvgObjectSettingsComponent), useExisting: forwardRef(() => ScadaSymbolObjectSettingsComponent),
multi: true multi: true
}, },
{ {
provide: NG_VALIDATORS, provide: NG_VALIDATORS,
useExisting: forwardRef(() => IotSvgObjectSettingsComponent), useExisting: forwardRef(() => ScadaSymbolObjectSettingsComponent),
multi: true multi: true
} }
] ],
encapsulation: ViewEncapsulation.None
}) })
export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, ControlValueAccessor, Validator { export class ScadaSymbolObjectSettingsComponent implements OnInit, OnChanges, ControlValueAccessor, Validator {
IotSvgBehaviorType = IotSvgBehaviorType; ScadaSymbolBehaviorType = ScadaSymbolBehaviorType;
IotSvgPropertyType = IotSvgPropertyType; ScadaSymbolPropertyType = ScadaSymbolPropertyType;
@Input() @Input()
disabled: boolean; disabled: boolean;
@Input() @Input()
svgPath = 'drawing.svg'; scadaSymbolUrl: string;
@Input() @Input()
svgUrl: string; scadaSymbolContent: string;
@Input() @Input()
svgContent: string; scadaSymbolMetadata: ScadaSymbolMetadata;
@Input() @Input()
aliasController: IAliasController; aliasController: IAliasController;
@ -95,31 +105,30 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
@Input() @Input()
widgetType: widgetType; widgetType: widgetType;
private modelValue: IotSvgObjectSettings; private modelValue: ScadaSymbolObjectSettings;
private propagateChange = null; private propagateChange = null;
private validatorTriggers: string[]; private validatorTriggers: string[];
private validatorSubscription: Subscription; private validatorSubscription: Subscription;
public iotSvgObjectSettingsFormGroup: UntypedFormGroup; public scadaSymbolObjectSettingsFormGroup: UntypedFormGroup;
metadata: IotSvgMetadata; metadata: ScadaSymbolMetadata;
propertyRows: IotSvgPropertyRow[]; propertyRows: ScadaSymbolPropertyRow[];
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder, private fb: UntypedFormBuilder,
private http: HttpClient,
private imageService: ImageService, private imageService: ImageService,
private cd: ChangeDetectorRef) { private cd: ChangeDetectorRef) {
} }
ngOnInit(): void { ngOnInit(): void {
this.iotSvgObjectSettingsFormGroup = this.fb.group({ this.scadaSymbolObjectSettingsFormGroup = this.fb.group({
behavior: this.fb.group({}), behavior: this.fb.group({}),
properties: this.fb.group({}) properties: this.fb.group({})
}); });
this.iotSvgObjectSettingsFormGroup.valueChanges.subscribe(() => { this.scadaSymbolObjectSettingsFormGroup.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();
}); });
this.loadMetadata(); this.loadMetadata();
@ -129,7 +138,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
for (const propName of Object.keys(changes)) { for (const propName of Object.keys(changes)) {
const change = changes[propName]; const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) { if (!change.firstChange && change.currentValue !== change.previousValue) {
if (['svgPath', 'svgUrl', 'svgContent'].includes(propName)) { if (['scadaSymbolUrl', 'scadaSymbolContent', 'scadaSymbolMetadata'].includes(propName)) {
this.loadMetadata(); this.loadMetadata();
} }
} }
@ -146,22 +155,22 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (isDisabled) { if (isDisabled) {
this.iotSvgObjectSettingsFormGroup.disable({emitEvent: false}); this.scadaSymbolObjectSettingsFormGroup.disable({emitEvent: false});
} else { } else {
this.iotSvgObjectSettingsFormGroup.enable({emitEvent: false}); this.scadaSymbolObjectSettingsFormGroup.enable({emitEvent: false});
this.updateValidators(); this.updateValidators();
} }
} }
writeValue(value: IotSvgObjectSettings): void { writeValue(value: ScadaSymbolObjectSettings): void {
this.modelValue = value || { behavior: {}, properties: {} }; this.modelValue = value || { behavior: {}, properties: {} };
this.setupValue(); this.setupValue();
} }
validate(c: UntypedFormControl) { validate(_c: UntypedFormControl) {
const valid = this.iotSvgObjectSettingsFormGroup.valid; const valid = this.scadaSymbolObjectSettingsFormGroup.valid;
return valid ? null : { return valid ? null : {
iotSvgObject: { scadaSymbolObjectSettings: {
valid: false, valid: false,
}, },
}; };
@ -174,28 +183,36 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
} }
this.validatorTriggers = []; this.validatorTriggers = [];
let svgContent$: Observable<string>; let metadata$: Observable<ScadaSymbolMetadata>;
if (this.svgContent) { if (this.scadaSymbolMetadata) {
svgContent$ = of(this.svgContent); metadata$ = of(this.scadaSymbolMetadata);
} else if (this.svgUrl) {
svgContent$ = this.imageService.getImageString(this.svgUrl);
} else { } else {
svgContent$ = this.http.get(this.svgPath, {responseType: 'text'}); let content$: Observable<string>;
if (this.scadaSymbolContent) {
content$ = of(this.scadaSymbolContent);
} else if (this.scadaSymbolUrl) {
content$ = this.imageService.getImageString(this.scadaSymbolUrl);
} else {
content$ = of('<svg></svg>');
}
metadata$ = content$.pipe(
map(content => parseScadaSymbolMetadataFromContent(content))
);
} }
svgContent$.subscribe( metadata$.subscribe(
(svgContent) => { (metadata) => {
this.metadata = parseIotSvgMetadataFromContent(svgContent); this.metadata = metadata;
this.propertyRows = toPropertyRows(this.metadata.properties); this.propertyRows = toPropertyRows(this.metadata.properties);
const behaviorFormGroup = this.iotSvgObjectSettingsFormGroup.get('behavior') as UntypedFormGroup; const behaviorFormGroup = this.scadaSymbolObjectSettingsFormGroup.get('behavior') as UntypedFormGroup;
for (const control of Object.keys(behaviorFormGroup.controls)) { for (const control of Object.keys(behaviorFormGroup.controls)) {
behaviorFormGroup.removeControl(control, {emitEvent: false}); behaviorFormGroup.removeControl(control, {emitEvent: false});
} }
const propertiesFormGroup = this.iotSvgObjectSettingsFormGroup.get('properties') as UntypedFormGroup; const propertiesFormGroup = this.scadaSymbolObjectSettingsFormGroup.get('properties') as UntypedFormGroup;
for (const control of Object.keys(propertiesFormGroup.controls)) { for (const control of Object.keys(propertiesFormGroup.controls)) {
propertiesFormGroup.removeControl(control, {emitEvent: false}); propertiesFormGroup.removeControl(control, {emitEvent: false});
} }
for (const behaviour of this.metadata.behavior) { for (const behavior of this.metadata.behavior) {
behaviorFormGroup.addControl(behaviour.id, this.fb.control(null, []), {emitEvent: false}); behaviorFormGroup.addControl(behavior.id, this.fb.control(null, []), {emitEvent: false});
} }
for (const property of this.metadata.properties) { for (const property of this.metadata.properties) {
if (property.disableOnProperty) { if (property.disableOnProperty) {
@ -207,7 +224,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
if (property.required) { if (property.required) {
validators.push(Validators.required); validators.push(Validators.required);
} }
if (property.type === IotSvgPropertyType.number) { if (property.type === ScadaSymbolPropertyType.number) {
if (isDefinedAndNotNull(property.min)) { if (isDefinedAndNotNull(property.min)) {
validators.push(Validators.min(property.min)); validators.push(Validators.min(property.min));
} }
@ -233,7 +250,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
} }
private updateValidators() { private updateValidators() {
const propertiesFormGroup = this.iotSvgObjectSettingsFormGroup.get('properties') as UntypedFormGroup; const propertiesFormGroup = this.scadaSymbolObjectSettingsFormGroup.get('properties') as UntypedFormGroup;
for (const trigger of this.validatorTriggers) { for (const trigger of this.validatorTriggers) {
const value: boolean = propertiesFormGroup.get(trigger).value; const value: boolean = propertiesFormGroup.get(trigger).value;
this.metadata.properties.filter(p => p.disableOnProperty === trigger).forEach( this.metadata.properties.filter(p => p.disableOnProperty === trigger).forEach(
@ -251,9 +268,9 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
private setupValue() { private setupValue() {
if (this.metadata) { if (this.metadata) {
const defaults = defaultIotSvgObjectSettings(this.metadata); const defaults = defaultScadaSymbolObjectSettings(this.metadata);
this.modelValue = mergeDeep<IotSvgObjectSettings>(defaults, this.modelValue); this.modelValue = mergeDeep<ScadaSymbolObjectSettings>(defaults, this.modelValue);
this.iotSvgObjectSettingsFormGroup.patchValue( this.scadaSymbolObjectSettingsFormGroup.patchValue(
this.modelValue, {emitEvent: false} this.modelValue, {emitEvent: false}
); );
this.setDisabledState(this.disabled); this.setDisabledState(this.disabled);
@ -261,7 +278,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
} }
private updateModel() { private updateModel() {
this.modelValue = this.iotSvgObjectSettingsFormGroup.getRawValue(); this.modelValue = this.scadaSymbolObjectSettingsFormGroup.getRawValue();
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
} }
} }

14
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.models.ts → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.models.ts

@ -14,17 +14,17 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { IotSvgProperty, IotSvgPropertyType } from '@home/components/widget/lib/svg/iot-svg.models'; import { ScadaSymbolProperty, ScadaSymbolPropertyType } from '@home/components/widget/lib/scada/scada-symbol.models';
export interface IotSvgPropertyRow { export interface ScadaSymbolPropertyRow {
label: string; label: string;
properties: IotSvgProperty[]; properties: ScadaSymbolProperty[];
switch?: IotSvgProperty; switch?: ScadaSymbolProperty;
rowClass?: string; rowClass?: string;
} }
export const toPropertyRows = (properties: IotSvgProperty[]): IotSvgPropertyRow[] => { export const toPropertyRows = (properties: ScadaSymbolProperty[]): ScadaSymbolPropertyRow[] => {
const result: IotSvgPropertyRow[] = []; const result: ScadaSymbolPropertyRow[] = [];
for (const property of properties) { for (const property of properties) {
let propertyRow = result.find(r => r.label === property.name); let propertyRow = result.find(r => r.label === property.name);
if (!propertyRow) { if (!propertyRow) {
@ -35,7 +35,7 @@ export const toPropertyRows = (properties: IotSvgProperty[]): IotSvgPropertyRow[
}; };
result.push(propertyRow); result.push(propertyRow);
} }
if (property.type === IotSvgPropertyType.switch) { if (property.type === ScadaSymbolPropertyType.switch) {
propertyRow.switch = property; propertyRow.switch = property;
} else { } else {
propertyRow.properties.push(property); propertyRow.properties.push(property);

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

@ -150,8 +150,8 @@ import {
} from '@home/components/widget/lib/settings/common/indicator/status-widget-state-settings.component'; } from '@home/components/widget/lib/settings/common/indicator/status-widget-state-settings.component';
import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/common/chart/chart-bar-settings.component'; import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/common/chart/chart-bar-settings.component';
import { import {
IotSvgObjectSettingsComponent ScadaSymbolObjectSettingsComponent
} from '@home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component'; } from '@home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -207,7 +207,7 @@ import {
TimeSeriesChartStateRowComponent, TimeSeriesChartStateRowComponent,
TimeSeriesChartGridSettingsComponent, TimeSeriesChartGridSettingsComponent,
StatusWidgetStateSettingsComponent, StatusWidgetStateSettingsComponent,
IotSvgObjectSettingsComponent, ScadaSymbolObjectSettingsComponent,
DataKeyInputComponent, DataKeyInputComponent,
EntityAliasInputComponent EntityAliasInputComponent
], ],
@ -269,7 +269,7 @@ import {
TimeSeriesChartStateRowComponent, TimeSeriesChartStateRowComponent,
TimeSeriesChartGridSettingsComponent, TimeSeriesChartGridSettingsComponent,
StatusWidgetStateSettingsComponent, StatusWidgetStateSettingsComponent,
IotSvgObjectSettingsComponent, ScadaSymbolObjectSettingsComponent,
DataKeyInputComponent, DataKeyInputComponent,
EntityAliasInputComponent EntityAliasInputComponent
], ],

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

@ -99,7 +99,7 @@ import { PieChartWidgetComponent } from '@home/components/widget/lib/chart/pie-c
import { BarChartWidgetComponent } from '@home/components/widget/lib/chart/bar-chart-widget.component'; import { BarChartWidgetComponent } from '@home/components/widget/lib/chart/bar-chart-widget.component';
import { PolarAreaWidgetComponent } from '@home/components/widget/lib/chart/polar-area-widget.component'; import { PolarAreaWidgetComponent } from '@home/components/widget/lib/chart/polar-area-widget.component';
import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/radar-chart-widget.component'; import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/radar-chart-widget.component';
import { IotSvgWidgetComponent } from '@home/components/widget/lib/svg/iot-svg-widget.component'; import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -166,7 +166,7 @@ import { IotSvgWidgetComponent } from '@home/components/widget/lib/svg/iot-svg-w
BarChartWidgetComponent, BarChartWidgetComponent,
PolarAreaWidgetComponent, PolarAreaWidgetComponent,
RadarChartWidgetComponent, RadarChartWidgetComponent,
IotSvgWidgetComponent ScadaSymbolWidgetComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -236,7 +236,7 @@ import { IotSvgWidgetComponent } from '@home/components/widget/lib/svg/iot-svg-w
BarChartWidgetComponent, BarChartWidgetComponent,
PolarAreaWidgetComponent, PolarAreaWidgetComponent,
RadarChartWidgetComponent, RadarChartWidgetComponent,
IotSvgWidgetComponent ScadaSymbolWidgetComponent
], ],
providers: [ providers: [
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule } {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }

6
ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts

@ -43,7 +43,7 @@ import { ImageGalleryComponent } from '@shared/components/image/image-gallery.co
import { ImageResourceType, IMAGES_URL_PREFIX, ResourceSubType } from '@shared/models/resource.models'; import { ImageResourceType, IMAGES_URL_PREFIX, ResourceSubType } from '@shared/models/resource.models';
import { ScadaSymbolComponent } from '@home/pages/scada-symbol/scada-symbol.component'; import { ScadaSymbolComponent } from '@home/pages/scada-symbol/scada-symbol.component';
import { ImageService } from '@core/http/image.service'; import { ImageService } from '@core/http/image.service';
import { ScadaSymbolData } from '@home/pages/scada-symbol/scada-symbol.models'; import { ScadaSymbolData } from '@home/pages/scada-symbol/scada-symbol-editor.models';
@Injectable() @Injectable()
export class OAuth2LoginProcessingUrlResolver implements Resolve<string> { export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
@ -64,7 +64,7 @@ export const scadaSymbolResolver: ResolveFn<ScadaSymbolData> =
const key = decodeURIComponent(route.params.key); const key = decodeURIComponent(route.params.key);
return forkJoin({ return forkJoin({
imageResource: imageService.getImageInfo(type, key), imageResource: imageService.getImageInfo(type, key),
svgContent: imageService.getImageString(`${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(key)}`) scadaSymbolContent: imageService.getImageString(`${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(key)}`)
}); });
}; };
@ -127,7 +127,7 @@ const routes: Routes = [
data: { data: {
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'scada.symbols', title: 'scada.symbols',
imageSubType: ResourceSubType.IOT_SVG imageSubType: ResourceSubType.SCADA_SYMBOL
} }
}, },
{ {

10
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.html

@ -40,13 +40,13 @@
<div class="fixed-title-width" translate>scada.behavior.type</div> <div class="fixed-title-width" translate>scada.behavior.type</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type"> <mat-select formControlName="type">
<mat-option *ngFor="let type of iotSvgBehaviorTypes" [value]="type"> <mat-option *ngFor="let type of scadaSymbolBehaviorTypes" [value]="type">
{{ iotSvgBehaviorTypeTranslations.get(type) | translate }} {{ scadaSymbolBehaviorTypeTranslations.get(type) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="[IotSvgBehaviorType.value, IotSvgBehaviorType.action].includes(behaviorFormGroup.get('type').value)" class="tb-form-row"> <div *ngIf="[ScadaSymbolBehaviorType.value, ScadaSymbolBehaviorType.action].includes(behaviorFormGroup.get('type').value)" class="tb-form-row">
<div class="fixed-title-width" translate>scada.behavior.value-type</div> <div class="fixed-title-width" translate>scada.behavior.value-type</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="flex tb-value-type"> <mat-form-field appearance="outline" subscriptSizing="dynamic" class="flex tb-value-type">
<mat-select formControlName="valueType"> <mat-select formControlName="valueType">
@ -63,7 +63,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<ng-container *ngIf="behaviorFormGroup.get('type').value === IotSvgBehaviorType.value"> <ng-container *ngIf="behaviorFormGroup.get('type').value === ScadaSymbolBehaviorType.value">
<div class="tb-form-row "> <div class="tb-form-row ">
<div class="fixed-title-width" translate>scada.behavior.default-value</div> <div class="fixed-title-width" translate>scada.behavior.default-value</div>
<tb-value-input fxFlex [valueType]="behaviorFormGroup.get('valueType').value" <tb-value-input fxFlex [valueType]="behaviorFormGroup.get('valueType').value"
@ -90,7 +90,7 @@
</div> </div>
</ng-container> </ng-container>
</ng-container> </ng-container>
<ng-container *ngIf="behaviorFormGroup.get('type').value === IotSvgBehaviorType.action"> <ng-container *ngIf="behaviorFormGroup.get('type').value === ScadaSymbolBehaviorType.action">
<div class="tb-form-panel stroked"> <div class="tb-form-panel stroked">
<div class="tb-form-row no-padding no-border column-xs"> <div class="tb-form-row no-padding no-border column-xs">
<div class="fixed-title-width">{{ 'scada.behavior.default-payload' | translate }}</div> <div class="fixed-title-width">{{ 'scada.behavior.default-payload' | translate }}</div>

28
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.ts

@ -19,11 +19,11 @@ import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { merge } from 'rxjs'; import { merge } from 'rxjs';
import { import {
IotSvgBehavior, ScadaSymbolBehavior,
IotSvgBehaviorType, ScadaSymbolBehaviorType,
iotSvgBehaviorTypes, scadaSymbolBehaviorTypes,
iotSvgBehaviorTypeTranslations scadaSymbolBehaviorTypeTranslations
} from '@app/modules/home/components/widget/lib/svg/iot-svg.models'; } from '@app/modules/home/components/widget/lib/scada/scada-symbol.models';
import { ValueType, valueTypesMap } from '@shared/models/constants'; import { ValueType, valueTypesMap } from '@shared/models/constants';
import { ValueToDataType } from '@shared/models/action-widget-settings.models'; import { ValueToDataType } from '@shared/models/action-widget-settings.models';
import { WidgetService } from '@core/http/widget.service'; import { WidgetService } from '@core/http/widget.service';
@ -36,14 +36,14 @@ import { WidgetService } from '@core/http/widget.service';
}) })
export class ScadaSymbolBehaviorPanelComponent implements OnInit { export class ScadaSymbolBehaviorPanelComponent implements OnInit {
IotSvgBehaviorType = IotSvgBehaviorType; ScadaSymbolBehaviorType = ScadaSymbolBehaviorType;
ValueType = ValueType; ValueType = ValueType;
ValueToDataType = ValueToDataType; ValueToDataType = ValueToDataType;
iotSvgBehaviorTypes = iotSvgBehaviorTypes; scadaSymbolBehaviorTypes = scadaSymbolBehaviorTypes;
iotSvgBehaviorTypeTranslations = iotSvgBehaviorTypeTranslations; scadaSymbolBehaviorTypeTranslations = scadaSymbolBehaviorTypeTranslations;
valueTypes = Object.keys(ValueType) as ValueType[]; valueTypes = Object.keys(ValueType) as ValueType[];
@ -53,13 +53,13 @@ export class ScadaSymbolBehaviorPanelComponent implements OnInit {
isAdd = false; isAdd = false;
@Input() @Input()
behavior: IotSvgBehavior; behavior: ScadaSymbolBehavior;
@Input() @Input()
popover: TbPopoverComponent<ScadaSymbolBehaviorPanelComponent>; popover: TbPopoverComponent<ScadaSymbolBehaviorPanelComponent>;
@Output() @Output()
behaviorSettingsApplied = new EventEmitter<IotSvgBehavior>(); behaviorSettingsApplied = new EventEmitter<ScadaSymbolBehavior>();
functionScopeVariables = this.widgetService.getWidgetScopeVariables(); functionScopeVariables = this.widgetService.getWidgetScopeVariables();
@ -107,7 +107,7 @@ export class ScadaSymbolBehaviorPanelComponent implements OnInit {
} }
private updateValidators() { private updateValidators() {
const type: IotSvgBehaviorType = this.behaviorFormGroup.get('type').value; const type: ScadaSymbolBehaviorType = this.behaviorFormGroup.get('type').value;
const valueType: ValueType = this.behaviorFormGroup.get('valueType').value; const valueType: ValueType = this.behaviorFormGroup.get('valueType').value;
let valueToDataType: ValueToDataType = this.behaviorFormGroup.get('valueToDataType').value; let valueToDataType: ValueToDataType = this.behaviorFormGroup.get('valueToDataType').value;
this.behaviorFormGroup.disable({emitEvent: false}); this.behaviorFormGroup.disable({emitEvent: false});
@ -116,7 +116,7 @@ export class ScadaSymbolBehaviorPanelComponent implements OnInit {
this.behaviorFormGroup.get('type').enable({emitEvent: false}); this.behaviorFormGroup.get('type').enable({emitEvent: false});
this.behaviorFormGroup.get('hint').enable({emitEvent: false}); this.behaviorFormGroup.get('hint').enable({emitEvent: false});
switch (type) { switch (type) {
case IotSvgBehaviorType.value: case ScadaSymbolBehaviorType.value:
this.behaviorFormGroup.get('valueType').enable({emitEvent: false}); this.behaviorFormGroup.get('valueType').enable({emitEvent: false});
this.behaviorFormGroup.get('defaultValue').enable({emitEvent: false}); this.behaviorFormGroup.get('defaultValue').enable({emitEvent: false});
if (valueType === ValueType.BOOLEAN) { if (valueType === ValueType.BOOLEAN) {
@ -125,7 +125,7 @@ export class ScadaSymbolBehaviorPanelComponent implements OnInit {
this.behaviorFormGroup.get('stateLabel').enable({emitEvent: false}); this.behaviorFormGroup.get('stateLabel').enable({emitEvent: false});
} }
break; break;
case IotSvgBehaviorType.action: case ScadaSymbolBehaviorType.action:
if (valueType === ValueType.BOOLEAN && valueToDataType === ValueToDataType.VALUE) { if (valueType === ValueType.BOOLEAN && valueToDataType === ValueToDataType.VALUE) {
this.behaviorFormGroup.patchValue({valueToDataType: ValueToDataType.CONSTANT}, {emitEvent: false}); this.behaviorFormGroup.patchValue({valueToDataType: ValueToDataType.CONSTANT}, {emitEvent: false});
valueToDataType = ValueToDataType.CONSTANT; valueToDataType = ValueToDataType.CONSTANT;
@ -141,7 +141,7 @@ export class ScadaSymbolBehaviorPanelComponent implements OnInit {
this.behaviorFormGroup.get('valueToDataFunction').enable({emitEvent: false}); this.behaviorFormGroup.get('valueToDataFunction').enable({emitEvent: false});
} }
break; break;
case IotSvgBehaviorType.widgetAction: case ScadaSymbolBehaviorType.widgetAction:
break; break;
} }
} }

4
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.html

@ -24,8 +24,8 @@
</mat-form-field> </mat-form-field>
<mat-form-field class="tb-inline-field tb-type-field fixed-height" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="tb-inline-field tb-type-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type"> <mat-select formControlName="type">
<mat-option *ngFor="let type of iotSvgBehaviorTypes" [value]="type"> <mat-option *ngFor="let type of scadaSymbolBehaviorTypes" [value]="type">
{{ iotSvgBehaviorTypeTranslations.get(type) | translate }} {{ scadaSymbolBehaviorTypeTranslations.get(type) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>

50
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts

@ -29,21 +29,23 @@ import {
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { import {
AbstractControl, ControlValueAccessor,
ControlValueAccessor, NG_VALIDATORS, NG_VALIDATORS,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
UntypedFormBuilder, UntypedFormControl, UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup, UntypedFormGroup,
ValidationErrors, Validator, ValidatorFn, Validator,
ValidatorFn,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { import {
IotSvgBehavior, ScadaSymbolBehavior,
IotSvgBehaviorType, ScadaSymbolBehaviorType,
iotSvgBehaviorTypes, scadaSymbolBehaviorTypes,
iotSvgBehaviorTypeTranslations scadaSymbolBehaviorTypeTranslations
} from '@home/components/widget/lib/svg/iot-svg.models'; } from '@home/components/widget/lib/scada/scada-symbol.models';
import { deepClone, isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils'; import { deepClone, isUndefinedOrNull } from '@core/utils';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service'; import { TbPopoverService } from '@shared/components/popover.service';
import { import {
@ -54,17 +56,17 @@ import {
ScadaSymbolBehaviorsComponent ScadaSymbolBehaviorsComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component'; } from '@home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component';
export const behaviorValid = (behavior: IotSvgBehavior): boolean => { export const behaviorValid = (behavior: ScadaSymbolBehavior): boolean => {
if (!behavior.id || !behavior.name || !behavior.type) { if (!behavior.id || !behavior.name || !behavior.type) {
return false; return false;
} }
switch (behavior.type) { switch (behavior.type) {
case IotSvgBehaviorType.value: case ScadaSymbolBehaviorType.value:
if (!behavior.valueType || isUndefinedOrNull(behavior.defaultValue)) { if (!behavior.valueType || isUndefinedOrNull(behavior.defaultValue)) {
return false; return false;
} }
break; break;
case IotSvgBehaviorType.action: case ScadaSymbolBehaviorType.action:
if (!behavior.valueToDataType) { if (!behavior.valueToDataType) {
return false; return false;
} }
@ -77,7 +79,7 @@ export const behaviorValid = (behavior: IotSvgBehavior): boolean => {
return false; return false;
} }
break; break;
case IotSvgBehaviorType.widgetAction: case ScadaSymbolBehaviorType.widgetAction:
break; break;
} }
return true; return true;
@ -109,8 +111,8 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
@ViewChild('editButton') @ViewChild('editButton')
editButton: MatButton; editButton: MatButton;
iotSvgBehaviorTypes = iotSvgBehaviorTypes; scadaSymbolBehaviorTypes = scadaSymbolBehaviorTypes;
iotSvgBehaviorTypeTranslations = iotSvgBehaviorTypeTranslations; scadaSymbolBehaviorTypeTranslations = scadaSymbolBehaviorTypeTranslations;
@Input() @Input()
disabled: boolean; disabled: boolean;
@ -123,7 +125,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
behaviorRowFormGroup: UntypedFormGroup; behaviorRowFormGroup: UntypedFormGroup;
modelValue: IotSvgBehavior; modelValue: ScadaSymbolBehavior;
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};
@ -144,7 +146,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
this.behaviorRowFormGroup.valueChanges.subscribe( this.behaviorRowFormGroup.valueChanges.subscribe(
() => this.updateModel() () => this.updateModel()
); );
this.behaviorRowFormGroup.get('type').valueChanges.subscribe((newType: IotSvgBehaviorType) => { this.behaviorRowFormGroup.get('type').valueChanges.subscribe((newType: ScadaSymbolBehaviorType) => {
this.onTypeChanged(newType); this.onTypeChanged(newType);
}); });
} }
@ -153,7 +155,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
this.propagateChange = fn; this.propagateChange = fn;
} }
registerOnTouched(fn: any): void { registerOnTouched(_fn: any): void {
} }
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
@ -165,7 +167,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
} }
} }
writeValue(value: IotSvgBehavior): void { writeValue(value: ScadaSymbolBehavior): void {
this.modelValue = value; this.modelValue = value;
this.behaviorRowFormGroup.patchValue( this.behaviorRowFormGroup.patchValue(
{ {
@ -225,7 +227,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
this.editBehavior(null, this.editButton, true, onCanceled); this.editBehavior(null, this.editButton, true, onCanceled);
} }
public validate(c: UntypedFormControl) { public validate(_c: UntypedFormControl) {
const idControl = this.behaviorRowFormGroup.get('id'); const idControl = this.behaviorRowFormGroup.get('id');
if (idControl.hasError('behaviorIdNotUnique')) { if (idControl.hasError('behaviorIdNotUnique')) {
idControl.updateValueAndValidity({onlySelf: false, emitEvent: false}); idControl.updateValueAndValidity({onlySelf: false, emitEvent: false});
@ -236,7 +238,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
behaviorIdNotUnique: true behaviorIdNotUnique: true
}; };
} }
const behavior: IotSvgBehavior = {...this.modelValue, ...this.behaviorRowFormGroup.value}; const behavior: ScadaSymbolBehavior = {...this.modelValue, ...this.behaviorRowFormGroup.value};
if (!behaviorValid(behavior)) { if (!behaviorValid(behavior)) {
return { return {
behavior: true behavior: true
@ -261,7 +263,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
}; };
} }
private onTypeChanged(newType: IotSvgBehaviorType) { private onTypeChanged(newType: ScadaSymbolBehaviorType) {
const prevType = this.modelValue.type; const prevType = this.modelValue.type;
this.modelValue = {...this.modelValue, ...{type: newType}}; this.modelValue = {...this.modelValue, ...{type: newType}};
if (!behaviorValid(this.modelValue)) { if (!behaviorValid(this.modelValue)) {
@ -276,7 +278,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
} }
private updateModel() { private updateModel() {
const value: IotSvgBehavior = this.behaviorRowFormGroup.value; const value: ScadaSymbolBehavior = this.behaviorRowFormGroup.value;
this.modelValue = {...this.modelValue, ...value}; this.modelValue = {...this.modelValue, ...value};
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
} }

12
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.ts

@ -35,7 +35,7 @@ import {
UntypedFormGroup, UntypedFormGroup,
Validator Validator
} from '@angular/forms'; } from '@angular/forms';
import { IotSvgBehavior, IotSvgBehaviorType } from '@home/components/widget/lib/svg/iot-svg.models'; import { ScadaSymbolBehavior, ScadaSymbolBehaviorType } from '@home/components/widget/lib/scada/scada-symbol.models';
import { ValueType } from '@shared/models/constants'; import { ValueType } from '@shared/models/constants';
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { import {
@ -94,7 +94,7 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
}); });
this.behaviorsFormGroup.valueChanges.subscribe( this.behaviorsFormGroup.valueChanges.subscribe(
() => { () => {
let behaviors: IotSvgBehavior[] = this.behaviorsFormGroup.get('behaviors').value; let behaviors: ScadaSymbolBehavior[] = this.behaviorsFormGroup.get('behaviors').value;
if (behaviors) { if (behaviors) {
behaviors = behaviors.filter(b => behaviorValid(b)); behaviors = behaviors.filter(b => behaviorValid(b));
} }
@ -119,7 +119,7 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
} }
} }
writeValue(value: IotSvgBehavior[] | undefined): void { writeValue(value: ScadaSymbolBehavior[] | undefined): void {
const behaviors= value || []; const behaviors= value || [];
this.behaviorsFormGroup.setControl('behaviors', this.prepareBehaviorsFormArray(behaviors), {emitEvent: false}); this.behaviorsFormGroup.setControl('behaviors', this.prepareBehaviorsFormArray(behaviors), {emitEvent: false});
} }
@ -176,10 +176,10 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
} }
addBehavior() { addBehavior() {
const behavior: IotSvgBehavior = { const behavior: ScadaSymbolBehavior = {
id: '', id: '',
name: '', name: '',
type: IotSvgBehaviorType.value, type: ScadaSymbolBehaviorType.value,
valueType: ValueType.BOOLEAN, valueType: ValueType.BOOLEAN,
defaultValue: false, defaultValue: false,
valueToDataType: ValueToDataType.CONSTANT, valueToDataType: ValueToDataType.CONSTANT,
@ -197,7 +197,7 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
}); });
} }
private prepareBehaviorsFormArray(behaviors: IotSvgBehavior[] | undefined): UntypedFormArray { private prepareBehaviorsFormArray(behaviors: ScadaSymbolBehavior[] | undefined): UntypedFormArray {
const behaviorsControls: Array<AbstractControl> = []; const behaviorsControls: Array<AbstractControl> = [];
if (behaviors) { if (behaviors) {
behaviors.forEach((behavior) => { behaviors.forEach((behavior) => {

3
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component.ts

@ -27,7 +27,6 @@ import {
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { WidgetService } from '@core/http/widget.service'; import { WidgetService } from '@core/http/widget.service';
import { TranslateService } from '@ngx-translate/core';
import { TbEditorCompleter } from '@shared/models/ace/completion.models'; import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import { TbHighlightRule } from '@shared/models/ace/ace.models'; import { TbHighlightRule } from '@shared/models/ace/ace.models';
import { import {
@ -35,7 +34,7 @@ import {
scadaSymbolClickActionPropertiesHighlightRules, scadaSymbolClickActionPropertiesHighlightRules,
scadaSymbolElementStateRenderHighlightRules, scadaSymbolElementStateRenderHighlightRules,
scadaSymbolElementStateRenderPropertiesHighlightRules scadaSymbolElementStateRenderPropertiesHighlightRules
} from '@home/pages/scada-symbol/scada-symbol.models'; } from '@home/pages/scada-symbol/scada-symbol-editor.models';
import { JsFuncComponent } from '@shared/components/js-func.component'; import { JsFuncComponent } from '@shared/components/js-func.component';
@Component({ @Component({

56
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts

@ -16,14 +16,12 @@
import { import {
Component, Component,
EventEmitter,
forwardRef, forwardRef,
Input, Input,
OnChanges,
OnInit, OnInit,
Output, Renderer2, Renderer2,
SimpleChanges, ViewChild,
ViewChild, ViewContainerRef, ViewContainerRef,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { import {
@ -34,26 +32,12 @@ import {
UntypedFormBuilder, UntypedFormBuilder,
UntypedFormControl, UntypedFormControl,
UntypedFormGroup, UntypedFormGroup,
Validator, Validator
Validators
} from '@angular/forms'; } from '@angular/forms';
import { IotSvgTag } from '@home/components/widget/lib/svg/iot-svg.models'; import { ScadaSymbolTag } from '@home/components/widget/lib/scada/scada-symbol.models';
import { MatExpansionPanel } from '@angular/material/expansion';
import { JsFuncComponent } from '@shared/components/js-func.component';
import { MatSelect } from '@angular/material/select';
import { TbEditorCompleter } from '@shared/models/ace/completion.models'; import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import {
scadaSymbolClickActionHighlightRules,
scadaSymbolClickActionPropertiesHighlightRules,
scadaSymbolElementStateRenderHighlightRules,
scadaSymbolElementStateRenderPropertiesHighlightRules
} from '@home/pages/scada-symbol/scada-symbol.models';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service'; import { TbPopoverService } from '@shared/components/popover.service';
import { deepClone } from '@core/utils';
import {
ScadaSymbolBehaviorPanelComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component';
import { import {
ScadaSymbolMetadataTagFunctionPanelComponent ScadaSymbolMetadataTagFunctionPanelComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component'; } from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component';
@ -95,7 +79,7 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
tagFormGroup: UntypedFormGroup; tagFormGroup: UntypedFormGroup;
modelValue: IotSvgTag; modelValue: ScadaSymbolTag;
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};
@ -129,9 +113,9 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
} }
} }
writeValue(value: IotSvgTag): void { writeValue(value: ScadaSymbolTag): void {
this.modelValue = value; this.modelValue = value;
const clickAction = value?.actions && value?.actions.click ? value.actions.click.actionFunction : null; const clickAction = value?.actions?.click?.actionFunction;
this.tagFormGroup.patchValue( this.tagFormGroup.patchValue(
{ {
tag: value?.tag, tag: value?.tag,
@ -152,20 +136,10 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
editTagStateRenderFunction(): void { editTagStateRenderFunction(): void {
this.openTagFunction('renderFunction', this.editStateRenderFunctionButton); this.openTagFunction('renderFunction', this.editStateRenderFunctionButton);
/*this.openPanelWithCallback(this.expansionPanel, () => {
this.openPanelWithCallback(this.renderFunctionExpansionPanel, () => {
this.stateRenderFunction.focus();
});
});*/
} }
editClickAction(): void { editClickAction(): void {
this.openTagFunction('clickAction', this.editClickActionButton); this.openTagFunction('clickAction', this.editClickActionButton);
/*this.openPanelWithCallback(this.expansionPanel, () => {
this.openPanelWithCallback(this.clickActionExpansionPanel, () => {
this.clickAction.focus();
});
});*/
} }
private openTagFunction(tagFunctionType: 'renderFunction' | 'clickAction', private openTagFunction(tagFunctionType: 'renderFunction' | 'clickAction',
@ -205,20 +179,6 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
} }
} }
/* private openPanelWithCallback(panel: MatExpansionPanel, callback: () => void) {
if (!panel.expanded) {
const s = panel.afterExpand.subscribe(() => {
s.unsubscribe();
setTimeout(() => {
callback();
});
});
panel.open();
} else {
callback();
}
}*/
private updateModel() { private updateModel() {
const value = this.tagFormGroup.value; const value = this.tagFormGroup.value;
this.modelValue = { this.modelValue = {

32
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts

@ -37,13 +37,13 @@ import {
UntypedFormGroup, UntypedFormGroup,
Validator Validator
} from '@angular/forms'; } from '@angular/forms';
import { IotSvgTag } from '@home/components/widget/lib/svg/iot-svg.models'; import { ScadaSymbolTag } from '@home/components/widget/lib/scada/scada-symbol.models';
import { import {
ScadaSymbolMetadataTagComponent ScadaSymbolMetadataTagComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component'; } from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component';
import { TbEditorCompleter } from '@shared/models/ace/completion.models'; import { TbEditorCompleter } from '@shared/models/ace/completion.models';
const tagIsEmpty = (tag: IotSvgTag): boolean => const tagIsEmpty = (tag: ScadaSymbolTag): boolean =>
!tag.stateRenderFunction && !tag.actions?.click?.actionFunction; !tag.stateRenderFunction && !tag.actions?.click?.actionFunction;
@Component({ @Component({
@ -86,7 +86,7 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
tagsFormGroup: UntypedFormGroup; tagsFormGroup: UntypedFormGroup;
private modelValue: IotSvgTag[]; private modelValue: ScadaSymbolTag[];
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};
@ -102,7 +102,7 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
this.tagsFormGroup.valueChanges.subscribe( this.tagsFormGroup.valueChanges.subscribe(
() => { () => {
let value: IotSvgTag[] = this.tagsFormGroup.get('tags').value; let value: ScadaSymbolTag[] = this.tagsFormGroup.get('tags').value;
if (value) { if (value) {
value = value.filter(t => !tagIsEmpty(t)); value = value.filter(t => !tagIsEmpty(t));
} }
@ -147,7 +147,7 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
} }
} }
writeValue(value: IotSvgTag[] | undefined): void { writeValue(value: ScadaSymbolTag[] | undefined): void {
this.modelValue = value || []; this.modelValue = value || [];
const tagsResult= this.setupTags(this.modelValue); const tagsResult= this.setupTags(this.modelValue);
this.tagsFormGroup.setControl('tags', this.prepareTagsFormArray(tagsResult.tags), {emitEvent: false}); this.tagsFormGroup.setControl('tags', this.prepareTagsFormArray(tagsResult.tags), {emitEvent: false});
@ -172,7 +172,7 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
editTagStateRenderFunction(tag: string): void { editTagStateRenderFunction(tag: string): void {
setTimeout(() => { setTimeout(() => {
const tags: IotSvgTag[] = this.tagsFormGroup.get('tags').value; const tags: ScadaSymbolTag[] = this.tagsFormGroup.get('tags').value;
const index = tags.findIndex(t => t.tag === tag); const index = tags.findIndex(t => t.tag === tag);
const tagComponent = this.metadataTags.get(index); const tagComponent = this.metadataTags.get(index);
tagComponent?.editTagStateRenderFunction(); tagComponent?.editTagStateRenderFunction();
@ -181,29 +181,31 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
editTagClickAction(tag: string): void { editTagClickAction(tag: string): void {
setTimeout(() => { setTimeout(() => {
const tags: IotSvgTag[] = this.tagsFormGroup.get('tags').value; const tags: ScadaSymbolTag[] = this.tagsFormGroup.get('tags').value;
const index = tags.findIndex(t => t.tag === tag); const index = tags.findIndex(t => t.tag === tag);
const tagComponent = this.metadataTags.get(index); const tagComponent = this.metadataTags.get(index);
tagComponent?.editClickAction(); tagComponent?.editClickAction();
}); });
} }
private setupTags(existing?: IotSvgTag[]): {tags: IotSvgTag[]; emitEvent: boolean} { private setupTags(existing?: ScadaSymbolTag[]): {tags: ScadaSymbolTag[]; emitEvent: boolean} {
existing = (existing || []).filter(t => !tagIsEmpty(t)); existing = (existing || []).filter(t => !tagIsEmpty(t));
const result = (this.tags || []).sort().map(tag => ({ const result = (this.tags || []).sort().map(tag => ({
tag, tag,
stateRenderFunction: null, stateRenderFunction: null,
actions: { actions: null
click: {
actionFunction: null
}
}
})); }));
for (const tag of existing) { for (const tag of existing) {
const found = result.find(t => t.tag === tag.tag); const found = result.find(t => t.tag === tag.tag);
if (found) { if (found) {
found.stateRenderFunction = tag.stateRenderFunction; found.stateRenderFunction = tag.stateRenderFunction;
found.actions.click.actionFunction = tag.actions?.click?.actionFunction; if (tag.actions?.click?.actionFunction) {
found.actions = {
click: {
actionFunction: tag.actions.click.actionFunction
}
};
}
} }
} }
const tagRemoved = !!existing.find(existingTag => const tagRemoved = !!existing.find(existingTag =>
@ -214,7 +216,7 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
}; };
} }
private prepareTagsFormArray(tags: IotSvgTag[] | undefined): UntypedFormArray { private prepareTagsFormArray(tags: ScadaSymbolTag[] | undefined): UntypedFormArray {
const tagsControls: Array<AbstractControl> = []; const tagsControls: Array<AbstractControl> = [];
if (tags) { if (tags) {
tags.forEach((tag) => { tags.forEach((tag) => {

16
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts

@ -35,7 +35,7 @@ import {
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { emptyMetadata, IotSvgMetadata } from '@home/components/widget/lib/svg/iot-svg.models'; import { emptyMetadata, ScadaSymbolMetadata } from '@home/components/widget/lib/scada/scada-symbol.models';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
@ -48,10 +48,10 @@ import {
clickActionFunctionCompletions, clickActionFunctionCompletions,
elementStateRenderFunctionCompletions, elementStateRenderFunctionCompletions,
generalStateRenderFunctionCompletions, generalStateRenderFunctionCompletions,
iotSvgContextCompletion, scadaSymbolContextCompletion,
scadaSymbolGeneralStateRenderHighlightRules, scadaSymbolGeneralStateRenderHighlightRules,
scadaSymbolGeneralStateRenderPropertiesHighlightRules scadaSymbolGeneralStateRenderPropertiesHighlightRules
} from '@home/pages/scada-symbol/scada-symbol.models'; } from '@home/pages/scada-symbol/scada-symbol-editor.models';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
@Component({ @Component({
@ -83,7 +83,7 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
@Input() @Input()
tags: string[]; tags: string[];
private modelValue: IotSvgMetadata; private modelValue: ScadaSymbolMetadata;
private propagateChange = null; private propagateChange = null;
@ -167,7 +167,7 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
} }
} }
writeValue(value: IotSvgMetadata): void { writeValue(value: ScadaSymbolMetadata): void {
this.modelValue = value; this.modelValue = value;
this.metadataFormGroup.patchValue( this.metadataFormGroup.patchValue(
value, {emitEvent: false} value, {emitEvent: false}
@ -195,14 +195,14 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
} }
private updateModel() { private updateModel() {
const metadata: IotSvgMetadata = this.metadataFormGroup.getRawValue(); const metadata: ScadaSymbolMetadata = this.metadataFormGroup.getRawValue();
this.modelValue = metadata; this.modelValue = metadata;
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
this.updateFunctionCompleters(metadata); this.updateFunctionCompleters(metadata);
} }
private updateFunctionCompleters(metadata: IotSvgMetadata) { private updateFunctionCompleters(metadata: ScadaSymbolMetadata) {
const contextCompleter = iotSvgContextCompletion(metadata, this.tags, this.customTranslate); const contextCompleter = scadaSymbolContextCompletion(metadata, this.tags, this.customTranslate);
const generalStateRender = generalStateRenderFunctionCompletions(contextCompleter); const generalStateRender = generalStateRenderFunctionCompletions(contextCompleter);
if (!this.generalStateRenderFunctionCompleter) { if (!this.generalStateRenderFunctionCompleter) {
this.generalStateRenderFunctionCompleter = new TbEditorCompleter(generalStateRender); this.generalStateRenderFunctionCompleter = new TbEditorCompleter(generalStateRender);

16
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.ts

@ -35,7 +35,7 @@ import {
UntypedFormGroup, UntypedFormGroup,
Validator Validator
} from '@angular/forms'; } from '@angular/forms';
import { IotSvgProperty, IotSvgPropertyType } from '@home/components/widget/lib/svg/iot-svg.models'; import { ScadaSymbolProperty, ScadaSymbolPropertyType } from '@home/components/widget/lib/scada/scada-symbol.models';
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { import {
propertyValid, propertyValid,
@ -94,11 +94,11 @@ export class ScadaSymbolPropertiesComponent implements ControlValueAccessor, OnI
}); });
this.propertiesFormGroup.valueChanges.subscribe( this.propertiesFormGroup.valueChanges.subscribe(
() => { () => {
let properties: IotSvgProperty[] = this.propertiesFormGroup.get('properties').value; let properties: ScadaSymbolProperty[] = this.propertiesFormGroup.get('properties').value;
if (properties) { if (properties) {
properties = properties.filter(p => propertyValid(p)); properties = properties.filter(p => propertyValid(p));
} }
this.booleanPropertyIds = properties.filter(p => p.type === IotSvgPropertyType.switch).map(p => p.id); this.booleanPropertyIds = properties.filter(p => p.type === ScadaSymbolPropertyType.switch).map(p => p.id);
this.propagateChange(properties); this.propagateChange(properties);
} }
); );
@ -120,10 +120,10 @@ export class ScadaSymbolPropertiesComponent implements ControlValueAccessor, OnI
} }
} }
writeValue(value: IotSvgProperty[] | undefined): void { writeValue(value: ScadaSymbolProperty[] | undefined): void {
const properties= value || []; const properties= value || [];
this.propertiesFormGroup.setControl('properties', this.preparePropertiesFormArray(properties), {emitEvent: false}); this.propertiesFormGroup.setControl('properties', this.preparePropertiesFormArray(properties), {emitEvent: false});
this.booleanPropertyIds = properties.filter(p => p.type === IotSvgPropertyType.switch).map(p => p.id); this.booleanPropertyIds = properties.filter(p => p.type === ScadaSymbolPropertyType.switch).map(p => p.id);
} }
public validate(c: UntypedFormControl) { public validate(c: UntypedFormControl) {
@ -178,10 +178,10 @@ export class ScadaSymbolPropertiesComponent implements ControlValueAccessor, OnI
} }
addProperty() { addProperty() {
const property: IotSvgProperty = { const property: ScadaSymbolProperty = {
id: '', id: '',
name: '', name: '',
type: IotSvgPropertyType.text, type: ScadaSymbolPropertyType.text,
default: '' default: ''
}; };
const propertiesArray = this.propertiesFormGroup.get('properties') as UntypedFormArray; const propertiesArray = this.propertiesFormGroup.get('properties') as UntypedFormArray;
@ -195,7 +195,7 @@ export class ScadaSymbolPropertiesComponent implements ControlValueAccessor, OnI
}); });
} }
private preparePropertiesFormArray(properties: IotSvgProperty[] | undefined): UntypedFormArray { private preparePropertiesFormArray(properties: ScadaSymbolProperty[] | undefined): UntypedFormArray {
const propertiesControls: Array<AbstractControl> = []; const propertiesControls: Array<AbstractControl> = [];
if (properties) { if (properties) {
properties.forEach((property) => { properties.forEach((property) => {

28
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.html

@ -34,29 +34,29 @@
<div class="fixed-title-width" translate>scada.property.type</div> <div class="fixed-title-width" translate>scada.property.type</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type"> <mat-select formControlName="type">
<mat-option *ngFor="let type of iotSvgPropertyTypes" [value]="type"> <mat-option *ngFor="let type of scadaSymbolPropertyTypes" [value]="type">
{{ iotSvgPropertyTypeTranslations.get(type) | translate }} {{ scadaSymbolPropertyTypeTranslations.get(type) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div class="fixed-title-width" translate>scada.property.default-value</div> <div class="fixed-title-width" translate>scada.property.default-value</div>
<mat-form-field *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.text" <mat-form-field *ngIf="propertyFormGroup.get('type').value === ScadaSymbolPropertyType.text"
class="flex" class="flex"
appearance="outline" subscriptSizing="dynamic"> appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="default" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="default" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
<tb-color-input *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.color" <tb-color-input *ngIf="propertyFormGroup.get('type').value === ScadaSymbolPropertyType.color"
asBoxInput asBoxInput
colorClearButton colorClearButton
formControlName="default"> formControlName="default">
</tb-color-input> </tb-color-input>
<tb-color-settings *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.color_settings" <tb-color-settings *ngIf="propertyFormGroup.get('type').value === ScadaSymbolPropertyType.color_settings"
formControlName="default" formControlName="default"
settingsKey="{{ propertyFormGroup.get('id').value }}"> settingsKey="{{ propertyFormGroup.get('id').value }}">
</tb-color-settings> </tb-color-settings>
<mat-form-field *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.number" <mat-form-field *ngIf="propertyFormGroup.get('type').value === ScadaSymbolPropertyType.number"
appearance="outline" appearance="outline"
class="number" class="number"
subscriptSizing="dynamic"> subscriptSizing="dynamic">
@ -66,18 +66,18 @@
[step]="propertyFormGroup.get('step').value" [step]="propertyFormGroup.get('step').value"
type="number" placeholder="{{ 'widget-config.set' | translate }}"> type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
<tb-font-settings *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.font" <tb-font-settings *ngIf="propertyFormGroup.get('type').value === ScadaSymbolPropertyType.font"
formControlName="default" formControlName="default"
clearButton clearButton
disabledLineHeight> disabledLineHeight>
</tb-font-settings> </tb-font-settings>
<tb-unit-input *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.units" <tb-unit-input *ngIf="propertyFormGroup.get('type').value === ScadaSymbolPropertyType.units"
formControlName="default"></tb-unit-input> formControlName="default"></tb-unit-input>
<tb-value-input *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.switch" <tb-value-input *ngIf="propertyFormGroup.get('type').value === ScadaSymbolPropertyType.switch"
[valueType]="ValueType.BOOLEAN" [valueType]="ValueType.BOOLEAN"
formControlName="default"></tb-value-input> formControlName="default"></tb-value-input>
</div> </div>
<div *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.number" class="tb-form-row space-between"> <div *ngIf="propertyFormGroup.get('type').value === ScadaSymbolPropertyType.number" class="tb-form-row space-between">
<div translate>scada.property.number-settings</div> <div translate>scada.property.number-settings</div>
<div fxLayout="row" fxFlex fxLayoutAlign="end center" fxLayoutGap="8px"> <div fxLayout="row" fxFlex fxLayoutAlign="end center" fxLayoutGap="8px">
<div class="tb-small-label" translate>scada.property.min</div> <div class="tb-small-label" translate>scada.property.min</div>
@ -124,7 +124,7 @@
{{ 'scada.property.vertical-divider-after' | translate }} {{ 'scada.property.vertical-divider-after' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div *ngIf="[IotSvgPropertyType.number, IotSvgPropertyType.text].includes(propertyFormGroup.get('type').value)" <div *ngIf="[ScadaSymbolPropertyType.number, ScadaSymbolPropertyType.text].includes(propertyFormGroup.get('type').value)"
class="tb-form-row space-between"> class="tb-form-row space-between">
<div class="fixed-title-width" translate>scada.property.input-field-suffix</div> <div class="fixed-title-width" translate>scada.property.input-field-suffix</div>
<mat-form-field class="flex" <mat-form-field class="flex"
@ -146,17 +146,17 @@
<div class="fixed-title-width" translate>scada.property.property-row-classes</div> <div class="fixed-title-width" translate>scada.property.property-row-classes</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="rowClass" multiple placeholder="{{ 'widget-config.set' | translate }}"> <mat-select formControlName="rowClass" multiple placeholder="{{ 'widget-config.set' | translate }}">
<mat-option *ngFor="let clazz of iotSvgPropertyRowClasses" [value]="clazz"> <mat-option *ngFor="let clazz of scadaSymbolPropertyRowClasses" [value]="clazz">
{{ clazz }} {{ clazz }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="propertyFormGroup.get('type').value !== IotSvgPropertyType.switch" class="tb-form-row"> <div *ngIf="propertyFormGroup.get('type').value !== ScadaSymbolPropertyType.switch" class="tb-form-row">
<div class="fixed-title-width" translate>scada.property.property-field-classes</div> <div class="fixed-title-width" translate>scada.property.property-field-classes</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="fieldClass" multiple placeholder="{{ 'widget-config.set' | translate }}"> <mat-select formControlName="fieldClass" multiple placeholder="{{ 'widget-config.set' | translate }}">
<mat-option *ngFor="let clazz of iotSvgPropertyFieldClasses" [value]="clazz"> <mat-option *ngFor="let clazz of scadaSymbolPropertyFieldClasses" [value]="clazz">
{{ clazz }} {{ clazz }}
</mat-option> </mat-option>
</mat-select> </mat-select>

36
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.ts

@ -18,12 +18,13 @@ import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } fro
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { import {
IotSvgProperty, iotSvgPropertyFieldClasses, iotSvgPropertyRowClasses, ScadaSymbolProperty,
IotSvgPropertyType, scadaSymbolPropertyFieldClasses,
iotSvgPropertyTypes, scadaSymbolPropertyRowClasses,
iotSvgPropertyTypeTranslations ScadaSymbolPropertyType,
} from '@home/components/widget/lib/svg/iot-svg.models'; scadaSymbolPropertyTypes,
import { WidgetService } from '@core/http/widget.service'; scadaSymbolPropertyTypeTranslations
} from '@home/components/widget/lib/scada/scada-symbol.models';
import { defaultPropertyValue } from '@home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component'; import { defaultPropertyValue } from '@home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component';
import { ValueType } from '@shared/models/constants'; import { ValueType } from '@shared/models/constants';
@ -37,20 +38,20 @@ export class ScadaSymbolPropertyPanelComponent implements OnInit {
ValueType = ValueType; ValueType = ValueType;
IotSvgPropertyType = IotSvgPropertyType; ScadaSymbolPropertyType = ScadaSymbolPropertyType;
iotSvgPropertyTypes = iotSvgPropertyTypes; scadaSymbolPropertyTypes = scadaSymbolPropertyTypes;
iotSvgPropertyTypeTranslations = iotSvgPropertyTypeTranslations; scadaSymbolPropertyTypeTranslations = scadaSymbolPropertyTypeTranslations;
iotSvgPropertyRowClasses = iotSvgPropertyRowClasses; scadaSymbolPropertyRowClasses = scadaSymbolPropertyRowClasses;
iotSvgPropertyFieldClasses = iotSvgPropertyFieldClasses; scadaSymbolPropertyFieldClasses = scadaSymbolPropertyFieldClasses;
@Input() @Input()
isAdd = false; isAdd = false;
@Input() @Input()
property: IotSvgProperty; property: ScadaSymbolProperty;
@Input() @Input()
booleanPropertyIds: string[]; booleanPropertyIds: string[];
@ -59,16 +60,15 @@ export class ScadaSymbolPropertyPanelComponent implements OnInit {
popover: TbPopoverComponent<ScadaSymbolPropertyPanelComponent>; popover: TbPopoverComponent<ScadaSymbolPropertyPanelComponent>;
@Output() @Output()
propertySettingsApplied = new EventEmitter<IotSvgProperty>(); propertySettingsApplied = new EventEmitter<ScadaSymbolProperty>();
panelTitle: string; panelTitle: string;
propertyFormGroup: UntypedFormGroup; propertyFormGroup: UntypedFormGroup;
private propertyType: IotSvgPropertyType; private propertyType: ScadaSymbolPropertyType;
constructor(private fb: UntypedFormBuilder, constructor(private fb: UntypedFormBuilder) {
private widgetService: WidgetService) {
} }
ngOnInit(): void { ngOnInit(): void {
@ -110,8 +110,8 @@ export class ScadaSymbolPropertyPanelComponent implements OnInit {
} }
private updateValidators() { private updateValidators() {
const type: IotSvgPropertyType = this.propertyFormGroup.get('type').value; const type: ScadaSymbolPropertyType = this.propertyFormGroup.get('type').value;
if (type === IotSvgPropertyType.number) { if (type === ScadaSymbolPropertyType.number) {
this.propertyFormGroup.get('min').enable({emitEvent: false}); this.propertyFormGroup.get('min').enable({emitEvent: false});
this.propertyFormGroup.get('max').enable({emitEvent: false}); this.propertyFormGroup.get('max').enable({emitEvent: false});
this.propertyFormGroup.get('step').enable({emitEvent: false}); this.propertyFormGroup.get('step').enable({emitEvent: false});

4
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.html

@ -24,8 +24,8 @@
</mat-form-field> </mat-form-field>
<mat-form-field class="tb-inline-field tb-type-field fixed-height" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="tb-inline-field tb-type-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type"> <mat-select formControlName="type">
<mat-option *ngFor="let type of iotSvgPropertyTypes" [value]="type"> <mat-option *ngFor="let type of scadaSymbolPropertyTypes" [value]="type">
{{ iotSvgPropertyTypeTranslations.get(type) | translate }} {{ scadaSymbolPropertyTypeTranslations.get(type) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>

44
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.ts

@ -40,11 +40,11 @@ import {
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { import {
IotSvgProperty, ScadaSymbolProperty,
IotSvgPropertyType, ScadaSymbolPropertyType,
iotSvgPropertyTypes, scadaSymbolPropertyTypes,
iotSvgPropertyTypeTranslations scadaSymbolPropertyTypeTranslations
} from '@home/components/widget/lib/svg/iot-svg.models'; } from '@home/components/widget/lib/scada/scada-symbol.models';
import { deepClone } from '@core/utils'; import { deepClone } from '@core/utils';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service'; import { TbPopoverService } from '@shared/components/popover.service';
@ -56,21 +56,21 @@ import {
ScadaSymbolPropertiesComponent ScadaSymbolPropertiesComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-properties.component'; } from '@home/pages/scada-symbol/metadata-components/scada-symbol-properties.component';
export const propertyValid = (property: IotSvgProperty): boolean => !(!property.id || !property.name || !property.type); export const propertyValid = (property: ScadaSymbolProperty): boolean => !(!property.id || !property.name || !property.type);
export const defaultPropertyValue = (type: IotSvgPropertyType): any => { export const defaultPropertyValue = (type: ScadaSymbolPropertyType): any => {
switch (type) { switch (type) {
case IotSvgPropertyType.text: case ScadaSymbolPropertyType.text:
return ''; return '';
case IotSvgPropertyType.number: case ScadaSymbolPropertyType.number:
return 0; return 0;
case IotSvgPropertyType.switch: case ScadaSymbolPropertyType.switch:
return false; return false;
case IotSvgPropertyType.color: case ScadaSymbolPropertyType.color:
return '#000'; return '#000';
case IotSvgPropertyType.color_settings: case ScadaSymbolPropertyType.color_settings:
return constantColor('#000'); return constantColor('#000');
case IotSvgPropertyType.font: case ScadaSymbolPropertyType.font:
return { return {
size: 12, size: 12,
sizeUnit: 'px', sizeUnit: 'px',
@ -79,7 +79,7 @@ export const defaultPropertyValue = (type: IotSvgPropertyType): any => {
style: 'normal', style: 'normal',
lineHeight: '1' lineHeight: '1'
} as Font; } as Font;
case IotSvgPropertyType.units: case ScadaSymbolPropertyType.units:
return ''; return '';
} }
}; };
@ -110,8 +110,8 @@ export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, On
@ViewChild('editButton') @ViewChild('editButton')
editButton: MatButton; editButton: MatButton;
iotSvgPropertyTypes = iotSvgPropertyTypes; scadaSymbolPropertyTypes = scadaSymbolPropertyTypes;
iotSvgPropertyTypeTranslations = iotSvgPropertyTypeTranslations; scadaSymbolPropertyTypeTranslations = scadaSymbolPropertyTypeTranslations;
@Input() @Input()
disabled: boolean; disabled: boolean;
@ -127,7 +127,7 @@ export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, On
propertyRowFormGroup: UntypedFormGroup; propertyRowFormGroup: UntypedFormGroup;
modelValue: IotSvgProperty; modelValue: ScadaSymbolProperty;
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};
@ -148,7 +148,7 @@ export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, On
this.propertyRowFormGroup.valueChanges.subscribe( this.propertyRowFormGroup.valueChanges.subscribe(
() => this.updateModel() () => this.updateModel()
); );
this.propertyRowFormGroup.get('type').valueChanges.subscribe((newType: IotSvgPropertyType) => { this.propertyRowFormGroup.get('type').valueChanges.subscribe((newType: ScadaSymbolPropertyType) => {
this.onTypeChanged(newType); this.onTypeChanged(newType);
}); });
} }
@ -169,7 +169,7 @@ export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, On
} }
} }
writeValue(value: IotSvgProperty): void { writeValue(value: ScadaSymbolProperty): void {
this.modelValue = value; this.modelValue = value;
this.propertyRowFormGroup.patchValue( this.propertyRowFormGroup.patchValue(
{ {
@ -241,7 +241,7 @@ export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, On
propertyIdNotUnique: true propertyIdNotUnique: true
}; };
} }
const property: IotSvgProperty = {...this.modelValue, ...this.propertyRowFormGroup.value}; const property: ScadaSymbolProperty = {...this.modelValue, ...this.propertyRowFormGroup.value};
if (!propertyValid(property)) { if (!propertyValid(property)) {
return { return {
property: true property: true
@ -266,13 +266,13 @@ export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, On
}; };
} }
private onTypeChanged(newType: IotSvgPropertyType) { private onTypeChanged(newType: ScadaSymbolPropertyType) {
this.modelValue = {...this.modelValue, ...{type: newType}}; this.modelValue = {...this.modelValue, ...{type: newType}};
this.modelValue.default = defaultPropertyValue(newType); this.modelValue.default = defaultPropertyValue(newType);
} }
private updateModel() { private updateModel() {
const value: IotSvgProperty = this.propertyRowFormGroup.value; const value: ScadaSymbolProperty = this.propertyRowFormGroup.value;
this.modelValue = {...this.modelValue, ...value}; this.modelValue = {...this.modelValue, ...value};
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
} }

2
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.html

@ -15,4 +15,4 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-scada-symbol-shape" #iotSvgShape></div> <div class="tb-scada-symbol-editor-shape" #scadaSymbolShape></div>

2
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
.tb-scada-symbol-shape { .tb-scada-symbol-editor-shape {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;

14
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts

@ -27,10 +27,10 @@ import {
ViewContainerRef, ViewContainerRef,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { ScadaSymbolEditObject, ScadaSymbolEditObjectCallbacks } from '@home/pages/scada-symbol/scada-symbol.models'; import { ScadaSymbolEditObject, ScadaSymbolEditObjectCallbacks } from '@home/pages/scada-symbol/scada-symbol-editor.models';
export interface ScadaSymbolEditorData { export interface ScadaSymbolEditorData {
svgContent: string; scadaSymbolContent: string;
} }
@Component({ @Component({
@ -41,8 +41,8 @@ export interface ScadaSymbolEditorData {
}) })
export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewInit, OnChanges { export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewInit, OnChanges {
@ViewChild('iotSvgShape', {static: false}) @ViewChild('scadaSymbolShape', {static: false})
iotSvgShape: ElementRef<HTMLElement>; scadaSymbolShape: ElementRef<HTMLElement>;
@Input() @Input()
data: ScadaSymbolEditorData; data: ScadaSymbolEditorData;
@ -59,10 +59,10 @@ export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewI
} }
ngAfterViewInit() { ngAfterViewInit() {
this.scadaSymbolEditObject = new ScadaSymbolEditObject(this.iotSvgShape.nativeElement, this.scadaSymbolEditObject = new ScadaSymbolEditObject(this.scadaSymbolShape.nativeElement,
this.viewContainerRef, this.editObjectCallbacks); this.viewContainerRef, this.editObjectCallbacks);
if (this.data) { if (this.data) {
this.scadaSymbolEditObject.setContent(this.data.svgContent); this.scadaSymbolEditObject.setContent(this.data.scadaSymbolContent);
} }
} }
@ -73,7 +73,7 @@ export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewI
if (propName === 'data') { if (propName === 'data') {
if (this.scadaSymbolEditObject) { if (this.scadaSymbolEditObject) {
setTimeout(() => { setTimeout(() => {
this.scadaSymbolEditObject.setContent(this.data.svgContent); this.scadaSymbolEditObject.setContent(this.data.scadaSymbolContent);
}); });
} }
} }

114
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.models.ts → ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts

@ -25,25 +25,25 @@ import {
setupTagPanelTooltip setupTagPanelTooltip
} from '@home/pages/scada-symbol/scada-symbol-tooltip.components'; } from '@home/pages/scada-symbol/scada-symbol-tooltip.components';
import { import {
IotSvgBehavior, ScadaSymbolBehavior,
IotSvgBehaviorType, ScadaSymbolBehaviorType,
iotSvgContentData, scadaSymbolContentData,
IotSvgMetadata, ScadaSymbolMetadata,
IotSvgProperty, ScadaSymbolProperty,
IotSvgPropertyType ScadaSymbolPropertyType
} from '@home/components/widget/lib/svg/iot-svg.models'; } from '@home/components/widget/lib/scada/scada-symbol.models';
import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models'; import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import { TbHighlightRule } from '@shared/models/ace/ace.models'; import { TbHighlightRule } from '@shared/models/ace/ace.models';
import { ValueType } from '@shared/models/constants';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
import TooltipPositioningSide = JQueryTooltipster.TooltipPositioningSide; import TooltipPositioningSide = JQueryTooltipster.TooltipPositioningSide;
import ITooltipsterHelper = JQueryTooltipster.ITooltipsterHelper; import ITooltipsterHelper = JQueryTooltipster.ITooltipsterHelper;
import ITooltipPosition = JQueryTooltipster.ITooltipPosition; import ITooltipPosition = JQueryTooltipster.ITooltipPosition;
import { ValueType } from '@shared/models/constants';
export interface ScadaSymbolData { export interface ScadaSymbolData {
imageResource: ImageResourceInfo; imageResource: ImageResourceInfo;
svgContent: string; scadaSymbolContent: string;
} }
export interface ScadaSymbolEditObjectCallbacks { export interface ScadaSymbolEditObjectCallbacks {
@ -83,7 +83,7 @@ export class ScadaSymbolEditObject {
this.svgShape.remove(); this.svgShape.remove();
} }
this.scale = 1; this.scale = 1;
const contentData = iotSvgContentData(svgContent); const contentData = scadaSymbolContentData(svgContent);
this.svgRootNodePart = contentData.svgRootNode; this.svgRootNodePart = contentData.svgRootNode;
this.svgShape = SVG().svg(contentData.innerSvg); this.svgShape = SVG().svg(contentData.innerSvg);
this.svgShape.node.style.overflow = 'visible'; this.svgShape.node.style.overflow = 'visible';
@ -153,10 +153,10 @@ export class ScadaSymbolEditObject {
}); });
e.preventDefault(); e.preventDefault();
}); });
this.svgShape.on('panStart', (e) => { this.svgShape.on('panStart', (_e) => {
this.svgShape.node.style.cursor = 'grab'; this.svgShape.node.style.cursor = 'grab';
}); });
this.svgShape.on('panEnd', (e) => { this.svgShape.on('panEnd', (_e) => {
this.svgShape.node.style.cursor = 'default'; this.svgShape.node.style.cursor = 'default';
}); });
} }
@ -258,8 +258,10 @@ export class ScadaSymbolEditObject {
const targetWidth = this.rootElement.getBoundingClientRect().width; const targetWidth = this.rootElement.getBoundingClientRect().width;
const targetHeight = this.rootElement.getBoundingClientRect().height; const targetHeight = this.rootElement.getBoundingClientRect().height;
if (targetWidth && targetHeight) { if (targetWidth && targetHeight) {
const svgAspect = this.box.width / this.box.height;
const shapeAspect = targetWidth / targetHeight;
let scale: number; let scale: number;
if (targetWidth < targetHeight) { if (svgAspect > shapeAspect) {
scale = targetWidth / this.box.width; scale = targetWidth / this.box.width;
} else { } else {
scale = targetHeight / this.box.height; scale = targetHeight / this.box.height;
@ -416,10 +418,10 @@ export class ScadaSymbolElement {
} else { } else {
this.element.addClass('tb-element'); this.element.addClass('tb-element');
} }
this.element.on('mouseenter', (event) => { this.element.on('mouseenter', (_event) => {
this.highlight(); this.highlight();
}); });
this.element.on('mouseleave', (event) => { this.element.on('mouseleave', (_event) => {
this.unhighlight(); this.unhighlight();
}); });
if (this.hasTag()) { if (this.hasTag()) {
@ -588,7 +590,7 @@ export class ScadaSymbolElement {
zIndex: 100, zIndex: 100,
arrow: this.isGroup(), arrow: this.isGroup(),
distance: this.isGroup() ? (this.scaled(groupRectPadding) + groupRectStroke) : 6, distance: this.isGroup() ? (this.scaled(groupRectPadding) + groupRectStroke) : 6,
theme: ['iot-svg'], theme: ['scada-symbol'],
delay: 0, delay: 0,
animationDuration: 0, animationDuration: 0,
interactive: true, interactive: true,
@ -598,7 +600,7 @@ export class ScadaSymbolElement {
content: '', content: '',
functionPosition: (instance, helper, position) => functionPosition: (instance, helper, position) =>
this.innerTagTooltipPosition(instance, helper, position), this.innerTagTooltipPosition(instance, helper, position),
functionReady: (instance, helper) => { functionReady: (_instance, helper) => {
const tooltipEl = $(helper.tooltip); const tooltipEl = $(helper.tooltip);
tooltipEl.on('mouseenter', () => { tooltipEl.on('mouseenter', () => {
this.highlight(); this.highlight();
@ -623,7 +625,7 @@ export class ScadaSymbolElement {
{ {
zIndex: 100, zIndex: 100,
arrow: true, arrow: true,
theme: ['iot-svg', 'tb-active'], theme: ['scada-symbol', 'tb-active'],
delay: [0, 300], delay: [0, 300],
interactive: true, interactive: true,
trigger: 'hover', trigger: 'hover',
@ -645,7 +647,7 @@ export class ScadaSymbolElement {
}, },
functionPosition: (instance, helper, position) => functionPosition: (instance, helper, position) =>
this.innerAddTagTooltipPosition(instance, helper, position), this.innerAddTagTooltipPosition(instance, helper, position),
functionReady: (instance, helper) => { functionReady: (_instance, helper) => {
const tooltipEl = $(helper.tooltip); const tooltipEl = $(helper.tooltip);
tooltipEl.on('mouseenter', () => { tooltipEl.on('mouseenter', () => {
this.highlight(); this.highlight();
@ -664,7 +666,7 @@ export class ScadaSymbolElement {
setupAddTagPanelTooltip(this, this.editObject.viewContainerRef); setupAddTagPanelTooltip(this, this.editObject.viewContainerRef);
} }
private innerTagTooltipPosition(instance: ITooltipsterInstance, helper: ITooltipsterHelper, private innerTagTooltipPosition(_instance: ITooltipsterInstance, helper: ITooltipsterHelper,
position: ITooltipPosition): ITooltipPosition { position: ITooltipPosition): ITooltipPosition {
const clientRect = helper.origin.getBoundingClientRect(); const clientRect = helper.origin.getBoundingClientRect();
if (!this.isGroup()) { if (!this.isGroup()) {
@ -678,8 +680,8 @@ export class ScadaSymbolElement {
return position; return position;
} }
private innerAddTagTooltipPosition(instance: ITooltipsterInstance, private innerAddTagTooltipPosition(_instance: ITooltipsterInstance,
helper: ITooltipsterHelper, position: ITooltipPosition): ITooltipPosition { _helper: ITooltipsterHelper, position: ITooltipPosition): ITooltipPosition {
const distance = 10; const distance = 10;
switch (position.side) { switch (position.side) {
case 'right': case 'right':
@ -803,61 +805,57 @@ const scadaSymbolCtxPropertyHighlightRules: TbHighlightRule[] = [
}, },
{ {
class: 'scada-symbol-ctx-property', class: 'scada-symbol-ctx-property',
regex: /(?<=ctx\.properties\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ regex: /(?<=ctx\.properties\.)([a-zA-Z$_\u00a1-\uffff][a-zA-Z\d$_\u00a1-\uffff]*)\b/
}, },
{ {
class: 'scada-symbol-ctx-tag', class: 'scada-symbol-ctx-tag',
regex: /(?<=ctx\.tags\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ regex: /(?<=ctx\.tags\.)([a-zA-Z$_\u00a1-\uffff][a-zA-Z\d$_\u00a1-\uffff]*)\b/
}, },
{ {
class: 'scada-symbol-ctx-value', class: 'scada-symbol-ctx-value',
regex: /(?<=ctx\.values\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ regex: /(?<=ctx\.values\.)([a-zA-Z$_\u00a1-\uffff][a-zA-Z\d$_\u00a1-\uffff]*)\b/
}, },
{ {
class: 'scada-symbol-ctx-api-method', class: 'scada-symbol-ctx-api-method',
regex: /(?<=ctx\.api\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ regex: /(?<=ctx\.api\.)([a-zA-Z$_\u00a1-\uffff][a-zA-Z\d$_\u00a1-\uffff]*)\b/
} }
]; ];
export const scadaSymbolGeneralStateRenderPropertiesHighlightRules: TbHighlightRule[] = export const scadaSymbolGeneralStateRenderPropertiesHighlightRules: TbHighlightRule[] =
scadaSymbolCtxPropertyHighlightRules.concat({ scadaSymbolCtxPropertyHighlightRules.concat({
class: 'scada-symbol-svg-properties', class: 'scada-symbol-svg-properties',
regex: /(?<=svg\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ regex: /(?<=svg\.)([a-zA-Z$_\u00a1-\uffff][a-zA-Z\d$_\u00a1-\uffff]*)\b/
}); });
export const scadaSymbolElementStateRenderPropertiesHighlightRules: TbHighlightRule[] = export const scadaSymbolElementStateRenderPropertiesHighlightRules: TbHighlightRule[] =
scadaSymbolCtxPropertyHighlightRules.concat({ scadaSymbolCtxPropertyHighlightRules.concat({
class: 'scada-symbol-element-properties', class: 'scada-symbol-element-properties',
regex: /(?<=element\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ regex: /(?<=element\.)([a-zA-Z$_\u00a1-\uffff][a-zA-Z\d$_\u00a1-\uffff]*)\b/
}); });
export const scadaSymbolClickActionPropertiesHighlightRules: TbHighlightRule[] = export const scadaSymbolClickActionPropertiesHighlightRules: TbHighlightRule[] =
scadaSymbolElementStateRenderPropertiesHighlightRules.concat({ scadaSymbolElementStateRenderPropertiesHighlightRules.concat({
class: 'scada-symbol-event-properties', class: 'scada-symbol-event-properties',
regex: /(?<=event\.)([a-zA-Z\$_\u00a1-\uffff][a-zA-Z\d\$_\u00a1-\uffff]*)\b/ regex: /(?<=event\.)([a-zA-Z$_\u00a1-\uffff][a-zA-Z\d$_\u00a1-\uffff]*)\b/
}); });
export const generalStateRenderFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => { export const generalStateRenderFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => ({
return {
ctx: ctxCompletion, ctx: ctxCompletion,
svg: { svg: {
meta: 'argument', meta: 'argument',
type: 'Svg', type: 'Svg',
description: 'A root svg node. Instance of SVG.Svg.' description: 'A root svg node. Instance of SVG.Svg.'
} }
}; });
};
export const elementStateRenderFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => { export const elementStateRenderFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => ({
return {
ctx: ctxCompletion, ctx: ctxCompletion,
element: { element: {
meta: 'argument', meta: 'argument',
type: 'Element', type: 'Element',
description: 'An SVG element.' description: 'An SVG element.'
}, },
}; });
};
export const clickActionFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => { export const clickActionFunctionCompletions = (ctxCompletion: TbEditorCompletion): TbEditorCompletions => {
const completions = elementStateRenderFunctionCompletions(ctxCompletion); const completions = elementStateRenderFunctionCompletions(ctxCompletion);
@ -869,8 +867,8 @@ export const clickActionFunctionCompletions = (ctxCompletion: TbEditorCompletion
return completions; return completions;
}; };
export const iotSvgContextCompletion = (metadata: IotSvgMetadata, tags: string[], export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags: string[],
customTranslate: CustomTranslatePipe): TbEditorCompletion => { customTranslate: CustomTranslatePipe): TbEditorCompletion => {
const properties: TbEditorCompletion = { const properties: TbEditorCompletion = {
meta: 'object', meta: 'object',
type: 'object', type: 'object',
@ -878,7 +876,7 @@ export const iotSvgContextCompletion = (metadata: IotSvgMetadata, tags: string[]
children: {} children: {}
}; };
for (const property of metadata.properties) { for (const property of metadata.properties) {
properties.children[property.id] = iotSvgPropertyCompletion(property, customTranslate); properties.children[property.id] = scadaSymbolPropertyCompletion(property, customTranslate);
} }
const values: TbEditorCompletion = { const values: TbEditorCompletion = {
meta: 'object', meta: 'object',
@ -886,9 +884,9 @@ export const iotSvgContextCompletion = (metadata: IotSvgMetadata, tags: string[]
description: 'An object holding all values obtained using behaviors of type \'Value\'', description: 'An object holding all values obtained using behaviors of type \'Value\'',
children: {} children: {}
}; };
const getValues = metadata.behavior.filter(b => b.type === IotSvgBehaviorType.value); const getValues = metadata.behavior.filter(b => b.type === ScadaSymbolBehaviorType.value);
for (const value of getValues) { for (const value of getValues) {
values.children[value.id] = iotSvgValueCompletion(value, customTranslate); values.children[value.id] = scadaSymbolValueCompletion(value, customTranslate);
} }
const tagsCompletions: TbEditorCompletion = { const tagsCompletions: TbEditorCompletion = {
meta: 'object', meta: 'object',
@ -907,12 +905,12 @@ export const iotSvgContextCompletion = (metadata: IotSvgMetadata, tags: string[]
} }
return { return {
meta: 'argument', meta: 'argument',
type: 'IotSvgContext', type: 'ScadaSymbolContext',
description: 'Context of svg object.', description: 'Context of svg object.',
children: { children: {
api: { api: {
meta: 'object', meta: 'object',
type: 'IotSvgApi', type: 'ScadaSymbolApi',
description: 'Svg object API', description: 'Svg object API',
children: { children: {
animate: { animate: {
@ -926,7 +924,7 @@ export const iotSvgContextCompletion = (metadata: IotSvgMetadata, tags: string[]
}, },
{ {
name: 'duration', name: 'duration',
description: 'Animation duretion is milliseconds', description: 'Animation duration in milliseconds',
type: 'number' type: 'number'
} }
], ],
@ -944,7 +942,7 @@ export const iotSvgContextCompletion = (metadata: IotSvgMetadata, tags: string[]
}; };
}; };
const iotSvgPropertyCompletion = (property: IotSvgProperty, customTranslate: CustomTranslatePipe): TbEditorCompletion => { const scadaSymbolPropertyCompletion = (property: ScadaSymbolProperty, customTranslate: CustomTranslatePipe): TbEditorCompletion => {
let description = customTranslate.transform(property.name, property.name); let description = customTranslate.transform(property.name, property.name);
if (property.subLabel) { if (property.subLabel) {
description += ` <small>${customTranslate.transform(property.subLabel, property.subLabel)}</small>`; description += ` <small>${customTranslate.transform(property.subLabel, property.subLabel)}</small>`;
@ -952,39 +950,39 @@ const iotSvgPropertyCompletion = (property: IotSvgProperty, customTranslate: Cus
return { return {
meta: 'property', meta: 'property',
description, description,
type: iotSvgPropertyCompletionType(property.type) type: scadaSymbolPropertyCompletionType(property.type)
}; };
}; };
const iotSvgValueCompletion = (value: IotSvgBehavior, customTranslate: CustomTranslatePipe): TbEditorCompletion => { const scadaSymbolValueCompletion = (value: ScadaSymbolBehavior, customTranslate: CustomTranslatePipe): TbEditorCompletion => {
const description = customTranslate.transform(value.name, value.name); const description = customTranslate.transform(value.name, value.name);
return { return {
meta: 'property', meta: 'property',
description, description,
type: iotSvgValueCompletionType(value.valueType) type: scadaSymbolValueCompletionType(value.valueType)
}; };
}; };
const iotSvgPropertyCompletionType = (type: IotSvgPropertyType): string => { const scadaSymbolPropertyCompletionType = (type: ScadaSymbolPropertyType): string => {
switch (type) { switch (type) {
case IotSvgPropertyType.text: case ScadaSymbolPropertyType.text:
return 'string'; return 'string';
case IotSvgPropertyType.number: case ScadaSymbolPropertyType.number:
return 'number'; return 'number';
case IotSvgPropertyType.switch: case ScadaSymbolPropertyType.switch:
return 'boolean'; return 'boolean';
case IotSvgPropertyType.color: case ScadaSymbolPropertyType.color:
return 'color string'; return 'color string';
case IotSvgPropertyType.color_settings: case ScadaSymbolPropertyType.color_settings:
return 'ColorProcessor'; return 'ColorProcessor';
case IotSvgPropertyType.font: case ScadaSymbolPropertyType.font:
return 'Font'; return 'Font';
case IotSvgPropertyType.units: case ScadaSymbolPropertyType.units:
return 'units string'; return 'units string';
} }
}; };
const iotSvgValueCompletionType = (type: ValueType): string => { const scadaSymbolValueCompletionType = (type: ValueType): string => {
switch (type) { switch (type) {
case ValueType.STRING: case ValueType.STRING:
return 'string'; return 'string';

6
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts

@ -30,7 +30,7 @@ import {
ViewContainerRef, ViewContainerRef,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { ScadaSymbolElement } from '@home/pages/scada-symbol/scada-symbol.models'; import { ScadaSymbolElement } from '@home/pages/scada-symbol/scada-symbol-editor.models';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module'; import { SharedModule } from '@shared/shared.module';
import { ENTER } from '@angular/cdk/keycodes'; import { ENTER } from '@angular/cdk/keycodes';
@ -234,7 +234,7 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements
{ {
zIndex: 200, zIndex: 200,
arrow: true, arrow: true,
theme: ['iot-svg', 'tb-active'], theme: ['scada-symbol', 'tb-active'],
interactive: true, interactive: true,
trigger: 'click', trigger: 'click',
side: 'top', side: 'top',
@ -254,7 +254,7 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements
{ {
zIndex: 200, zIndex: 200,
arrow: true, arrow: true,
theme: ['iot-svg', 'tb-active'], theme: ['scada-symbol', 'tb-active'],
interactive: true, interactive: true,
trigger: 'click', trigger: 'click',
side: 'top', side: 'top',

31
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html

@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-scada-symbol mat-content" fxLayout="column"> <div class="tb-scada-symbol-editor mat-content" fxLayout="column">
<section class="tb-scada-symbol-container" fxFlex fxLayout="column"> <section class="tb-scada-symbol-editor-container" fxFlex fxLayout="column">
<div class="tb-scada-symbol-layout" fxFlex fxLayout="row"> <div class="tb-scada-symbol-editor-layout" fxFlex fxLayout="row">
<mat-drawer-container style="width: 100%; height: 100%;"> <mat-drawer-container style="width: 100%; height: 100%;">
<mat-drawer class="tb-scada-symbol-details-drawer mat-elevation-z4" <mat-drawer class="tb-scada-symbol-editor-details-drawer mat-elevation-z4"
disableClose="true" disableClose="true"
mode="side" mode="side"
opened opened
@ -29,8 +29,8 @@
[showCloseDetails]="false" [showCloseDetails]="false"
headerHeightPx="64" headerHeightPx="64"
headerTitle="{{symbolData?.imageResource?.title}}"> headerTitle="{{symbolData?.imageResource?.title}}">
<div *ngIf="previewMode" class="tb-scada-symbol-preview-content tb-absolute-fill"> <div *ngIf="previewMode" class="tb-scada-symbol-editor-preview-content tb-absolute-fill">
<div class="tb-scada-symbol-preview-header"> <div class="tb-scada-symbol-editor-preview-header">
<button mat-button <button mat-button
(click)="exitPreviewMode()"> (click)="exitPreviewMode()">
<mat-icon>chevron_left</mat-icon> <mat-icon>chevron_left</mat-icon>
@ -50,15 +50,22 @@
{{ 'action.apply' | translate }} {{ 'action.apply' | translate }}
</button> </button>
</div> </div>
<div [formGroup]="scadaPreviewFormGroup" class="tb-scada-symbol-preview-settings"> <div [formGroup]="scadaPreviewFormGroup" class="tb-scada-symbol-editor-preview-settings">
<div class="mat-content"> <div class="mat-content">
<tb-iot-svg-object-settings <div class="tb-form-panel">
formControlName="iotSvgObject" <div class="tb-form-panel-title" translate>scada.symbol</div>
[svgContent]="symbolData.svgContent" <tb-scada-symbol-input
[scadaSymbolContent]="symbolData.scadaSymbolContent"
[scadaSymbolMetadata]="previewMetadata">
</tb-scada-symbol-input>
</div>
<tb-scada-symbol-object-settings
formControlName="scadaSymbolObjectSettings"
[scadaSymbolMetadata]="previewMetadata"
[aliasController]="aliasController" [aliasController]="aliasController"
[widgetType]="widgetType.rpc" [widgetType]="widgetType.rpc"
[callbacks]="widgetActionCallbacks"> [callbacks]="widgetActionCallbacks">
</tb-iot-svg-object-settings> </tb-scada-symbol-object-settings>
</div> </div>
</div> </div>
</div> </div>
@ -103,7 +110,7 @@
</div> </div>
</tb-details-panel> </tb-details-panel>
</mat-drawer> </mat-drawer>
<mat-drawer-content class="tb-scada-symbol-content"> <mat-drawer-content class="tb-scada-symbol-editor-content">
<tb-scada-symbol-editor *ngIf="!previewMode" #symbolEditor <tb-scada-symbol-editor *ngIf="!previewMode" #symbolEditor
[data]="symbolEditorData" [data]="symbolEditorData"
[editObjectCallbacks]="editObjectCallbacks"></tb-scada-symbol-editor> [editObjectCallbacks]="editObjectCallbacks"></tb-scada-symbol-editor>

16
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.scss

@ -16,16 +16,16 @@
@import '../../../../../scss/constants'; @import '../../../../../scss/constants';
.tb-scada-symbol { .tb-scada-symbol-editor {
width: 100%; width: 100%;
height: 100%; height: 100%;
.tb-scada-symbol-details-drawer { .tb-scada-symbol-editor-details-drawer {
width: 50%; width: 50%;
.tb-scada-symbol-preview-content { .tb-scada-symbol-editor-preview-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
.tb-scada-symbol-preview-header { .tb-scada-symbol-editor-preview-header {
padding: 24px 24px 0; padding: 24px 24px 0;
display: flex; display: flex;
gap: 12px; gap: 12px;
@ -33,7 +33,7 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
} }
.tb-scada-symbol-preview-settings { .tb-scada-symbol-editor-preview-settings {
& > .mat-content { & > .mat-content {
padding-top: 8px; padding-top: 8px;
@media #{$mat-xs} { @media #{$mat-xs} {
@ -57,13 +57,11 @@
} }
} }
} }
.tb-scada-symbol-content { .tb-scada-symbol-editor-content {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
min-height: 0; min-height: 0;
max-width: 50%;
background: #fff; background: #fff;
.tb-iot-svg-panel {
padding: 0;
}
} }
} }

72
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts

@ -28,15 +28,16 @@ import {
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { map, switchMap, takeUntil } from 'rxjs/operators'; import { map, switchMap, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { ScadaSymbolData, ScadaSymbolEditObjectCallbacks } from '@home/pages/scada-symbol/scada-symbol.models'; import { ScadaSymbolData, ScadaSymbolEditObjectCallbacks } from '@home/pages/scada-symbol/scada-symbol-editor.models';
import { import {
IotSvgMetadata, IotSvgObjectSettings, parseScadaSymbolMetadataFromContent,
parseIotSvgMetadataFromContent, ScadaSymbolMetadata,
updateIotSvgMetadataInContent ScadaSymbolObjectSettings,
} from '@home/components/widget/lib/svg/iot-svg.models'; updateScadaSymbolMetadataInContent
} from '@home/components/widget/lib/scada/scada-symbol.models';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { createFileFromContent, deepClone } from '@core/utils'; import { createFileFromContent, deepClone } from '@core/utils';
import { import {
@ -55,9 +56,9 @@ import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Widget, widgetType } from '@shared/models/widget.models'; import { Widget, widgetType } from '@shared/models/widget.models';
import { import {
iotSvgWidgetDefaultSettings, scadaSymbolWidgetDefaultSettings,
IotSvgWidgetSettings ScadaSymbolWidgetSettings
} from '@home/components/widget/lib/svg/iot-svg-widget.models'; } from '@home/components/widget/lib/scada/scada-symbol-widget.models';
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models'; import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
import { import {
ScadaSymbolMetadataComponent ScadaSymbolMetadataComponent
@ -87,6 +88,7 @@ export class ScadaSymbolComponent extends PageComponent
symbolEditorData: ScadaSymbolEditorData; symbolEditorData: ScadaSymbolEditorData;
previewMode = false; previewMode = false;
previewMetadata: ScadaSymbolMetadata;
scadaSymbolFormGroup: UntypedFormGroup; scadaSymbolFormGroup: UntypedFormGroup;
@ -113,7 +115,7 @@ export class ScadaSymbolComponent extends PageComponent
symbolEditorDirty = false; symbolEditorDirty = false;
private previewIotSvgObjectSettings: IotSvgObjectSettings; private previewScadaSymbolObjectSettings: ScadaSymbolObjectSettings;
private previewWidget: Widget; private previewWidget: Widget;
@ -128,7 +130,6 @@ export class ScadaSymbolComponent extends PageComponent
} }
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private fb: UntypedFormBuilder, private fb: UntypedFormBuilder,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef,
@ -144,7 +145,7 @@ export class ScadaSymbolComponent extends PageComponent
metadata: [null] metadata: [null]
}); });
this.scadaPreviewFormGroup = this.fb.group({ this.scadaPreviewFormGroup = this.fb.group({
iotSvgObject: [null] scadaSymbolObjectSettings: [null]
}); });
const entitiAliases: EntityAliases = {}; const entitiAliases: EntityAliases = {};
@ -182,13 +183,13 @@ export class ScadaSymbolComponent extends PageComponent
onApplyScadaSymbolConfig() { onApplyScadaSymbolConfig() {
if (this.scadaSymbolFormGroup.valid) { if (this.scadaSymbolFormGroup.valid) {
const svgContent = this.prepareSvgContent(); const metadata: ScadaSymbolMetadata = this.scadaSymbolFormGroup.get('metadata').value;
const file = createFileFromContent(svgContent, this.symbolData.imageResource.fileName, const scadaSymbolContent = this.prepareScadaSymbolContent(metadata);
const file = createFileFromContent(scadaSymbolContent, this.symbolData.imageResource.fileName,
this.symbolData.imageResource.descriptor.mediaType); this.symbolData.imageResource.descriptor.mediaType);
const type = imageResourceType(this.symbolData.imageResource); const type = imageResourceType(this.symbolData.imageResource);
let imageInfoObservable = let imageInfoObservable =
this.imageService.updateImage(type, this.symbolData.imageResource.resourceKey, file); this.imageService.updateImage(type, this.symbolData.imageResource.resourceKey, file);
const metadata: IotSvgMetadata = this.scadaSymbolFormGroup.get('metadata').value;
if (metadata.title !== this.symbolData.imageResource.title) { if (metadata.title !== this.symbolData.imageResource.title) {
imageInfoObservable = imageInfoObservable.pipe( imageInfoObservable = imageInfoObservable.pipe(
switchMap(imageInfo => { switchMap(imageInfo => {
@ -202,7 +203,7 @@ export class ScadaSymbolComponent extends PageComponent
`${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(imageInfo.resourceKey)}`).pipe( `${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(imageInfo.resourceKey)}`).pipe(
map(content => ({ map(content => ({
imageResource: imageInfo, imageResource: imageInfo,
svgContent: content scadaSymbolContent: content
})) }))
)) ))
).subscribe(data => { ).subscribe(data => {
@ -217,25 +218,27 @@ export class ScadaSymbolComponent extends PageComponent
} }
enterPreviewMode() { enterPreviewMode() {
this.symbolData.svgContent = this.prepareSvgContent(); this.previewMetadata = this.scadaSymbolFormGroup.get('metadata').value;
this.previewIotSvgObjectSettings = { this.symbolData.scadaSymbolContent = this.prepareScadaSymbolContent(this.previewMetadata);
this.previewScadaSymbolObjectSettings = {
behavior: {}, behavior: {},
properties: {} properties: {}
}; };
this.scadaPreviewFormGroup.patchValue({ this.scadaPreviewFormGroup.patchValue({
iotSvgObject: this.previewIotSvgObjectSettings scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings
}, {emitEvent: false}); }, {emitEvent: false});
this.scadaPreviewFormGroup.markAsPristine(); this.scadaPreviewFormGroup.markAsPristine();
const settings: IotSvgWidgetSettings = {...iotSvgWidgetDefaultSettings, const settings: ScadaSymbolWidgetSettings = {...scadaSymbolWidgetDefaultSettings,
...{ ...{
simulated: true, simulated: true,
iotSvg: null, scadaSymbolUrl: null,
iotSvgContent: this.symbolData.svgContent, scadaSymbolContent: this.symbolData.scadaSymbolContent,
iotSvgObject: this.previewIotSvgObjectSettings scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings,
padding: '0'
} }
}; };
this.previewWidget = { this.previewWidget = {
typeFullFqn: 'system.iot_svg', typeFullFqn: 'system.scada_symbol',
type: widgetType.rpc, type: widgetType.rpc,
sizeX: 24, sizeX: 24,
sizeY: 24, sizeY: 24,
@ -255,21 +258,21 @@ export class ScadaSymbolComponent extends PageComponent
exitPreviewMode() { exitPreviewMode() {
this.symbolEditorData = { this.symbolEditorData = {
svgContent: this.symbolData.svgContent scadaSymbolContent: this.symbolData.scadaSymbolContent
}; };
this.previewMode = false; this.previewMode = false;
} }
onRevertPreviewSettings() { onRevertPreviewSettings() {
this.scadaPreviewFormGroup.patchValue({ this.scadaPreviewFormGroup.patchValue({
iotSvgObject: this.previewIotSvgObjectSettings scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings
}, {emitEvent: false}); }, {emitEvent: false});
this.scadaPreviewFormGroup.markAsPristine(); this.scadaPreviewFormGroup.markAsPristine();
} }
onApplyPreviewSettings() { onApplyPreviewSettings() {
this.scadaPreviewFormGroup.markAsPristine(); this.scadaPreviewFormGroup.markAsPristine();
this.previewIotSvgObjectSettings = this.scadaPreviewFormGroup.get('iotSvgObject').value; this.previewScadaSymbolObjectSettings = this.scadaPreviewFormGroup.get('scadaSymbolObjectSettings').value;
this.updatePreviewWidgetSettings(); this.updatePreviewWidgetSettings();
} }
@ -278,7 +281,7 @@ export class ScadaSymbolComponent extends PageComponent
} }
tagHasStateRenderFunction(tag: string): boolean { tagHasStateRenderFunction(tag: string): boolean {
const metadata: IotSvgMetadata = this.scadaSymbolFormGroup.get('metadata').value; const metadata: ScadaSymbolMetadata = this.scadaSymbolFormGroup.get('metadata').value;
if (metadata.tags) { if (metadata.tags) {
const found = metadata.tags.find(t => t.tag === tag); const found = metadata.tags.find(t => t.tag === tag);
return !!found?.stateRenderFunction; return !!found?.stateRenderFunction;
@ -287,7 +290,7 @@ export class ScadaSymbolComponent extends PageComponent
} }
tagHasClickAction(tag: string): boolean { tagHasClickAction(tag: string): boolean {
const metadata: IotSvgMetadata = this.scadaSymbolFormGroup.get('metadata').value; const metadata: ScadaSymbolMetadata = this.scadaSymbolFormGroup.get('metadata').value;
if (metadata.tags) { if (metadata.tags) {
const found = metadata.tags.find(t => t.tag === tag); const found = metadata.tags.find(t => t.tag === tag);
return !!found?.actions?.click?.actionFunction; return !!found?.actions?.click?.actionFunction;
@ -309,14 +312,13 @@ export class ScadaSymbolComponent extends PageComponent
private updatePreviewWidgetSettings() { private updatePreviewWidgetSettings() {
this.previewWidget = deepClone(this.previewWidget); this.previewWidget = deepClone(this.previewWidget);
this.previewWidget.config.settings.iotSvgObject = this.previewIotSvgObjectSettings; this.previewWidget.config.settings.scadaSymbolObjectSettings = this.previewScadaSymbolObjectSettings;
this.previewWidgets = [this.previewWidget]; this.previewWidgets = [this.previewWidget];
} }
private prepareSvgContent(): string { private prepareScadaSymbolContent(metadata: ScadaSymbolMetadata): string {
const svgContent = this.symbolEditor.getContent(); const svgContent = this.symbolEditor.getContent();
const metadata: IotSvgMetadata = this.scadaSymbolFormGroup.get('metadata').value; return updateScadaSymbolMetadataInContent(svgContent, metadata);
return updateIotSvgMetadataInContent(svgContent, metadata);
} }
private reset(): void { private reset(): void {
@ -330,9 +332,9 @@ export class ScadaSymbolComponent extends PageComponent
this.origSymbolData = data; this.origSymbolData = data;
this.symbolData = deepClone(data); this.symbolData = deepClone(data);
this.symbolEditorData = { this.symbolEditorData = {
svgContent: this.symbolData.svgContent scadaSymbolContent: this.symbolData.scadaSymbolContent
}; };
const metadata = parseIotSvgMetadataFromContent(this.symbolData.svgContent); const metadata = parseScadaSymbolMetadataFromContent(this.symbolData.scadaSymbolContent);
this.scadaSymbolFormGroup.patchValue({ this.scadaSymbolFormGroup.patchValue({
metadata metadata
}, {emitEvent: false}); }, {emitEvent: false});

33
ui-ngx/src/app/shared/components/image/gallery-image-input.component.ts

@ -124,21 +124,26 @@ export class GalleryImageInputComponent extends PageComponent implements OnInit,
this.detectLinkType(); this.detectLinkType();
if (this.linkType === ImageLinkType.resource) { if (this.linkType === ImageLinkType.resource) {
const params = extractParamsFromImageResourceUrl(this.imageUrl); const params = extractParamsFromImageResourceUrl(this.imageUrl);
this.loadingImageResource = true; if (params) {
this.imageService.getImageInfo(params.type, params.key, {ignoreLoading: true, ignoreErrors: true}).subscribe( this.loadingImageResource = true;
{ this.imageService.getImageInfo(params.type, params.key, {ignoreLoading: true, ignoreErrors: true}).subscribe(
next: (res) => { {
this.imageResource = res; next: (res) => {
this.loadingImageResource = false; this.imageResource = res;
this.cd.markForCheck(); this.loadingImageResource = false;
}, this.cd.markForCheck();
error: () => { },
this.reset(); error: () => {
this.loadingImageResource = false; this.reset();
this.cd.markForCheck(); this.loadingImageResource = false;
this.cd.markForCheck();
}
} }
} );
); } else {
this.reset();
this.cd.markForCheck();
}
} else if (this.linkType === ImageLinkType.base64) { } else if (this.linkType === ImageLinkType.base64) {
this.cd.markForCheck(); this.cd.markForCheck();
} else if (this.linkType === ImageLinkType.external) { } else if (this.linkType === ImageLinkType.external) {

2
ui-ngx/src/app/shared/components/image/image-gallery.component.ts

@ -178,7 +178,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
actionColumnWidth = '240px'; actionColumnWidth = '240px';
get isScada() { get isScada() {
return this.imageSubType === ResourceSubType.IOT_SVG; return this.imageSubType === ResourceSubType.SCADA_SYMBOL;
} }
private updateDataSubscription: Subscription; private updateDataSubscription: Subscription;

65
ui-ngx/src/app/shared/components/image/scada-symbol-input.component.html

@ -0,0 +1,65 @@
<!--
Copyright © 2016-2024 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 class="tb-container">
<label class="tb-title" *ngIf="label" [class]="{'tb-error': !disabled && (required && linkType === ScadaSymbolLinkType.none), 'tb-required': !disabled && required}">{{label}}</label>
<div class="tb-scada-symbol-select-container" [class]="{disabled: disabled && linkType === ScadaSymbolLinkType.none}">
<div class="tb-scada-symbol-container">
<img *ngIf="linkType !== ScadaSymbolLinkType.none && scadaSymbolUrl; else noSymbol" class="tb-scada-symbol-preview" [src]="scadaSymbolUrl | image: {preview: true} | async">
</div>
<div *ngIf="linkType !== ScadaSymbolLinkType.none && !disabled" class="tb-scada-symbol-info-container">
<div *ngIf="linkType === ScadaSymbolLinkType.resource" class="tb-resource-scada-symbol-container" [class]="{loading: loadingImageResource}">
<ng-container *ngIf="!loadingImageResource; else loading">
<div class="tb-scada-symbol-title">
{{ imageResource.title | customTranslate }}
</div>
</ng-container>
</div>
<div *ngIf="linkType === ScadaSymbolLinkType.content" class="tb-resource-scada-symbol-container">
<div class="tb-scada-symbol-title">
{{ scadaSymbolMetadata.title | customTranslate }}
</div>
</div>
<button *ngIf="linkType === ScadaSymbolLinkType.resource && !disabled"
class="tb-scada-symbol-clear-btn"
type="button"
mat-icon-button
matTooltip="{{ 'scada.clear-symbol' | translate }}"
matTooltipPosition="above"
(click)="clearSymbol()">
<mat-icon>close</mat-icon>
</button>
</div>
<div *ngIf="linkType === ScadaSymbolLinkType.none && !disabled" class="tb-scada-symbol-select-buttons-container">
<button mat-stroked-button
type="button"
color="primary"
class="tb-scada-symbol-select-button"
(click)="openGallery($event)">
<tb-icon matButtonIcon>filter</tb-icon>
<span translate>scada.browse-symbol-from-gallery</span>
</button>
</div>
</div>
</div>
<ng-template #noSymbol>
<div class="tb-no-symbol">{{ (disabled ? 'scada.no-symbol' : 'scada.no-symbol-selected') | translate }}</div>
</ng-template>
<ng-template #loading>
<mat-spinner [diameter]="80" strokeWidth="4"></mat-spinner>
</ng-template>

149
ui-ngx/src/app/shared/components/image/scada-symbol-input.component.scss

@ -0,0 +1,149 @@
/**
* Copyright © 2016-2024 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 "../../../../scss/constants";
$containerHeight: 96px !default;
:host {
.tb-container {
margin-top: 0;
padding: 0 0 16px;
display: flex;
flex-direction: column;
gap: 8px;
label.tb-title {
display: block;
padding-bottom: 0;
}
}
.tb-scada-symbol-select-container {
width: 100%;
height: $containerHeight;
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.12);
display: flex;
align-items: center;
&.disabled {
width: $containerHeight;
.tb-scada-symbol-container {
width: $containerHeight - 2px;
border-right: none;
}
}
}
.tb-scada-symbol-container {
width: $containerHeight - 1px;
height: $containerHeight - 2px;
padding: 8px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px;
border-right: 1px solid rgba(0, 0, 0, 0.12);
background: #fff;
overflow: hidden;
}
.tb-scada-symbol-preview {
width: auto;
max-width: $containerHeight - 2px;
height: auto;
max-height: $containerHeight - 2px;
}
.tb-no-symbol {
text-align: center;
color: rgba(0, 0, 0, 0.38);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
letter-spacing: 0.4px;
}
.tb-scada-symbol-info-container {
display: flex;
flex: 1;
align-self: stretch;
padding: 0 8px;
justify-content: flex-end;
align-items: center;
gap: 4px;
.tb-resource-scada-symbol-container {
padding: 8px;
display: flex;
flex: 1;
align-self: stretch;
justify-content: center;
align-items: flex-start;
flex-direction: column;
gap: 4px;
.tb-scada-symbol-title {
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 24px;
letter-spacing: 0.25px;
}
&.loading {
align-items: center;
}
}
.tb-scada-symbol-clear-btn {
color: rgba(0,0,0,0.38);
}
}
.tb-scada-symbol-select-buttons-container {
display: flex;
flex: 1;
align-self: stretch;
padding: 8px;
gap: 8px;
justify-content: center;
align-items: flex-start;
.tb-scada-symbol-select-button {
width: 100%;
height: 100%;
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
padding: 8px;
line-height: normal;
font-size: 12px;
@media #{$mat-gt-xs} {
padding: 16px;
}
.mat-icon {
width: 24px;
height: 24px;
font-size: 24px;
margin: 0;
}
}
}
}

198
ui-ngx/src/app/shared/components/image/scada-symbol-input.component.ts

@ -0,0 +1,198 @@
///
/// Copyright © 2016-2024 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 { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, } from '@angular/forms';
import { coerceBoolean } from '@shared/decorators/coercion';
import {
extractParamsFromImageResourceUrl,
IMAGE_BASE64_URL_PREFIX,
ImageResourceInfo,
prependTbImagePrefix,
removeTbImagePrefix,
ResourceSubType
} from '@shared/models/resource.models';
import { ImageService } from '@core/http/image.service';
import { MatDialog } from '@angular/material/dialog';
import {
ImageGalleryDialogComponent,
ImageGalleryDialogData
} from '@shared/components/image/image-gallery-dialog.component';
import { ScadaSymbolMetadata } from '@home/components/widget/lib/scada/scada-symbol.models';
import { stringToBase64 } from '@core/utils';
export enum ScadaSymbolLinkType {
none = 'none',
content = 'content',
resource = 'resource'
}
@Component({
selector: 'tb-scada-symbol-input',
templateUrl: './scada-symbol-input.component.html',
styleUrls: ['./scada-symbol-input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ScadaSymbolInputComponent),
multi: true
}
]
})
export class ScadaSymbolInputComponent extends PageComponent implements OnInit, OnDestroy, ControlValueAccessor {
@Input()
label: string;
@Input()
@coerceBoolean()
required = false;
@Input()
disabled: boolean;
@Input()
scadaSymbolContent: string;
@Input()
scadaSymbolMetadata: ScadaSymbolMetadata;
scadaSymbolUrl: string;
imageResource: ImageResourceInfo;
loadingImageResource = false;
ScadaSymbolLinkType = ScadaSymbolLinkType;
linkType: ScadaSymbolLinkType = ScadaSymbolLinkType.none;
private propagateChange = null;
constructor(protected store: Store<AppState>,
private imageService: ImageService,
private dialog: MatDialog,
private cd: ChangeDetectorRef) {
super(store);
}
ngOnInit() {
if (this.scadaSymbolContent && this.scadaSymbolMetadata) {
this.scadaSymbolUrl = IMAGE_BASE64_URL_PREFIX + 'svg+xml;base64,' + stringToBase64(this.scadaSymbolContent);
this.linkType = ScadaSymbolLinkType.content;
}
}
ngOnDestroy() {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(_fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.detectLinkType();
}
}
writeValue(value: string): void {
value = removeTbImagePrefix(value);
if (this.scadaSymbolUrl !== value) {
this.reset();
this.scadaSymbolUrl = value;
this.detectLinkType();
if (this.linkType === ScadaSymbolLinkType.resource) {
const params = extractParamsFromImageResourceUrl(this.scadaSymbolUrl);
if (params) {
this.loadingImageResource = true;
this.imageService.getImageInfo(params.type, params.key, {ignoreLoading: true, ignoreErrors: true}).subscribe(
{
next: (res) => {
this.imageResource = res;
this.loadingImageResource = false;
this.cd.markForCheck();
},
error: () => {
this.reset();
this.loadingImageResource = false;
this.cd.markForCheck();
}
}
);
} else {
this.reset();
this.cd.markForCheck();
}
}
}
}
private detectLinkType() {
if (this.scadaSymbolUrl) {
this.linkType = ScadaSymbolLinkType.resource;
} else {
this.linkType = ScadaSymbolLinkType.none;
}
}
private updateModel(value: string) {
this.cd.markForCheck();
if (this.scadaSymbolUrl !== value) {
this.scadaSymbolUrl = value;
this.propagateChange(prependTbImagePrefix(this.scadaSymbolUrl));
}
}
private reset() {
this.linkType = ScadaSymbolLinkType.none;
this.imageResource = null;
}
clearSymbol() {
this.reset();
this.updateModel(null);
}
openGallery($event: Event): void {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<ImageGalleryDialogComponent, ImageGalleryDialogData,
ImageResourceInfo>(ImageGalleryDialogComponent, {
autoFocus: false,
disableClose: false,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
imageSubType: ResourceSubType.SCADA_SYMBOL
}
}).afterClosed().subscribe((image) => {
if (image) {
this.linkType = ScadaSymbolLinkType.resource;
this.imageResource = image;
this.updateModel(image.link);
}
});
}
}

32
ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts

@ -36,10 +36,10 @@ import { forkJoin } from 'rxjs';
import { blobToBase64, blobToText, updateFileContent } from '@core/utils'; import { blobToBase64, blobToText, updateFileContent } from '@core/utils';
import { import {
emptyMetadata, emptyMetadata,
IotSvgMetadata, ScadaSymbolMetadata,
parseIotSvgMetadataFromContent, parseScadaSymbolMetadataFromContent,
updateIotSvgMetadataInContent updateScadaSymbolMetadataInContent
} from '@home/components/widget/lib/svg/iot-svg.models'; } from '@home/components/widget/lib/scada/scada-symbol.models';
export interface UploadImageDialogData { export interface UploadImageDialogData {
imageSubType: ResourceSubType; imageSubType: ResourceSubType;
@ -64,11 +64,11 @@ export class UploadImageDialogComponent extends
maxResourceSize = getCurrentAuthState(this.store).maxResourceSize; maxResourceSize = getCurrentAuthState(this.store).maxResourceSize;
get isScada() { get isScada() {
return this.data.imageSubType === ResourceSubType.IOT_SVG; return this.data.imageSubType === ResourceSubType.SCADA_SYMBOL;
} }
private iotSvgContent: string; private scadaSymbolContent: string;
private iotSvgMetadata: IotSvgMetadata; private scadaSymbolMetadata: ScadaSymbolMetadata;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router, protected router: Router,
@ -91,11 +91,11 @@ export class UploadImageDialogComponent extends
this.uploadImageFormGroup.get('file').valueChanges.subscribe((file: File) => { this.uploadImageFormGroup.get('file').valueChanges.subscribe((file: File) => {
if (file) { if (file) {
blobToText(file).subscribe(content => { blobToText(file).subscribe(content => {
this.iotSvgContent = content; this.scadaSymbolContent = content;
this.iotSvgMetadata = parseIotSvgMetadataFromContent(this.iotSvgContent); this.scadaSymbolMetadata = parseScadaSymbolMetadataFromContent(this.scadaSymbolContent);
const titleControl = this.uploadImageFormGroup.get('title'); const titleControl = this.uploadImageFormGroup.get('title');
if (this.iotSvgMetadata.title && (!titleControl.value || !titleControl.touched)) { if (this.scadaSymbolMetadata.title && (!titleControl.value || !titleControl.touched)) {
titleControl.setValue(this.iotSvgMetadata.title); titleControl.setValue(this.scadaSymbolMetadata.title);
} }
}); });
} }
@ -129,13 +129,13 @@ export class UploadImageDialogComponent extends
if (this.uploadImage) { if (this.uploadImage) {
const title: string = this.uploadImageFormGroup.get('title').value; const title: string = this.uploadImageFormGroup.get('title').value;
if (this.isScada) { if (this.isScada) {
if (!this.iotSvgMetadata) { if (!this.scadaSymbolMetadata) {
this.iotSvgMetadata = emptyMetadata(); this.scadaSymbolMetadata = emptyMetadata();
} }
if (this.iotSvgMetadata.title !== title) { if (this.scadaSymbolMetadata.title !== title) {
this.iotSvgMetadata.title = title; this.scadaSymbolMetadata.title = title;
} }
const newContent = updateIotSvgMetadataInContent(this.iotSvgContent, this.iotSvgMetadata); const newContent = updateScadaSymbolMetadataInContent(this.scadaSymbolContent, this.scadaSymbolMetadata);
file = updateFileContent(file, newContent); file = updateFileContent(file, newContent);
} }
forkJoin([ forkJoin([

8
ui-ngx/src/app/shared/models/resource.models.ts

@ -29,7 +29,7 @@ export enum ResourceType {
export enum ResourceSubType { export enum ResourceSubType {
IMAGE = 'IMAGE', IMAGE = 'IMAGE',
IOT_SVG = 'IOT_SVG' SCADA_SYMBOL = 'SCADA_SYMBOL'
} }
export const ResourceTypeMIMETypes = new Map<ResourceType, string>( export const ResourceTypeMIMETypes = new Map<ResourceType, string>(
@ -167,7 +167,11 @@ export const isImageResourceUrl = (url: string): boolean => url && IMAGES_URL_RE
export const extractParamsFromImageResourceUrl = (url: string): {type: ImageResourceType; key: string} => { export const extractParamsFromImageResourceUrl = (url: string): {type: ImageResourceType; key: string} => {
const res = url.match(IMAGES_URL_REGEXP); const res = url.match(IMAGES_URL_REGEXP);
return {type: res[1] as ImageResourceType, key: res[2]}; if (res?.length > 2) {
return {type: res[1] as ImageResourceType, key: res[2]};
} else {
return null;
}
}; };
export const isBase64DataImageUrl = (url: string): boolean => url && url.startsWith(IMAGE_BASE64_URL_PREFIX); export const isBase64DataImageUrl = (url: string): boolean => url && url.startsWith(IMAGE_BASE64_URL_PREFIX);

7
ui-ngx/src/app/shared/shared.module.ts

@ -220,6 +220,7 @@ import { RuleChainSelectPanelComponent } from '@shared/components/rule-chain/rul
import { WidgetButtonComponent } from '@shared/components/button/widget-button.component'; import { WidgetButtonComponent } from '@shared/components/button/widget-button.component';
import { HexInputComponent } from '@shared/components/color-picker/hex-input.component'; import { HexInputComponent } from '@shared/components/color-picker/hex-input.component';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import { ScadaSymbolInputComponent } from '@shared/components/image/scada-symbol-input.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService; return markedOptionsService;
@ -421,7 +422,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
EmbedImageDialogComponent, EmbedImageDialogComponent,
ImageGalleryDialogComponent, ImageGalleryDialogComponent,
WidgetButtonComponent, WidgetButtonComponent,
HexInputComponent HexInputComponent,
ScadaSymbolInputComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -675,7 +677,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
MultipleGalleryImageInputComponent, MultipleGalleryImageInputComponent,
EmbedImageDialogComponent, EmbedImageDialogComponent,
ImageGalleryDialogComponent, ImageGalleryDialogComponent,
WidgetButtonComponent WidgetButtonComponent,
ScadaSymbolInputComponent
] ]
}) })
export class SharedModule { } export class SharedModule { }

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

@ -3415,6 +3415,10 @@
"state-render-function": "State render function", "state-render-function": "State render function",
"preview": "Preview", "preview": "Preview",
"preview-widget-action-text": "Widget action '{{type}}' successfully invoked!", "preview-widget-action-text": "Widget action '{{type}}' successfully invoked!",
"no-symbol": "No SCADA symbol",
"no-symbol-selected": "No SCADA symbol selected",
"clear-symbol": "Clear SCADA symbol",
"browse-symbol-from-gallery": "Browse SCADA symbol from gallery",
"tag": { "tag": {
"tag": "Tag", "tag": "Tag",
"on-click-action": "On click action", "on-click-action": "On click action",

2
ui-ngx/src/styles.scss

@ -461,7 +461,7 @@ mat-icon {
} }
} }
.tooltipster-sidetip.iot-svg { .tooltipster-sidetip.scada-symbol {
.tooltipster-box { .tooltipster-box {
user-select: none; user-select: none;
background: rgba(0, 0, 0, 0.54); background: rgba(0, 0, 0, 0.54);

Loading…
Cancel
Save