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",
"simple_value_and_chart_card",
"progress_bar",
"iot_svg",
"cards.label_widget",
"cards.dashboard_state_widget",
"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,
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
@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,
@Parameter(description = RESOURCE_INCLUDE_SYSTEM_IMAGES_DESCRIPTION)
@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
public void testUploadIoTSvgImage() throws Exception {
String filename = "my_iot_svg_image.svg";
TbResourceInfo imageInfo = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.IOT_SVG.name(), filename, "image/svg+xml", SVG_IMAGE);
public void testUploadScadaSymbolImage() throws Exception {
String filename = "my_scada_symbol_image.svg";
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);
checkSvgImageDescriptor(imageDescriptor);
@ -224,37 +224,37 @@ public class ImageControllerTest extends AbstractControllerTest {
String systemImageName = "my_system_png_image.png";
TbResourceInfo systemImage = uploadImage(HttpMethod.POST, "/api/image", systemImageName, "image/png", PNG_IMAGE);
String systemIotSvgName = "my_system_iot_svg_image.svg";
TbResourceInfo systemIotSvg = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.IOT_SVG.name(), systemIotSvgName, "image/svg+xml", SVG_IMAGE);
String systemScadaSymbolName = "my_system_scada_symbol_image.svg";
TbResourceInfo systemScadaSymbol = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.SCADA_SYMBOL.name(), systemScadaSymbolName, "image/svg+xml", SVG_IMAGE);
loginTenantAdmin();
String tenantImageName = "my_jpeg_image.jpg";
TbResourceInfo tenantImage = uploadImage(HttpMethod.POST, "/api/image", tenantImageName, "image/jpeg", JPEG_IMAGE);
String tenantIotSvgName = "my_iot_svg_image.svg";
TbResourceInfo tenantIotSvg = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.IOT_SVG.name(), tenantIotSvgName, "image/svg+xml", SVG_IMAGE);
String tenantScadaSymbolName = "my_scada_symbol_image.svg";
TbResourceInfo tenantScadaSymbol = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.SCADA_SYMBOL.name(), tenantScadaSymbolName, "image/svg+xml", SVG_IMAGE);
List<TbResourceInfo> tenantImages = getImages(null, false, 10);
assertThat(tenantImages).containsOnly(tenantImage);
List<TbResourceInfo> tenantIotSvgs = getImages(null, ResourceSubType.IOT_SVG.name(), false, 10);
assertThat(tenantIotSvgs).containsOnly(tenantIotSvg);
List<TbResourceInfo> tenantScadaSymbols = getImages(null, ResourceSubType.SCADA_SYMBOL.name(), false, 10);
assertThat(tenantScadaSymbols).containsOnly(tenantScadaSymbol);
List<TbResourceInfo> allImages = getImages(null, true, 10);
assertThat(allImages).containsOnly(tenantImage, systemImage);
List<TbResourceInfo> allIotSvgs = getImages(null, ResourceSubType.IOT_SVG.name(), true, 10);
assertThat(allIotSvgs).containsOnly(tenantIotSvg, systemIotSvg);
List<TbResourceInfo> allScadaSymbols = getImages(null, ResourceSubType.SCADA_SYMBOL.name(), true, 10);
assertThat(allScadaSymbols).containsOnly(tenantScadaSymbol, systemScadaSymbol);
assertThat(getImages("png", true, 10))
.containsOnly(systemImage);
assertThat(getImages("jpg", true, 10))
.containsOnly(tenantImage);
assertThat(getImages("my_system_iot", ResourceSubType.IOT_SVG.name(), true, 10))
.containsOnly(systemIotSvg);
assertThat(getImages("my_iot_svg", ResourceSubType.IOT_SVG.name(),true, 10))
.containsOnly(tenantIotSvg);
assertThat(getImages("my_system_scada_symbol", ResourceSubType.SCADA_SYMBOL.name(), true, 10))
.containsOnly(systemScadaSymbol);
assertThat(getImages("my_scada_symbol", ResourceSubType.SCADA_SYMBOL.name(),true, 10))
.containsOnly(tenantScadaSymbol);
}
@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 {
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.background.imageUrl", "$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");
}
@ -425,7 +426,7 @@ public class BaseImageService extends BaseResourceService implements ImageServic
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) {
if (StringUtils.isBlank(data)) {
@ -435,11 +436,15 @@ public class BaseImageService extends BaseResourceService implements ImageServic
boolean matches = matcher.matches();
String mdResourceKey = null;
String mdResourceName = null;
String mdResourceSubType = null;
String mdMediaType;
if (matches) {
mdResourceKey = new String(Base64.getDecoder().decode(matcher.group(1)), 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/"))) {
mdMediaType = StringUtils.substringBetween(data, "data:", ";base64");
} else {
@ -468,6 +473,11 @@ public class BaseImageService extends BaseResourceService implements ImageServic
} else {
fileName = mdResourceKey;
}
if (StringUtils.isBlank(mdResourceSubType)) {
image.setResourceSubType(ResourceSubType.IMAGE);
} else {
image.setResourceSubType(ResourceSubType.valueOf(mdResourceSubType));
}
image.setFileName(fileName);
image.setDescriptor(JacksonUtil.newObjectNode().put("mediaType", mdMediaType));
image.setData(imageData);
@ -629,7 +639,8 @@ public class BaseImageService extends BaseResourceService implements ImageServic
String tbImagePrefix = "";
if (addTbImagePrefix) {
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);
}

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

@ -129,7 +129,7 @@ import {
import {
RadarChartBasicConfigComponent
} 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({
declarations: [
@ -173,7 +173,7 @@ import { IotSvgBasicConfigComponent } from '@home/components/widget/config/basic
BarChartBasicConfigComponent,
PolarAreaChartBasicConfigComponent,
RadarChartBasicConfigComponent,
IotSvgBasicConfigComponent
ScadaSymbolBasicConfigComponent
],
imports: [
CommonModule,
@ -219,7 +219,7 @@ import { IotSvgBasicConfigComponent } from '@home/components/widget/config/basic
BarChartBasicConfigComponent,
PolarAreaChartBasicConfigComponent,
RadarChartBasicConfigComponent,
IotSvgBasicConfigComponent
ScadaSymbolBasicConfigComponent
]
})
export class BasicWidgetConfigModule {
@ -259,5 +259,5 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-bar-chart-basic-config': BarChartBasicConfigComponent,
'tb-polar-area-chart-basic-config': PolarAreaChartBasicConfigComponent,
'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"
[class.overlay]="overlayEnabled" [style]="backgroundStyle$ | async">
<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 #chartShape class="tb-time-series-chart-shape">
</div>

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

@ -43,6 +43,7 @@ import {
TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import { mergeDeep } from '@core/utils';
import { WidgetComponent } from '@home/components/widget/widget.component';
@Component({
selector: 'tb-time-series-chart-widget',
@ -85,7 +86,8 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV
private timeSeriesChart: TbTimeSeriesChart;
constructor(private imagePipe: ImagePipe,
constructor(public widgetComponent: WidgetComponent,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer,
private renderer: Renderer2,
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.
-->
<div class="tb-iot-svg-panel" [style]="backgroundStyle$ | async">
<div class="tb-iot-svg-overlay" [style]="overlayStyle"></div>
<div class="tb-scada-symbol-panel" [style.padding]="padding"
[class.overlay]="overlayEnabled" [style]="backgroundStyle$ | async">
<div class="tb-scada-symbol-overlay" [style]="overlayStyle"></div>
<ng-container *ngIf="widgetComponent.dashboardWidget.showWidgetTitlePanel">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</ng-container>
<div class="tb-iot-svg-content">
<div #iotSvgShape class="tb-iot-svg-shape">
<div class="tb-scada-symbol-content">
<div #scadaSymbolShape class="tb-scada-symbol-shape">
</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>

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.
*/
.tb-iot-svg-panel {
.tb-scada-symbol-panel {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
gap: 16px;
padding: 20px 24px 24px 24px;
> div:not(.tb-iot-svg-overlay) {
gap: 8px;
&.overlay {
padding: 20px 24px 24px 24px;
}
> div:not(.tb-scada-symbol-overlay) {
z-index: 1;
}
.tb-iot-svg-overlay {
.tb-scada-symbol-overlay {
position: absolute;
top: 12px;
left: 12px;
@ -35,11 +37,11 @@
div.tb-widget-title {
padding: 0;
}
.tb-iot-svg-content {
.tb-scada-symbol-content {
flex: 1;
min-width: 0;
min-height: 0;
.tb-iot-svg-shape {
.tb-scada-symbol-shape {
width: 100%;
height: 100%;
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,
OnDestroy,
OnInit,
Renderer2,
TemplateRef,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser';
import { HttpClient } from '@angular/common/http';
import { IotSvgObject, IotSvgObjectCallbacks } from '@home/components/widget/lib/svg/iot-svg.models';
import { ScadaSymbolObject, ScadaSymbolObjectCallbacks } from '@home/components/widget/lib/scada/scada-symbol.models';
import {
iotSvgWidgetDefaultSettings,
IotSvgWidgetSettings
} from '@home/components/widget/lib/svg/iot-svg-widget.models';
import { Observable, of } from 'rxjs';
scadaSymbolWidgetDefaultSettings,
ScadaSymbolWidgetSettings
} from '@home/components/widget/lib/scada/scada-symbol-widget.models';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models';
import { ImageService } from '@core/http/image.service';
import { WidgetComponent } from '@home/components/widget/widget.component';
import { isDefinedAndNotNull, mergeDeep } from '@core/utils';
import { WidgetContext } from '@home/models/widget-component.models';
import { catchError, share } from 'rxjs/operators';
@Component({
selector: 'tb-iot-svg-widget',
templateUrl: './iot-svg-widget.component.html',
styleUrls: ['../action/action-widget.scss', './iot-svg-widget.component.scss'],
selector: 'tb-scada-symbol-widget',
templateUrl: './scada-symbol-widget.component.html',
styleUrls: ['../action/action-widget.scss', './scada-symbol-widget.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class IotSvgWidgetComponent implements OnInit, AfterViewInit, OnDestroy, IotSvgObjectCallbacks {
export class ScadaSymbolWidgetComponent implements OnInit, AfterViewInit, OnDestroy, ScadaSymbolObjectCallbacks {
@ViewChild('iotSvgShape', {static: false})
iotSvgShape: ElementRef<HTMLElement>;
@ViewChild('scadaSymbolShape', {static: false})
scadaSymbolShape: ElementRef<HTMLElement>;
@Input()
ctx: WidgetContext;
@ -59,47 +58,57 @@ export class IotSvgWidgetComponent implements OnInit, AfterViewInit, OnDestroy,
@Input()
widgetTitlePanel: TemplateRef<any>;
private settings: IotSvgWidgetSettings;
private svgContent$: Observable<string>;
private loadingSubject = new BehaviorSubject(false);
private settings: ScadaSymbolWidgetSettings;
private scadaSymbolContent$: Observable<string>;
backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {};
iotSvgObject: IotSvgObject;
overlayEnabled: boolean;
padding: string;
loading$ = this.loadingSubject.asObservable().pipe(share());
scadaSymbolObject: ScadaSymbolObject;
constructor(public widgetComponent: WidgetComponent,
protected imagePipe: ImagePipe,
protected sanitizer: DomSanitizer,
private imageService: ImageService,
protected cd: ChangeDetectorRef,
private http: HttpClient) {
protected cd: ChangeDetectorRef) {
}
ngOnInit(): void {
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.overlayStyle = overlayStyle(this.settings.background.overlay);
if (this.settings.iotSvgContent) {
this.svgContent$ = of(this.settings.iotSvgContent);
} else if (this.settings.iotSvgUrl) {
this.svgContent$ = this.imageService.getImageString(this.settings.iotSvgUrl);
this.overlayEnabled = this.settings.background.overlay.enabled;
this.padding = this.overlayEnabled ? undefined : this.settings.padding;
if (this.settings.scadaSymbolContent) {
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 {
this.svgContent$ = this.http.get(this.settings.iotSvg, {responseType: 'text'});
this.scadaSymbolContent$ = of('<svg></svg>');
}
}
ngAfterViewInit(): void {
this.svgContent$.subscribe((content) => {
this.initObject(this.iotSvgShape.nativeElement, content);
this.scadaSymbolContent$.subscribe((content) => {
this.initObject(this.scadaSymbolShape.nativeElement, content);
});
}
ngOnDestroy() {
if (this.iotSvgObject) {
this.iotSvgObject.destroy();
if (this.scadaSymbolObject) {
this.scadaSymbolObject.destroy();
}
this.loadingSubject.complete();
this.loadingSubject.unsubscribe();
}
public onInit() {
@ -108,19 +117,24 @@ export class IotSvgWidgetComponent implements OnInit, AfterViewInit, OnDestroy,
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);
}
onSvgObjectMessage(message: string) {
onScadaSymbolObjectMessage(message: string) {
this.ctx.showSuccessToast(message, 3000, 'bottom', 'center', this.ctx.toastTargetId, true);
}
private initObject(rootElement: HTMLElement,
svgContent: string) {
content: string) {
const simulated = this.ctx.utilsService.widgetEditMode ||
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.
///
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';
export interface IotSvgWidgetSettings {
iotSvg?: string;
iotSvgUrl?: string;
iotSvgContent?: string;
export interface ScadaSymbolWidgetSettings {
scadaSymbolUrl?: string;
scadaSymbolContent?: string;
simulated?: boolean;
iotSvgObject: IotSvgObjectSettings;
scadaSymbolObjectSettings: ScadaSymbolObjectSettings;
background: BackgroundSettings;
padding: string;
}
export const iotSvgWidgetDefaultSettings: IotSvgWidgetSettings = {
iotSvg: '/assets/widget/svg/drawing.svg',
iotSvgObject: {
export const scadaSymbolWidgetDefaultSettings: ScadaSymbolWidgetSettings = {
scadaSymbolObjectSettings: {
behavior: {},
properties: {}
},
@ -40,5 +39,6 @@ export const iotSvgWidgetDefaultSettings: IotSvgWidgetSettings = {
color: 'rgba(255,255,255,0.72)',
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,
parseFunction
} from '@core/utils';
import { BehaviorSubject, forkJoin, Observable, Observer } from 'rxjs';
import { share } from 'rxjs/operators';
import { BehaviorSubject, forkJoin, Observable, Observer, Subject } from 'rxjs';
import { ValueAction, ValueGetter, ValueSetter } from '@home/components/widget/lib/action/action-widget.models';
import { WidgetContext } from '@home/models/widget-component.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 { WidgetAction, WidgetActionType, widgetActionTypeTranslationMap } from '@shared/models/widget.models';
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;
text: (element: Element | Element[], text: string) => void;
font: (element: Element | Element[], font: Font, color: string) => void;
@ -54,57 +54,57 @@ export interface IotSvgApi {
setValue: (valueId: string, value: any) => void;
}
export interface IotSvgContext {
api: IotSvgApi;
export interface ScadaSymbolContext {
api: ScadaSymbolApi;
tags: {[id: string]: Element[]};
values: {[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 interface IotSvgAction {
export type ScadaSymbolActionFunction = (ctx: ScadaSymbolContext, element: Element, event: Event) => void;
export interface ScadaSymbolAction {
actionFunction?: string;
action?: IotSvgActionFunction;
action?: ScadaSymbolActionFunction;
}
export interface IotSvgTag {
export interface ScadaSymbolTag {
tag: string;
stateRenderFunction?: string;
stateRender?: IotSvgTagStateRenderFunction;
actions?: {[trigger: string]: IotSvgAction};
stateRender?: ScadaSymbolTagStateRenderFunction;
actions?: {[trigger: string]: ScadaSymbolAction};
}
export enum IotSvgBehaviorType {
export enum ScadaSymbolBehaviorType {
value = 'value',
action = 'action',
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'],
[IotSvgBehaviorType.action, 'scada.behavior.type-action'],
[IotSvgBehaviorType.widgetAction, 'scada.behavior.type-widget-action']
[ScadaSymbolBehaviorType.value, 'scada.behavior.type-value'],
[ScadaSymbolBehaviorType.action, 'scada.behavior.type-action'],
[ScadaSymbolBehaviorType.widgetAction, 'scada.behavior.type-widget-action']
]
);
export interface IotSvgBehaviorBase {
export interface ScadaSymbolBehaviorBase {
id: string;
name: string;
hint?: string;
type: IotSvgBehaviorType;
type: ScadaSymbolBehaviorType;
}
export interface IotSvgBehaviorValue extends IotSvgBehaviorBase {
export interface ScadaSymbolBehaviorValue extends ScadaSymbolBehaviorBase {
valueType: ValueType;
defaultValue: any;
trueLabel?: string;
@ -112,16 +112,16 @@ export interface IotSvgBehaviorValue extends IotSvgBehaviorBase {
stateLabel?: string;
}
export interface IotSvgBehaviorAction extends IotSvgBehaviorBase {
export interface ScadaSymbolBehaviorAction extends ScadaSymbolBehaviorBase {
valueType: ValueType;
valueToDataType: ValueToDataType;
constantValue: any;
valueToDataFunction: string;
}
export type IotSvgBehavior = IotSvgBehaviorValue & IotSvgBehaviorAction;
export type ScadaSymbolBehavior = ScadaSymbolBehaviorValue & ScadaSymbolBehaviorAction;
export enum IotSvgPropertyType {
export enum ScadaSymbolPropertyType {
text = 'text',
number = 'number',
switch = 'switch',
@ -131,30 +131,30 @@ export enum IotSvgPropertyType {
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'],
[IotSvgPropertyType.number, 'scada.property.type-number'],
[IotSvgPropertyType.switch, 'scada.property.type-switch'],
[IotSvgPropertyType.color, 'scada.property.type-color'],
[IotSvgPropertyType.color_settings, 'scada.property.type-color-settings'],
[IotSvgPropertyType.font, 'scada.property.type-font'],
[IotSvgPropertyType.units, 'scada.property.type-units']
[ScadaSymbolPropertyType.text, 'scada.property.type-text'],
[ScadaSymbolPropertyType.number, 'scada.property.type-number'],
[ScadaSymbolPropertyType.switch, 'scada.property.type-switch'],
[ScadaSymbolPropertyType.color, 'scada.property.type-color'],
[ScadaSymbolPropertyType.color_settings, 'scada.property.type-color-settings'],
[ScadaSymbolPropertyType.font, 'scada.property.type-font'],
[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'];
export const iotSvgPropertyFieldClasses =
export const scadaSymbolPropertyFieldClasses =
['medium-width', 'flex', 'flex-xs', 'flex-lt-md'];
export interface IotSvgPropertyBase {
export interface ScadaSymbolPropertyBase {
id: string;
name: string;
type: IotSvgPropertyType;
type: ScadaSymbolPropertyType;
default: any;
required?: boolean;
subLabel?: string;
@ -165,24 +165,24 @@ export interface IotSvgPropertyBase {
fieldClass?: string;
}
export interface IotSvgNumberProperty extends IotSvgPropertyBase {
export interface ScadaSymbolNumberProperty extends ScadaSymbolPropertyBase {
min?: number;
max?: number;
step?: number;
}
export type IotSvgProperty = IotSvgPropertyBase & IotSvgNumberProperty;
export type ScadaSymbolProperty = ScadaSymbolPropertyBase & ScadaSymbolNumberProperty;
export interface IotSvgMetadata {
export interface ScadaSymbolMetadata {
title: string;
stateRenderFunction?: string;
stateRender?: IotSvgStateRenderFunction;
tags: IotSvgTag[];
behavior: IotSvgBehavior[];
properties: IotSvgProperty[];
stateRender?: ScadaSymbolStateRenderFunction;
tags: ScadaSymbolTag[];
behavior: ScadaSymbolBehavior[];
properties: ScadaSymbolProperty[];
}
export const emptyMetadata = (): IotSvgMetadata => ({
export const emptyMetadata = (): ScadaSymbolMetadata => ({
title: '',
tags: [],
behavior: [],
@ -190,16 +190,16 @@ export const emptyMetadata = (): IotSvgMetadata => ({
});
export const parseIotSvgMetadataFromContent = (svgContent: string): IotSvgMetadata => {
export const parseScadaSymbolMetadataFromContent = (svgContent: string): ScadaSymbolMetadata => {
try {
const svgDoc = new DOMParser().parseFromString(svgContent, 'image/svg+xml');
return parseIotSvgMetadataFromDom(svgDoc);
return parseScadaSymbolMetadataFromDom(svgDoc);
} catch (_e) {
return emptyMetadata();
}
};
const parseIotSvgMetadataFromDom = (svgDoc: Document): IotSvgMetadata => {
const parseScadaSymbolMetadataFromDom = (svgDoc: Document): ScadaSymbolMetadata => {
try {
const elements = svgDoc.getElementsByTagName('tb:metadata');
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');
updateIotSvgMetadataInDom(svgDoc, metadata);
updateScadaSymbolMetadataInDom(svgDoc, metadata);
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');
let metadataElement: Node;
const elements = svgDoc.getElementsByTagName('tb:metadata');
@ -238,13 +238,13 @@ const updateIotSvgMetadataInDom = (svgDoc: Document, metadata: IotSvgMetadata) =
const svgPartsRegex = /(<svg .*?>)(.*)<\/svg>/gms;
const tbMetadataRegex = /<tb:metadata>.*<\/tb:metadata>/gs;
export interface IoTSvgContentData {
export interface ScadaSymbolContentData {
svgRootNode: string;
innerSvg: string;
}
export const iotSvgContentData = (svgContent: string): IoTSvgContentData => {
const result: IoTSvgContentData = {
export const scadaSymbolContentData = (svgContent: string): ScadaSymbolContentData => {
const result: ScadaSymbolContentData = {
svgRootNode: '',
innerSvg: ''
};
@ -268,7 +268,7 @@ export const iotSvgContentData = (svgContent: string): IoTSvgContentData => {
return result;
};
const defaultGetValueSettings = (get: IotSvgBehaviorValue): GetValueSettings<any> => ({
const defaultGetValueSettings = (get: ScadaSymbolBehaviorValue): GetValueSettings<any> => ({
action: GetValueAction.DO_NOTHING,
defaultValue: get.defaultValue,
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,
executeRpc: {
method: 'setState',
@ -314,7 +314,7 @@ const defaultSetValueSettings = (set: IotSvgBehaviorAction): SetValueSettings =>
}
});
const defaultWidgetActionSettings = (widgetAction: IotSvgBehavior): WidgetAction => ({
const defaultWidgetActionSettings = (widgetAction: ScadaSymbolBehavior): WidgetAction => ({
type: WidgetActionType.updateDashboardState,
targetDashboardStateId: null,
openRightLayout: false,
@ -322,17 +322,17 @@ const defaultWidgetActionSettings = (widgetAction: IotSvgBehavior): WidgetAction
stateEntityParamName: null
});
export const defaultIotSvgObjectSettings = (metadata: IotSvgMetadata): IotSvgObjectSettings => {
const settings: IotSvgObjectSettings = {
export const defaultScadaSymbolObjectSettings = (metadata: ScadaSymbolMetadata): ScadaSymbolObjectSettings => {
const settings: ScadaSymbolObjectSettings = {
behavior: {},
properties: {}
};
for (const behavior of metadata.behavior) {
if (behavior.type === IotSvgBehaviorType.value) {
settings.behavior[behavior.id] = defaultGetValueSettings(behavior as IotSvgBehaviorValue);
} else if (behavior.type === IotSvgBehaviorType.action) {
settings.behavior[behavior.id] = defaultSetValueSettings(behavior as IotSvgBehaviorAction);
} else if (behavior.type === IotSvgBehaviorType.widgetAction) {
if (behavior.type === ScadaSymbolBehaviorType.value) {
settings.behavior[behavior.id] = defaultGetValueSettings(behavior as ScadaSymbolBehaviorValue);
} else if (behavior.type === ScadaSymbolBehaviorType.action) {
settings.behavior[behavior.id] = defaultSetValueSettings(behavior as ScadaSymbolBehaviorAction);
} else if (behavior.type === ScadaSymbolBehaviorType.widgetAction) {
settings.behavior[behavior.id] = defaultWidgetActionSettings(behavior);
}
}
@ -342,7 +342,7 @@ export const defaultIotSvgObjectSettings = (metadata: IotSvgMetadata): IotSvgObj
return settings;
};
export type IotSvgObjectSettings = {
export type ScadaSymbolObjectSettings = {
behavior: {[id: string]: any};
properties: {[id: string]: any};
};
@ -350,21 +350,21 @@ export type IotSvgObjectSettings = {
const parseError = (ctx: WidgetContext, err: any): string =>
ctx.$injector.get(UtilsService).parseException(err).message || 'Unknown Error';
export interface IotSvgObjectCallbacks {
onSvgObjectError: (error: string) => void;
onSvgObjectMessage: (message: string) => void;
export interface ScadaSymbolObjectCallbacks {
onScadaSymbolObjectLoadingState: (loading: boolean) => void;
onScadaSymbolObjectError: (error: string) => void;
onScadaSymbolObjectMessage: (message: string) => void;
}
export class IotSvgObject {
export class ScadaSymbolObject {
private metadata: IotSvgMetadata;
private settings: IotSvgObjectSettings;
private context: IotSvgContext;
private metadata: ScadaSymbolMetadata;
private settings: ScadaSymbolObjectSettings;
private context: ScadaSymbolContext;
private svgShape: Svg;
private box: Box;
private loadingSubject = new BehaviorSubject(false);
private valueGetters: ValueGetter<any>[] = [];
private valueActions: ValueAction[] = [];
private valueSetters: {[behaviorId: string]: ValueSetter<any>} = {};
@ -372,32 +372,34 @@ export class IotSvgObject {
private stateValueSubjects: {[id: string]: BehaviorSubject<any>} = {};
private readonly shapeResize$: ResizeObserver;
private readonly destroy$ = new Subject<void>();
private scale = 1;
private performInit = true;
loading$ = this.loadingSubject.asObservable().pipe(share());
constructor(private rootElement: HTMLElement,
private ctx: WidgetContext,
private svgContent: string,
private inputSettings: IotSvgObjectSettings,
private callbacks: IotSvgObjectCallbacks,
private inputSettings: ScadaSymbolObjectSettings,
private callbacks: ScadaSymbolObjectCallbacks,
private simulated: boolean) {
this.shapeResize$ = new ResizeObserver(() => {
this.resize();
});
const doc: XMLDocument = new DOMParser().parseFromString(this.svgContent, 'image/svg+xml');
this.metadata = parseIotSvgMetadataFromDom(doc);
const defaults = defaultIotSvgObjectSettings(this.metadata);
this.settings = mergeDeep<IotSvgObjectSettings>({} as IotSvgObjectSettings,
defaults, this.inputSettings || {} as IotSvgObjectSettings);
this.metadata = parseScadaSymbolMetadataFromDom(doc);
const defaults = defaultScadaSymbolObjectSettings(this.metadata);
this.settings = mergeDeep<ScadaSymbolObjectSettings>({} as ScadaSymbolObjectSettings,
defaults, this.inputSettings || {} as ScadaSymbolObjectSettings);
this.prepareMetadata();
this.prepareSvgShape(doc);
this.shapeResize$.observe(this.rootElement);
}
public destroy() {
this.destroy$.next();
this.destroy$.complete();
if (this.shapeResize$) {
this.shapeResize$.disconnect();
}
@ -406,8 +408,6 @@ export class IotSvgObject {
this.stateValueSubjects[stateValueId].unsubscribe();
}
this.valueActions.forEach(v => v.destroy());
this.loadingSubject.complete();
this.loadingSubject.unsubscribe();
for (const tag of this.metadata.tags) {
const elements = this.context.tags[tag.tag];
elements.forEach(element => {
@ -490,15 +490,15 @@ export class IotSvgObject {
}
}
for (const behavior of this.metadata.behavior) {
if (behavior.type === IotSvgBehaviorType.value) {
const getBehavior = behavior as IotSvgBehaviorValue;
if (behavior.type === ScadaSymbolBehaviorType.value) {
const getBehavior = behavior as ScadaSymbolBehaviorValue;
let getValueSettings: GetValueSettings<any> = this.settings.behavior[getBehavior.id];
getValueSettings = {...getValueSettings, actionLabel:
this.ctx.utilsService.customTranslation(getBehavior.name, getBehavior.name)};
const stateValueSubject = new BehaviorSubject<any>(getValueSettings.defaultValue);
this.stateValueSubjects[getBehavior.id] = stateValueSubject;
this.context.values[getBehavior.id] = getValueSettings.defaultValue;
stateValueSubject.subscribe((value) => {
stateValueSubject.pipe(takeUntil(this.destroy$)).subscribe((value) => {
this.onStateValueChanged(getBehavior.id, value);
});
const valueGetter =
@ -511,15 +511,15 @@ export class IotSvgObject {
}, this.simulated);
this.valueGetters.push(valueGetter);
this.valueActions.push(valueGetter);
} else if (behavior.type === IotSvgBehaviorType.action) {
const setBehavior = behavior as IotSvgBehaviorAction;
} else if (behavior.type === ScadaSymbolBehaviorType.action) {
const setBehavior = behavior as ScadaSymbolBehaviorAction;
let setValueSettings: SetValueSettings = this.settings.behavior[setBehavior.id];
setValueSettings = {...setValueSettings, actionLabel:
this.ctx.utilsService.customTranslation(setBehavior.name, setBehavior.name)};
const valueSetter = ValueSetter.fromSettings<any>(this.ctx, setValueSettings, this.simulated);
this.valueSetters[setBehavior.id] = valueSetter;
this.valueActions.push(valueSetter);
} else if (behavior.type === IotSvgBehaviorType.widgetAction) {
} else if (behavior.type === ScadaSymbolBehaviorType.widgetAction) {
// TODO:
}
}
@ -529,45 +529,49 @@ export class IotSvgObject {
this.valueGetters.forEach(valueGetter => {
getValueObservables.push(valueGetter.getValue());
});
this.loadingSubject.next(true);
forkJoin(getValueObservables).subscribe(
this.onLoadingState(true);
forkJoin(getValueObservables).pipe(takeUntil(this.destroy$)).subscribe(
{
next: () => {
this.loadingSubject.next(false);
this.onLoadingState(false);
},
error: () => {
this.loadingSubject.next(false);
this.onLoadingState(false);
}
}
);
}
}
private onLoadingState(loading: boolean) {
this.callbacks.onScadaSymbolObjectLoadingState(loading);
}
private onError(error: string) {
this.callbacks.onSvgObjectError(error);
this.callbacks.onScadaSymbolObjectError(error);
}
private onMessage(message: string) {
this.callbacks.onSvgObjectMessage(message);
this.callbacks.onScadaSymbolObjectMessage(message);
}
private callAction(event: Event, behaviorId: string, value?: any, observer?: Partial<Observer<void>>) {
const behavior = this.metadata.behavior.find(b => b.id === behaviorId);
if (behavior) {
if (behavior.type === IotSvgBehaviorType.action) {
if (behavior.type === ScadaSymbolBehaviorType.action) {
const valueSetter = this.valueSetters[behaviorId];
if (valueSetter) {
this.loadingSubject.next(true);
valueSetter.setValue(value).subscribe(
this.onLoadingState(true);
valueSetter.setValue(value).pipe(takeUntil(this.destroy$)).subscribe(
{
next: () => {
if (observer?.next) {
observer.next();
}
this.loadingSubject.next(false);
this.onLoadingState(false);
},
error: (err) => {
this.loadingSubject.next(false);
this.onLoadingState(false);
if (observer?.error) {
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];
if (this.simulated) {
const translatedType = this.ctx.translate.instant(widgetActionTypeTranslationMap.get(widgetAction.type));
@ -598,8 +602,10 @@ export class IotSvgObject {
const targetWidth = this.rootElement.getBoundingClientRect().width;
const targetHeight = this.rootElement.getBoundingClientRect().height;
if (targetWidth && targetHeight) {
const svgAspect = this.box.width / this.box.height;
const shapeAspect = targetWidth / targetHeight;
let scale: number;
if (targetWidth < targetHeight) {
if (svgAspect > shapeAspect) {
scale = targetWidth / this.box.width;
} else {
scale = targetHeight / this.box.height;
@ -617,7 +623,7 @@ export class IotSvgObject {
}
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);
this.setValue(valueBehavior.id, value);
}
@ -725,7 +731,7 @@ export class IotSvgObject {
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);
}
@ -734,9 +740,9 @@ export class IotSvgObject {
if (property) {
const value = this.settings.properties[id];
if (isDefinedAndNotNull(value)) {
if (property.type === IotSvgPropertyType.color_settings) {
if (property.type === ScadaSymbolPropertyType.color_settings) {
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 entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo();
return createLabelFromSubscriptionEntityInfo(entityInfo, result);
@ -744,13 +750,13 @@ export class IotSvgObject {
return value;
} else {
switch (property.type) {
case IotSvgPropertyType.text:
case ScadaSymbolPropertyType.text:
return '';
case IotSvgPropertyType.number:
case ScadaSymbolPropertyType.number:
return 0;
case IotSvgPropertyType.color:
case ScadaSymbolPropertyType.color:
return '#000';
case IotSvgPropertyType.color_settings:
case ScadaSymbolPropertyType.color_settings:
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.
-->
<ng-container [formGroup]="iotSvgObjectSettingsFormGroup">
<div class="iot-svg-object-title">{{ metadata?.title | customTranslate }}</div>
<ng-container [formGroup]="scadaSymbolObjectSettingsFormGroup">
<div *ngIf="metadata?.behavior?.length" class="tb-form-panel" formGroupName="behavior">
<div class="tb-form-panel-title" translate>scada.behavior.behavior</div>
<div *ngFor="let behaviour of metadata.behavior" class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{ behaviour.hint | customTranslate }}">{{ behaviour.name | customTranslate }}</div>
<tb-get-value-action-settings *ngIf="behaviour.type === IotSvgBehaviorType.value"
<div *ngFor="let behavior of metadata.behavior" class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{ behavior.hint | customTranslate }}">{{ behavior.name | customTranslate }}</div>
<tb-get-value-action-settings *ngIf="behavior.type === ScadaSymbolBehaviorType.value"
fxFlex
panelTitle="{{ behaviour.name | customTranslate }}"
[valueType]="behaviour.valueType"
[trueLabel]="behaviour.trueLabel | customTranslate"
[falseLabel]="behaviour.falseLabel | customTranslate"
[stateLabel]="behaviour.stateLabel | customTranslate"
panelTitle="{{ behavior.name | customTranslate }}"
[valueType]="behavior.valueType"
[trueLabel]="behavior.trueLabel | customTranslate"
[falseLabel]="behavior.falseLabel | customTranslate"
[stateLabel]="behavior.stateLabel | customTranslate"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="{{ behaviour.id }}">
formControlName="{{ behavior.id }}">
</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
panelTitle="{{ behaviour.name | customTranslate }}"
[valueType]="behaviour.valueType"
panelTitle="{{ behavior.name | customTranslate }}"
[valueType]="behavior.valueType"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="{{ behaviour.id }}">
formControlName="{{ behavior.id }}">
</tb-set-value-action-settings>
<tb-widget-action-settings *ngIf="behaviour.type === IotSvgBehaviorType.widgetAction"
<tb-widget-action-settings *ngIf="behavior.type === ScadaSymbolBehaviorType.widgetAction"
fxFlex
panelTitle="{{ behaviour.name | customTranslate }}"
panelTitle="{{ behavior.name | customTranslate }}"
[callbacks]="callbacks"
[widgetType]="widgetType"
formControlName="{{ behaviour.id }}">
formControlName="{{ behavior.id }}">
</tb-widget-action-settings>
</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 #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">
<mat-slide-toggle *ngIf="propertyRow.switch" class="mat-slide fixed-title-width" formControlName="{{ propertyRow.switch.id }}">
{{ propertyRow.label | customTranslate }}
@ -61,35 +61,35 @@
<div fxLayout="row" fxFlex fxLayoutAlign="end center" fxLayoutGap="8px">
<ng-container *ngFor="let property of propertyRow.properties">
<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 }}">
<div fxHide.lt-md matSuffix *ngIf="property.fieldSuffix">{{ property.fieldSuffix | customTranslate }}</div>
</mat-form-field>
<tb-color-input *ngIf="property.type === IotSvgPropertyType.color"
<tb-color-input *ngIf="property.type === ScadaSymbolPropertyType.color"
[required]="property.required"
[class]="property.fieldClass"
asBoxInput
colorClearButton
formControlName="{{ property.id }}">
</tb-color-input>
<tb-color-settings *ngIf="property.type === IotSvgPropertyType.color_settings"
<tb-color-settings *ngIf="property.type === ScadaSymbolPropertyType.color_settings"
[class]="property.fieldClass"
formControlName="{{ property.id }}"
settingsKey="{{ property.name | customTranslate }}">
</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"
[min]="property.min" [max]="property.max" [step]="property.step"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
<div fxHide.lt-md matSuffix *ngIf="property.fieldSuffix">{{ property.fieldSuffix | customTranslate }}</div>
</mat-form-field>
<tb-font-settings *ngIf="property.type === IotSvgPropertyType.font"
<tb-font-settings *ngIf="property.type === ScadaSymbolPropertyType.font"
[class]="property.fieldClass"
formControlName="{{ property.id }}"
clearButton
disabledLineHeight>
</tb-font-settings>
<tb-unit-input *ngIf="property.type === IotSvgPropertyType.units"
<tb-unit-input *ngIf="property.type === ScadaSymbolPropertyType.units"
[class]="property.fieldClass"
[required]="property.required"
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
* limitations under the License.
*/
:host {
.iot-svg-object-title {
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 24px;
}
.tb-scada-symbol-appearance-properties {
gap: 16px;
display: flex;
flex-direction: column;
}

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.
///
import { ChangeDetectorRef, Component, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import {
ChangeDetectorRef,
Component,
forwardRef,
Input,
OnChanges,
OnInit,
SimpleChanges,
ViewEncapsulation
} from '@angular/core';
import {
ControlValueAccessor,
NG_VALIDATORS,
@ -29,59 +38,60 @@ import {
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import {
defaultIotSvgObjectSettings,
IotSvgBehaviorType,
IotSvgMetadata,
IotSvgObjectSettings,
IotSvgPropertyType,
parseIotSvgMetadataFromContent
} from '@home/components/widget/lib/svg/iot-svg.models';
import { HttpClient } from '@angular/common/http';
defaultScadaSymbolObjectSettings,
parseScadaSymbolMetadataFromContent,
ScadaSymbolBehaviorType,
ScadaSymbolMetadata,
ScadaSymbolObjectSettings,
ScadaSymbolPropertyType
} from '@home/components/widget/lib/scada/scada-symbol.models';
import { IAliasController } from '@core/api/widget-api.models';
import { TargetDevice, widgetType } from '@shared/models/widget.models';
import { isDefinedAndNotNull, mergeDeep } from '@core/utils';
import {
IotSvgPropertyRow,
ScadaSymbolPropertyRow,
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 { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
import { ImageService } from '@core/http/image.service';
import { map } from 'rxjs/operators';
@Component({
selector: 'tb-iot-svg-object-settings',
templateUrl: './iot-svg-object-settings.component.html',
styleUrls: ['./iot-svg-object-settings.component.scss', './../../widget-settings.scss'],
selector: 'tb-scada-symbol-object-settings',
templateUrl: './scada-symbol-object-settings.component.html',
styleUrls: ['./scada-symbol-object-settings.component.scss', './../../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => IotSvgObjectSettingsComponent),
useExisting: forwardRef(() => ScadaSymbolObjectSettingsComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => IotSvgObjectSettingsComponent),
useExisting: forwardRef(() => ScadaSymbolObjectSettingsComponent),
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()
disabled: boolean;
@Input()
svgPath = 'drawing.svg';
scadaSymbolUrl: string;
@Input()
svgUrl: string;
scadaSymbolContent: string;
@Input()
svgContent: string;
scadaSymbolMetadata: ScadaSymbolMetadata;
@Input()
aliasController: IAliasController;
@ -95,31 +105,30 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
@Input()
widgetType: widgetType;
private modelValue: IotSvgObjectSettings;
private modelValue: ScadaSymbolObjectSettings;
private propagateChange = null;
private validatorTriggers: string[];
private validatorSubscription: Subscription;
public iotSvgObjectSettingsFormGroup: UntypedFormGroup;
public scadaSymbolObjectSettingsFormGroup: UntypedFormGroup;
metadata: IotSvgMetadata;
propertyRows: IotSvgPropertyRow[];
metadata: ScadaSymbolMetadata;
propertyRows: ScadaSymbolPropertyRow[];
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder,
private http: HttpClient,
private imageService: ImageService,
private cd: ChangeDetectorRef) {
}
ngOnInit(): void {
this.iotSvgObjectSettingsFormGroup = this.fb.group({
this.scadaSymbolObjectSettingsFormGroup = this.fb.group({
behavior: this.fb.group({}),
properties: this.fb.group({})
});
this.iotSvgObjectSettingsFormGroup.valueChanges.subscribe(() => {
this.scadaSymbolObjectSettingsFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
this.loadMetadata();
@ -129,7 +138,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (['svgPath', 'svgUrl', 'svgContent'].includes(propName)) {
if (['scadaSymbolUrl', 'scadaSymbolContent', 'scadaSymbolMetadata'].includes(propName)) {
this.loadMetadata();
}
}
@ -146,22 +155,22 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.iotSvgObjectSettingsFormGroup.disable({emitEvent: false});
this.scadaSymbolObjectSettingsFormGroup.disable({emitEvent: false});
} else {
this.iotSvgObjectSettingsFormGroup.enable({emitEvent: false});
this.scadaSymbolObjectSettingsFormGroup.enable({emitEvent: false});
this.updateValidators();
}
}
writeValue(value: IotSvgObjectSettings): void {
writeValue(value: ScadaSymbolObjectSettings): void {
this.modelValue = value || { behavior: {}, properties: {} };
this.setupValue();
}
validate(c: UntypedFormControl) {
const valid = this.iotSvgObjectSettingsFormGroup.valid;
validate(_c: UntypedFormControl) {
const valid = this.scadaSymbolObjectSettingsFormGroup.valid;
return valid ? null : {
iotSvgObject: {
scadaSymbolObjectSettings: {
valid: false,
},
};
@ -174,28 +183,36 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
}
this.validatorTriggers = [];
let svgContent$: Observable<string>;
if (this.svgContent) {
svgContent$ = of(this.svgContent);
} else if (this.svgUrl) {
svgContent$ = this.imageService.getImageString(this.svgUrl);
let metadata$: Observable<ScadaSymbolMetadata>;
if (this.scadaSymbolMetadata) {
metadata$ = of(this.scadaSymbolMetadata);
} 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(
(svgContent) => {
this.metadata = parseIotSvgMetadataFromContent(svgContent);
metadata$.subscribe(
(metadata) => {
this.metadata = metadata;
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)) {
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)) {
propertiesFormGroup.removeControl(control, {emitEvent: false});
}
for (const behaviour of this.metadata.behavior) {
behaviorFormGroup.addControl(behaviour.id, this.fb.control(null, []), {emitEvent: false});
for (const behavior of this.metadata.behavior) {
behaviorFormGroup.addControl(behavior.id, this.fb.control(null, []), {emitEvent: false});
}
for (const property of this.metadata.properties) {
if (property.disableOnProperty) {
@ -207,7 +224,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
if (property.required) {
validators.push(Validators.required);
}
if (property.type === IotSvgPropertyType.number) {
if (property.type === ScadaSymbolPropertyType.number) {
if (isDefinedAndNotNull(property.min)) {
validators.push(Validators.min(property.min));
}
@ -233,7 +250,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
}
private updateValidators() {
const propertiesFormGroup = this.iotSvgObjectSettingsFormGroup.get('properties') as UntypedFormGroup;
const propertiesFormGroup = this.scadaSymbolObjectSettingsFormGroup.get('properties') as UntypedFormGroup;
for (const trigger of this.validatorTriggers) {
const value: boolean = propertiesFormGroup.get(trigger).value;
this.metadata.properties.filter(p => p.disableOnProperty === trigger).forEach(
@ -251,9 +268,9 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
private setupValue() {
if (this.metadata) {
const defaults = defaultIotSvgObjectSettings(this.metadata);
this.modelValue = mergeDeep<IotSvgObjectSettings>(defaults, this.modelValue);
this.iotSvgObjectSettingsFormGroup.patchValue(
const defaults = defaultScadaSymbolObjectSettings(this.metadata);
this.modelValue = mergeDeep<ScadaSymbolObjectSettings>(defaults, this.modelValue);
this.scadaSymbolObjectSettingsFormGroup.patchValue(
this.modelValue, {emitEvent: false}
);
this.setDisabledState(this.disabled);
@ -261,7 +278,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
}
private updateModel() {
this.modelValue = this.iotSvgObjectSettingsFormGroup.getRawValue();
this.modelValue = this.scadaSymbolObjectSettingsFormGroup.getRawValue();
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.
///
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;
properties: IotSvgProperty[];
switch?: IotSvgProperty;
properties: ScadaSymbolProperty[];
switch?: ScadaSymbolProperty;
rowClass?: string;
}
export const toPropertyRows = (properties: IotSvgProperty[]): IotSvgPropertyRow[] => {
const result: IotSvgPropertyRow[] = [];
export const toPropertyRows = (properties: ScadaSymbolProperty[]): ScadaSymbolPropertyRow[] => {
const result: ScadaSymbolPropertyRow[] = [];
for (const property of properties) {
let propertyRow = result.find(r => r.label === property.name);
if (!propertyRow) {
@ -35,7 +35,7 @@ export const toPropertyRows = (properties: IotSvgProperty[]): IotSvgPropertyRow[
};
result.push(propertyRow);
}
if (property.type === IotSvgPropertyType.switch) {
if (property.type === ScadaSymbolPropertyType.switch) {
propertyRow.switch = property;
} else {
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';
import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/common/chart/chart-bar-settings.component';
import {
IotSvgObjectSettingsComponent
} from '@home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component';
ScadaSymbolObjectSettingsComponent
} from '@home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component';
@NgModule({
declarations: [
@ -207,7 +207,7 @@ import {
TimeSeriesChartStateRowComponent,
TimeSeriesChartGridSettingsComponent,
StatusWidgetStateSettingsComponent,
IotSvgObjectSettingsComponent,
ScadaSymbolObjectSettingsComponent,
DataKeyInputComponent,
EntityAliasInputComponent
],
@ -269,7 +269,7 @@ import {
TimeSeriesChartStateRowComponent,
TimeSeriesChartGridSettingsComponent,
StatusWidgetStateSettingsComponent,
IotSvgObjectSettingsComponent,
ScadaSymbolObjectSettingsComponent,
DataKeyInputComponent,
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 { PolarAreaWidgetComponent } from '@home/components/widget/lib/chart/polar-area-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({
declarations:
@ -166,7 +166,7 @@ import { IotSvgWidgetComponent } from '@home/components/widget/lib/svg/iot-svg-w
BarChartWidgetComponent,
PolarAreaWidgetComponent,
RadarChartWidgetComponent,
IotSvgWidgetComponent
ScadaSymbolWidgetComponent
],
imports: [
CommonModule,
@ -236,7 +236,7 @@ import { IotSvgWidgetComponent } from '@home/components/widget/lib/svg/iot-svg-w
BarChartWidgetComponent,
PolarAreaWidgetComponent,
RadarChartWidgetComponent,
IotSvgWidgetComponent
ScadaSymbolWidgetComponent
],
providers: [
{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 { ScadaSymbolComponent } from '@home/pages/scada-symbol/scada-symbol.component';
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()
export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
@ -64,7 +64,7 @@ export const scadaSymbolResolver: ResolveFn<ScadaSymbolData> =
const key = decodeURIComponent(route.params.key);
return forkJoin({
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: {
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
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>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type">
<mat-option *ngFor="let type of iotSvgBehaviorTypes" [value]="type">
{{ iotSvgBehaviorTypeTranslations.get(type) | translate }}
<mat-option *ngFor="let type of scadaSymbolBehaviorTypes" [value]="type">
{{ scadaSymbolBehaviorTypeTranslations.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</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>
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="flex tb-value-type">
<mat-select formControlName="valueType">
@ -63,7 +63,7 @@
</mat-select>
</mat-form-field>
</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="fixed-title-width" translate>scada.behavior.default-value</div>
<tb-value-input fxFlex [valueType]="behaviorFormGroup.get('valueType').value"
@ -90,7 +90,7 @@
</div>
</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-row no-padding no-border column-xs">
<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 { merge } from 'rxjs';
import {
IotSvgBehavior,
IotSvgBehaviorType,
iotSvgBehaviorTypes,
iotSvgBehaviorTypeTranslations
} from '@app/modules/home/components/widget/lib/svg/iot-svg.models';
ScadaSymbolBehavior,
ScadaSymbolBehaviorType,
scadaSymbolBehaviorTypes,
scadaSymbolBehaviorTypeTranslations
} from '@app/modules/home/components/widget/lib/scada/scada-symbol.models';
import { ValueType, valueTypesMap } from '@shared/models/constants';
import { ValueToDataType } from '@shared/models/action-widget-settings.models';
import { WidgetService } from '@core/http/widget.service';
@ -36,14 +36,14 @@ import { WidgetService } from '@core/http/widget.service';
})
export class ScadaSymbolBehaviorPanelComponent implements OnInit {
IotSvgBehaviorType = IotSvgBehaviorType;
ScadaSymbolBehaviorType = ScadaSymbolBehaviorType;
ValueType = ValueType;
ValueToDataType = ValueToDataType;
iotSvgBehaviorTypes = iotSvgBehaviorTypes;
iotSvgBehaviorTypeTranslations = iotSvgBehaviorTypeTranslations;
scadaSymbolBehaviorTypes = scadaSymbolBehaviorTypes;
scadaSymbolBehaviorTypeTranslations = scadaSymbolBehaviorTypeTranslations;
valueTypes = Object.keys(ValueType) as ValueType[];
@ -53,13 +53,13 @@ export class ScadaSymbolBehaviorPanelComponent implements OnInit {
isAdd = false;
@Input()
behavior: IotSvgBehavior;
behavior: ScadaSymbolBehavior;
@Input()
popover: TbPopoverComponent<ScadaSymbolBehaviorPanelComponent>;
@Output()
behaviorSettingsApplied = new EventEmitter<IotSvgBehavior>();
behaviorSettingsApplied = new EventEmitter<ScadaSymbolBehavior>();
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
@ -107,7 +107,7 @@ export class ScadaSymbolBehaviorPanelComponent implements OnInit {
}
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;
let valueToDataType: ValueToDataType = this.behaviorFormGroup.get('valueToDataType').value;
this.behaviorFormGroup.disable({emitEvent: false});
@ -116,7 +116,7 @@ export class ScadaSymbolBehaviorPanelComponent implements OnInit {
this.behaviorFormGroup.get('type').enable({emitEvent: false});
this.behaviorFormGroup.get('hint').enable({emitEvent: false});
switch (type) {
case IotSvgBehaviorType.value:
case ScadaSymbolBehaviorType.value:
this.behaviorFormGroup.get('valueType').enable({emitEvent: false});
this.behaviorFormGroup.get('defaultValue').enable({emitEvent: false});
if (valueType === ValueType.BOOLEAN) {
@ -125,7 +125,7 @@ export class ScadaSymbolBehaviorPanelComponent implements OnInit {
this.behaviorFormGroup.get('stateLabel').enable({emitEvent: false});
}
break;
case IotSvgBehaviorType.action:
case ScadaSymbolBehaviorType.action:
if (valueType === ValueType.BOOLEAN && valueToDataType === ValueToDataType.VALUE) {
this.behaviorFormGroup.patchValue({valueToDataType: ValueToDataType.CONSTANT}, {emitEvent: false});
valueToDataType = ValueToDataType.CONSTANT;
@ -141,7 +141,7 @@ export class ScadaSymbolBehaviorPanelComponent implements OnInit {
this.behaviorFormGroup.get('valueToDataFunction').enable({emitEvent: false});
}
break;
case IotSvgBehaviorType.widgetAction:
case ScadaSymbolBehaviorType.widgetAction:
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 class="tb-inline-field tb-type-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type">
<mat-option *ngFor="let type of iotSvgBehaviorTypes" [value]="type">
{{ iotSvgBehaviorTypeTranslations.get(type) | translate }}
<mat-option *ngFor="let type of scadaSymbolBehaviorTypes" [value]="type">
{{ scadaSymbolBehaviorTypeTranslations.get(type) | translate }}
</mat-option>
</mat-select>
</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
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor, NG_VALIDATORS,
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder, UntypedFormControl,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
ValidationErrors, Validator, ValidatorFn,
Validator,
ValidatorFn,
Validators
} from '@angular/forms';
import {
IotSvgBehavior,
IotSvgBehaviorType,
iotSvgBehaviorTypes,
iotSvgBehaviorTypeTranslations
} from '@home/components/widget/lib/svg/iot-svg.models';
import { deepClone, isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils';
ScadaSymbolBehavior,
ScadaSymbolBehaviorType,
scadaSymbolBehaviorTypes,
scadaSymbolBehaviorTypeTranslations
} from '@home/components/widget/lib/scada/scada-symbol.models';
import { deepClone, isUndefinedOrNull } from '@core/utils';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import {
@ -54,17 +56,17 @@ import {
ScadaSymbolBehaviorsComponent
} 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) {
return false;
}
switch (behavior.type) {
case IotSvgBehaviorType.value:
case ScadaSymbolBehaviorType.value:
if (!behavior.valueType || isUndefinedOrNull(behavior.defaultValue)) {
return false;
}
break;
case IotSvgBehaviorType.action:
case ScadaSymbolBehaviorType.action:
if (!behavior.valueToDataType) {
return false;
}
@ -77,7 +79,7 @@ export const behaviorValid = (behavior: IotSvgBehavior): boolean => {
return false;
}
break;
case IotSvgBehaviorType.widgetAction:
case ScadaSymbolBehaviorType.widgetAction:
break;
}
return true;
@ -109,8 +111,8 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
@ViewChild('editButton')
editButton: MatButton;
iotSvgBehaviorTypes = iotSvgBehaviorTypes;
iotSvgBehaviorTypeTranslations = iotSvgBehaviorTypeTranslations;
scadaSymbolBehaviorTypes = scadaSymbolBehaviorTypes;
scadaSymbolBehaviorTypeTranslations = scadaSymbolBehaviorTypeTranslations;
@Input()
disabled: boolean;
@ -123,7 +125,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
behaviorRowFormGroup: UntypedFormGroup;
modelValue: IotSvgBehavior;
modelValue: ScadaSymbolBehavior;
private propagateChange = (_val: any) => {};
@ -144,7 +146,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
this.behaviorRowFormGroup.valueChanges.subscribe(
() => this.updateModel()
);
this.behaviorRowFormGroup.get('type').valueChanges.subscribe((newType: IotSvgBehaviorType) => {
this.behaviorRowFormGroup.get('type').valueChanges.subscribe((newType: ScadaSymbolBehaviorType) => {
this.onTypeChanged(newType);
});
}
@ -153,7 +155,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
registerOnTouched(_fn: any): 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.behaviorRowFormGroup.patchValue(
{
@ -225,7 +227,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
this.editBehavior(null, this.editButton, true, onCanceled);
}
public validate(c: UntypedFormControl) {
public validate(_c: UntypedFormControl) {
const idControl = this.behaviorRowFormGroup.get('id');
if (idControl.hasError('behaviorIdNotUnique')) {
idControl.updateValueAndValidity({onlySelf: false, emitEvent: false});
@ -236,7 +238,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
behaviorIdNotUnique: true
};
}
const behavior: IotSvgBehavior = {...this.modelValue, ...this.behaviorRowFormGroup.value};
const behavior: ScadaSymbolBehavior = {...this.modelValue, ...this.behaviorRowFormGroup.value};
if (!behaviorValid(behavior)) {
return {
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;
this.modelValue = {...this.modelValue, ...{type: newType}};
if (!behaviorValid(this.modelValue)) {
@ -276,7 +278,7 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
}
private updateModel() {
const value: IotSvgBehavior = this.behaviorRowFormGroup.value;
const value: ScadaSymbolBehavior = this.behaviorRowFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
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,
Validator
} 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 { CdkDragDrop } from '@angular/cdk/drag-drop';
import {
@ -94,7 +94,7 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
});
this.behaviorsFormGroup.valueChanges.subscribe(
() => {
let behaviors: IotSvgBehavior[] = this.behaviorsFormGroup.get('behaviors').value;
let behaviors: ScadaSymbolBehavior[] = this.behaviorsFormGroup.get('behaviors').value;
if (behaviors) {
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 || [];
this.behaviorsFormGroup.setControl('behaviors', this.prepareBehaviorsFormArray(behaviors), {emitEvent: false});
}
@ -176,10 +176,10 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
}
addBehavior() {
const behavior: IotSvgBehavior = {
const behavior: ScadaSymbolBehavior = {
id: '',
name: '',
type: IotSvgBehaviorType.value,
type: ScadaSymbolBehaviorType.value,
valueType: ValueType.BOOLEAN,
defaultValue: false,
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> = [];
if (behaviors) {
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 { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { WidgetService } from '@core/http/widget.service';
import { TranslateService } from '@ngx-translate/core';
import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import { TbHighlightRule } from '@shared/models/ace/ace.models';
import {
@ -35,7 +34,7 @@ import {
scadaSymbolClickActionPropertiesHighlightRules,
scadaSymbolElementStateRenderHighlightRules,
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';
@Component({

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

@ -16,14 +16,12 @@
import {
Component,
EventEmitter,
forwardRef,
Input,
OnChanges,
OnInit,
Output, Renderer2,
SimpleChanges,
ViewChild, ViewContainerRef,
Renderer2,
ViewChild,
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import {
@ -34,26 +32,12 @@ import {
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validator,
Validators
Validator
} from '@angular/forms';
import { IotSvgTag } from '@home/components/widget/lib/svg/iot-svg.models';
import { MatExpansionPanel } from '@angular/material/expansion';
import { JsFuncComponent } from '@shared/components/js-func.component';
import { MatSelect } from '@angular/material/select';
import { ScadaSymbolTag } from '@home/components/widget/lib/scada/scada-symbol.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 { 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 {
ScadaSymbolMetadataTagFunctionPanelComponent
} 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;
modelValue: IotSvgTag;
modelValue: ScadaSymbolTag;
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;
const clickAction = value?.actions && value?.actions.click ? value.actions.click.actionFunction : null;
const clickAction = value?.actions?.click?.actionFunction;
this.tagFormGroup.patchValue(
{
tag: value?.tag,
@ -152,20 +136,10 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
editTagStateRenderFunction(): void {
this.openTagFunction('renderFunction', this.editStateRenderFunctionButton);
/*this.openPanelWithCallback(this.expansionPanel, () => {
this.openPanelWithCallback(this.renderFunctionExpansionPanel, () => {
this.stateRenderFunction.focus();
});
});*/
}
editClickAction(): void {
this.openTagFunction('clickAction', this.editClickActionButton);
/*this.openPanelWithCallback(this.expansionPanel, () => {
this.openPanelWithCallback(this.clickActionExpansionPanel, () => {
this.clickAction.focus();
});
});*/
}
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() {
const value = this.tagFormGroup.value;
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,
Validator
} 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 {
ScadaSymbolMetadataTagComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component';
import { TbEditorCompleter } from '@shared/models/ace/completion.models';
const tagIsEmpty = (tag: IotSvgTag): boolean =>
const tagIsEmpty = (tag: ScadaSymbolTag): boolean =>
!tag.stateRenderFunction && !tag.actions?.click?.actionFunction;
@Component({
@ -86,7 +86,7 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
tagsFormGroup: UntypedFormGroup;
private modelValue: IotSvgTag[];
private modelValue: ScadaSymbolTag[];
private propagateChange = (_val: any) => {};
@ -102,7 +102,7 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
this.tagsFormGroup.valueChanges.subscribe(
() => {
let value: IotSvgTag[] = this.tagsFormGroup.get('tags').value;
let value: ScadaSymbolTag[] = this.tagsFormGroup.get('tags').value;
if (value) {
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 || [];
const tagsResult= this.setupTags(this.modelValue);
this.tagsFormGroup.setControl('tags', this.prepareTagsFormArray(tagsResult.tags), {emitEvent: false});
@ -172,7 +172,7 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
editTagStateRenderFunction(tag: string): void {
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 tagComponent = this.metadataTags.get(index);
tagComponent?.editTagStateRenderFunction();
@ -181,29 +181,31 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
editTagClickAction(tag: string): void {
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 tagComponent = this.metadataTags.get(index);
tagComponent?.editClickAction();
});
}
private setupTags(existing?: IotSvgTag[]): {tags: IotSvgTag[]; emitEvent: boolean} {
private setupTags(existing?: ScadaSymbolTag[]): {tags: ScadaSymbolTag[]; emitEvent: boolean} {
existing = (existing || []).filter(t => !tagIsEmpty(t));
const result = (this.tags || []).sort().map(tag => ({
tag,
stateRenderFunction: null,
actions: {
click: {
actionFunction: null
}
}
actions: null
}));
for (const tag of existing) {
const found = result.find(t => t.tag === tag.tag);
if (found) {
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 =>
@ -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> = [];
if (tags) {
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
} from '@angular/forms';
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 { AppState } from '@core/core.state';
import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
@ -48,10 +48,10 @@ import {
clickActionFunctionCompletions,
elementStateRenderFunctionCompletions,
generalStateRenderFunctionCompletions,
iotSvgContextCompletion,
scadaSymbolContextCompletion,
scadaSymbolGeneralStateRenderHighlightRules,
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';
@Component({
@ -83,7 +83,7 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
@Input()
tags: string[];
private modelValue: IotSvgMetadata;
private modelValue: ScadaSymbolMetadata;
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.metadataFormGroup.patchValue(
value, {emitEvent: false}
@ -195,14 +195,14 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
}
private updateModel() {
const metadata: IotSvgMetadata = this.metadataFormGroup.getRawValue();
const metadata: ScadaSymbolMetadata = this.metadataFormGroup.getRawValue();
this.modelValue = metadata;
this.propagateChange(this.modelValue);
this.updateFunctionCompleters(metadata);
}
private updateFunctionCompleters(metadata: IotSvgMetadata) {
const contextCompleter = iotSvgContextCompletion(metadata, this.tags, this.customTranslate);
private updateFunctionCompleters(metadata: ScadaSymbolMetadata) {
const contextCompleter = scadaSymbolContextCompletion(metadata, this.tags, this.customTranslate);
const generalStateRender = generalStateRenderFunctionCompletions(contextCompleter);
if (!this.generalStateRenderFunctionCompleter) {
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,
Validator
} 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 {
propertyValid,
@ -94,11 +94,11 @@ export class ScadaSymbolPropertiesComponent implements ControlValueAccessor, OnI
});
this.propertiesFormGroup.valueChanges.subscribe(
() => {
let properties: IotSvgProperty[] = this.propertiesFormGroup.get('properties').value;
let properties: ScadaSymbolProperty[] = this.propertiesFormGroup.get('properties').value;
if (properties) {
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);
}
);
@ -120,10 +120,10 @@ export class ScadaSymbolPropertiesComponent implements ControlValueAccessor, OnI
}
}
writeValue(value: IotSvgProperty[] | undefined): void {
writeValue(value: ScadaSymbolProperty[] | undefined): void {
const properties= value || [];
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) {
@ -178,10 +178,10 @@ export class ScadaSymbolPropertiesComponent implements ControlValueAccessor, OnI
}
addProperty() {
const property: IotSvgProperty = {
const property: ScadaSymbolProperty = {
id: '',
name: '',
type: IotSvgPropertyType.text,
type: ScadaSymbolPropertyType.text,
default: ''
};
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> = [];
if (properties) {
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>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type">
<mat-option *ngFor="let type of iotSvgPropertyTypes" [value]="type">
{{ iotSvgPropertyTypeTranslations.get(type) | translate }}
<mat-option *ngFor="let type of scadaSymbolPropertyTypes" [value]="type">
{{ scadaSymbolPropertyTypeTranslations.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<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"
appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="default" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-color-input *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.color"
<tb-color-input *ngIf="propertyFormGroup.get('type').value === ScadaSymbolPropertyType.color"
asBoxInput
colorClearButton
formControlName="default">
</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"
settingsKey="{{ propertyFormGroup.get('id').value }}">
</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"
class="number"
subscriptSizing="dynamic">
@ -66,18 +66,18 @@
[step]="propertyFormGroup.get('step').value"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</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"
clearButton
disabledLineHeight>
</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>
<tb-value-input *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.switch"
<tb-value-input *ngIf="propertyFormGroup.get('type').value === ScadaSymbolPropertyType.switch"
[valueType]="ValueType.BOOLEAN"
formControlName="default"></tb-value-input>
</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 fxLayout="row" fxFlex fxLayoutAlign="end center" fxLayoutGap="8px">
<div class="tb-small-label" translate>scada.property.min</div>
@ -124,7 +124,7 @@
{{ 'scada.property.vertical-divider-after' | translate }}
</mat-slide-toggle>
</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">
<div class="fixed-title-width" translate>scada.property.input-field-suffix</div>
<mat-form-field class="flex"
@ -146,17 +146,17 @@
<div class="fixed-title-width" translate>scada.property.property-row-classes</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<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 }}
</mat-option>
</mat-select>
</mat-form-field>
</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>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<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 }}
</mat-option>
</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 { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import {
IotSvgProperty, iotSvgPropertyFieldClasses, iotSvgPropertyRowClasses,
IotSvgPropertyType,
iotSvgPropertyTypes,
iotSvgPropertyTypeTranslations
} from '@home/components/widget/lib/svg/iot-svg.models';
import { WidgetService } from '@core/http/widget.service';
ScadaSymbolProperty,
scadaSymbolPropertyFieldClasses,
scadaSymbolPropertyRowClasses,
ScadaSymbolPropertyType,
scadaSymbolPropertyTypes,
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 { ValueType } from '@shared/models/constants';
@ -37,20 +38,20 @@ export class ScadaSymbolPropertyPanelComponent implements OnInit {
ValueType = ValueType;
IotSvgPropertyType = IotSvgPropertyType;
ScadaSymbolPropertyType = ScadaSymbolPropertyType;
iotSvgPropertyTypes = iotSvgPropertyTypes;
iotSvgPropertyTypeTranslations = iotSvgPropertyTypeTranslations;
scadaSymbolPropertyTypes = scadaSymbolPropertyTypes;
scadaSymbolPropertyTypeTranslations = scadaSymbolPropertyTypeTranslations;
iotSvgPropertyRowClasses = iotSvgPropertyRowClasses;
scadaSymbolPropertyRowClasses = scadaSymbolPropertyRowClasses;
iotSvgPropertyFieldClasses = iotSvgPropertyFieldClasses;
scadaSymbolPropertyFieldClasses = scadaSymbolPropertyFieldClasses;
@Input()
isAdd = false;
@Input()
property: IotSvgProperty;
property: ScadaSymbolProperty;
@Input()
booleanPropertyIds: string[];
@ -59,16 +60,15 @@ export class ScadaSymbolPropertyPanelComponent implements OnInit {
popover: TbPopoverComponent<ScadaSymbolPropertyPanelComponent>;
@Output()
propertySettingsApplied = new EventEmitter<IotSvgProperty>();
propertySettingsApplied = new EventEmitter<ScadaSymbolProperty>();
panelTitle: string;
propertyFormGroup: UntypedFormGroup;
private propertyType: IotSvgPropertyType;
private propertyType: ScadaSymbolPropertyType;
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService) {
constructor(private fb: UntypedFormBuilder) {
}
ngOnInit(): void {
@ -110,8 +110,8 @@ export class ScadaSymbolPropertyPanelComponent implements OnInit {
}
private updateValidators() {
const type: IotSvgPropertyType = this.propertyFormGroup.get('type').value;
if (type === IotSvgPropertyType.number) {
const type: ScadaSymbolPropertyType = this.propertyFormGroup.get('type').value;
if (type === ScadaSymbolPropertyType.number) {
this.propertyFormGroup.get('min').enable({emitEvent: false});
this.propertyFormGroup.get('max').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 class="tb-inline-field tb-type-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type">
<mat-option *ngFor="let type of iotSvgPropertyTypes" [value]="type">
{{ iotSvgPropertyTypeTranslations.get(type) | translate }}
<mat-option *ngFor="let type of scadaSymbolPropertyTypes" [value]="type">
{{ scadaSymbolPropertyTypeTranslations.get(type) | translate }}
</mat-option>
</mat-select>
</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
} from '@angular/forms';
import {
IotSvgProperty,
IotSvgPropertyType,
iotSvgPropertyTypes,
iotSvgPropertyTypeTranslations
} from '@home/components/widget/lib/svg/iot-svg.models';
ScadaSymbolProperty,
ScadaSymbolPropertyType,
scadaSymbolPropertyTypes,
scadaSymbolPropertyTypeTranslations
} from '@home/components/widget/lib/scada/scada-symbol.models';
import { deepClone } from '@core/utils';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
@ -56,21 +56,21 @@ import {
ScadaSymbolPropertiesComponent
} 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) {
case IotSvgPropertyType.text:
case ScadaSymbolPropertyType.text:
return '';
case IotSvgPropertyType.number:
case ScadaSymbolPropertyType.number:
return 0;
case IotSvgPropertyType.switch:
case ScadaSymbolPropertyType.switch:
return false;
case IotSvgPropertyType.color:
case ScadaSymbolPropertyType.color:
return '#000';
case IotSvgPropertyType.color_settings:
case ScadaSymbolPropertyType.color_settings:
return constantColor('#000');
case IotSvgPropertyType.font:
case ScadaSymbolPropertyType.font:
return {
size: 12,
sizeUnit: 'px',
@ -79,7 +79,7 @@ export const defaultPropertyValue = (type: IotSvgPropertyType): any => {
style: 'normal',
lineHeight: '1'
} as Font;
case IotSvgPropertyType.units:
case ScadaSymbolPropertyType.units:
return '';
}
};
@ -110,8 +110,8 @@ export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, On
@ViewChild('editButton')
editButton: MatButton;
iotSvgPropertyTypes = iotSvgPropertyTypes;
iotSvgPropertyTypeTranslations = iotSvgPropertyTypeTranslations;
scadaSymbolPropertyTypes = scadaSymbolPropertyTypes;
scadaSymbolPropertyTypeTranslations = scadaSymbolPropertyTypeTranslations;
@Input()
disabled: boolean;
@ -127,7 +127,7 @@ export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, On
propertyRowFormGroup: UntypedFormGroup;
modelValue: IotSvgProperty;
modelValue: ScadaSymbolProperty;
private propagateChange = (_val: any) => {};
@ -148,7 +148,7 @@ export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, On
this.propertyRowFormGroup.valueChanges.subscribe(
() => this.updateModel()
);
this.propertyRowFormGroup.get('type').valueChanges.subscribe((newType: IotSvgPropertyType) => {
this.propertyRowFormGroup.get('type').valueChanges.subscribe((newType: ScadaSymbolPropertyType) => {
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.propertyRowFormGroup.patchValue(
{
@ -241,7 +241,7 @@ export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, On
propertyIdNotUnique: true
};
}
const property: IotSvgProperty = {...this.modelValue, ...this.propertyRowFormGroup.value};
const property: ScadaSymbolProperty = {...this.modelValue, ...this.propertyRowFormGroup.value};
if (!propertyValid(property)) {
return {
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.default = defaultPropertyValue(newType);
}
private updateModel() {
const value: IotSvgProperty = this.propertyRowFormGroup.value;
const value: ScadaSymbolProperty = this.propertyRowFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
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.
-->
<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
* limitations under the License.
*/
.tb-scada-symbol-shape {
.tb-scada-symbol-editor-shape {
width: 100%;
height: 100%;
display: flex;

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

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

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

@ -30,7 +30,7 @@ import {
ViewContainerRef,
ViewEncapsulation
} 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 { SharedModule } from '@shared/shared.module';
import { ENTER } from '@angular/cdk/keycodes';
@ -234,7 +234,7 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements
{
zIndex: 200,
arrow: true,
theme: ['iot-svg', 'tb-active'],
theme: ['scada-symbol', 'tb-active'],
interactive: true,
trigger: 'click',
side: 'top',
@ -254,7 +254,7 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements
{
zIndex: 200,
arrow: true,
theme: ['iot-svg', 'tb-active'],
theme: ['scada-symbol', 'tb-active'],
interactive: true,
trigger: 'click',
side: 'top',

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

@ -15,11 +15,11 @@
limitations under the License.
-->
<div class="tb-scada-symbol mat-content" fxLayout="column">
<section class="tb-scada-symbol-container" fxFlex fxLayout="column">
<div class="tb-scada-symbol-layout" fxFlex fxLayout="row">
<div class="tb-scada-symbol-editor mat-content" fxLayout="column">
<section class="tb-scada-symbol-editor-container" fxFlex fxLayout="column">
<div class="tb-scada-symbol-editor-layout" fxFlex fxLayout="row">
<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"
mode="side"
opened
@ -29,8 +29,8 @@
[showCloseDetails]="false"
headerHeightPx="64"
headerTitle="{{symbolData?.imageResource?.title}}">
<div *ngIf="previewMode" class="tb-scada-symbol-preview-content tb-absolute-fill">
<div class="tb-scada-symbol-preview-header">
<div *ngIf="previewMode" class="tb-scada-symbol-editor-preview-content tb-absolute-fill">
<div class="tb-scada-symbol-editor-preview-header">
<button mat-button
(click)="exitPreviewMode()">
<mat-icon>chevron_left</mat-icon>
@ -50,15 +50,22 @@
{{ 'action.apply' | translate }}
</button>
</div>
<div [formGroup]="scadaPreviewFormGroup" class="tb-scada-symbol-preview-settings">
<div [formGroup]="scadaPreviewFormGroup" class="tb-scada-symbol-editor-preview-settings">
<div class="mat-content">
<tb-iot-svg-object-settings
formControlName="iotSvgObject"
[svgContent]="symbolData.svgContent"
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>scada.symbol</div>
<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"
[widgetType]="widgetType.rpc"
[callbacks]="widgetActionCallbacks">
</tb-iot-svg-object-settings>
</tb-scada-symbol-object-settings>
</div>
</div>
</div>
@ -103,7 +110,7 @@
</div>
</tb-details-panel>
</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
[data]="symbolEditorData"
[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';
.tb-scada-symbol {
.tb-scada-symbol-editor {
width: 100%;
height: 100%;
.tb-scada-symbol-details-drawer {
.tb-scada-symbol-editor-details-drawer {
width: 50%;
.tb-scada-symbol-preview-content {
.tb-scada-symbol-editor-preview-content {
display: flex;
flex-direction: column;
gap: 8px;
.tb-scada-symbol-preview-header {
.tb-scada-symbol-editor-preview-header {
padding: 24px 24px 0;
display: flex;
gap: 12px;
@ -33,7 +33,7 @@
align-items: center;
justify-content: space-between;
}
.tb-scada-symbol-preview-settings {
.tb-scada-symbol-editor-preview-settings {
& > .mat-content {
padding-top: 8px;
@media #{$mat-xs} {
@ -57,13 +57,11 @@
}
}
}
.tb-scada-symbol-content {
.tb-scada-symbol-editor-content {
flex: 1;
min-width: 0;
min-height: 0;
max-width: 50%;
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 { Store } from '@ngrx/store';
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 { 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 {
IotSvgMetadata, IotSvgObjectSettings,
parseIotSvgMetadataFromContent,
updateIotSvgMetadataInContent
} from '@home/components/widget/lib/svg/iot-svg.models';
parseScadaSymbolMetadataFromContent,
ScadaSymbolMetadata,
ScadaSymbolObjectSettings,
updateScadaSymbolMetadataInContent
} from '@home/components/widget/lib/scada/scada-symbol.models';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { createFileFromContent, deepClone } from '@core/utils';
import {
@ -55,9 +56,9 @@ import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { Widget, widgetType } from '@shared/models/widget.models';
import {
iotSvgWidgetDefaultSettings,
IotSvgWidgetSettings
} from '@home/components/widget/lib/svg/iot-svg-widget.models';
scadaSymbolWidgetDefaultSettings,
ScadaSymbolWidgetSettings
} from '@home/components/widget/lib/scada/scada-symbol-widget.models';
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
import {
ScadaSymbolMetadataComponent
@ -87,6 +88,7 @@ export class ScadaSymbolComponent extends PageComponent
symbolEditorData: ScadaSymbolEditorData;
previewMode = false;
previewMetadata: ScadaSymbolMetadata;
scadaSymbolFormGroup: UntypedFormGroup;
@ -113,7 +115,7 @@ export class ScadaSymbolComponent extends PageComponent
symbolEditorDirty = false;
private previewIotSvgObjectSettings: IotSvgObjectSettings;
private previewScadaSymbolObjectSettings: ScadaSymbolObjectSettings;
private previewWidget: Widget;
@ -128,7 +130,6 @@ export class ScadaSymbolComponent extends PageComponent
}
constructor(protected store: Store<AppState>,
private router: Router,
private route: ActivatedRoute,
private fb: UntypedFormBuilder,
private cd: ChangeDetectorRef,
@ -144,7 +145,7 @@ export class ScadaSymbolComponent extends PageComponent
metadata: [null]
});
this.scadaPreviewFormGroup = this.fb.group({
iotSvgObject: [null]
scadaSymbolObjectSettings: [null]
});
const entitiAliases: EntityAliases = {};
@ -182,13 +183,13 @@ export class ScadaSymbolComponent extends PageComponent
onApplyScadaSymbolConfig() {
if (this.scadaSymbolFormGroup.valid) {
const svgContent = this.prepareSvgContent();
const file = createFileFromContent(svgContent, this.symbolData.imageResource.fileName,
const metadata: ScadaSymbolMetadata = this.scadaSymbolFormGroup.get('metadata').value;
const scadaSymbolContent = this.prepareScadaSymbolContent(metadata);
const file = createFileFromContent(scadaSymbolContent, this.symbolData.imageResource.fileName,
this.symbolData.imageResource.descriptor.mediaType);
const type = imageResourceType(this.symbolData.imageResource);
let imageInfoObservable =
this.imageService.updateImage(type, this.symbolData.imageResource.resourceKey, file);
const metadata: IotSvgMetadata = this.scadaSymbolFormGroup.get('metadata').value;
if (metadata.title !== this.symbolData.imageResource.title) {
imageInfoObservable = imageInfoObservable.pipe(
switchMap(imageInfo => {
@ -202,7 +203,7 @@ export class ScadaSymbolComponent extends PageComponent
`${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(imageInfo.resourceKey)}`).pipe(
map(content => ({
imageResource: imageInfo,
svgContent: content
scadaSymbolContent: content
}))
))
).subscribe(data => {
@ -217,25 +218,27 @@ export class ScadaSymbolComponent extends PageComponent
}
enterPreviewMode() {
this.symbolData.svgContent = this.prepareSvgContent();
this.previewIotSvgObjectSettings = {
this.previewMetadata = this.scadaSymbolFormGroup.get('metadata').value;
this.symbolData.scadaSymbolContent = this.prepareScadaSymbolContent(this.previewMetadata);
this.previewScadaSymbolObjectSettings = {
behavior: {},
properties: {}
};
this.scadaPreviewFormGroup.patchValue({
iotSvgObject: this.previewIotSvgObjectSettings
scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings
}, {emitEvent: false});
this.scadaPreviewFormGroup.markAsPristine();
const settings: IotSvgWidgetSettings = {...iotSvgWidgetDefaultSettings,
const settings: ScadaSymbolWidgetSettings = {...scadaSymbolWidgetDefaultSettings,
...{
simulated: true,
iotSvg: null,
iotSvgContent: this.symbolData.svgContent,
iotSvgObject: this.previewIotSvgObjectSettings
scadaSymbolUrl: null,
scadaSymbolContent: this.symbolData.scadaSymbolContent,
scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings,
padding: '0'
}
};
this.previewWidget = {
typeFullFqn: 'system.iot_svg',
typeFullFqn: 'system.scada_symbol',
type: widgetType.rpc,
sizeX: 24,
sizeY: 24,
@ -255,21 +258,21 @@ export class ScadaSymbolComponent extends PageComponent
exitPreviewMode() {
this.symbolEditorData = {
svgContent: this.symbolData.svgContent
scadaSymbolContent: this.symbolData.scadaSymbolContent
};
this.previewMode = false;
}
onRevertPreviewSettings() {
this.scadaPreviewFormGroup.patchValue({
iotSvgObject: this.previewIotSvgObjectSettings
scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings
}, {emitEvent: false});
this.scadaPreviewFormGroup.markAsPristine();
}
onApplyPreviewSettings() {
this.scadaPreviewFormGroup.markAsPristine();
this.previewIotSvgObjectSettings = this.scadaPreviewFormGroup.get('iotSvgObject').value;
this.previewScadaSymbolObjectSettings = this.scadaPreviewFormGroup.get('scadaSymbolObjectSettings').value;
this.updatePreviewWidgetSettings();
}
@ -278,7 +281,7 @@ export class ScadaSymbolComponent extends PageComponent
}
tagHasStateRenderFunction(tag: string): boolean {
const metadata: IotSvgMetadata = this.scadaSymbolFormGroup.get('metadata').value;
const metadata: ScadaSymbolMetadata = this.scadaSymbolFormGroup.get('metadata').value;
if (metadata.tags) {
const found = metadata.tags.find(t => t.tag === tag);
return !!found?.stateRenderFunction;
@ -287,7 +290,7 @@ export class ScadaSymbolComponent extends PageComponent
}
tagHasClickAction(tag: string): boolean {
const metadata: IotSvgMetadata = this.scadaSymbolFormGroup.get('metadata').value;
const metadata: ScadaSymbolMetadata = this.scadaSymbolFormGroup.get('metadata').value;
if (metadata.tags) {
const found = metadata.tags.find(t => t.tag === tag);
return !!found?.actions?.click?.actionFunction;
@ -309,14 +312,13 @@ export class ScadaSymbolComponent extends PageComponent
private updatePreviewWidgetSettings() {
this.previewWidget = deepClone(this.previewWidget);
this.previewWidget.config.settings.iotSvgObject = this.previewIotSvgObjectSettings;
this.previewWidget.config.settings.scadaSymbolObjectSettings = this.previewScadaSymbolObjectSettings;
this.previewWidgets = [this.previewWidget];
}
private prepareSvgContent(): string {
private prepareScadaSymbolContent(metadata: ScadaSymbolMetadata): string {
const svgContent = this.symbolEditor.getContent();
const metadata: IotSvgMetadata = this.scadaSymbolFormGroup.get('metadata').value;
return updateIotSvgMetadataInContent(svgContent, metadata);
return updateScadaSymbolMetadataInContent(svgContent, metadata);
}
private reset(): void {
@ -330,9 +332,9 @@ export class ScadaSymbolComponent extends PageComponent
this.origSymbolData = data;
this.symbolData = deepClone(data);
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({
metadata
}, {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();
if (this.linkType === ImageLinkType.resource) {
const params = extractParamsFromImageResourceUrl(this.imageUrl);
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();
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();
}
} else if (this.linkType === ImageLinkType.base64) {
this.cd.markForCheck();
} 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';
get isScada() {
return this.imageSubType === ResourceSubType.IOT_SVG;
return this.imageSubType === ResourceSubType.SCADA_SYMBOL;
}
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 {
emptyMetadata,
IotSvgMetadata,
parseIotSvgMetadataFromContent,
updateIotSvgMetadataInContent
} from '@home/components/widget/lib/svg/iot-svg.models';
ScadaSymbolMetadata,
parseScadaSymbolMetadataFromContent,
updateScadaSymbolMetadataInContent
} from '@home/components/widget/lib/scada/scada-symbol.models';
export interface UploadImageDialogData {
imageSubType: ResourceSubType;
@ -64,11 +64,11 @@ export class UploadImageDialogComponent extends
maxResourceSize = getCurrentAuthState(this.store).maxResourceSize;
get isScada() {
return this.data.imageSubType === ResourceSubType.IOT_SVG;
return this.data.imageSubType === ResourceSubType.SCADA_SYMBOL;
}
private iotSvgContent: string;
private iotSvgMetadata: IotSvgMetadata;
private scadaSymbolContent: string;
private scadaSymbolMetadata: ScadaSymbolMetadata;
constructor(protected store: Store<AppState>,
protected router: Router,
@ -91,11 +91,11 @@ export class UploadImageDialogComponent extends
this.uploadImageFormGroup.get('file').valueChanges.subscribe((file: File) => {
if (file) {
blobToText(file).subscribe(content => {
this.iotSvgContent = content;
this.iotSvgMetadata = parseIotSvgMetadataFromContent(this.iotSvgContent);
this.scadaSymbolContent = content;
this.scadaSymbolMetadata = parseScadaSymbolMetadataFromContent(this.scadaSymbolContent);
const titleControl = this.uploadImageFormGroup.get('title');
if (this.iotSvgMetadata.title && (!titleControl.value || !titleControl.touched)) {
titleControl.setValue(this.iotSvgMetadata.title);
if (this.scadaSymbolMetadata.title && (!titleControl.value || !titleControl.touched)) {
titleControl.setValue(this.scadaSymbolMetadata.title);
}
});
}
@ -129,13 +129,13 @@ export class UploadImageDialogComponent extends
if (this.uploadImage) {
const title: string = this.uploadImageFormGroup.get('title').value;
if (this.isScada) {
if (!this.iotSvgMetadata) {
this.iotSvgMetadata = emptyMetadata();
if (!this.scadaSymbolMetadata) {
this.scadaSymbolMetadata = emptyMetadata();
}
if (this.iotSvgMetadata.title !== title) {
this.iotSvgMetadata.title = title;
if (this.scadaSymbolMetadata.title !== title) {
this.scadaSymbolMetadata.title = title;
}
const newContent = updateIotSvgMetadataInContent(this.iotSvgContent, this.iotSvgMetadata);
const newContent = updateScadaSymbolMetadataInContent(this.scadaSymbolContent, this.scadaSymbolMetadata);
file = updateFileContent(file, newContent);
}
forkJoin([

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

@ -29,7 +29,7 @@ export enum ResourceType {
export enum ResourceSubType {
IMAGE = 'IMAGE',
IOT_SVG = 'IOT_SVG'
SCADA_SYMBOL = 'SCADA_SYMBOL'
}
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} => {
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);

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 { HexInputComponent } from '@shared/components/color-picker/hex-input.component';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import { ScadaSymbolInputComponent } from '@shared/components/image/scada-symbol-input.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@ -421,7 +422,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
EmbedImageDialogComponent,
ImageGalleryDialogComponent,
WidgetButtonComponent,
HexInputComponent
HexInputComponent,
ScadaSymbolInputComponent
],
imports: [
CommonModule,
@ -675,7 +677,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
MultipleGalleryImageInputComponent,
EmbedImageDialogComponent,
ImageGalleryDialogComponent,
WidgetButtonComponent
WidgetButtonComponent,
ScadaSymbolInputComponent
]
})
export class SharedModule { }

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

@ -3415,6 +3415,10 @@
"state-render-function": "State render function",
"preview": "Preview",
"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",
"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 {
user-select: none;
background: rgba(0, 0, 0, 0.54);

Loading…
Cancel
Save