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 e0f4f4c615..84f8cd9785 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -2,10 +2,9 @@ "widgetsBundle": { "alias": "cards", "title": "Cards", - "image": "data:image/png;base64,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", + "image": "tb-image:Y2FyZHNfc3lzdGVtX2J1bmRsZV9pbWFnZS5wbmc=:IkNhcmRzIiBzeXN0ZW0gYnVuZGxlIGltYWdl;data:image/png;base64,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", "description": "Includes cards that display dynamic content based on data from one or more entities. It also includes static HTML cards.", "order": 2000, - "externalId": null, "name": "Cards" }, "widgetTypeFqns": [ @@ -13,6 +12,8 @@ "cards.horizontal_value_card", "cards.aggregated_value_card", "simple_value_and_chart_card", + "label_card", + "label_value_card", "progress_bar", "cards.label_widget", "cards.dashboard_state_widget", diff --git a/application/src/main/data/json/system/widget_types/label___value_card.json b/application/src/main/data/json/system/widget_types/label___value_card.json new file mode 100644 index 0000000000..57e26d3050 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/label___value_card.json @@ -0,0 +1,23 @@ +{ + "fqn": "label_value_card", + "name": "Label & value card", + "deprecated": false, + "image": null, + "description": "Displays a single entity attribute or the latest telemetry with optional label and icon.", + "descriptor": { + "type": "latest", + "sizeX": 4.5, + "sizeY": 1, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.labelValueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.labelValueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n horizontal: true,\n previewWidth: '280px',\n previewHeight: '80px',\n embedTitlePanel: true\n };\n};\n\nself.actionSources = function() {\n return {\n 'cardClick': {\n name: 'widget-action.card-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n};\n", + "settingsSchema": "", + "dataKeySettingsSchema": "", + "settingsDirective": "", + "hasBasicMode": false, + "basicModeDirective": "", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Label & value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1.6\"},\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null}}" + }, + "tags": null +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/label_card.json b/application/src/main/data/json/system/widget_types/label_card.json new file mode 100644 index 0000000000..51eb9d3360 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/label_card.json @@ -0,0 +1,25 @@ +{ + "fqn": "label_card", + "name": "Label card", + "deprecated": false, + "image": "tb-image:bGFiZWxDYXJkLnN2Zw==:IkxhYmVsIGNhcmQiIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEyIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIxMiAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfNDk3OF80NTAzOSkiPgo8cmVjdCB4PSI2IiB5PSI1MiIgd2lkdGg9IjIwMCIgaGVpZ2h0PSI1NiIgcng9IjQiIGZpbGw9IndoaXRlIi8+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIxX2JfNDk3OF80NTAzOSkiPgo8cmVjdCB4PSIxOCIgeT0iNjQiIHdpZHRoPSIxNzYiIGhlaWdodD0iMzIiIHJ4PSI0IiBmaWxsPSJ3aGl0ZSIgZmlsbC1vcGFjaXR5PSIwLjc2Ii8+CjwvZz4KPHBhdGggZD0iTTU2LjUgODFWNzNDNTYuNSA3MS4zNCA1NS4xNiA3MCA1My41IDcwQzUxLjg0IDcwIDUwLjUgNzEuMzQgNTAuNSA3M1Y4MUM0OS4yOSA4MS45MSA0OC41IDgzLjM3IDQ4LjUgODVDNDguNSA4Ny43NiA1MC43NCA5MCA1My41IDkwQzU2LjI2IDkwIDU4LjUgODcuNzYgNTguNSA4NUM1OC41IDgzLjM3IDU3LjcxIDgxLjkxIDU2LjUgODFaTTUyLjUgNzNDNTIuNSA3Mi40NSA1Mi45NSA3MiA1My41IDcyQzU0LjA1IDcyIDU0LjUgNzIuNDUgNTQuNSA3M0g1My41Vjc0SDU0LjVWNzZINTMuNVY3N0g1NC41Vjc5SDUyLjVWNzNaIiBmaWxsPSIjNTQ2OUZGIi8+CjxwYXRoIGQ9Ik03OC42MDY0IDc1LjA0NjlWODVINzYuOTA0M1Y3NS4wNDY5SDc4LjYwNjRaTTgxLjczMDUgNzUuMDQ2OVY3Ni40MTQxSDczLjgwNzZWNzUuMDQ2OUg4MS43MzA1Wk04NC43NTU5IDc0LjVWODVIODMuMTE1MlY3NC41SDg0Ljc1NTlaTTg0LjQ2ODggODEuMDI4M0w4My45MzU1IDgxLjAyMTVDODMuOTQwMSA4MC41MTExIDg0LjAxMDcgODAuMDM5NCA4NC4xNDc1IDc5LjYwNjRDODQuMjg4NyA3OS4xNzM1IDg0LjQ4NDcgNzguNzk3NSA4NC43MzU0IDc4LjQ3ODVDODQuOTkwNiA3OC4xNTQ5IDg1LjI5NTkgNzcuOTA2NiA4NS42NTE0IDc3LjczMzRDODYuMDA2OCA3Ny41NTU3IDg2LjQwMSA3Ny40NjY4IDg2LjgzNCA3Ny40NjY4Qzg3LjE5ODYgNzcuNDY2OCA4Ny41MjY3IDc3LjUxNjkgODcuODE4NCA3Ny42MTcyQzg4LjExNDYgNzcuNzE3NCA4OC4zNjk4IDc3Ljg3OTIgODguNTg0IDc4LjEwMjVDODguNzk4MiA3OC4zMjEzIDg4Ljk2IDc4LjYwODQgODkuMDY5MyA3OC45NjM5Qzg5LjE4MzMgNzkuMzE0OCA4OS4yNDAyIDc5Ljc0MzIgODkuMjQwMiA4MC4yNDlWODVIODcuNTg1OVY4MC4yMzU0Qzg3LjU4NTkgNzkuODc5OSA4Ny41MzM1IDc5LjU5NzMgODcuNDI4NyA3OS4zODc3Qzg3LjMyODUgNzkuMTc4MSA4Ny4xODAzIDc5LjAyNzcgODYuOTg0NCA3OC45MzY1Qzg2Ljc4ODQgNzguODQwOCA4Ni41NDkyIDc4Ljc5MyA4Ni4yNjY2IDc4Ljc5M0M4NS45NzA0IDc4Ljc5MyA4NS43MDgzIDc4Ljg1MjIgODUuNDgwNSA3OC45NzA3Qzg1LjI1NzIgNzkuMDg5MiA4NS4wNzAzIDc5LjI1MSA4NC45MTk5IDc5LjQ1NjFDODQuNzY5NSA3OS42NjExIDg0LjY1NTYgNzkuODk4MSA4NC41NzgxIDgwLjE2N0M4NC41MDUyIDgwLjQzNTkgODQuNDY4OCA4MC43MjMgODQuNDY4OCA4MS4wMjgzWk05NC40MTg5IDg1LjEzNjdDOTMuODcyMSA4NS4xMzY3IDkzLjM3NzYgODUuMDQ3OSA5Mi45MzU1IDg0Ljg3MDFDOTIuNDk4IDg0LjY4NzggOTIuMTI0MyA4NC40MzQ5IDkxLjgxNDUgODQuMTExM0M5MS41MDkxIDgzLjc4NzggOTEuMjc0NCA4My40MDcyIDkxLjExMDQgODIuOTY5N0M5MC45NDYzIDgyLjUzMjIgOTAuODY0MyA4Mi4wNjA1IDkwLjg2NDMgODEuNTU0N1Y4MS4yODEyQzkwLjg2NDMgODAuNzAyNSA5MC45NDg2IDgwLjE3ODQgOTEuMTE3MiA3OS43MDlDOTEuMjg1OCA3OS4yMzk2IDkxLjUyMDUgNzguODM4NSA5MS44MjEzIDc4LjUwNTlDOTIuMTIyMSA3OC4xNjg2IDkyLjQ3NzUgNzcuOTExMSA5Mi44ODc3IDc3LjczMzRDOTMuMjk3OSA3Ny41NTU3IDkzLjc0MjIgNzcuNDY2OCA5NC4yMjA3IDc3LjQ2NjhDOTQuNzQ5MyA3Ny40NjY4IDk1LjIxMTkgNzcuNTU1NyA5NS42MDg0IDc3LjczMzRDOTYuMDA0OSA3Ny45MTExIDk2LjMzMyA3OC4xNjE4IDk2LjU5MjggNzguNDg1NEM5Ni44NTcxIDc4LjgwNDQgOTcuMDUzMSA3OS4xODQ5IDk3LjE4MDcgNzkuNjI3Qzk3LjMxMjggODAuMDY5IDk3LjM3ODkgODAuNTU2NiA5Ny4zNzg5IDgxLjA4OThWODEuNzkzOUg5MS42NjQxVjgwLjYxMTNIOTUuNzUyVjgwLjQ4MTRDOTUuNzQyOCA4MC4xODUyIDk1LjY4MzYgNzkuOTA3MiA5NS41NzQyIDc5LjY0NzVDOTUuNDY5NCA3OS4zODc3IDk1LjMwNzYgNzkuMTc4MSA5NS4wODg5IDc5LjAxODZDOTQuODcwMSA3OC44NTkgOTQuNTc4NSA3OC43NzkzIDk0LjIxMzkgNzguNzc5M0M5My45NDA0IDc4Ljc3OTMgOTMuNjk2NiA3OC44Mzg1IDkzLjQ4MjQgNzguOTU3QzkzLjI3MjggNzkuMDcxIDkzLjA5NzMgNzkuMjM3MyA5Mi45NTYxIDc5LjQ1NjFDOTIuODE0OCA3OS42NzQ4IDkyLjcwNTQgNzkuOTM5MSA5Mi42Mjc5IDgwLjI0OUM5Mi41NTUgODAuNTU0NCA5Mi41MTg2IDgwLjg5ODQgOTIuNTE4NiA4MS4yODEyVjgxLjU1NDdDOTIuNTE4NiA4MS44NzgzIDkyLjU2MTggODIuMTc5IDkyLjY0ODQgODIuNDU3QzkyLjczOTYgODIuNzMwNSA5Mi44NzE3IDgyLjk2OTcgOTMuMDQ0OSA4My4xNzQ4QzkzLjIxODEgODMuMzc5OSA5My40Mjc3IDgzLjU0MTcgOTMuNjczOCA4My42NjAyQzkzLjkxOTkgODMuNzc0MSA5NC4yMDAyIDgzLjgzMTEgOTQuNTE0NiA4My44MzExQzk0LjkxMTEgODMuODMxMSA5NS4yNjQzIDgzLjc1MTMgOTUuNTc0MiA4My41OTE4Qzk1Ljg4NDEgODMuNDMyMyA5Ni4xNTMgODMuMjA2NyA5Ni4zODA5IDgyLjkxNUw5Ny4yNDkgODMuNzU1OUM5Ny4wODk1IDgzLjk4ODMgOTYuODgyMiA4NC4yMTE2IDk2LjYyNyA4NC40MjU4Qzk2LjM3MTcgODQuNjM1NCA5Ni4wNTk2IDg0LjgwNjMgOTUuNjkwNCA4NC45Mzg1Qzk1LjMyNTggODUuMDcwNiA5NC45MDIgODUuMTM2NyA5NC40MTg5IDg1LjEzNjdaTTEwMC41NjggNzkuMDExN1Y4NUg5OC45MjA5Vjc3LjYwMzVIMTAwLjQ5M0wxMDAuNTY4IDc5LjAxMTdaTTEwMi44MzEgNzcuNTU1N0wxMDIuODE3IDc5LjA4NjlDMTAyLjcxNyA3OS4wNjg3IDEwMi42MDggNzkuMDU1IDEwMi40ODkgNzkuMDQ1OUMxMDIuMzc1IDc5LjAzNjggMTAyLjI2MSA3OS4wMzIyIDEwMi4xNDcgNzkuMDMyMkMxMDEuODY1IDc5LjAzMjIgMTAxLjYxNyA3OS4wNzMyIDEwMS40MDIgNzkuMTU1M0MxMDEuMTg4IDc5LjIzMjcgMTAxLjAwOCA3OS4zNDY3IDEwMC44NjIgNzkuNDk3MUMxMDAuNzIxIDc5LjY0MjkgMTAwLjYxMiA3OS44MjA2IDEwMC41MzQgODAuMDMwM0MxMDAuNDU3IDgwLjIzOTkgMTAwLjQxMSA4MC40NzQ2IDEwMC4zOTcgODAuNzM0NEwxMDAuMDIxIDgwLjc2MTdDMTAwLjAyMSA4MC4yOTY5IDEwMC4wNjcgNzkuODY2MiAxMDAuMTU4IDc5LjQ2OTdDMTAwLjI0OSA3OS4wNzMyIDEwMC4zODYgNzguNzI0NiAxMDAuNTY4IDc4LjQyMzhDMTAwLjc1NSA3OC4xMjMgMTAwLjk4OCA3Ny44ODgzIDEwMS4yNjYgNzcuNzE5N0MxMDEuNTQ4IDc3LjU1MTEgMTAxLjg3NCA3Ny40NjY4IDEwMi4yNDMgNzcuNDY2OEMxMDIuMzQzIDc3LjQ2NjggMTAyLjQ1MSA3Ny40NzU5IDEwMi41NjQgNzcuNDk0MUMxMDIuNjgzIDc3LjUxMjQgMTAyLjc3MiA3Ny41MzI5IDEwMi44MzEgNzcuNTU1N1pNMTA1Ljc2MSA3OS4xMDc0Vjg1SDEwNC4xMTNWNzcuNjAzNUgxMDUuNjY1TDEwNS43NjEgNzkuMTA3NFpNMTA1LjQ5NCA4MS4wMjgzTDEwNC45MzQgODEuMDIxNUMxMDQuOTM0IDgwLjUxMTEgMTA0Ljk5NyA4MC4wMzk0IDEwNS4xMjUgNzkuNjA2NEMxMDUuMjUzIDc5LjE3MzUgMTA1LjQzOSA3OC43OTc1IDEwNS42ODYgNzguNDc4NUMxMDUuOTMyIDc4LjE1NDkgMTA2LjIzNyA3Ny45MDY2IDEwNi42MDIgNzcuNzMzNEMxMDYuOTcxIDc3LjU1NTcgMTA3LjM5NyA3Ny40NjY4IDEwNy44OCA3Ny40NjY4QzEwOC4yMTcgNzcuNDY2OCAxMDguNTI1IDc3LjUxNjkgMTA4LjgwMyA3Ny42MTcyQzEwOS4wODUgNzcuNzEyOSAxMDkuMzI5IDc3Ljg2NTYgMTA5LjUzNCA3OC4wNzUyQzEwOS43NDQgNzguMjg0OCAxMDkuOTAzIDc4LjU1MzcgMTEwLjAxMyA3OC44ODE4QzExMC4xMjcgNzkuMjEgMTEwLjE4NCA3OS42MDY0IDExMC4xODQgODAuMDcxM1Y4NUgxMDguNTM2VjgwLjIxNDhDMTA4LjUzNiA3OS44NTQ4IDEwOC40ODEgNzkuNTcyMyAxMDguMzcyIDc5LjM2NzJDMTA4LjI2NyA3OS4xNjIxIDEwOC4xMTUgNzkuMDE2MyAxMDcuOTE0IDc4LjkyOTdDMTA3LjcxOCA3OC44Mzg1IDEwNy40ODMgNzguNzkzIDEwNy4yMSA3OC43OTNDMTA2LjkgNzguNzkzIDEwNi42MzYgNzguODUyMiAxMDYuNDE3IDc4Ljk3MDdDMTA2LjIwMyA3OS4wODkyIDEwNi4wMjcgNzkuMjUxIDEwNS44OTEgNzkuNDU2MUMxMDUuNzU0IDc5LjY2MTEgMTA1LjY1NCA3OS44OTgxIDEwNS41OSA4MC4xNjdDMTA1LjUyNiA4MC40MzU5IDEwNS40OTQgODAuNzIzIDEwNS40OTQgODEuMDI4M1pNMTEwLjA4MSA4MC41OTA4TDEwOS4zMDkgODAuNzYxN0MxMDkuMzA5IDgwLjMxNTEgMTA5LjM3IDc5Ljg5MzYgMTA5LjQ5MyA3OS40OTcxQzEwOS42MjEgNzkuMDk2IDEwOS44MDUgNzguNzQ1MSAxMTAuMDQ3IDc4LjQ0NDNDMTEwLjI5MyA3OC4xMzkgMTEwLjU5NiA3Ny44OTk3IDExMC45NTYgNzcuNzI2NkMxMTEuMzE2IDc3LjU1MzQgMTExLjcyOSA3Ny40NjY4IDExMi4xOTMgNzcuNDY2OEMxMTIuNTcyIDc3LjQ2NjggMTEyLjkwOSA3Ny41MTkyIDExMy4yMDUgNzcuNjI0QzExMy41MDYgNzcuNzI0MyAxMTMuNzYxIDc3Ljg4MzggMTEzLjk3MSA3OC4xMDI1QzExNC4xOCA3OC4zMjEzIDExNC4zNCA3OC42MDYxIDExNC40NDkgNzguOTU3QzExNC41NTkgNzkuMzAzNCAxMTQuNjEzIDc5LjcyMjcgMTE0LjYxMyA4MC4yMTQ4Vjg1SDExMi45NTlWODAuMjA4QzExMi45NTkgNzkuODM0MyAxMTIuOTA0IDc5LjU0NDkgMTEyLjc5NSA3OS4zMzk4QzExMi42OSA3OS4xMzQ4IDExMi41NCA3OC45OTM1IDExMi4zNDQgNzguOTE2QzExMi4xNDggNzguODM0IDExMS45MTMgNzguNzkzIDExMS42NCA3OC43OTNDMTExLjM4NCA3OC43OTMgMTExLjE1OSA3OC44NDA4IDExMC45NjMgNzguOTM2NUMxMTAuNzcxIDc5LjAyNzcgMTEwLjYxIDc5LjE1NzYgMTEwLjQ3OCA3OS4zMjYyQzExMC4zNDUgNzkuNDkwMiAxMTAuMjQ1IDc5LjY3OTQgMTEwLjE3NyA3OS44OTM2QzExMC4xMTMgODAuMTA3NyAxMTAuMDgxIDgwLjM0MDIgMTEwLjA4MSA4MC41OTA4Wk0xMTYuMjQ0IDgxLjM4MzhWODEuMjI2NkMxMTYuMjQ0IDgwLjY5MzQgMTE2LjMyMiA4MC4xOTg5IDExNi40NzcgNzkuNzQzMkMxMTYuNjMyIDc5LjI4MjkgMTE2Ljg1NSA3OC44ODQxIDExNy4xNDYgNzguNTQ2OUMxMTcuNDQzIDc4LjIwNTEgMTE3LjgwMyA3Ny45NDA4IDExOC4yMjcgNzcuNzUzOUMxMTguNjU1IDc3LjU2MjUgMTE5LjEzOCA3Ny40NjY4IDExOS42NzYgNzcuNDY2OEMxMjAuMjE4IDc3LjQ2NjggMTIwLjcwMSA3Ny41NjI1IDEyMS4xMjUgNzcuNzUzOUMxMjEuNTUzIDc3Ljk0MDggMTIxLjkxNiA3OC4yMDUxIDEyMi4yMTIgNzguNTQ2OUMxMjIuNTA4IDc4Ljg4NDEgMTIyLjczNCA3OS4yODI5IDEyMi44ODkgNzkuNzQzMkMxMjMuMDQ0IDgwLjE5ODkgMTIzLjEyMSA4MC42OTM0IDEyMy4xMjEgODEuMjI2NlY4MS4zODM4QzEyMy4xMjEgODEuOTE3IDEyMy4wNDQgODIuNDExNSAxMjIuODg5IDgyLjg2NzJDMTIyLjczNCA4My4zMjI5IDEyMi41MDggODMuNzIxNyAxMjIuMjEyIDg0LjA2MzVDMTIxLjkxNiA4NC40MDA3IDEyMS41NTYgODQuNjY1IDEyMS4xMzIgODQuODU2NEMxMjAuNzA4IDg1LjA0MzMgMTIwLjIyNyA4NS4xMzY3IDExOS42ODkgODUuMTM2N0MxMTkuMTQ3IDg1LjEzNjcgMTE4LjY2MiA4NS4wNDMzIDExOC4yMzMgODQuODU2NEMxMTcuODEgODQuNjY1IDExNy40NSA4NC40MDA3IDExNy4xNTMgODQuMDYzNUMxMTYuODU3IDgzLjcyMTcgMTE2LjYzMiA4My4zMjI5IDExNi40NzcgODIuODY3MkMxMTYuMzIyIDgyLjQxMTUgMTE2LjI0NCA4MS45MTcgMTE2LjI0NCA4MS4zODM4Wk0xMTcuODkyIDgxLjIyNjZWODEuMzgzOEMxMTcuODkyIDgxLjcxNjUgMTE3LjkyNiA4Mi4wMzA5IDExNy45OTQgODIuMzI3MUMxMTguMDYyIDgyLjYyMzQgMTE4LjE3IDgyLjg4MzEgMTE4LjMxNSA4My4xMDY0QzExOC40NjEgODMuMzI5OCAxMTguNjQ4IDgzLjUwNTIgMTE4Ljg3NiA4My42MzI4QzExOS4xMDQgODMuNzYwNCAxMTkuMzc1IDgzLjgyNDIgMTE5LjY4OSA4My44MjQyQzExOS45OTUgODMuODI0MiAxMjAuMjU5IDgzLjc2MDQgMTIwLjQ4MiA4My42MzI4QzEyMC43MSA4My41MDUyIDEyMC44OTcgODMuMzI5OCAxMjEuMDQzIDgzLjEwNjRDMTIxLjE4OSA4Mi44ODMxIDEyMS4yOTYgODIuNjIzNCAxMjEuMzY0IDgyLjMyNzFDMTIxLjQzNyA4Mi4wMzA5IDEyMS40NzQgODEuNzE2NSAxMjEuNDc0IDgxLjM4MzhWODEuMjI2NkMxMjEuNDc0IDgwLjg5ODQgMTIxLjQzNyA4MC41ODg1IDEyMS4zNjQgODAuMjk2OUMxMjEuMjk2IDgwLjAwMDcgMTIxLjE4NyA3OS43Mzg2IDEyMS4wMzYgNzkuNTEwN0MxMjAuODkgNzkuMjgyOSAxMjAuNzAzIDc5LjEwNTEgMTIwLjQ3NiA3OC45Nzc1QzEyMC4yNTIgNzguODQ1NCAxMTkuOTg2IDc4Ljc3OTMgMTE5LjY3NiA3OC43NzkzQzExOS4zNjYgNzguNzc5MyAxMTkuMDk3IDc4Ljg0NTQgMTE4Ljg2OSA3OC45Nzc1QzExOC42NDYgNzkuMTA1MSAxMTguNDYxIDc5LjI4MjkgMTE4LjMxNSA3OS41MTA3QzExOC4xNyA3OS43Mzg2IDExOC4wNjIgODAuMDAwNyAxMTcuOTk0IDgwLjI5NjlDMTE3LjkyNiA4MC41ODg1IDExNy44OTIgODAuODk4NCAxMTcuODkyIDgxLjIyNjZaTTEyOC45MDEgODIuOTk3MUMxMjguOTAxIDgyLjgzMyAxMjguODYgODIuNjg0OSAxMjguNzc4IDgyLjU1MjdDMTI4LjY5NiA4Mi40MTYgMTI4LjUzOSA4Mi4yOTMgMTI4LjMwNyA4Mi4xODM2QzEyOC4wNzkgODIuMDc0MiAxMjcuNzQyIDgxLjk3NCAxMjcuMjk1IDgxLjg4MjhDMTI2LjkwMyA4MS43OTYyIDEyNi41NDMgODEuNjkzNyAxMjYuMjE1IDgxLjU3NTJDMTI1Ljg5MSA4MS40NTIxIDEyNS42MTMgODEuMzA0IDEyNS4zODEgODEuMTMwOUMxMjUuMTQ4IDgwLjk1NzcgMTI0Ljk2OCA4MC43NTI2IDEyNC44NDEgODAuNTE1NkMxMjQuNzEzIDgwLjI3ODYgMTI0LjY0OSA4MC4wMDUyIDEyNC42NDkgNzkuNjk1M0MxMjQuNjQ5IDc5LjM5NDUgMTI0LjcxNSA3OS4xMDk3IDEyNC44NDggNzguODQwOEMxMjQuOTggNzguNTcxOSAxMjUuMTY5IDc4LjMzNSAxMjUuNDE1IDc4LjEyOTlDMTI1LjY2MSA3Ny45MjQ4IDEyNS45NiA3Ny43NjMgMTI2LjMxMSA3Ny42NDQ1QzEyNi42NjYgNzcuNTI2IDEyNy4wNjIgNzcuNDY2OCAxMjcuNSA3Ny40NjY4QzEyOC4xMiA3Ny40NjY4IDEyOC42NTEgNzcuNTcxNiAxMjkuMDkzIDc3Ljc4MTJDMTI5LjUzOSA3Ny45ODYzIDEyOS44ODEgNzguMjY2NiAxMzAuMTE4IDc4LjYyMjFDMTMwLjM1NSA3OC45NzMgMTMwLjQ3NCA3OS4zNjk1IDEzMC40NzQgNzkuODExNUgxMjguODI2QzEyOC44MjYgNzkuNjE1NiAxMjguNzc2IDc5LjQzMzMgMTI4LjY3NiA3OS4yNjQ2QzEyOC41OCA3OS4wOTE1IDEyOC40MzQgNzguOTUyNSAxMjguMjM4IDc4Ljg0NzdDMTI4LjA0MiA3OC43MzgzIDEyNy43OTYgNzguNjgzNiAxMjcuNSA3OC42ODM2QzEyNy4yMTcgNzguNjgzNiAxMjYuOTgzIDc4LjcyOTIgMTI2Ljc5NiA3OC44MjAzQzEyNi42MTQgNzguOTA2OSAxMjYuNDc3IDc5LjAyMDggMTI2LjM4NiA3OS4xNjIxQzEyNi4yOTkgNzkuMzAzNCAxMjYuMjU2IDc5LjQ1ODMgMTI2LjI1NiA3OS42MjdDMTI2LjI1NiA3OS43NSAxMjYuMjc5IDc5Ljg2MTcgMTI2LjMyNCA3OS45NjE5QzEyNi4zNzQgODAuMDU3NiAxMjYuNDU2IDgwLjE0NjUgMTI2LjU3IDgwLjIyODVDMTI2LjY4NCA4MC4zMDYgMTI2LjgzOSA4MC4zNzg5IDEyNy4wMzUgODAuNDQ3M0MxMjcuMjM2IDgwLjUxNTYgMTI3LjQ4NiA4MC41ODE3IDEyNy43ODcgODAuNjQ1NUMxMjguMzUyIDgwLjc2NCAxMjguODM4IDgwLjkxNjcgMTI5LjI0MyA4MS4xMDM1QzEyOS42NTMgODEuMjg1OCAxMjkuOTY4IDgxLjUyMjggMTMwLjE4NyA4MS44MTQ1QzEzMC40MDUgODIuMTAxNiAxMzAuNTE1IDgyLjQ2NjEgMTMwLjUxNSA4Mi45MDgyQzEzMC41MTUgODMuMjM2MyAxMzAuNDQ0IDgzLjUzNzEgMTMwLjMwMyA4My44MTA1QzEzMC4xNjYgODQuMDc5NCAxMjkuOTY1IDg0LjMxNDEgMTI5LjcwMSA4NC41MTQ2QzEyOS40MzcgODQuNzEwNiAxMjkuMTIgODQuODYzMyAxMjguNzUxIDg0Ljk3MjdDMTI4LjM4NiA4NS4wODIgMTI3Ljk3NiA4NS4xMzY3IDEyNy41MjEgODUuMTM2N0MxMjYuODUxIDg1LjEzNjcgMTI2LjI4MyA4NS4wMTgyIDEyNS44MTggODQuNzgxMkMxMjUuMzU0IDg0LjUzOTcgMTI1IDg0LjIzMjEgMTI0Ljc1OSA4My44NTg0QzEyNC41MjIgODMuNDgwMSAxMjQuNDAzIDgzLjA4ODIgMTI0LjQwMyA4Mi42ODI2SDEyNS45OTZDMTI2LjAxNCA4Mi45ODggMTI2LjA5OSA4My4yMzE4IDEyNi4yNDkgODMuNDE0MUMxMjYuNDA0IDgzLjU5MTggMTI2LjU5NSA4My43MjE3IDEyNi44MjMgODMuODAzN0MxMjcuMDU2IDgzLjg4MTIgMTI3LjI5NSA4My45MTk5IDEyNy41NDEgODMuOTE5OUMxMjcuODM3IDgzLjkxOTkgMTI4LjA4NiA4My44ODEyIDEyOC4yODYgODMuODAzN0MxMjguNDg3IDgzLjcyMTcgMTI4LjYzOSA4My42MTIzIDEyOC43NDQgODMuNDc1NkMxMjguODQ5IDgzLjMzNDMgMTI4LjkwMSA4My4xNzQ4IDEyOC45MDEgODIuOTk3MVpNMTM1LjU5OCA3Ny42MDM1Vjc4LjgwNjZIMTMxLjQyOFY3Ny42MDM1SDEzNS41OThaTTEzMi42MzEgNzUuNzkySDEzNC4yNzhWODIuOTU2MUMxMzQuMjc4IDgzLjE4MzkgMTM0LjMxIDgzLjM1OTQgMTM0LjM3NCA4My40ODI0QzEzNC40NDIgODMuNjAwOSAxMzQuNTM2IDgzLjY4MDcgMTM0LjY1NCA4My43MjE3QzEzNC43NzMgODMuNzYyNyAxMzQuOTEyIDgzLjc4MzIgMTM1LjA3MSA4My43ODMyQzEzNS4xODUgODMuNzgzMiAxMzUuMjk1IDgzLjc3NjQgMTM1LjM5OSA4My43NjI3QzEzNS41MDQgODMuNzQ5IDEzNS41ODkgODMuNzM1NCAxMzUuNjUyIDgzLjcyMTdMMTM1LjY1OSA4NC45Nzk1QzEzNS41MjIgODUuMDIwNSAxMzUuMzYzIDg1LjA1NyAxMzUuMTgxIDg1LjA4ODlDMTM1LjAwMyA4NS4xMjA4IDEzNC43OTggODUuMTM2NyAxMzQuNTY1IDg1LjEzNjdDMTM0LjE4NyA4NS4xMzY3IDEzMy44NTIgODUuMDcwNiAxMzMuNTYxIDg0LjkzODVDMTMzLjI2OSA4NC44MDE4IDEzMy4wNDEgODQuNTgwNyAxMzIuODc3IDg0LjI3NTRDMTMyLjcxMyA4My45NzAxIDEzMi42MzEgODMuNTY0NSAxMzIuNjMxIDgzLjA1ODZWNzUuNzkyWk0xNDEuMjg5IDgzLjUxNjZWNzkuOTg5M0MxNDEuMjg5IDc5LjcyNDkgMTQxLjI0MSA3OS40OTcxIDE0MS4xNDYgNzkuMzA1N0MxNDEuMDUgNzkuMTE0MyAxNDAuOTA0IDc4Ljk2NjEgMTQwLjcwOCA3OC44NjEzQzE0MC41MTcgNzguNzU2NSAxNDAuMjc1IDc4LjcwNDEgMTM5Ljk4MyA3OC43MDQxQzEzOS43MTUgNzguNzA0MSAxMzkuNDgyIDc4Ljc0OTcgMTM5LjI4NiA3OC44NDA4QzEzOS4wOSA3OC45MzIgMTM4LjkzOCA3OS4wNTUgMTM4LjgyOCA3OS4yMUMxMzguNzE5IDc5LjM2NDkgMTM4LjY2NCA3OS41NDA0IDEzOC42NjQgNzkuNzM2M0gxMzcuMDIzQzEzNy4wMjMgNzkuNDQ0NyAxMzcuMDk0IDc5LjE2MjEgMTM3LjIzNSA3OC44ODg3QzEzNy4zNzcgNzguNjE1MiAxMzcuNTgyIDc4LjM3MTQgMTM3Ljg1MSA3OC4xNTcyQzEzOC4xMTkgNzcuOTQzIDEzOC40NDEgNzcuNzc0NCAxMzguODE0IDc3LjY1MTRDMTM5LjE4OCA3Ny41MjgzIDEzOS42MDcgNzcuNDY2OCAxNDAuMDcyIDc3LjQ2NjhDMTQwLjYyOCA3Ny40NjY4IDE0MS4xMiA3Ny41NjAyIDE0MS41NDkgNzcuNzQ3MUMxNDEuOTgyIDc3LjkzMzkgMTQyLjMyMSA3OC4yMTY1IDE0Mi41NjcgNzguNTk0N0MxNDIuODE4IDc4Ljk2ODQgMTQyLjk0MyA3OS40Mzc4IDE0Mi45NDMgODAuMDAyOVY4My4yOTFDMTQyLjk0MyA4My42MjgzIDE0Mi45NjYgODMuOTMxMyAxNDMuMDEyIDg0LjIwMDJDMTQzLjA2MiA4NC40NjQ1IDE0My4xMzIgODQuNjk0NyAxNDMuMjI0IDg0Ljg5MDZWODVIMTQxLjUzNUMxNDEuNDU4IDg0LjgyMjMgMTQxLjM5NiA4NC41OTY3IDE0MS4zNTEgODQuMzIzMkMxNDEuMzEgODQuMDQ1MiAxNDEuMjg5IDgzLjc3NjQgMTQxLjI4OSA4My41MTY2Wk0xNDEuNTI4IDgwLjUwMkwxNDEuNTQyIDgxLjUyMDVIMTQwLjM1OUMxNDAuMDU0IDgxLjUyMDUgMTM5Ljc4NSA4MS41NTAxIDEzOS41NTMgODEuNjA5NEMxMzkuMzIgODEuNjY0MSAxMzkuMTI3IDgxLjc0NjEgMTM4Ljk3MiA4MS44NTU1QzEzOC44MTcgODEuOTY0OCAxMzguNzAxIDgyLjA5NyAxMzguNjIzIDgyLjI1MkMxMzguNTQ2IDgyLjQwNjkgMTM4LjUwNyA4Mi41ODI0IDEzOC41MDcgODIuNzc4M0MxMzguNTA3IDgyLjk3NDMgMTM4LjU1MiA4My4xNTQzIDEzOC42NDQgODMuMzE4NEMxMzguNzM1IDgzLjQ3NzkgMTM4Ljg2NyA4My42MDMyIDEzOS4wNCA4My42OTQzQzEzOS4yMTggODMuNzg1NSAxMzkuNDMyIDgzLjgzMTEgMTM5LjY4MyA4My44MzExQzE0MC4wMiA4My44MzExIDE0MC4zMTQgODMuNzYyNyAxNDAuNTY0IDgzLjYyNkMxNDAuODIgODMuNDg0NyAxNDEuMDIgODMuMzEzOCAxNDEuMTY2IDgzLjExMzNDMTQxLjMxMiA4Mi45MDgyIDE0MS4zODkgODIuNzE0NSAxNDEuMzk4IDgyLjUzMjJMMTQxLjkzMiA4My4yNjM3QzE0MS44NzcgODMuNDUwNSAxNDEuNzg0IDgzLjY1MSAxNDEuNjUxIDgzLjg2NTJDMTQxLjUxOSA4NC4wNzk0IDE0MS4zNDYgODQuMjg0NSAxNDEuMTMyIDg0LjQ4MDVDMTQwLjkyMiA4NC42NzE5IDE0MC42NjkgODQuODI5MSAxNDAuMzczIDg0Ljk1MjFDMTQwLjA4MSA4NS4wNzUyIDEzOS43NDQgODUuMTM2NyAxMzkuMzYxIDg1LjEzNjdDMTM4Ljg3OCA4NS4xMzY3IDEzOC40NDggODUuMDQxIDEzOC4wNjkgODQuODQ5NkMxMzcuNjkxIDg0LjY1MzYgMTM3LjM5NSA4NC4zOTE2IDEzNy4xODEgODQuMDYzNUMxMzYuOTY2IDgzLjczMDggMTM2Ljg1OSA4My4zNTQ4IDEzNi44NTkgODIuOTM1NUMxMzYuODU5IDgyLjU0MzYgMTM2LjkzMiA4Mi4xOTczIDEzNy4wNzggODEuODk2NUMxMzcuMjI5IDgxLjU5MTEgMTM3LjQ0NyA4MS4zMzU5IDEzNy43MzQgODEuMTMwOUMxMzguMDI2IDgwLjkyNTggMTM4LjM4MiA4MC43NzA4IDEzOC44MDEgODAuNjY2QzEzOS4yMiA4MC41NTY2IDEzOS42OTkgODAuNTAyIDE0MC4yMzYgODAuNTAySDE0MS41MjhaTTE0OC4zMDcgNzcuNjAzNVY3OC44MDY2SDE0NC4xMzdWNzcuNjAzNUgxNDguMzA3Wk0xNDUuMzQgNzUuNzkySDE0Ni45ODdWODIuOTU2MUMxNDYuOTg3IDgzLjE4MzkgMTQ3LjAxOSA4My4zNTk0IDE0Ny4wODMgODMuNDgyNEMxNDcuMTUxIDgzLjYwMDkgMTQ3LjI0NSA4My42ODA3IDE0Ny4zNjMgODMuNzIxN0MxNDcuNDgyIDgzLjc2MjcgMTQ3LjYyMSA4My43ODMyIDE0Ny43OCA4My43ODMyQzE0Ny44OTQgODMuNzgzMiAxNDguMDA0IDgzLjc3NjQgMTQ4LjEwOCA4My43NjI3QzE0OC4yMTMgODMuNzQ5IDE0OC4yOTggODMuNzM1NCAxNDguMzYxIDgzLjcyMTdMMTQ4LjM2OCA4NC45Nzk1QzE0OC4yMzEgODUuMDIwNSAxNDguMDcyIDg1LjA1NyAxNDcuODkgODUuMDg4OUMxNDcuNzEyIDg1LjEyMDggMTQ3LjUwNyA4NS4xMzY3IDE0Ny4yNzQgODUuMTM2N0MxNDYuODk2IDg1LjEzNjcgMTQ2LjU2MSA4NS4wNzA2IDE0Ni4yNyA4NC45Mzg1QzE0NS45NzggODQuODAxOCAxNDUuNzUgODQuNTgwNyAxNDUuNTg2IDg0LjI3NTRDMTQ1LjQyMiA4My45NzAxIDE0NS4zNCA4My41NjQ1IDE0NS4zNCA4My4wNTg2Vjc1Ljc5MlpNMTU3LjYwNCA3Ni4zNzNMMTU0LjYzMSA4NUgxNTIuODMzTDE1Ni41NzkgNzUuMDQ2OUgxNTcuNzI4TDE1Ny42MDQgNzYuMzczWk0xNjAuMDkzIDg1TDE1Ny4xMTIgNzYuMzczTDE1Ni45ODIgNzUuMDQ2OUgxNTguMTM4TDE2MS44OTcgODVIMTYwLjA5M1pNMTU5Ljk0OSA4MS4zMDg2VjgyLjY2ODlIMTU0LjUzNVY4MS4zMDg2SDE1OS45NDlaTTE2Ny41IDc1LjAxMjdWODVIMTY1Ljg1M1Y3Ni45Njc4TDE2My40MTIgNzcuNzk0OVY3Ni40MzQ2TDE2Ny4zMDIgNzUuMDEyN0gxNjcuNVoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPC9nPgo8ZGVmcz4KPGZpbHRlciBpZD0iZmlsdGVyMF9kXzQ5NzhfNDUwMzkiIHg9IjAiIHk9IjQ4IiB3aWR0aD0iMjEyIiBoZWlnaHQ9IjY4IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQgZHk9IjIiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMyIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4xMiAwIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0iZWZmZWN0MV9kcm9wU2hhZG93XzQ5NzhfNDUwMzkiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3dfNDk3OF80NTAzOSIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPGZpbHRlciBpZD0iZmlsdGVyMV9iXzQ5NzhfNDUwMzkiIHg9IjEyIiB5PSI1OCIgd2lkdGg9IjE4OCIgaGVpZ2h0PSI0NCIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVHYXVzc2lhbkJsdXIgaW49IkJhY2tncm91bmRJbWFnZUZpeCIgc3RkRGV2aWF0aW9uPSIzIi8+CjxmZUNvbXBvc2l0ZSBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iZWZmZWN0MV9iYWNrZ3JvdW5kQmx1cl80OTc4XzQ1MDM5Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9iYWNrZ3JvdW5kQmx1cl80OTc4XzQ1MDM5IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8L2RlZnM+Cjwvc3ZnPgo=", + "description": "Displays a single label with optional icon.", + "descriptor": { + "type": "static", + "sizeX": 4.5, + "sizeY": 1, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.labelCardWidget.onInit();\n};\n\n\nself.typeParameters = function() {\n return {\n previewWidth: '280px',\n previewHeight: '80px',\n embedTitlePanel: true\n };\n};\n\nself.actionSources = function() {\n return {\n 'cardClick': {\n name: 'widget-action.card-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n};\n", + "settingsSchema": "", + "dataKeySettingsSchema": "", + "settingsDirective": "tb-label-card-widget-settings", + "hasBasicMode": true, + "basicModeDirective": "tb-label-card-basic-config", + "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Label card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1.6\"},\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\"}" + }, + "tags": [ + "label" + ] +} \ No newline at end of file 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 6a7e8738cc..1a705c3c6f 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 @@ -133,6 +133,9 @@ import { DigitalSimpleGaugeBasicConfigComponent } from '@home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component'; import { MobileAppQrCodeBasicConfigComponent } from '@home/components/widget/config/basic/cards/mobile-app-qr-code-basic-config.component'; +import { + LabelCardBasicConfigComponent +} from '@home/components/widget/config/basic/cards/label-card-basic-config.component'; @NgModule({ declarations: [ @@ -177,7 +180,8 @@ import { MobileAppQrCodeBasicConfigComponent } from '@home/components/widget/con PolarAreaChartBasicConfigComponent, RadarChartBasicConfigComponent, DigitalSimpleGaugeBasicConfigComponent, - MobileAppQrCodeBasicConfigComponent + MobileAppQrCodeBasicConfigComponent, + LabelCardBasicConfigComponent ], imports: [ CommonModule, @@ -224,7 +228,8 @@ import { MobileAppQrCodeBasicConfigComponent } from '@home/components/widget/con PolarAreaChartBasicConfigComponent, RadarChartBasicConfigComponent, DigitalSimpleGaugeBasicConfigComponent, - MobileAppQrCodeBasicConfigComponent + MobileAppQrCodeBasicConfigComponent, + LabelCardBasicConfigComponent ] }) export class BasicWidgetConfigModule { @@ -265,5 +270,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type + +
+
widget-config.appearance
+
+ + {{ 'widgets.label-card.auto-scale' | translate }} + +
+
+
widgets.label-card.label
+
+ + + + + + + +
+
+
+ + {{ 'widgets.label-card.icon' | translate }} + +
+ + + + + + + + +
+
+
+
{{ '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/cards/label-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/label-card-basic-config.component.ts new file mode 100644 index 0000000000..1865bed12c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/label-card-basic-config.component.ts @@ -0,0 +1,130 @@ +/// +/// 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 { WidgetConfig, } from '@shared/models/widget.models'; +import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { isUndefined } from '@core/utils'; +import { + labelCardWidgetDefaultSettings, + LabelCardWidgetSettings +} from '@home/components/widget/lib/cards/label-card-widget.models'; + +@Component({ + selector: 'tb-label-card-basic-config', + templateUrl: './label-card-basic-config.component.html', + styleUrls: ['../basic-config.scss'] +}) +export class LabelCardBasicConfigComponent extends BasicWidgetConfigComponent { + + labelCardWidgetConfigForm: UntypedFormGroup; + + constructor(protected store: Store, + protected widgetConfigComponent: WidgetConfigComponent, + private fb: UntypedFormBuilder) { + super(store, widgetConfigComponent); + } + + protected configForm(): UntypedFormGroup { + return this.labelCardWidgetConfigForm; + } + + protected onConfigSet(configData: WidgetConfigComponentData) { + const settings: LabelCardWidgetSettings = {...labelCardWidgetDefaultSettings, ...(configData.config.settings || {})}; + this.labelCardWidgetConfigForm = this.fb.group({ + autoScale: [settings.autoScale, []], + + label: [settings.label, []], + labelFont: [settings.labelFont, []], + labelColor: [settings.labelColor, []], + + showIcon: [settings.showIcon, []], + iconSize: [settings.iconSize, [Validators.min(0)]], + iconSizeUnit: [settings.iconSizeUnit, []], + icon: [settings.icon, []], + iconColor: [settings.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.settings = this.widgetConfig.config.settings || {}; + + this.widgetConfig.config.settings.autoScale = config.autoScale; + + this.widgetConfig.config.settings.label = config.label; + this.widgetConfig.config.settings.labelFont = config.labelFont; + this.widgetConfig.config.settings.labelColor = config.labelColor; + + this.widgetConfig.config.settings.showIcon = config.showIcon; + this.widgetConfig.config.settings.iconSize = config.iconSize; + this.widgetConfig.config.settings.iconSizeUnit = config.iconSizeUnit; + this.widgetConfig.config.settings.icon = config.icon; + this.widgetConfig.config.settings.iconColor = config.iconColor; + + 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 ['showIcon']; + } + + protected updateValidators(emitEvent: boolean, trigger?: string) { + const showIcon: boolean = this.labelCardWidgetConfigForm.get('showIcon').value; + if (showIcon) { + this.labelCardWidgetConfigForm.get('iconSize').enable(); + this.labelCardWidgetConfigForm.get('iconSizeUnit').enable(); + this.labelCardWidgetConfigForm.get('icon').enable(); + this.labelCardWidgetConfigForm.get('iconColor').enable(); + } else { + this.labelCardWidgetConfigForm.get('iconSize').disable(); + this.labelCardWidgetConfigForm.get('iconSizeUnit').disable(); + this.labelCardWidgetConfigForm.get('icon').disable(); + this.labelCardWidgetConfigForm.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/lib/cards/label-card-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.component.html new file mode 100644 index 0000000000..4dfc45a4f5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.component.html @@ -0,0 +1,30 @@ + +
+
+
+ +
+
+
+ {{ icon }} +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.component.scss new file mode 100644 index 0000000000..317b3617b0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.component.scss @@ -0,0 +1,70 @@ +/** + * 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. + */ + +.tb-label-card-panel { + width: 100%; + height: 100%; + position: relative; + display: flex; + align-items: center; + justify-content: center; + padding: 20px 24px; + &.tb-label-card-pointer { + cursor: pointer; + } + > div:not(.tb-label-card-overlay), > tb-icon { + z-index: 1; + } + .tb-label-card-overlay { + position: absolute; + top: 12px; + left: 12px; + bottom: 12px; + right: 12px; + } + > div.tb-label-card-title-panel { + position: absolute; + top: 12px; + left: 12px; + right: 12px; + z-index: 2; + } + div.tb-widget-title { + padding: 0; + } + .tb-label-card-content { + width: 100%; + height: 100%; + position: relative; + display: flex; + align-items: center; + justify-content: center; + .tb-label-card-row { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 8px; + } + .tb-label-card-label { + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 2; + display: -webkit-box; + -webkit-box-orient: vertical; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.component.ts new file mode 100644 index 0000000000..5bd6411b86 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.component.ts @@ -0,0 +1,170 @@ +/// +/// 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 { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + Input, + OnDestroy, + OnInit, + Renderer2, + TemplateRef, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { + labelCardWidgetDefaultSettings, + LabelCardWidgetSettings +} from '@home/components/widget/lib/cards/label-card-widget.models'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { Observable } from 'rxjs'; +import { + backgroundStyle, + ComponentStyle, + iconStyle, + overlayStyle, + resolveCssSize, + textStyle +} from '@shared/models/widget-settings.models'; +import { ResizeObserver } from '@juggle/resize-observer'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; + +@Component({ + selector: 'tb-label-card-widget', + templateUrl: './label-card-widget.component.html', + styleUrls: ['./label-card-widget.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class LabelCardWidgetComponent implements OnInit, AfterViewInit, OnDestroy { + + @ViewChild('labelCardPanel', {static: false}) + labelCardPanel: ElementRef; + + @ViewChild('labelCardContent', {static: false}) + labelCardContent: ElementRef; + + @ViewChild('labelCardRow', {static: false}) + labelCardRow: ElementRef; + + settings: LabelCardWidgetSettings; + + @Input() + ctx: WidgetContext; + + @Input() + widgetTitlePanel: TemplateRef; + + backgroundStyle$: Observable; + overlayStyle: ComponentStyle = {}; + padding: string; + + label: string; + labelStyle: ComponentStyle = {}; + + showIcon = true; + icon = ''; + iconStyle: ComponentStyle = {}; + + hasCardClickAction = false; + + private panelResize$: ResizeObserver; + + constructor(private imagePipe: ImagePipe, + private sanitizer: DomSanitizer, + private renderer: Renderer2, + private cd: ChangeDetectorRef) { + } + + ngOnInit(): void { + this.ctx.$scope.labelCardWidget = this; + this.settings = {...labelCardWidgetDefaultSettings, ...this.ctx.settings}; + + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); + this.overlayStyle = overlayStyle(this.settings.background.overlay); + this.padding = this.settings.background.overlay.enabled ? undefined : this.settings.padding; + + this.showIcon = this.settings.showIcon; + this.icon = this.settings.icon; + this.iconStyle = iconStyle(this.settings.iconSize, this.settings.iconSizeUnit); + this.iconStyle.color = this.settings.iconColor; + + this.label = this.settings.label; + this.labelStyle = textStyle(this.settings.labelFont); + this.labelStyle.color = this.settings.labelColor; + + this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0; + } + + public ngAfterViewInit() { + if (this.settings.autoScale) { + this.renderer.setStyle(this.labelCardContent.nativeElement, 'overflow', 'visible'); + this.renderer.setStyle(this.labelCardContent.nativeElement, 'position', 'absolute'); + this.panelResize$ = new ResizeObserver(() => { + this.onResize(); + }); + this.panelResize$.observe(this.labelCardPanel.nativeElement); + this.onResize(); + } + } + + ngOnDestroy() { + if (this.panelResize$) { + this.panelResize$.disconnect(); + } + } + + public onInit() { + const borderRadius = this.ctx.$widgetElement.css('borderRadius'); + this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; + this.cd.detectChanges(); + } + + public cardClick($event: Event) { + this.ctx.actionsApi.cardClick($event); + } + + private onResize() { + const paddingLeft = getComputedStyle(this.labelCardPanel.nativeElement).paddingLeft; + const paddingRight = getComputedStyle(this.labelCardPanel.nativeElement).paddingRight; + const paddingTop = getComputedStyle(this.labelCardPanel.nativeElement).paddingTop; + const paddingBottom = getComputedStyle(this.labelCardPanel.nativeElement).paddingBottom; + const pLeft = resolveCssSize(paddingLeft)[0]; + const pRight = resolveCssSize(paddingRight)[0]; + const pTop = resolveCssSize(paddingTop)[0]; + const pBottom = resolveCssSize(paddingBottom)[0]; + const panelWidth = this.labelCardPanel.nativeElement.getBoundingClientRect().width - (pLeft + pRight); + const panelHeight = this.labelCardPanel.nativeElement.getBoundingClientRect().height - (pTop + pBottom); + this.renderer.setStyle(this.labelCardContent.nativeElement, 'width', 'auto'); + this.renderer.setStyle(this.labelCardContent.nativeElement, 'transform', `none`); + const contentWidth = this.labelCardRow.nativeElement.getBoundingClientRect().width; + const contentHeight = this.labelCardRow.nativeElement.getBoundingClientRect().height; + const panelAspect = panelWidth / panelHeight; + const contentAspect = contentWidth / contentHeight; + let scale: number; + if (contentAspect > panelAspect) { + scale = panelWidth / contentWidth; + } else { + scale = panelHeight / contentHeight; + } + const width = panelWidth / scale; + this.renderer.setStyle(this.labelCardContent.nativeElement, 'width', width + 'px'); + this.renderer.setStyle(this.labelCardContent.nativeElement, 'transform', `scale(${scale})`); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.models.ts new file mode 100644 index 0000000000..de90d287ca --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-card-widget.models.ts @@ -0,0 +1,60 @@ +/// +/// 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 { BackgroundSettings, BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models'; + +export interface LabelCardWidgetSettings { + autoScale: boolean; + label: string; + labelFont: Font; + labelColor: string; + showIcon: boolean; + icon: string; + iconSize: number; + iconSizeUnit: cssUnit; + iconColor: string; + background: BackgroundSettings; + padding: string; +} + +export const labelCardWidgetDefaultSettings: LabelCardWidgetSettings = { + autoScale: true, + label: 'Thermostat', + labelFont: { + family: 'Roboto', + size: 20, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '24px' + }, + labelColor: 'rgba(0, 0, 0, 0.87)', + showIcon: true, + icon: 'thermostat', + iconSize: 24, + iconSizeUnit: 'px', + iconColor: '#5469FF', + background: { + type: BackgroundType.color, + color: '#fff', + overlay: { + enabled: false, + color: 'rgba(255,255,255,0.72)', + blur: 3 + } + }, + padding: '12px' +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.component.html new file mode 100644 index 0000000000..5f1628233a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.component.html @@ -0,0 +1,34 @@ + +
+
+
+ +
+
+
+
+ {{ icon }} +
+
+
{{ valueText }}
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.component.scss new file mode 100644 index 0000000000..67e2ce9a6d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.component.scss @@ -0,0 +1,80 @@ +/** + * 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. + */ + +.tb-label-value-card-panel { + width: 100%; + height: 100%; + position: relative; + display: flex; + align-items: center; + justify-content: center; + padding: 20px 24px; + &.tb-label-value-card-pointer { + cursor: pointer; + } + > div:not(.tb-label-value-card-overlay), > tb-icon { + z-index: 1; + } + .tb-label-value-card-overlay { + position: absolute; + top: 12px; + left: 12px; + bottom: 12px; + right: 12px; + } + > div.tb-label-value-card-title-panel { + position: absolute; + top: 12px; + left: 12px; + right: 12px; + z-index: 2; + } + div.tb-widget-title { + padding: 0; + } + .tb-label-value-card-content { + width: 100%; + height: 100%; + position: relative; + display: flex; + align-items: center; + justify-content: center; + .tb-label-value-card-row { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 16px; + } + .tb-label-value-card-icon-label { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 8px; + } + .tb-label-value-card-label { + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 2; + display: -webkit-box; + -webkit-box-orient: vertical; + } + .tb-label-value-card-value { + white-space: nowrap; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.component.ts new file mode 100644 index 0000000000..c4aecd7943 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.component.ts @@ -0,0 +1,220 @@ +/// +/// 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 { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + Input, + OnDestroy, + OnInit, + Renderer2, + TemplateRef, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { Observable } from 'rxjs'; +import { + backgroundStyle, + ColorProcessor, + ComponentStyle, + getDataKey, + getSingleTsValue, + iconStyle, + overlayStyle, + resolveCssSize, + textStyle +} from '@shared/models/widget-settings.models'; +import { ResizeObserver } from '@juggle/resize-observer'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; +import { + labelValueCardWidgetDefaultSettings, + LabelValueCardWidgetSettings +} from '@home/components/widget/lib/cards/label-value-card-widget.models'; +import { formatValue, isDefinedAndNotNull } from '@core/utils'; + +@Component({ + selector: 'tb-label-value-card-widget', + templateUrl: './label-value-card-widget.component.html', + styleUrls: ['./label-value-card-widget.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class LabelValueCardWidgetComponent implements OnInit, AfterViewInit, OnDestroy { + + @ViewChild('labelCardPanel', {static: false}) + labelCardPanel: ElementRef; + + @ViewChild('labelCardContent', {static: false}) + labelCardContent: ElementRef; + + @ViewChild('labelCardRow', {static: false}) + labelCardRow: ElementRef; + + settings: LabelValueCardWidgetSettings; + + @Input() + ctx: WidgetContext; + + @Input() + widgetTitlePanel: TemplateRef; + + backgroundStyle$: Observable; + overlayStyle: ComponentStyle = {}; + padding: string; + + showLabel = true; + label$: Observable; + labelStyle: ComponentStyle = {}; + labelColor: ColorProcessor; + + valueText = 'N/A'; + valueStyle: ComponentStyle = {}; + valueColor: ColorProcessor; + + showIcon = true; + icon = ''; + iconStyle: ComponentStyle = {}; + iconColor: ColorProcessor; + + hasCardClickAction = false; + + private panelResize$: ResizeObserver; + + private decimals = 0; + private units = ''; + + constructor(private imagePipe: ImagePipe, + private sanitizer: DomSanitizer, + private renderer: Renderer2, + private cd: ChangeDetectorRef) { + } + + ngOnInit(): void { + this.ctx.$scope.labelValueCardWidget = this; + this.settings = {...labelValueCardWidgetDefaultSettings, ...this.ctx.settings}; + + this.decimals = this.ctx.decimals; + this.units = this.ctx.units; + const dataKey = getDataKey(this.ctx.datasources); + if (isDefinedAndNotNull(dataKey?.decimals)) { + this.decimals = dataKey.decimals; + } + if (dataKey?.units) { + this.units = dataKey.units; + } + + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); + this.overlayStyle = overlayStyle(this.settings.background.overlay); + this.padding = this.settings.background.overlay.enabled ? undefined : this.settings.padding; + + this.showIcon = this.settings.showIcon; + this.icon = this.settings.icon; + this.iconStyle = iconStyle(this.settings.iconSize, this.settings.iconSizeUnit); + this.iconColor = ColorProcessor.fromSettings(this.settings.iconColor); + + this.showLabel = this.settings.showLabel; + const label = this.settings.label; + this.label$ = this.ctx.registerLabelPattern(label, this.label$); + this.labelStyle = textStyle(this.settings.labelFont); + this.labelColor = ColorProcessor.fromSettings(this.settings.labelColor); + this.valueStyle = textStyle(this.settings.valueFont); + this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor); + + this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0; + } + + public ngAfterViewInit() { + if (this.settings.autoScale) { + this.renderer.setStyle(this.labelCardContent.nativeElement, 'overflow', 'visible'); + this.renderer.setStyle(this.labelCardContent.nativeElement, 'position', 'absolute'); + this.panelResize$ = new ResizeObserver(() => { + this.onResize(); + }); + this.panelResize$.observe(this.labelCardPanel.nativeElement); + this.onResize(); + } + } + + ngOnDestroy() { + if (this.panelResize$) { + this.panelResize$.disconnect(); + } + } + + public onInit() { + const borderRadius = this.ctx.$widgetElement.css('borderRadius'); + this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; + this.cd.detectChanges(); + } + + public onDataUpdated() { + const tsValue = getSingleTsValue(this.ctx.data); + let ts; + let value; + if (tsValue && isDefinedAndNotNull(tsValue[1]) && tsValue[0] !== 0) { + ts = tsValue[0]; + value = tsValue[1]; + this.valueText = formatValue(value, this.decimals, this.units, false); + } else { + this.valueText = 'N/A'; + } + this.iconColor.update(value); + this.labelColor.update(value); + this.valueColor.update(value); + this.cd.detectChanges(); + if (this.settings.autoScale) { + setTimeout(() => { + this.onResize(); + }, 0); + } + } + + public cardClick($event: Event) { + this.ctx.actionsApi.cardClick($event); + } + + private onResize() { + const paddingLeft = getComputedStyle(this.labelCardPanel.nativeElement).paddingLeft; + const paddingRight = getComputedStyle(this.labelCardPanel.nativeElement).paddingRight; + const paddingTop = getComputedStyle(this.labelCardPanel.nativeElement).paddingTop; + const paddingBottom = getComputedStyle(this.labelCardPanel.nativeElement).paddingBottom; + const pLeft = resolveCssSize(paddingLeft)[0]; + const pRight = resolveCssSize(paddingRight)[0]; + const pTop = resolveCssSize(paddingTop)[0]; + const pBottom = resolveCssSize(paddingBottom)[0]; + const panelWidth = this.labelCardPanel.nativeElement.getBoundingClientRect().width - (pLeft + pRight); + const panelHeight = this.labelCardPanel.nativeElement.getBoundingClientRect().height - (pTop + pBottom); + this.renderer.setStyle(this.labelCardContent.nativeElement, 'width', 'auto'); + this.renderer.setStyle(this.labelCardContent.nativeElement, 'transform', `none`); + const contentWidth = this.labelCardRow.nativeElement.getBoundingClientRect().width; + const contentHeight = this.labelCardRow.nativeElement.getBoundingClientRect().height; + const panelAspect = panelWidth / panelHeight; + const contentAspect = contentWidth / contentHeight; + let scale: number; + if (contentAspect > panelAspect) { + scale = panelWidth / contentWidth; + } else { + scale = panelHeight / contentHeight; + } + const width = panelWidth / scale; + this.renderer.setStyle(this.labelCardContent.nativeElement, 'width', width + 'px'); + this.renderer.setStyle(this.labelCardContent.nativeElement, 'transform', `scale(${scale})`); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.models.ts new file mode 100644 index 0000000000..b24651d688 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.models.ts @@ -0,0 +1,80 @@ +/// +/// 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 { + BackgroundSettings, + BackgroundType, + ColorSettings, + constantColor, + cssUnit, + Font +} from '@shared/models/widget-settings.models'; + +export interface LabelValueCardWidgetSettings { + autoScale: boolean; + showLabel: boolean; + label: string; + labelFont: Font; + labelColor: ColorSettings; + showIcon: boolean; + icon: string; + iconSize: number; + iconSizeUnit: cssUnit; + iconColor: ColorSettings; + valueFont: Font; + valueColor: ColorSettings; + background: BackgroundSettings; + padding: string; +} + +export const labelValueCardWidgetDefaultSettings: LabelValueCardWidgetSettings = { + autoScale: true, + showLabel: true, + label: 'Temperature', + labelFont: { + family: 'Roboto', + size: 20, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '24px' + }, + labelColor: constantColor('rgba(0, 0, 0, 0.87)'), + showIcon: true, + icon: 'thermostat', + iconSize: 24, + iconSizeUnit: 'px', + iconColor: constantColor('#5469FF'), + valueFont: { + family: 'Roboto', + size: 20, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '24px' + }, + valueColor: constantColor('rgba(0, 0, 0, 0.87)'), + background: { + type: BackgroundType.color, + color: '#fff', + overlay: { + enabled: false, + color: 'rgba(255,255,255,0.72)', + blur: 3 + } + }, + padding: '12px' +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-card-widget-settings.component.html new file mode 100644 index 0000000000..5f7e4e6359 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-card-widget-settings.component.html @@ -0,0 +1,73 @@ + + +
+
widgets.label-card.label-card-style
+
+ + {{ 'widgets.label-card.auto-scale' | translate }} + +
+
+
widgets.label-card.label
+
+ + + + + + + +
+
+
+ + {{ 'widgets.label-card.icon' | translate }} + +
+ + + + + + + + +
+
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-card-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-card-widget-settings.component.ts new file mode 100644 index 0000000000..b3ebca2ae8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-card-widget-settings.component.ts @@ -0,0 +1,84 @@ +/// +/// 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, Injector } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { labelCardWidgetDefaultSettings } from '@home/components/widget/lib/cards/label-card-widget.models'; + +@Component({ + selector: 'tb-label-card-widget-settings', + templateUrl: './label-card-widget-settings.component.html', + styleUrls: [] +}) +export class LabelCardWidgetSettingsComponent extends WidgetSettingsComponent { + + labelCardWidgetSettingsForm: UntypedFormGroup; + + constructor(protected store: Store, + private $injector: Injector, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.labelCardWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return {...labelCardWidgetDefaultSettings}; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.labelCardWidgetSettingsForm = this.fb.group({ + autoScale: [settings.autoScale, []], + + label: [settings.label, []], + labelFont: [settings.labelFont, []], + labelColor: [settings.labelColor, []], + + showIcon: [settings.showIcon, []], + iconSize: [settings.iconSize, [Validators.min(0)]], + iconSizeUnit: [settings.iconSizeUnit, []], + icon: [settings.icon, []], + iconColor: [settings.iconColor, []], + + background: [settings.background, []], + padding: [settings.padding, []] + }); + } + + protected validatorTriggers(): string[] { + return ['showIcon']; + } + + protected updateValidators(emitEvent: boolean) { + const showIcon: boolean = this.labelCardWidgetSettingsForm.get('showIcon').value; + if (showIcon) { + this.labelCardWidgetSettingsForm.get('iconSize').enable(); + this.labelCardWidgetSettingsForm.get('iconSizeUnit').enable(); + this.labelCardWidgetSettingsForm.get('icon').enable(); + this.labelCardWidgetSettingsForm.get('iconColor').enable(); + } else { + this.labelCardWidgetSettingsForm.get('iconSize').disable(); + this.labelCardWidgetSettingsForm.get('iconSizeUnit').disable(); + this.labelCardWidgetSettingsForm.get('icon').disable(); + this.labelCardWidgetSettingsForm.get('iconColor').disable(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index 461c92bfea..47db116fbd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -356,6 +356,9 @@ import { import { MobileAppQrCodeWidgetSettingsComponent } from '@home/components/widget/lib/settings/cards/mobile-app-qr-code-widget-settings.component'; +import { + LabelCardWidgetSettingsComponent +} from '@home/components/widget/lib/settings/cards/label-card-widget-settings.component'; @NgModule({ declarations: [ @@ -482,7 +485,8 @@ import { PieChartWidgetSettingsComponent, BarChartWidgetSettingsComponent, PolarAreaChartWidgetSettingsComponent, - RadarChartWidgetSettingsComponent + RadarChartWidgetSettingsComponent, + LabelCardWidgetSettingsComponent ], imports: [ CommonModule, @@ -614,7 +618,8 @@ import { PieChartWidgetSettingsComponent, BarChartWidgetSettingsComponent, PolarAreaChartWidgetSettingsComponent, - RadarChartWidgetSettingsComponent + RadarChartWidgetSettingsComponent, + LabelCardWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -713,5 +718,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type