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..e9ec9bae73 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/label___value_card.json @@ -0,0 +1,25 @@ +{ + "fqn": "label_value_card", + "name": "Label & value card", + "deprecated": false, + "image": "tb-image:bGFiZWxWYWx1ZUNhcmQuc3Zn:IkxhYmVsICYgdmFsdWUgY2FyZCIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEyIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIxMiAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfNDk4OF80NTA1NikiPgo8cmVjdCB4PSI2IiB5PSI1MiIgd2lkdGg9IjIwMCIgaGVpZ2h0PSI1NiIgcng9IjQiIGZpbGw9IndoaXRlIi8+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIxX2JfNDk4OF80NTA1NikiPgo8cmVjdCB4PSIxOCIgeT0iNjQiIHdpZHRoPSIxNzYiIGhlaWdodD0iMzIiIHJ4PSI0IiBmaWxsPSJ3aGl0ZSIgZmlsbC1vcGFjaXR5PSIwLjc2Ii8+CjwvZz4KPHBhdGggZD0iTTM5LjUgODFWNzNDMzkuNSA3MS4zNCAzOC4xNiA3MCAzNi41IDcwQzM0Ljg0IDcwIDMzLjUgNzEuMzQgMzMuNSA3M1Y4MUMzMi4yOSA4MS45MSAzMS41IDgzLjM3IDMxLjUgODVDMzEuNSA4Ny43NiAzMy43NCA5MCAzNi41IDkwQzM5LjI2IDkwIDQxLjUgODcuNzYgNDEuNSA4NUM0MS41IDgzLjM3IDQwLjcxIDgxLjkxIDM5LjUgODFaTTM1LjUgNzNDMzUuNSA3Mi40NSAzNS45NSA3MiAzNi41IDcyQzM3LjA1IDcyIDM3LjUgNzIuNDUgMzcuNSA3M0gzNi41Vjc0SDM3LjVWNzZIMzYuNVY3N0gzNy41Vjc5SDM1LjVWNzNaIiBmaWxsPSIjNTQ2OUZGIi8+CjxwYXRoIGQ9Ik02MS42MDY0IDc1LjA0NjlWODVINTkuOTA0M1Y3NS4wNDY5SDYxLjYwNjRaTTY0LjczMDUgNzUuMDQ2OVY3Ni40MTQxSDU2LjgwNzZWNzUuMDQ2OUg2NC43MzA1Wk02OC43MzM0IDg1LjEzNjdDNjguMTg2NSA4NS4xMzY3IDY3LjY5MjEgODUuMDQ3OSA2Ny4yNSA4NC44NzAxQzY2LjgxMjUgODQuNjg3OCA2Ni40Mzg4IDg0LjQzNDkgNjYuMTI4OSA4NC4xMTEzQzY1LjgyMzYgODMuNzg3OCA2NS41ODg5IDgzLjQwNzIgNjUuNDI0OCA4Mi45Njk3QzY1LjI2MDcgODIuNTMyMiA2NS4xNzg3IDgyLjA2MDUgNjUuMTc4NyA4MS41NTQ3VjgxLjI4MTJDNjUuMTc4NyA4MC43MDI1IDY1LjI2MyA4MC4xNzg0IDY1LjQzMTYgNzkuNzA5QzY1LjYwMDMgNzkuMjM5NiA2NS44MzUgNzguODM4NSA2Ni4xMzU3IDc4LjUwNTlDNjYuNDM2NSA3OC4xNjg2IDY2Ljc5MiA3Ny45MTExIDY3LjIwMjEgNzcuNzMzNEM2Ny42MTIzIDc3LjU1NTcgNjguMDU2NiA3Ny40NjY4IDY4LjUzNTIgNzcuNDY2OEM2OS4wNjM4IDc3LjQ2NjggNjkuNTI2NCA3Ny41NTU3IDY5LjkyMjkgNzcuNzMzNEM3MC4zMTkzIDc3LjkxMTEgNzAuNjQ3NSA3OC4xNjE4IDcwLjkwNzIgNzguNDg1NEM3MS4xNzE1IDc4LjgwNDQgNzEuMzY3NSA3OS4xODQ5IDcxLjQ5NTEgNzkuNjI3QzcxLjYyNzMgODAuMDY5IDcxLjY5MzQgODAuNTU2NiA3MS42OTM0IDgxLjA4OThWODEuNzkzOUg2NS45Nzg1VjgwLjYxMTNINzAuMDY2NFY4MC40ODE0QzcwLjA1NzMgODAuMTg1MiA2OS45OTggNzkuOTA3MiA2OS44ODg3IDc5LjY0NzVDNjkuNzgzOSA3OS4zODc3IDY5LjYyMjEgNzkuMTc4MSA2OS40MDMzIDc5LjAxODZDNjkuMTg0NiA3OC44NTkgNjguODkyOSA3OC43NzkzIDY4LjUyODMgNzguNzc5M0M2OC4yNTQ5IDc4Ljc3OTMgNjguMDExMSA3OC44Mzg1IDY3Ljc5NjkgNzguOTU3QzY3LjU4NzIgNzkuMDcxIDY3LjQxMTggNzkuMjM3MyA2Ny4yNzA1IDc5LjQ1NjFDNjcuMTI5MiA3OS42NzQ4IDY3LjAxOTkgNzkuOTM5MSA2Ni45NDI0IDgwLjI0OUM2Ni44Njk1IDgwLjU1NDQgNjYuODMzIDgwLjg5ODQgNjYuODMzIDgxLjI4MTJWODEuNTU0N0M2Ni44MzMgODEuODc4MyA2Ni44NzYzIDgyLjE3OSA2Ni45NjI5IDgyLjQ1N0M2Ny4wNTQgODIuNzMwNSA2Ny4xODYyIDgyLjk2OTcgNjcuMzU5NCA4My4xNzQ4QzY3LjUzMjYgODMuMzc5OSA2Ny43NDIyIDgzLjU0MTcgNjcuOTg4MyA4My42NjAyQzY4LjIzNDQgODMuNzc0MSA2OC41MTQ2IDgzLjgzMTEgNjguODI5MSA4My44MzExQzY5LjIyNTYgODMuODMxMSA2OS41Nzg4IDgzLjc1MTMgNjkuODg4NyA4My41OTE4QzcwLjE5ODYgODMuNDMyMyA3MC40Njc0IDgzLjIwNjcgNzAuNjk1MyA4Mi45MTVMNzEuNTYzNSA4My43NTU5QzcxLjQwNCA4My45ODgzIDcxLjE5NjYgODQuMjExNiA3MC45NDE0IDg0LjQyNThDNzAuNjg2MiA4NC42MzU0IDcwLjM3NCA4NC44MDYzIDcwLjAwNDkgODQuOTM4NUM2OS42NDAzIDg1LjA3MDYgNjkuMjE2NSA4NS4xMzY3IDY4LjczMzQgODUuMTM2N1pNNzQuODc2IDc5LjEwNzRWODVINzMuMjI4NVY3Ny42MDM1SDc0Ljc4MDNMNzQuODc2IDc5LjEwNzRaTTc0LjYwOTQgODEuMDI4M0w3NC4wNDg4IDgxLjAyMTVDNzQuMDQ4OCA4MC41MTExIDc0LjExMjYgODAuMDM5NCA3NC4yNDAyIDc5LjYwNjRDNzQuMzY3OCA3OS4xNzM1IDc0LjU1NDcgNzguNzk3NSA3NC44MDA4IDc4LjQ3ODVDNzUuMDQ2OSA3OC4xNTQ5IDc1LjM1MjIgNzcuOTA2NiA3NS43MTY4IDc3LjczMzRDNzYuMDg1OSA3Ny41NTU3IDc2LjUxMiA3Ny40NjY4IDc2Ljk5NTEgNzcuNDY2OEM3Ny4zMzI0IDc3LjQ2NjggNzcuNjQgNzcuNTE2OSA3Ny45MTggNzcuNjE3MkM3OC4yMDA1IDc3LjcxMjkgNzguNDQ0MyA3Ny44NjU2IDc4LjY0OTQgNzguMDc1MkM3OC44NTkgNzguMjg0OCA3OS4wMTg2IDc4LjU1MzcgNzkuMTI3OSA3OC44ODE4Qzc5LjI0MTkgNzkuMjEgNzkuMjk4OCA3OS42MDY0IDc5LjI5ODggODAuMDcxM1Y4NUg3Ny42NTE0VjgwLjIxNDhDNzcuNjUxNCA3OS44NTQ4IDc3LjU5NjcgNzkuNTcyMyA3Ny40ODczIDc5LjM2NzJDNzcuMzgyNSA3OS4xNjIxIDc3LjIyOTggNzkuMDE2MyA3Ny4wMjkzIDc4LjkyOTdDNzYuODMzMyA3OC44Mzg1IDc2LjU5ODYgNzguNzkzIDc2LjMyNTIgNzguNzkzQzc2LjAxNTMgNzguNzkzIDc1Ljc1MSA3OC44NTIyIDc1LjUzMjIgNzguOTcwN0M3NS4zMTggNzkuMDg5MiA3NS4xNDI2IDc5LjI1MSA3NS4wMDU5IDc5LjQ1NjFDNzQuODY5MSA3OS42NjExIDc0Ljc2ODkgNzkuODk4MSA3NC43MDUxIDgwLjE2N0M3NC42NDEzIDgwLjQzNTkgNzQuNjA5NCA4MC43MjMgNzQuNjA5NCA4MS4wMjgzWk03OS4xOTYzIDgwLjU5MDhMNzguNDIzOCA4MC43NjE3Qzc4LjQyMzggODAuMzE1MSA3OC40ODU0IDc5Ljg5MzYgNzguNjA4NCA3OS40OTcxQzc4LjczNiA3OS4wOTYgNzguOTIwNiA3OC43NDUxIDc5LjE2MjEgNzguNDQ0M0M3OS40MDgyIDc4LjEzOSA3OS43MTEzIDc3Ljg5OTcgODAuMDcxMyA3Ny43MjY2QzgwLjQzMTMgNzcuNTUzNCA4MC44NDM4IDc3LjQ2NjggODEuMzA4NiA3Ny40NjY4QzgxLjY4NjggNzcuNDY2OCA4Mi4wMjQxIDc3LjUxOTIgODIuMzIwMyA3Ny42MjRDODIuNjIxMSA3Ny43MjQzIDgyLjg3NjMgNzcuODgzOCA4My4wODU5IDc4LjEwMjVDODMuMjk1NiA3OC4zMjEzIDgzLjQ1NTEgNzguNjA2MSA4My41NjQ1IDc4Ljk1N0M4My42NzM4IDc5LjMwMzQgODMuNzI4NSA3OS43MjI3IDgzLjcyODUgODAuMjE0OFY4NUg4Mi4wNzQyVjgwLjIwOEM4Mi4wNzQyIDc5LjgzNDMgODIuMDE5NSA3OS41NDQ5IDgxLjkxMDIgNzkuMzM5OEM4MS44MDUzIDc5LjEzNDggODEuNjU0OSA3OC45OTM1IDgxLjQ1OSA3OC45MTZDODEuMjYzIDc4LjgzNCA4MS4wMjgzIDc4Ljc5MyA4MC43NTQ5IDc4Ljc5M0M4MC40OTk3IDc4Ljc5MyA4MC4yNzQxIDc4Ljg0MDggODAuMDc4MSA3OC45MzY1Qzc5Ljg4NjcgNzkuMDI3NyA3OS43MjQ5IDc5LjE1NzYgNzkuNTkyOCA3OS4zMjYyQzc5LjQ2MDYgNzkuNDkwMiA3OS4zNjA0IDc5LjY3OTQgNzkuMjkyIDc5Ljg5MzZDNzkuMjI4MiA4MC4xMDc3IDc5LjE5NjMgODAuMzQwMiA3OS4xOTYzIDgwLjU5MDhaTTg3LjMyODEgNzkuMDI1NFY4Ny44NDM4SDg1LjY4MDdWNzcuNjAzNUg4Ny4xOTgyTDg3LjMyODEgNzkuMDI1NFpNOTIuMTQ3NSA4MS4yMzM0VjgxLjM3N0M5Mi4xNDc1IDgxLjkxNDcgOTIuMDgzNyA4Mi40MTM3IDkxLjk1NjEgODIuODc0QzkxLjgzMyA4My4zMjk4IDkxLjY0ODQgODMuNzI4NSA5MS40MDIzIDg0LjA3MDNDOTEuMTYwOCA4NC40MDc2IDkwLjg2MjMgODQuNjY5NiA5MC41MDY4IDg0Ljg1NjRDOTAuMTUxNCA4NS4wNDMzIDg5Ljc0MTIgODUuMTM2NyA4OS4yNzY0IDg1LjEzNjdDODguODE2MSA4NS4xMzY3IDg4LjQxMjggODUuMDUyNCA4OC4wNjY0IDg0Ljg4MzhDODcuNzI0NiA4NC43MTA2IDg3LjQzNTIgODQuNDY2OCA4Ny4xOTgyIDg0LjE1MjNDODYuOTYxMyA4My44Mzc5IDg2Ljc2OTkgODMuNDY4OCA4Ni42MjQgODMuMDQ0OUM4Ni40ODI3IDgyLjYxNjUgODYuMzgyNSA4Mi4xNDcxIDg2LjMyMzIgODEuNjM2N1Y4MS4wODNDODYuMzgyNSA4MC41NDA3IDg2LjQ4MjcgODAuMDQ4NSA4Ni42MjQgNzkuNjA2NEM4Ni43Njk5IDc5LjE2NDQgODYuOTYxMyA3OC43ODM5IDg3LjE5ODIgNzguNDY0OEM4Ny40MzUyIDc4LjE0NTggODcuNzI0NiA3Ny44OTk3IDg4LjA2NjQgNzcuNzI2NkM4OC40MDgyIDc3LjU1MzQgODguODA3IDc3LjQ2NjggODkuMjYyNyA3Ny40NjY4Qzg5LjcyNzUgNzcuNDY2OCA5MC4xNCA3Ny41NTc5IDkwLjUgNzcuNzQwMkM5MC44NiA3Ny45MTggOTEuMTYzMSA3OC4xNzMyIDkxLjQwOTIgNzguNTA1OUM5MS42NTUzIDc4LjgzNCA5MS44Mzk4IDc5LjIzMDUgOTEuOTYyOSA3OS42OTUzQzkyLjA4NTkgODAuMTU1NiA5Mi4xNDc1IDgwLjY2ODMgOTIuMTQ3NSA4MS4yMzM0Wk05MC41IDgxLjM3N1Y4MS4yMzM0QzkwLjUgODAuODkxNiA5MC40NjgxIDgwLjU3NDkgOTAuNDA0MyA4MC4yODMyQzkwLjM0MDUgNzkuOTg3IDkwLjI0MDIgNzkuNzI3MiA5MC4xMDM1IDc5LjUwMzlDODkuOTY2OCA3OS4yODA2IDg5Ljc5MTMgNzkuMTA3NCA4OS41NzcxIDc4Ljk4NDRDODkuMzY3NSA3OC44NTY4IDg5LjExNDYgNzguNzkzIDg4LjgxODQgNzguNzkzQzg4LjUyNjcgNzguNzkzIDg4LjI3NiA3OC44NDMxIDg4LjA2NjQgNzguOTQzNEM4Ny44NTY4IDc5LjAzOTEgODcuNjgxMyA3OS4xNzM1IDg3LjU0IDc5LjM0NjdDODcuMzk4OCA3OS41MTk5IDg3LjI4OTQgNzkuNzIyNyA4Ny4yMTE5IDc5Ljk1NTFDODcuMTM0NCA4MC4xODI5IDg3LjA3OTggODAuNDMxMyA4Ny4wNDc5IDgwLjcwMDJWODIuMDI2NEM4Ny4xMDI1IDgyLjM1NDUgODcuMTk2IDgyLjY1NTMgODcuMzI4MSA4Mi45Mjg3Qzg3LjQ2MDMgODMuMjAyMSA4Ny42NDcxIDgzLjQyMDkgODcuODg4NyA4My41ODVDODguMTM0OCA4My43NDQ1IDg4LjQ0OTIgODMuODI0MiA4OC44MzIgODMuODI0MkM4OS4xMjgzIDgzLjgyNDIgODkuMzgxMiA4My43NjA0IDg5LjU5MDggODMuNjMyOEM4OS44MDA1IDgzLjUwNTIgODkuOTcxNCA4My4zMjk4IDkwLjEwMzUgODMuMTA2NEM5MC4yNDAyIDgyLjg3ODYgOTAuMzQwNSA4Mi42MTY1IDkwLjQwNDMgODIuMzIwM0M5MC40NjgxIDgyLjAyNDEgOTAuNSA4MS43MDk2IDkwLjUgODEuMzc3Wk05Ny4wNTk2IDg1LjEzNjdDOTYuNTEyNyA4NS4xMzY3IDk2LjAxODIgODUuMDQ3OSA5NS41NzYyIDg0Ljg3MDFDOTUuMTM4NyA4NC42ODc4IDk0Ljc2NSA4NC40MzQ5IDk0LjQ1NTEgODQuMTExM0M5NC4xNDk3IDgzLjc4NzggOTMuOTE1IDgzLjQwNzIgOTMuNzUxIDgyLjk2OTdDOTMuNTg2OSA4Mi41MzIyIDkzLjUwNDkgODIuMDYwNSA5My41MDQ5IDgxLjU1NDdWODEuMjgxMkM5My41MDQ5IDgwLjcwMjUgOTMuNTg5MiA4MC4xNzg0IDkzLjc1NzggNzkuNzA5QzkzLjkyNjQgNzkuMjM5NiA5NC4xNjExIDc4LjgzODUgOTQuNDYxOSA3OC41MDU5Qzk0Ljc2MjcgNzguMTY4NiA5NS4xMTgyIDc3LjkxMTEgOTUuNTI4MyA3Ny43MzM0Qzk1LjkzODUgNzcuNTU1NyA5Ni4zODI4IDc3LjQ2NjggOTYuODYxMyA3Ny40NjY4Qzk3LjM5IDc3LjQ2NjggOTcuODUyNSA3Ny41NTU3IDk4LjI0OSA3Ny43MzM0Qzk4LjY0NTUgNzcuOTExMSA5OC45NzM2IDc4LjE2MTggOTkuMjMzNCA3OC40ODU0Qzk5LjQ5NzcgNzguODA0NCA5OS42OTM3IDc5LjE4NDkgOTkuODIxMyA3OS42MjdDOTkuOTUzNSA4MC4wNjkgMTAwLjAyIDgwLjU1NjYgMTAwLjAyIDgxLjA4OThWODEuNzkzOUg5NC4zMDQ3VjgwLjYxMTNIOTguMzkyNlY4MC40ODE0Qzk4LjM4MzUgODAuMTg1MiA5OC4zMjQyIDc5LjkwNzIgOTguMjE0OCA3OS42NDc1Qzk4LjExIDc5LjM4NzcgOTcuOTQ4MiA3OS4xNzgxIDk3LjcyOTUgNzkuMDE4NkM5Ny41MTA3IDc4Ljg1OSA5Ny4yMTkxIDc4Ljc3OTMgOTYuODU0NSA3OC43NzkzQzk2LjU4MTEgNzguNzc5MyA5Ni4zMzcyIDc4LjgzODUgOTYuMTIzIDc4Ljk1N0M5NS45MTM0IDc5LjA3MSA5NS43MzggNzkuMjM3MyA5NS41OTY3IDc5LjQ1NjFDOTUuNDU1NCA3OS42NzQ4IDk1LjM0NiA3OS45MzkxIDk1LjI2ODYgODAuMjQ5Qzk1LjE5NTYgODAuNTU0NCA5NS4xNTkyIDgwLjg5ODQgOTUuMTU5MiA4MS4yODEyVjgxLjU1NDdDOTUuMTU5MiA4MS44NzgzIDk1LjIwMjUgODIuMTc5IDk1LjI4OTEgODIuNDU3Qzk1LjM4MDIgODIuNzMwNSA5NS41MTI0IDgyLjk2OTcgOTUuNjg1NSA4My4xNzQ4Qzk1Ljg1ODcgODMuMzc5OSA5Ni4wNjg0IDgzLjU0MTcgOTYuMzE0NSA4My42NjAyQzk2LjU2MDUgODMuNzc0MSA5Ni44NDA4IDgzLjgzMTEgOTcuMTU1MyA4My44MzExQzk3LjU1MTggODMuODMxMSA5Ny45MDQ5IDgzLjc1MTMgOTguMjE0OCA4My41OTE4Qzk4LjUyNDcgODMuNDMyMyA5OC43OTM2IDgzLjIwNjcgOTkuMDIxNSA4Mi45MTVMOTkuODg5NiA4My43NTU5Qzk5LjczMDEgODMuOTg4MyA5OS41MjI4IDg0LjIxMTYgOTkuMjY3NiA4NC40MjU4Qzk5LjAxMjQgODQuNjM1NCA5OC43MDAyIDg0LjgwNjMgOTguMzMxMSA4NC45Mzg1Qzk3Ljk2NjUgODUuMDcwNiA5Ny41NDI2IDg1LjEzNjcgOTcuMDU5NiA4NS4xMzY3Wk0xMDMuMjA5IDc5LjAxMTdWODVIMTAxLjU2MlY3Ny42MDM1SDEwMy4xMzRMMTAzLjIwOSA3OS4wMTE3Wk0xMDUuNDcyIDc3LjU1NTdMMTA1LjQ1OCA3OS4wODY5QzEwNS4zNTggNzkuMDY4NyAxMDUuMjQ4IDc5LjA1NSAxMDUuMTMgNzkuMDQ1OUMxMDUuMDE2IDc5LjAzNjggMTA0LjkwMiA3OS4wMzIyIDEwNC43ODggNzkuMDMyMkMxMDQuNTA2IDc5LjAzMjIgMTA0LjI1NyA3OS4wNzMyIDEwNC4wNDMgNzkuMTU1M0MxMDMuODI5IDc5LjIzMjcgMTAzLjY0OSA3OS4zNDY3IDEwMy41MDMgNzkuNDk3MUMxMDMuMzYyIDc5LjY0MjkgMTAzLjI1MiA3OS44MjA2IDEwMy4xNzUgODAuMDMwM0MxMDMuMDk3IDgwLjIzOTkgMTAzLjA1MiA4MC40NzQ2IDEwMy4wMzggODAuNzM0NEwxMDIuNjYyIDgwLjc2MTdDMTAyLjY2MiA4MC4yOTY5IDEwMi43MDggNzkuODY2MiAxMDIuNzk5IDc5LjQ2OTdDMTAyLjg5IDc5LjA3MzIgMTAzLjAyNyA3OC43MjQ2IDEwMy4yMDkgNzguNDIzOEMxMDMuMzk2IDc4LjEyMyAxMDMuNjI4IDc3Ljg4ODMgMTAzLjkwNiA3Ny43MTk3QzEwNC4xODkgNzcuNTUxMSAxMDQuNTE1IDc3LjQ2NjggMTA0Ljg4NCA3Ny40NjY4QzEwNC45ODQgNzcuNDY2OCAxMDUuMDkxIDc3LjQ3NTkgMTA1LjIwNSA3Ny40OTQxQzEwNS4zMjQgNzcuNTEyNCAxMDUuNDEyIDc3LjUzMjkgMTA1LjQ3MiA3Ny41NTU3Wk0xMTAuNjc4IDgzLjUxNjZWNzkuOTg5M0MxMTAuNjc4IDc5LjcyNDkgMTEwLjYzIDc5LjQ5NzEgMTEwLjUzNCA3OS4zMDU3QzExMC40MzggNzkuMTE0MyAxMTAuMjkzIDc4Ljk2NjEgMTEwLjA5NyA3OC44NjEzQzEwOS45MDUgNzguNzU2NSAxMDkuNjY0IDc4LjcwNDEgMTA5LjM3MiA3OC43MDQxQzEwOS4xMDMgNzguNzA0MSAxMDguODcxIDc4Ljc0OTcgMTA4LjY3NSA3OC44NDA4QzEwOC40NzkgNzguOTMyIDEwOC4zMjYgNzkuMDU1IDEwOC4yMTcgNzkuMjFDMTA4LjEwNyA3OS4zNjQ5IDEwOC4wNTMgNzkuNTQwNCAxMDguMDUzIDc5LjczNjNIMTA2LjQxMkMxMDYuNDEyIDc5LjQ0NDcgMTA2LjQ4MyA3OS4xNjIxIDEwNi42MjQgNzguODg4N0MxMDYuNzY1IDc4LjYxNTIgMTA2Ljk3IDc4LjM3MTQgMTA3LjIzOSA3OC4xNTcyQzEwNy41MDggNzcuOTQzIDEwNy44MjkgNzcuNzc0NCAxMDguMjAzIDc3LjY1MTRDMTA4LjU3NyA3Ny41MjgzIDEwOC45OTYgNzcuNDY2OCAxMDkuNDYxIDc3LjQ2NjhDMTEwLjAxNyA3Ny40NjY4IDExMC41MDkgNzcuNTYwMiAxMTAuOTM4IDc3Ljc0NzFDMTExLjM3IDc3LjkzMzkgMTExLjcxIDc4LjIxNjUgMTExLjk1NiA3OC41OTQ3QzExMi4yMDcgNzguOTY4NCAxMTIuMzMyIDc5LjQzNzggMTEyLjMzMiA4MC4wMDI5VjgzLjI5MUMxMTIuMzMyIDgzLjYyODMgMTEyLjM1NSA4My45MzEzIDExMi40IDg0LjIwMDJDMTEyLjQ1MSA4NC40NjQ1IDExMi41MjEgODQuNjk0NyAxMTIuNjEyIDg0Ljg5MDZWODVIMTEwLjkyNEMxMTAuODQ2IDg0LjgyMjMgMTEwLjc4NSA4NC41OTY3IDExMC43MzkgODQuMzIzMkMxMTAuNjk4IDg0LjA0NTIgMTEwLjY3OCA4My43NzY0IDExMC42NzggODMuNTE2NlpNMTEwLjkxNyA4MC41MDJMMTEwLjkzMSA4MS41MjA1SDEwOS43NDhDMTA5LjQ0MyA4MS41MjA1IDEwOS4xNzQgODEuNTUwMSAxMDguOTQxIDgxLjYwOTRDMTA4LjcwOSA4MS42NjQxIDEwOC41MTUgODEuNzQ2MSAxMDguMzYgODEuODU1NUMxMDguMjA1IDgxLjk2NDggMTA4LjA4OSA4Mi4wOTcgMTA4LjAxMiA4Mi4yNTJDMTA3LjkzNCA4Mi40MDY5IDEwNy44OTYgODIuNTgyNCAxMDcuODk2IDgyLjc3ODNDMTA3Ljg5NiA4Mi45NzQzIDEwNy45NDEgODMuMTU0MyAxMDguMDMyIDgzLjMxODRDMTA4LjEyMyA4My40Nzc5IDEwOC4yNTYgODMuNjAzMiAxMDguNDI5IDgzLjY5NDNDMTA4LjYwNiA4My43ODU1IDEwOC44MjEgODMuODMxMSAxMDkuMDcxIDgzLjgzMTFDMTA5LjQwOSA4My44MzExIDEwOS43MDIgODMuNzYyNyAxMDkuOTUzIDgzLjYyNkMxMTAuMjA4IDgzLjQ4NDcgMTEwLjQwOSA4My4zMTM4IDExMC41NTUgODMuMTEzM0MxMTAuNzAxIDgyLjkwODIgMTEwLjc3OCA4Mi43MTQ1IDExMC43ODcgODIuNTMyMkwxMTEuMzIgODMuMjYzN0MxMTEuMjY2IDgzLjQ1MDUgMTExLjE3MiA4My42NTEgMTExLjA0IDgzLjg2NTJDMTEwLjkwOCA4NC4wNzk0IDExMC43MzUgODQuMjg0NSAxMTAuNTIxIDg0LjQ4MDVDMTEwLjMxMSA4NC42NzE5IDExMC4wNTggODQuODI5MSAxMDkuNzYyIDg0Ljk1MjFDMTA5LjQ3IDg1LjA3NTIgMTA5LjEzMyA4NS4xMzY3IDEwOC43NSA4NS4xMzY3QzEwOC4yNjcgODUuMTM2NyAxMDcuODM2IDg1LjA0MSAxMDcuNDU4IDg0Ljg0OTZDMTA3LjA4IDg0LjY1MzYgMTA2Ljc4NCA4NC4zOTE2IDEwNi41NjkgODQuMDYzNUMxMDYuMzU1IDgzLjczMDggMTA2LjI0OCA4My4zNTQ4IDEwNi4yNDggODIuOTM1NUMxMDYuMjQ4IDgyLjU0MzYgMTA2LjMyMSA4Mi4xOTczIDEwNi40NjcgODEuODk2NUMxMDYuNjE3IDgxLjU5MTEgMTA2LjgzNiA4MS4zMzU5IDEwNy4xMjMgODEuMTMwOUMxMDcuNDE1IDgwLjkyNTggMTA3Ljc3IDgwLjc3MDggMTA4LjE4OSA4MC42NjZDMTA4LjYwOSA4MC41NTY2IDEwOS4wODcgODAuNTAyIDEwOS42MjUgODAuNTAySDExMC45MTdaTTExNy42OTUgNzcuNjAzNVY3OC44MDY2SDExMy41MjVWNzcuNjAzNUgxMTcuNjk1Wk0xMTQuNzI5IDc1Ljc5MkgxMTYuMzc2VjgyLjk1NjFDMTE2LjM3NiA4My4xODM5IDExNi40MDggODMuMzU5NCAxMTYuNDcyIDgzLjQ4MjRDMTE2LjU0IDgzLjYwMDkgMTE2LjYzMyA4My42ODA3IDExNi43NTIgODMuNzIxN0MxMTYuODcgODMuNzYyNyAxMTcuMDA5IDgzLjc4MzIgMTE3LjE2OSA4My43ODMyQzExNy4yODMgODMuNzgzMiAxMTcuMzkyIDgzLjc3NjQgMTE3LjQ5NyA4My43NjI3QzExNy42MDIgODMuNzQ5IDExNy42ODYgODMuNzM1NCAxMTcuNzUgODMuNzIxN0wxMTcuNzU3IDg0Ljk3OTVDMTE3LjYyIDg1LjAyMDUgMTE3LjQ2MSA4NS4wNTcgMTE3LjI3OCA4NS4wODg5QzExNy4xMDEgODUuMTIwOCAxMTYuODk2IDg1LjEzNjcgMTE2LjY2MyA4NS4xMzY3QzExNi4yODUgODUuMTM2NyAxMTUuOTUgODUuMDcwNiAxMTUuNjU4IDg0LjkzODVDMTE1LjM2NyA4NC44MDE4IDExNS4xMzkgODQuNTgwNyAxMTQuOTc1IDg0LjI3NTRDMTE0LjgxMSA4My45NzAxIDExNC43MjkgODMuNTY0NSAxMTQuNzI5IDgzLjA1ODZWNzUuNzkyWk0xMjMuNjY3IDgzLjI1NjhWNzcuNjAzNUgxMjUuMzIxVjg1SDEyMy43NjNMMTIzLjY2NyA4My4yNTY4Wk0xMjMuODk5IDgxLjcxODhMMTI0LjQ1MyA4MS43MDUxQzEyNC40NTMgODIuMjAxOCAxMjQuMzk4IDgyLjY1OTggMTI0LjI4OSA4My4wNzkxQzEyNC4xOCA4My40OTM4IDEyNC4wMTEgODMuODU2MSAxMjMuNzgzIDg0LjE2NkMxMjMuNTU1IDg0LjQ3MTQgMTIzLjI2NCA4NC43MTA2IDEyMi45MDggODQuODgzOEMxMjIuNTUzIDg1LjA1MjQgMTIyLjEyNyA4NS4xMzY3IDEyMS42MyA4NS4xMzY3QzEyMS4yNyA4NS4xMzY3IDEyMC45MzkgODUuMDg0MyAxMjAuNjM5IDg0Ljk3OTVDMTIwLjMzOCA4NC44NzQ3IDEyMC4wNzggODQuNzEyOSAxMTkuODU5IDg0LjQ5NDFDMTE5LjY0NSA4NC4yNzU0IDExOS40NzkgODMuOTkwNiAxMTkuMzYgODMuNjM5NkMxMTkuMjQyIDgzLjI4ODcgMTE5LjE4MyA4Mi44Njk1IDExOS4xODMgODIuMzgxOFY3Ny42MDM1SDEyMC44M1Y4Mi4zOTU1QzEyMC44MyA4Mi42NjQ0IDEyMC44NjIgODIuODkgMTIwLjkyNiA4My4wNzIzQzEyMC45OSA4My4yNSAxMjEuMDc2IDgzLjM5MzYgMTIxLjE4NiA4My41MDI5QzEyMS4yOTUgODMuNjEyMyAxMjEuNDIzIDgzLjY4OTggMTIxLjU2OCA4My43MzU0QzEyMS43MTQgODMuNzgwOSAxMjEuODY5IDgzLjgwMzcgMTIyLjAzMyA4My44MDM3QzEyMi41MDMgODMuODAzNyAxMjIuODcyIDgzLjcxMjYgMTIzLjE0MSA4My41MzAzQzEyMy40MTQgODMuMzQzNCAxMjMuNjA4IDgzLjA5MjggMTIzLjcyMiA4Mi43NzgzQzEyMy44NCA4Mi40NjM5IDEyMy44OTkgODIuMTEwNyAxMjMuODk5IDgxLjcxODhaTTEyOC45MTQgNzkuMDExN1Y4NUgxMjcuMjY3Vjc3LjYwMzVIMTI4LjgzOUwxMjguOTE0IDc5LjAxMTdaTTEzMS4xNzcgNzcuNTU1N0wxMzEuMTYzIDc5LjA4NjlDMTMxLjA2MyA3OS4wNjg3IDEzMC45NTMgNzkuMDU1IDEzMC44MzUgNzkuMDQ1OUMxMzAuNzIxIDc5LjAzNjggMTMwLjYwNyA3OS4wMzIyIDEzMC40OTMgNzkuMDMyMkMxMzAuMjExIDc5LjAzMjIgMTI5Ljk2MiA3OS4wNzMyIDEyOS43NDggNzkuMTU1M0MxMjkuNTM0IDc5LjIzMjcgMTI5LjM1NCA3OS4zNDY3IDEyOS4yMDggNzkuNDk3MUMxMjkuMDY3IDc5LjY0MjkgMTI4Ljk1NyA3OS44MjA2IDEyOC44OCA4MC4wMzAzQzEyOC44MDIgODAuMjM5OSAxMjguNzU3IDgwLjQ3NDYgMTI4Ljc0MyA4MC43MzQ0TDEyOC4zNjcgODAuNzYxN0MxMjguMzY3IDgwLjI5NjkgMTI4LjQxMyA3OS44NjYyIDEyOC41MDQgNzkuNDY5N0MxMjguNTk1IDc5LjA3MzIgMTI4LjczMiA3OC43MjQ2IDEyOC45MTQgNzguNDIzOEMxMjkuMTAxIDc4LjEyMyAxMjkuMzMzIDc3Ljg4ODMgMTI5LjYxMSA3Ny43MTk3QzEyOS44OTQgNzcuNTUxMSAxMzAuMjIgNzcuNDY2OCAxMzAuNTg5IDc3LjQ2NjhDMTMwLjY4OSA3Ny40NjY4IDEzMC43OTYgNzcuNDc1OSAxMzAuOTEgNzcuNDk0MUMxMzEuMDI5IDc3LjUxMjQgMTMxLjExOCA3Ny41MzI5IDEzMS4xNzcgNzcuNTU1N1pNMTM1LjU4MyA4NS4xMzY3QzEzNS4wMzYgODUuMTM2NyAxMzQuNTQyIDg1LjA0NzkgMTM0LjEgODQuODcwMUMxMzMuNjYyIDg0LjY4NzggMTMzLjI4OCA4NC40MzQ5IDEzMi45NzkgODQuMTExM0MxMzIuNjczIDgzLjc4NzggMTMyLjQzOCA4My40MDcyIDEzMi4yNzQgODIuOTY5N0MxMzIuMTEgODIuNTMyMiAxMzIuMDI4IDgyLjA2MDUgMTMyLjAyOCA4MS41NTQ3VjgxLjI4MTJDMTMyLjAyOCA4MC43MDI1IDEzMi4xMTMgODAuMTc4NCAxMzIuMjgxIDc5LjcwOUMxMzIuNDUgNzkuMjM5NiAxMzIuNjg1IDc4LjgzODUgMTMyLjk4NSA3OC41MDU5QzEzMy4yODYgNzguMTY4NiAxMzMuNjQyIDc3LjkxMTEgMTM0LjA1MiA3Ny43MzM0QzEzNC40NjIgNzcuNTU1NyAxMzQuOTA2IDc3LjQ2NjggMTM1LjM4NSA3Ny40NjY4QzEzNS45MTMgNzcuNDY2OCAxMzYuMzc2IDc3LjU1NTcgMTM2Ljc3MiA3Ny43MzM0QzEzNy4xNjkgNzcuOTExMSAxMzcuNDk3IDc4LjE2MTggMTM3Ljc1NyA3OC40ODU0QzEzOC4wMjEgNzguODA0NCAxMzguMjE3IDc5LjE4NDkgMTM4LjM0NSA3OS42MjdDMTM4LjQ3NyA4MC4wNjkgMTM4LjU0MyA4MC41NTY2IDEzOC41NDMgODEuMDg5OFY4MS43OTM5SDEzMi44MjhWODAuNjExM0gxMzYuOTE2VjgwLjQ4MTRDMTM2LjkwNyA4MC4xODUyIDEzNi44NDggNzkuOTA3MiAxMzYuNzM4IDc5LjY0NzVDMTM2LjYzMyA3OS4zODc3IDEzNi40NzIgNzkuMTc4MSAxMzYuMjUzIDc5LjAxODZDMTM2LjAzNCA3OC44NTkgMTM1Ljc0MyA3OC43NzkzIDEzNS4zNzggNzguNzc5M0MxMzUuMTA0IDc4Ljc3OTMgMTM0Ljg2MSA3OC44Mzg1IDEzNC42NDYgNzguOTU3QzEzNC40MzcgNzkuMDcxIDEzNC4yNjEgNzkuMjM3MyAxMzQuMTIgNzkuNDU2MUMxMzMuOTc5IDc5LjY3NDggMTMzLjg2OSA3OS45MzkxIDEzMy43OTIgODAuMjQ5QzEzMy43MTkgODAuNTU0NCAxMzMuNjgzIDgwLjg5ODQgMTMzLjY4MyA4MS4yODEyVjgxLjU1NDdDMTMzLjY4MyA4MS44NzgzIDEzMy43MjYgODIuMTc5IDEzMy44MTIgODIuNDU3QzEzMy45MDQgODIuNzMwNSAxMzQuMDM2IDgyLjk2OTcgMTM0LjIwOSA4My4xNzQ4QzEzNC4zODIgODMuMzc5OSAxMzQuNTkyIDgzLjU0MTcgMTM0LjgzOCA4My42NjAyQzEzNS4wODQgODMuNzc0MSAxMzUuMzY0IDgzLjgzMTEgMTM1LjY3OSA4My44MzExQzEzNi4wNzUgODMuODMxMSAxMzYuNDI4IDgzLjc1MTMgMTM2LjczOCA4My41OTE4QzEzNy4wNDggODMuNDMyMyAxMzcuMzE3IDgzLjIwNjcgMTM3LjU0NSA4Mi45MTVMMTM4LjQxMyA4My43NTU5QzEzOC4yNTQgODMuOTg4MyAxMzguMDQ2IDg0LjIxMTYgMTM3Ljc5MSA4NC40MjU4QzEzNy41MzYgODQuNjM1NCAxMzcuMjI0IDg0LjgwNjMgMTM2Ljg1NCA4NC45Mzg1QzEzNi40OSA4NS4wNzA2IDEzNi4wNjYgODUuMTM2NyAxMzUuNTgzIDg1LjEzNjdaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg3Ii8+CjxwYXRoIGQ9Ik0xNjIuOTI0IDgzLjY4NzVWODVIMTU2LjI1MlY4My44NzIxTDE1OS40OTIgODAuMzM3OUMxNTkuODQ4IDc5LjkzNjggMTYwLjEyOCA3OS41OTA1IDE2MC4zMzMgNzkuMjk4OEMxNjAuNTM4IDc5LjAwNzIgMTYwLjY4MiA3OC43NDUxIDE2MC43NjQgNzguNTEyN0MxNjAuODUgNzguMjc1NyAxNjAuODk0IDc4LjA0NTYgMTYwLjg5NCA3Ny44MjIzQzE2MC44OTQgNzcuNTA3OCAxNjAuODM0IDc3LjIzMjEgMTYwLjcxNiA3Ni45OTUxQzE2MC42MDIgNzYuNzUzNiAxNjAuNDMzIDc2LjU2NDUgMTYwLjIxIDc2LjQyNzdDMTU5Ljk4NyA3Ni4yODY1IDE1OS43MTUgNzYuMjE1OCAxNTkuMzk2IDc2LjIxNThDMTU5LjAyNyA3Ni4yMTU4IDE1OC43MTcgNzYuMjk1NiAxNTguNDY3IDc2LjQ1NTFDMTU4LjIxNiA3Ni42MTQ2IDE1OC4wMjcgNzYuODM1NiAxNTcuODk5IDc3LjExODJDMTU3Ljc3MiA3Ny4zOTYyIDE1Ny43MDggNzcuNzE1MiAxNTcuNzA4IDc4LjA3NTJIMTU2LjA2MUMxNTYuMDYxIDc3LjQ5NjQgMTU2LjE5MyA3Ni45Njc4IDE1Ni40NTcgNzYuNDg5M0MxNTYuNzIxIDc2LjAwNjIgMTU3LjEwNCA3NS42MjM0IDE1Ny42MDUgNzUuMzQwOEMxNTguMTA3IDc1LjA1MzcgMTU4LjcxMSA3NC45MTAyIDE1OS40MTcgNzQuOTEwMkMxNjAuMDgyIDc0LjkxMDIgMTYwLjY0NyA3NS4wMjE4IDE2MS4xMTIgNzUuMjQ1MUMxNjEuNTc3IDc1LjQ2ODQgMTYxLjkzIDc1Ljc4NTIgMTYyLjE3MiA3Ni4xOTUzQzE2Mi40MTggNzYuNjA1NSAxNjIuNTQxIDc3LjA5MDggMTYyLjU0MSA3Ny42NTE0QzE2Mi41NDEgNzcuOTYxMyAxNjIuNDkxIDc4LjI2ODkgMTYyLjM5MSA3OC41NzQyQzE2Mi4yOSA3OC44Nzk2IDE2Mi4xNDcgNzkuMTg0OSAxNjEuOTYgNzkuNDkwMkMxNjEuNzc4IDc5Ljc5MSAxNjEuNTYxIDgwLjA5NDEgMTYxLjMxMSA4MC4zOTk0QzE2MS4wNiA4MC43MDAyIDE2MC43ODQgODEuMDA1NSAxNjAuNDgzIDgxLjMxNTRMMTU4LjMzIDgzLjY4NzVIMTYyLjkyNFpNMTcxLjEzMSA4My42ODc1Vjg1SDE2NC40NTlWODMuODcyMUwxNjcuNjk5IDgwLjMzNzlDMTY4LjA1NSA3OS45MzY4IDE2OC4zMzUgNzkuNTkwNSAxNjguNTQgNzkuMjk4OEMxNjguNzQ1IDc5LjAwNzIgMTY4Ljg4OSA3OC43NDUxIDE2OC45NzEgNzguNTEyN0MxNjkuMDU3IDc4LjI3NTcgMTY5LjEwMSA3OC4wNDU2IDE2OS4xMDEgNzcuODIyM0MxNjkuMTAxIDc3LjUwNzggMTY5LjA0MSA3Ny4yMzIxIDE2OC45MjMgNzYuOTk1MUMxNjguODA5IDc2Ljc1MzYgMTY4LjY0IDc2LjU2NDUgMTY4LjQxNyA3Ni40Mjc3QzE2OC4xOTQgNzYuMjg2NSAxNjcuOTIzIDc2LjIxNTggMTY3LjYwNCA3Ni4yMTU4QzE2Ny4yMzQgNzYuMjE1OCAxNjYuOTI0IDc2LjI5NTYgMTY2LjY3NCA3Ni40NTUxQzE2Ni40MjMgNzYuNjE0NiAxNjYuMjM0IDc2LjgzNTYgMTY2LjEwNiA3Ny4xMTgyQzE2NS45NzkgNzcuMzk2MiAxNjUuOTE1IDc3LjcxNTIgMTY1LjkxNSA3OC4wNzUySDE2NC4yNjhDMTY0LjI2OCA3Ny40OTY0IDE2NC40IDc2Ljk2NzggMTY0LjY2NCA3Ni40ODkzQzE2NC45MjggNzYuMDA2MiAxNjUuMzExIDc1LjYyMzQgMTY1LjgxMiA3NS4zNDA4QzE2Ni4zMTQgNzUuMDUzNyAxNjYuOTE4IDc0LjkxMDIgMTY3LjYyNCA3NC45MTAyQzE2OC4yODkgNzQuOTEwMiAxNjguODU0IDc1LjAyMTggMTY5LjMxOSA3NS4yNDUxQzE2OS43ODQgNzUuNDY4NCAxNzAuMTM3IDc1Ljc4NTIgMTcwLjM3OSA3Ni4xOTUzQzE3MC42MjUgNzYuNjA1NSAxNzAuNzQ4IDc3LjA5MDggMTcwLjc0OCA3Ny42NTE0QzE3MC43NDggNzcuOTYxMyAxNzAuNjk4IDc4LjI2ODkgMTcwLjU5OCA3OC41NzQyQzE3MC40OTcgNzguODc5NiAxNzAuMzU0IDc5LjE4NDkgMTcwLjE2NyA3OS40OTAyQzE2OS45ODUgNzkuNzkxIDE2OS43NjggODAuMDk0MSAxNjkuNTE4IDgwLjM5OTRDMTY5LjI2NyA4MC43MDAyIDE2OC45OTEgODEuMDA1NSAxNjguNjkgODEuMzE1NEwxNjYuNTM3IDgzLjY4NzVIMTcxLjEzMVpNMTcyLjc5NiA3Ni43MzU0QzE3Mi43OTYgNzYuNDAyNyAxNzIuODc4IDc2LjA5NzMgMTczLjA0MiA3NS44MTkzQzE3My4yMDYgNzUuNTQxMyAxNzMuNDI1IDc1LjMyMDMgMTczLjY5OCA3NS4xNTYyQzE3My45NzYgNzQuOTg3NiAxNzQuMjc3IDc0LjkwMzMgMTc0LjYwMSA3NC45MDMzQzE3NC45MjkgNzQuOTAzMyAxNzUuMjI3IDc0Ljk4NzYgMTc1LjQ5NiA3NS4xNTYyQzE3NS43NjUgNzUuMzIwMyAxNzUuOTc5IDc1LjU0MTMgMTc2LjEzOSA3NS44MTkzQzE3Ni4zMDMgNzYuMDk3MyAxNzYuMzg1IDc2LjQwMjcgMTc2LjM4NSA3Ni43MzU0QzE3Ni4zODUgNzcuMDY4IDE3Ni4zMDMgNzcuMzczNCAxNzYuMTM5IDc3LjY1MTRDMTc1Ljk3OSA3Ny45MjQ4IDE3NS43NjUgNzguMTQxMyAxNzUuNDk2IDc4LjMwMDhDMTc1LjIyNyA3OC40NjAzIDE3NC45MjkgNzguNTQgMTc0LjYwMSA3OC41NEMxNzQuMjc3IDc4LjU0IDE3My45NzYgNzguNDYwMyAxNzMuNjk4IDc4LjMwMDhDMTczLjQyNSA3OC4xNDEzIDE3My4yMDYgNzcuOTI0OCAxNzMuMDQyIDc3LjY1MTRDMTcyLjg3OCA3Ny4zNzM0IDE3Mi43OTYgNzcuMDY4IDE3Mi43OTYgNzYuNzM1NFpNMTczLjcxOSA3Ni43MzU0QzE3My43MTkgNzYuOTgxNCAxNzMuODA1IDc3LjE4ODggMTczLjk3OSA3Ny4zNTc0QzE3NC4xNTIgNzcuNTIxNSAxNzQuMzU5IDc3LjYwMzUgMTc0LjYwMSA3Ny42MDM1QzE3NC44NDIgNzcuNjAzNSAxNzUuMDQ1IDc3LjUyMTUgMTc1LjIwOSA3Ny4zNTc0QzE3NS4zNzMgNzcuMTkzNCAxNzUuNDU1IDc2Ljk4NiAxNzUuNDU1IDc2LjczNTRDMTc1LjQ1NSA3Ni40ODAxIDE3NS4zNzMgNzYuMjY4MiAxNzUuMjA5IDc2LjA5OTZDMTc1LjA0NSA3NS45MzEgMTc0Ljg0MiA3NS44NDY3IDE3NC42MDEgNzUuODQ2N0MxNzQuMzU5IDc1Ljg0NjcgMTc0LjE1MiA3NS45MzEgMTczLjk3OSA3Ni4wOTk2QzE3My44MDUgNzYuMjY4MiAxNzMuNzE5IDc2LjQ4MDEgMTczLjcxOSA3Ni43MzU0Wk0xODQuNDE0IDgxLjc1OThIMTg2LjEyM0MxODYuMDY4IDgyLjQxMTUgMTg1Ljg4NiA4Mi45OTI1IDE4NS41NzYgODMuNTAyOUMxODUuMjY2IDg0LjAwODggMTg0LjgzMSA4NC40MDc2IDE4NC4yNzEgODQuNjk5MkMxODMuNzEgODQuOTkwOSAxODMuMDI5IDg1LjEzNjcgMTgyLjIyNyA4NS4xMzY3QzE4MS42MTEgODUuMTM2NyAxODEuMDU4IDg1LjAyNzMgMTgwLjU2NSA4NC44MDg2QzE4MC4wNzMgODQuNTg1MyAxNzkuNjUyIDg0LjI3MDggMTc5LjMwMSA4My44NjUyQzE3OC45NSA4My40NTUxIDE3OC42ODEgODIuOTYwNiAxNzguNDk0IDgyLjM4MThDMTc4LjMxMiA4MS44MDMxIDE3OC4yMjEgODEuMTU1OSAxNzguMjIxIDgwLjQ0MDRWNzkuNjEzM0MxNzguMjIxIDc4Ljg5NzggMTc4LjMxNCA3OC4yNTA3IDE3OC41MDEgNzcuNjcxOUMxNzguNjkyIDc3LjA5MzEgMTc4Ljk2NiA3Ni41OTg2IDE3OS4zMjEgNzYuMTg4NUMxNzkuNjc3IDc1Ljc3MzggMTgwLjEwMyA3NS40NTcgMTgwLjYgNzUuMjM4M0MxODEuMTAxIDc1LjAxOTUgMTgxLjY2NCA3NC45MTAyIDE4Mi4yODggNzQuOTEwMkMxODMuMDgxIDc0LjkxMDIgMTgzLjc1MSA3NS4wNTYgMTg0LjI5OCA3NS4zNDc3QzE4NC44NDUgNzUuNjM5MyAxODUuMjY5IDc2LjA0MjYgMTg1LjU2OSA3Ni41NTc2QzE4NS44NzUgNzcuMDcyNiAxODYuMDYyIDc3LjY2MjggMTg2LjEzIDc4LjMyODFIMTg0LjQyMUMxODQuMzc1IDc3Ljg5OTcgMTg0LjI3NSA3Ny41MzI5IDE4NC4xMiA3Ny4yMjc1QzE4My45NyA3Ni45MjIyIDE4My43NDYgNzYuNjg5OCAxODMuNDUgNzYuNTMwM0MxODMuMTU0IDc2LjM2NjIgMTgyLjc2NyA3Ni4yODQyIDE4Mi4yODggNzYuMjg0MkMxODEuODk2IDc2LjI4NDIgMTgxLjU1NCA3Ni4zNTcxIDE4MS4yNjMgNzYuNTAyOUMxODAuOTcxIDc2LjY0ODggMTgwLjcyNyA3Ni44NjMgMTgwLjUzMSA3Ny4xNDU1QzE4MC4zMzUgNzcuNDI4MSAxODAuMTg3IDc3Ljc3NjcgMTgwLjA4NyA3OC4xOTE0QzE3OS45OTEgNzguNjAxNiAxNzkuOTQzIDc5LjA3MSAxNzkuOTQzIDc5LjU5OTZWODAuNDQwNEMxNzkuOTQzIDgwLjk0MTcgMTc5Ljk4NyA4MS4zOTc1IDE4MC4wNzMgODEuODA3NkMxODAuMTY0IDgyLjIxMzIgMTgwLjMwMSA4Mi41NjE4IDE4MC40ODMgODIuODUzNUMxODAuNjcgODMuMTQ1MiAxODAuOTA3IDgzLjM3MDggMTgxLjE5NCA4My41MzAzQzE4MS40ODEgODMuNjg5OCAxODEuODI2IDgzLjc2OTUgMTgyLjIyNyA4My43Njk1QzE4Mi43MTQgODMuNzY5NSAxODMuMTA4IDgzLjY5MjEgMTgzLjQwOSA4My41MzcxQzE4My43MTUgODMuMzgyMiAxODMuOTQ1IDgzLjE1NjYgMTg0LjEgODIuODYwNEMxODQuMjU5IDgyLjU1OTYgMTg0LjM2NCA4Mi4xOTI3IDE4NC40MTQgODEuNzU5OFoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPC9nPgo8ZGVmcz4KPGZpbHRlciBpZD0iZmlsdGVyMF9kXzQ5ODhfNDUwNTYiIHg9IjAiIHk9IjQ4IiB3aWR0aD0iMjEyIiBoZWlnaHQ9IjY4IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQgZHk9IjIiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMyIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4xMiAwIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0iZWZmZWN0MV9kcm9wU2hhZG93XzQ5ODhfNDUwNTYiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3dfNDk4OF80NTA1NiIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPGZpbHRlciBpZD0iZmlsdGVyMV9iXzQ5ODhfNDUwNTYiIHg9IjEyIiB5PSI1OCIgd2lkdGg9IjE4OCIgaGVpZ2h0PSI0NCIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVHYXVzc2lhbkJsdXIgaW49IkJhY2tncm91bmRJbWFnZUZpeCIgc3RkRGV2aWF0aW9uPSIzIi8+CjxmZUNvbXBvc2l0ZSBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iZWZmZWN0MV9iYWNrZ3JvdW5kQmx1cl80OTg4XzQ1MDU2Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9iYWNrZ3JvdW5kQmx1cl80OTg4XzQ1MDU2IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8L2RlZnM+Cjwvc3ZnPgo=", + "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": "tb-label-value-card-widget-settings", + "hasBasicMode": true, + "basicModeDirective": "tb-label-value-card-basic-config", + "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": [ + "label" + ] +} \ 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..ac8cb25d60 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,12 @@ 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'; +import { + LabelValueCardBasicConfigComponent +} from '@home/components/widget/config/basic/cards/label-value-card-basic-config.component'; @NgModule({ declarations: [ @@ -177,7 +183,9 @@ import { MobileAppQrCodeBasicConfigComponent } from '@home/components/widget/con PolarAreaChartBasicConfigComponent, RadarChartBasicConfigComponent, DigitalSimpleGaugeBasicConfigComponent, - MobileAppQrCodeBasicConfigComponent + MobileAppQrCodeBasicConfigComponent, + LabelCardBasicConfigComponent, + LabelValueCardBasicConfigComponent ], imports: [ CommonModule, @@ -224,7 +232,9 @@ import { MobileAppQrCodeBasicConfigComponent } from '@home/components/widget/con PolarAreaChartBasicConfigComponent, RadarChartBasicConfigComponent, DigitalSimpleGaugeBasicConfigComponent, - MobileAppQrCodeBasicConfigComponent + MobileAppQrCodeBasicConfigComponent, + LabelCardBasicConfigComponent, + LabelValueCardBasicConfigComponent ] }) export class BasicWidgetConfigModule { @@ -265,5 +275,7 @@ 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/config/basic/cards/label-value-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/label-value-card-basic-config.component.html new file mode 100644 index 0000000000..e0d301344e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/label-value-card-basic-config.component.html @@ -0,0 +1,114 @@ + + + + + + +
+
widget-config.appearance
+
+ + {{ 'widgets.label-card.auto-scale' | translate }} + +
+
+ + {{ 'widgets.label-card.label' | translate }} + +
+ + + + + + + +
+
+
+ + {{ 'widgets.label-card.icon' | translate }} + +
+ + + + + + + + +
+
+
+
widgets.label-value-card.value
+
+ + + +
widget-config.decimals-suffix
+
+ + + + +
+
+
+
{{ '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-value-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/label-value-card-basic-config.component.ts new file mode 100644 index 0000000000..5add2284ab --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/label-value-card-basic-config.component.ts @@ -0,0 +1,191 @@ +/// +/// 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 { + DataKey, + datasourcesHasAggregation, + datasourcesHasOnlyComparisonAggregation, + WidgetConfig, +} from '@shared/models/widget.models'; +import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { formatValue, isUndefined } from '@core/utils'; +import { + labelValueCardWidgetDefaultSettings, + LabelValueCardWidgetSettings +} from '@home/components/widget/lib/cards/label-value-card-widget.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { + getTimewindowConfig, + setTimewindowConfig +} from '@home/components/widget/config/timewindow-config-panel.component'; + +@Component({ + selector: 'tb-label-value-card-basic-config', + templateUrl: './label-value-card-basic-config.component.html', + styleUrls: ['../basic-config.scss'] +}) +export class LabelValueCardBasicConfigComponent extends BasicWidgetConfigComponent { + + public get displayTimewindowConfig(): boolean { + const datasources = this.labelValueCardWidgetConfigForm.get('datasources').value; + return datasourcesHasAggregation(datasources); + } + + public onlyHistoryTimewindow(): boolean { + const datasources = this.labelValueCardWidgetConfigForm.get('datasources').value; + return datasourcesHasOnlyComparisonAggregation(datasources); + } + + labelValueCardWidgetConfigForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + constructor(protected store: Store, + protected widgetConfigComponent: WidgetConfigComponent, + private fb: UntypedFormBuilder) { + super(store, widgetConfigComponent); + } + + protected configForm(): UntypedFormGroup { + return this.labelValueCardWidgetConfigForm; + } + + protected defaultDataKeys(configData: WidgetConfigComponentData): DataKey[] { + return [{ name: 'temperature', label: 'Temperature', type: DataKeyType.timeseries }]; + } + + protected onConfigSet(configData: WidgetConfigComponentData) { + const settings: LabelValueCardWidgetSettings = {...labelValueCardWidgetDefaultSettings, ...(configData.config.settings || {})}; + this.labelValueCardWidgetConfigForm = this.fb.group({ + timewindowConfig: [getTimewindowConfig(configData.config), []], + datasources: [configData.config.datasources, []], + autoScale: [settings.autoScale, []], + + showLabel: [settings.showLabel, []], + 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, []], + + units: [configData.config.units, []], + decimals: [configData.config.decimals, []], + valueFont: [settings.valueFont, []], + valueColor: [settings.valueColor, []], + + background: [settings.background, []], + + cardButtons: [this.getCardButtons(configData.config), []], + borderRadius: [configData.config.borderRadius, []], + padding: [settings.padding, []], + + actions: [configData.config.actions || {}, []] + }); + } + + protected prepareOutputConfig(config: any): WidgetConfigComponentData { + setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig); + this.widgetConfig.config.datasources = config.datasources; + + this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; + + this.widgetConfig.config.settings.autoScale = config.autoScale; + + this.widgetConfig.config.settings.showLabel = config.showLabel; + 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.units = config.units; + this.widgetConfig.config.decimals = config.decimals; + this.widgetConfig.config.settings.valueFont = config.valueFont; + this.widgetConfig.config.settings.valueColor = config.valueColor; + + 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 ['showLabel', 'showIcon']; + } + + protected updateValidators(emitEvent: boolean, trigger?: string) { + const showLabel: boolean = this.labelValueCardWidgetConfigForm.get('showLabel').value; + const showIcon: boolean = this.labelValueCardWidgetConfigForm.get('showIcon').value; + + if (showLabel) { + this.labelValueCardWidgetConfigForm.get('label').enable(); + this.labelValueCardWidgetConfigForm.get('labelFont').enable(); + this.labelValueCardWidgetConfigForm.get('labelColor').enable(); + } else { + this.labelValueCardWidgetConfigForm.get('label').disable(); + this.labelValueCardWidgetConfigForm.get('labelFont').disable(); + this.labelValueCardWidgetConfigForm.get('labelColor').disable(); + } + + if (showIcon) { + this.labelValueCardWidgetConfigForm.get('iconSize').enable(); + this.labelValueCardWidgetConfigForm.get('iconSizeUnit').enable(); + this.labelValueCardWidgetConfigForm.get('icon').enable(); + this.labelValueCardWidgetConfigForm.get('iconColor').enable(); + } else { + this.labelValueCardWidgetConfigForm.get('iconSize').disable(); + this.labelValueCardWidgetConfigForm.get('iconSizeUnit').disable(); + this.labelValueCardWidgetConfigForm.get('icon').disable(); + this.labelValueCardWidgetConfigForm.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'); + } + + private _valuePreviewFn(): string { + const units: string = this.labelValueCardWidgetConfigForm.get('units').value; + const decimals: number = this.labelValueCardWidgetConfigForm.get('decimals').value; + return formatValue(22, decimals, units, true); + } +} 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..9a47a5db9d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/label-value-card-widget.component.ts @@ -0,0 +1,218 @@ +/// +/// 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 value; + if (tsValue && isDefinedAndNotNull(tsValue[1]) && tsValue[0] !== 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..166170c8ee --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-card-widget-settings.component.ts @@ -0,0 +1,83 @@ +/// +/// 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 { 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 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/cards/label-value-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-value-card-widget-settings.component.html new file mode 100644 index 0000000000..b10d497f6c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-value-card-widget-settings.component.html @@ -0,0 +1,81 @@ + + +
+
widgets.label-value-card.label-value-card-style
+
+ + {{ 'widgets.label-card.auto-scale' | translate }} + +
+
+ + {{ 'widgets.label-card.label' | translate }} + +
+ + + + + + + +
+
+
+ + {{ 'widgets.label-card.icon' | translate }} + +
+ + + + + + + + +
+
+
+
widgets.label-value-card.value
+
+ + + + +
+
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-value-card-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-value-card-widget-settings.component.ts new file mode 100644 index 0000000000..b8c80ba0de --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-value-card-widget-settings.component.ts @@ -0,0 +1,108 @@ +/// +/// 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 { 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 { labelValueCardWidgetDefaultSettings } from '@home/components/widget/lib/cards/label-value-card-widget.models'; +import { formatValue } from '@core/utils'; + +@Component({ + selector: 'tb-label-value-card-widget-settings', + templateUrl: './label-value-card-widget-settings.component.html', + styleUrls: [] +}) +export class LabelValueCardWidgetSettingsComponent extends WidgetSettingsComponent { + + labelValueCardWidgetSettingsForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.labelValueCardWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return {...labelValueCardWidgetDefaultSettings}; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.labelValueCardWidgetSettingsForm = this.fb.group({ + autoScale: [settings.autoScale, []], + + showLabel: [settings.showLabel, []], + 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, []], + + valueFont: [settings.valueFont, []], + valueColor: [settings.valueColor, []], + + background: [settings.background, []], + padding: [settings.padding, []] + }); + } + + protected validatorTriggers(): string[] { + return ['showLabel', 'showIcon']; + } + + protected updateValidators(emitEvent: boolean) { + const showLabel: boolean = this.labelValueCardWidgetSettingsForm.get('showLabel').value; + const showIcon: boolean = this.labelValueCardWidgetSettingsForm.get('showIcon').value; + + if (showLabel) { + this.labelValueCardWidgetSettingsForm.get('label').enable(); + this.labelValueCardWidgetSettingsForm.get('labelFont').enable(); + this.labelValueCardWidgetSettingsForm.get('labelColor').enable(); + } else { + this.labelValueCardWidgetSettingsForm.get('label').disable(); + this.labelValueCardWidgetSettingsForm.get('labelFont').disable(); + this.labelValueCardWidgetSettingsForm.get('labelColor').disable(); + } + + if (showIcon) { + this.labelValueCardWidgetSettingsForm.get('iconSize').enable(); + this.labelValueCardWidgetSettingsForm.get('iconSizeUnit').enable(); + this.labelValueCardWidgetSettingsForm.get('icon').enable(); + this.labelValueCardWidgetSettingsForm.get('iconColor').enable(); + } else { + this.labelValueCardWidgetSettingsForm.get('iconSize').disable(); + this.labelValueCardWidgetSettingsForm.get('iconSizeUnit').disable(); + this.labelValueCardWidgetSettingsForm.get('icon').disable(); + this.labelValueCardWidgetSettingsForm.get('iconColor').disable(); + } + } + + private _valuePreviewFn(): string { + const units: string = this.widgetConfig.config.units; + const decimals: number = this.widgetConfig.config.decimals; + return formatValue(22, decimals, units, true); + } +} 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..e4df67e2f8 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,12 @@ 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'; +import { + LabelValueCardWidgetSettingsComponent +} from '@home/components/widget/lib/settings/cards/label-value-card-widget-settings.component'; @NgModule({ declarations: [ @@ -482,7 +488,9 @@ import { PieChartWidgetSettingsComponent, BarChartWidgetSettingsComponent, PolarAreaChartWidgetSettingsComponent, - RadarChartWidgetSettingsComponent + RadarChartWidgetSettingsComponent, + LabelCardWidgetSettingsComponent, + LabelValueCardWidgetSettingsComponent ], imports: [ CommonModule, @@ -614,7 +622,9 @@ import { PieChartWidgetSettingsComponent, BarChartWidgetSettingsComponent, PolarAreaChartWidgetSettingsComponent, - RadarChartWidgetSettingsComponent + RadarChartWidgetSettingsComponent, + LabelCardWidgetSettingsComponent, + LabelValueCardWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -713,5 +723,7 @@ export const widgetSettingsComponentsMap: {[key: string]: Type