diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index 568a144874..560063a2c4 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -11,6 +11,7 @@ "single_switch", "command_button", "power_button", + "slider", "control_widgets.switch_control", "control_widgets.slide_toggle_control", "control_widgets.round_switch", diff --git a/application/src/main/data/json/system/widget_types/progress_bar.json b/application/src/main/data/json/system/widget_types/progress_bar.json index 1a92b6d470..6e60c20942 100644 --- a/application/src/main/data/json/system/widget_types/progress_bar.json +++ b/application/src/main/data/json/system/widget_types/progress_bar.json @@ -2,7 +2,7 @@ "fqn": "progress_bar", "name": "Progress bar", "deprecated": false, - "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAeFBMVEXg4ODf39/g4ODg4OAAAAD////g4OD19fUhISE/Ut3j4+OsrKzHx8eQkJB0dHQ8PDzx8fEvLy+6urrV1dWCgoJYWFienp5KSkrv7++GhoZmZmaXl5f39/epqanLy8vCwsL29vZtfORiceJ+fn7m5ubU1NTn6fve4fIbcz38AAAABXRSTlPvIL+vAC9A4IoAAASZSURBVHja7d0NT9swEIDhMji4nb/jxEk/NmCM7f//w/niQLtJyxamtdfiF6EmDkh+1Di0IojVzdWH1e25d311s7paDXD2+cy49nABDfm0gotodSmQ2woRVoVIq0KkVSHSqhBpVYi0KkRaFSKtGUijctrDv6XQwEzHgDjkXLoAiKaoAurzhxAAGGxBm2gMgE+25SHeUNE0QKbRNo/otlWQo2QNQa6xrdpD1DSqW6vyBhmtrD86RKHNGIcOyOUHZJDDEEIe1xgQdT487gGF0CNmgUXnsH+BhPzhCKDFfNh50OgQ6dgQv0HFEB2hw8QYDQo3HlqGZFf0066GDiNQFmvcMEZNEMdH2zza8WhivopwVEjX9QF7yBCVSbwFCc04YdBoy4xhkw3QYAsWjQciiNhH8OT3a8RnXo4ooRm/M3dMSN91m+QnCM+6zN9h2ShTyq4xy08X9mpcVxgswR4yfktymDsJhICbIPEA4n+GJJXTfNS48atIWQzxABKQl0YEJQDiQ/Dl1LeoDyHtONWYj41TDtDYvJMwTZA2w7ADwwNJAAQS9sqgY4NLyb1CKGCrGNeh0QY7Pq6bHvUEQaMcKh5VJkiAgAmIHfGGw7B5hQD1iMHkR4vlC5TLA+l1jeS9dlo5LdojQeYj/7rBp8xrnvz+kSOCg349WjolpES9itphBEm9BeI3OJ46omLI8qhppP2e6z28sTqvKkRaFSKtCpFWhUirQqRVIdKqEGlViLQqRFoVIq0KkVaFSKtCpFUh0qoQaVWItCpEWhUirQqR1jzER609HEbSbhT4G4g3AXOWYKrpAnYgszlIjyVXJNTl7XOEJMQ+ArXT5L3LpBQJZDYD6RGJAQED5DKoBbnN3uTfvIAAgBB7EFyBzBbQlXsTNV/EQGh/hPgWUZW7e1PglR+hJOw6PA/RfCs2Gsg5nApFYvtO1L2A8xC1n3ne6DWQRewKEfQGSM7FeB5CSrUOUY0QN13D0DPEQuzANiAlhsxHAYNnSA9ci6gZ5Jq2VRbEVCCzWcRYrl2cKRAgk6gT9NPx9xBvbTqYeofBTyz9IlTaiDm3Zp6RckoxAePIScxzGKBEPTnVK5DRDMS+vtZyLMguBbTZv1BxpAw0UpbJDMQ7LAWCXIMl52EsGVAtKCmvv2YgQNYxY0PTbs97xh9SN07KemfITFHHw4nzntA3jO/kPfsZVSHSqhBpVYi0KkRaFSKtCpHWO4B8e/y4rC/f7/5/D8shXz4u7fHuCO0WQz4u7+4YLYY8LnZ8uTtCu+VrZOm59XiMNbJ7x1etM6tCpFUh0qoQac1A7s325L+P2t1/AoBhu90O04SWQ4b1ANsTP10Pa7XOD5/u4fMWbte74WlYDnlOAM3Jn5KBIesBHp7gXmXS5+WQz5/EQOz42TQVIgWyHgDyqfVGyPD0AOkZThxD+JrznBf7NpO+LodAszZbOHEF8nW9ZYEyZvnll9vtQErDNKH3/ZP9vKoQaVWItCpEWhUirQqRVoVIq0KkVSHSqhBpVYi0LgiygovodvXhAv71NIC/Xl2txPzpxNsbMuPm6vr23Ft9uLr5AdpbpSAPQyy7AAAAAElFTkSuQmCC", + "image": "tb-image:cHJvZ3Jlc3NfYmFyX3N5c3RlbV93aWRnZXRfaW1hZ2UucG5n:IlByb2dyZXNzIGJhciIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF80Njg1XzQwOTg5KSI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfZF80Njg1XzQwOTg5KSI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiByeD0iNCIgZmlsbD0id2hpdGUiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMV9iXzQ2ODVfNDA5ODkpIj4KPHJlY3QgeD0iMTIiIHk9IjEyIiB3aWR0aD0iMTc2IiBoZWlnaHQ9IjEzNiIgcng9IjQiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPC9nPgo8cGF0aCBkPSJNMTYuNDQ5NyAxOS41NTMySDE0LjA0MzlWMTguMjlIMTYuNDQ5N0MxNi44Njg3IDE4LjI5IDE3LjIwNzIgMTguMjIyMyAxNy40NjUzIDE4LjA4NjlDMTcuNzIzNSAxNy45NTE1IDE3LjkxMTggMTcuNzY1MyAxOC4wMzAzIDE3LjUyODNDMTguMTUzIDE3LjI4NzEgMTguMjE0NCAxNy4wMTIgMTguMjE0NCAxNi43MDMxQzE4LjIxNDQgMTYuNDExMSAxOC4xNTMgMTYuMTM4MiAxOC4wMzAzIDE1Ljg4NDNDMTcuOTExOCAxNS42MjYxIDE3LjcyMzUgMTUuNDE4OCAxNy40NjUzIDE1LjI2MjJDMTcuMjA3MiAxNS4xMDU2IDE2Ljg2ODcgMTUuMDI3MyAxNi40NDk3IDE1LjAyNzNIMTQuNTMyN1YyM0gxMi45Mzk1VjEzLjc1NzhIMTYuNDQ5N0MxNy4xNjQ5IDEzLjc1NzggMTcuNzcyMSAxMy44ODQ4IDE4LjI3MTUgMTQuMTM4N0MxOC43NzUxIDE0LjM4ODMgMTkuMTU4IDE0LjczNTQgMTkuNDIwNCAxNS4xNzk3QzE5LjY4MjggMTUuNjE5OCAxOS44MTQgMTYuMTIzNCAxOS44MTQgMTYuNjkwNEMxOS44MTQgMTcuMjg3MSAxOS42ODI4IDE3Ljc5OTIgMTkuNDIwNCAxOC4yMjY2QzE5LjE1OCAxOC42NTQgMTguNzc1MSAxOC45ODE5IDE4LjI3MTUgMTkuMjEwNEMxNy43NzIxIDE5LjQzOSAxNy4xNjQ5IDE5LjU1MzIgMTYuNDQ5NyAxOS41NTMyWk0yMi44NzYgMTcuNDM5NVYyM0gyMS4zNDYyVjE2LjEzMThIMjIuODA2MkwyMi44NzYgMTcuNDM5NVpNMjQuOTc3MSAxNi4wODc0TDI0Ljk2NDQgMTcuNTA5M0MyNC44NzEzIDE3LjQ5MjQgMjQuNzY5NyAxNy40Nzk3IDI0LjY1OTcgMTcuNDcxMkMyNC41NTM5IDE3LjQ2MjcgMjQuNDQ4MSAxNy40NTg1IDI0LjM0MjMgMTcuNDU4NUMyNC4wNzk5IDE3LjQ1ODUgMjMuODQ5MyAxNy40OTY2IDIzLjY1MDQgMTcuNTcyOEMyMy40NTE1IDE3LjY0NDcgMjMuMjg0MyAxNy43NTA1IDIzLjE0ODkgMTcuODkwMUMyMy4wMTc3IDE4LjAyNTYgMjIuOTE2MiAxOC4xOTA2IDIyLjg0NDIgMTguMzg1M0MyMi43NzIzIDE4LjU3OTkgMjIuNzMgMTguNzk3OSAyMi43MTczIDE5LjAzOTFMMjIuMzY4MiAxOS4wNjQ1QzIyLjM2ODIgMTguNjMyOCAyMi40MTA1IDE4LjIzMjkgMjIuNDk1MSAxNy44NjQ3QzIyLjU3OTggMTcuNDk2NiAyMi43MDY3IDE3LjE3MjkgMjIuODc2IDE2Ljg5MzZDMjMuMDQ5NSAxNi42MTQzIDIzLjI2NTMgMTYuMzk2MyAyMy41MjM0IDE2LjIzOTdDMjMuNzg1OCAxNi4wODMyIDI0LjA4ODQgMTYuMDA0OSAyNC40MzEyIDE2LjAwNDlDMjQuNTI0MyAxNi4wMDQ5IDI0LjYyMzcgMTYuMDEzMyAyNC43Mjk1IDE2LjAzMDNDMjQuODM5NSAxNi4wNDcyIDI0LjkyMiAxNi4wNjYyIDI0Ljk3NzEgMTYuMDg3NFpNMjUuNzI4NSAxOS42NDIxVjE5LjQ5NjFDMjUuNzI4NSAxOS4wMDEgMjUuODAwNSAxOC41NDE4IDI1Ljk0NDMgMTguMTE4N0MyNi4wODgyIDE3LjY5MTIgMjYuMjk1NiAxNy4zMjEgMjYuNTY2NCAxNy4wMDc4QzI2Ljg0MTUgMTYuNjkwNCAyNy4xNzU4IDE2LjQ0NSAyNy41NjkzIDE2LjI3MTVDMjcuOTY3MSAxNi4wOTM4IDI4LjQxNTcgMTYuMDA0OSAyOC45MTUgMTYuMDA0OUMyOS40MTg2IDE2LjAwNDkgMjkuODY3MiAxNi4wOTM4IDMwLjI2MDcgMTYuMjcxNUMzMC42NTg1IDE2LjQ0NSAzMC45OTUgMTYuNjkwNCAzMS4yNyAxNy4wMDc4QzMxLjU0NTEgMTcuMzIxIDMxLjc1NDYgMTcuNjkxMiAzMS44OTg0IDE4LjExODdDMzIuMDQyMyAxOC41NDE4IDMyLjExNDMgMTkuMDAxIDMyLjExNDMgMTkuNDk2MVYxOS42NDIxQzMyLjExNDMgMjAuMTM3MiAzMi4wNDIzIDIwLjU5NjQgMzEuODk4NCAyMS4wMTk1QzMxLjc1NDYgMjEuNDQyNyAzMS41NDUxIDIxLjgxMyAzMS4yNyAyMi4xMzA0QzMwLjk5NSAyMi40NDM1IDMwLjY2MDYgMjIuNjg5IDMwLjI2NzEgMjIuODY2N0MyOS44NzM1IDIzLjA0MDIgMjkuNDI3MSAyMy4xMjcgMjguOTI3NyAyMy4xMjdDMjguNDI0MiAyMy4xMjcgMjcuOTczNSAyMy4wNDAyIDI3LjU3NTcgMjIuODY2N0MyNy4xODIxIDIyLjY4OSAyNi44NDc4IDIyLjQ0MzUgMjYuNTcyOCAyMi4xMzA0QzI2LjI5NzcgMjEuODEzIDI2LjA4ODIgMjEuNDQyNyAyNS45NDQzIDIxLjAxOTVDMjUuODAwNSAyMC41OTY0IDI1LjcyODUgMjAuMTM3MiAyNS43Mjg1IDE5LjY0MjFaTTI3LjI1ODMgMTkuNDk2MVYxOS42NDIxQzI3LjI1ODMgMTkuOTUxIDI3LjI5IDIwLjI0MyAyNy4zNTM1IDIwLjUxODFDMjcuNDE3IDIwLjc5MzEgMjcuNTE2NCAyMS4wMzQzIDI3LjY1MTkgMjEuMjQxN0MyNy43ODczIDIxLjQ0OTEgMjcuOTYwOCAyMS42MTIgMjguMTcyNCAyMS43MzA1QzI4LjM4NCAyMS44NDkgMjguNjM1NyAyMS45MDgyIDI4LjkyNzcgMjEuOTA4MkMyOS4yMTEzIDIxLjkwODIgMjkuNDU2NyAyMS44NDkgMjkuNjY0MSAyMS43MzA1QzI5Ljg3NTcgMjEuNjEyIDMwLjA0OTIgMjEuNDQ5MSAzMC4xODQ2IDIxLjI0MTdDMzAuMzIgMjEuMDM0MyAzMC40MTk0IDIwLjc5MzEgMzAuNDgyOSAyMC41MTgxQzMwLjU1MDYgMjAuMjQzIDMwLjU4NDUgMTkuOTUxIDMwLjU4NDUgMTkuNjQyMVYxOS40OTYxQzMwLjU4NDUgMTkuMTkxNCAzMC41NTA2IDE4LjkwMzYgMzAuNDgyOSAxOC42MzI4QzMwLjQxOTQgMTguMzU3NyAzMC4zMTc5IDE4LjExNDQgMzAuMTc4MiAxNy45MDI4QzMwLjA0MjggMTcuNjkxMiAyOS44NjkzIDE3LjUyNjIgMjkuNjU3NyAxNy40MDc3QzI5LjQ1MDQgMTcuMjg1IDI5LjIwMjggMTcuMjIzNiAyOC45MTUgMTcuMjIzNkMyOC42MjczIDE3LjIyMzYgMjguMzc3NiAxNy4yODUgMjguMTY2IDE3LjQwNzdDMjcuOTU4NyAxNy41MjYyIDI3Ljc4NzMgMTcuNjkxMiAyNy42NTE5IDE3LjkwMjhDMjcuNTE2NCAxOC4xMTQ0IDI3LjQxNyAxOC4zNTc3IDI3LjM1MzUgMTguNjMyOEMyNy4yOSAxOC45MDM2IDI3LjI1ODMgMTkuMTkxNCAyNy4yNTgzIDE5LjQ5NjFaTTM4LjA0NTQgMTYuMTMxOEgzOS40MzU1VjIyLjgwOTZDMzkuNDM1NSAyMy40Mjc0IDM5LjMwNDQgMjMuOTUyMSAzOS4wNDIgMjQuMzgzOEMzOC43Nzk2IDI0LjgxNTQgMzguNDEzNiAyNS4xNDM0IDM3Ljk0MzggMjUuMzY3N0MzNy40NzQxIDI1LjU5NjIgMzYuOTMwMyAyNS43MTA0IDM2LjMxMjUgMjUuNzEwNEMzNi4wNTAxIDI1LjcxMDQgMzUuNzU4MSAyNS42NzI0IDM1LjQzNjUgMjUuNTk2MkMzNS4xMTkxIDI1LjUyIDM0LjgxMDIgMjUuMzk3MyAzNC41MDk4IDI1LjIyOEMzNC4yMTM1IDI1LjA2MyAzMy45NjYgMjQuODQ1MSAzMy43NjcxIDI0LjU3NDJMMzQuNDg0NCAyMy42NzI5QzM0LjcyOTggMjMuOTY0OCAzNS4wMDA3IDI0LjE3ODUgMzUuMjk2OSAyNC4zMTRDMzUuNTkzMSAyNC40NDk0IDM1LjkwNDEgMjQuNTE3MSAzNi4yMyAyNC41MTcxQzM2LjU4MTIgMjQuNTE3MSAzNi44Nzk2IDI0LjQ1MTUgMzcuMTI1IDI0LjMyMDNDMzcuMzc0NyAyNC4xOTM0IDM3LjU2NzIgMjQuMDA1IDM3LjcwMjYgMjMuNzU1NEMzNy44MzgxIDIzLjUwNTcgMzcuOTA1OCAyMy4yMDEgMzcuOTA1OCAyMi44NDEzVjE3LjY4N0wzOC4wNDU0IDE2LjEzMThaTTMzLjM3OTkgMTkuNjQyMVYxOS41MDg4QzMzLjM3OTkgMTguOTg4MyAzMy40NDM0IDE4LjUxNDMgMzMuNTcwMyAxOC4wODY5QzMzLjY5NzMgMTcuNjU1MyAzMy44NzkyIDE3LjI4NSAzNC4xMTYyIDE2Ljk3NjFDMzQuMzUzMiAxNi42NjI5IDM0LjY0MSAxNi40MjM4IDM0Ljk3OTUgMTYuMjU4OEMzNS4zMTggMTYuMDg5NSAzNS43MDEgMTYuMDA0OSAzNi4xMjg0IDE2LjAwNDlDMzYuNTcyOCAxNi4wMDQ5IDM2Ljk1MTUgMTYuMDg1MyAzNy4yNjQ2IDE2LjI0NjFDMzcuNTgyIDE2LjQwNjkgMzcuODQ2NSAxNi42Mzc1IDM4LjA1ODEgMTYuOTM4QzM4LjI2OTcgMTcuMjM0MiAzOC40MzQ3IDE3LjU4OTcgMzguNTUzMiAxOC4wMDQ0QzM4LjY3NTkgMTguNDE0OSAzOC43NjY5IDE4Ljg3MTkgMzguODI2MiAxOS4zNzU1VjE5LjgwMDhDMzguNzcxMiAyMC4yOTE3IDM4LjY3ODEgMjAuNzQwMiAzOC41NDY5IDIxLjE0NjVDMzguNDE1NyAyMS41NTI3IDM4LjI0MjIgMjEuOTA0IDM4LjAyNjQgMjIuMjAwMkMzNy44MTA1IDIyLjQ5NjQgMzcuNTQzOSAyMi43MjQ5IDM3LjIyNjYgMjIuODg1N0MzNi45MTM0IDIzLjA0NjUgMzYuNTQzMSAyMy4xMjcgMzYuMTE1NyAyMy4xMjdDMzUuNjk2OCAyMy4xMjcgMzUuMzE4IDIzLjA0MDIgMzQuOTc5NSAyMi44NjY3QzM0LjY0NTIgMjIuNjkzMiAzNC4zNTc0IDIyLjQ0OTkgMzQuMTE2MiAyMi4xMzY3QzMzLjg3OTIgMjEuODIzNiAzMy42OTczIDIxLjQ1NTQgMzMuNTcwMyAyMS4wMzIyQzMzLjQ0MzQgMjAuNjA0OCAzMy4zNzk5IDIwLjE0MTQgMzMuMzc5OSAxOS42NDIxWk0zNC45MDk3IDE5LjUwODhWMTkuNjQyMUMzNC45MDk3IDE5Ljk1NTIgMzQuOTM5MyAyMC4yNDcyIDM0Ljk5ODUgMjAuNTE4MUMzNS4wNjIgMjAuNzg4OSAzNS4xNTcyIDIxLjAyOCAzNS4yODQyIDIxLjIzNTRDMzUuNDE1NCAyMS40Mzg1IDM1LjU4MDQgMjEuNTk5MyAzNS43NzkzIDIxLjcxNzhDMzUuOTgyNCAyMS44MzIgMzYuMjIxNSAyMS44ODkyIDM2LjQ5NjYgMjEuODg5MkMzNi44NTYzIDIxLjg4OTIgMzcuMTUwNCAyMS44MTMgMzcuMzc4OSAyMS42NjA2QzM3LjYxMTcgMjEuNTA4MyAzNy43ODk0IDIxLjMwMzEgMzcuOTEyMSAyMS4wNDQ5QzM4LjAzOTEgMjAuNzgyNiAzOC4xMjc5IDIwLjQ5MDYgMzguMTc4NyAyMC4xNjg5VjE5LjAyQzM4LjE1MzMgMTguNzcwMyAzOC4xMDA0IDE4LjUzNzYgMzguMDIgMTguMzIxOEMzNy45NDM4IDE4LjEwNiAzNy44NDAyIDE3LjkxNzYgMzcuNzA5IDE3Ljc1NjhDMzcuNTc3OCAxNy41OTE4IDM3LjQxMjggMTcuNDY0OCAzNy4yMTM5IDE3LjM3NkMzNy4wMTUgMTcuMjgyOSAzNi43ODAxIDE3LjIzNjMgMzYuNTA5MyAxNy4yMzYzQzM2LjIzNDIgMTcuMjM2MyAzNS45OTUxIDE3LjI5NTYgMzUuNzkyIDE3LjQxNDFDMzUuNTg4OSAxNy41MzI2IDM1LjQyMTcgMTcuNjk1NSAzNS4yOTA1IDE3LjkwMjhDMzUuMTYzNiAxOC4xMTAyIDM1LjA2ODQgMTguMzUxNCAzNS4wMDQ5IDE4LjYyNjVDMzQuOTQxNCAxOC45MDE1IDM0LjkwOTcgMTkuMTk1NiAzNC45MDk3IDE5LjUwODhaTTQyLjgwODYgMTcuNDM5NVYyM0g0MS4yNzg4VjE2LjEzMThINDIuNzM4OEw0Mi44MDg2IDE3LjQzOTVaTTQ0LjkwOTcgMTYuMDg3NEw0NC44OTcgMTcuNTA5M0M0NC44MDM5IDE3LjQ5MjQgNDQuNzAyMyAxNy40Nzk3IDQ0LjU5MjMgMTcuNDcxMkM0NC40ODY1IDE3LjQ2MjcgNDQuMzgwNyAxNy40NTg1IDQ0LjI3NDkgMTcuNDU4NUM0NC4wMTI1IDE3LjQ1ODUgNDMuNzgxOSAxNy40OTY2IDQzLjU4MyAxNy41NzI4QzQzLjM4NDEgMTcuNjQ0NyA0My4yMTcgMTcuNzUwNSA0My4wODE1IDE3Ljg5MDFDNDIuOTUwNCAxOC4wMjU2IDQyLjg0ODggMTguMTkwNiA0Mi43NzY5IDE4LjM4NTNDNDIuNzA0OSAxOC41Nzk5IDQyLjY2MjYgMTguNzk3OSA0Mi42NDk5IDE5LjAzOTFMNDIuMzAwOCAxOS4wNjQ1QzQyLjMwMDggMTguNjMyOCA0Mi4zNDMxIDE4LjIzMjkgNDIuNDI3NyAxNy44NjQ3QzQyLjUxMjQgMTcuNDk2NiA0Mi42MzkzIDE3LjE3MjkgNDIuODA4NiAxNi44OTM2QzQyLjk4MjEgMTYuNjE0MyA0My4xOTc5IDE2LjM5NjMgNDMuNDU2MSAxNi4yMzk3QzQzLjcxODQgMTYuMDgzMiA0NC4wMjEgMTYuMDA0OSA0NC4zNjM4IDE2LjAwNDlDNDQuNDU2OSAxNi4wMDQ5IDQ0LjU1NjMgMTYuMDEzMyA0NC42NjIxIDE2LjAzMDNDNDQuNzcyMSAxNi4wNDcyIDQ0Ljg1NDcgMTYuMDY2MiA0NC45MDk3IDE2LjA4NzRaTTQ5LjAxOSAyMy4xMjdDNDguNTExMiAyMy4xMjcgNDguMDUyMSAyMy4wNDQ0IDQ3LjY0MTYgMjIuODc5NEM0Ny4yMzU0IDIyLjcxMDEgNDYuODg4MyAyMi40NzUzIDQ2LjYwMDYgMjIuMTc0OEM0Ni4zMTcxIDIxLjg3NDMgNDYuMDk5MSAyMS41MjEgNDUuOTQ2OCAyMS4xMTQ3QzQ1Ljc5NDQgMjAuNzA4NSA0NS43MTgzIDIwLjI3MDUgNDUuNzE4MyAxOS44MDA4VjE5LjU0NjlDNDUuNzE4MyAxOS4wMDk0IDQ1Ljc5NjUgMTguNTIyOCA0NS45NTMxIDE4LjA4NjlDNDYuMTA5NyAxNy42NTEgNDYuMzI3NiAxNy4yNzg2IDQ2LjYwNjkgMTYuOTY5N0M0Ni44ODYyIDE2LjY1NjYgNDcuMjE2MyAxNi40MTc1IDQ3LjU5NzIgMTYuMjUyNEM0Ny45NzggMTYuMDg3NCA0OC4zOTA2IDE2LjAwNDkgNDguODM1IDE2LjAwNDlDNDkuMzI1OCAxNi4wMDQ5IDQ5Ljc1NTQgMTYuMDg3NCA1MC4xMjM1IDE2LjI1MjRDNTAuNDkxNyAxNi40MTc1IDUwLjc5NjQgMTYuNjUwMiA1MS4wMzc2IDE2Ljk1MDdDNTEuMjgzIDE3LjI0NjkgNTEuNDY1IDE3LjYwMDMgNTEuNTgzNSAxOC4wMTA3QzUxLjcwNjIgMTguNDIxMiA1MS43Njc2IDE4Ljg3NCA1MS43Njc2IDE5LjM2OTFWMjAuMDIyOUg0Ni40NjA5VjE4LjkyNDhINTAuMjU2OFYxOC44MDQyQzUwLjI0ODQgMTguNTI5MSA1MC4xOTM0IDE4LjI3MSA1MC4wOTE4IDE4LjAyOThDNDkuOTk0NSAxNy43ODg2IDQ5Ljg0NDIgMTcuNTkzOSA0OS42NDExIDE3LjQ0NThDNDkuNDM4IDE3LjI5NzcgNDkuMTY3MiAxNy4yMjM2IDQ4LjgyODYgMTcuMjIzNkM0OC41NzQ3IDE3LjIyMzYgNDguMzQ4MyAxNy4yNzg2IDQ4LjE0OTQgMTcuMzg4N0M0Ny45NTQ4IDE3LjQ5NDUgNDcuNzkxOCAxNy42NDg5IDQ3LjY2MDYgMTcuODUyMUM0Ny41Mjk1IDE4LjA1NTIgNDcuNDI3OSAxOC4zMDA2IDQ3LjM1NiAxOC41ODg0QzQ3LjI4ODIgMTguODcxOSA0Ny4yNTQ0IDE5LjE5MTQgNDcuMjU0NCAxOS41NDY5VjE5LjgwMDhDNDcuMjU0NCAyMC4xMDEyIDQ3LjI5NDYgMjAuMzgwNSA0Ny4zNzUgMjAuNjM4N0M0Ny40NTk2IDIwLjg5MjYgNDcuNTgyNCAyMS4xMTQ3IDQ3Ljc0MzIgMjEuMzA1MkM0Ny45MDQgMjEuNDk1NiA0OC4wOTg2IDIxLjY0NTggNDguMzI3MSAyMS43NTU5QzQ4LjU1NTcgMjEuODYxNyA0OC44MTU5IDIxLjkxNDYgNDkuMTA3OSAyMS45MTQ2QzQ5LjQ3NjEgMjEuOTE0NiA0OS44MDQgMjEuODQwNSA1MC4wOTE4IDIxLjY5MjRDNTAuMzc5NiAyMS41NDQzIDUwLjYyOTIgMjEuMzM0OCA1MC44NDA4IDIxLjA2NEw1MS42NDcgMjEuODQ0N0M1MS40OTg5IDIyLjA2MDUgNTEuMzA2MyAyMi4yNjc5IDUxLjA2OTMgMjIuNDY2OEM1MC44MzI0IDIyLjY2MTUgNTAuNTQyNSAyMi44MjAxIDUwLjE5OTcgMjIuOTQyOUM0OS44NjEyIDIzLjA2NTYgNDkuNDY3NiAyMy4xMjcgNDkuMDE5IDIzLjEyN1pNNTcuMDY0IDIxLjE0MDFDNTcuMDY0IDIwLjk4NzggNTcuMDI1OSAyMC44NTAzIDU2Ljk0OTcgMjAuNzI3NUM1Ni44NzM1IDIwLjYwMDYgNTYuNzI3NSAyMC40ODYzIDU2LjUxMTcgMjAuMzg0OEM1Ni4zMDAxIDIwLjI4MzIgNTUuOTg3IDIwLjE5MDEgNTUuNTcyMyAyMC4xMDU1QzU1LjIwODMgMjAuMDI1MSA1NC44NzQgMTkuOTI5OSA1NC41NjkzIDE5LjgxOThDNTQuMjY4OSAxOS43MDU2IDU0LjAxMDcgMTkuNTY4IDUzLjc5NDkgMTkuNDA3MkM1My41NzkxIDE5LjI0NjQgNTMuNDExOSAxOS4wNTYgNTMuMjkzNSAxOC44MzU5QzUzLjE3NSAxOC42MTU5IDUzLjExNTcgMTguMzYyIDUzLjExNTcgMTguMDc0MkM1My4xMTU3IDE3Ljc5NDkgNTMuMTc3MSAxNy41MzA0IDUzLjI5OTggMTcuMjgwOEM1My40MjI1IDE3LjAzMTEgNTMuNTk4MSAxNi44MTEgNTMuODI2NyAxNi42MjA2QzU0LjA1NTIgMTYuNDMwMiA1NC4zMzI0IDE2LjI3OTkgNTQuNjU4MiAxNi4xNjk5QzU0Ljk4ODMgMTYuMDU5OSA1NS4zNTY0IDE2LjAwNDkgNTUuNzYyNyAxNi4wMDQ5QzU2LjMzODIgMTYuMDA0OSA1Ni44MzEyIDE2LjEwMjIgNTcuMjQxNyAxNi4yOTY5QzU3LjY1NjQgMTYuNDg3MyA1Ny45NzM4IDE2Ljc0NzYgNTguMTkzOCAxNy4wNzc2QzU4LjQxMzkgMTcuNDAzNSA1OC41MjM5IDE3Ljc3MTYgNTguNTIzOSAxOC4xODIxSDU2Ljk5NDFDNTYuOTk0MSAxOC4wMDAyIDU2Ljk0NzYgMTcuODMwOSA1Ni44NTQ1IDE3LjY3NDNDNTYuNzY1NiAxNy41MTM1IDU2LjYzMDIgMTcuMzg0NCA1Ni40NDgyIDE3LjI4NzFDNTYuMjY2MyAxNy4xODU1IDU2LjAzNzggMTcuMTM0OCA1NS43NjI3IDE3LjEzNDhDNTUuNTAwMyAxNy4xMzQ4IDU1LjI4MjQgMTcuMTc3MSA1NS4xMDg5IDE3LjI2MTdDNTQuOTM5NiAxNy4zNDIxIDU0LjgxMjcgMTcuNDQ3OSA1NC43MjggMTcuNTc5MUM1NC42NDc2IDE3LjcxMDMgNTQuNjA3NCAxNy44NTQyIDU0LjYwNzQgMTguMDEwN0M1NC42MDc0IDE4LjEyNSA1NC42Mjg2IDE4LjIyODcgNTQuNjcwOSAxOC4zMjE4QzU0LjcxNzQgMTguNDEwNiA1NC43OTM2IDE4LjQ5MzIgNTQuODk5NCAxOC41NjkzQzU1LjAwNTIgMTguNjQxMyA1NS4xNDkxIDE4LjcwOSA1NS4zMzExIDE4Ljc3MjVDNTUuNTE3MyAxOC44MzU5IDU1Ljc1IDE4Ljg5NzMgNTYuMDI5MyAxOC45NTY1QzU2LjU1NCAxOS4wNjY2IDU3LjAwNDcgMTkuMjA4MyA1Ny4zODEzIDE5LjM4MThDNTcuNzYyMiAxOS41NTExIDU4LjA1NDIgMTkuNzcxMiA1OC4yNTczIDIwLjA0MkM1OC40NjA0IDIwLjMwODYgNTguNTYyIDIwLjY0NzEgNTguNTYyIDIxLjA1NzZDNTguNTYyIDIxLjM2MjMgNTguNDk2NCAyMS42NDE2IDU4LjM2NTIgMjEuODk1NUM1OC4yMzgzIDIyLjE0NTIgNTguMDUyMSAyMi4zNjMxIDU3LjgwNjYgMjIuNTQ5M0M1Ny41NjEyIDIyLjczMTMgNTcuMjY3MSAyMi44NzMgNTYuOTI0MyAyMi45NzQ2QzU2LjU4NTggMjMuMDc2MiA1Ni4yMDQ5IDIzLjEyNyA1NS43ODE3IDIzLjEyN0M1NS4xNTk3IDIzLjEyNyA1NC42MzI4IDIzLjAxNjkgNTQuMjAxMiAyMi43OTY5QzUzLjc2OTUgMjIuNTcyNiA1My40NDE2IDIyLjI4NjkgNTMuMjE3MyAyMS45Mzk5QzUyLjk5NzIgMjEuNTg4NyA1Mi44ODcyIDIxLjIyNDggNTIuODg3MiAyMC44NDgxSDU0LjM2NjJDNTQuMzgzMSAyMS4xMzE3IDU0LjQ2MTQgMjEuMzU4MSA1NC42MDExIDIxLjUyNzNDNTQuNzQ1IDIxLjY5MjQgNTQuOTIyNyAyMS44MTMgNTUuMTM0MyAyMS44ODkyQzU1LjM1MDEgMjEuOTYxMSA1NS41NzIzIDIxLjk5NzEgNTUuODAwOCAyMS45OTcxQzU2LjA3NTggMjEuOTk3MSA1Ni4zMDY1IDIxLjk2MTEgNTYuNDkyNyAyMS44ODkyQzU2LjY3ODkgMjEuODEzIDU2LjgyMDYgMjEuNzExNCA1Ni45MTggMjEuNTg0NUM1Ny4wMTUzIDIxLjQ1MzMgNTcuMDY0IDIxLjMwNTIgNTcuMDY0IDIxLjE0MDFaTTY0LjAwNDQgMjEuMTQwMUM2NC4wMDQ0IDIwLjk4NzggNjMuOTY2MyAyMC44NTAzIDYzLjg5MDEgMjAuNzI3NUM2My44MTQgMjAuNjAwNiA2My42NjggMjAuNDg2MyA2My40NTIxIDIwLjM4NDhDNjMuMjQwNiAyMC4yODMyIDYyLjkyNzQgMjAuMTkwMSA2Mi41MTI3IDIwLjEwNTVDNjIuMTQ4OCAyMC4wMjUxIDYxLjgxNDUgMTkuOTI5OSA2MS41MDk4IDE5LjgxOThDNjEuMjA5MyAxOS43MDU2IDYwLjk1MTIgMTkuNTY4IDYwLjczNTQgMTkuNDA3MkM2MC41MTk1IDE5LjI0NjQgNjAuMzUyNCAxOS4wNTYgNjAuMjMzOSAxOC44MzU5QzYwLjExNTQgMTguNjE1OSA2MC4wNTYyIDE4LjM2MiA2MC4wNTYyIDE4LjA3NDJDNjAuMDU2MiAxNy43OTQ5IDYwLjExNzUgMTcuNTMwNCA2MC4yNDAyIDE3LjI4MDhDNjAuMzYzIDE3LjAzMTEgNjAuNTM4NiAxNi44MTEgNjAuNzY3MSAxNi42MjA2QzYwLjk5NTYgMTYuNDMwMiA2MS4yNzI4IDE2LjI3OTkgNjEuNTk4NiAxNi4xNjk5QzYxLjkyODcgMTYuMDU5OSA2Mi4yOTY5IDE2LjAwNDkgNjIuNzAzMSAxNi4wMDQ5QzYzLjI3ODYgMTYuMDA0OSA2My43NzE2IDE2LjEwMjIgNjQuMTgyMSAxNi4yOTY5QzY0LjU5NjggMTYuNDg3MyA2NC45MTQyIDE2Ljc0NzYgNjUuMTM0MyAxNy4wNzc2QzY1LjM1NDMgMTcuNDAzNSA2NS40NjQ0IDE3Ljc3MTYgNjUuNDY0NCAxOC4xODIxSDYzLjkzNDZDNjMuOTM0NiAxOC4wMDAyIDYzLjg4OCAxNy44MzA5IDYzLjc5NDkgMTcuNjc0M0M2My43MDYxIDE3LjUxMzUgNjMuNTcwNiAxNy4zODQ0IDYzLjM4ODcgMTcuMjg3MUM2My4yMDY3IDE3LjE4NTUgNjIuOTc4MiAxNy4xMzQ4IDYyLjcwMzEgMTcuMTM0OEM2Mi40NDA4IDE3LjEzNDggNjIuMjIyOCAxNy4xNzcxIDYyLjA0OTMgMTcuMjYxN0M2MS44OCAxNy4zNDIxIDYxLjc1MzEgMTcuNDQ3OSA2MS42Njg1IDE3LjU3OTFDNjEuNTg4MSAxNy43MTAzIDYxLjU0NzkgMTcuODU0MiA2MS41NDc5IDE4LjAxMDdDNjEuNTQ3OSAxOC4xMjUgNjEuNTY5IDE4LjIyODcgNjEuNjExMyAxOC4zMjE4QzYxLjY1NzkgMTguNDEwNiA2MS43MzQgMTguNDkzMiA2MS44Mzk4IDE4LjU2OTNDNjEuOTQ1NiAxOC42NDEzIDYyLjA4OTUgMTguNzA5IDYyLjI3MTUgMTguNzcyNUM2Mi40NTc3IDE4LjgzNTkgNjIuNjkwNCAxOC44OTczIDYyLjk2OTcgMTguOTU2NUM2My40OTQ1IDE5LjA2NjYgNjMuOTQ1MSAxOS4yMDgzIDY0LjMyMTggMTkuMzgxOEM2NC43MDI2IDE5LjU1MTEgNjQuOTk0NiAxOS43NzEyIDY1LjE5NzggMjAuMDQyQzY1LjQwMDkgMjAuMzA4NiA2NS41MDI0IDIwLjY0NzEgNjUuNTAyNCAyMS4wNTc2QzY1LjUwMjQgMjEuMzYyMyA2NS40MzY4IDIxLjY0MTYgNjUuMzA1NyAyMS44OTU1QzY1LjE3ODcgMjIuMTQ1MiA2NC45OTI1IDIyLjM2MzEgNjQuNzQ3MSAyMi41NDkzQzY0LjUwMTYgMjIuNzMxMyA2NC4yMDc1IDIyLjg3MyA2My44NjQ3IDIyLjk3NDZDNjMuNTI2MiAyMy4wNzYyIDYzLjE0NTMgMjMuMTI3IDYyLjcyMjIgMjMuMTI3QzYyLjEwMDEgMjMuMTI3IDYxLjU3MzIgMjMuMDE2OSA2MS4xNDE2IDIyLjc5NjlDNjAuNzEgMjIuNTcyNiA2MC4zODIgMjIuMjg2OSA2MC4xNTc3IDIxLjkzOTlDNTkuOTM3NyAyMS41ODg3IDU5LjgyNzYgMjEuMjI0OCA1OS44Mjc2IDIwLjg0ODFINjEuMzA2NkM2MS4zMjM2IDIxLjEzMTcgNjEuNDAxOSAyMS4zNTgxIDYxLjU0MTUgMjEuNTI3M0M2MS42ODU0IDIxLjY5MjQgNjEuODYzMSAyMS44MTMgNjIuMDc0NyAyMS44ODkyQzYyLjI5MDUgMjEuOTYxMSA2Mi41MTI3IDIxLjk5NzEgNjIuNzQxMiAyMS45OTcxQzYzLjAxNjMgMjEuOTk3MSA2My4yNDY5IDIxLjk2MTEgNjMuNDMzMSAyMS44ODkyQzYzLjYxOTMgMjEuODEzIDYzLjc2MTEgMjEuNzExNCA2My44NTg0IDIxLjU4NDVDNjMuOTU1NyAyMS40NTMzIDY0LjAwNDQgMjEuMzA1MiA2NC4wMDQ0IDIxLjE0MDFaTTcwLjU4NTQgMTMuMjVINzIuMTE1MlYyMS41MzM3TDcxLjk2OTIgMjNINzAuNTg1NFYxMy4yNVpNNzYuNTk2NyAxOS41MDI0VjE5LjYzNTdDNzYuNTk2NyAyMC4xNDM2IDc2LjUzOTYgMjAuNjExMiA3Ni40MjUzIDIxLjAzODZDNzYuMzE1MyAyMS40NjE4IDc2LjE0NiAyMS44Mjk5IDc1LjkxNzUgMjIuMTQzMUM3NS42OTMyIDIyLjQ1NjIgNzUuNDEzOSAyMi42OTk1IDc1LjA3OTYgMjIuODczQzc0Ljc0OTUgMjMuMDQyMyA3NC4zNjY1IDIzLjEyNyA3My45MzA3IDIzLjEyN0M3My41MDMzIDIzLjEyNyA3My4xMzA5IDIzLjA0NjUgNzIuODEzNSAyMi44ODU3QzcyLjQ5NjEgMjIuNzI0OSA3Mi4yMjk1IDIyLjQ5NjQgNzIuMDEzNyAyMi4yMDAyQzcxLjgwMjEgMjEuOTA0IDcxLjYzMDcgMjEuNTUwNiA3MS40OTk1IDIxLjE0MDFDNzEuMzY4MyAyMC43Mjk3IDcxLjI3NTIgMjAuMjc2OSA3MS4yMjAyIDE5Ljc4MTdWMTkuMzU2NEM3MS4yNzUyIDE4Ljg1NzEgNzEuMzY4MyAxOC40MDQzIDcxLjQ5OTUgMTcuOTk4QzcxLjYzMDcgMTcuNTg3NiA3MS44MDIxIDE3LjIzNDIgNzIuMDEzNyAxNi45MzhDNzIuMjI5NSAxNi42Mzc1IDcyLjQ5NCAxNi40MDY5IDcyLjgwNzEgMTYuMjQ2MUM3My4xMjQ1IDE2LjA4NTMgNzMuNDk0OCAxNi4wMDQ5IDczLjkxOCAxNi4wMDQ5Qzc0LjM1ODEgMTYuMDA0OSA3NC43NDUzIDE2LjA4OTUgNzUuMDc5NiAxNi4yNTg4Qzc1LjQxODEgMTYuNDI4MSA3NS42OTk1IDE2LjY2OTMgNzUuOTIzOCAxNi45ODI0Qzc2LjE0ODEgMTcuMjkxMyA3Ni4zMTUzIDE3LjY1OTUgNzYuNDI1MyAxOC4wODY5Qzc2LjUzOTYgMTguNTE0MyA3Ni41OTY3IDE4Ljk4NjIgNzYuNTk2NyAxOS41MDI0Wk03NS4wNjY5IDE5LjYzNTdWMTkuNTAyNEM3NS4wNjY5IDE5LjE5MzUgNzUuMDQxNSAxOC45MDM2IDc0Ljk5MDcgMTguNjMyOEM3NC45Mzk5IDE4LjM1NzcgNzQuODU1MyAxOC4xMTY1IDc0LjczNjggMTcuOTA5MkM3NC42MjI2IDE3LjcwMTggNzQuNDY2IDE3LjUzODkgNzQuMjY3MSAxNy40MjA0Qzc0LjA3MjQgMTcuMjk3NyA3My44MjkxIDE3LjIzNjMgNzMuNTM3MSAxNy4yMzYzQzczLjI2NjMgMTcuMjM2MyA3My4wMzM1IDE3LjI4MjkgNzIuODM4OSAxNy4zNzZDNzIuNjQ0MiAxNy40NjkxIDcyLjQ4MTMgMTcuNTk2IDcyLjM1MDEgMTcuNzU2OEM3Mi4yMTg5IDE3LjkxNzYgNzIuMTE1MiAxOC4xMDM4IDcyLjAzOTEgMTguMzE1NEM3MS45NjcxIDE4LjUyNyA3MS45MTg1IDE4Ljc1NTUgNzEuODkzMSAxOS4wMDFWMjAuMTQ5OUM3MS45MzEyIDIwLjQ2NzMgNzIuMDExNiAyMC43NTkzIDcyLjEzNDMgMjEuMDI1OUM3Mi4yNjEyIDIxLjI4ODIgNzIuNDM5IDIxLjQ5OTggNzIuNjY3NSAyMS42NjA2QzcyLjg5NiAyMS44MTcyIDczLjE5MDEgMjEuODk1NSA3My41NDk4IDIxLjg5NTVDNzMuODMzMyAyMS44OTU1IDc0LjA3MjQgMjEuODM4NCA3NC4yNjcxIDIxLjcyNDFDNzQuNDYxOCAyMS42MDk5IDc0LjYxNjIgMjEuNDUxMiA3NC43MzA1IDIxLjI0OEM3NC44NDkgMjEuMDQwNyA3NC45MzM2IDIwLjc5OTUgNzQuOTg0NCAyMC41MjQ0Qzc1LjAzOTQgMjAuMjQ5MyA3NS4wNjY5IDE5Ljk1MzEgNzUuMDY2OSAxOS42MzU3Wk04Mi4wMTM3IDIxLjYyMjZWMTguMzQ3MkM4Mi4wMTM3IDE4LjEwMTcgODEuOTY5MiAxNy44OTAxIDgxLjg4MDQgMTcuNzEyNEM4MS43OTE1IDE3LjUzNDcgODEuNjU2MSAxNy4zOTcxIDgxLjQ3NDEgMTcuMjk5OEM4MS4yOTY0IDE3LjIwMjUgODEuMDcyMSAxNy4xNTM4IDgwLjgwMTMgMTcuMTUzOEM4MC41NTE2IDE3LjE1MzggODAuMzM1OCAxNy4xOTYxIDgwLjE1MzggMTcuMjgwOEM3OS45NzE4IDE3LjM2NTQgNzkuODMwMSAxNy40Nzk3IDc5LjcyODUgMTcuNjIzNUM3OS42MjcgMTcuNzY3NCA3OS41NzYyIDE3LjkzMDMgNzkuNTc2MiAxOC4xMTIzSDc4LjA1MjdDNzguMDUyNyAxNy44NDE1IDc4LjExODMgMTcuNTc5MSA3OC4yNDk1IDE3LjMyNTJDNzguMzgwNyAxNy4wNzEzIDc4LjU3MTEgMTYuODQ0OSA3OC44MjA4IDE2LjY0NkM3OS4wNzA1IDE2LjQ0NzEgNzkuMzY4OCAxNi4yOTA1IDc5LjcxNTggMTYuMTc2M0M4MC4wNjI4IDE2LjA2MiA4MC40NTIxIDE2LjAwNDkgODAuODgzOCAxNi4wMDQ5QzgxLjQwMDEgMTYuMDA0OSA4MS44NTcxIDE2LjA5MTYgODIuMjU0OSAxNi4yNjUxQzgyLjY1NjkgMTYuNDM4NiA4Mi45NzIyIDE2LjcwMSA4My4yMDA3IDE3LjA1MjJDODMuNDMzNCAxNy4zOTkzIDgzLjU0OTggMTcuODM1MSA4My41NDk4IDE4LjM1OTlWMjEuNDEzMUM4My41NDk4IDIxLjcyNjIgODMuNTcxIDIyLjAwNzYgODMuNjEzMyAyMi4yNTczQzgzLjY1OTggMjIuNTAyOCA4My43MjU0IDIyLjcxNjUgODMuODEwMSAyMi44OTg0VjIzSDgyLjI0MjJDODIuMTcwMiAyMi44MzUgODIuMTEzMSAyMi42MjU1IDgyLjA3MDggMjIuMzcxNkM4Mi4wMzI3IDIyLjExMzQgODIuMDEzNyAyMS44NjM4IDgyLjAxMzcgMjEuNjIyNlpNODIuMjM1OCAxOC44MjMyTDgyLjI0ODUgMTkuNzY5SDgxLjE1MDRDODAuODY2OSAxOS43NjkgODAuNjE3MiAxOS43OTY1IDgwLjQwMTQgMTkuODUxNkM4MC4xODU1IDE5LjkwMjMgODAuMDA1NyAxOS45Nzg1IDc5Ljg2MTggMjAuMDgwMUM3OS43MTc5IDIwLjE4MTYgNzkuNjEgMjAuMzA0NCA3OS41MzgxIDIwLjQ0ODJDNzkuNDY2MSAyMC41OTIxIDc5LjQzMDIgMjAuNzU1IDc5LjQzMDIgMjAuOTM3Qzc5LjQzMDIgMjEuMTE5IDc5LjQ3MjUgMjEuMjg2MSA3OS41NTcxIDIxLjQzODVDNzkuNjQxOCAyMS41ODY2IDc5Ljc2NDUgMjEuNzAzIDc5LjkyNTMgMjEuNzg3NkM4MC4wOTAzIDIxLjg3MjIgODAuMjg5MiAyMS45MTQ2IDgwLjUyMiAyMS45MTQ2QzgwLjgzNTEgMjEuOTE0NiA4MS4xMDgxIDIxLjg1MTEgODEuMzQwOCAyMS43MjQxQzgxLjU3NzggMjEuNTkyOSA4MS43NjQgMjEuNDM0MiA4MS44OTk0IDIxLjI0OEM4Mi4wMzQ4IDIxLjA1NzYgODIuMTA2OCAyMC44Nzc4IDgyLjExNTIgMjAuNzA4NUw4Mi42MTA0IDIxLjM4NzdDODIuNTU5NiAyMS41NjEyIDgyLjQ3MjggMjEuNzQ3NCA4Mi4zNTAxIDIxLjk0NjNDODIuMjI3NCAyMi4xNDUyIDgyLjA2NjYgMjIuMzM1NiA4MS44Njc3IDIyLjUxNzZDODEuNjczIDIyLjY5NTMgODEuNDM4MiAyMi44NDEzIDgxLjE2MzEgMjIuOTU1NkM4MC44OTIzIDIzLjA2OTggODAuNTc5MSAyMy4xMjcgODAuMjIzNiAyMy4xMjdDNzkuNzc1MSAyMy4xMjcgNzkuMzc1MiAyMy4wMzgxIDc5LjAyMzkgMjIuODYwNEM3OC42NzI3IDIyLjY3ODQgNzguMzk3NiAyMi40MzUxIDc4LjE5ODcgMjIuMTMwNEM3Ny45OTk4IDIxLjgyMTUgNzcuOTAwNCAyMS40NzIzIDc3LjkwMDQgMjEuMDgzQzc3LjkwMDQgMjAuNzE5MSA3Ny45NjgxIDIwLjM5NzUgNzguMTAzNSAyMC4xMTgyQzc4LjI0MzIgMTkuODM0NiA3OC40NDYzIDE5LjU5NzcgNzguNzEyOSAxOS40MDcyQzc4Ljk4MzcgMTkuMjE2OCA3OS4zMTM4IDE5LjA3MjkgNzkuNzAzMSAxOC45NzU2QzgwLjA5MjQgMTguODc0IDgwLjUzNjggMTguODIzMiA4MS4wMzYxIDE4LjgyMzJIODIuMjM1OFpNODYuOTM1NSAxNy40Mzk1VjIzSDg1LjQwNThWMTYuMTMxOEg4Ni44NjU3TDg2LjkzNTUgMTcuNDM5NVpNODkuMDM2NiAxNi4wODc0TDg5LjAyMzkgMTcuNTA5M0M4OC45MzA4IDE3LjQ5MjQgODguODI5MyAxNy40Nzk3IDg4LjcxOTIgMTcuNDcxMkM4OC42MTM0IDE3LjQ2MjcgODguNTA3NiAxNy40NTg1IDg4LjQwMTkgMTcuNDU4NUM4OC4xMzk1IDE3LjQ1ODUgODcuOTA4OSAxNy40OTY2IDg3LjcxIDE3LjU3MjhDODcuNTExMSAxNy42NDQ3IDg3LjM0MzkgMTcuNzUwNSA4Ny4yMDg1IDE3Ljg5MDFDODcuMDc3MyAxOC4wMjU2IDg2Ljk3NTcgMTguMTkwNiA4Ni45MDM4IDE4LjM4NTNDODYuODMxOSAxOC41Nzk5IDg2Ljc4OTYgMTguNzk3OSA4Ni43NzY5IDE5LjAzOTFMODYuNDI3NyAxOS4wNjQ1Qzg2LjQyNzcgMTguNjMyOCA4Ni40NzAxIDE4LjIzMjkgODYuNTU0NyAxNy44NjQ3Qzg2LjYzOTMgMTcuNDk2NiA4Ni43NjYzIDE3LjE3MjkgODYuOTM1NSAxNi44OTM2Qzg3LjEwOSAxNi42MTQzIDg3LjMyNDkgMTYuMzk2MyA4Ny41ODMgMTYuMjM5N0M4Ny44NDU0IDE2LjA4MzIgODguMTQ3OSAxNi4wMDQ5IDg4LjQ5MDcgMTYuMDA0OUM4OC41ODM4IDE2LjAwNDkgODguNjgzMyAxNi4wMTMzIDg4Ljc4OTEgMTYuMDMwM0M4OC44OTkxIDE2LjA0NzIgODguOTgxNiAxNi4wNjYyIDg5LjAzNjYgMTYuMDg3NFoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPHBhdGggZD0iTTE2LjYwNTUgOTcuMTkxNEgxOC4yOTNDMTguOTQ5MiA5Ny4xOTE0IDE5LjQ5MjIgOTcuMDc4MSAxOS45MjE5IDk2Ljg1MTZDMjAuMzU5NCA5Ni42MjUgMjAuNjgzNiA5Ni4zMTI1IDIwLjg5NDUgOTUuOTE0MUMyMS4xMDU1IDk1LjUxNTYgMjEuMjEwOSA5NS4wNTg2IDIxLjIxMDkgOTQuNTQzQzIxLjIxMDkgOTQuMDAzOSAyMS4xMTMzIDkzLjU0MyAyMC45MTggOTMuMTYwMkMyMC43MzA1IDkyLjc2OTUgMjAuNDQxNCA5Mi40Njg4IDIwLjA1MDggOTIuMjU3OEMxOS42NjggOTIuMDQ2OSAxOS4xNzk3IDkxLjk0MTQgMTguNTg1OSA5MS45NDE0QzE4LjA4NTkgOTEuOTQxNCAxNy42MzI4IDkyLjA0MyAxNy4yMjY2IDkyLjI0NjFDMTYuODI4MSA5Mi40NDE0IDE2LjUxMTcgOTIuNzIyNyAxNi4yNzczIDkzLjA4OThDMTYuMDQzIDkzLjQ0OTIgMTUuOTI1OCA5My44Nzg5IDE1LjkyNTggOTQuMzc4OUgxMy4wODk4QzEzLjA4OTggOTMuNDcyNyAxMy4zMjgxIDkyLjY2OCAxMy44MDQ3IDkxLjk2NDhDMTQuMjgxMiA5MS4yNjE3IDE0LjkyOTcgOTAuNzEwOSAxNS43NSA5MC4zMTI1QzE2LjU3ODEgODkuOTA2MiAxNy41MDc4IDg5LjcwMzEgMTguNTM5MSA4OS43MDMxQzE5LjY0MDYgODkuNzAzMSAyMC42MDE2IDg5Ljg4NjcgMjEuNDIxOSA5MC4yNTM5QzIyLjI1IDkwLjYxMzMgMjIuODk0NSA5MS4xNTIzIDIzLjM1NTUgOTEuODcxMUMyMy44MTY0IDkyLjU4OTggMjQuMDQ2OSA5My40ODA1IDI0LjA0NjkgOTQuNTQzQzI0LjA0NjkgOTUuMDI3MyAyMy45MzM2IDk1LjUxOTUgMjMuNzA3IDk2LjAxOTVDMjMuNDgwNSA5Ni41MTk1IDIzLjE0NDUgOTYuOTc2NiAyMi42OTkyIDk3LjM5MDZDMjIuMjUzOSA5Ny43OTY5IDIxLjY5OTIgOTguMTI4OSAyMS4wMzUyIDk4LjM4NjdDMjAuMzcxMSA5OC42MzY3IDE5LjYwMTYgOTguNzYxNyAxOC43MjY2IDk4Ljc2MTdIMTYuNjA1NVY5Ny4xOTE0Wk0xNi42MDU1IDk5LjM5NDVWOTcuODQ3N0gxOC43MjY2QzE5LjcyNjYgOTcuODQ3NyAyMC41NzgxIDk3Ljk2NDggMjEuMjgxMiA5OC4xOTkyQzIxLjk5MjIgOTguNDMzNiAyMi41NzAzIDk4Ljc1NzggMjMuMDE1NiA5OS4xNzE5QzIzLjQ2MDkgOTkuNTc4MSAyMy43ODUyIDEwMC4wNDMgMjMuOTg4MyAxMDAuNTY2QzI0LjE5OTIgMTAxLjA5IDI0LjMwNDcgMTAxLjY0NSAyNC4zMDQ3IDEwMi4yM0MyNC4zMDQ3IDEwMy4wMjcgMjQuMTYwMiAxMDMuNzM4IDIzLjg3MTEgMTA0LjM2M0MyMy41ODk4IDEwNC45OCAyMy4xODc1IDEwNS41MDQgMjIuNjY0MSAxMDUuOTM0QzIyLjE0MDYgMTA2LjM2MyAyMS41MjczIDEwNi42ODggMjAuODI0MiAxMDYuOTA2QzIwLjEyODkgMTA3LjEyNSAxOS4zNzExIDEwNy4yMzQgMTguNTUwOCAxMDcuMjM0QzE3LjgxNjQgMTA3LjIzNCAxNy4xMTMzIDEwNy4xMzMgMTYuNDQxNCAxMDYuOTNDMTUuNzY5NSAxMDYuNzI3IDE1LjE2OCAxMDYuNDI2IDE0LjYzNjcgMTA2LjAyN0MxNC4xMDU1IDEwNS42MjEgMTMuNjgzNiAxMDUuMTE3IDEzLjM3MTEgMTA0LjUxNkMxMy4wNjY0IDEwMy45MDYgMTIuOTE0MSAxMDMuMjAzIDEyLjkxNDEgMTAyLjQwNkgxNS43MzgzQzE1LjczODMgMTAyLjkxNCAxNS44NTU1IDEwMy4zNjMgMTYuMDg5OCAxMDMuNzU0QzE2LjMzMiAxMDQuMTM3IDE2LjY2OCAxMDQuNDM4IDE3LjA5NzcgMTA0LjY1NkMxNy41MzUyIDEwNC44NzUgMTguMDM1MiAxMDQuOTg0IDE4LjU5NzcgMTA0Ljk4NEMxOS4xOTE0IDEwNC45ODQgMTkuNzAzMSAxMDQuODc5IDIwLjEzMjggMTA0LjY2OEMyMC41NjI1IDEwNC40NTcgMjAuODkwNiAxMDQuMTQ1IDIxLjExNzIgMTAzLjczQzIxLjM1MTYgMTAzLjMxNiAyMS40Njg4IDEwMi44MTYgMjEuNDY4OCAxMDIuMjNDMjEuNDY4OCAxMDEuNTY2IDIxLjMzOTggMTAxLjAyNyAyMS4wODIgMTAwLjYxM0MyMC44MjQyIDEwMC4xOTkgMjAuNDU3IDk5Ljg5NDUgMTkuOTgwNSA5OS42OTkyQzE5LjUwMzkgOTkuNDk2MSAxOC45NDE0IDk5LjM5NDUgMTguMjkzIDk5LjM5NDVIMTYuNjA1NVpNMzUuNDYwOSA4OS44MzJIMzUuODEyNVY5Mi4xNDA2SDM1LjYxMzNDMzQuNjA1NSA5Mi4xNDA2IDMzLjczNDQgOTIuMjk2OSAzMyA5Mi42MDk0QzMyLjI3MzQgOTIuOTIxOSAzMS42NzU4IDkzLjM1MTYgMzEuMjA3IDkzLjg5ODRDMzAuNzM4MyA5NC40NDUzIDMwLjM4NjcgOTUuMDg1OSAzMC4xNTIzIDk1LjgyMDNDMjkuOTI1OCA5Ni41NDY5IDI5LjgxMjUgOTcuMzIwMyAyOS44MTI1IDk4LjE0MDZWMTAwLjgyNEMyOS44MTI1IDEwMS41MDQgMjkuODg2NyAxMDIuMTA1IDMwLjAzNTIgMTAyLjYyOUMzMC4xODM2IDEwMy4xNDUgMzAuMzkwNiAxMDMuNTc4IDMwLjY1NjIgMTAzLjkzQzMwLjkyOTcgMTA0LjI3MyAzMS4yNDIyIDEwNC41MzUgMzEuNTkzOCAxMDQuNzE1QzMxLjk0NTMgMTA0Ljg5NSAzMi4zMjQyIDEwNC45ODQgMzIuNzMwNSAxMDQuOTg0QzMzLjE1MjMgMTA0Ljk4NCAzMy41MzUyIDEwNC44OTggMzMuODc4OSAxMDQuNzI3QzM0LjIyMjcgMTA0LjU0NyAzNC41MTU2IDEwNC4zMDEgMzQuNzU3OCAxMDMuOTg4QzM1IDEwMy42NzYgMzUuMTgzNiAxMDMuMzA1IDM1LjMwODYgMTAyLjg3NUMzNS40MzM2IDEwMi40NDUgMzUuNDk2MSAxMDEuOTc3IDM1LjQ5NjEgMTAxLjQ2OUMzNS40OTYxIDEwMC45ODQgMzUuNDMzNiAxMDAuNTMxIDM1LjMwODYgMTAwLjEwOUMzNS4xOTE0IDk5LjY3OTcgMzUuMDE1NiA5OS4zMDQ3IDM0Ljc4MTIgOTguOTg0NEMzNC41NDY5IDk4LjY1NjIgMzQuMjUzOSA5OC40MDIzIDMzLjkwMjMgOTguMjIyN0MzMy41NTg2IDk4LjAzNTIgMzMuMTYwMiA5Ny45NDE0IDMyLjcwNyA5Ny45NDE0QzMyLjE0NDUgOTcuOTQxNCAzMS42MzI4IDk4LjA3NDIgMzEuMTcxOSA5OC4zMzk4QzMwLjcxODggOTguNjA1NSAzMC4zNTE2IDk4Ljk1MzEgMzAuMDcwMyA5OS4zODI4QzI5Ljc5NjkgOTkuODA0NyAyOS42NDg0IDEwMC4yNTQgMjkuNjI1IDEwMC43M0wyOC41NDY5IDEwMC4zNzlDMjguNjA5NCA5OS42NTIzIDI4Ljc2OTUgOTkgMjkuMDI3MyA5OC40MjE5QzI5LjI5MyA5Ny44NDM4IDI5LjY0MDYgOTcuMzUxNiAzMC4wNzAzIDk2Ljk0NTNDMzAuNSA5Ni41MzkxIDMwLjk5NjEgOTYuMjMwNSAzMS41NTg2IDk2LjAxOTVDMzIuMTI4OSA5NS44MDA4IDMyLjc1MzkgOTUuNjkxNCAzMy40MzM2IDk1LjY5MTRDMzQuMjYxNyA5NS42OTE0IDM0Ljk4MDUgOTUuODQ3NyAzNS41ODk4IDk2LjE2MDJDMzYuMTk5MiA5Ni40NzI3IDM2LjcwMzEgOTYuODk4NCAzNy4xMDE2IDk3LjQzNzVDMzcuNTA3OCA5Ny45Njg4IDM3LjgwODYgOTguNTc4MSAzOC4wMDM5IDk5LjI2NTZDMzguMjA3IDk5Ljk0NTMgMzguMzA4NiAxMDAuNjU2IDM4LjMwODYgMTAxLjM5OEMzOC4zMDg2IDEwMi4yMTkgMzguMTgzNiAxMDIuOTg0IDM3LjkzMzYgMTAzLjY5NUMzNy42ODM2IDEwNC4zOTggMzcuMzE2NCAxMDUuMDE2IDM2LjgzMiAxMDUuNTQ3QzM2LjM1NTUgMTA2LjA3OCAzNS43NzM0IDEwNi40OTIgMzUuMDg1OSAxMDYuNzg5QzM0LjQwNjIgMTA3LjA4NiAzMy42MzI4IDEwNy4yMzQgMzIuNzY1NiAxMDcuMjM0QzMxLjg1MTYgMTA3LjIzNCAzMS4wMzUyIDEwNy4wNTkgMzAuMzE2NCAxMDYuNzA3QzI5LjYwNTUgMTA2LjM1NSAyOSAxMDUuODcxIDI4LjUgMTA1LjI1NEMyOC4wMDc4IDEwNC42MzcgMjcuNjMyOCAxMDMuOTI2IDI3LjM3NSAxMDMuMTIxQzI3LjExNzIgMTAyLjMxNiAyNi45ODgzIDEwMS40NjUgMjYuOTg4MyAxMDAuNTY2Vjk5LjM5NDVDMjYuOTg4MyA5OC4wOTc3IDI3LjE1MjMgOTYuODc1IDI3LjQ4MDUgOTUuNzI2NkMyNy44MDg2IDk0LjU3MDMgMjguMzEyNSA5My41NTA4IDI4Ljk5MjIgOTIuNjY4QzI5LjY3OTcgOTEuNzg1MiAzMC41NTg2IDkxLjA5MzggMzEuNjI4OSA5MC41OTM4QzMyLjY5OTIgOTAuMDg1OSAzMy45NzY2IDg5LjgzMiAzNS40NjA5IDg5LjgzMloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPHBhdGggZD0iTTQyLjcxNzggOTkuNDg3M1Y5OC45NjA5QzQyLjcxNzggOTguNTgyNyA0Mi43OTk4IDk4LjIzODYgNDIuOTYzOSA5Ny45Mjg3QzQzLjEyNzkgOTcuNjE4OCA0My4zNjI2IDk3LjM3MDQgNDMuNjY4IDk3LjE4MzZDNDMuOTczMyA5Ni45OTY3IDQ0LjMzNTYgOTYuOTAzMyA0NC43NTQ5IDk2LjkwMzNDNDUuMTgzMyA5Ni45MDMzIDQ1LjU0NzkgOTYuOTk2NyA0NS44NDg2IDk3LjE4MzZDNDYuMTU0IDk3LjM3MDQgNDYuMzg4NyA5Ny42MTg4IDQ2LjU1MjcgOTcuOTI4N0M0Ni43MTY4IDk4LjIzODYgNDYuNzk4OCA5OC41ODI3IDQ2Ljc5ODggOTguOTYwOVY5OS40ODczQzQ2Ljc5ODggOTkuODU2NCA0Ni43MTY4IDEwMC4xOTYgNDYuNTUyNyAxMDAuNTA2QzQ2LjM5MzIgMTAwLjgxNiA0Ni4xNjA4IDEwMS4wNjQgNDUuODU1NSAxMDEuMjUxQzQ1LjU1NDcgMTAxLjQzOCA0NS4xOTI0IDEwMS41MzEgNDQuNzY4NiAxMDEuNTMxQzQ0LjM0NDcgMTAxLjUzMSA0My45Nzc5IDEwMS40MzggNDMuNjY4IDEwMS4yNTFDNDMuMzYyNiAxMDEuMDY0IDQzLjEyNzkgMTAwLjgxNiA0Mi45NjM5IDEwMC41MDZDNDIuNzk5OCAxMDAuMTk2IDQyLjcxNzggOTkuODU2NCA0Mi43MTc4IDk5LjQ4NzNaTTQzLjY2OCA5OC45NjA5Vjk5LjQ4NzNDNDMuNjY4IDk5LjY5NjkgNDMuNzA2NyA5OS44OTUyIDQzLjc4NDIgMTAwLjA4MkM0My44NjYyIDEwMC4yNjkgNDMuOTg5MyAxMDAuNDIyIDQ0LjE1MzMgMTAwLjU0QzQ0LjMxNzQgMTAwLjY1NCA0NC41MjI1IDEwMC43MTEgNDQuNzY4NiAxMDAuNzExQzQ1LjAxNDYgMTAwLjcxMSA0NS4yMTc0IDEwMC42NTQgNDUuMzc3IDEwMC41NEM0NS41MzY1IDEwMC40MjIgNDUuNjU0OSAxMDAuMjY5IDQ1LjczMjQgMTAwLjA4MkM0NS44MDk5IDk5Ljg5NTIgNDUuODQ4NiA5OS42OTY5IDQ1Ljg0ODYgOTkuNDg3M1Y5OC45NjA5QzQ1Ljg0ODYgOTguNzQ2NyA0NS44MDc2IDk4LjU0NjIgNDUuNzI1NiA5OC4zNTk0QzQ1LjY0ODEgOTguMTY4IDQ1LjUyNzMgOTguMDE1MyA0NS4zNjMzIDk3LjkwMTRDNDUuMjAzOCA5Ny43ODI5IDQ1LjAwMSA5Ny43MjM2IDQ0Ljc1NDkgOTcuNzIzNkM0NC41MTMzIDk3LjcyMzYgNDQuMzEwNSA5Ny43ODI5IDQ0LjE0NjUgOTcuOTAxNEM0My45ODcgOTguMDE1MyA0My44NjYyIDk4LjE2OCA0My43ODQyIDk4LjM1OTRDNDMuNzA2NyA5OC41NDYyIDQzLjY2OCA5OC43NDY3IDQzLjY2OCA5OC45NjA5Wk00Ny41NjQ1IDEwNS4wOTNWMTA0LjU2QzQ3LjU2NDUgMTA0LjE4NiA0Ny42NDY1IDEwMy44NDQgNDcuODEwNSAxMDMuNTM0QzQ3Ljk3NDYgMTAzLjIyNCA0OC4yMDkzIDEwMi45NzYgNDguNTE0NiAxMDIuNzg5QzQ4LjgyIDEwMi42MDIgNDkuMTgyMyAxMDIuNTA5IDQ5LjYwMTYgMTAyLjUwOUM1MC4wMjk5IDEwMi41MDkgNTAuMzk0NSAxMDIuNjAyIDUwLjY5NTMgMTAyLjc4OUM1MS4wMDA3IDEwMi45NzYgNTEuMjM1NCAxMDMuMjI0IDUxLjM5OTQgMTAzLjUzNEM1MS41NjM1IDEwMy44NDQgNTEuNjQ1NSAxMDQuMTg2IDUxLjY0NTUgMTA0LjU2VjEwNS4wOTNDNTEuNjQ1NSAxMDUuNDY2IDUxLjU2MzUgMTA1LjgwOCA1MS4zOTk0IDEwNi4xMThDNTEuMjM5OSAxMDYuNDI4IDUxLjAwNzUgMTA2LjY3NiA1MC43MDIxIDEwNi44NjNDNTAuNDAxNCAxMDcuMDUgNTAuMDM5MSAxMDcuMTQ0IDQ5LjYxNTIgMTA3LjE0NEM0OS4xOTE0IDEwNy4xNDQgNDguODI2OCAxMDcuMDUgNDguNTIxNSAxMDYuODYzQzQ4LjIxNjEgMTA2LjY3NiA0Ny45NzkyIDEwNi40MjggNDcuODEwNSAxMDYuMTE4QzQ3LjY0NjUgMTA1LjgwOCA0Ny41NjQ1IDEwNS40NjYgNDcuNTY0NSAxMDUuMDkzWk00OC41MTQ2IDEwNC41NlYxMDUuMDkzQzQ4LjUxNDYgMTA1LjMwMiA0OC41NTM0IDEwNS41MDMgNDguNjMwOSAxMDUuNjk0QzQ4LjcxMjkgMTA1Ljg4MSA0OC44MzU5IDEwNi4wMzQgNDkgMTA2LjE1MkM0OS4xNjQxIDEwNi4yNjYgNDkuMzY5MSAxMDYuMzIzIDQ5LjYxNTIgMTA2LjMyM0M0OS44NjEzIDEwNi4zMjMgNTAuMDY0MSAxMDYuMjY2IDUwLjIyMzYgMTA2LjE1MkM1MC4zODc3IDEwNi4wMzQgNTAuNTA4NSAxMDUuODgxIDUwLjU4NTkgMTA1LjY5NEM1MC42NjM0IDEwNS41MDcgNTAuNzAyMSAxMDUuMzA3IDUwLjcwMjEgMTA1LjA5M1YxMDQuNTZDNTAuNzAyMSAxMDQuMzQ1IDUwLjY2MTEgMTA0LjE0NSA1MC41NzkxIDEwMy45NThDNTAuNTAxNiAxMDMuNzcxIDUwLjM4MDkgMTAzLjYyMSA1MC4yMTY4IDEwMy41MDdDNTAuMDU3MyAxMDMuMzg4IDQ5Ljg1MjIgMTAzLjMyOSA0OS42MDE2IDEwMy4zMjlDNDkuMzYgMTAzLjMyOSA0OS4xNTcyIDEwMy4zODggNDguOTkzMiAxMDMuNTA3QzQ4LjgzMzcgMTAzLjYyMSA0OC43MTI5IDEwMy43NzEgNDguNjMwOSAxMDMuOTU4QzQ4LjU1MzQgMTA0LjE0NSA0OC41MTQ2IDEwNC4zNDUgNDguNTE0NiAxMDQuNTZaTTQ5LjkxNiA5OC40NjE5TDQ1LjA1NTcgMTA2LjI0MUw0NC4zNDQ3IDEwNS43OUw0OS4yMDUxIDk4LjAxMDdMNDkuOTE2IDk4LjQ2MTlaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg3Ii8+CjxyZWN0IHg9IjEyIiB5PSIxMjMiIHdpZHRoPSIxNzYiIGhlaWdodD0iNSIgcng9IjIuNSIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC4wNCIvPgo8cmVjdCB3aWR0aD0iNTEiIGhlaWdodD0iNSIgcng9IjIuNSIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgNjMgMTIzKSIgZmlsbD0iIzNGNTJERCIvPgo8cGF0aCBkPSJNMTcuNTU5MSAxMzkuNDY3VjE0MC42NTlDMTcuNTU5MSAxNDEuMyAxNy41MDE4IDE0MS44NDEgMTcuMzg3MiAxNDIuMjgxQzE3LjI3MjYgMTQyLjcyMiAxNy4xMDc5IDE0My4wNzYgMTYuODkzMSAxNDMuMzQ1QzE2LjY3ODIgMTQzLjYxMyAxNi40MTg2IDE0My44MDggMTYuMTE0MyAxNDMuOTNDMTUuODEzNSAxNDQuMDQ4IDE1LjQ3MzMgMTQ0LjEwNyAxNS4wOTM4IDE0NC4xMDdDMTQuNzkzIDE0NC4xMDcgMTQuNTE1NSAxNDQuMDcgMTQuMjYxMiAxNDMuOTk1QzE0LjAwNyAxNDMuOTE5IDEzLjc3NzggMTQzLjc5OSAxMy41NzM3IDE0My42MzVDMTMuMzczMiAxNDMuNDY2IDEzLjIwMTMgMTQzLjI0OCAxMy4wNTgxIDE0Mi45NzlDMTIuOTE0OSAxNDIuNzExIDEyLjgwNTcgMTQyLjM4NSAxMi43MzA1IDE0Mi4wMDJDMTIuNjU1MyAxNDEuNjE5IDEyLjYxNzcgMTQxLjE3MSAxMi42MTc3IDE0MC42NTlWMTM5LjQ2N0MxMi42MTc3IDEzOC44MjYgMTIuNjc1IDEzOC4yODkgMTIuNzg5NiAxMzcuODU1QzEyLjkwNzcgMTM3LjQyMiAxMy4wNzQyIDEzNy4wNzUgMTMuMjg5MSAxMzYuODEzQzEzLjUwMzkgMTM2LjU0OSAxMy43NjE3IDEzNi4zNTkgMTQuMDYyNSAxMzYuMjQ0QzE0LjM2NjkgMTM2LjEzIDE0LjcwNyAxMzYuMDcyIDE1LjA4MyAxMzYuMDcyQzE1LjM4NzQgMTM2LjA3MiAxNS42NjY3IDEzNi4xMSAxNS45MjA5IDEzNi4xODVDMTYuMTc4NyAxMzYuMjU3IDE2LjQwNzkgMTM2LjM3MyAxNi42MDg0IDEzNi41MzRDMTYuODA4OSAxMzYuNjkyIDE2Ljk3OSAxMzYuOTAzIDE3LjExODcgMTM3LjE2OEMxNy4yNjE5IDEzNy40MjkgMTcuMzcxMSAxMzcuNzUgMTcuNDQ2MyAxMzguMTI5QzE3LjUyMTUgMTM4LjUwOSAxNy41NTkxIDEzOC45NTUgMTcuNTU5MSAxMzkuNDY3Wk0xNi41NjAxIDE0MC44MlYxMzkuM0MxNi41NjAxIDEzOC45NDkgMTYuNTM4NiAxMzguNjQxIDE2LjQ5NTYgMTM4LjM3NkMxNi40NTYyIDEzOC4xMDggMTYuMzk3MSAxMzcuODc5IDE2LjMxODQgMTM3LjY4OUMxNi4yMzk2IDEzNy40OTkgMTYuMTM5MyAxMzcuMzQ1IDE2LjAxNzYgMTM3LjIyN0MxNS44OTk0IDEzNy4xMDkgMTUuNzYxNiAxMzcuMDIzIDE1LjYwNCAxMzYuOTY5QzE1LjQ1IDEzNi45MTIgMTUuMjc2NCAxMzYuODgzIDE1LjA4MyAxMzYuODgzQzE0Ljg0NjcgMTM2Ljg4MyAxNC42MzcyIDEzNi45MjggMTQuNDU0NiAxMzcuMDE4QzE0LjI3MiAxMzcuMTA0IDE0LjExOCAxMzcuMjQxIDEzLjk5MjcgMTM3LjQzMUMxMy44NzA5IDEzNy42MjEgMTMuNzc3OCAxMzcuODcgMTMuNzEzNCAxMzguMTc4QzEzLjY0ODkgMTM4LjQ4NiAxMy42MTY3IDEzOC44NiAxMy42MTY3IDEzOS4zVjE0MC44MkMxMy42MTY3IDE0MS4xNzEgMTMuNjM2NCAxNDEuNDgxIDEzLjY3NTggMTQxLjc1QzEzLjcxODggMTQyLjAxOCAxMy43ODE0IDE0Mi4yNTEgMTMuODYzOCAxNDIuNDQ4QzEzLjk0NjEgMTQyLjY0MSAxNC4wNDY0IDE0Mi44IDE0LjE2NDYgMTQyLjkyNkMxNC4yODI3IDE0My4wNTEgMTQuNDE4OCAxNDMuMTQ0IDE0LjU3MjggMTQzLjIwNUMxNC43MzAzIDE0My4yNjIgMTQuOTA0IDE0My4yOTEgMTUuMDkzOCAxNDMuMjkxQzE1LjMzNzIgMTQzLjI5MSAxNS41NTAzIDE0My4yNDQgMTUuNzMyOSAxNDMuMTUxQzE1LjkxNTUgMTQzLjA1OCAxNi4wNjc3IDE0Mi45MTMgMTYuMTg5NSAxNDIuNzE2QzE2LjMxNDggMTQyLjUxNiAxNi40MDc5IDE0Mi4yNiAxNi40Njg4IDE0MS45NDhDMTYuNTI5NiAxNDEuNjMzIDE2LjU2MDEgMTQxLjI1NyAxNi41NjAxIDE0MC44MloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTE3MS45MTYgMTM2LjEzN1YxNDRIMTcwLjkyMlYxMzcuMzc3TDE2OC45MTggMTM4LjEwOFYxMzcuMjExTDE3MS43NiAxMzYuMTM3SDE3MS45MTZaTTE3OS45OTcgMTM5LjQ2N1YxNDAuNjU5QzE3OS45OTcgMTQxLjMgMTc5LjkzOSAxNDEuODQxIDE3OS44MjUgMTQyLjI4MUMxNzkuNzEgMTQyLjcyMiAxNzkuNTQ1IDE0My4wNzYgMTc5LjMzMSAxNDMuMzQ1QzE3OS4xMTYgMTQzLjYxMyAxNzguODU2IDE0My44MDggMTc4LjU1MiAxNDMuOTNDMTc4LjI1MSAxNDQuMDQ4IDE3Ny45MTEgMTQ0LjEwNyAxNzcuNTMxIDE0NC4xMDdDMTc3LjIzIDE0NC4xMDcgMTc2Ljk1MyAxNDQuMDcgMTc2LjY5OSAxNDMuOTk1QzE3Ni40NDQgMTQzLjkxOSAxNzYuMjE1IDE0My43OTkgMTc2LjAxMSAxNDMuNjM1QzE3NS44MTEgMTQzLjQ2NiAxNzUuNjM5IDE0My4yNDggMTc1LjQ5NiAxNDIuOTc5QzE3NS4zNTIgMTQyLjcxMSAxNzUuMjQzIDE0Mi4zODUgMTc1LjE2OCAxNDIuMDAyQzE3NS4wOTMgMTQxLjYxOSAxNzUuMDU1IDE0MS4xNzEgMTc1LjA1NSAxNDAuNjU5VjEzOS40NjdDMTc1LjA1NSAxMzguODI2IDE3NS4xMTIgMTM4LjI4OSAxNzUuMjI3IDEzNy44NTVDMTc1LjM0NSAxMzcuNDIyIDE3NS41MTIgMTM3LjA3NSAxNzUuNzI3IDEzNi44MTNDMTc1Ljk0MSAxMzYuNTQ5IDE3Ni4xOTkgMTM2LjM1OSAxNzYuNSAxMzYuMjQ0QzE3Ni44MDQgMTM2LjEzIDE3Ny4xNDUgMTM2LjA3MiAxNzcuNTIxIDEzNi4wNzJDMTc3LjgyNSAxMzYuMDcyIDE3OC4xMDQgMTM2LjExIDE3OC4zNTggMTM2LjE4NUMxNzguNjE2IDEzNi4yNTcgMTc4Ljg0NSAxMzYuMzczIDE3OS4wNDYgMTM2LjUzNEMxNzkuMjQ2IDEzNi42OTIgMTc5LjQxNyAxMzYuOTAzIDE3OS41NTYgMTM3LjE2OEMxNzkuNjk5IDEzNy40MjkgMTc5LjgwOSAxMzcuNzUgMTc5Ljg4NCAxMzguMTI5QzE3OS45NTkgMTM4LjUwOSAxNzkuOTk3IDEzOC45NTUgMTc5Ljk5NyAxMzkuNDY3Wk0xNzguOTk4IDE0MC44MlYxMzkuM0MxNzguOTk4IDEzOC45NDkgMTc4Ljk3NiAxMzguNjQxIDE3OC45MzMgMTM4LjM3NkMxNzguODk0IDEzOC4xMDggMTc4LjgzNSAxMzcuODc5IDE3OC43NTYgMTM3LjY4OUMxNzguNjc3IDEzNy40OTkgMTc4LjU3NyAxMzcuMzQ1IDE3OC40NTUgMTM3LjIyN0MxNzguMzM3IDEzNy4xMDkgMTc4LjE5OSAxMzcuMDIzIDE3OC4wNDIgMTM2Ljk2OUMxNzcuODg4IDEzNi45MTIgMTc3LjcxNCAxMzYuODgzIDE3Ny41MjEgMTM2Ljg4M0MxNzcuMjg0IDEzNi44ODMgMTc3LjA3NSAxMzYuOTI4IDE3Ni44OTIgMTM3LjAxOEMxNzYuNzA5IDEzNy4xMDQgMTc2LjU1NiAxMzcuMjQxIDE3Ni40MyAxMzcuNDMxQzE3Ni4zMDggMTM3LjYyMSAxNzYuMjE1IDEzNy44NyAxNzYuMTUxIDEzOC4xNzhDMTc2LjA4NiAxMzguNDg2IDE3Ni4wNTQgMTM4Ljg2IDE3Ni4wNTQgMTM5LjNWMTQwLjgyQzE3Ni4wNTQgMTQxLjE3MSAxNzYuMDc0IDE0MS40ODEgMTc2LjExMyAxNDEuNzVDMTc2LjE1NiAxNDIuMDE4IDE3Ni4yMTkgMTQyLjI1MSAxNzYuMzAxIDE0Mi40NDhDMTc2LjM4NCAxNDIuNjQxIDE3Ni40ODQgMTQyLjggMTc2LjYwMiAxNDIuOTI2QzE3Ni43MiAxNDMuMDUxIDE3Ni44NTYgMTQzLjE0NCAxNzcuMDEgMTQzLjIwNUMxNzcuMTY4IDE0My4yNjIgMTc3LjM0MSAxNDMuMjkxIDE3Ny41MzEgMTQzLjI5MUMxNzcuNzc1IDE0My4yOTEgMTc3Ljk4OCAxNDMuMjQ0IDE3OC4xNyAxNDMuMTUxQzE3OC4zNTMgMTQzLjA1OCAxNzguNTA1IDE0Mi45MTMgMTc4LjYyNyAxNDIuNzE2QzE3OC43NTIgMTQyLjUxNiAxNzguODQ1IDE0Mi4yNiAxNzguOTA2IDE0MS45NDhDMTc4Ljk2NyAxNDEuNjMzIDE3OC45OTggMTQxLjI1NyAxNzguOTk4IDE0MC44MlpNMTg2LjQzNCAxMzkuNDY3VjE0MC42NTlDMTg2LjQzNCAxNDEuMyAxODYuMzc3IDE0MS44NDEgMTg2LjI2MiAxNDIuMjgxQzE4Ni4xNDggMTQyLjcyMiAxODUuOTgzIDE0My4wNzYgMTg1Ljc2OCAxNDMuMzQ1QzE4NS41NTMgMTQzLjYxMyAxODUuMjk0IDE0My44MDggMTg0Ljk4OSAxNDMuOTNDMTg0LjY4OCAxNDQuMDQ4IDE4NC4zNDggMTQ0LjEwNyAxODMuOTY5IDE0NC4xMDdDMTgzLjY2OCAxNDQuMTA3IDE4My4zOSAxNDQuMDcgMTgzLjEzNiAxNDMuOTk1QzE4Mi44ODIgMTQzLjkxOSAxODIuNjUzIDE0My43OTkgMTgyLjQ0OSAxNDMuNjM1QzE4Mi4yNDggMTQzLjQ2NiAxODIuMDc2IDE0My4yNDggMTgxLjkzMyAxNDIuOTc5QzE4MS43OSAxNDIuNzExIDE4MS42ODEgMTQyLjM4NSAxODEuNjA1IDE0Mi4wMDJDMTgxLjUzIDE0MS42MTkgMTgxLjQ5MyAxNDEuMTcxIDE4MS40OTMgMTQwLjY1OVYxMzkuNDY3QzE4MS40OTMgMTM4LjgyNiAxODEuNTUgMTM4LjI4OSAxODEuNjY1IDEzNy44NTVDMTgxLjc4MyAxMzcuNDIyIDE4MS45NDkgMTM3LjA3NSAxODIuMTY0IDEzNi44MTNDMTgyLjM3OSAxMzYuNTQ5IDE4Mi42MzcgMTM2LjM1OSAxODIuOTM4IDEzNi4yNDRDMTgzLjI0MiAxMzYuMTMgMTgzLjU4MiAxMzYuMDcyIDE4My45NTggMTM2LjA3MkMxODQuMjYyIDEzNi4wNzIgMTg0LjU0MiAxMzYuMTEgMTg0Ljc5NiAxMzYuMTg1QzE4NS4wNTQgMTM2LjI1NyAxODUuMjgzIDEzNi4zNzMgMTg1LjQ4MyAxMzYuNTM0QzE4NS42ODQgMTM2LjY5MiAxODUuODU0IDEzNi45MDMgMTg1Ljk5NCAxMzcuMTY4QzE4Ni4xMzcgMTM3LjQyOSAxODYuMjQ2IDEzNy43NSAxODYuMzIxIDEzOC4xMjlDMTg2LjM5NiAxMzguNTA5IDE4Ni40MzQgMTM4Ljk1NSAxODYuNDM0IDEzOS40NjdaTTE4NS40MzUgMTQwLjgyVjEzOS4zQzE4NS40MzUgMTM4Ljk0OSAxODUuNDE0IDEzOC42NDEgMTg1LjM3MSAxMzguMzc2QzE4NS4zMzEgMTM4LjEwOCAxODUuMjcyIDEzNy44NzkgMTg1LjE5MyAxMzcuNjg5QzE4NS4xMTUgMTM3LjQ5OSAxODUuMDE0IDEzNy4zNDUgMTg0Ljg5MyAxMzcuMjI3QzE4NC43NzQgMTM3LjEwOSAxODQuNjM3IDEzNy4wMjMgMTg0LjQ3OSAxMzYuOTY5QzE4NC4zMjUgMTM2LjkxMiAxODQuMTUxIDEzNi44ODMgMTgzLjk1OCAxMzYuODgzQzE4My43MjIgMTM2Ljg4MyAxODMuNTEyIDEzNi45MjggMTgzLjMzIDEzNy4wMThDMTgzLjE0NyAxMzcuMTA0IDE4Mi45OTMgMTM3LjI0MSAxODIuODY4IDEzNy40MzFDMTgyLjc0NiAxMzcuNjIxIDE4Mi42NTMgMTM3Ljg3IDE4Mi41ODggMTM4LjE3OEMxODIuNTI0IDEzOC40ODYgMTgyLjQ5MiAxMzguODYgMTgyLjQ5MiAxMzkuM1YxNDAuODJDMTgyLjQ5MiAxNDEuMTcxIDE4Mi41MTEgMTQxLjQ4MSAxODIuNTUxIDE0MS43NUMxODIuNTk0IDE0Mi4wMTggMTgyLjY1NiAxNDIuMjUxIDE4Mi43MzkgMTQyLjQ0OEMxODIuODIxIDE0Mi42NDEgMTgyLjkyMSAxNDIuOCAxODMuMDQgMTQyLjkyNkMxODMuMTU4IDE0My4wNTEgMTgzLjI5NCAxNDMuMTQ0IDE4My40NDggMTQzLjIwNUMxODMuNjA1IDE0My4yNjIgMTgzLjc3OSAxNDMuMjkxIDE4My45NjkgMTQzLjI5MUMxODQuMjEyIDE0My4yOTEgMTg0LjQyNSAxNDMuMjQ0IDE4NC42MDggMTQzLjE1MUMxODQuNzkxIDE0My4wNTggMTg0Ljk0MyAxNDIuOTEzIDE4NS4wNjQgMTQyLjcxNkMxODUuMTkgMTQyLjUxNiAxODUuMjgzIDE0Mi4yNiAxODUuMzQ0IDE0MS45NDhDMTg1LjQwNSAxNDEuNjMzIDE4NS40MzUgMTQxLjI1NyAxODUuNDM1IDE0MC44MloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPC9nPgo8L2c+CjxyZWN0IHg9IjAuNSIgeT0iMC41IiB3aWR0aD0iMTk5IiBoZWlnaHQ9IjE1OSIgcng9IjMuNSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZF80Njg1XzQwOTg5IiB4PSItOCIgeT0iLTQiIHdpZHRoPSIyMTYiIGhlaWdodD0iMTc2IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQgZHk9IjQiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNCIvPgo8ZmVDb21wb3NpdGUgaW4yPSJoYXJkQWxwaGEiIG9wZXJhdG9yPSJvdXQiLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvd180Njg1XzQwOTg5Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93XzQ2ODVfNDA5ODkiIHJlc3VsdD0ic2hhcGUiLz4KPC9maWx0ZXI+CjxmaWx0ZXIgaWQ9ImZpbHRlcjFfYl80Njg1XzQwOTg5IiB4PSI2IiB5PSI2IiB3aWR0aD0iMTg4IiBoZWlnaHQ9IjE0OCIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVHYXVzc2lhbkJsdXIgaW49IkJhY2tncm91bmRJbWFnZUZpeCIgc3RkRGV2aWF0aW9uPSIzIi8+CjxmZUNvbXBvc2l0ZSBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iZWZmZWN0MV9iYWNrZ3JvdW5kQmx1cl80Njg1XzQwOTg5Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9iYWNrZ3JvdW5kQmx1cl80Njg1XzQwOTg5IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzQ2ODVfNDA5ODkiPgo8cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgcng9IjQiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==", "description": "Displays any value reading as a horizontal progress bar. Allows to configure value range, bar colors, and other settings.", "descriptor": { "type": "latest", @@ -19,7 +19,6 @@ "basicModeDirective": "tb-progress-bar-basic-config", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 7;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 0;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"layout\":\"default\",\"autoScale\":true,\"showValue\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"32px\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"tickMin\":0,\"tickMax\":100,\"showTicks\":true,\"ticksFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"ticksColor\":\"rgba(0,0,0,0.54)\",\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Progress bar\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"%\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"mdi:water-percent\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"18px\",\"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},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}" }, - "externalId": null, "tags": [ "progress", "loading", diff --git a/application/src/main/data/json/system/widget_types/simple_value_and_chart_card.json b/application/src/main/data/json/system/widget_types/simple_value_and_chart_card.json index e19ff918be..a17d2ee031 100644 --- a/application/src/main/data/json/system/widget_types/simple_value_and_chart_card.json +++ b/application/src/main/data/json/system/widget_types/simple_value_and_chart_card.json @@ -2,7 +2,7 @@ "fqn": "simple_value_and_chart_card", "name": "Simple Value and chart card", "deprecated": false, - "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAflBMVEUAAADg4ODg4ODf39/////g4OAhISHj4+M/Ut09PT2srKx0dHSQkJDn6fvHx8fx8fEvLy9XaOHP1PdYWFi3vvKenp7z9P3V1dW6urqCgoKHk+pvfeVLXd/b3/lmZmaTnuyrs/DDyfWfqO5jc+Orq6tLS0ufqe5LXN97iOhKSkrA29V9AAAABHRSTlMA77cggcfpsAAABRRJREFUeNrsz0EBACEIADBBedu/7RlDua3BBgAAAAAAwO/MjHpdzvOIvZ63Y45s8DiTHLVaiC6RErmMyG0+9stdRXIYiKLJ5RZ6IBUIITlx1A7m/39wq3A3y/ZqN5jA0/M4ILnqWhYc5MD+EXk1vqZIlTv4fPwhUnin4B1IHLie9YnEGFVtEryDwozLWYs4IZxOPUcAJVoxIT0LIFGmpY5dzyB2oOQ8feY+MWIFZrQxRxZPu2DNJSKiVB5AYgjkzkAdiAyqbEA9LAjiAQOyr8nYlNqsKUAj4EsLEpWeLLhCxGdBZkeyIeSwZkPkXtFYsHmqzUWK1JBgaT1frd8i3KRGi0QDVlwiMthEBpOJyBmJNZERmGZ0qIgcegZAHaVRnkV8n51FxPdYcYVIIZ22EPGCJ3eRrhp0LdLofKDIYBJnIVK4QYM4jyAB6V8nMsTAkitEoCpArM8iGyyZNm5AGadIZAbaQ6RbVMNDJPsTMrHiGpEbQwuczyJsB70JXmynSLVGlQOVemDQu4dIfbOF+oYL+Eukdziy7VsBZq5nVPNEZN92DyB9Tzdg5OGNhV7htifL7M7Mj3086xX/5fqPRj+Al+VbipRU8LJ8zf+Rz8wv9ukYBWIYBqJoO40KCePCjhBJYe/9T7ikCCHJBTTg32m6B/aCZGtBsrUg2VqQbOWD+L5bpYdIbDjrhRsS2KK426HoQgxpiOtRlalCCwkc9yGqlRRip+POEaSQrs97QCghjvIc6vxRQtrnTwwYIUTQ3lOdnRDSIJ9twOggf3bNqElVEAzDNwx+CpggUimW5s457f//g0eWU5gszTi7hRc+F/lmTuMz34eQZgvil2T1IkJeeuZfovySrFuk02ComZv+bEH8kqxBhAnRBsZD0zPVnrnubx6afW8MIraIumgY4Z0KL0UYh7odNxd9M/LgTWQRxqHphJBX8BZ/0i1F1BkMT5a6EkRUEca5sElwzuYfTd/1l0vXojADVzFFrq7n2d/GmzAW0MIwNVFCyp69TUSA9K+hrrEWIV1zKtFoMAzyTSI1D11DGR/QQgSHq2wZ688a9LlnTHQD8At7tYg/TXfQuZbXbPnX/eFg0LW4y9VuFgqIHI6E5jbuMpcNFcmQJafkmCVPuqFFU5pbO6nGOi2m7aUU6sHOqIigSJJiQ7lzmZhsOOJPTExISvwFRQGGWfuoGngnTG/o7jfvuHCoAyK7FBc0I+YcjUda2WwdcYUo3lnBE6XjaxX+iTRDDjCiG4Z+FdkFRDL7d4sSF4/ZsMc5ys2u0S23z9CLXXBF7sGEbBV6Db7ICX+aTYWxyaXJdMyWNCVpOm4LTOxoskI+vEZvxm+tJLGjIb1nMmZLQlIy7soxPtiDKd3/YFXxShE34sk8OzJbiDCcowh4IrYIySx74yhMBxK9k7AItb3j8iIRAVcUASPie9CH7Ivsn9WDMxQBX+Rjcu6Vy47DvUh5Pi+NaKBRKAK+yB7j4+SUKfLY3Y7I522nzsB7FANfZF9gMsseJS7shXk2WKSGc5xy+CKJWXdkhvwrZ//zA0mB0yrPysd6tQ00LYqFv0S5QRCd5Ef2qd1/nN5qAN2hWPgiObnxgQ6TPCP5OJWn46RQPf95V63hqa6EQaCFrFKESRSdtdzE3kQ2kbWyiayNTWRtbCJrYxNZG//auQMaAEAYiIFZwvxbZjLgd+egBirkNUEhdSJ0znInZoI0W6r+Xc2WCgAAAAAAYJsLnGVmvd/WiIIAAAAASUVORK5CYII=", + "image": "tb-image:c2ltcGxlX3ZhbHVlX2FuZF9jaGFydF9jYXJkX3N5c3RlbV93aWRnZXRfaW1hZ2UucG5n:IlNpbXBsZSBWYWx1ZSBhbmQgY2hhcnQgY2FyZCIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHk9IjMyIiB3aWR0aD0iMjAwIiBoZWlnaHQ9Ijk2IiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iMC41IiB5PSIzMi41IiB3aWR0aD0iMTk5IiBoZWlnaHQ9Ijk1IiByeD0iMy41IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMTYuNzQxNyA0NS43NTc4VjU1SDE1LjE2MTFWNDUuNzU3OEgxNi43NDE3Wk0xOS42NDI2IDQ1Ljc1NzhWNDcuMDI3M0gxMi4yODU2VjQ1Ljc1NzhIMTkuNjQyNlpNMjMuMzc3NCA1NS4xMjdDMjIuODY5NiA1NS4xMjcgMjIuNDEwNSA1NS4wNDQ0IDIyIDU0Ljg3OTRDMjEuNTkzOCA1NC43MTAxIDIxLjI0NjcgNTQuNDc1MyAyMC45NTkgNTQuMTc0OEMyMC42NzU1IDUzLjg3NDMgMjAuNDU3NSA1My41MjEgMjAuMzA1MiA1My4xMTQ3QzIwLjE1MjggNTIuNzA4NSAyMC4wNzY3IDUyLjI3MDUgMjAuMDc2NyA1MS44MDA4VjUxLjU0NjlDMjAuMDc2NyA1MS4wMDk0IDIwLjE1NDkgNTAuNTIyOCAyMC4zMTE1IDUwLjA4NjlDMjAuNDY4MSA0OS42NTEgMjAuNjg2IDQ5LjI3ODYgMjAuOTY1MyA0OC45Njk3QzIxLjI0NDYgNDguNjU2NiAyMS41NzQ3IDQ4LjQxNzUgMjEuOTU1NiA0OC4yNTI0QzIyLjMzNjQgNDguMDg3NCAyMi43NDkgNDguMDA0OSAyMy4xOTM0IDQ4LjAwNDlDMjMuNjg0MiA0OC4wMDQ5IDI0LjExMzggNDguMDg3NCAyNC40ODE5IDQ4LjI1MjRDMjQuODUwMSA0OC40MTc1IDI1LjE1NDggNDguNjUwMiAyNS4zOTYgNDguOTUwN0MyNS42NDE0IDQ5LjI0NjkgMjUuODIzNCA0OS42MDAzIDI1Ljk0MTkgNTAuMDEwN0MyNi4wNjQ2IDUwLjQyMTIgMjYuMTI2IDUwLjg3NCAyNi4xMjYgNTEuMzY5MVY1Mi4wMjI5SDIwLjgxOTNWNTAuOTI0OEgyNC42MTUyVjUwLjgwNDJDMjQuNjA2OCA1MC41MjkxIDI0LjU1MTggNTAuMjcxIDI0LjQ1MDIgNTAuMDI5OEMyNC4zNTI5IDQ5Ljc4ODYgMjQuMjAyNiA0OS41OTM5IDIzLjk5OTUgNDkuNDQ1OEMyMy43OTY0IDQ5LjI5NzcgMjMuNTI1NiA0OS4yMjM2IDIzLjE4NyA0OS4yMjM2QzIyLjkzMzEgNDkuMjIzNiAyMi43MDY3IDQ5LjI3ODYgMjIuNTA3OCA0OS4zODg3QzIyLjMxMzIgNDkuNDk0NSAyMi4xNTAyIDQ5LjY0ODkgMjIuMDE5IDQ5Ljg1MjFDMjEuODg3OSA1MC4wNTUyIDIxLjc4NjMgNTAuMzAwNiAyMS43MTQ0IDUwLjU4ODRDMjEuNjQ2NiA1MC44NzE5IDIxLjYxMjggNTEuMTkxNCAyMS42MTI4IDUxLjU0NjlWNTEuODAwOEMyMS42MTI4IDUyLjEwMTIgMjEuNjUzIDUyLjM4MDUgMjEuNzMzNCA1Mi42Mzg3QzIxLjgxOCA1Mi44OTI2IDIxLjk0MDggNTMuMTE0NyAyMi4xMDE2IDUzLjMwNTJDMjIuMjYyNCA1My40OTU2IDIyLjQ1NyA1My42NDU4IDIyLjY4NTUgNTMuNzU1OUMyMi45MTQxIDUzLjg2MTcgMjMuMTc0MyA1My45MTQ2IDIzLjQ2NjMgNTMuOTE0NkMyMy44MzQ1IDUzLjkxNDYgMjQuMTYyNCA1My44NDA1IDI0LjQ1MDIgNTMuNjkyNEMyNC43MzggNTMuNTQ0MyAyNC45ODc2IDUzLjMzNDggMjUuMTk5MiA1My4wNjRMMjYuMDA1NCA1My44NDQ3QzI1Ljg1NzMgNTQuMDYwNSAyNS42NjQ3IDU0LjI2NzkgMjUuNDI3NyA1NC40NjY4QzI1LjE5MDggNTQuNjYxNSAyNC45MDA5IDU0LjgyMDEgMjQuNTU4MSA1NC45NDI5QzI0LjIxOTYgNTUuMDY1NiAyMy44MjYgNTUuMTI3IDIzLjM3NzQgNTUuMTI3Wk0yOS4wOTkxIDQ5LjUyODNWNTVIMjcuNTY5M1Y0OC4xMzE4SDI5LjAxMDNMMjkuMDk5MSA0OS41MjgzWk0yOC44NTE2IDUxLjMxMkwyOC4zMzExIDUxLjMwNTdDMjguMzMxMSA1MC44MzE3IDI4LjM5MDMgNTAuMzkzNyAyOC41MDg4IDQ5Ljk5MTdDMjguNjI3MyA0OS41ODk3IDI4LjgwMDggNDkuMjQwNiAyOS4wMjkzIDQ4Ljk0NDNDMjkuMjU3OCA0OC42NDM5IDI5LjU0MTMgNDguNDEzMiAyOS44Nzk5IDQ4LjI1MjRDMzAuMjIyNyA0OC4wODc0IDMwLjYxODMgNDguMDA0OSAzMS4wNjY5IDQ4LjAwNDlDMzEuMzggNDguMDA0OSAzMS42NjU3IDQ4LjA1MTQgMzEuOTIzOCA0OC4xNDQ1QzMyLjE4NjIgNDguMjMzNCAzMi40MTI2IDQ4LjM3NTIgMzIuNjAzIDQ4LjU2OThDMzIuNzk3NyA0OC43NjQ1IDMyLjk0NTggNDkuMDE0MiAzMy4wNDc0IDQ5LjMxODhDMzMuMTUzMiA0OS42MjM1IDMzLjIwNjEgNDkuOTkxNyAzMy4yMDYxIDUwLjQyMzNWNTVIMzEuNjc2M1Y1MC41NTY2QzMxLjY3NjMgNTAuMjIyMyAzMS42MjU1IDQ5Ljk2IDMxLjUyMzkgNDkuNzY5NUMzMS40MjY2IDQ5LjU3OTEgMzEuMjg0OCA0OS40NDM3IDMxLjA5ODYgNDkuMzYzM0MzMC45MTY3IDQ5LjI3ODYgMzAuNjk4NyA0OS4yMzYzIDMwLjQ0NDggNDkuMjM2M0MzMC4xNTcxIDQ5LjIzNjMgMjkuOTExNiA0OS4yOTEzIDI5LjcwODUgNDkuNDAxNEMyOS41MDk2IDQ5LjUxMTQgMjkuMzQ2NyA0OS42NjE2IDI5LjIxOTcgNDkuODUyMUMyOS4wOTI4IDUwLjA0MjUgMjguOTk5NyA1MC4yNjI1IDI4Ljk0MDQgNTAuNTEyMkMyOC44ODEyIDUwLjc2MTkgMjguODUxNiA1MS4wMjg1IDI4Ljg1MTYgNTEuMzEyWk0zMy4xMTA4IDUwLjkwNThMMzIuMzkzNiA1MS4wNjQ1QzMyLjM5MzYgNTAuNjQ5NyAzMi40NTA3IDUwLjI1ODMgMzIuNTY0OSA0OS44OTAxQzMyLjY4MzQgNDkuNTE3NyAzMi44NTQ4IDQ5LjE5MTkgMzMuMDc5MSA0OC45MTI2QzMzLjMwNzYgNDguNjI5MSAzMy41ODkgNDguNDA2OSAzMy45MjMzIDQ4LjI0NjFDMzQuMjU3NiA0OC4wODUzIDM0LjY0MDYgNDguMDA0OSAzNS4wNzIzIDQ4LjAwNDlDMzUuNDIzNSA0OC4wMDQ5IDM1LjczNjcgNDguMDUzNSAzNi4wMTE3IDQ4LjE1MDlDMzYuMjkxIDQ4LjI0NCAzNi41MjggNDguMzkyMSAzNi43MjI3IDQ4LjU5NTJDMzYuOTE3MyA0OC43OTgzIDM3LjA2NTQgNDkuMDYyOCAzNy4xNjcgNDkuMzg4N0MzNy4yNjg2IDQ5LjcxMDMgMzcuMzE5MyA1MC4wOTk2IDM3LjMxOTMgNTAuNTU2NlY1NUgzNS43ODMyVjUwLjU1MDNDMzUuNzgzMiA1MC4yMDMzIDM1LjczMjQgNDkuOTM0NiAzNS42MzA5IDQ5Ljc0NDFDMzUuNTMzNSA0OS41NTM3IDM1LjM5MzkgNDkuNDIyNSAzNS4yMTE5IDQ5LjM1MDZDMzUuMDI5OSA0OS4yNzQ0IDM0LjgxMiA0OS4yMzYzIDM0LjU1ODEgNDkuMjM2M0MzNC4zMjExIDQ5LjIzNjMgMzQuMTExNyA0OS4yODA4IDMzLjkyOTcgNDkuMzY5NkMzMy43NTIgNDkuNDU0MyAzMy42MDE3IDQ5LjU3NDkgMzMuNDc5IDQ5LjczMTRDMzMuMzU2MyA0OS44ODM4IDMzLjI2MzIgNTAuMDU5NCAzMy4xOTk3IDUwLjI1ODNDMzMuMTQwNSA1MC40NTcyIDMzLjExMDggNTAuNjczIDMzLjExMDggNTAuOTA1OFpNNDAuNjc5NyA0OS40NTIxVjU3LjY0MDZIMzkuMTQ5OVY0OC4xMzE4SDQwLjU1OTFMNDAuNjc5NyA0OS40NTIxWk00NS4xNTQ4IDUxLjUwMjRWNTEuNjM1N0M0NS4xNTQ4IDUyLjEzNTEgNDUuMDk1NSA1Mi41OTg1IDQ0Ljk3NzEgNTMuMDI1OUM0NC44NjI4IDUzLjQ0OTEgNDQuNjkxNCA1My44MTkzIDQ0LjQ2MjkgNTQuMTM2N0M0NC4yMzg2IDU0LjQ0OTkgNDMuOTYxNCA1NC42OTMyIDQzLjYzMTMgNTQuODY2N0M0My4zMDEzIDU1LjA0MDIgNDIuOTIwNCA1NS4xMjcgNDIuNDg4OCA1NS4xMjdDNDIuMDYxNCA1NS4xMjcgNDEuNjg2OCA1NS4wNDg3IDQxLjM2NTIgNTQuODkyMUM0MS4wNDc5IDU0LjczMTMgNDAuNzc5MSA1NC41MDQ5IDQwLjU1OTEgNTQuMjEyOUM0MC4zMzkgNTMuOTIwOSA0MC4xNjEzIDUzLjU3ODEgNDAuMDI1OSA1My4xODQ2QzM5Ljg5NDcgNTIuNzg2OCAzOS44MDE2IDUyLjM1MDkgMzkuNzQ2NiA1MS44NzdWNTEuMzYyOEMzOS44MDE2IDUwLjg1OTIgMzkuODk0NyA1MC40MDIyIDQwLjAyNTkgNDkuOTkxN0M0MC4xNjEzIDQ5LjU4MTIgNDAuMzM5IDQ5LjIyNzkgNDAuNTU5MSA0OC45MzE2QzQwLjc3OTEgNDguNjM1NCA0MS4wNDc5IDQ4LjQwNjkgNDEuMzY1MiA0OC4yNDYxQzQxLjY4MjYgNDguMDg1MyA0Mi4wNTI5IDQ4LjAwNDkgNDIuNDc2MSA0OC4wMDQ5QzQyLjkwNzcgNDguMDA0OSA0My4yOTA3IDQ4LjA4OTUgNDMuNjI1IDQ4LjI1ODhDNDMuOTU5MyA0OC40MjM4IDQ0LjI0MDcgNDguNjYwOCA0NC40NjkyIDQ4Ljk2OTdDNDQuNjk3OCA0OS4yNzQ0IDQ0Ljg2OTEgNDkuNjQyNiA0NC45ODM0IDUwLjA3NDJDNDUuMDk3NyA1MC41MDE2IDQ1LjE1NDggNTAuOTc3NyA0NS4xNTQ4IDUxLjUwMjRaTTQzLjYyNSA1MS42MzU3VjUxLjUwMjRDNDMuNjI1IDUxLjE4NTEgNDMuNTk1NCA1MC44OTEgNDMuNTM2MSA1MC42MjAxQzQzLjQ3NjkgNTAuMzQ1MSA0My4zODM4IDUwLjEwMzggNDMuMjU2OCA0OS44OTY1QzQzLjEyOTkgNDkuNjg5MSA0Mi45NjcgNDkuNTI4MyA0Mi43NjgxIDQ5LjQxNDFDNDIuNTczNCA0OS4yOTU2IDQyLjMzODUgNDkuMjM2MyA0Mi4wNjM1IDQ5LjIzNjNDNDEuNzkyNiA0OS4yMzYzIDQxLjU1OTkgNDkuMjgyOSA0MS4zNjUyIDQ5LjM3NkM0MS4xNzA2IDQ5LjQ2NDggNDEuMDA3NiA0OS41ODk3IDQwLjg3NjUgNDkuNzUwNUM0MC43NDUzIDQ5LjkxMTMgNDAuNjQzNyA1MC4wOTk2IDQwLjU3MTggNTAuMzE1NEM0MC40OTk4IDUwLjUyNyA0MC40NDkxIDUwLjc1NzYgNDAuNDE5NCA1MS4wMDczVjUyLjIzODhDNDAuNDcwMiA1Mi41NDM1IDQwLjU1NyA1Mi44MjI4IDQwLjY3OTcgNTMuMDc2N0M0MC44MDI0IDUzLjMzMDYgNDAuOTc1OSA1My41MzM3IDQxLjIwMDIgNTMuNjg2QzQxLjQyODcgNTMuODM0MSA0MS43MjA3IDUzLjkwODIgNDIuMDc2MiA1My45MDgyQzQyLjM1MTIgNTMuOTA4MiA0Mi41ODYxIDUzLjg0OSA0Mi43ODA4IDUzLjczMDVDNDIuOTc1NCA1My42MTIgNDMuMTM0MSA1My40NDkxIDQzLjI1NjggNTMuMjQxN0M0My4zODM4IDUzLjAzMDEgNDMuNDc2OSA1Mi43ODY4IDQzLjUzNjEgNTIuNTExN0M0My41OTU0IDUyLjIzNjcgNDMuNjI1IDUxLjk0NDcgNDMuNjI1IDUxLjYzNTdaTTQ5LjczMzkgNTUuMTI3QzQ5LjIyNjEgNTUuMTI3IDQ4Ljc2NjkgNTUuMDQ0NCA0OC4zNTY0IDU0Ljg3OTRDNDcuOTUwMiA1NC43MTAxIDQ3LjYwMzIgNTQuNDc1MyA0Ny4zMTU0IDU0LjE3NDhDNDcuMDMxOSA1My44NzQzIDQ2LjgxNCA1My41MjEgNDYuNjYxNiA1My4xMTQ3QzQ2LjUwOTMgNTIuNzA4NSA0Ni40MzMxIDUyLjI3MDUgNDYuNDMzMSA1MS44MDA4VjUxLjU0NjlDNDYuNDMzMSA1MS4wMDk0IDQ2LjUxMTQgNTAuNTIyOCA0Ni42NjggNTAuMDg2OUM0Ni44MjQ1IDQ5LjY1MSA0Ny4wNDI1IDQ5LjI3ODYgNDcuMzIxOCA0OC45Njk3QzQ3LjYwMTEgNDguNjU2NiA0Ny45MzEyIDQ4LjQxNzUgNDguMzEyIDQ4LjI1MjRDNDguNjkyOSA0OC4wODc0IDQ5LjEwNTUgNDguMDA0OSA0OS41NDk4IDQ4LjAwNDlDNTAuMDQwNyA0OC4wMDQ5IDUwLjQ3MDIgNDguMDg3NCA1MC44Mzg0IDQ4LjI1MjRDNTEuMjA2NSA0OC40MTc1IDUxLjUxMTIgNDguNjUwMiA1MS43NTI0IDQ4Ljk1MDdDNTEuOTk3OSA0OS4yNDY5IDUyLjE3OTkgNDkuNjAwMyA1Mi4yOTgzIDUwLjAxMDdDNTIuNDIxMSA1MC40MjEyIDUyLjQ4MjQgNTAuODc0IDUyLjQ4MjQgNTEuMzY5MVY1Mi4wMjI5SDQ3LjE3NThWNTAuOTI0OEg1MC45NzE3VjUwLjgwNDJDNTAuOTYzMiA1MC41MjkxIDUwLjkwODIgNTAuMjcxIDUwLjgwNjYgNTAuMDI5OEM1MC43MDkzIDQ5Ljc4ODYgNTAuNTU5MSA0OS41OTM5IDUwLjM1NiA0OS40NDU4QzUwLjE1MjggNDkuMjk3NyA0OS44ODIgNDkuMjIzNiA0OS41NDM1IDQ5LjIyMzZDNDkuMjg5NiA0OS4yMjM2IDQ5LjA2MzIgNDkuMjc4NiA0OC44NjQzIDQ5LjM4ODdDNDguNjY5NiA0OS40OTQ1IDQ4LjUwNjcgNDkuNjQ4OSA0OC4zNzU1IDQ5Ljg1MjFDNDguMjQ0MyA1MC4wNTUyIDQ4LjE0MjcgNTAuMzAwNiA0OC4wNzA4IDUwLjU4ODRDNDguMDAzMSA1MC44NzE5IDQ3Ljk2OTIgNTEuMTkxNCA0Ny45NjkyIDUxLjU0NjlWNTEuODAwOEM0Ny45NjkyIDUyLjEwMTIgNDguMDA5NCA1Mi4zODA1IDQ4LjA4OTggNTIuNjM4N0M0OC4xNzQ1IDUyLjg5MjYgNDguMjk3MiA1My4xMTQ3IDQ4LjQ1OCA1My4zMDUyQzQ4LjYxODggNTMuNDk1NiA0OC44MTM1IDUzLjY0NTggNDkuMDQyIDUzLjc1NTlDNDkuMjcwNSA1My44NjE3IDQ5LjUzMDggNTMuOTE0NiA0OS44MjI4IDUzLjkxNDZDNTAuMTkwOSA1My45MTQ2IDUwLjUxODkgNTMuODQwNSA1MC44MDY2IDUzLjY5MjRDNTEuMDk0NCA1My41NDQzIDUxLjM0NDEgNTMuMzM0OCA1MS41NTU3IDUzLjA2NEw1Mi4zNjE4IDUzLjg0NDdDNTIuMjEzNyA1NC4wNjA1IDUyLjAyMTIgNTQuMjY3OSA1MS43ODQyIDU0LjQ2NjhDNTEuNTQ3MiA1NC42NjE1IDUxLjI1NzMgNTQuODIwMSA1MC45MTQ2IDU0Ljk0MjlDNTAuNTc2IDU1LjA2NTYgNTAuMTgyNSA1NS4xMjcgNDkuNzMzOSA1NS4xMjdaTTU1LjQ2MTkgNDkuNDM5NVY1NUg1My45MzIxVjQ4LjEzMThINTUuMzkyMUw1NS40NjE5IDQ5LjQzOTVaTTU3LjU2MyA0OC4wODc0TDU3LjU1MDMgNDkuNTA5M0M1Ny40NTcyIDQ5LjQ5MjQgNTcuMzU1NiA0OS40Nzk3IDU3LjI0NTYgNDkuNDcxMkM1Ny4xMzk4IDQ5LjQ2MjcgNTcuMDM0IDQ5LjQ1ODUgNTYuOTI4MiA0OS40NTg1QzU2LjY2NTkgNDkuNDU4NSA1Ni40MzUyIDQ5LjQ5NjYgNTYuMjM2MyA0OS41NzI4QzU2LjAzNzQgNDkuNjQ0NyA1NS44NzAzIDQ5Ljc1MDUgNTUuNzM0OSA0OS44OTAxQzU1LjYwMzcgNTAuMDI1NiA1NS41MDIxIDUwLjE5MDYgNTUuNDMwMiA1MC4zODUzQzU1LjM1ODIgNTAuNTc5OSA1NS4zMTU5IDUwLjc5NzkgNTUuMzAzMiA1MS4wMzkxTDU0Ljk1NDEgNTEuMDY0NUM1NC45NTQxIDUwLjYzMjggNTQuOTk2NCA1MC4yMzI5IDU1LjA4MTEgNDkuODY0N0M1NS4xNjU3IDQ5LjQ5NjYgNTUuMjkyNiA0OS4xNzI5IDU1LjQ2MTkgNDguODkzNkM1NS42MzU0IDQ4LjYxNDMgNTUuODUxMiA0OC4zOTYzIDU2LjEwOTQgNDguMjM5N0M1Ni4zNzE3IDQ4LjA4MzIgNTYuNjc0MyA0OC4wMDQ5IDU3LjAxNzEgNDguMDA0OUM1Ny4xMTAyIDQ4LjAwNDkgNTcuMjA5NiA0OC4wMTMzIDU3LjMxNTQgNDguMDMwM0M1Ny40MjU1IDQ4LjA0NzIgNTcuNTA4IDQ4LjA2NjIgNTcuNTYzIDQ4LjA4NzRaTTYyLjQxNSA1My42MjI2VjUwLjM0NzJDNjIuNDE1IDUwLjEwMTcgNjIuMzcwNiA0OS44OTAxIDYyLjI4MTcgNDkuNzEyNEM2Mi4xOTI5IDQ5LjUzNDcgNjIuMDU3NSA0OS4zOTcxIDYxLjg3NTUgNDkuMjk5OEM2MS42OTc4IDQ5LjIwMjUgNjEuNDczNSA0OS4xNTM4IDYxLjIwMjYgNDkuMTUzOEM2MC45NTMgNDkuMTUzOCA2MC43MzcxIDQ5LjE5NjEgNjAuNTU1MiA0OS4yODA4QzYwLjM3MzIgNDkuMzY1NCA2MC4yMzE0IDQ5LjQ3OTcgNjAuMTI5OSA0OS42MjM1QzYwLjAyODMgNDkuNzY3NCA1OS45Nzc1IDQ5LjkzMDMgNTkuOTc3NSA1MC4xMTIzSDU4LjQ1NDFDNTguNDU0MSA0OS44NDE1IDU4LjUxOTcgNDkuNTc5MSA1OC42NTA5IDQ5LjMyNTJDNTguNzgyMSA0OS4wNzEzIDU4Ljk3MjUgNDguODQ0OSA1OS4yMjIyIDQ4LjY0NkM1OS40NzE4IDQ4LjQ0NzEgNTkuNzcwMiA0OC4yOTA1IDYwLjExNzIgNDguMTc2M0M2MC40NjQyIDQ4LjA2MiA2MC44NTM1IDQ4LjAwNDkgNjEuMjg1MiA0OC4wMDQ5QzYxLjgwMTQgNDguMDA0OSA2Mi4yNTg1IDQ4LjA5MTYgNjIuNjU2MiA0OC4yNjUxQzYzLjA1ODMgNDguNDM4NiA2My4zNzM1IDQ4LjcwMSA2My42MDIxIDQ5LjA1MjJDNjMuODM0OCA0OS4zOTkzIDYzLjk1MTIgNDkuODM1MSA2My45NTEyIDUwLjM1OTlWNTMuNDEzMUM2My45NTEyIDUzLjcyNjIgNjMuOTcyMyA1NC4wMDc2IDY0LjAxNDYgNTQuMjU3M0M2NC4wNjEyIDU0LjUwMjggNjQuMTI2OCA1NC43MTY1IDY0LjIxMTQgNTQuODk4NFY1NUg2Mi42NDM2QzYyLjU3MTYgNTQuODM1IDYyLjUxNDUgNTQuNjI1NSA2Mi40NzIyIDU0LjM3MTZDNjIuNDM0MSA1NC4xMTM0IDYyLjQxNSA1My44NjM4IDYyLjQxNSA1My42MjI2Wk02Mi42MzcyIDUwLjgyMzJMNjIuNjQ5OSA1MS43NjlINjEuNTUxOEM2MS4yNjgyIDUxLjc2OSA2MS4wMTg2IDUxLjc5NjUgNjAuODAyNyA1MS44NTE2QzYwLjU4NjkgNTEuOTAyMyA2MC40MDcxIDUxLjk3ODUgNjAuMjYzMiA1Mi4wODAxQzYwLjExOTMgNTIuMTgxNiA2MC4wMTE0IDUyLjMwNDQgNTkuOTM5NSA1Mi40NDgyQzU5Ljg2NzUgNTIuNTkyMSA1OS44MzE1IDUyLjc1NSA1OS44MzE1IDUyLjkzN0M1OS44MzE1IDUzLjExOSA1OS44NzM5IDUzLjI4NjEgNTkuOTU4NSA1My40Mzg1QzYwLjA0MzEgNTMuNTg2NiA2MC4xNjU5IDUzLjcwMyA2MC4zMjY3IDUzLjc4NzZDNjAuNDkxNyA1My44NzIyIDYwLjY5MDYgNTMuOTE0NiA2MC45MjMzIDUzLjkxNDZDNjEuMjM2NSA1My45MTQ2IDYxLjUwOTQgNTMuODUxMSA2MS43NDIyIDUzLjcyNDFDNjEuOTc5MiA1My41OTI5IDYyLjE2NTQgNTMuNDM0MiA2Mi4zMDA4IDUzLjI0OEM2Mi40MzYyIDUzLjA1NzYgNjIuNTA4MSA1Mi44Nzc4IDYyLjUxNjYgNTIuNzA4NUw2My4wMTE3IDUzLjM4NzdDNjIuOTYwOSA1My41NjEyIDYyLjg3NDIgNTMuNzQ3NCA2Mi43NTE1IDUzLjk0NjNDNjIuNjI4NyA1NC4xNDUyIDYyLjQ2NzkgNTQuMzM1NiA2Mi4yNjkgNTQuNTE3NkM2Mi4wNzQ0IDU0LjY5NTMgNjEuODM5NSA1NC44NDEzIDYxLjU2NDUgNTQuOTU1NkM2MS4yOTM2IDU1LjA2OTggNjAuOTgwNSA1NS4xMjcgNjAuNjI1IDU1LjEyN0M2MC4xNzY0IDU1LjEyNyA1OS43NzY1IDU1LjAzODEgNTkuNDI1MyA1NC44NjA0QzU5LjA3NDEgNTQuNjc4NCA1OC43OTkgNTQuNDM1MSA1OC42MDAxIDU0LjEzMDRDNTguNDAxMiA1My44MjE1IDU4LjMwMTggNTMuNDcyMyA1OC4zMDE4IDUzLjA4M0M1OC4zMDE4IDUyLjcxOTEgNTguMzY5NSA1Mi4zOTc1IDU4LjUwNDkgNTIuMTE4MkM1OC42NDQ1IDUxLjgzNDYgNTguODQ3NyA1MS41OTc3IDU5LjExNDMgNTEuNDA3MkM1OS4zODUxIDUxLjIxNjggNTkuNzE1MiA1MS4wNzI5IDYwLjEwNDUgNTAuOTc1NkM2MC40OTM4IDUwLjg3NCA2MC45MzgyIDUwLjgyMzIgNjEuNDM3NSA1MC44MjMySDYyLjYzNzJaTTY4Ljk0OTIgNDguMTMxOFY0OS4yNDlINjUuMDc3MVY0OC4xMzE4SDY4Ljk0OTJaTTY2LjE5NDMgNDYuNDQ5N0g2Ny43MjQxVjUzLjEwMjFDNjcuNzI0MSA1My4zMTM2IDY3Ljc1MzcgNTMuNDc2NiA2Ny44MTMgNTMuNTkwOEM2Ny44NzY1IDUzLjcwMDggNjcuOTYzMiA1My43NzQ5IDY4LjA3MzIgNTMuODEzQzY4LjE4MzMgNTMuODUxMSA2OC4zMTIzIDUzLjg3MDEgNjguNDYwNCA1My44NzAxQzY4LjU2NjIgNTMuODcwMSA2OC42Njc4IDUzLjg2MzggNjguNzY1MSA1My44NTExQzY4Ljg2MjUgNTMuODM4NCA2OC45NDA4IDUzLjgyNTcgNjkgNTMuODEzTDY5LjAwNjMgNTQuOTgxQzY4Ljg3OTQgNTUuMDE5IDY4LjczMTMgNTUuMDUyOSA2OC41NjIgNTUuMDgyNUM2OC4zOTcgNTUuMTEyMSA2OC4yMDY1IDU1LjEyNyA2Ny45OTA3IDU1LjEyN0M2Ny42Mzk1IDU1LjEyNyA2Ny4zMjg1IDU1LjA2NTYgNjcuMDU3NiA1NC45NDI5QzY2Ljc4NjggNTQuODE1OSA2Ni41NzUyIDU0LjYxMDcgNjYuNDIyOSA1NC4zMjcxQzY2LjI3MDUgNTQuMDQzNiA2Ni4xOTQzIDUzLjY2NyA2Ni4xOTQzIDUzLjE5NzNWNDYuNDQ5N1pNNzQuNTEyMiA1My4zODEzVjQ4LjEzMThINzYuMDQ4M1Y1NUg3NC42MDExTDc0LjUxMjIgNTMuMzgxM1pNNzQuNzI4IDUxLjk1MzFMNzUuMjQyMiA1MS45NDA0Qzc1LjI0MjIgNTIuNDAxNyA3NS4xOTE0IDUyLjgyNyA3NS4wODk4IDUzLjIxNjNDNzQuOTg4MyA1My42MDE0IDc0LjgzMTcgNTMuOTM3OCA3NC42MjAxIDU0LjIyNTZDNzQuNDA4NSA1NC41MDkxIDc0LjEzNzcgNTQuNzMxMyA3My44MDc2IDU0Ljg5MjFDNzMuNDc3NSA1NS4wNDg3IDczLjA4MTkgNTUuMTI3IDcyLjYyMDYgNTUuMTI3QzcyLjI4NjMgNTUuMTI3IDcxLjk3OTUgNTUuMDc4MyA3MS43MDAyIDU0Ljk4MUM3MS40MjA5IDU0Ljg4MzYgNzEuMTc5NyA1NC43MzM0IDcwLjk3NjYgNTQuNTMwM0M3MC43Nzc3IDU0LjMyNzEgNzAuNjIzMiA1NC4wNjI3IDcwLjUxMzIgNTMuNzM2OEM3MC40MDMyIDUzLjQxMSA3MC4zNDgxIDUzLjAyMTYgNzAuMzQ4MSA1Mi41Njg4VjQ4LjEzMThINzEuODc3OVY1Mi41ODE1QzcxLjg3NzkgNTIuODMxMiA3MS45MDc2IDUzLjA0MDcgNzEuOTY2OCA1My4yMUM3Mi4wMjYgNTMuMzc1IDcyLjEwNjQgNTMuNTA4MyA3Mi4yMDggNTMuNjA5OUM3Mi4zMDk2IDUzLjcxMTQgNzIuNDI4MSA1My43ODM0IDcyLjU2MzUgNTMuODI1N0M3Mi42OTg5IDUzLjg2OCA3Mi44NDI4IDUzLjg4OTIgNzIuOTk1MSA1My44ODkyQzczLjQzMSA1My44ODkyIDczLjc3MzggNTMuODA0NSA3NC4wMjM0IDUzLjYzNTNDNzQuMjc3MyA1My40NjE4IDc0LjQ1NzIgNTMuMjI5IDc0LjU2MyA1Mi45MzdDNzQuNjczIDUyLjY0NSA3NC43MjggNTIuMzE3MSA3NC43MjggNTEuOTUzMVpNNzkuNDAyMyA0OS40Mzk1VjU1SDc3Ljg3MjZWNDguMTMxOEg3OS4zMzI1TDc5LjQwMjMgNDkuNDM5NVpNODEuNTAzNCA0OC4wODc0TDgxLjQ5MDcgNDkuNTA5M0M4MS4zOTc2IDQ5LjQ5MjQgODEuMjk2MSA0OS40Nzk3IDgxLjE4NiA0OS40NzEyQzgxLjA4MDIgNDkuNDYyNyA4MC45NzQ0IDQ5LjQ1ODUgODAuODY4NyA0OS40NTg1QzgwLjYwNjMgNDkuNDU4NSA4MC4zNzU3IDQ5LjQ5NjYgODAuMTc2OCA0OS41NzI4Qzc5Ljk3NzkgNDkuNjQ0NyA3OS44MTA3IDQ5Ljc1MDUgNzkuNjc1MyA0OS44OTAxQzc5LjU0NDEgNTAuMDI1NiA3OS40NDI1IDUwLjE5MDYgNzkuMzcwNiA1MC4zODUzQzc5LjI5ODcgNTAuNTc5OSA3OS4yNTYzIDUwLjc5NzkgNzkuMjQzNyA1MS4wMzkxTDc4Ljg5NDUgNTEuMDY0NUM3OC44OTQ1IDUwLjYzMjggNzguOTM2OCA1MC4yMzI5IDc5LjAyMTUgNDkuODY0N0M3OS4xMDYxIDQ5LjQ5NjYgNzkuMjMzMSA0OS4xNzI5IDc5LjQwMjMgNDguODkzNkM3OS41NzU4IDQ4LjYxNDMgNzkuNzkxNyA0OC4zOTYzIDgwLjA0OTggNDguMjM5N0M4MC4zMTIyIDQ4LjA4MzIgODAuNjE0NyA0OC4wMDQ5IDgwLjk1NzUgNDguMDA0OUM4MS4wNTA2IDQ4LjAwNDkgODEuMTUwMSA0OC4wMTMzIDgxLjI1NTkgNDguMDMwM0M4MS4zNjU5IDQ4LjA0NzIgODEuNDQ4NCA0OC4wNjYyIDgxLjUwMzQgNDguMDg3NFpNODUuNjEyOCA1NS4xMjdDODUuMTA1IDU1LjEyNyA4NC42NDU4IDU1LjA0NDQgODQuMjM1NCA1NC44Nzk0QzgzLjgyOTEgNTQuNzEwMSA4My40ODIxIDU0LjQ3NTMgODMuMTk0MyA1NC4xNzQ4QzgyLjkxMDggNTMuODc0MyA4Mi42OTI5IDUzLjUyMSA4Mi41NDA1IDUzLjExNDdDODIuMzg4MiA1Mi43MDg1IDgyLjMxMiA1Mi4yNzA1IDgyLjMxMiA1MS44MDA4VjUxLjU0NjlDODIuMzEyIDUxLjAwOTQgODIuMzkwMyA1MC41MjI4IDgyLjU0NjkgNTAuMDg2OUM4Mi43MDM1IDQ5LjY1MSA4Mi45MjE0IDQ5LjI3ODYgODMuMjAwNyA0OC45Njk3QzgzLjQ4IDQ4LjY1NjYgODMuODEwMSA0OC40MTc1IDg0LjE5MDkgNDguMjUyNEM4NC41NzE4IDQ4LjA4NzQgODQuOTg0NCA0OC4wMDQ5IDg1LjQyODcgNDguMDA0OUM4NS45MTk2IDQ4LjAwNDkgODYuMzQ5MSA0OC4wODc0IDg2LjcxNzMgNDguMjUyNEM4Ny4wODU0IDQ4LjQxNzUgODcuMzkwMSA0OC42NTAyIDg3LjYzMTMgNDguOTUwN0M4Ny44NzY4IDQ5LjI0NjkgODguMDU4OCA0OS42MDAzIDg4LjE3NzIgNTAuMDEwN0M4OC4zIDUwLjQyMTIgODguMzYxMyA1MC44NzQgODguMzYxMyA1MS4zNjkxVjUyLjAyMjlIODMuMDU0N1Y1MC45MjQ4SDg2Ljg1MDZWNTAuODA0MkM4Ni44NDIxIDUwLjUyOTEgODYuNzg3MSA1MC4yNzEgODYuNjg1NSA1MC4wMjk4Qzg2LjU4ODIgNDkuNzg4NiA4Ni40MzggNDkuNTkzOSA4Ni4yMzQ5IDQ5LjQ0NThDODYuMDMxNyA0OS4yOTc3IDg1Ljc2MDkgNDkuMjIzNiA4NS40MjI0IDQ5LjIyMzZDODUuMTY4NSA0OS4yMjM2IDg0Ljk0MjEgNDkuMjc4NiA4NC43NDMyIDQ5LjM4ODdDODQuNTQ4NSA0OS40OTQ1IDg0LjM4NTYgNDkuNjQ4OSA4NC4yNTQ0IDQ5Ljg1MjFDODQuMTIzMiA1MC4wNTUyIDg0LjAyMTYgNTAuMzAwNiA4My45NDk3IDUwLjU4ODRDODMuODgyIDUwLjg3MTkgODMuODQ4MSA1MS4xOTE0IDgzLjg0ODEgNTEuNTQ2OVY1MS44MDA4QzgzLjg0ODEgNTIuMTAxMiA4My44ODgzIDUyLjM4MDUgODMuOTY4OCA1Mi42Mzg3Qzg0LjA1MzQgNTIuODkyNiA4NC4xNzYxIDUzLjExNDcgODQuMzM2OSA1My4zMDUyQzg0LjQ5NzcgNTMuNDk1NiA4NC42OTI0IDUzLjY0NTggODQuOTIwOSA1My43NTU5Qzg1LjE0OTQgNTMuODYxNyA4NS40MDk3IDUzLjkxNDYgODUuNzAxNyA1My45MTQ2Qzg2LjA2OTggNTMuOTE0NiA4Ni4zOTc4IDUzLjg0MDUgODYuNjg1NSA1My42OTI0Qzg2Ljk3MzMgNTMuNTQ0MyA4Ny4yMjMgNTMuMzM0OCA4Ny40MzQ2IDUzLjA2NEw4OC4yNDA3IDUzLjg0NDdDODguMDkyNiA1NC4wNjA1IDg3LjkwMDEgNTQuMjY3OSA4Ny42NjMxIDU0LjQ2NjhDODcuNDI2MSA1NC42NjE1IDg3LjEzNjIgNTQuODIwMSA4Ni43OTM1IDU0Ljk0MjlDODYuNDU0OSA1NS4wNjU2IDg2LjA2MTQgNTUuMTI3IDg1LjYxMjggNTUuMTI3WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC44NyIvPgo8cGF0aCBkPSJNMjMuMTYzNiAxMDguMDI2VjExMEgxMy4xMzA4VjEwOC4zMDRMMTguMDAzMyAxMDIuOTg5QzE4LjUzNzggMTAyLjM4NiAxOC45NTkzIDEwMS44NjUgMTkuMjY3NyAxMDEuNDI3QzE5LjU3NjEgMTAwLjk4OCAxOS43OTE5IDEwMC41OTQgMTkuOTE1MyAxMDAuMjQ1QzIwLjA0NTUgOTkuODg4MyAyMC4xMTA2IDk5LjU0MjIgMjAuMTEwNiA5OS4yMDY0QzIwLjExMDYgOTguNzMzNiAyMC4wMjE1IDk4LjMxODkgMTkuODQzMyA5Ny45NjI2QzE5LjY3MiA5Ny41OTk0IDE5LjQxODQgOTcuMzE1IDE5LjA4MjYgOTcuMTA5NEMxOC43NDY4IDk2Ljg5NjkgMTguMzM5MSA5Ni43OTA3IDE3Ljg1OTQgOTYuNzkwN0MxNy4zMDQzIDk2Ljc5MDcgMTYuODM4MyA5Ni45MTA2IDE2LjQ2MTMgOTcuMTUwNUMxNi4wODQ0IDk3LjM5MDQgMTUuOCA5Ny43MjI3IDE1LjYwODEgOTguMTQ3NkMxNS40MTYzIDk4LjU2NTcgMTUuMzIwMyA5OS4wNDU0IDE1LjMyMDMgOTkuNTg2OEgxMi44NDI5QzEyLjg0MjkgOTguNzE2NCAxMy4wNDE3IDk3LjkyMTUgMTMuNDM5MSA5Ny4yMDE5QzEzLjgzNjYgOTYuNDc1NSAxNC40MTIzIDk1Ljg5OTggMTUuMTY2MSA5NS40NzQ5QzE1LjkyIDk1LjA0MzIgMTYuODI4IDk0LjgyNzMgMTcuODkwMiA5NC44MjczQzE4Ljg5MDggOTQuODI3MyAxOS43NDA1IDk0Ljk5NTIgMjAuNDM5NiA5NS4zMzFDMjEuMTM4NiA5NS42NjY4IDIxLjY2OTcgOTYuMTQzMSAyMi4wMzI5IDk2Ljc1OTlDMjIuNDAzIDk3LjM3NjcgMjIuNTg4IDk4LjEwNjUgMjIuNTg4IDk4Ljk0OTRDMjIuNTg4IDk5LjQxNTQgMjIuNTEyNiA5OS44NzggMjIuMzYxOCAxMDAuMzM3QzIyLjIxMTEgMTAwLjc5NiAyMS45OTUyIDEwMS4yNTUgMjEuNzE0MiAxMDEuNzE1QzIxLjQ0MDEgMTAyLjE2NyAyMS4xMTQ2IDEwMi42MjMgMjAuNzM3NyAxMDMuMDgyQzIwLjM2MDcgMTAzLjUzNCAxOS45NDYxIDEwMy45OTMgMTkuNDkzOCAxMDQuNDU5TDE2LjI1NTggMTA4LjAyNkgyMy4xNjM2Wk0zNS4xMjkxIDEwOC4wMjZWMTEwSDI1LjA5NjJWMTA4LjMwNEwyOS45Njg3IDEwMi45ODlDMzAuNTAzMyAxMDIuMzg2IDMwLjkyNDcgMTAxLjg2NSAzMS4yMzMxIDEwMS40MjdDMzEuNTQxNSAxMDAuOTg4IDMxLjc1NzQgMTAwLjU5NCAzMS44ODA3IDEwMC4yNDVDMzIuMDExIDk5Ljg4ODMgMzIuMDc2MSA5OS41NDIyIDMyLjA3NjEgOTkuMjA2NEMzMi4wNzYxIDk4LjczMzYgMzEuOTg3IDk4LjMxODkgMzEuODA4OCA5Ny45NjI2QzMxLjYzNzUgOTcuNTk5NCAzMS4zODM5IDk3LjMxNSAzMS4wNDgxIDk3LjEwOTRDMzAuNzEyMyA5Ni44OTY5IDMwLjMwNDUgOTYuNzkwNyAyOS44MjQ4IDk2Ljc5MDdDMjkuMjY5NyA5Ni43OTA3IDI4LjgwMzcgOTYuOTEwNiAyOC40MjY4IDk3LjE1MDVDMjguMDQ5OSA5Ny4zOTA0IDI3Ljc2NTUgOTcuNzIyNyAyNy41NzM2IDk4LjE0NzZDMjcuMzgxNyA5OC41NjU3IDI3LjI4NTggOTkuMDQ1NCAyNy4yODU4IDk5LjU4NjhIMjQuODA4NEMyNC44MDg0IDk4LjcxNjQgMjUuMDA3MSA5Ny45MjE1IDI1LjQwNDYgOTcuMjAxOUMyNS44MDIxIDk2LjQ3NTUgMjYuMzc3NyA5NS44OTk4IDI3LjEzMTYgOTUuNDc0OUMyNy44ODU0IDk1LjA0MzIgMjguNzkzNCA5NC44MjczIDI5Ljg1NTcgOTQuODI3M0MzMC44NTYyIDk0LjgyNzMgMzEuNzA2IDk0Ljk5NTIgMzIuNDA1IDk1LjMzMUMzMy4xMDQgOTUuNjY2OCAzMy42MzUxIDk2LjE0MzEgMzMuOTk4MyA5Ni43NTk5QzM0LjM2ODQgOTcuMzc2NyAzNC41NTM0IDk4LjEwNjUgMzQuNTUzNCA5OC45NDk0QzM0LjU1MzQgOTkuNDE1NCAzNC40NzgxIDk5Ljg3OCAzNC4zMjczIDEwMC4zMzdDMzQuMTc2NSAxMDAuNzk2IDMzLjk2MDYgMTAxLjI1NSAzMy42Nzk3IDEwMS43MTVDMzMuNDA1NiAxMDIuMTY3IDMzLjA4IDEwMi42MjMgMzIuNzAzMSAxMDMuMDgyQzMyLjMyNjIgMTAzLjUzNCAzMS45MTE2IDEwMy45OTMgMzEuNDU5MyAxMDQuNDU5TDI4LjIyMTIgMTA4LjAyNkgzNS4xMjkxWiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC44NyIvPgo8cGF0aCBkPSJNNDEuMzI2MSA5Ny41NzJDNDEuMzI2MSA5Ny4wNzE3IDQxLjQ0OTQgOTYuNjEyNSA0MS42OTYxIDk2LjE5NDVDNDEuOTQyOCA5NS43NzY1IDQyLjI3MTggOTUuNDQ0MSA0Mi42ODMgOTUuMTk3NEM0My4xMDEgOTQuOTQzOCA0My41NTMzIDk0LjgxNyA0NC4wMzk5IDk0LjgxN0M0NC41MzMzIDk0LjgxNyA0NC45ODIyIDk0Ljk0MzggNDUuMzg2NSA5NS4xOTc0QzQ1Ljc5MDggOTUuNDQ0MSA0Ni4xMTI5IDk1Ljc3NjUgNDYuMzUyOCA5Ni4xOTQ1QzQ2LjU5OTUgOTYuNjEyNSA0Ni43MjI5IDk3LjA3MTcgNDYuNzIyOSA5Ny41NzJDNDYuNzIyOSA5OC4wNzIyIDQ2LjU5OTUgOTguNTMxNCA0Ni4zNTI4IDk4Ljk0OTRDNDYuMTEyOSA5OS4zNjA2IDQ1Ljc5MDggOTkuNjg2MSA0NS4zODY1IDk5LjkyNkM0NC45ODIyIDEwMC4xNjYgNDQuNTMzMyAxMDAuMjg2IDQ0LjAzOTkgMTAwLjI4NkM0My41NTMzIDEwMC4yODYgNDMuMTAxIDEwMC4xNjYgNDIuNjgzIDk5LjkyNkM0Mi4yNzE4IDk5LjY4NjEgNDEuOTQyOCA5OS4zNjA2IDQxLjY5NjEgOTguOTQ5NEM0MS40NDk0IDk4LjUzMTQgNDEuMzI2MSA5OC4wNzIyIDQxLjMyNjEgOTcuNTcyWk00Mi43MTM4IDk3LjU3MkM0Mi43MTM4IDk3Ljk0MiA0Mi44NDQgOTguMjUzOCA0My4xMDQ0IDk4LjUwNzRDNDMuMzY0OSA5OC43NTQxIDQzLjY3NjcgOTguODc3NSA0NC4wMzk5IDk4Ljg3NzVDNDQuNDAzMSA5OC44Nzc1IDQ0LjcwODEgOTguNzU0MSA0NC45NTQ4IDk4LjUwNzRDNDUuMjAxNSA5OC4yNjA3IDQ1LjMyNDggOTcuOTQ4OSA0NS4zMjQ4IDk3LjU3MkM0NS4zMjQ4IDk3LjE4ODIgNDUuMjAxNSA5Ni44Njk1IDQ0Ljk1NDggOTYuNjE2QzQ0LjcwODEgOTYuMzYyNCA0NC40MDMxIDk2LjIzNTYgNDQuMDM5OSA5Ni4yMzU2QzQzLjY3NjcgOTYuMjM1NiA0My4zNjQ5IDk2LjM2MjQgNDMuMTA0NCA5Ni42MTZDNDIuODQ0IDk2Ljg2OTUgNDIuNzEzOCA5Ny4xODgyIDQyLjcxMzggOTcuNTcyWk01OC40MjEgMTA1LjEyN0g2MC45OTA5QzYwLjkwODcgMTA2LjEwNyA2MC42MzQ2IDEwNi45ODEgNjAuMTY4NiAxMDcuNzQ5QzU5LjcwMjYgMTA4LjUwOSA1OS4wNDgxIDEwOS4xMDkgNTguMjA1MiAxMDkuNTQ4QzU3LjM2MjIgMTA5Ljk4NiA1Ni4zMzc3IDExMC4yMDYgNTUuMTMxNiAxMTAuMjA2QzU0LjIwNjQgMTEwLjIwNiA1My4zNzM4IDExMC4wNDEgNTIuNjMzNiAxMDkuNzEyQzUxLjg5MzUgMTA5LjM3NiA1MS4yNTk2IDEwOC45MDQgNTAuNzMxOSAxMDguMjk0QzUwLjIwNDIgMTA3LjY3NyA0OS43OTk5IDEwNi45MzMgNDkuNTE4OSAxMDYuMDYzQzQ5LjI0NDggMTA1LjE5MyA0OS4xMDc3IDEwNC4yMTkgNDkuMTA3NyAxMDMuMTQ0VjEwMS45QzQ5LjEwNzcgMTAwLjgyNCA0OS4yNDgyIDk5Ljg1MDYgNDkuNTI5MiA5OC45ODAzQzQ5LjgxNyA5OC4xMDk5IDUwLjIyODIgOTcuMzY2NCA1MC43NjI3IDk2Ljc0OTZDNTEuMjk3MyA5Ni4xMjYgNTEuOTM4IDk1LjY0OTcgNTIuNjg1IDk1LjMyMDdDNTMuNDM4OSA5NC45OTE4IDU0LjI4NTIgOTQuODI3MyA1NS4yMjQxIDk0LjgyNzNDNTYuNDE2NSA5NC44MjczIDU3LjQyMzkgOTUuMDQ2NiA1OC4yNDYzIDk1LjQ4NTJDNTkuMDY4NyA5NS45MjM4IDU5LjcwNiA5Ni41MzAzIDYwLjE1ODMgOTcuMzA0N0M2MC42MTc0IDk4LjA3OTEgNjAuODk4NCA5OC45NjY2IDYxLjAwMTIgOTkuOTY3MUg1OC40MzEzQzU4LjM2MjggOTkuMzIyOSA1OC4yMTIgOTguNzcxMyA1Ny45NzkgOTguMzEyMUM1Ny43NTI5IDk3Ljg1MjkgNTcuNDE3MSA5Ny41MDM0IDU2Ljk3MTYgOTcuMjYzNkM1Ni41MjYyIDk3LjAxNjkgNTUuOTQzNyA5Ni44OTM1IDU1LjIyNDEgOTYuODkzNUM1NC42MzQ3IDk2Ljg5MzUgNTQuMTIwNyA5Ny4wMDMyIDUzLjY4MjEgOTcuMjIyNUM1My4yNDM1IDk3LjQ0MTggNTIuODc2OSA5Ny43NjM5IDUyLjU4MjIgOTguMTg4N0M1Mi4yODc1IDk4LjYxMzYgNTIuMDY0OCA5OS4xMzc5IDUxLjkxNDEgOTkuNzYxNUM1MS43NzAxIDEwMC4zNzggNTEuNjk4MiAxMDEuMDg0IDUxLjY5ODIgMTAxLjg3OVYxMDMuMTQ0QzUxLjY5ODIgMTAzLjg5NyA1MS43NjMzIDEwNC41ODMgNTEuODkzNSAxMDUuMTk5QzUyLjAzMDYgMTA1LjgwOSA1Mi4yMzYxIDEwNi4zMzQgNTIuNTEwMyAxMDYuNzcyQzUyLjc5MTIgMTA3LjIxMSA1My4xNDc2IDEwNy41NSA1My41NzkzIDEwNy43OUM1NC4wMTExIDEwOC4wMyA1NC41Mjg1IDEwOC4xNSA1NS4xMzE2IDEwOC4xNUM1NS44NjQ4IDEwOC4xNSA1Ni40NTc2IDEwOC4wMzMgNTYuOTA5OSAxMDcuOEM1Ny4zNjkxIDEwNy41NjcgNTcuNzE1MiAxMDcuMjI4IDU3Ljk0ODIgMTA2Ljc4MkM1OC4xODggMTA2LjMzIDU4LjM0NTcgMTA1Ljc3OSA1OC40MjEgMTA1LjEyN1oiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPHBhdGggZD0iTTY4Ljc4MiAxMTEuMTQ4TDc5LjA3MzIgMTA0Ljg3NkM4MC43NjQ3IDEwMy44NDUgODIuMDcgMTAyLjI4NiA4Mi43ODc1IDEwMC40NEw4NS44OTA4IDkyLjQ1NDVDODguMjQ5NSA4Ni4zODQ5IDk2LjgzNzkgODYuMzg0OSA5OS4xOTY3IDkyLjQ1NDVWOTIuNDU0NUMxMDEuMjczIDk3Ljc5NzkgMTA4LjQ4MyA5OC42MzY4IDExMS43MzEgOTMuOTEzTDExNS40MDMgODguNTcyNUMxMTcuNjY3IDg1LjI3OTIgMTIyLjMyOCA4NC43NjUgMTI1LjI1NiA4Ny40ODU2Vjg3LjQ4NTZDMTI4LjUzMyA5MC41MzA0IDEzMy44MyA4OS40NjY1IDEzNS42NzcgODUuMzkyN0wxMzguNTAxIDc5LjE2MzFDMTQxLjYxMiA3Mi4zMDIxIDE1MS4yNjQgNzIuMDQ5MSAxNTQuNzMgNzguNzM3OUwxNjAuMTcgODkuMjM3QzE2Mi4yNjEgOTMuMjcyOCAxNjcuMDI4IDk1LjEyMjkgMTcxLjI5NCA5My41NTQ2TDE3NC42MTIgOTIuMzM0OEMxNzYuODM4IDkxLjUxNjYgMTc5LjI5NyA5MS42MDc1IDE4MS40NTYgOTIuNTg4MUwxODkuNjI3IDk2LjI5ODgiIHN0cm9rZT0iIzNGNTJERCIgc3Ryb2tlLXdpZHRoPSIxLjUwMzc2IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4KPC9zdmc+Cg==", "description": "Displays a single entity historical telemetry values as a simplified chart. Optionally may display the corresponding latest telemetry value.", "descriptor": { "type": "timeseries", @@ -22,6 +22,5 @@ "basicModeDirective": "tb-value-chart-card-basic-config", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"rgb(63, 82, 221)\",\"settings\":{},\"_hash\":0.8587686344902596,\"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}],\"latestDataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Latest\",\"color\":\"rgb(63, 82, 221)\",\"settings\":{},\"_hash\":0.8587686344902596,\"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}]}],\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":null,\"padding\":\"0\",\"settings\":{\"layout\":\"left\",\"autoScale\":true,\"showValue\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":28,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"32px\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Simple Value and chart card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"iconSize\":\"18px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"useDashboardTimewindow\":true,\"decimals\":0,\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"borderRadius\":null,\"units\":\"°C\",\"displayTimewindow\":true,\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":1,\"history\":{\"historyType\":2,\"timewindowMs\":60000,\"interval\":43200000,\"fixedTimewindow\":{\"startTimeMs\":1697382151041,\"endTimeMs\":1697468551041},\"quickInterval\":\"CURRENT_MONTH_SO_FAR\"},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000}},\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true}}" }, - "externalId": null, "tags": null } \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/slider.json b/application/src/main/data/json/system/widget_types/slider.json new file mode 100644 index 0000000000..8cbf5326c3 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/slider.json @@ -0,0 +1,39 @@ +{ + "fqn": "slider", + "name": "Slider", + "deprecated": false, + "image": "tb-image:SG9yaXpvbnRhbCBzbGlkZXIuc3Zn:IlNsaWRlciIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik05MC41MDY0IDYwLjkxOFY2My4xNjhINzguMjAxN0w3OC4xMDggNjEuNDY4OEw4NS40NjczIDQ5LjkzNzVIODcuNzI5MUw4NS4yNzk4IDU0LjEzMjhMODEuMDQ5NCA2MC45MThIOTAuNTA2NFpNODguMzczNiA0OS45Mzc1VjY3SDg1LjU0OTRWNDkuOTM3NUg4OC4zNzM2Wk0xMDMuNjA5IDYyLjM0NzdDMTAzLjYwOSA2My40MTAyIDEwMy4zNjMgNjQuMzA0NyAxMDIuODcxIDY1LjAzMTJDMTAyLjM3OSA2NS43NTc4IDEwMS43MDcgNjYuMzA4NiAxMDAuODU1IDY2LjY4MzZDMTAwLjAxMiA2Ny4wNTA4IDk5LjA1ODYgNjcuMjM0NCA5Ny45OTYxIDY3LjIzNDRDOTYuOTMzNiA2Ny4yMzQ0IDk1Ljk3NjYgNjcuMDUwOCA5NS4xMjUgNjYuNjgzNkM5NC4yNzM0IDY2LjMwODYgOTMuNjAxNiA2NS43NTc4IDkzLjEwOTQgNjUuMDMxMkM5Mi42MTcyIDY0LjMwNDcgOTIuMzcxMSA2My40MTAyIDkyLjM3MTEgNjIuMzQ3N0M5Mi4zNzExIDYxLjY0NDUgOTIuNTA3OCA2MS4wMDc4IDkyLjc4MTMgNjAuNDM3NUM5My4wNTQ3IDU5Ljg1OTQgOTMuNDQxNCA1OS4zNjMzIDkzLjk0MTQgNTguOTQ5MkM5NC40NDkyIDU4LjUyNzMgOTUuMDQzIDU4LjIwMzEgOTUuNzIyNyA1Ny45NzY2Qzk2LjQxMDIgNTcuNzUgOTcuMTYwMiA1Ny42MzY3IDk3Ljk3MjcgNTcuNjM2N0M5OS4wNTA4IDU3LjYzNjcgMTAwLjAxNiA1Ny44MzU5IDEwMC44NjcgNTguMjM0NEMxMDEuNzE5IDU4LjYzMjggMTAyLjM4NyA1OS4xODM2IDEwMi44NzEgNTkuODg2N0MxMDMuMzYzIDYwLjU4OTggMTAzLjYwOSA2MS40MTAyIDEwMy42MDkgNjIuMzQ3N1pNMTAwLjc3MyA2Mi4yMDdDMTAwLjc3MyA2MS42MzY3IDEwMC42NTYgNjEuMTM2NyAxMDAuNDIyIDYwLjcwN0MxMDAuMTg4IDYwLjI3NzMgOTkuODU5NCA1OS45NDUzIDk5LjQzNzUgNTkuNzEwOUM5OS4wMTU2IDU5LjQ3NjYgOTguNTI3MyA1OS4zNTk0IDk3Ljk3MjcgNTkuMzU5NEM5Ny40MTAyIDU5LjM1OTQgOTYuOTIxOSA1OS40NzY2IDk2LjUwNzggNTkuNzEwOUM5Ni4wOTM4IDU5Ljk0NTMgOTUuNzY5NSA2MC4yNzczIDk1LjUzNTIgNjAuNzA3Qzk1LjMwODYgNjEuMTM2NyA5NS4xOTUzIDYxLjYzNjcgOTUuMTk1MyA2Mi4yMDdDOTUuMTk1MyA2Mi43ODUyIDk1LjMwODYgNjMuMjg1MiA5NS41MzUyIDYzLjcwN0M5NS43NjE3IDY0LjEyMTEgOTYuMDg1OSA2NC40Mzc1IDk2LjUwNzggNjQuNjU2MkM5Ni45Mjk3IDY0Ljg3NSA5Ny40MjU4IDY0Ljk4NDQgOTcuOTk2MSA2NC45ODQ0Qzk4LjU2NjQgNjQuOTg0NCA5OS4wNTg2IDY0Ljg3NSA5OS40NzI3IDY0LjY1NjJDOTkuODg2NyA2NC40Mzc1IDEwMC4yMDcgNjQuMTIxMSAxMDAuNDM0IDYzLjcwN0MxMDAuNjYgNjMuMjg1MiAxMDAuNzczIDYyLjc4NTIgMTAwLjc3MyA2Mi4yMDdaTTEwMy4yMjMgNTQuNDI1OEMxMDMuMjIzIDU1LjI3NzMgMTAyLjk5NiA1Ni4wMzUyIDEwMi41NDMgNTYuNjk5MkMxMDIuMDk4IDU3LjM2MzMgMTAxLjQ4IDU3Ljg4NjcgMTAwLjY5MSA1OC4yNjk1Qzk5LjkwMjMgNTguNjQ0NSA5OS4wMDM5IDU4LjgzMiA5Ny45OTYxIDU4LjgzMkM5Ni45ODA1IDU4LjgzMiA5Ni4wNzQyIDU4LjY0NDUgOTUuMjc3MyA1OC4yNjk1Qzk0LjQ4ODMgNTcuODg2NyA5My44NjcyIDU3LjM2MzMgOTMuNDE0MSA1Ni42OTkyQzkyLjk2ODggNTYuMDM1MiA5Mi43NDYxIDU1LjI3NzMgOTIuNzQ2MSA1NC40MjU4QzkyLjc0NjEgNTMuNDEwMiA5Mi45Njg4IDUyLjU1NDcgOTMuNDE0MSA1MS44NTk0QzkzLjg2NzIgNTEuMTU2MiA5NC40ODgzIDUwLjYyMTEgOTUuMjc3MyA1MC4yNTM5Qzk2LjA2NjQgNDkuODg2NyA5Ni45Njg4IDQ5LjcwMzEgOTcuOTg0NCA0OS43MDMxQzk5IDQ5LjcwMzEgOTkuOTAyMyA0OS44ODY3IDEwMC42OTEgNTAuMjUzOUMxMDEuNDggNTAuNjIxMSAxMDIuMDk4IDUxLjE1NjIgMTAyLjU0MyA1MS44NTk0QzEwMi45OTYgNTIuNTU0NyAxMDMuMjIzIDUzLjQxMDIgMTAzLjIyMyA1NC40MjU4Wk0xMDAuMzk4IDU0LjUxOTVDMTAwLjM5OCA1NC4wMTE3IDEwMC4yOTcgNTMuNTY2NCAxMDAuMDk0IDUzLjE4MzZDOTkuODk4NCA1Mi43OTMgOTkuNjIxMSA1Mi40ODgzIDk5LjI2MTcgNTIuMjY5NUM5OC45MDIzIDUyLjA1MDggOTguNDc2NiA1MS45NDE0IDk3Ljk4NDQgNTEuOTQxNEM5Ny40OTIyIDUxLjk0MTQgOTcuMDY2NCA1Mi4wNDY5IDk2LjcwNyA1Mi4yNTc4Qzk2LjM0NzcgNTIuNDY4OCA5Ni4wNzAzIDUyLjc2NTYgOTUuODc1IDUzLjE0ODRDOTUuNjc5NyA1My41MzEyIDk1LjU4MiA1My45ODgzIDk1LjU4MiA1NC41MTk1Qzk1LjU4MiA1NS4wNDMgOTUuNjc5NyA1NS41IDk1Ljg3NSA1NS44OTA2Qzk2LjA3MDMgNTYuMjczNCA5Ni4zNDc3IDU2LjU3NDIgOTYuNzA3IDU2Ljc5M0M5Ny4wNzQyIDU3LjAxMTcgOTcuNTAzOSA1Ny4xMjExIDk3Ljk5NjEgNTcuMTIxMUM5OC40ODgzIDU3LjEyMTEgOTguOTE0MSA1Ny4wMTE3IDk5LjI3MzQgNTYuNzkzQzk5LjYzMjggNTYuNTc0MiA5OS45MTAyIDU2LjI3MzQgMTAwLjEwNSA1NS44OTA2QzEwMC4zMDEgNTUuNSAxMDAuMzk4IDU1LjA0MyAxMDAuMzk4IDU0LjUxOTVaTTEwNi4wMzcgNTQuMTIxMVY1My4yMTg4QzEwNi4wMzcgNTIuNTcwMyAxMDYuMTc3IDUxLjk4MDUgMTA2LjQ1OCA1MS40NDkyQzEwNi43NCA1MC45MTggMTA3LjE1IDUwLjQ5MjIgMTA3LjY4OSA1MC4xNzE5QzEwOC4yMjggNDkuODUxNiAxMDguODc2IDQ5LjY5MTQgMTA5LjYzNCA0OS42OTE0QzExMC40MTUgNDkuNjkxNCAxMTEuMDcyIDQ5Ljg1MTYgMTExLjYwMyA1MC4xNzE5QzExMi4xNDIgNTAuNDkyMiAxMTIuNTUyIDUwLjkxOCAxMTIuODMzIDUxLjQ0OTJDMTEzLjExNSA1MS45ODA1IDExMy4yNTUgNTIuNTcwMyAxMTMuMjU1IDUzLjIxODhWNTQuMTIxMUMxMTMuMjU1IDU0Ljc1MzkgMTEzLjExNSA1NS4zMzU5IDExMi44MzMgNTUuODY3MkMxMTIuNTYgNTYuMzk4NCAxMTIuMTU0IDU2LjgyNDIgMTExLjYxNSA1Ny4xNDQ1QzExMS4wODMgNTcuNDY0OCAxMTAuNDMxIDU3LjYyNSAxMDkuNjU4IDU3LjYyNUMxMDguODkyIDU3LjYyNSAxMDguMjM2IDU3LjQ2NDggMTA3LjY4OSA1Ny4xNDQ1QzEwNy4xNSA1Ni44MjQyIDEwNi43NCA1Ni4zOTg0IDEwNi40NTggNTUuODY3MkMxMDYuMTc3IDU1LjMzNTkgMTA2LjAzNyA1NC43NTM5IDEwNi4wMzcgNTQuMTIxMVpNMTA3Ljk5NCA1My4yMTg4VjU0LjEyMTFDMTA3Ljk5NCA1NC40MzM2IDEwOC4wNTIgNTQuNzMwNSAxMDguMTY5IDU1LjAxMTdDMTA4LjI5NCA1NS4yOTMgMTA4LjQ4MiA1NS41MTk1IDEwOC43MzIgNTUuNjkxNEMxMDguOTgyIDU1Ljg2MzMgMTA5LjI5IDU1Ljk0OTIgMTA5LjY1OCA1NS45NDkyQzExMC4wMzMgNTUuOTQ5MiAxMTAuMzM3IDU1Ljg2MzMgMTEwLjU3MiA1NS42OTE0QzExMC44MTQgNTUuNTE5NSAxMTAuOTk0IDU1LjI5MyAxMTEuMTExIDU1LjAxMTdDMTExLjIyOCA1NC43MzA1IDExMS4yODcgNTQuNDMzNiAxMTEuMjg3IDU0LjEyMTFWNTMuMjE4OEMxMTEuMjg3IDUyLjg5ODQgMTExLjIyNCA1Mi41OTc3IDExMS4wOTkgNTIuMzE2NEMxMTAuOTgyIDUyLjAyNzMgMTEwLjgwMiA1MS43OTY5IDExMC41NiA1MS42MjVDMTEwLjMxOCA1MS40NTMxIDExMC4wMDkgNTEuMzY3MiAxMDkuNjM0IDUxLjM2NzJDMTA5LjI3NSA1MS4zNjcyIDEwOC45NyA1MS40NTMxIDEwOC43MiA1MS42MjVDMTA4LjQ3OCA1MS43OTY5IDEwOC4yOTQgNTIuMDI3MyAxMDguMTY5IDUyLjMxNjRDMTA4LjA1MiA1Mi41OTc3IDEwNy45OTQgNTIuODk4NCAxMDcuOTk0IDUzLjIxODhaTTExNC4yNjMgNjMuNzMwNVY2Mi44MTY0QzExNC4yNjMgNjIuMTc1OCAxMTQuNDA0IDYxLjU4OTggMTE0LjY4NSA2MS4wNTg2QzExNC45NzQgNjAuNTI3MyAxMTUuMzg4IDYwLjEwMTYgMTE1LjkyNyA1OS43ODEyQzExNi40NjYgNTkuNDYwOSAxMTcuMTE1IDU5LjMwMDggMTE3Ljg3MyA1OS4zMDA4QzExOC42NTQgNTkuMzAwOCAxMTkuMzEgNTkuNDYwOSAxMTkuODQxIDU5Ljc4MTJDMTIwLjM4IDYwLjEwMTYgMTIwLjc4NyA2MC41MjczIDEyMS4wNiA2MS4wNTg2QzEyMS4zNDEgNjEuNTg5OCAxMjEuNDgyIDYyLjE3NTggMTIxLjQ4MiA2Mi44MTY0VjYzLjczMDVDMTIxLjQ4MiA2NC4zNzExIDEyMS4zNDEgNjQuOTU3IDEyMS4wNiA2NS40ODgzQzEyMC43ODcgNjYuMDE5NSAxMjAuMzg0IDY2LjQ0NTMgMTE5Ljg1MyA2Ni43NjU2QzExOS4zMjIgNjcuMDg1OSAxMTguNjczIDY3LjI0NjEgMTE3LjkwOCA2Ny4yNDYxQzExNy4xMzQgNjcuMjQ2MSAxMTYuNDc0IDY3LjA4NTkgMTE1LjkyNyA2Ni43NjU2QzExNS4zODggNjYuNDQ1MyAxMTQuOTc0IDY2LjAxOTUgMTE0LjY4NSA2NS40ODgzQzExNC40MDQgNjQuOTU3IDExNC4yNjMgNjQuMzcxMSAxMTQuMjYzIDYzLjczMDVaTTExNi4yMzIgNjIuODE2NFY2My43MzA1QzExNi4yMzIgNjQuMDQzIDExNi4yOTQgNjQuMzM5OCAxMTYuNDE5IDY0LjYyMTFDMTE2LjU1MiA2NC45MDIzIDExNi43NDQgNjUuMTMyOCAxMTYuOTk0IDY1LjMxMjVDMTE3LjI0NCA2NS40ODQ0IDExNy41NDQgNjUuNTcwMyAxMTcuODk2IDY1LjU3MDNDMTE4LjI5NCA2NS41NzAzIDExOC42MTUgNjUuNDg0NCAxMTguODU3IDY1LjMxMjVDMTE5LjA5OSA2NS4xMzI4IDExOS4yNzEgNjQuOTA2MiAxMTkuMzczIDY0LjYzMjhDMTE5LjQ4MiA2NC4zNTE2IDExOS41MzcgNjQuMDUwOCAxMTkuNTM3IDYzLjczMDVWNjIuODE2NEMxMTkuNTM3IDYyLjQ5NjEgMTE5LjQ3NCA2Mi4xOTUzIDExOS4zNDkgNjEuOTE0MUMxMTkuMjMyIDYxLjYzMjggMTE5LjA0OCA2MS40MDYyIDExOC43OTggNjEuMjM0NEMxMTguNTU2IDYxLjA2MjUgMTE4LjI0OCA2MC45NzY2IDExNy44NzMgNjAuOTc2NkMxMTcuNTA1IDYwLjk3NjYgMTE3LjIwMSA2MS4wNjI1IDExNi45NTggNjEuMjM0NEMxMTYuNzE2IDYxLjQwNjIgMTE2LjUzMyA2MS42MzI4IDExNi40MDggNjEuOTE0MUMxMTYuMjkgNjIuMTk1MyAxMTYuMjMyIDYyLjQ5NjEgMTE2LjIzMiA2Mi44MTY0Wk0xMTguNTc2IDUyLjM3NUwxMTAuMjQ0IDY1LjcxMDlMMTA4LjgwMiA2NC44Nzg5TDExNy4xMzQgNTEuNTQzTDExOC41NzYgNTIuMzc1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC44NyIvPgo8cmVjdCB5PSI3OSIgd2lkdGg9IjIwMCIgaGVpZ2h0PSI4IiByeD0iMi43MDE3NCIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjMiLz4KPGNpcmNsZSBjeD0iMi45MTk0NiIgY3k9IjgzIiByPSIwLjU2ODg3OCIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjE4LjI1MjUiIGN5PSI4MyIgcj0iMC41Njg4NzgiIGZpbGw9IiM1NDY5RkYiIGZpbGwtb3BhY2l0eT0iMC44MiIvPgo8Y2lyY2xlIGN4PSIzMy41ODQ1IiBjeT0iODMiIHI9IjAuNTY4ODc4IiBmaWxsPSIjNTQ2OUZGIiBmaWxsLW9wYWNpdHk9IjAuODIiLz4KPGNpcmNsZSBjeD0iNDguOTE2NSIgY3k9IjgzIiByPSIwLjU2ODg3OCIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjY0LjI0ODYiIGN5PSI4MyIgcj0iMC41Njg4NzgiIGZpbGw9IiM1NDY5RkYiIGZpbGwtb3BhY2l0eT0iMC44MiIvPgo8Y2lyY2xlIGN4PSI3OS41ODA2IiBjeT0iODMiIHI9IjAuNTY4ODc4IiBmaWxsPSIjNTQ2OUZGIiBmaWxsLW9wYWNpdHk9IjAuODIiLz4KPGNpcmNsZSBjeD0iOTQuOTEzNiIgY3k9IjgzIiByPSIwLjU2ODg3OCIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjExMC42NzciIGN5PSI4MyIgcj0iMSIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjEyNi44NzEiIGN5PSI4MyIgcj0iMSIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjE0My4wNjUiIGN5PSI4MyIgcj0iMSIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjE1OS4yNiIgY3k9IjgzIiByPSIxIiBmaWxsPSIjNTQ2OUZGIiBmaWxsLW9wYWNpdHk9IjAuODIiLz4KPGNpcmNsZSBjeD0iMTc1LjQ1NSIgY3k9IjgzIiByPSIxIiBmaWxsPSIjNTQ2OUZGIiBmaWxsLW9wYWNpdHk9IjAuODIiLz4KPGNpcmNsZSBjeD0iMTkxLjY0OSIgY3k9IjgzIiByPSIxIiBmaWxsPSIjNTQ2OUZGIiBmaWxsLW9wYWNpdHk9IjAuODIiLz4KPHJlY3QgeT0iNzkiIHdpZHRoPSIxMDEiIGhlaWdodD0iOCIgcng9IjIuNzAxNzQiIGZpbGw9IiM1NDY5RkYiLz4KPHBhdGggZD0iTTUuNTU5MDggOTUuNDY2OFY5Ni42NTkyQzUuNTU5MDggOTcuMzAwMSA1LjUwMTc5IDk3Ljg0MDggNS4zODcyMSA5OC4yODEyQzUuMjcyNjIgOTguNzIxNyA1LjEwNzkxIDk5LjA3NjIgNC44OTMwNyA5OS4zNDQ3QzQuNjc4MjIgOTkuNjEzMyA0LjQxODYyIDk5LjgwODQgNC4xMTQyNiA5OS45MzAyQzMuODEzNDggMTAwLjA0OCAzLjQ3MzMxIDEwMC4xMDcgMy4wOTM3NSAxMDAuMTA3QzIuNzkyOTcgMTAwLjEwNyAyLjUxNTQ2IDEwMC4wNyAyLjI2MTIzIDk5Ljk5NDZDMi4wMDcgOTkuOTE5NCAxLjc3NzgzIDk5Ljc5OTUgMS41NzM3MyA5OS42MzQ4QzEuMzczMjEgOTkuNDY2NSAxLjIwMTMzIDk5LjI0OCAxLjA1ODExIDk4Ljk3OTVDMC45MTQ4NzYgOTguNzEwOSAwLjgwNTY2NCA5OC4zODUxIDAuNzMwNDY5IDk4LjAwMkMwLjY1NTI3MyA5Ny42MTg4IDAuNjE3Njc2IDk3LjE3MTIgMC42MTc2NzYgOTYuNjU5MlY5NS40NjY4QzAuNjE3Njc2IDk0LjgyNTggMC42NzQ5NjcgOTQuMjg4NyAwLjc4OTU1MSA5My44NTU1QzAuOTA3NzE1IDkzLjQyMjIgMS4wNzQyMiA5My4wNzQ5IDEuMjg5MDYgOTIuODEzNUMxLjUwMzkxIDkyLjU0ODUgMS43NjE3MiA5Mi4zNTg3IDIuMDYyNSA5Mi4yNDQxQzIuMzY2ODYgOTIuMTI5NiAyLjcwNzAzIDkyLjA3MjMgMy4wODMwMSA5Mi4wNzIzQzMuMzg3MzcgOTIuMDcyMyAzLjY2NjY3IDkyLjEwOTkgMy45MjA5IDkyLjE4NTFDNC4xNzg3MSA5Mi4yNTY3IDQuNDA3ODggOTIuMzczIDQuNjA4NCA5Mi41MzQyQzQuODA4OTIgOTIuNjkxNyA0Ljk3OSA5Mi45MDMgNS4xMTg2NSA5My4xNjhDNS4yNjE4OCA5My40Mjk0IDUuMzcxMDkgOTMuNzQ5OCA1LjQ0NjI5IDk0LjEyOTRDNS41MjE0OCA5NC41MDkgNS41NTkwOCA5NC45NTQ4IDUuNTU5MDggOTUuNDY2OFpNNC41NjAwNiA5Ni44MjAzVjk1LjMwMDNDNC41NjAwNiA5NC45NDk0IDQuNTM4NTcgOTQuNjQxNCA0LjQ5NTYxIDk0LjM3NjVDNC40NTYyMiA5NC4xMDc5IDQuMzk3MTQgOTMuODc4NyA0LjMxODM2IDkzLjY4OUM0LjIzOTU4IDkzLjQ5OTIgNC4xMzkzMiA5My4zNDUyIDQuMDE3NTggOTMuMjI3MUMzLjg5OTQxIDkzLjEwODkgMy43NjE1NiA5My4wMjI5IDMuNjA0IDkyLjk2OTJDMy40NTAwMyA5Mi45MTE5IDMuMjc2MzcgOTIuODgzMyAzLjA4MzAxIDkyLjg4MzNDMi44NDY2OCA5Mi44ODMzIDIuNjM3MjEgOTIuOTI4MSAyLjQ1NDU5IDkzLjAxNzZDMi4yNzE5NyA5My4xMDM1IDIuMTE4IDkzLjI0MTQgMS45OTI2OCA5My40MzEyQzEuODcwOTMgOTMuNjIwOSAxLjc3NzgzIDkzLjg2OTggMS43MTMzOCA5NC4xNzc3QzEuNjQ4OTMgOTQuNDg1NyAxLjYxNjcgOTQuODU5OSAxLjYxNjcgOTUuMzAwM1Y5Ni44MjAzQzEuNjE2NyA5Ny4xNzEyIDEuNjM2MzkgOTcuNDgxIDEuNjc1NzggOTcuNzQ5NUMxLjcxODc1IDk4LjAxODEgMS43ODE0MSA5OC4yNTA4IDEuODYzNzcgOTguNDQ3OEMxLjk0NjEzIDk4LjY0MTEgMi4wNDYzOSA5OC44MDA1IDIuMTY0NTUgOTguOTI1OEMyLjI4MjcxIDk5LjA1MTEgMi40MTg3OCA5OS4xNDQyIDIuNTcyNzUgOTkuMjA1MUMyLjczMDMxIDk5LjI2MjQgMi45MDM5NyA5OS4yOTEgMy4wOTM3NSA5OS4yOTFDMy4zMzcyNCA5OS4yOTEgMy41NTAyOSA5OS4yNDQ1IDMuNzMyOTEgOTkuMTUxNEMzLjkxNTUzIDk5LjA1ODMgNC4wNjc3MSA5OC45MTMyIDQuMTg5NDUgOTguNzE2M0M0LjMxNDc4IDk4LjUxNTggNC40MDc4OCA5OC4yNTk4IDQuNDY4NzUgOTcuOTQ4MkM0LjUyOTYyIDk3LjYzMzEgNC41NjAwNiA5Ny4yNTcyIDQuNTYwMDYgOTYuODIwM1oiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTE4NC44NTMgOTIuMTM2N1YxMDBIMTgzLjg1OVY5My4zNzc0TDE4MS44NTYgOTQuMTA3OVY5My4yMTA5TDE4NC42OTcgOTIuMTM2N0gxODQuODUzWk0xOTIuOTM0IDk1LjQ2NjhWOTYuNjU5MkMxOTIuOTM0IDk3LjMwMDEgMTkyLjg3NyA5Ny44NDA4IDE5Mi43NjIgOTguMjgxMkMxOTIuNjQ4IDk4LjcyMTcgMTkyLjQ4MyA5OS4wNzYyIDE5Mi4yNjggOTkuMzQ0N0MxOTIuMDUzIDk5LjYxMzMgMTkxLjc5NCA5OS44MDg0IDE5MS40ODkgOTkuOTMwMkMxOTEuMTg4IDEwMC4wNDggMTkwLjg0OCAxMDAuMTA3IDE5MC40NjkgMTAwLjEwN0MxOTAuMTY4IDEwMC4xMDcgMTg5Ljg5IDEwMC4wNyAxODkuNjM2IDk5Ljk5NDZDMTg5LjM4MiA5OS45MTk0IDE4OS4xNTMgOTkuNzk5NSAxODguOTQ5IDk5LjYzNDhDMTg4Ljc0OCA5OS40NjY1IDE4OC41NzYgOTkuMjQ4IDE4OC40MzMgOTguOTc5NUMxODguMjkgOTguNzEwOSAxODguMTgxIDk4LjM4NTEgMTg4LjEwNSA5OC4wMDJDMTg4LjAzIDk3LjYxODggMTg3Ljk5MyA5Ny4xNzEyIDE4Ny45OTMgOTYuNjU5MlY5NS40NjY4QzE4Ny45OTMgOTQuODI1OCAxODguMDUgOTQuMjg4NyAxODguMTY1IDkzLjg1NTVDMTg4LjI4MyA5My40MjIyIDE4OC40NDkgOTMuMDc0OSAxODguNjY0IDkyLjgxMzVDMTg4Ljg3OSA5Mi41NDg1IDE4OS4xMzcgOTIuMzU4NyAxODkuNDM4IDkyLjI0NDFDMTg5Ljc0MiA5Mi4xMjk2IDE5MC4wODIgOTIuMDcyMyAxOTAuNDU4IDkyLjA3MjNDMTkwLjc2MiA5Mi4wNzIzIDE5MS4wNDIgOTIuMTA5OSAxOTEuMjk2IDkyLjE4NTFDMTkxLjU1NCA5Mi4yNTY3IDE5MS43ODMgOTIuMzczIDE5MS45ODMgOTIuNTM0MkMxOTIuMTg0IDkyLjY5MTcgMTkyLjM1NCA5Mi45MDMgMTkyLjQ5NCA5My4xNjhDMTkyLjYzNyA5My40Mjk0IDE5Mi43NDYgOTMuNzQ5OCAxOTIuODIxIDk0LjEyOTRDMTkyLjg5NiA5NC41MDkgMTkyLjkzNCA5NC45NTQ4IDE5Mi45MzQgOTUuNDY2OFpNMTkxLjkzNSA5Ni44MjAzVjk1LjMwMDNDMTkxLjkzNSA5NC45NDk0IDE5MS45MTQgOTQuNjQxNCAxOTEuODcxIDk0LjM3NjVDMTkxLjgzMSA5NC4xMDc5IDE5MS43NzIgOTMuODc4NyAxOTEuNjkzIDkzLjY4OUMxOTEuNjE1IDkzLjQ5OTIgMTkxLjUxNCA5My4zNDUyIDE5MS4zOTMgOTMuMjI3MUMxOTEuMjc0IDkzLjEwODkgMTkxLjEzNyA5My4wMjI5IDE5MC45NzkgOTIuOTY5MkMxOTAuODI1IDkyLjkxMTkgMTkwLjY1MSA5Mi44ODMzIDE5MC40NTggOTIuODgzM0MxOTAuMjIyIDkyLjg4MzMgMTkwLjAxMiA5Mi45MjgxIDE4OS44MyA5My4wMTc2QzE4OS42NDcgOTMuMTAzNSAxODkuNDkzIDkzLjI0MTQgMTg5LjM2OCA5My40MzEyQzE4OS4yNDYgOTMuNjIwOSAxODkuMTUzIDkzLjg2OTggMTg5LjA4OCA5NC4xNzc3QzE4OS4wMjQgOTQuNDg1NyAxODguOTkyIDk0Ljg1OTkgMTg4Ljk5MiA5NS4zMDAzVjk2LjgyMDNDMTg4Ljk5MiA5Ny4xNzEyIDE4OS4wMTEgOTcuNDgxIDE4OS4wNTEgOTcuNzQ5NUMxODkuMDk0IDk4LjAxODEgMTg5LjE1NiA5OC4yNTA4IDE4OS4yMzkgOTguNDQ3OEMxODkuMzIxIDk4LjY0MTEgMTg5LjQyMSA5OC44MDA1IDE4OS41NCA5OC45MjU4QzE4OS42NTggOTkuMDUxMSAxODkuNzk0IDk5LjE0NDIgMTg5Ljk0OCA5OS4yMDUxQzE5MC4xMDUgOTkuMjYyNCAxOTAuMjc5IDk5LjI5MSAxOTAuNDY5IDk5LjI5MUMxOTAuNzEyIDk5LjI5MSAxOTAuOTI1IDk5LjI0NDUgMTkxLjEwOCA5OS4xNTE0QzE5MS4yOTEgOTkuMDU4MyAxOTEuNDQzIDk4LjkxMzIgMTkxLjU2NCA5OC43MTYzQzE5MS42OSA5OC41MTU4IDE5MS43ODMgOTguMjU5OCAxOTEuODQ0IDk3Ljk0ODJDMTkxLjkwNSA5Ny42MzMxIDE5MS45MzUgOTcuMjU3MiAxOTEuOTM1IDk2LjgyMDNaTTE5OS4zNzIgOTUuNDY2OFY5Ni42NTkyQzE5OS4zNzIgOTcuMzAwMSAxOTkuMzE0IDk3Ljg0MDggMTk5LjIgOTguMjgxMkMxOTkuMDg1IDk4LjcyMTcgMTk4LjkyIDk5LjA3NjIgMTk4LjcwNiA5OS4zNDQ3QzE5OC40OTEgOTkuNjEzMyAxOTguMjMxIDk5LjgwODQgMTk3LjkyNyA5OS45MzAyQzE5Ny42MjYgMTAwLjA0OCAxOTcuMjg2IDEwMC4xMDcgMTk2LjkwNiAxMDAuMTA3QzE5Ni42MDUgMTAwLjEwNyAxOTYuMzI4IDEwMC4wNyAxOTYuMDc0IDk5Ljk5NDZDMTk1LjgxOSA5OS45MTk0IDE5NS41OSA5OS43OTk1IDE5NS4zODYgOTkuNjM0OEMxOTUuMTg2IDk5LjQ2NjUgMTk1LjAxNCA5OS4yNDggMTk0Ljg3MSA5OC45Nzk1QzE5NC43MjcgOTguNzEwOSAxOTQuNjE4IDk4LjM4NTEgMTk0LjU0MyA5OC4wMDJDMTk0LjQ2OCA5Ny42MTg4IDE5NC40MyA5Ny4xNzEyIDE5NC40MyA5Ni42NTkyVjk1LjQ2NjhDMTk0LjQzIDk0LjgyNTggMTk0LjQ4NyA5NC4yODg3IDE5NC42MDIgOTMuODU1NUMxOTQuNzIgOTMuNDIyMiAxOTQuODg3IDkzLjA3NDkgMTk1LjEwMiA5Mi44MTM1QzE5NS4zMTYgOTIuNTQ4NSAxOTUuNTc0IDkyLjM1ODcgMTk1Ljg3NSA5Mi4yNDQxQzE5Ni4xNzkgOTIuMTI5NiAxOTYuNTIgOTIuMDcyMyAxOTYuODk2IDkyLjA3MjNDMTk3LjIgOTIuMDcyMyAxOTcuNDc5IDkyLjEwOTkgMTk3LjczMyA5Mi4xODUxQzE5Ny45OTEgOTIuMjU2NyAxOTguMjIgOTIuMzczIDE5OC40MjEgOTIuNTM0MkMxOTguNjIxIDkyLjY5MTcgMTk4Ljc5MiA5Mi45MDMgMTk4LjkzMSA5My4xNjhDMTk5LjA3NCA5My40Mjk0IDE5OS4xODQgOTMuNzQ5OCAxOTkuMjU5IDk0LjEyOTRDMTk5LjMzNCA5NC41MDkgMTk5LjM3MiA5NC45NTQ4IDE5OS4zNzIgOTUuNDY2OFpNMTk4LjM3MyA5Ni44MjAzVjk1LjMwMDNDMTk4LjM3MyA5NC45NDk0IDE5OC4zNTEgOTQuNjQxNCAxOTguMzA4IDk0LjM3NjVDMTk4LjI2OSA5NC4xMDc5IDE5OC4yMSA5My44Nzg3IDE5OC4xMzEgOTMuNjg5QzE5OC4wNTIgOTMuNDk5MiAxOTcuOTUyIDkzLjM0NTIgMTk3LjgzIDkzLjIyNzFDMTk3LjcxMiA5My4xMDg5IDE5Ny41NzQgOTMuMDIyOSAxOTcuNDE3IDkyLjk2OTJDMTk3LjI2MyA5Mi45MTE5IDE5Ny4wODkgOTIuODgzMyAxOTYuODk2IDkyLjg4MzNDMTk2LjY1OSA5Mi44ODMzIDE5Ni40NSA5Mi45MjgxIDE5Ni4yNjcgOTMuMDE3NkMxOTYuMDg0IDkzLjEwMzUgMTk1LjkzMSA5My4yNDE0IDE5NS44MDUgOTMuNDMxMkMxOTUuNjgzIDkzLjYyMDkgMTk1LjU5IDkzLjg2OTggMTk1LjUyNiA5NC4xNzc3QzE5NS40NjEgOTQuNDg1NyAxOTUuNDI5IDk0Ljg1OTkgMTk1LjQyOSA5NS4zMDAzVjk2LjgyMDNDMTk1LjQyOSA5Ny4xNzEyIDE5NS40NDkgOTcuNDgxIDE5NS40ODggOTcuNzQ5NUMxOTUuNTMxIDk4LjAxODEgMTk1LjU5NCA5OC4yNTA4IDE5NS42NzYgOTguNDQ3OEMxOTUuNzU5IDk4LjY0MTEgMTk1Ljg1OSA5OC44MDA1IDE5NS45NzcgOTguOTI1OEMxOTYuMDk1IDk5LjA1MTEgMTk2LjIzMSA5OS4xNDQyIDE5Ni4zODUgOTkuMjA1MUMxOTYuNTQzIDk5LjI2MjQgMTk2LjcxNiA5OS4yOTEgMTk2LjkwNiA5OS4yOTFDMTk3LjE1IDk5LjI5MSAxOTcuMzYzIDk5LjI0NDUgMTk3LjU0NSA5OS4xNTE0QzE5Ny43MjggOTkuMDU4MyAxOTcuODggOTguOTEzMiAxOTguMDAyIDk4LjcxNjNDMTk4LjEyNyA5OC41MTU4IDE5OC4yMiA5OC4yNTk4IDE5OC4yODEgOTcuOTQ4MkMxOTguMzQyIDk3LjYzMzEgMTk4LjM3MyA5Ny4yNTcyIDE5OC4zNzMgOTYuODIwM1oiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iODMiIHI9IjgiIGZpbGw9IiM1NDY5RkYiLz4KPC9zdmc+Cg==", + "description": "Allows users to move thumb of a slider to send commands to devices or update attributes/time-series data. Configurable settings let users define how to retrieve the initial state and specify actions for the value change.", + "descriptor": { + "type": "rpc", + "sizeX": 5, + "sizeY": 3, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '360px',\n previewHeight: '220px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "", + "dataKeySettingsSchema": "{}\n", + "settingsDirective": "tb-slider-widget-settings", + "hasBasicMode": true, + "basicModeDirective": "tb-slider-basic-config", + "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Slider\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"actions\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":null,\"weight\":\"500\",\"style\":null,\"lineHeight\":\"24px\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"configMode\":\"basic\",\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}" + }, + "tags": [ + "command", + "downlink", + "device configuration", + "device control", + "invocation", + "remote method", + "remote function", + "interface", + "subroutine call", + "inter-process communication", + "server request", + "update attribute", + "set attribute", + "add time-series", + "slider" + ] +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java index abdcc5b299..0a9d0deb1d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java @@ -15,6 +15,8 @@ */ package org.thingsboard.rule.engine.geo; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -28,6 +30,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import java.util.Collections; @@ -39,6 +42,11 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.ENTERED; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.INSIDE; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.LEFT; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.OUTSIDE; + /** * Created by ashvayka on 19.01.18. */ @@ -46,15 +54,24 @@ import java.util.concurrent.TimeoutException; @RuleNode( type = ComponentType.ACTION, name = "gps geofencing events", + version = 1, configClazz = TbGpsGeofencingActionNodeConfiguration.class, relationTypes = {"Success", "Entered", "Left", "Inside", "Outside"}, nodeDescription = "Produces incoming messages using GPS based geofencing", - nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns different events based on configuration parameters", + nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns different events based on configuration parameters. " + + "

" + + "If an object with coordinates extracted from incoming message enters the geofence, sends a message with the type Entered. " + + "If an object leaves the geofence, sends a message with the type Left. " + + "If the presence monitoring strategy \"On first message\" is selected, sends messages via rule node connection type Inside or Outside only the first time the geofencing and duration conditions are satisfied; otherwise sends messages via rule node connection type Success. " + + "If the presence monitoring strategy \"On each message\" is selected, sends messages via rule node connection type Inside or Outside every time the geofencing condition is satisfied. " + + "

" + + "Output connections: Entered, Left, Inside, Outside, Success", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeGpsGeofencingConfig" ) public class TbGpsGeofencingActionNode extends AbstractGeofencingNode { + private static final String REPORT_PRESENCE_STATUS_ON_EACH_MESSAGE = "reportPresenceStatusOnEachMessage"; private final Map entityStates = new HashMap<>(); private final Gson gson = new Gson(); private final JsonParser parser = new JsonParser(); @@ -80,25 +97,32 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode (entityState.isInside() ? - TimeUnit.valueOf(config.getMinInsideDurationTimeUnit()).toMillis(config.getMinInsideDuration()) : TimeUnit.valueOf(config.getMinOutsideDurationTimeUnit()).toMillis(config.getMinOutsideDuration()))) { - setStaid(ctx, msg.getOriginator(), entityState); - ctx.tellNext(msg, entityState.isInside() ? "Inside" : "Outside"); - told = true; - } - } + ctx.tellNext(msg, matches ? ENTERED : LEFT); + return; + } + + if (config.isReportPresenceStatusOnEachMessage()) { + ctx.tellNext(msg, entityState.isInside() ? INSIDE : OUTSIDE); + return; } - if (!told) { + + if (entityState.isStayed()) { ctx.tellSuccess(msg); + return; + } + + long stayTime = ts - entityState.getStateSwitchTime(); + if (stayTime > (entityState.isInside() ? + TimeUnit.valueOf(config.getMinInsideDurationTimeUnit()).toMillis(config.getMinInsideDuration()) : + TimeUnit.valueOf(config.getMinOutsideDurationTimeUnit()).toMillis(config.getMinOutsideDuration()))) { + setStaid(ctx, msg.getOriginator(), entityState); + ctx.tellNext(msg, entityState.isInside() ? INSIDE : OUTSIDE); + return; } + + ctx.tellSuccess(msg); } private void switchState(TbContext ctx, EntityId entityId, EntityGeofencingState entityState, boolean matches, long ts) { @@ -127,4 +151,17 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode getConfigClazz() { return TbGpsGeofencingActionNodeConfiguration.class; } + + @Override + public TbPair upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { + boolean hasChanges = false; + if (fromVersion == 0) { + if (!oldConfiguration.has(REPORT_PRESENCE_STATUS_ON_EACH_MESSAGE)) { + hasChanges = true; + ((ObjectNode) oldConfiguration).put(REPORT_PRESENCE_STATUS_ON_EACH_MESSAGE, false); + } + } + return new TbPair<>(hasChanges, oldConfiguration); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java index d0adad8996..04bbab01b7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java @@ -31,6 +31,8 @@ public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilte private String minInsideDurationTimeUnit; private String minOutsideDurationTimeUnit; + private boolean reportPresenceStatusOnEachMessage; + @Override public TbGpsGeofencingActionNodeConfiguration defaultConfiguration() { TbGpsGeofencingActionNodeConfiguration configuration = new TbGpsGeofencingActionNodeConfiguration(); @@ -43,6 +45,7 @@ public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilte configuration.setMinOutsideDurationTimeUnit(TimeUnit.MINUTES.name()); configuration.setMinInsideDuration(1); configuration.setMinOutsideDuration(1); + configuration.setReportPresenceStatusOnEachMessage(true); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/GpsGeofencingEvents.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/GpsGeofencingEvents.java new file mode 100644 index 0000000000..db5ddaf698 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/GpsGeofencingEvents.java @@ -0,0 +1,23 @@ +/** + * 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. + */ +package org.thingsboard.rule.engine.util; + +public class GpsGeofencingEvents { + public static final String ENTERED = "Entered"; + public static final String INSIDE = "Inside"; + public static final String LEFT = "Left"; + public static final String OUTSIDE = "Outside"; +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java new file mode 100644 index 0000000000..804d30d28f --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java @@ -0,0 +1,39 @@ +/** + * 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. + */ +package org.thingsboard.rule.engine.geo; + +import lombok.Data; +import org.thingsboard.server.common.data.id.EntityId; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class GpsGeofencingActionTestCase { + + private EntityId entityId; + private Map entityStates; + private boolean msgInside; + private boolean reportPresenceStatusOnEachMessage; + + public GpsGeofencingActionTestCase(EntityId entityId, boolean msgInside, boolean reportPresenceStatusOnEachMessage, EntityGeofencingState entityGeofencingState) { + this.entityId = entityId; + this.msgInside = msgInside; + this.reportPresenceStatusOnEachMessage = reportPresenceStatusOnEachMessage; + this.entityStates = new HashMap<>(); + this.entityStates.put(entityId, entityGeofencingState); + } +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java new file mode 100644 index 0000000000..5cd7e02c59 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java @@ -0,0 +1,259 @@ +/** + * 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. + */ +package org.thingsboard.rule.engine.geo; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.AbstractRuleNodeUpgradeTest; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.attributes.AttributesService; + +import java.time.Duration; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.ENTERED; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.INSIDE; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.LEFT; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.OUTSIDE; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; + +@ExtendWith(MockitoExtension.class) +class TbGpsGeofencingActionNodeTest extends AbstractRuleNodeUpgradeTest { + + @Mock + private TbContext ctx; + @Mock + private AttributesService attributesService; + private TbGpsGeofencingActionNode node; + + @BeforeEach + void setUp() { + node = spy(new TbGpsGeofencingActionNode()); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + private static Stream givenReportPresenceStatusOnEachMessage_whenOnMsg_thenVerifyOutputMsgType() { + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + long tsNow = System.currentTimeMillis(); + long tsNowMinusMinuteAndMillis = tsNow - Duration.ofMinutes(1).plusMillis(1).toMillis(); + return Stream.of( + // default config with presenceMonitoringStrategyOnEachMessage false and msgInside true + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, + new EntityGeofencingState(false, 0, false)), ENTERED), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, + new EntityGeofencingState(true, tsNow, false)), SUCCESS), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, + new EntityGeofencingState(true, tsNowMinusMinuteAndMillis, false)), INSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, + new EntityGeofencingState(true, tsNow, true)), SUCCESS), + // default config with presenceMonitoringStrategyOnEachMessage false and msgInside false + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, + new EntityGeofencingState(false, 0, false)), LEFT), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, + new EntityGeofencingState(false, tsNow, false)), SUCCESS), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, + new EntityGeofencingState(false, tsNowMinusMinuteAndMillis, false)), OUTSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, + new EntityGeofencingState(false, tsNow, true)), SUCCESS), + // default config with presenceMonitoringStrategyOnEachMessage true and msgInside true + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, + new EntityGeofencingState(false, 0, false)), ENTERED), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, + new EntityGeofencingState(true, tsNow, false)), INSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, + new EntityGeofencingState(true, tsNowMinusMinuteAndMillis, false)), INSIDE), + // default config with presenceMonitoringStrategyOnEachMessage true and msgInside false + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, + new EntityGeofencingState(false, 0, false)), LEFT), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, + new EntityGeofencingState(false, tsNow, false)), OUTSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, + new EntityGeofencingState(false, tsNowMinusMinuteAndMillis, false)), OUTSIDE) + ); + } + + @ParameterizedTest + @MethodSource + void givenReportPresenceStatusOnEachMessage_whenOnMsg_thenVerifyOutputMsgType( + GpsGeofencingActionTestCase gpsGeofencingActionTestCase, + String expectedOutput + ) throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingActionNodeConfiguration().defaultConfiguration(); + config.setReportPresenceStatusOnEachMessage(gpsGeofencingActionTestCase.isReportPresenceStatusOnEachMessage()); + + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + TbMsg msg = gpsGeofencingActionTestCase.isMsgInside() ? + getInsideRectangleTbMsg(gpsGeofencingActionTestCase.getEntityId()) : + getOutsideRectangleTbMsg(gpsGeofencingActionTestCase.getEntityId()); + + when(ctx.getAttributesService()).thenReturn(attributesService); + + ReflectionTestUtils.setField(node, "entityStates", gpsGeofencingActionTestCase.getEntityStates()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + verify(ctx.getAttributesService(), never()).find(any(), any(), any(), anyString()); + verify(ctx, never()).tellFailure(any(), any(Throwable.class)); + verify(ctx, never()).enqueueForTellNext(any(), eq(expectedOutput), any(), any()); + verify(ctx, never()).ack(any()); + + if (SUCCESS.equals(expectedOutput)) { + verify(ctx).tellSuccess(eq(msg)); + } else { + verify(ctx).tellNext(eq(msg), eq(expectedOutput)); + } + } + + private TbMsg getOutsideRectangleTbMsg(EntityId entityId) { + return getTbMsg(entityId, getMetadataForNewVersionPolygonPerimeter(), + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + } + + private TbMsg getInsideRectangleTbMsg(EntityId entityId) { + return getTbMsg(entityId, getMetadataForNewVersionPolygonPerimeter(), + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + } + + private TbMsg getTbMsg(EntityId entityId, TbMsgMetaData metadata, double latitude, double longitude) { + String data = "{\"latitude\": " + latitude + ", \"longitude\": " + longitude + "}"; + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, metadata, data); + } + + private TbMsgMetaData getMetadataForNewVersionPolygonPerimeter() { + var metadata = new TbMsgMetaData(); + metadata.putValue("ss_perimeter", GeoUtilTest.SIMPLE_RECT); + return metadata; + } + + // Rule nodes upgrade + private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { + return Stream.of( + // default config for version 0 + Arguments.of(0, + "{\n" + + " \"minInsideDuration\": 1,\n" + + " \"minOutsideDuration\": 1,\n" + + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"latitudeKeyName\": \"latitude\",\n" + + " \"longitudeKeyName\": \"longitude\",\n" + + " \"perimeterType\": \"POLYGON\",\n" + + " \"fetchPerimeterInfoFromMessageMetadata\": true,\n" + + " \"perimeterKeyName\": \"ss_perimeter\",\n" + + " \"polygonsDefinition\": null,\n" + + " \"centerLatitude\": null,\n" + + " \"centerLongitude\": null,\n" + + " \"range\": null,\n" + + " \"rangeUnit\": null\n" + + "}\n", + true, + "{\n" + + " \"minInsideDuration\": 1,\n" + + " \"minOutsideDuration\": 1,\n" + + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"reportPresenceStatusOnEachMessage\": false,\n" + + " \"latitudeKeyName\": \"latitude\",\n" + + " \"longitudeKeyName\": \"longitude\",\n" + + " \"perimeterType\": \"POLYGON\",\n" + + " \"fetchPerimeterInfoFromMessageMetadata\": true,\n" + + " \"perimeterKeyName\": \"ss_perimeter\",\n" + + " \"polygonsDefinition\": null,\n" + + " \"centerLatitude\": null,\n" + + " \"centerLongitude\": null,\n" + + " \"range\": null,\n" + + " \"rangeUnit\": null\n" + + "}\n"), + // default config for version 1 with upgrade from version 0 + Arguments.of(0, + "{\n" + + " \"minInsideDuration\": 1,\n" + + " \"minOutsideDuration\": 1,\n" + + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"reportPresenceStatusOnEachMessage\": false,\n" + + " \"latitudeKeyName\": \"latitude\",\n" + + " \"longitudeKeyName\": \"longitude\",\n" + + " \"perimeterType\": \"POLYGON\",\n" + + " \"fetchPerimeterInfoFromMessageMetadata\": true,\n" + + " \"perimeterKeyName\": \"ss_perimeter\",\n" + + " \"polygonsDefinition\": null,\n" + + " \"centerLatitude\": null,\n" + + " \"centerLongitude\": null,\n" + + " \"range\": null,\n" + + " \"rangeUnit\": null\n" + + "}\n", + false, + "{\n" + + " \"minInsideDuration\": 1,\n" + + " \"minOutsideDuration\": 1,\n" + + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"reportPresenceStatusOnEachMessage\": false,\n" + + " \"latitudeKeyName\": \"latitude\",\n" + + " \"longitudeKeyName\": \"longitude\",\n" + + " \"perimeterType\": \"POLYGON\",\n" + + " \"fetchPerimeterInfoFromMessageMetadata\": true,\n" + + " \"perimeterKeyName\": \"ss_perimeter\",\n" + + " \"polygonsDefinition\": null,\n" + + " \"centerLatitude\": null,\n" + + " \"centerLongitude\": null,\n" + + " \"range\": null,\n" + + " \"rangeUnit\": null\n" + + "}\n") + ); + } + + @Override + protected TbNode getTestNode() { + return node; + } + +} 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 c268be7bcd..32a6a755b4 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 @@ -103,6 +103,7 @@ import { import { PowerButtonBasicConfigComponent } from '@home/components/widget/config/basic/button/power-button-basic-config.component'; +import { SliderBasicConfigComponent } from '@home/components/widget/config/basic/rpc/slider-basic-config.component'; @NgModule({ declarations: [ @@ -135,7 +136,8 @@ import { SingleSwitchBasicConfigComponent, ActionButtonBasicConfigComponent, CommandButtonBasicConfigComponent, - PowerButtonBasicConfigComponent + PowerButtonBasicConfigComponent, + SliderBasicConfigComponent ], imports: [ CommonModule, @@ -172,7 +174,8 @@ import { SingleSwitchBasicConfigComponent, ActionButtonBasicConfigComponent, CommandButtonBasicConfigComponent, - PowerButtonBasicConfigComponent + PowerButtonBasicConfigComponent, + SliderBasicConfigComponent ] }) export class BasicWidgetConfigModule { @@ -203,5 +206,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type -
+
{{ 'widgets.power-button.power-on-colors' | translate }}
@@ -134,7 +134,7 @@
-
+
{{ 'widgets.power-button.power-off-colors' | translate }}
@@ -152,7 +152,7 @@
-
+
{{ 'widgets.power-button.disabled-colors' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html index 249acae52a..9f8d580bbc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html @@ -86,7 +86,7 @@
-
+
{{ 'widgets.progress-bar.value' | translate }} @@ -104,7 +104,7 @@
-
+
{{ 'widgets.progress-bar.range' | translate }}
widgets.progress-bar.min
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html index 18fa3b5587..c6ac3abb45 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html @@ -82,7 +82,7 @@ {{ 'widgets.single-switch.auto-scale' | translate }}
-
+
{{ 'widgets.single-switch.label' | translate }} @@ -99,7 +99,7 @@
-
+
{{ 'widgets.single-switch.icon' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html new file mode 100644 index 0000000000..a8d84d4dc4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html @@ -0,0 +1,271 @@ + + + +
+
widgets.slider.behavior
+
+
widgets.slider.initial-value
+ +
+
+
widgets.slider.on-value-change
+ +
+
+
widgets.rpc-state.disabled-state
+ +
+
+
+
widget-config.appearance
+ + + {{ sliderLayoutTranslationMap.get(layout) | translate }} + + +
+ + {{ 'widgets.slider.auto-scale' | translate }} + +
+
+ + {{ 'widget-config.title' | translate }} + +
+ + + + + + + +
+
+
+ + {{ 'widgets.slider.icon' | translate }} + +
+ + + + + + + + +
+
+
+ + {{ 'widgets.slider.value' | translate }} + +
+ + + +
widget-config.decimals-suffix
+
+ + + + +
+
+
+
{{ 'widgets.slider.range' | translate }}
+
+
widgets.slider.min
+ + + +
widgets.slider.max
+ + + +
+
+
+ + {{ 'widgets.slider.range-ticks' | translate }} + +
+ + + + +
+
+
+ + {{ 'widgets.slider.tick-marks' | translate }} + +
+ + + + + +
+
+
+
{{ 'widgets.slider.colors' | translate }}
+
+
+
widgets.slider.main
+ + +
+ +
+
widgets.slider.background
+ + +
+
+
+
+
{{ 'widgets.rpc-state.disabled-state' | translate }}
+
+
+
widgets.slider.main
+ + +
+ +
+
widgets.slider.background
+ + +
+
+
+
+
+ {{ 'widgets.slider.left-icon' | translate }} +
+
+ + + + + + + + +
+
+
+
+ {{ 'widgets.slider.right-icon' | translate }} +
+
+ + + + + + + + +
+
+
+
+
widget-config.card-appearance
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
widget-config.show-card-buttons
+ + {{ 'fullscreen.fullscreen' | translate }} + +
+
+
{{ 'widget-config.card-border-radius' | translate }}
+ + + +
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.ts new file mode 100644 index 0000000000..b80c6acb08 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.ts @@ -0,0 +1,309 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { TargetDevice, WidgetConfig, } from '@shared/models/widget.models'; +import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { formatValue, isUndefined } from '@core/utils'; +import { ValueType } from '@shared/models/constants'; +import { + SliderLayout, + sliderLayoutImages, + sliderLayouts, + sliderLayoutTranslations, + sliderWidgetDefaultSettings, + SliderWidgetSettings +} from '@home/components/widget/lib/rpc/slider-widget.models'; +import { cssSizeToStrSize, resolveCssSize } from '@shared/models/widget-settings.models'; + +@Component({ + selector: 'tb-slider-basic-config', + templateUrl: './slider-basic-config.component.html', + styleUrls: ['../basic-config.scss'] +}) +export class SliderBasicConfigComponent extends BasicWidgetConfigComponent { + + get targetDevice(): TargetDevice { + return this.sliderWidgetConfigForm.get('targetDevice').value; + } + + sliderLayout = SliderLayout; + + sliderLayouts = sliderLayouts; + + sliderLayoutTranslationMap = sliderLayoutTranslations; + sliderLayoutImageMap = sliderLayoutImages; + + valueType = ValueType; + + sliderWidgetConfigForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + constructor(protected store: Store, + protected widgetConfigComponent: WidgetConfigComponent, + private fb: UntypedFormBuilder) { + super(store, widgetConfigComponent); + } + + protected configForm(): UntypedFormGroup { + return this.sliderWidgetConfigForm; + } + + protected onConfigSet(configData: WidgetConfigComponentData) { + const settings: SliderWidgetSettings = {...sliderWidgetDefaultSettings, ...(configData.config.settings || {})}; + const iconSize = resolveCssSize(configData.config.iconSize); + this.sliderWidgetConfigForm = this.fb.group({ + targetDevice: [configData.config.targetDevice, []], + + initialState: [settings.initialState, []], + valueChange: [settings.valueChange, []], + disabledState: [settings.disabledState, []], + + layout: [settings.layout, []], + autoScale: [settings.autoScale, []], + + showTitle: [configData.config.showTitle, []], + title: [configData.config.title, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], + + showIcon: [configData.config.showTitleIcon, []], + iconSize: [iconSize[0], [Validators.min(0)]], + iconSizeUnit: [iconSize[1], []], + icon: [configData.config.titleIcon, []], + iconColor: [configData.config.iconColor, []], + + showValue: [settings.showValue, []], + valueUnits: [settings.valueUnits, []], + valueDecimals: [settings.valueDecimals, []], + valueFont: [settings.valueFont, []], + valueColor: [settings.valueColor, []], + + tickMin: [settings.tickMin, []], + tickMax: [settings.tickMax, []], + + showTicks: [settings.showTicks, []], + ticksFont: [settings.ticksFont, []], + ticksColor: [settings.ticksColor, []], + + showTickMarks: [settings.showTickMarks, []], + tickMarksCount: [settings.tickMarksCount, [Validators.min(2)]], + tickMarksColor: [settings.tickMarksColor, []], + + mainColor: [settings.mainColor, []], + backgroundColor: [settings.backgroundColor, []], + + mainColorDisabled: [settings.mainColorDisabled, []], + backgroundColorDisabled: [settings.backgroundColorDisabled, []], + + leftIconSize: [settings.leftIconSize, [Validators.min(0)]], + leftIconSizeUnit: [settings.leftIconSizeUnit, []], + leftIcon: [settings.leftIcon, []], + leftIconColor: [settings.leftIconColor, []], + + rightIconSize: [settings.rightIconSize, [Validators.min(0)]], + rightIconSizeUnit: [settings.rightIconSizeUnit, []], + rightIcon: [settings.rightIcon, []], + rightIconColor: [settings.rightIconColor, []], + + background: [settings.background, []], + + cardButtons: [this.getCardButtons(configData.config), []], + borderRadius: [configData.config.borderRadius, []], + + actions: [configData.config.actions || {}, []] + }); + } + + protected prepareOutputConfig(config: any): WidgetConfigComponentData { + this.widgetConfig.config.targetDevice = config.targetDevice; + + this.widgetConfig.config.showTitle = config.showTitle; + this.widgetConfig.config.title = config.title; + this.widgetConfig.config.titleFont = config.titleFont; + this.widgetConfig.config.titleColor = config.titleColor; + + this.widgetConfig.config.showTitleIcon = config.showIcon; + this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit); + this.widgetConfig.config.titleIcon = config.icon; + this.widgetConfig.config.iconColor = config.iconColor; + + this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; + + this.widgetConfig.config.settings.initialState = config.initialState; + this.widgetConfig.config.settings.valueChange = config.valueChange; + this.widgetConfig.config.settings.disabledState = config.disabledState; + + this.widgetConfig.config.settings.layout = config.layout; + this.widgetConfig.config.settings.autoScale = config.autoScale; + + this.widgetConfig.config.settings.showValue = config.showValue; + this.widgetConfig.config.settings.valueUnits = config.valueUnits; + this.widgetConfig.config.settings.valueDecimals = config.valueDecimals; + this.widgetConfig.config.settings.valueFont = config.valueFont; + this.widgetConfig.config.settings.valueColor = config.valueColor; + + this.widgetConfig.config.settings.tickMin = config.tickMin; + this.widgetConfig.config.settings.tickMax = config.tickMax; + + this.widgetConfig.config.settings.showTicks = config.showTicks; + this.widgetConfig.config.settings.ticksFont = config.ticksFont; + this.widgetConfig.config.settings.ticksColor = config.ticksColor; + + this.widgetConfig.config.settings.showTickMarks = config.showTickMarks; + this.widgetConfig.config.settings.tickMarksCount = config.tickMarksCount; + this.widgetConfig.config.settings.tickMarksColor = config.tickMarksColor; + + this.widgetConfig.config.settings.mainColor = config.mainColor; + this.widgetConfig.config.settings.backgroundColor = config.backgroundColor; + + this.widgetConfig.config.settings.mainColorDisabled = config.mainColorDisabled; + this.widgetConfig.config.settings.backgroundColorDisabled = config.backgroundColorDisabled; + + this.widgetConfig.config.settings.leftIconSize = config.leftIconSize; + this.widgetConfig.config.settings.leftIconSizeUnit = config.leftIconSizeUnit; + this.widgetConfig.config.settings.leftIcon = config.leftIcon; + this.widgetConfig.config.settings.leftIconColor = config.leftIconColor; + + this.widgetConfig.config.settings.rightIconSize = config.rightIconSize; + this.widgetConfig.config.settings.rightIconSizeUnit = config.rightIconSizeUnit; + this.widgetConfig.config.settings.rightIcon = config.rightIcon; + this.widgetConfig.config.settings.rightIconColor = config.rightIconColor; + + this.widgetConfig.config.settings.background = config.background; + + this.setCardButtons(config.cardButtons, this.widgetConfig.config); + this.widgetConfig.config.borderRadius = config.borderRadius; + + this.widgetConfig.config.actions = config.actions; + return this.widgetConfig; + } + + protected validatorTriggers(): string[] { + return ['showTitle', 'showIcon', 'showValue', 'showTicks', 'showTickMarks', 'layout']; + } + + protected updateValidators(emitEvent: boolean, trigger?: string) { + const showTitle: boolean = this.sliderWidgetConfigForm.get('showTitle').value; + const showIcon: boolean = this.sliderWidgetConfigForm.get('showIcon').value; + const showValue: boolean = this.sliderWidgetConfigForm.get('showValue').value; + const showTicks: boolean = this.sliderWidgetConfigForm.get('showTicks').value; + const showTickMarks: boolean = this.sliderWidgetConfigForm.get('showTickMarks').value; + const layout: SliderLayout = this.sliderWidgetConfigForm.get('layout').value; + + const valueEnabled = layout !== SliderLayout.simplified; + const leftRightIconsEnabled = layout === SliderLayout.extended; + + if (showTitle) { + this.sliderWidgetConfigForm.get('title').enable(); + this.sliderWidgetConfigForm.get('titleFont').enable(); + this.sliderWidgetConfigForm.get('titleColor').enable(); + this.sliderWidgetConfigForm.get('showIcon').enable({emitEvent: false}); + if (showIcon) { + this.sliderWidgetConfigForm.get('iconSize').enable(); + this.sliderWidgetConfigForm.get('iconSizeUnit').enable(); + this.sliderWidgetConfigForm.get('icon').enable(); + this.sliderWidgetConfigForm.get('iconColor').enable(); + } else { + this.sliderWidgetConfigForm.get('iconSize').disable(); + this.sliderWidgetConfigForm.get('iconSizeUnit').disable(); + this.sliderWidgetConfigForm.get('icon').disable(); + this.sliderWidgetConfigForm.get('iconColor').disable(); + } + } else { + this.sliderWidgetConfigForm.get('title').disable(); + this.sliderWidgetConfigForm.get('titleFont').disable(); + this.sliderWidgetConfigForm.get('titleColor').disable(); + this.sliderWidgetConfigForm.get('showIcon').disable({emitEvent: false}); + this.sliderWidgetConfigForm.get('iconSize').disable(); + this.sliderWidgetConfigForm.get('iconSizeUnit').disable(); + this.sliderWidgetConfigForm.get('icon').disable(); + this.sliderWidgetConfigForm.get('iconColor').disable(); + } + + if (valueEnabled && showValue) { + this.sliderWidgetConfigForm.get('valueUnits').enable(); + this.sliderWidgetConfigForm.get('valueDecimals').enable(); + this.sliderWidgetConfigForm.get('valueFont').enable(); + this.sliderWidgetConfigForm.get('valueColor').enable(); + } else { + this.sliderWidgetConfigForm.get('valueUnits').disable(); + this.sliderWidgetConfigForm.get('valueDecimals').disable(); + this.sliderWidgetConfigForm.get('valueFont').disable(); + this.sliderWidgetConfigForm.get('valueColor').disable(); + } + + if (showTicks) { + this.sliderWidgetConfigForm.get('ticksFont').enable(); + this.sliderWidgetConfigForm.get('ticksColor').enable(); + } else { + this.sliderWidgetConfigForm.get('ticksFont').disable(); + this.sliderWidgetConfigForm.get('ticksColor').disable(); + } + + if (showTickMarks) { + this.sliderWidgetConfigForm.get('tickMarksCount').enable(); + this.sliderWidgetConfigForm.get('tickMarksColor').enable(); + } else { + this.sliderWidgetConfigForm.get('tickMarksCount').disable(); + this.sliderWidgetConfigForm.get('tickMarksColor').disable(); + } + + if (leftRightIconsEnabled) { + this.sliderWidgetConfigForm.get('leftIconSize').enable(); + this.sliderWidgetConfigForm.get('leftIconSizeUnit').enable(); + this.sliderWidgetConfigForm.get('leftIcon').enable(); + this.sliderWidgetConfigForm.get('leftIconColor').enable(); + this.sliderWidgetConfigForm.get('rightIconSize').enable(); + this.sliderWidgetConfigForm.get('rightIconSizeUnit').enable(); + this.sliderWidgetConfigForm.get('rightIcon').enable(); + this.sliderWidgetConfigForm.get('rightIconColor').enable(); + } else { + this.sliderWidgetConfigForm.get('leftIconSize').disable(); + this.sliderWidgetConfigForm.get('leftIconSizeUnit').disable(); + this.sliderWidgetConfigForm.get('leftIcon').disable(); + this.sliderWidgetConfigForm.get('leftIconColor').disable(); + this.sliderWidgetConfigForm.get('rightIconSize').disable(); + this.sliderWidgetConfigForm.get('rightIconSizeUnit').disable(); + this.sliderWidgetConfigForm.get('rightIcon').disable(); + this.sliderWidgetConfigForm.get('rightIconColor').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.sliderWidgetConfigForm.get('valueUnits').value; + const decimals: number = this.sliderWidgetConfigForm.get('valueDecimals').value; + return formatValue(48, decimals, units, false); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts index dc56a1e992..cac6928bac 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts @@ -62,8 +62,6 @@ export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, A loading$ = this.loadingSubject.asObservable().pipe(share()); - error = ''; - protected constructor(protected cd: ChangeDetectorRef) { } @@ -99,8 +97,7 @@ export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, A } public clearError() { - this.error = ''; - this.cd.markForCheck(); + this.ctx.hideToast(this.ctx.toastTargetId); } protected createValueGetter(getValueSettings: GetValueSettings, @@ -113,7 +110,8 @@ export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, A } }, error: (err: any) => { - this.onError(err); + const message = parseError(this.ctx, err); + this.onError(message); if (valueObserver?.error) { valueObserver.error(err); } @@ -132,8 +130,7 @@ export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, A } private onError(error: string) { - this.error = error; - this.cd.markForCheck(); + this.ctx.showErrorToast(error, 'bottom', 'center', this.ctx.toastTargetId, true); } protected updateValue(valueSetter: ValueSetter, @@ -295,6 +292,8 @@ export class ValueToDataConverter { constructor(protected settings: ValueToDataSettings) { switch (settings.type) { + case ValueToDataType.VALUE: + break; case ValueToDataType.CONSTANT: this.constantValue = this.settings.constantValue; break; @@ -312,6 +311,8 @@ export class ValueToDataConverter { valueToData(value: V): any { switch (this.settings.type) { + case ValueToDataType.VALUE: + return value; case ValueToDataType.CONSTANT: return this.constantValue; case ValueToDataType.FUNCTION: diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss index 6641c2c32e..081cb00f0d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss @@ -19,34 +19,3 @@ left: 0; right: 0; } -.tb-action-widget-error-container { - position: absolute; - bottom: 0; - left: 0; - right: 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - z-index: 1; - .tb-action-widget-error-panel { - display: flex; - padding: 4px 4px 4px 12px; - justify-content: center; - align-items: center; - gap: 4px; - border-radius: 4px; - background-color: #fff2f3; - box-shadow: -2px 2px 4px 0px rgba(0,0,0,0.2); - .tb-action-widget-error-text { - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 16px; - color: rgba(209, 39, 48, 1); - } - .tb-action-widget-error-clear { - color: rgba(209, 39, 48, 1); - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html index 02e2406859..294ba855fe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html @@ -27,10 +27,4 @@ [ctx]="ctx" (clicked)="onClick($event)"> -
-
-
- -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.component.html index 26f910fdd7..d48ba2f5e4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.component.html @@ -27,10 +27,4 @@ (clicked)="onClick($event)"> -
-
-
- -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html index 93e394ac5e..48e4106a97 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html @@ -23,10 +23,4 @@
-
-
-
- -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html index c1c05cf880..fd9aaca4a6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html @@ -37,10 +37,4 @@
-
-
-
- -
-
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.html new file mode 100644 index 0000000000..f15892dd52 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.html @@ -0,0 +1,59 @@ + +
+
+
+ +
+
+
+
{{ valueText }}
+
+
+
+ {{ leftIcon }} +
+
+ + + +
+
+
{{ settings.tickMin }}
+
+
+
{{ settings.tickMax }}
+
+
+
+
+ {{ rightIcon }} +
+
+
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss new file mode 100644 index 0000000000..0d7115fccb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss @@ -0,0 +1,135 @@ +/** + * 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. + */ +$mainColor: var(--tb-slider-main-color, #5469FF); +$hoverRippleColor: var(--tb-slider-hover-ripple-color, rgba(84, 105, 255, 0.05)); +$focusRippleColor: var(--tb-slider-focus-ripple-color, rgba(84, 105, 255, 0.2)); +$backgroundColor: var(--tb-slider-background-color, #CCD2FF); +$tickMarksColor: var(--tb-slider-tick-marks-color, #5469FF); + +$mainColorDisabled: var(--tb-slider-main-color-disabled, #9BA2B0); +$backgroundColorDisabled: var(--tb-slider-background-color-disabled, #D5D7E5); + +.tb-slider-panel { + + .mat-mdc-slider.mat-primary.tb-slider { + --mdc-slider-active-track-color: #{$mainColor}; + --mdc-slider-handle-color: #{$mainColor}; + --mdc-slider-focus-handle-color: #{$mainColor}; + --mdc-slider-hover-handle-color: #{$mainColor}; + + --mdc-slider-with-tick-marks-inactive-container-color: #{$tickMarksColor}; + --mat-mdc-slider-ripple-color: #{$mainColor}; + + --mat-mdc-slider-hover-ripple-color: #{$hoverRippleColor}; + --mat-mdc-slider-focus-ripple-color: #{$focusRippleColor}; + + --mdc-slider-inactive-track-color: #{$backgroundColor}; + + --mdc-slider-disabled-active-track-color: #{$mainColorDisabled}; + --mdc-slider-disabled-handle-color: #{$mainColorDisabled}; + + --mdc-slider-disabled-inactive-track-color: #{$backgroundColorDisabled}; + --mdc-slider-with-tick-marks-disabled-container-color: #{$mainColorDisabled}; + + --mdc-slider-handle-width: 16px; + --mdc-slider-handle-height: 16px; + } + + width: 100%; + height: 100%; + position: relative; + display: flex; + flex-direction: column; + padding: 20px 24px 24px 24px; + gap: 8px; + > div:not(.tb-slider-overlay), > tb-icon { + z-index: 1; + } + .tb-slider-overlay { + position: absolute; + inset: 12px; + } + div.tb-slider-title-panel { + z-index: 2; + } + .tb-slider-content { + flex: 1; + min-height: 0; + display: flex; + position: relative; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + .tb-slider-value-container { + min-height: 0; + } + .tb-slider-value { + white-space: nowrap; + } + .tb-slider-container { + align-self: stretch; + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 8px; + &.tb-min-height { + height: 6px; + } + .tb-slider-column { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + .mat-mdc-slider.tb-slider { + margin: 0; + height: 6px; + min-height: 6px; + min-width: 0; + &.mdc-slider--disabled { + opacity: 1; + } + .mdc-slider__track--inactive { + opacity: 1; + } + .mdc-slider__tick-marks { + .mdc-slider__tick-mark--active { + display: none; + } + .mdc-slider__tick-mark--inactive { + opacity: 1; + } + } + .mdc-slider__thumb.mat-mdc-slider-visual-thumb { + top: -21px; + .mat-ripple { + overflow: visible; + } + } + .mdc-slider__value-indicator-text { + white-space: nowrap; + } + } + .tb-slider-ticks { + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts new file mode 100644 index 0000000000..37b5319d44 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts @@ -0,0 +1,347 @@ +/// +/// 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, + OnDestroy, + OnInit, + Renderer2, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { BasicActionWidgetComponent, ValueSetter } from '@home/components/widget/lib/action/action-widget.models'; +import { + backgroundStyle, + ComponentStyle, + iconStyle, + overlayStyle, + textStyle +} from '@shared/models/widget-settings.models'; +import { Observable } from 'rxjs'; +import { ResizeObserver } from '@juggle/resize-observer'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ValueType } from '@shared/models/constants'; +import { UtilsService } from '@core/services/utils.service'; +import { + SliderLayout, + sliderWidgetDefaultSettings, + SliderWidgetSettings +} from '@home/components/widget/lib/rpc/slider-widget.models'; +import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; +import { WidgetComponent } from '@home/components/widget/widget.component'; +import tinycolor from 'tinycolor2'; + +@Component({ + selector: 'tb-slider-widget', + templateUrl: './slider-widget.component.html', + styleUrls: ['../action/action-widget.scss', './slider-widget.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class SliderWidgetComponent extends + BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy { + + @ViewChild('sliderContent', {static: false}) + sliderContent: ElementRef; + + @ViewChild('sliderValueContainer', {static: false}) + sliderValueContainer: ElementRef; + + @ViewChild('sliderValue', {static: false}) + sliderValue: ElementRef; + + @ViewChild('sliderTickMinContainer', {static: false}) + sliderTickMinContainer: ElementRef; + + @ViewChild('sliderTickMin', {static: false}) + sliderTickMin: ElementRef; + + @ViewChild('sliderTickMaxContainer', {static: false}) + sliderTickMaxContainer: ElementRef; + + @ViewChild('sliderTickMax', {static: false}) + sliderTickMax: ElementRef; + + @ViewChild('leftSliderIconContainer', {static: false, read: ElementRef}) + leftSliderIconContainer: ElementRef; + + @ViewChild('leftSliderIcon', {static: false, read: ElementRef}) + leftSliderIcon: ElementRef; + + @ViewChild('rightSliderIconContainer', {static: false, read: ElementRef}) + rightSliderIconContainer: ElementRef; + + @ViewChild('rightSliderIcon', {static: false, read: ElementRef}) + rightSliderIcon: ElementRef; + + settings: SliderWidgetSettings; + + backgroundStyle$: Observable; + overlayStyle: ComponentStyle = {}; + + value: number = null; + private prevValue: number = null; + + disabled = false; + + layout: SliderLayout; + + showValue = true; + valueText = 'N/A'; + valueStyle: ComponentStyle = {}; + + showLeftRightIcon = false; + leftIcon = ''; + leftIconStyle: ComponentStyle = {}; + rightIcon = ''; + rightIconStyle: ComponentStyle = {}; + + showTicks = true; + ticksStyle: ComponentStyle = {}; + + sliderStep: number = undefined; + + autoScale = false; + + showWidgetTitlePanel = this.widgetComponent.dashboardWidget.showWidgetTitlePanel; + + sliderValueText = this._sliderValueText.bind(this); + + private panelResize$: ResizeObserver; + + private valueSetter: ValueSetter; + + private sliderCssClass: string; + + constructor(protected imagePipe: ImagePipe, + protected sanitizer: DomSanitizer, + private renderer: Renderer2, + private utils: UtilsService, + private widgetComponent: WidgetComponent, + protected cd: ChangeDetectorRef, + private elementRef: ElementRef) { + super(cd); + } + + ngOnInit(): void { + super.ngOnInit(); + this.settings = {...sliderWidgetDefaultSettings, ...this.ctx.settings}; + + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); + this.overlayStyle = overlayStyle(this.settings.background.overlay); + + this.layout = this.settings.layout; + + this.autoScale = this.settings.autoScale; + + this.showValue = this.layout !== SliderLayout.simplified && this.settings.showValue; + this.valueStyle = textStyle(this.settings.valueFont); + this.valueStyle.color = this.settings.valueColor; + + this.showLeftRightIcon = this.layout === SliderLayout.extended; + if (this.showLeftRightIcon) { + this.leftIcon = this.settings.leftIcon; + this.leftIconStyle = iconStyle(this.settings.leftIconSize, this.settings.leftIconSizeUnit ); + this.rightIcon = this.settings.rightIcon; + this.rightIconStyle = iconStyle(this.settings.rightIconSize, this.settings.rightIconSizeUnit ); + if (!this.autoScale) { + const leftIconMargin = this.settings.leftIconSize / 2 + (this.settings.leftIconSizeUnit || 'px'); + this.leftIconStyle.marginTop = `calc(-${leftIconMargin} + 3px)`; + const rightIconMargin = this.settings.rightIconSize / 2 + (this.settings.rightIconSizeUnit || 'px'); + this.rightIconStyle.marginTop = `calc(-${rightIconMargin} + 3px)`; + } + } + + this.showTicks = this.settings.showTicks; + if (this.showTicks) { + this.ticksStyle = textStyle(this.settings.ticksFont); + this.ticksStyle.color = this.settings.ticksColor; + } + + if (this.settings.showTickMarks) { + const range = this.settings.tickMax - this.settings.tickMin; + this.sliderStep = range / (this.settings.tickMarksCount - 1); + } + + const mainColorInstance = tinycolor(this.settings.mainColor); + const hoverRippleColor = mainColorInstance.clone().setAlpha(mainColorInstance.getAlpha() * 0.05).toRgbString(); + const focusRippleColor = mainColorInstance.clone().setAlpha(mainColorInstance.getAlpha() * 0.2).toRgbString(); + + const sliderVariablesCss = `.tb-slider-panel {\n`+ + `--tb-slider-main-color: ${this.settings.mainColor};\n`+ + `--tb-slider-background-color: ${this.settings.backgroundColor};\n`+ + `--tb-slider-hover-ripple-color: ${hoverRippleColor};\n`+ + `--tb-slider-focus-ripple-color: ${focusRippleColor};\n`+ + `--tb-slider-tick-marks-color: ${this.settings.tickMarksColor};\n`+ + `--tb-slider-main-color-disabled: ${this.settings.mainColorDisabled};\n`+ + `--tb-slider-background-disabled: ${this.settings.backgroundColorDisabled};\n`+ + `}`; + this.sliderCssClass = + this.utils.applyCssToElement(this.renderer, this.elementRef.nativeElement, 'tb-slider', sliderVariablesCss); + + const getInitialStateSettings = + {...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.slider.initial-value')}; + this.createValueGetter(getInitialStateSettings, ValueType.INTEGER, { + next: (value) => this.onValue(value) + }); + + const disabledStateSettings = + {...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.disabled-state')}; + this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, { + next: (value) => this.onDisabled(value) + }); + + const valueChangeSettings = {...this.settings.valueChange, + actionLabel: this.ctx.translate.instant('widgets.slider.on-value-change')}; + this.valueSetter = this.createValueSetter(valueChangeSettings); + } + + ngAfterViewInit(): void { + if (this.autoScale) { + this.panelResize$ = new ResizeObserver(() => { + this.onResize(); + }); + this.panelResize$.observe(this.sliderContent.nativeElement); + if (this.showValue) { + this.panelResize$.observe(this.sliderValueContainer.nativeElement); + } + this.onResize(); + } + super.ngAfterViewInit(); + } + + ngOnDestroy() { + if (this.panelResize$) { + this.panelResize$.disconnect(); + } + if (this.sliderCssClass) { + this.utils.clearCssElement(this.renderer, this.sliderCssClass); + } + super.ngOnDestroy(); + } + + public onInit() { + super.onInit(); + const borderRadius = this.ctx.$widgetElement.css('borderRadius'); + this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; + this.cd.detectChanges(); + } + + public onSliderChange() { + this.updateValueText(); + if (!this.ctx.isEdit && !this.ctx.isPreview) { + const prevValue = this.prevValue; + const targetValue = this.value; + this.updateValue(this.valueSetter, targetValue, { + next: () => this.onValue(targetValue), + error: () => this.onValue(prevValue) + }); + } + } + + private _sliderValueText(value: number): string { + return formatValue(value, this.settings.valueDecimals, this.settings.valueUnits, false); + } + + private onValue(value: number): void { + this.value = value; + this.prevValue = value; + this.updateValueText(); + this.cd.markForCheck(); + } + + private updateValueText() { + if (isDefinedAndNotNull(this.value) && isNumeric(this.value)) { + this.valueText = formatValue(this.value, this.settings.valueDecimals, this.settings.valueUnits, false); + } else { + this.valueText = 'N/A'; + } + } + + private onDisabled(value: boolean): void { + this.disabled = !!value; + this.cd.markForCheck(); + } + + private onResize() { + const panelWidth = this.sliderContent.nativeElement.getBoundingClientRect().width; + const panelHeight = this.sliderContent.nativeElement.getBoundingClientRect().height; + + if (this.showValue) { + this.resetScale(this.sliderValueContainer.nativeElement, this.sliderValue.nativeElement); + } + + if (this.showLeftRightIcon) { + this.resetScale(this.leftSliderIconContainer.nativeElement, this.leftSliderIcon.nativeElement); + this.resetScale(this.rightSliderIconContainer.nativeElement, this.rightSliderIcon.nativeElement); + } + + if (this.showTicks) { + this.resetScale(this.sliderTickMinContainer.nativeElement, this.sliderTickMin.nativeElement); + this.resetScale(this.sliderTickMaxContainer.nativeElement, this.sliderTickMax.nativeElement); + } + + let minAspect = 0.2; + let avgContentHeight = 35; + if (this.showTicks) { + minAspect += 0.1; + avgContentHeight += 20; + } + if (this.showValue) { + minAspect += 0.1; + avgContentHeight += 50; + } + const aspect = Math.min(panelHeight / panelWidth, minAspect); + const targetHeight = panelWidth * aspect; + const scale = targetHeight / avgContentHeight; + + if (this.showValue) { + this.updateScale(this.sliderValueContainer.nativeElement, this.sliderValue.nativeElement, scale); + } + if (this.showLeftRightIcon) { + const leftIconContainerRect = this.leftSliderIconContainer.nativeElement.getBoundingClientRect(); + const leftIconContainerMarginTop = -(leftIconContainerRect.width * scale) / 2 + 3; + this.renderer.setStyle(this.leftSliderIconContainer.nativeElement, 'marginTop', `${leftIconContainerMarginTop}px`); + this.updateScale(this.leftSliderIconContainer.nativeElement, this.leftSliderIcon.nativeElement, scale, true); + const rightIconContainerRect = this.rightSliderIconContainer.nativeElement.getBoundingClientRect(); + const rightIconContainerMarginTop = -(rightIconContainerRect.width * scale) / 2 + 3; + this.renderer.setStyle(this.rightSliderIconContainer.nativeElement, 'marginTop', `${rightIconContainerMarginTop}px`); + this.updateScale(this.rightSliderIconContainer.nativeElement, this.rightSliderIcon.nativeElement, scale, true); + } + if (this.showTicks) { + this.updateScale(this.sliderTickMinContainer.nativeElement, this.sliderTickMin.nativeElement, scale); + this.updateScale(this.sliderTickMaxContainer.nativeElement, this.sliderTickMax.nativeElement, scale); + } + } + + private resetScale(container: HTMLElement, element: HTMLElement): void { + this.renderer.setStyle(container, 'width', ''); + this.renderer.setStyle(container, 'height', ''); + this.renderer.setStyle(element, 'transform', ''); + } + + private updateScale(container: HTMLElement, element: HTMLElement, scale: number, sameHeight = false): void { + const rect = container.getBoundingClientRect(); + this.renderer.setStyle(container, 'width', `${rect.width * scale}px`); + this.renderer.setStyle(container, 'height', `${(sameHeight ? rect.width : rect.height) * scale}px`); + this.renderer.setStyle(element, 'transform', `scale(${scale})`); + this.renderer.setStyle(element, 'transform-origin', 'left top'); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts new file mode 100644 index 0000000000..c6fe1e290a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts @@ -0,0 +1,196 @@ +/// +/// 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 { + DataToValueType, + GetValueAction, + GetValueSettings, + SetValueAction, + SetValueSettings, + ValueToDataType +} from '@shared/models/action-widget-settings.models'; +import { BackgroundSettings, BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; + +export enum SliderLayout { + default = 'default', + extended = 'extended', + simplified = 'simplified' +} + +export const sliderLayouts = Object.keys(SliderLayout) as SliderLayout[]; + +export const sliderLayoutTranslations = new Map( + [ + [SliderLayout.default, 'widgets.slider.layout-default'], + [SliderLayout.extended, 'widgets.slider.layout-extended'], + [SliderLayout.simplified, 'widgets.slider.layout-simplified'] + ] +); + +export const sliderLayoutImages = new Map( + [ + [SliderLayout.default, 'assets/widget/slider/default-layout.svg'], + [SliderLayout.extended, 'assets/widget/slider/extended-layout.svg'], + [SliderLayout.simplified, 'assets/widget/slider/simplified-layout.svg'] + ] +); + +export interface SliderWidgetSettings { + initialState: GetValueSettings; + disabledState: GetValueSettings; + valueChange: SetValueSettings; + layout: SliderLayout; + autoScale: boolean; + showValue: boolean; + valueUnits: string; + valueDecimals: number; + valueFont: Font; + valueColor: string; + showTicks: boolean; + tickMin: number; + tickMax: number; + ticksFont: Font; + ticksColor: string; + showTickMarks: boolean; + tickMarksCount: number; + tickMarksColor: string; + mainColor: string; + backgroundColor: string; + mainColorDisabled: string; + backgroundColorDisabled: string; + leftIcon: string; + leftIconSize: number; + leftIconSizeUnit: cssUnit; + leftIconColor: string; + rightIcon: string; + rightIconSize: number; + rightIconSizeUnit: cssUnit; + rightIconColor: string; + background: BackgroundSettings; +} + +export const sliderWidgetDefaultSettings: SliderWidgetSettings = { + initialState: { + action: GetValueAction.EXECUTE_RPC, + defaultValue: 0, + executeRpc: { + method: 'getState', + requestTimeout: 5000, + requestPersistent: false, + persistentPollingInterval: 1000 + }, + getAttribute: { + key: 'state', + scope: null + }, + getTimeSeries: { + key: 'state' + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return integer value */\nreturn data;' + } + }, + disabledState: { + action: GetValueAction.DO_NOTHING, + defaultValue: false, + getAttribute: { + key: 'state', + scope: null + }, + getTimeSeries: { + key: 'state' + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return boolean value */\nreturn data;' + } + }, + valueChange: { + action: SetValueAction.EXECUTE_RPC, + executeRpc: { + method: 'setState', + requestTimeout: 5000, + requestPersistent: false, + persistentPollingInterval: 1000 + }, + setAttribute: { + key: 'state', + scope: AttributeScope.SHARED_SCOPE + }, + putTimeSeries: { + key: 'state' + }, + valueToData: { + type: ValueToDataType.VALUE, + constantValue: 0, + valueToDataFunction: '/* Convert input integer value to RPC parameters or attribute/time-series value */\nreturn value;' + } + }, + layout: SliderLayout.default, + autoScale: true, + showValue: true, + valueUnits: '%', + valueDecimals: 0, + valueFont: { + family: 'Roboto', + size: 36, + sizeUnit: 'px', + style: 'normal', + weight: '500', + lineHeight: '36px' + }, + valueColor: 'rgba(0, 0, 0, 0.87)', + showTicks: true, + tickMin: 0, + tickMax: 100, + ticksFont: { + family: 'Roboto', + size: 11, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '16px' + }, + ticksColor: 'rgba(0,0,0,0.54)', + showTickMarks: true, + tickMarksCount: 11, + tickMarksColor: '#5469FF', + mainColor: '#5469FF', + backgroundColor: '#CCD2FF', + mainColorDisabled: '#9BA2B0', + backgroundColorDisabled: '#D5D7E5', + leftIcon: 'lightbulb', + leftIconSize: 24, + leftIconSizeUnit: 'px', + leftIconColor: '#5469FF', + rightIcon: 'mdi:lightbulb-on', + rightIconSize: 24, + rightIconSizeUnit: 'px', + rightIconColor: '#5469FF', + background: { + type: BackgroundType.color, + color: '#fff', + overlay: { + enabled: false, + color: 'rgba(255,255,255,0.72)', + blur: 3 + } + } +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html index ced9bf9575..b37030c884 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html @@ -84,7 +84,7 @@
widgets.power-button.button
-
+
{{ 'widgets.power-button.power-on-colors' | translate }}
@@ -102,7 +102,7 @@
-
+
{{ 'widgets.power-button.power-off-colors' | translate }}
@@ -120,7 +120,7 @@
-
+
{{ 'widgets.power-button.disabled-colors' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html index de641b8808..31c8276666 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html @@ -35,7 +35,7 @@ {{ 'widgets.progress-bar.auto-scale' | translate }}
-
+
{{ 'widgets.progress-bar.value' | translate }} @@ -48,7 +48,7 @@
-
+
{{ 'widgets.progress-bar.range' | translate }}
widgets.progress-bar.min
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html index b1fccafac0..4cf5857612 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html @@ -97,7 +97,8 @@
{{ (setValueSettingsFormGroup.get('action').value === setValueAction.EXECUTE_RPC ? 'widgets.value-action.parameters' : 'widgets.value-action.value') | translate }}
- {{ 'widgets.value-action.converter-constant' | translate }} + {{ 'widgets.value-action.converter-value' | translate }} + {{ 'widgets.value-action.converter-constant' | translate }} {{ 'widgets.value-action.converter-function' | translate }} {{ 'widgets.value-action.converter-none' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.ts index d718168b11..819ed2a3fc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.ts @@ -32,6 +32,7 @@ import { TargetDevice, widgetType } from '@shared/models/widget.models'; import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models'; import { IAliasController } from '@core/api/widget-api.models'; import { WidgetService } from '@core/http/widget.service'; +import { ValueType } from '@shared/models/constants'; @Component({ selector: 'tb-set-value-action-settings-panel', @@ -45,6 +46,9 @@ export class SetValueActionSettingsPanelComponent extends PageComponent implemen @Input() panelTitle: string; + @Input() + valueType = ValueType.BOOLEAN; + @Input() setValueSettings: SetValueSettings; @@ -79,6 +83,8 @@ export class SetValueActionSettingsPanelComponent extends PageComponent implemen functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + ValueType = ValueType; + setValueSettingsFormGroup: UntypedFormGroup; constructor(private fb: UntypedFormBuilder, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts index fce1e23673..7b4c8e0428 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts @@ -36,6 +36,7 @@ import { isDefinedAndNotNull } from '@core/utils'; import { SetValueActionSettingsPanelComponent } from '@home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component'; +import { ValueType } from '@shared/models/constants'; @Component({ selector: 'tb-set-value-action-settings', @@ -58,6 +59,9 @@ export class SetValueActionSettingsComponent implements OnInit, ControlValueAcce @Input() panelTitle: string; + @Input() + valueType = ValueType.BOOLEAN; + @Input() aliasController: IAliasController; @@ -114,6 +118,7 @@ export class SetValueActionSettingsComponent implements OnInit, ControlValueAcce const ctx: any = { setValueSettings: this.modelValue, panelTitle: this.panelTitle, + valueType: this.valueType, aliasController: this.aliasController, targetDevice: this.targetDevice, widgetType: this.widgetType @@ -137,6 +142,9 @@ export class SetValueActionSettingsComponent implements OnInit, ControlValueAcce private updateDisplayValue() { let value: any; switch (this.modelValue.valueToData.type) { + case ValueToDataType.VALUE: + value = 'value'; + break; case ValueToDataType.CONSTANT: value = this.modelValue.valueToData.constantValue; break; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html index b4ce9c8ce2..bdecc9d36b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html @@ -56,7 +56,7 @@
-
+
{{ 'widgets.button.color-palette' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html index 9350c2d932..9208efd68a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html @@ -81,7 +81,7 @@ {{ 'widgets.single-switch.auto-scale' | translate }}
-
+
{{ 'widgets.single-switch.label' | translate }} @@ -98,7 +98,7 @@
-
+
{{ 'widgets.single-switch.icon' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html new file mode 100644 index 0000000000..0f3c3f11a3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html @@ -0,0 +1,216 @@ + + +
+
widgets.slider.behavior
+
+
widgets.slider.initial-value
+ +
+
+
widgets.slider.on-value-change
+ +
+
+
widgets.rpc-state.disabled-state
+ +
+
+
+
widget-config.card-style
+ + + {{ sliderLayoutTranslationMap.get(layout) | translate }} + + +
+ + {{ 'widgets.slider.auto-scale' | translate }} + +
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
+
widgets.slider.slider
+
+ + {{ 'widgets.slider.value' | translate }} + +
+ + + +
widget-config.decimals-suffix
+
+ + + + +
+
+
+
{{ 'widgets.slider.range' | translate }}
+
+
widgets.slider.min
+ + + +
widgets.slider.max
+ + + +
+
+
+ + {{ 'widgets.slider.range-ticks' | translate }} + +
+ + + + +
+
+
+ + {{ 'widgets.slider.tick-marks' | translate }} + +
+ + + + + +
+
+
+
{{ 'widgets.slider.colors' | translate }}
+
+
+
widgets.slider.main
+ + +
+ +
+
widgets.slider.background
+ + +
+
+
+
+
{{ 'widgets.rpc-state.disabled-state' | translate }}
+
+
+
widgets.slider.main
+ + +
+ +
+
widgets.slider.background
+ + +
+
+
+
+
+ {{ 'widgets.slider.left-icon' | translate }} +
+
+ + + + + + + + +
+
+
+
+ {{ 'widgets.slider.right-icon' | translate }} +
+
+ + + + + + + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.ts new file mode 100644 index 0000000000..a215a93d88 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.ts @@ -0,0 +1,186 @@ +/// +/// 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 { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ValueType } from '@shared/models/constants'; +import { + SliderLayout, + sliderLayoutImages, + sliderLayouts, + sliderLayoutTranslations, + sliderWidgetDefaultSettings +} from '@home/components/widget/lib/rpc/slider-widget.models'; +import { formatValue } from '@core/utils'; + +@Component({ + selector: 'tb-slider-widget-settings', + templateUrl: './slider-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class SliderWidgetSettingsComponent extends WidgetSettingsComponent { + + get targetDevice(): TargetDevice { + return this.widgetConfig?.config?.targetDevice; + } + + get widgetType(): widgetType { + return this.widgetConfig?.widgetType; + } + + sliderLayout = SliderLayout; + + sliderLayouts = sliderLayouts; + + sliderLayoutTranslationMap = sliderLayoutTranslations; + sliderLayoutImageMap = sliderLayoutImages; + + valueType = ValueType; + + sliderWidgetSettingsForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.sliderWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return {...sliderWidgetDefaultSettings}; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.sliderWidgetSettingsForm = this.fb.group({ + initialState: [settings.initialState, []], + valueChange: [settings.valueChange, []], + disabledState: [settings.disabledState, []], + + layout: [settings.layout, []], + autoScale: [settings.autoScale, []], + + showValue: [settings.showValue, []], + valueUnits: [settings.valueUnits, []], + valueDecimals: [settings.valueDecimals, []], + valueFont: [settings.valueFont, []], + valueColor: [settings.valueColor, []], + + tickMin: [settings.tickMin, []], + tickMax: [settings.tickMax, []], + + showTicks: [settings.showTicks, []], + ticksFont: [settings.ticksFont, []], + ticksColor: [settings.ticksColor, []], + + showTickMarks: [settings.showTickMarks, []], + tickMarksCount: [settings.tickMarksCount, [Validators.min(2)]], + tickMarksColor: [settings.tickMarksColor, []], + + mainColor: [settings.mainColor, []], + backgroundColor: [settings.backgroundColor, []], + + mainColorDisabled: [settings.mainColorDisabled, []], + backgroundColorDisabled: [settings.backgroundColorDisabled, []], + + leftIconSize: [settings.leftIconSize, [Validators.min(0)]], + leftIconSizeUnit: [settings.leftIconSizeUnit, []], + leftIcon: [settings.leftIcon, []], + leftIconColor: [settings.leftIconColor, []], + + rightIconSize: [settings.rightIconSize, [Validators.min(0)]], + rightIconSizeUnit: [settings.rightIconSizeUnit, []], + rightIcon: [settings.rightIcon, []], + rightIconColor: [settings.rightIconColor, []], + + background: [settings.background, []], + }); + } + + protected validatorTriggers(): string[] { + return ['showValue', 'showTicks', 'showTickMarks', 'layout']; + } + + protected updateValidators(_emitEvent: boolean): void { + const showValue: boolean = this.sliderWidgetSettingsForm.get('showValue').value; + const showTicks: boolean = this.sliderWidgetSettingsForm.get('showTicks').value; + const showTickMarks: boolean = this.sliderWidgetSettingsForm.get('showTickMarks').value; + const layout: SliderLayout = this.sliderWidgetSettingsForm.get('layout').value; + + const valueEnabled = layout !== SliderLayout.simplified; + const leftRightIconsEnabled = layout === SliderLayout.extended; + + if (valueEnabled && showValue) { + this.sliderWidgetSettingsForm.get('valueUnits').enable(); + this.sliderWidgetSettingsForm.get('valueDecimals').enable(); + this.sliderWidgetSettingsForm.get('valueFont').enable(); + this.sliderWidgetSettingsForm.get('valueColor').enable(); + } else { + this.sliderWidgetSettingsForm.get('valueUnits').disable(); + this.sliderWidgetSettingsForm.get('valueDecimals').disable(); + this.sliderWidgetSettingsForm.get('valueFont').disable(); + this.sliderWidgetSettingsForm.get('valueColor').disable(); + } + + if (showTicks) { + this.sliderWidgetSettingsForm.get('ticksFont').enable(); + this.sliderWidgetSettingsForm.get('ticksColor').enable(); + } else { + this.sliderWidgetSettingsForm.get('ticksFont').disable(); + this.sliderWidgetSettingsForm.get('ticksColor').disable(); + } + + if (showTickMarks) { + this.sliderWidgetSettingsForm.get('tickMarksCount').enable(); + this.sliderWidgetSettingsForm.get('tickMarksColor').enable(); + } else { + this.sliderWidgetSettingsForm.get('tickMarksCount').disable(); + this.sliderWidgetSettingsForm.get('tickMarksColor').disable(); + } + + if (leftRightIconsEnabled) { + this.sliderWidgetSettingsForm.get('leftIconSize').enable(); + this.sliderWidgetSettingsForm.get('leftIconSizeUnit').enable(); + this.sliderWidgetSettingsForm.get('leftIcon').enable(); + this.sliderWidgetSettingsForm.get('leftIconColor').enable(); + this.sliderWidgetSettingsForm.get('rightIconSize').enable(); + this.sliderWidgetSettingsForm.get('rightIconSizeUnit').enable(); + this.sliderWidgetSettingsForm.get('rightIcon').enable(); + this.sliderWidgetSettingsForm.get('rightIconColor').enable(); + } else { + this.sliderWidgetSettingsForm.get('leftIconSize').disable(); + this.sliderWidgetSettingsForm.get('leftIconSizeUnit').disable(); + this.sliderWidgetSettingsForm.get('leftIcon').disable(); + this.sliderWidgetSettingsForm.get('leftIconColor').disable(); + this.sliderWidgetSettingsForm.get('rightIconSize').disable(); + this.sliderWidgetSettingsForm.get('rightIconSizeUnit').disable(); + this.sliderWidgetSettingsForm.get('rightIcon').disable(); + this.sliderWidgetSettingsForm.get('rightIconColor').disable(); + } + } + + private _valuePreviewFn(): string { + const units: string = this.sliderWidgetSettingsForm.get('valueUnits').value; + const decimals: number = this.sliderWidgetSettingsForm.get('valueDecimals').value; + return formatValue(48, decimals, units, false); + } +} 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 d3fe9dac3e..8fc6ef96c2 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 @@ -324,6 +324,9 @@ import { import { PowerButtonWidgetSettingsComponent } from '@home/components/widget/lib/settings/button/power-button-widget-settings.component'; +import { + SliderWidgetSettingsComponent +} from '@home/components/widget/lib/settings/control/slider-widget-settings.component'; @NgModule({ declarations: [ @@ -440,7 +443,8 @@ import { SingleSwitchWidgetSettingsComponent, ActionButtonWidgetSettingsComponent, CommandButtonWidgetSettingsComponent, - PowerButtonWidgetSettingsComponent + PowerButtonWidgetSettingsComponent, + SliderWidgetSettingsComponent ], imports: [ CommonModule, @@ -562,7 +566,8 @@ import { SingleSwitchWidgetSettingsComponent, ActionButtonWidgetSettingsComponent, CommandButtonWidgetSettingsComponent, - PowerButtonWidgetSettingsComponent + PowerButtonWidgetSettingsComponent, + SliderWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -651,5 +656,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type { if (this.snackBarRef) { this.snackBarRef.dismiss(); diff --git a/ui-ngx/src/app/shared/models/action-widget-settings.models.ts b/ui-ngx/src/app/shared/models/action-widget-settings.models.ts index d7f4710f8d..a019f086dd 100644 --- a/ui-ngx/src/app/shared/models/action-widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/action-widget-settings.models.ts @@ -111,6 +111,7 @@ export const setValueActionTranslations = new Map( ); export enum ValueToDataType { + VALUE = 'VALUE', CONSTANT = 'CONSTANT', FUNCTION = 'FUNCTION', NONE = 'NONE' diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 19981797f8..c71f33fca7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6342,6 +6342,31 @@ "off-label": "Off label", "switch": "Switch" }, + "slider": { + "behavior": "Behavior", + "initial-value": "Initial value", + "initial-value-hint": "Action to get the initial value of the slider.", + "on-value-change": "On value change", + "on-value-change-hint": "Action triggered when the slider value is changed.", + "layout": "Layout", + "layout-default": "Default", + "layout-extended": "Extended", + "layout-simplified": "Simplified", + "auto-scale": "Auto scale", + "icon": "Icon", + "value": "Value", + "range": "Range", + "min": "min", + "max": "max", + "range-ticks": "Range ticks", + "tick-marks": "Tick marks", + "colors": "Colors", + "main": "Main", + "background": "Background", + "left-icon": "Left icon", + "right-icon": "Right icon", + "slider": "Slider" + }, "value-card": { "layout": "Layout", "layout-square": "Square", @@ -6613,6 +6638,7 @@ "converter-none": "None", "converter-function": "Function", "converter-constant": "Constant", + "converter-value": "Value", "parse-value-function": "Parse value function", "state-when-result-is": "'{{state}}' when result is", "parameters": "Parameters", diff --git a/ui-ngx/src/assets/widget/slider/default-layout.svg b/ui-ngx/src/assets/widget/slider/default-layout.svg new file mode 100644 index 0000000000..0b6d1bce33 --- /dev/null +++ b/ui-ngx/src/assets/widget/slider/default-layout.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/slider/extended-layout.svg b/ui-ngx/src/assets/widget/slider/extended-layout.svg new file mode 100644 index 0000000000..923ee8af3d --- /dev/null +++ b/ui-ngx/src/assets/widget/slider/extended-layout.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/slider/simplified-layout.svg b/ui-ngx/src/assets/widget/slider/simplified-layout.svg new file mode 100644 index 0000000000..d2e3936db0 --- /dev/null +++ b/ui-ngx/src/assets/widget/slider/simplified-layout.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +