diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 43f1ab4886..5b6b928015 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/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", diff --git a/application/src/main/data/json/system/widget_bundles/scada_symbols.json b/application/src/main/data/json/system/widget_bundles/scada_symbols.json new file mode 100644 index 0000000000..47615e6137 --- /dev/null +++ b/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" + ] +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/iot_svg.json b/application/src/main/data/json/system/widget_types/iot_svg.json deleted file mode 100644 index fde568de95..0000000000 --- a/application/src/main/data/json/system/widget_types/iot_svg.json +++ /dev/null @@ -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": "\n", - "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '280px',\n previewHeight: '280px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n", - "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" - ] -} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/scada_symbol.json b/application/src/main/data/json/system/widget_types/scada_symbol.json new file mode 100644 index 0000000000..e7e56176f9 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/scada_symbol.json @@ -0,0 +1,28 @@ +{ + "fqn": "scada_symbol", + "name": "SCADA symbol", + "deprecated": false, + "image": null, + "description": "SCADA symbol widget", + "descriptor": { + "type": "rpc", + "sizeX": 3.5, + "sizeY": 3.5, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '280px',\n previewHeight: '280px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "", + "dataKeySettingsSchema": "{}\n", + "settingsDirective": "", + "hasBasicMode": true, + "basicModeDirective": "tb-scada-symbol-basic-config", + "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"iotSvgObject\":{\"behavior\":{},\"properties\":{}},\"background\":{\"type\":\"color\",\"imageUrl\":null,\"color\":\"rgb(255, 255, 255)\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"padding\":\"12px\",\"scadaSymbolUrl\":\"tb-image:ZHJhd2luZy5zdmc=:TGV2ZWwgYW5kIEZhbg==:U0NBREFfU1lNQk9M;data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnRiPSJodHRwczovL3RoaW5nc2JvYXJkLmlvL3N2ZyIgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDEwMCAxMDAiPgo8dGI6bWV0YWRhdGEgeG1sbnM9IiI+PCFbQ0RBVEFbewogICJ0aXRsZSI6ICJMZXZlbCBhbmQgRmFuIiwKICAic3RhdGVSZW5kZXJGdW5jdGlvbiI6ICJ2YXIgc2hvd01pbk1heExldmVsID0gY3R4LnByb3BlcnRpZXMuc2hvd01pbk1heExldmVsO1xudmFyIG1pbkxldmVsRWxlbWVudCA9IGN0eC50YWdzLm1pbkxldmVsWzBdO1xudmFyIG1heExldmVsRWxlbWVudCA9IGN0eC50YWdzLm1heExldmVsWzBdO1xudmFyIG1pbkxldmVsID0gY3R4LnByb3BlcnRpZXMubWluTGV2ZWw7IFxudmFyIG1heExldmVsID0gY3R4LnByb3BlcnRpZXMubWF4TGV2ZWw7XG5cbmlmIChzaG93TWluTWF4TGV2ZWwpIHtcbiAgIFxuICAgdmFyIG1pbk1heExldmVsRm9udCA9IGN0eC5wcm9wZXJ0aWVzLm1pbk1heExldmVsRm9udDtcbiAgIHZhciBtaW5NYXhMZXZlbENvbG9yID0gY3R4LnByb3BlcnRpZXMubWluTWF4TGV2ZWxDb2xvcjtcbiAgIFxuICAgY3R4LmFwaS50ZXh0KG1pbkxldmVsRWxlbWVudCwgbWluTGV2ZWwpO1xuICAgY3R4LmFwaS50ZXh0KG1heExldmVsRWxlbWVudCwgbWF4TGV2ZWwpO1xuICAgXG4gICBjdHguYXBpLmZvbnQobWluTGV2ZWxFbGVtZW50LCBtaW5NYXhMZXZlbEZvbnQsIG1pbk1heExldmVsQ29sb3IpO1xuICAgY3R4LmFwaS5mb250KG1heExldmVsRWxlbWVudCwgbWluTWF4TGV2ZWxGb250LCBtaW5NYXhMZXZlbENvbG9yKTtcbiAgICAgIFxufSBlbHNlIHtcbiAgIG1pbkxldmVsRWxlbWVudC5oaWRlKCk7XG4gICBtYXhMZXZlbEVsZW1lbnQuaGlkZSgpO1xufVxuXG52YXIgZGlzYWJsZWQgPSBjdHgudmFsdWVzLmRpc2FibGVkO1xudmFyIG9uID0gY3R4LnZhbHVlcy5vbjtcbnZhciBsZXZlbCA9IGN0eC52YWx1ZXMubGV2ZWw7XG5cbnZhciBvbkJ1dHRvbiA9IGN0eC50YWdzLm9uQnV0dG9uO1xudmFyIG9mZkJ1dHRvbiA9IGN0eC50YWdzLm9mZkJ1dHRvbjtcbnZhciBsZXZlbFVwQnV0dG9uID0gY3R4LnRhZ3MubGV2ZWxVcEJ1dHRvbjtcbnZhciBsZXZlbERvd25CdXR0b24gPSBjdHgudGFncy5sZXZlbERvd25CdXR0b247XG5cbnZhciBvbkJ1dHRvbkVuYWJsZWQgPSAhZGlzYWJsZWQgJiYgIW9uO1xudmFyIG9mZkJ1dHRvbkVuYWJsZWQgPSAhZGlzYWJsZWQgJiYgb247XG52YXIgbGV2ZWxVcEVuYWJsZWQgPSAhZGlzYWJsZWQgJiYgbGV2ZWwgPCBtYXhMZXZlbDtcbnZhciBsZXZlbERvd25FbmFibGVkID0gIWRpc2FibGVkICYmIGxldmVsID4gbWluTGV2ZWw7XG5cbmlmIChvbkJ1dHRvbkVuYWJsZWQpIHtcbiAgICBjdHguYXBpLmVuYWJsZShvbkJ1dHRvbik7XG4gICAgb25CdXR0b25bMF0uZmluZE9uZSgncmVjdCcpLmF0dHIoe2ZpbGw6ICcjMTJlZDE5J30pO1xufSBlbHNlIHtcbiAgICBjdHguYXBpLmRpc2FibGUob25CdXR0b24pO1xuICAgIG9uQnV0dG9uWzBdLmZpbmRPbmUoJ3JlY3QnKS5hdHRyKHtmaWxsOiAnIzc3Nyd9KTtcbn1cbiAgIFxuaWYgKG9mZkJ1dHRvbkVuYWJsZWQpIHtcbiAgICBjdHguYXBpLmVuYWJsZShvZmZCdXR0b24pO1xuICAgIG9mZkJ1dHRvblswXS5maW5kT25lKCdyZWN0JykuYXR0cih7ZmlsbDogJyNlZDEyMWYnfSk7XG59IGVsc2Uge1xuICAgIGN0eC5hcGkuZGlzYWJsZShvZmZCdXR0b24pO1xuICAgIG9mZkJ1dHRvblswXS5maW5kT25lKCdyZWN0JykuYXR0cih7ZmlsbDogJyM3NzcnfSk7XG59ICAgIFxuXG5cbmlmIChsZXZlbFVwRW5hYmxlZCkge1xuICAgIGN0eC5hcGkuZW5hYmxlKGxldmVsVXBCdXR0b24pO1xuICAgIGxldmVsVXBCdXR0b25bMF0uZmluZE9uZSgncmVjdCcpLmF0dHIoe2ZpbGw6ICcjZmZmJ30pO1xufSBlbHNlIHtcbiAgICBjdHguYXBpLmRpc2FibGUobGV2ZWxVcEJ1dHRvbik7XG4gICAgbGV2ZWxVcEJ1dHRvblswXS5maW5kT25lKCdyZWN0JykuYXR0cih7ZmlsbDogJyM3NzcnfSk7XG59XG4gICBcbmlmIChsZXZlbERvd25FbmFibGVkKSB7XG4gICAgY3R4LmFwaS5lbmFibGUobGV2ZWxEb3duQnV0dG9uKTtcbiAgICBsZXZlbERvd25CdXR0b25bMF0uZmluZE9uZSgncmVjdCcpLmF0dHIoe2ZpbGw6ICcjZmZmJ30pO1xufSBlbHNlIHtcbiAgICBjdHguYXBpLmRpc2FibGUobGV2ZWxEb3duQnV0dG9uKTtcbiAgICBsZXZlbERvd25CdXR0b25bMF0uZmluZE9uZSgncmVjdCcpLmF0dHIoe2ZpbGw6ICcjNzc3J30pO1xufSIsCiAgInRhZ3MiOiBbCiAgICB7CiAgICAgICJ0YWciOiAiZmFuIiwKICAgICAgInN0YXRlUmVuZGVyRnVuY3Rpb24iOiAidmFyIG9uID0gY3R4LnZhbHVlcy5vbjtcbmlmIChvbikge1xuICAgIHZhciBsZXZlbCA9IGN0eC52YWx1ZXMubGV2ZWw7IFxuICAgIHZhciBtaW5MZXZlbCA9IGN0eC5wcm9wZXJ0aWVzLm1pbkxldmVsOyBcbiAgICB2YXIgbWF4TGV2ZWwgPSBjdHgucHJvcGVydGllcy5tYXhMZXZlbDtcblxuICAgIHZhciBzcGVlZCA9IChsZXZlbCAtIG1pbkxldmVsKSAvIChtYXhMZXZlbCAtIG1pbkxldmVsKTtcbiAgICBzcGVlZCA9IE1hdGgubWF4KDAsIE1hdGgubWluKDEsIHNwZWVkKSkqMjsgXG5cbiAgICB2YXIgdCA9IGVsZW1lbnQudGltZWxpbmUoKTtcbiAgICBpZiAoIXQuYWN0aXZlKCkpIHtcbiAgICAgIGN0eC5hcGkuYW5pbWF0ZShlbGVtZW50LCAxMDAwKS5lYXNlKCctJykucm90YXRlKDM2MCkubG9vcCgpO1xuICAgICAgdCA9IGVsZW1lbnQudGltZWxpbmUoKTtcbiAgICB9XG4gICAgdC5zcGVlZChzcGVlZCk7XG59IGVsc2Uge1xuICAgIGVsZW1lbnQudGltZWxpbmUoKS5zdG9wKCk7XG59XG4iLAogICAgICAiYWN0aW9ucyI6IG51bGwKICAgIH0sCiAgICB7CiAgICAgICJ0YWciOiAibGV2ZWwiLAogICAgICAic3RhdGVSZW5kZXJGdW5jdGlvbiI6ICJ2YXIgbGV2ZWwgPSBjdHgudmFsdWVzLmxldmVsOyBcbnZhciBkaXNhYmxlZCA9IGN0eC52YWx1ZXMuZGlzYWJsZWQ7XG52YXIgbWluTGV2ZWwgPSBjdHgucHJvcGVydGllcy5taW5MZXZlbDsgXG52YXIgbWF4TGV2ZWwgPSBjdHgucHJvcGVydGllcy5tYXhMZXZlbDtcblxudmFyIGhlaWdodCA9IChsZXZlbCAtIG1pbkxldmVsKSAvIChtYXhMZXZlbCAtIG1pbkxldmVsKTtcbmhlaWdodCA9IE1hdGgubWF4KDAsIE1hdGgubWluKDEsIGhlaWdodCkpKjgwOyBcblxuY3R4LmFwaS5hbmltYXRlKGVsZW1lbnQsIDIwMCkuYXR0cih7aGVpZ2h0OiBoZWlnaHR9KTtcblxudmFyIGZpbGw7XG5pZiAoZGlzYWJsZWQpIHtcbiAgZmlsbCA9IGN0eC5wcm9wZXJ0aWVzLmRpc2FibGVkTGV2ZWxCYWNrZ3JvdW5kO1xufSBlbHNlIHtcbiAgdmFyIGNvbG9yUHJvY2Vzc29yID0gY3R4LnByb3BlcnRpZXMubGV2ZWxCYWNrZ3JvdW5kO1xuICBjb2xvclByb2Nlc3Nvci51cGRhdGUobGV2ZWwpO1xuICBmaWxsID0gY29sb3JQcm9jZXNzb3IuY29sb3I7XG59XG5cbmVsZW1lbnQuYXR0cih7ZmlsbDogZmlsbH0pOyIsCiAgICAgICJhY3Rpb25zIjogbnVsbAogICAgfSwKICAgIHsKICAgICAgInRhZyI6ICJsZXZlbERvd25CdXR0b24iLAogICAgICAiYWN0aW9ucyI6IHsKICAgICAgICAiY2xpY2siOiB7CiAgICAgICAgICAiYWN0aW9uRnVuY3Rpb24iOiAidmFyIGxldmVsID0gY3R4LnZhbHVlcy5sZXZlbDsgXG52YXIgbWluTGV2ZWwgPSBjdHgucHJvcGVydGllcy5taW5MZXZlbDtcblxudmFyIG5ld0xldmVsID0gTWF0aC5tYXgobWluTGV2ZWwsIGxldmVsIC0gNSk7XG5jdHguYXBpLnNldFZhbHVlKCdsZXZlbCcsIG5ld0xldmVsKTtcbmN0eC5hcGkuY2FsbEFjdGlvbihldmVudCwgJ2xldmVsVXBkYXRlU3RhdGUnLCBuZXdMZXZlbCwge1xuICBlcnJvcjogKCkgPT4ge1xuICAgICBjdHguYXBpLnNldFZhbHVlKCdsZXZlbCcsIGxldmVsKTtcbiAgfVxufSk7IgogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgIHsKICAgICAgInRhZyI6ICJsZXZlbFRpdGxlIiwKICAgICAgInN0YXRlUmVuZGVyRnVuY3Rpb24iOiAidmFyIHNob3dMZXZlbFRpdGxlID0gY3R4LnByb3BlcnRpZXMuc2hvd0xldmVsVGl0bGU7XG5cbmlmIChzaG93TGV2ZWxUaXRsZSkge1xuICAgdmFyIGxldmVsVGl0bGUgPSBjdHgucHJvcGVydGllcy5sZXZlbFRpdGxlO1xuICAgdmFyIGxldmVsVGl0bGVGb250ID0gY3R4LnByb3BlcnRpZXMubGV2ZWxUaXRsZUZvbnQ7XG4gICB2YXIgbGV2ZWxUaXRsZUNvbG9yID0gY3R4LnByb3BlcnRpZXMubGV2ZWxUaXRsZUNvbG9yO1xuICAgXG4gICBjdHguYXBpLnRleHQoZWxlbWVudCwgbGV2ZWxUaXRsZSk7XG4gICBjdHguYXBpLmZvbnQoZWxlbWVudCwgbGV2ZWxUaXRsZUZvbnQsIGxldmVsVGl0bGVDb2xvcik7XG4gICBcbn0gZWxzZSB7XG4gICBlbGVtZW50LmhpZGUoKTtcbn0iLAogICAgICAiYWN0aW9ucyI6IG51bGwKICAgIH0sCiAgICB7CiAgICAgICJ0YWciOiAibGV2ZWxVcEJ1dHRvbiIsCiAgICAgICJhY3Rpb25zIjogewogICAgICAgICJjbGljayI6IHsKICAgICAgICAgICJhY3Rpb25GdW5jdGlvbiI6ICJ2YXIgbGV2ZWwgPSBjdHgudmFsdWVzLmxldmVsOyBcbnZhciBtYXhMZXZlbCA9IGN0eC5wcm9wZXJ0aWVzLm1heExldmVsO1xuXG52YXIgbmV3TGV2ZWwgPSBNYXRoLm1pbihtYXhMZXZlbCwgbGV2ZWwgKyA1KTtcbmN0eC5hcGkuc2V0VmFsdWUoJ2xldmVsJywgbmV3TGV2ZWwpO1xuY3R4LmFwaS5jYWxsQWN0aW9uKGV2ZW50LCAnbGV2ZWxVcGRhdGVTdGF0ZScsIG5ld0xldmVsLCB7XG4gIGVycm9yOiAoKSA9PiB7XG4gICAgIGN0eC5hcGkuc2V0VmFsdWUoJ2xldmVsJywgbGV2ZWwpO1xuICB9XG59KTsiCiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgewogICAgICAidGFnIjogImxldmVsVmFsdWUiLAogICAgICAic3RhdGVSZW5kZXJGdW5jdGlvbiI6ICJ2YXIgc2hvd1ZhbHVlID0gY3R4LnByb3BlcnRpZXMuc2hvd1ZhbHVlO1xuXG5pZiAoc2hvd1ZhbHVlKSB7XG4gIHZhciB2YWx1ZUZvbnQgPSBjdHgucHJvcGVydGllcy52YWx1ZUZvbnQ7XG4gIHZhciB2YWx1ZUNvbG9yID0gY3R4LnByb3BlcnRpZXMudmFsdWVDb2xvcjtcbiAgdmFyIGxldmVsID0gY3R4LnZhbHVlcy5sZXZlbDsgXG4gIFxuICB2YXIgbGV2ZWxUZXh0ID0gY3R4LmFwaS5mb3JtYXRWYWx1ZShsZXZlbCwgY3R4LnByb3BlcnRpZXMudmFsdWVEZWNpbWFscywgY3R4LnByb3BlcnRpZXMudmFsdWVVbml0cywgZmFsc2UpO1xuXG4gIGN0eC5hcGkuZm9udChlbGVtZW50LCB2YWx1ZUZvbnQsIHZhbHVlQ29sb3IpO1xuICBjdHguYXBpLnRleHQoZWxlbWVudCwgbGV2ZWxUZXh0KTtcblxufSBlbHNlIHtcbiAgZWxlbWVudC5oaWRlKCk7XG4gIGN0eC50YWdzLmxldmVsVmFsdWVCYWNrZ3JvdW5kWzBdLmhpZGUoKTtcbn0iLAogICAgICAiYWN0aW9ucyI6IHsKICAgICAgICAiY2xpY2siOiB7CiAgICAgICAgICAiYWN0aW9uRnVuY3Rpb24iOiAiY3R4LmFwaS5jYWxsQWN0aW9uKGV2ZW50LCAnbGV2ZWxWYWx1ZUNsaWNrJyk7IgogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgIHsKICAgICAgInRhZyI6ICJvZmZCdXR0b24iLAogICAgICAiYWN0aW9ucyI6IHsKICAgICAgICAiY2xpY2siOiB7CiAgICAgICAgICAiYWN0aW9uRnVuY3Rpb24iOiAiY3R4LmFwaS5jYWxsQWN0aW9uKGV2ZW50LCAnb2ZmVXBkYXRlU3RhdGUnLCB1bmRlZmluZWQsIHtcbiAgbmV4dDogKCkgPT4ge1xuICAgICBjdHguYXBpLnNldFZhbHVlKCdvbicsIGZhbHNlKTtcbiAgfVxufSk7XG4iCiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgewogICAgICAidGFnIjogIm9uQnV0dG9uIiwKICAgICAgImFjdGlvbnMiOiB7CiAgICAgICAgImNsaWNrIjogewogICAgICAgICAgImFjdGlvbkZ1bmN0aW9uIjogImN0eC5hcGkuY2FsbEFjdGlvbihldmVudCwgJ29uVXBkYXRlU3RhdGUnLCB1bmRlZmluZWQsIHtcbiAgbmV4dDogKCkgPT4ge1xuICAgICBjdHguYXBpLnNldFZhbHVlKCdvbicsIHRydWUpO1xuICB9XG59KTtcblxuIgogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgIHsKICAgICAgInRhZyI6ICJyYW1rYSIsCiAgICAgICJzdGF0ZVJlbmRlckZ1bmN0aW9uIjogInZhciBvbiA9IGN0eC52YWx1ZXMub247XG5pZiAob24pIHtcbiAgICBpZiAoIWVsZW1lbnQudGltZWxpbmUoKS5hY3RpdmUoKSkge1xuICAgICAgICBjdHguYXBpLmFuaW1hdGUoZWxlbWVudCwgMTAwMCkuc2NhbGUoMS4xKS5sb29wKDAsIHRydWUpO1xuICAgIH1cbn0gZWxzZSB7XG4gICAgZWxlbWVudC50aW1lbGluZSgpLnN0b3AoKTtcbn0iLAogICAgICAiYWN0aW9ucyI6IG51bGwKICAgIH0KICBdLAogICJiZWhhdmlvciI6IFsKICAgIHsKICAgICAgImlkIjogImxldmVsIiwKICAgICAgIm5hbWUiOiAiTGV2ZWwiLAogICAgICAiaGludCI6IG51bGwsCiAgICAgICJ0eXBlIjogInZhbHVlIiwKICAgICAgInZhbHVlVHlwZSI6ICJET1VCTEUiLAogICAgICAiZGVmYXVsdFZhbHVlIjogMTAsCiAgICAgICJ0cnVlTGFiZWwiOiBudWxsLAogICAgICAiZmFsc2VMYWJlbCI6IG51bGwsCiAgICAgICJzdGF0ZUxhYmVsIjogbnVsbCwKICAgICAgInZhbHVlVG9EYXRhVHlwZSI6IG51bGwsCiAgICAgICJjb25zdGFudFZhbHVlIjogbnVsbCwKICAgICAgInZhbHVlVG9EYXRhRnVuY3Rpb24iOiBudWxsCiAgICB9LAogICAgewogICAgICAiaWQiOiAiZGlzYWJsZWQiLAogICAgICAibmFtZSI6ICJ7aTE4bjp3aWRnZXRzLnJwYy1zdGF0ZS5kaXNhYmxlZC1zdGF0ZX0iLAogICAgICAiaGludCI6ICJ7aTE4bjp3aWRnZXRzLnJwYy1zdGF0ZS5kaXNhYmxlZC1zdGF0ZS1oaW50fSIsCiAgICAgICJ0eXBlIjogInZhbHVlIiwKICAgICAgInZhbHVlVHlwZSI6ICJCT09MRUFOIiwKICAgICAgImRlZmF1bHRWYWx1ZSI6IGZhbHNlLAogICAgICAidHJ1ZUxhYmVsIjogIiIsCiAgICAgICJmYWxzZUxhYmVsIjogIiIsCiAgICAgICJzdGF0ZUxhYmVsIjogIntpMThuOndpZGdldHMucnBjLXN0YXRlLmRpc2FibGVkfSIsCiAgICAgICJ2YWx1ZVRvRGF0YVR5cGUiOiBudWxsLAogICAgICAiY29uc3RhbnRWYWx1ZSI6IG51bGwsCiAgICAgICJ2YWx1ZVRvRGF0YUZ1bmN0aW9uIjogbnVsbAogICAgfSwKICAgIHsKICAgICAgImlkIjogIm9uIiwKICAgICAgIm5hbWUiOiAiT24vT2ZmIHN0YXRlIiwKICAgICAgImhpbnQiOiBudWxsLAogICAgICAidHlwZSI6ICJ2YWx1ZSIsCiAgICAgICJ2YWx1ZVR5cGUiOiAiQk9PTEVBTiIsCiAgICAgICJkZWZhdWx0VmFsdWUiOiB0cnVlLAogICAgICAidHJ1ZUxhYmVsIjogIntpMThuOndpZGdldHMucnBjLXN0YXRlLm9ufSIsCiAgICAgICJmYWxzZUxhYmVsIjogIntpMThuOndpZGdldHMucnBjLXN0YXRlLm9mZn0iLAogICAgICAic3RhdGVMYWJlbCI6ICJ7aTE4bjp3aWRnZXRzLnJwYy1zdGF0ZS5vbn0iLAogICAgICAidmFsdWVUb0RhdGFUeXBlIjogbnVsbCwKICAgICAgImNvbnN0YW50VmFsdWUiOiBudWxsLAogICAgICAidmFsdWVUb0RhdGFGdW5jdGlvbiI6IG51bGwKICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJvblVwZGF0ZVN0YXRlIiwKICAgICAgIm5hbWUiOiAie2kxOG46d2lkZ2V0cy5ycGMtc3RhdGUudHVybi1vbn0iLAogICAgICAiaGludCI6ICJ7aTE4bjp3aWRnZXRzLnJwYy1zdGF0ZS50dXJuLW9uLWhpbnR9IiwKICAgICAgInR5cGUiOiAiYWN0aW9uIiwKICAgICAgInZhbHVlVHlwZSI6ICJCT09MRUFOIiwKICAgICAgInZhbHVlVG9EYXRhVHlwZSI6ICJDT05TVEFOVCIsCiAgICAgICJjb25zdGFudFZhbHVlIjogdHJ1ZQogICAgfSwKICAgIHsKICAgICAgImlkIjogIm9mZlVwZGF0ZVN0YXRlIiwKICAgICAgIm5hbWUiOiAie2kxOG46d2lkZ2V0cy5ycGMtc3RhdGUudHVybi1vZmZ9IiwKICAgICAgImhpbnQiOiAie2kxOG46d2lkZ2V0cy5ycGMtc3RhdGUudHVybi1vZmYtaGludH0iLAogICAgICAidHlwZSI6ICJhY3Rpb24iLAogICAgICAidmFsdWVUeXBlIjogIkJPT0xFQU4iLAogICAgICAidmFsdWVUb0RhdGFUeXBlIjogIkNPTlNUQU5UIiwKICAgICAgImNvbnN0YW50VmFsdWUiOiBmYWxzZQogICAgfSwKICAgIHsKICAgICAgImlkIjogImxldmVsVXBkYXRlU3RhdGUiLAogICAgICAibmFtZSI6ICJVcGRhdGUgbGV2ZWwiLAogICAgICAidHlwZSI6ICJhY3Rpb24iLAogICAgICAidmFsdWVUeXBlIjogIkRPVUJMRSIsCiAgICAgICJ2YWx1ZVRvRGF0YVR5cGUiOiAiVkFMVUUiLAogICAgICAiY29uc3RhbnRWYWx1ZSI6IDAKICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJsZXZlbFZhbHVlQ2xpY2siLAogICAgICAibmFtZSI6ICJPbiBsZXZlbCB2YWx1ZSBjbGljayIsCiAgICAgICJ0eXBlIjogIndpZGdldEFjdGlvbiIKICAgIH0KICBdLAogICJwcm9wZXJ0aWVzIjogWwogICAgewogICAgICAiaWQiOiAic2hvd0xldmVsVGl0bGUiLAogICAgICAibmFtZSI6ICJMZXZlbCB0aXRsZSIsCiAgICAgICJ0eXBlIjogInN3aXRjaCIsCiAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgInJvd0NsYXNzIjogImNvbHVtbi14cyIKICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJsZXZlbFRpdGxlIiwKICAgICAgIm5hbWUiOiAiTGV2ZWwgdGl0bGUiLAogICAgICAidHlwZSI6ICJ0ZXh0IiwKICAgICAgImRlZmF1bHQiOiAie2kxOG46d2lkZ2V0cy5iYXR0ZXJ5LWxldmVsLnZhbHVlfSIsCiAgICAgICJkaXNhYmxlT25Qcm9wZXJ0eSI6ICJzaG93TGV2ZWxUaXRsZSIsCiAgICAgICJmaWVsZENsYXNzIjogImZsZXgiCiAgICB9LAogICAgewogICAgICAiaWQiOiAibGV2ZWxUaXRsZUZvbnQiLAogICAgICAibmFtZSI6ICJMZXZlbCB0aXRsZSIsCiAgICAgICJ0eXBlIjogImZvbnQiLAogICAgICAiZGVmYXVsdCI6IHsKICAgICAgICAic2l6ZSI6IDYsCiAgICAgICAgInNpemVVbml0IjogInB4IiwKICAgICAgICAiZmFtaWx5IjogIlJvYm90byIsCiAgICAgICAgIndlaWdodCI6ICJub3JtYWwiLAogICAgICAgICJzdHlsZSI6ICJub3JtYWwiCiAgICAgIH0sCiAgICAgICJkaXNhYmxlT25Qcm9wZXJ0eSI6ICJzaG93TGV2ZWxUaXRsZSIKICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJsZXZlbFRpdGxlQ29sb3IiLAogICAgICAibmFtZSI6ICJMZXZlbCB0aXRsZSIsCiAgICAgICJ0eXBlIjogImNvbG9yIiwKICAgICAgImRlZmF1bHQiOiAiIzAwMDAwMCIsCiAgICAgICJkaXNhYmxlT25Qcm9wZXJ0eSI6ICJzaG93TGV2ZWxUaXRsZSIKICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJzaG93VmFsdWUiLAogICAgICAibmFtZSI6ICJWYWx1ZSIsCiAgICAgICJ0eXBlIjogInN3aXRjaCIsCiAgICAgICJkZWZhdWx0IjogdHJ1ZSwKICAgICAgInJvd0NsYXNzIjogImNvbHVtbi14cyIKICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJ2YWx1ZVVuaXRzIiwKICAgICAgIm5hbWUiOiAiVmFsdWUiLAogICAgICAidHlwZSI6ICJ1bml0cyIsCiAgICAgICJkZWZhdWx0IjogIiIsCiAgICAgICJkaXNhYmxlT25Qcm9wZXJ0eSI6ICJzaG93VmFsdWUiLAogICAgICAiZmllbGRDbGFzcyI6ICJmbGV4IgogICAgfSwKICAgIHsKICAgICAgImlkIjogInZhbHVlRGVjaW1hbHMiLAogICAgICAibmFtZSI6ICJWYWx1ZSIsCiAgICAgICJ0eXBlIjogIm51bWJlciIsCiAgICAgICJkZWZhdWx0IjogMiwKICAgICAgIm1pbiI6IDAsCiAgICAgICJtYXgiOiAxNSwKICAgICAgInN0ZXAiOiAxLAogICAgICAiZmllbGRTdWZmaXgiOiAiZGVjaW1hbHMiLAogICAgICAiZGlzYWJsZU9uUHJvcGVydHkiOiAic2hvd1ZhbHVlIiwKICAgICAgImZpZWxkQ2xhc3MiOiAiZmxleCIKICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJ2YWx1ZUZvbnQiLAogICAgICAibmFtZSI6ICJWYWx1ZSIsCiAgICAgICJ0eXBlIjogImZvbnQiLAogICAgICAiZGVmYXVsdCI6IHsKICAgICAgICAic2l6ZSI6IDYsCiAgICAgICAgInNpemVVbml0IjogInB4IiwKICAgICAgICAiZmFtaWx5IjogIlJvYm90byIsCiAgICAgICAgIndlaWdodCI6ICJub3JtYWwiLAogICAgICAgICJzdHlsZSI6ICJub3JtYWwiCiAgICAgIH0sCiAgICAgICJkaXNhYmxlT25Qcm9wZXJ0eSI6ICJzaG93VmFsdWUiCiAgICB9LAogICAgewogICAgICAiaWQiOiAidmFsdWVDb2xvciIsCiAgICAgICJuYW1lIjogIlZhbHVlIiwKICAgICAgInR5cGUiOiAiY29sb3IiLAogICAgICAiZGVmYXVsdCI6ICIjMDAwMDAwIiwKICAgICAgImRpc2FibGVPblByb3BlcnR5IjogInNob3dWYWx1ZSIKICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJtaW5MZXZlbCIsCiAgICAgICJuYW1lIjogIkxldmVsIHJhbmdlIiwKICAgICAgInN1YkxhYmVsIjogIm1pbiIsCiAgICAgICJ0eXBlIjogIm51bWJlciIsCiAgICAgICJyZXF1aXJlZCI6IHRydWUsCiAgICAgICJkZWZhdWx0IjogMCwKICAgICAgIm1pbiI6IDAsCiAgICAgICJyb3dDbGFzcyI6ICJjb2x1bW4teHMiLAogICAgICAiZmllbGRDbGFzcyI6ICJmbGV4LXhzIgogICAgfSwKICAgIHsKICAgICAgImlkIjogIm1heExldmVsIiwKICAgICAgIm5hbWUiOiAiTGV2ZWwgcmFuZ2UiLAogICAgICAic3ViTGFiZWwiOiAibWF4IiwKICAgICAgInR5cGUiOiAibnVtYmVyIiwKICAgICAgInJlcXVpcmVkIjogdHJ1ZSwKICAgICAgImRlZmF1bHQiOiAxMDAsCiAgICAgICJtaW4iOiAwLAogICAgICAiZmllbGRDbGFzcyI6ICJmbGV4LXhzIgogICAgfSwKICAgIHsKICAgICAgImlkIjogInNob3dNaW5NYXhMZXZlbCIsCiAgICAgICJuYW1lIjogIk1pbi9NYXggbGFiZWwiLAogICAgICAidHlwZSI6ICJzd2l0Y2giLAogICAgICAiZGVmYXVsdCI6IHRydWUKICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJtaW5NYXhMZXZlbEZvbnQiLAogICAgICAibmFtZSI6ICJNaW4vTWF4IGxhYmVsIiwKICAgICAgInR5cGUiOiAiZm9udCIsCiAgICAgICJkZWZhdWx0IjogewogICAgICAgICJzaXplIjogNiwKICAgICAgICAic2l6ZVVuaXQiOiAicHgiLAogICAgICAgICJmYW1pbHkiOiAiUm9ib3RvIiwKICAgICAgICAid2VpZ2h0IjogIm5vcm1hbCIsCiAgICAgICAgInN0eWxlIjogIm5vcm1hbCIKICAgICAgfSwKICAgICAgImRpc2FibGVPblByb3BlcnR5IjogInNob3dNaW5NYXhMZXZlbCIKICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJtaW5NYXhMZXZlbENvbG9yIiwKICAgICAgIm5hbWUiOiAiTWluL01heCBsYWJlbCIsCiAgICAgICJ0eXBlIjogImNvbG9yIiwKICAgICAgImRlZmF1bHQiOiAiIzY2NiIsCiAgICAgICJkaXNhYmxlT25Qcm9wZXJ0eSI6ICJzaG93TWluTWF4TGV2ZWwiCiAgICB9LAogICAgewogICAgICAiaWQiOiAibGV2ZWxCYWNrZ3JvdW5kIiwKICAgICAgIm5hbWUiOiAiTGV2ZWwgYmFja2dyb3VuZCIsCiAgICAgICJzdWJMYWJlbCI6ICJFbmFibGVkIiwKICAgICAgInR5cGUiOiAiY29sb3Jfc2V0dGluZ3MiLAogICAgICAiZGVmYXVsdCI6IHsKICAgICAgICAidHlwZSI6ICJjb25zdGFudCIsCiAgICAgICAgImNvbG9yIjogIiMxYWJiNDgiLAogICAgICAgICJjb2xvckZ1bmN0aW9uIjogInJldHVybiB2YWx1ZSA+IDcwID8gJyNkNTI4MGQnIDogJyMxYWJiNDgnOyIKICAgICAgfSwKICAgICAgImRpdmlkZXIiOiB0cnVlLAogICAgICAicm93Q2xhc3MiOiAiY29sdW1uLXhzIgogICAgfSwKICAgIHsKICAgICAgImlkIjogImRpc2FibGVkTGV2ZWxCYWNrZ3JvdW5kIiwKICAgICAgIm5hbWUiOiAiTGV2ZWwgYmFja2dyb3VuZCIsCiAgICAgICJzdWJMYWJlbCI6ICJEaXNhYmxlZCIsCiAgICAgICJ0eXBlIjogImNvbG9yIiwKICAgICAgImRlZmF1bHQiOiAiI2NjYyIKICAgIH0KICBdCn1dXT48L3RiOm1ldGFkYXRhPjxyZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiByeD0iMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjY2NjIiBzdHJva2Utd2lkdGg9IjIiIHRiOnRhZz0icmVjdCIvPjxyZWN0IHg9IjgiIHk9IjE1IiB3aWR0aD0iMjAiIGhlaWdodD0iODEiIHJ4PSIyIiBmaWxsPSIjZWNlY2VjIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS4wMzU5Ii8+PHJlY3QgdHJhbnNmb3JtPSJzY2FsZSgxIC0xKSIgeD0iOC41IiB5PSItOTUuNSIgd2lkdGg9IjE5IiBoZWlnaHQ9IjQwIiByeD0iMS41IiBmaWxsPSIjMWFiYjQ4IiB0Yjp0YWc9ImxldmVsIi8+PHRleHQgeD0iMzIiIHk9Ijk1IiBmaWxsPSIjNjY2IiBmb250LWZhbWlseT0iUm9ib3RvIiBmb250LXNpemU9IjZweCIgdGI6dGFnPSJtaW5MZXZlbCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHg9IjMxLjM1NDMwNyIgeT0iOTYuNjI2MjUxIj5taW48L3RzcGFuPjwvdGV4dD48cmVjdCB4PSIxMC45NDEiIHk9IjQ5LjU0NCIgd2lkdGg9IjE0LjE1NSIgaGVpZ2h0PSIxMiIgcng9IjIuODY2OCIgZmlsbD0iI2ZmZiIgZmlsbC1vcGFjaXR5PSIuNDUxNDgiIHRiOnRhZz0ibGV2ZWxWYWx1ZUJhY2tncm91bmQiLz48ZyBmb250LWZhbWlseT0iUm9ib3RvIiBmb250LXNpemU9IjZweCI+CiAgPHRleHQgeD0iMTgiIHk9IjU2IiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiBmaWxsPSIjMDAwMDAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB0Yjp0YWc9ImxldmVsVmFsdWUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3Bhbj5OL0E8L3RzcGFuPjwvdGV4dD4KICA8dGV4dCB4PSIxNy42MzY4MDUiIHk9IjEwLjM0NzkzMyIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgZmlsbD0iIzAwMDAwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgdGI6dGFnPSJsZXZlbFRpdGxlIiB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4+TGV2ZWw8L3RzcGFuPjwvdGV4dD4KICA8dGV4dCB4PSIzMiIgeT0iMjAiIGZpbGw9IiM2NjY2NjYiIHRiOnRhZz0ibWF4TGV2ZWwiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSIyOS44NjU0OTYiIHk9IjE4LjcxNjEyNyI+bWF4PC90c3Bhbj48L3RleHQ+CiA8L2c+PHBhdGggZD0ibTgyLjE0MyAyNy41MzhoLTIwLjQ5OWMtMS4xMDQ1IDAtMS45OTk5IDAuODk1NC0xLjk5OTkgMS45OTk5djIwLjQ5OWMwIDEuMTA0NSAwLjg5NTQgMS45OTk5IDEuOTk5OSAxLjk5OTloMjAuNDk5YzEuMTA0NSAwIDEuOTk5OS0wLjg5NTQgMS45OTk5LTEuOTk5OXYtMjAuNDk5YzAtMS4xMDQ1LTAuODk1NC0xLjk5OTktMS45OTk5LTEuOTk5OXptLTIwLjQ5OSAxLjQ5OTljMC4yNzYxMyAwIDAuNDk5OTggMC4yMjM4NSAwLjQ5OTk4IDAuNDk5OThzLTAuMjIzODUgMC40OTk5OC0wLjQ5OTk4IDAuNDk5OTgtMC40OTk5OC0wLjIyMzg1LTAuNDk5OTgtMC40OTk5OCAwLjIyMzg1LTAuNDk5OTggMC40OTk5OC0wLjQ5OTk4em0wIDIxLjQ5OWMtMC4yNzYxMyAwLTAuNDk5OTgtMC4yMjM4NS0wLjQ5OTk4LTAuNDk5OThzMC4yMjM4NS0wLjQ5OTk4IDAuNDk5OTgtMC40OTk5OCAwLjQ5OTk4IDAuMjIzODUgMC40OTk5OCAwLjQ5OTk4LTAuMjIzODUgMC40OTk5OC0wLjQ5OTk4IDAuNDk5OTh6bTEwLjI0OS0wLjc5OTk3Yy01LjQ5NSAwLTkuOTQ5NS00LjQ1NDUtOS45NDk1LTkuOTQ5NXM0LjQ1NDUtOS45NDk1IDkuOTQ5NS05Ljk0OTUgOS45NDk1IDQuNDU0NSA5Ljk0OTUgOS45NDk1LTQuNDU0NSA5Ljk0OTUtOS45NDk1IDkuOTQ5NXptMTAuMjQ5IDAuNzk5OTdjLTAuMjc2MTMgMC0wLjQ5OTk4LTAuMjIzODUtMC40OTk5OC0wLjQ5OTk4czAuMjIzODUtMC40OTk5OCAwLjQ5OTk4LTAuNDk5OTggMC40OTk5OCAwLjIyMzg1IDAuNDk5OTggMC40OTk5OC0wLjIyMzg1IDAuNDk5OTgtMC40OTk5OCAwLjQ5OTk4em0wLTIwLjQ5OWMtMC4yNzYxMyAwLTAuNDk5OTgtMC4yMjM4NS0wLjQ5OTk4LTAuNDk5OThzMC4yMjM4NS0wLjQ5OTk4IDAuNDk5OTgtMC40OTk5OCAwLjQ5OTk4IDAuMjIzODUgMC40OTk5OCAwLjQ5OTk4LTAuMjIzODUgMC40OTk5OC0wLjQ5OTk4IDAuNDk5OTh6IiBmaWxsPSIjMDgwZDdkIiBzdHJva2Utd2lkdGg9Ii4wNDAwMzEiIHRiOnRhZz0icmFta2EiLz48cGF0aCBkPSJtODAuNjYgMzcuOTgzYy0wLjA0OTU2LTAuMjQwNzgtMC4xNTI4OC0wLjQxMjYtMC4zNDI0Mi0wLjU2OTE2LTEuNTE0OC0xLjI1MTgtMy42MTU0LTEuMTY3Ny02LjMwMjEgMC4yNTIzOS0wLjE3NTMzLTAuMTc1MzMtMC4zNjUxMi0wLjMyMzUzLTAuNTc3OC0wLjQ1MTE0IDEuODUxNy0xLjY2MTIgMy41Mzk2LTIuMzU4NiA1LjA2MzctMi4wOTI0IDAuNDQ5MzQgMC4wNzg1IDAuNzYwNS0wLjQzNjM3IDAuNDgyMDktMC43OTc2NS0wLjYxNzk1LTAuODAyMDEtMS4zMjEyLTEuNDU1LTIuMTY2OC0yLjAxMTktMC4yMDUzNi0wLjEzNTIyLTAuMzk5ODctMC4xODM1OC0wLjY0NDYxLTAuMTYwMzItMS45NTYzIDAuMTg1OTQtMy4zODIyIDEuNzMwOC00LjI3NzggNC42MzQ3LTAuMjQ3OTkgMC0wLjQ4Njk3IDAuMDI5MzgtMC43Mjc1NiAwLjA4OTU1IDAuMTM0Ny0yLjQ4NCAwLjgzNTA0LTQuMTcwNyAyLjEwMS01LjA2MDEgMC4zNzMyNC0wLjI2MjIgMC4yMjkxOC0wLjg0NjI5LTAuMjIzMTMtMC45MDQ5Ny0xLjAwNDEtMC4xMzAxNC0xLjk2MzEtMC4wOTQ1OS0yLjk1NDggMC4xMDk1Ni0wLjI0MDc4IDAuMDQ5NTYtMC40MTI2IDAuMTUyODgtMC41NjkxNiAwLjM0MjQyLTEuMjUxOCAxLjUxNDgtMS4xNjc3IDMuNjE1NCAwLjI1MjM5IDYuMzAyMS0wLjE3NTMzIDAuMTc1MzMtMC4zMjM1MyAwLjM2NTEyLTAuNDUxMTQgMC41Nzc4LTEuNjYxMi0xLjg1MTctMi4zNTg2LTMuNTM5Ni0yLjA5MjQtNS4wNjM3IDAuMDc4NS0wLjQ0OTM0LTAuNDM2MzctMC43NjA1LTAuNzk3NjUtMC40ODIxMy0wLjgwMjAxIDAuNjE3OTUtMS40NTUgMS4zMjEyLTIuMDExOSAyLjE2NjgtMC4xMzUyMiAwLjIwNTM2LTAuMTgzNTggMC4zOTk4Ni0wLjE2MDMyIDAuNjQ0NjEgMC4xODU5NCAxLjk1NjMgMS43MzA4IDMuMzgyMiA0LjYzNDcgNC4yNzc4IDAgMC4yNDc5OSAwLjAyOTM4IDAuNDg2OTcgMC4wODk1NSAwLjcyNzU2LTIuNDg0LTAuMTM0Ny00LjE3MDctMC44MzUwNC01LjA2MDEtMi4xMDEtMC4yNjIyLTAuMzczMjQtMC44NDYyOS0wLjIyOTE4LTAuOTA0OTcgMC4yMjMxMy0wLjEzMDE0IDEuMDA0MS0wLjA5NDU5IDEuOTYzMSAwLjEwOTU2IDIuOTU0OCAwLjA0OTU2IDAuMjQwNzggMC4xNTI4OCAwLjQxMjYgMC4zNDI0MiAwLjU2OTE2IDEuNTE0OCAxLjI1MTggMy42MTU0IDEuMTY3NyA2LjMwMjEtMC4yNTIzOSAwLjE3NTMzIDAuMTc1MzMgMC4zNjUxMiAwLjMyMzUzIDAuNTc3OCAwLjQ1MTE0LTEuODUxNyAxLjY2MTItMy41Mzk2IDIuMzU4Ni01LjA2MzcgMi4wOTI0LTAuNDQ5MzQtMC4wNzg1LTAuNzYwNSAwLjQzNjM3LTAuNDgyMDkgMC43OTc2NSAwLjYxNzk1IDAuODAyMDEgMS4zMjEyIDEuNDU1IDIuMTY2OCAyLjAxMTkgMC4yMDUzNiAwLjEzNTIyIDAuMzk5ODYgMC4xODM1OCAwLjY0NDYxIDAuMTYwMzIgMS45NTYzLTAuMTg1OTQgMy4zODIyLTEuNzMwOCA0LjI3NzgtNC42MzQ3IDAuMjQ3OTkgMCAwLjQ4Njk3LTAuMDI5MzggMC43Mjc1Ni0wLjA4OTU5LTAuMTM0NyAyLjQ4NC0wLjgzNTA0IDQuMTcwNy0yLjEwMSA1LjA2MDEtMC4zNzMyNCAwLjI2MjItMC4yMjkxOCAwLjg0NjI5IDAuMjIzMTMgMC45MDQ5NyAxLjAwNDEgMC4xMzAxNCAxLjk2MzEgMC4wOTQ1OSAyLjk1NDgtMC4xMDk1NiAwLjI0MDc4LTAuMDQ5NTYgMC40MTI2LTAuMTUyODggMC41NjkxNi0wLjM0MjQyIDEuMjUxOC0xLjUxNDggMS4xNjc3LTMuNjE1NC0wLjI1MjM5LTYuMzAyMSAwLjE3NTMzLTAuMTc1MzMgMC4zMjM1My0wLjM2NTEyIDAuNDUxMTQtMC41Nzc4IDEuNjYxMiAxLjg1MTcgMi4zNTg2IDMuNTM5NiAyLjA5MjQgNS4wNjM3LTAuMDc4NSAwLjQ0OTM0IDAuNDM2MzcgMC43NjA1IDAuNzk3NjUgMC40ODIwOSAwLjgwMjAxLTAuNjE3OTUgMS40NTUtMS4zMjEyIDIuMDExOS0yLjE2NjggMC4xMzUyMi0wLjIwNTM2IDAuMTgzNTgtMC4zOTk4NyAwLjE2MDMyLTAuNjQ0NjEtMC4xODU5NC0xLjk1NjMtMS43MzA4LTMuMzgyMi00LjYzNDctNC4yNzc4IDAtMC4yNDc5OS0wLjAyOTM4LTAuNDg2OTctMC4wODk1NS0wLjcyNzU2IDIuNDg0IDAuMTM0NyA0LjE3MDcgMC44MzUwNCA1LjA2MDEgMi4xMDEgMC4yNjIyIDAuMzczMjQgMC44NDYyOSAwLjIyOTE4IDAuOTA0OTctMC4yMjMxMyAwLjEzMDE0LTEuMDA0IDAuMDk0NTktMS45NjMtMC4xMDk1Ni0yLjk1NDd6bS04Ljc2NTcgNC4zMDQzYy0xLjM4MDYgMC0yLjQ5OTktMS4xMTkzLTIuNDk5OS0yLjQ5OTlzMS4xMTkzLTIuNDk5OSAyLjQ5OTktMi40OTk5IDIuNDk5OSAxLjExOTMgMi40OTk5IDIuNDk5OS0xLjExOTMgMi40OTk5LTIuNDk5OSAyLjQ5OTl6IiBmaWxsPSIjN2QwODFlIiBzdHJva2Utd2lkdGg9Ii4wNDAwMzEiIHRiOnRhZz0iZmFuIi8+PGcgdGI6dGFnPSJvbkJ1dHRvbiI+CiAgPHJlY3QgeD0iNTQuNzAyIiB5PSI2MC4zNzIiIHdpZHRoPSIxNC4yNjMiIGhlaWdodD0iNy40MjYxIiByeD0iMS41IiBmaWxsPSIjMTJlZDE5IiBzdHJva2U9IiMwMDAiIHRiOnRhZz0ib25CdXR0b25CYWNrZ3JvdW5kIi8+CiAgPHRleHQgeD0iNjEuODU1NTE4IiB5PSI2NC40OTEyOCIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9IlJvYm90byIgZm9udC1zaXplPSI0LjQ0NjFweCIgc3Ryb2tlLXdpZHRoPSIuNzQxMDEiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiBzdHJva2Utd2lkdGg9Ii43NDEwMSI+T248L3RzcGFuPjwvdGV4dD4KIDwvZz48ZyB0Yjp0YWc9Im9mZkJ1dHRvbiI+CiAgPHJlY3QgeD0iNzQuMzY3IiB5PSI2MC4zMTEiIHdpZHRoPSIxNC4yNjMiIGhlaWdodD0iNy40MjYxIiByeD0iMS41IiBmaWxsPSIjZWQxMjFmIiBzdHJva2U9IiMwMDAiIHRiOnRhZz0ib2ZmQnV0dG9uQmFja2dyb3VuZCIvPgogIDx0ZXh0IHg9IjgxLjM2NTk0NCIgeT0iNjQuNTE4MzQ5IiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0iUm9ib3RvIiBmb250LXNpemU9IjQuNDQ2MXB4IiBzdHJva2Utd2lkdGg9Ii43NDEwMSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuIHN0cm9rZS13aWR0aD0iLjc0MTAxIj5PZmY8L3RzcGFuPjwvdGV4dD4KIDwvZz48ZyBzdHJva2U9IiMwMDAiIHN0cm9rZS1taXRlcmxpbWl0PSIxIiB0Yjp0YWc9ImxldmVsVXBCdXR0b24iPgogIDxyZWN0IHg9IjM0Ljk3IiB5PSIzOS4zNzciIHdpZHRoPSI5LjUwMDUiIGhlaWdodD0iMTIuNTg0IiByeD0iLjc1IiBmaWxsPSJub25lIi8+CiAgPHBhdGggdHJhbnNmb3JtPSJtYXRyaXgoLjQ3NTMgLS4yOTM4IC4yOTM4IC40NzUzIDQuODgyMSAzMS4wNCkiIGQ9Im00MS41NDQgNjAuMjQtNy44OTMtNC45NzYyIDguMjU2LTQuMzQ3NC0wLjE4MTUzIDQuNjYxOHoiIGZpbGw9IiMxMmVkMTkiLz4KIDwvZz48ZyBzdHJva2U9IiMwMDAiIHN0cm9rZS1taXRlcmxpbWl0PSIxIiB0Yjp0YWc9ImxldmVsRG93bkJ1dHRvbiI+CiAgPHJlY3QgeD0iMzQuOTciIHk9IjU5LjAzOSIgd2lkdGg9IjkuNTAwNSIgaGVpZ2h0PSIxMi41ODQiIHJ4PSIuNzUiIGZpbGw9Im5vbmUiLz4KICA8cGF0aCB0cmFuc2Zvcm09Im1hdHJpeCgtLjQ3NTE0IC4yOTQwNSAtLjI5NDA1IC0uNDc1MTQgNzQuNTY4IDc5Ljc3MSkiIGQ9Im00MS41NDQgNjAuMjQtNy44OTMtNC45NzYyIDguMjU2LTQuMzQ3NC0wLjE4MTUzIDQuNjYxOHoiIGZpbGw9IiMxMmVkMTkiLz4KIDwvZz4KPC9zdmc+\",\"scadaSymbolObjectSettings\":{\"behavior\":{},\"properties\":{}}},\"title\":\"SCADA symbol\",\"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\":null,\"pageSize\":1024,\"titleIcon\":\"mdi:lightbulb-outline\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"configMode\":\"basic\",\"targetDevice\":null,\"titleColor\":null,\"borderRadius\":\"0px\",\"margin\":\"0px\"}" + }, + "tags": [ + "svg", + "scada", + "SCADA", + "symbol" + ] +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/ImageController.java b/application/src/main/java/org/thingsboard/server/controller/ImageController.java index b81dcd326d..d93afab66d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ImageController.java +++ b/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, diff --git a/application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java index cf43f54df9..29bf048082 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java +++ b/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 tenantImages = getImages(null, false, 10); assertThat(tenantImages).containsOnly(tenantImage); - List tenantIotSvgs = getImages(null, ResourceSubType.IOT_SVG.name(), false, 10); - assertThat(tenantIotSvgs).containsOnly(tenantIotSvg); + List tenantScadaSymbols = getImages(null, ResourceSubType.SCADA_SYMBOL.name(), false, 10); + assertThat(tenantScadaSymbols).containsOnly(tenantScadaSymbol); List allImages = getImages(null, true, 10); assertThat(allImages).containsOnly(tenantImage, systemImage); - List allIotSvgs = getImages(null, ResourceSubType.IOT_SVG.name(), true, 10); - assertThat(allIotSvgs).containsOnly(tenantIotSvg, systemIotSvg); + List 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 diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceSubType.java b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceSubType.java index 13203f908f..10a033f75d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceSubType.java +++ b/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 } diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java index 28d00f8b5d..79427f8d06 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java +++ b/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); } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts index 6f83f9ac25..97829fcf2c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts +++ b/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 + + +
+
scada.symbol
+ +
+ +
+
+ + {{ 'widget-config.title' | translate }} + +
+ + + + + + + +
+
+
+ + {{ 'widget-config.card-icon' | translate }} + +
+ + + + + + + + +
+
+
+
+
+
widget-config.card-appearance
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
widget-config.show-card-buttons
+ + {{ 'fullscreen.fullscreen' | translate }} + +
+
+
{{ 'widget-config.card-border-radius' | translate }}
+ + + +
+
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.ts new file mode 100644 index 0000000000..42b0dae3d8 --- /dev/null +++ b/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, + 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'); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/svg/iot-svg-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/svg/iot-svg-basic-config.component.html deleted file mode 100644 index f0dfeacf66..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/svg/iot-svg-basic-config.component.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - -
-
widget-config.card-appearance
-
-
{{ 'widgets.background.background' | translate }}
- - -
-
-
widget-config.show-card-buttons
- - {{ 'fullscreen.fullscreen' | translate }} - -
-
-
{{ 'widget-config.card-border-radius' | translate }}
- - - -
-
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/svg/iot-svg-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/svg/iot-svg-basic-config.component.ts deleted file mode 100644 index cccdf03aba..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/svg/iot-svg-basic-config.component.ts +++ /dev/null @@ -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, - 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'); - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html index 147214c355..13e672a3fe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html @@ -18,7 +18,9 @@
- + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts index 48a328613e..992660b3dc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts +++ b/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) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.html similarity index 69% rename from ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.html index 3a9afa0048..bece57e381 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.html @@ -15,14 +15,15 @@ limitations under the License. --> -
-
+
+
-
-
+
+
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.scss similarity index 82% rename from ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.scss index b109bac863..13543851ad 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.scss +++ b/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; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.ts similarity index 52% rename from ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.component.ts index 52f40152a0..7f275cd09a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts +++ b/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; + @ViewChild('scadaSymbolShape', {static: false}) + scadaSymbolShape: ElementRef; @Input() ctx: WidgetContext; @@ -59,47 +58,57 @@ export class IotSvgWidgetComponent implements OnInit, AfterViewInit, OnDestroy, @Input() widgetTitlePanel: TemplateRef; - private settings: IotSvgWidgetSettings; - private svgContent$: Observable; + private loadingSubject = new BehaviorSubject(false); + private settings: ScadaSymbolWidgetSettings; + private scadaSymbolContent$: Observable; backgroundStyle$: Observable; 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(''))); } else { - this.svgContent$ = this.http.get(this.settings.iotSvg, {responseType: 'text'}); + this.scadaSymbolContent$ = of(''); } } 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); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.models.ts similarity index 70% rename from ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol-widget.models.ts index 6d25e94c71..691bffd1c0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts +++ b/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' }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts similarity index 73% rename from ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts index f2ed7c201b..82dd2de1cc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts +++ b/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( +export const scadaSymbolBehaviorTypeTranslations = new Map( [ - [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( +export const scadaSymbolPropertyTypeTranslations = new Map( [ - [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>/gms; const tbMetadataRegex = /.*<\/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 => ({ +const defaultGetValueSettings = (get: ScadaSymbolBehaviorValue): GetValueSettings => ({ action: GetValueAction.DO_NOTHING, defaultValue: get.defaultValue, executeRpc: { @@ -291,7 +291,7 @@ const defaultGetValueSettings = (get: IotSvgBehaviorValue): GetValueSettings ({ +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[] = []; private valueActions: ValueAction[] = []; private valueSetters: {[behaviorId: string]: ValueSetter} = {}; @@ -372,32 +372,34 @@ export class IotSvgObject { private stateValueSubjects: {[id: string]: BehaviorSubject} = {}; private readonly shapeResize$: ResizeObserver; + private readonly destroy$ = new Subject(); + 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({} as IotSvgObjectSettings, - defaults, this.inputSettings || {} as IotSvgObjectSettings); + this.metadata = parseScadaSymbolMetadataFromDom(doc); + const defaults = defaultScadaSymbolObjectSettings(this.metadata); + this.settings = mergeDeep({} 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 = this.settings.behavior[getBehavior.id]; getValueSettings = {...getValueSettings, actionLabel: this.ctx.utilsService.customTranslation(getBehavior.name, getBehavior.name)}; const stateValueSubject = new BehaviorSubject(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(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>) { 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')); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.html similarity index 64% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.html index 6ddb440827..1271f8b4bb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.html +++ b/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. --> - -
{{ metadata?.title | customTranslate }}
+
scada.behavior.behavior
-
-
{{ behaviour.name | customTranslate }}
- +
{{ behavior.name | customTranslate }}
+ + formControlName="{{ behavior.id }}"> - + formControlName="{{ behavior.id }}"> - + formControlName="{{ behavior.id }}">
-
+
widget-config.appearance
+
{{ propertyRow.label | customTranslate }} @@ -61,35 +61,35 @@
{{ property.subLabel | customTranslate }}
- +
{{ property.fieldSuffix | customTranslate }}
- - - +
{{ property.fieldSuffix | customTranslate }}
- - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.scss similarity index 82% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.scss index 8e51b991ff..99b5c5df13 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.scss +++ b/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; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.ts similarity index 61% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.component.ts index 29c1d83332..5048c53213 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts +++ b/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, 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; - if (this.svgContent) { - svgContent$ = of(this.svgContent); - } else if (this.svgUrl) { - svgContent$ = this.imageService.getImageString(this.svgUrl); + let metadata$: Observable; + if (this.scadaSymbolMetadata) { + metadata$ = of(this.scadaSymbolMetadata); } else { - svgContent$ = this.http.get(this.svgPath, {responseType: 'text'}); + let content$: Observable; + if (this.scadaSymbolContent) { + content$ = of(this.scadaSymbolContent); + } else if (this.scadaSymbolUrl) { + content$ = this.imageService.getImageString(this.scadaSymbolUrl); + } else { + content$ = of(''); + } + 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(defaults, this.modelValue); - this.iotSvgObjectSettingsFormGroup.patchValue( + const defaults = defaultScadaSymbolObjectSettings(this.metadata); + this.modelValue = mergeDeep(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); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.models.ts similarity index 71% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.models.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/scada/scada-symbol-object-settings.models.ts index 807dffcc56..60cef4fe95 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.models.ts +++ b/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); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index 5f2229cba5..b9c41c5ced 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/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 ], diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index ead107dd62..b39ec1c73f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/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 } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index e292f3cfbb..ef9126e02c 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/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 { @@ -64,7 +64,7 @@ export const scadaSymbolResolver: ResolveFn = 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 } }, { diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.html index c1698f8fe3..456b209d09 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.html @@ -40,13 +40,13 @@
scada.behavior.type
- - {{ iotSvgBehaviorTypeTranslations.get(type) | translate }} + + {{ scadaSymbolBehaviorTypeTranslations.get(type) | translate }}
-
+
scada.behavior.value-type
@@ -63,7 +63,7 @@
- +
scada.behavior.default-value
- +
{{ 'scada.behavior.default-payload' | translate }}
diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.ts index bdb2827dac..9b23ccbc75 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.ts +++ b/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; @Output() - behaviorSettingsApplied = new EventEmitter(); + behaviorSettingsApplied = new EventEmitter(); 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; } } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.html index 766cce736b..72aad310e4 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.html @@ -24,8 +24,8 @@ - - {{ iotSvgBehaviorTypeTranslations.get(type) | translate }} + + {{ scadaSymbolBehaviorTypeTranslations.get(type) | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts index 12bc0d2ecc..14ae531008 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts +++ b/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); } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.ts index 7217c7ccef..02afbb6966 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.ts +++ b/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 = []; if (behaviors) { behaviors.forEach((behavior) => { diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component.ts index 127724938d..ce04b5510d 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component.ts +++ b/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({ diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts index cb9b950f2c..820ad84832 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts +++ b/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 = { diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts index c429b5c0b4..e480bb06cc 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts +++ b/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 = []; if (tags) { tags.forEach((tag) => { diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts index fba374b848..788ab4b17a 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts @@ -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); diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.ts index a36536464f..7a2fff993a 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.ts +++ b/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 = []; if (properties) { properties.forEach((property) => { diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.html index 8a76578487..7ab700a5a3 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.html @@ -34,29 +34,29 @@
scada.property.type
- - {{ iotSvgPropertyTypeTranslations.get(type) | translate }} + + {{ scadaSymbolPropertyTypeTranslations.get(type) | translate }}
scada.property.default-value
- - - - @@ -66,18 +66,18 @@ [step]="propertyFormGroup.get('step').value" type="number" placeholder="{{ 'widget-config.set' | translate }}"> - - -
-
+
scada.property.number-settings
scada.property.min
@@ -124,7 +124,7 @@ {{ 'scada.property.vertical-divider-after' | translate }}
-
scada.property.input-field-suffix
scada.property.property-row-classes
- + {{ clazz }}
-
+
scada.property.property-field-classes
- + {{ clazz }} diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.ts index 9be8959dca..8db37fd4e5 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.ts +++ b/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; @Output() - propertySettingsApplied = new EventEmitter(); + propertySettingsApplied = new EventEmitter(); 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}); diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.html index 7016461cce..bfc715e05a 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.html @@ -24,8 +24,8 @@ - - {{ iotSvgPropertyTypeTranslations.get(type) | translate }} + + {{ scadaSymbolPropertyTypeTranslations.get(type) | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.ts index 60a56aae50..36d5f50436 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.ts +++ b/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); } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.html index 96e1018eec..3f73cfbcbd 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.html @@ -15,4 +15,4 @@ limitations under the License. --> -
+
diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss index 7bad327b9a..cbdd38f7b0 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.scss +++ b/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; diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts index 10c2b3f7f0..bda484d640 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts +++ b/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; + @ViewChild('scadaSymbolShape', {static: false}) + scadaSymbolShape: ElementRef; @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); }); } } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts similarity index 89% rename from ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.models.ts rename to ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts index 72571662f5..bbece3b3de 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.models.ts +++ b/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 += ` ${customTranslate.transform(property.subLabel, property.subLabel)}`; @@ -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'; diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts index 4dcac6038f..e0adf2892b 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts +++ b/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', diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html index 449bf95bc7..8a6da0f84f 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html @@ -15,11 +15,11 @@ limitations under the License. --> -
-
-
+
+
+
- -
-
+
+
-
+
- +
scada.symbol
+ + +
+ - +
@@ -103,7 +110,7 @@
- + diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.scss b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.scss index ca6a01b972..bf9fb9de5e 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.scss +++ b/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; - } } } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts index 086ca454d7..feba6296dc 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts +++ b/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, - 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}); diff --git a/ui-ngx/src/app/shared/components/image/gallery-image-input.component.ts b/ui-ngx/src/app/shared/components/image/gallery-image-input.component.ts index fd44b6cfec..5894ce9bb4 100644 --- a/ui-ngx/src/app/shared/components/image/gallery-image-input.component.ts +++ b/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) { diff --git a/ui-ngx/src/app/shared/components/image/image-gallery.component.ts b/ui-ngx/src/app/shared/components/image/image-gallery.component.ts index b0d4217eaa..0ea85837a8 100644 --- a/ui-ngx/src/app/shared/components/image/image-gallery.component.ts +++ b/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; diff --git a/ui-ngx/src/app/shared/components/image/scada-symbol-input.component.html b/ui-ngx/src/app/shared/components/image/scada-symbol-input.component.html new file mode 100644 index 0000000000..cbe1bb6567 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/scada-symbol-input.component.html @@ -0,0 +1,65 @@ + +
+ +
+
+ +
+
+
+ +
+ {{ imageResource.title | customTranslate }} +
+
+
+
+
+ {{ scadaSymbolMetadata.title | customTranslate }} +
+
+ +
+
+ +
+
+
+ + +
{{ (disabled ? 'scada.no-symbol' : 'scada.no-symbol-selected') | translate }}
+
+ + + diff --git a/ui-ngx/src/app/shared/components/image/scada-symbol-input.component.scss b/ui-ngx/src/app/shared/components/image/scada-symbol-input.component.scss new file mode 100644 index 0000000000..2392f8185d --- /dev/null +++ b/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; + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/image/scada-symbol-input.component.ts b/ui-ngx/src/app/shared/components/image/scada-symbol-input.component.ts new file mode 100644 index 0000000000..78aca19ce5 --- /dev/null +++ b/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, + 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, { + 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); + } + }); + } + +} diff --git a/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts b/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts index 4438034062..7c02505913 100644 --- a/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts +++ b/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, 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([ diff --git a/ui-ngx/src/app/shared/models/resource.models.ts b/ui-ngx/src/app/shared/models/resource.models.ts index f090e67522..688493d22f 100644 --- a/ui-ngx/src/app/shared/models/resource.models.ts +++ b/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( @@ -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); diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 127ced222c..4e925484ca 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/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 { } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index b70f4cee72..163eec5a71 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/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", diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index d6a5d0c8ea..d9346ce416 100644 --- a/ui-ngx/src/styles.scss +++ b/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);