mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-11-04 00:19:47 +00:00 
			
		
		
		
	Compare commits
	
		
			699 Commits
		
	
	
		
			20240228.1
			...
			dashboard_
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ebe5207b6e | ||
| 
						 | 
					bd1ede4145 | ||
| 
						 | 
					321a085c0e | ||
| 
						 | 
					6a3041988a | ||
| 
						 | 
					23fcdf876c | ||
| 
						 | 
					00f325e961 | ||
| 
						 | 
					d00b3cfc61 | ||
| 
						 | 
					4cc9e74ea8 | ||
| 
						 | 
					a56b9a96ce | ||
| 
						 | 
					d4b5f4bc14 | ||
| 
						 | 
					cf1523ee73 | ||
| 
						 | 
					f5d571ca84 | ||
| 
						 | 
					362e92f313 | ||
| 
						 | 
					5ddf72b973 | ||
| 
						 | 
					6e78c28f51 | ||
| 
						 | 
					772f0bb669 | ||
| 
						 | 
					846c2a848f | ||
| 
						 | 
					8495757005 | ||
| 
						 | 
					9960d38b91 | ||
| 
						 | 
					d3222f8bb0 | ||
| 
						 | 
					2e5cce5409 | ||
| 
						 | 
					f78946447f | ||
| 
						 | 
					eb0579ddc5 | ||
| 
						 | 
					686424fc70 | ||
| 
						 | 
					039e9b40bd | ||
| 
						 | 
					8272bef890 | ||
| 
						 | 
					62528b2413 | ||
| 
						 | 
					fa24f529e0 | ||
| 
						 | 
					43a54f6cda | ||
| 
						 | 
					9c153bbd58 | ||
| 
						 | 
					27afe9ecb7 | ||
| 
						 | 
					72f989e2bd | ||
| 
						 | 
					a6ef46565f | ||
| 
						 | 
					a35ac09688 | ||
| 
						 | 
					27024135ea | ||
| 
						 | 
					8759ed740a | ||
| 
						 | 
					bb3e8ae33d | ||
| 
						 | 
					b5b60c9bf0 | ||
| 
						 | 
					3b6a2cf7d8 | ||
| 
						 | 
					7e10e14102 | ||
| 
						 | 
					a580abab4a | ||
| 
						 | 
					11523c08c4 | ||
| 
						 | 
					7a8988528b | ||
| 
						 | 
					2a6380f083 | ||
| 
						 | 
					29881c8bb4 | ||
| 
						 | 
					56254ddf03 | ||
| 
						 | 
					007ba70641 | ||
| 
						 | 
					3e1227b064 | ||
| 
						 | 
					067e179f26 | ||
| 
						 | 
					9a3f7df25e | ||
| 
						 | 
					c7b4e8f37c | ||
| 
						 | 
					bfa8b886ab | ||
| 
						 | 
					433c00b73a | ||
| 
						 | 
					a497f42f73 | ||
| 
						 | 
					165723cb5b | ||
| 
						 | 
					42b5fa696a | ||
| 
						 | 
					59062d96a8 | ||
| 
						 | 
					d36bbfe07d | ||
| 
						 | 
					0d489213a4 | ||
| 
						 | 
					c54acc9369 | ||
| 
						 | 
					562bc084f0 | ||
| 
						 | 
					6fce2f35a5 | ||
| 
						 | 
					f4e24bed2e | ||
| 
						 | 
					09969c0e2d | ||
| 
						 | 
					4b0181774b | ||
| 
						 | 
					272db5e9e8 | ||
| 
						 | 
					9ae3a824d9 | ||
| 
						 | 
					9db55c9391 | ||
| 
						 | 
					59697127c0 | ||
| 
						 | 
					565600e945 | ||
| 
						 | 
					721eebf367 | ||
| 
						 | 
					f5ae842167 | ||
| 
						 | 
					3575734ed0 | ||
| 
						 | 
					4e8de1f64d | ||
| 
						 | 
					cd73b8ac29 | ||
| 
						 | 
					74eca6b1f5 | ||
| 
						 | 
					9ef0bd6e46 | ||
| 
						 | 
					53eb7f771f | ||
| 
						 | 
					cd62f064cb | ||
| 
						 | 
					db82b856e0 | ||
| 
						 | 
					ab340e13e9 | ||
| 
						 | 
					22c54b3fea | ||
| 
						 | 
					2dd7e598d5 | ||
| 
						 | 
					d1ce06e368 | ||
| 
						 | 
					9717304b68 | ||
| 
						 | 
					c646f3c39a | ||
| 
						 | 
					0297ec5a7b | ||
| 
						 | 
					e48286c2a0 | ||
| 
						 | 
					f2b2da9877 | ||
| 
						 | 
					250f87cfd8 | ||
| 
						 | 
					9f6afb162a | ||
| 
						 | 
					0add65feff | ||
| 
						 | 
					c08d9a9166 | ||
| 
						 | 
					d9a9038cec | ||
| 
						 | 
					6bee3ef45c | ||
| 
						 | 
					3a855f95ad | ||
| 
						 | 
					e7f3393ec6 | ||
| 
						 | 
					d7cb4cb537 | ||
| 
						 | 
					c55720c933 | ||
| 
						 | 
					f78e757485 | ||
| 
						 | 
					fa03c58a93 | ||
| 
						 | 
					49f1ad633f | ||
| 
						 | 
					d449e10120 | ||
| 
						 | 
					474c8c243e | ||
| 
						 | 
					e9e53e9451 | ||
| 
						 | 
					cfa84f30be | ||
| 
						 | 
					7416ae7dfd | ||
| 
						 | 
					6f1fa139e7 | ||
| 
						 | 
					b38a348957 | ||
| 
						 | 
					f97971faf6 | ||
| 
						 | 
					c5ae9e8497 | ||
| 
						 | 
					c00287c401 | ||
| 
						 | 
					c0e048023d | ||
| 
						 | 
					431f4937c1 | ||
| 
						 | 
					0a55220837 | ||
| 
						 | 
					13f01492b4 | ||
| 
						 | 
					ce5bcf61f9 | ||
| 
						 | 
					d31a777135 | ||
| 
						 | 
					5cc08cfe0b | ||
| 
						 | 
					3eea7dc6cd | ||
| 
						 | 
					a629f01300 | ||
| 
						 | 
					f1345af526 | ||
| 
						 | 
					064c51f487 | ||
| 
						 | 
					d88670034a | ||
| 
						 | 
					5fab1969a8 | ||
| 
						 | 
					b3e14d449e | ||
| 
						 | 
					97206ee8fe | ||
| 
						 | 
					7748315fc3 | ||
| 
						 | 
					e059ca146b | ||
| 
						 | 
					56cabeb497 | ||
| 
						 | 
					7a7bd87f50 | ||
| 
						 | 
					ff9c794659 | ||
| 
						 | 
					2921161336 | ||
| 
						 | 
					91e5fcacd5 | ||
| 
						 | 
					febbf34de6 | ||
| 
						 | 
					5a2977f4d4 | ||
| 
						 | 
					7a7a355765 | ||
| 
						 | 
					ccebae84a7 | ||
| 
						 | 
					f96f38ee82 | ||
| 
						 | 
					d4056e6a32 | ||
| 
						 | 
					389a7a6ed9 | ||
| 
						 | 
					05aecaaaf1 | ||
| 
						 | 
					085131d546 | ||
| 
						 | 
					c2737d5cec | ||
| 
						 | 
					29ae46d775 | ||
| 
						 | 
					dfee3ba089 | ||
| 
						 | 
					1dbb70b964 | ||
| 
						 | 
					1eecc5c0e2 | ||
| 
						 | 
					81c0bcff0b | ||
| 
						 | 
					6ccbeb8a75 | ||
| 
						 | 
					f59ed0a72b | ||
| 
						 | 
					a9f00ded0f | ||
| 
						 | 
					74730ba201 | ||
| 
						 | 
					95b2f7d821 | ||
| 
						 | 
					661b14da54 | ||
| 
						 | 
					41e34c0d61 | ||
| 
						 | 
					5d044a06eb | ||
| 
						 | 
					f617426808 | ||
| 
						 | 
					3c3d54243c | ||
| 
						 | 
					afc624bf4b | ||
| 
						 | 
					0991628843 | ||
| 
						 | 
					34b9c7b9d1 | ||
| 
						 | 
					d52641b495 | ||
| 
						 | 
					80c7fd2bf2 | ||
| 
						 | 
					e0062cf190 | ||
| 
						 | 
					7d2cee650d | ||
| 
						 | 
					66560b1f1c | ||
| 
						 | 
					a500b582e3 | ||
| 
						 | 
					19f94ff8cc | ||
| 
						 | 
					0b6994d402 | ||
| 
						 | 
					9fe8f507ec | ||
| 
						 | 
					2113cf5280 | ||
| 
						 | 
					ae9e1b724f | ||
| 
						 | 
					9b28c7cf69 | ||
| 
						 | 
					d9bac06806 | ||
| 
						 | 
					b1e37cb1e1 | ||
| 
						 | 
					a2a89502d8 | ||
| 
						 | 
					4cc5d2d04b | ||
| 
						 | 
					79abcca3b3 | ||
| 
						 | 
					043f383a35 | ||
| 
						 | 
					d4dd767941 | ||
| 
						 | 
					174f1991b1 | ||
| 
						 | 
					e15495a626 | ||
| 
						 | 
					a8a9a797cb | ||
| 
						 | 
					914dbc1e28 | ||
| 
						 | 
					111816f08a | ||
| 
						 | 
					1b4534890c | ||
| 
						 | 
					ed6542469d | ||
| 
						 | 
					3774a3d6ba | ||
| 
						 | 
					bfa293ae3a | ||
| 
						 | 
					9264adb799 | ||
| 
						 | 
					829ea4a9e4 | ||
| 
						 | 
					be26f8bc24 | ||
| 
						 | 
					c864b34a9a | ||
| 
						 | 
					099ea61a94 | ||
| 
						 | 
					3ebe6027be | ||
| 
						 | 
					f5f2a5ad5b | ||
| 
						 | 
					d046700d06 | ||
| 
						 | 
					2ad84b2832 | ||
| 
						 | 
					f9ccb9fc72 | ||
| 
						 | 
					6d3940db1e | ||
| 
						 | 
					20d174431d | ||
| 
						 | 
					1900710e06 | ||
| 
						 | 
					ed86a48e1c | ||
| 
						 | 
					d2bdb52926 | ||
| 
						 | 
					9c57c9f151 | ||
| 
						 | 
					9e9cb15a42 | ||
| 
						 | 
					6421a9443d | ||
| 
						 | 
					f2b43ddad8 | ||
| 
						 | 
					e55b59d9b7 | ||
| 
						 | 
					4a77359a06 | ||
| 
						 | 
					505d7b6ddb | ||
| 
						 | 
					79cdc43699 | ||
| 
						 | 
					8ff9823cd7 | ||
| 
						 | 
					3488c60818 | ||
| 
						 | 
					b2af21ba5c | ||
| 
						 | 
					12a61a0021 | ||
| 
						 | 
					649917cdde | ||
| 
						 | 
					3ed27ee853 | ||
| 
						 | 
					c1d3a76917 | ||
| 
						 | 
					571ed6b9e9 | ||
| 
						 | 
					a347315fa7 | ||
| 
						 | 
					57d1405115 | ||
| 
						 | 
					e5ff6bd2f5 | ||
| 
						 | 
					43a422cdca | ||
| 
						 | 
					11f2bef05c | ||
| 
						 | 
					ff9f331287 | ||
| 
						 | 
					cdf64ccdaa | ||
| 
						 | 
					8b220acca2 | ||
| 
						 | 
					8fdb7fa1d5 | ||
| 
						 | 
					008c842431 | ||
| 
						 | 
					bc41de0d9c | ||
| 
						 | 
					7310c9cf6d | ||
| 
						 | 
					84b436c08e | ||
| 
						 | 
					1925a47bdc | ||
| 
						 | 
					438a426458 | ||
| 
						 | 
					f923deb71d | ||
| 
						 | 
					e79bc71ab7 | ||
| 
						 | 
					11b0990d2b | ||
| 
						 | 
					870cb0c65f | ||
| 
						 | 
					deda2009f8 | ||
| 
						 | 
					b2797ab8da | ||
| 
						 | 
					644dcb0381 | ||
| 
						 | 
					c65f4f7a6e | ||
| 
						 | 
					e2266aa671 | ||
| 
						 | 
					68a79490dc | ||
| 
						 | 
					6febe8552e | ||
| 
						 | 
					f611f23f6f | ||
| 
						 | 
					ef4f11fdf8 | ||
| 
						 | 
					627e06663b | ||
| 
						 | 
					ab01633069 | ||
| 
						 | 
					17dcc90638 | ||
| 
						 | 
					d0df029ff1 | ||
| 
						 | 
					86a7e69812 | ||
| 
						 | 
					af9417f2a6 | ||
| 
						 | 
					7120ad99b9 | ||
| 
						 | 
					334c245b65 | ||
| 
						 | 
					bcb72d83b8 | ||
| 
						 | 
					c99e0e846b | ||
| 
						 | 
					ec3f63e8a3 | ||
| 
						 | 
					1bc33a30ec | ||
| 
						 | 
					8cca233b7c | ||
| 
						 | 
					a78608bfb4 | ||
| 
						 | 
					e7c1ac94af | ||
| 
						 | 
					1a797b3415 | ||
| 
						 | 
					2b27a4da2b | ||
| 
						 | 
					1df92fa863 | ||
| 
						 | 
					cdde85315a | ||
| 
						 | 
					dc67f9faf4 | ||
| 
						 | 
					3ad1be50a2 | ||
| 
						 | 
					8aadfe7d28 | ||
| 
						 | 
					cff54b73a4 | ||
| 
						 | 
					b54cfeb0c0 | ||
| 
						 | 
					cefe612b11 | ||
| 
						 | 
					4bc874b497 | ||
| 
						 | 
					f3abaa8e02 | ||
| 
						 | 
					21a563fe98 | ||
| 
						 | 
					1acbcccd62 | ||
| 
						 | 
					35d6c638ab | ||
| 
						 | 
					68f8239708 | ||
| 
						 | 
					0db64cca0b | ||
| 
						 | 
					accfda5f4b | ||
| 
						 | 
					c97c20f57d | ||
| 
						 | 
					2725d0191d | ||
| 
						 | 
					852cc62398 | ||
| 
						 | 
					654e3ce437 | ||
| 
						 | 
					20a3a00aec | ||
| 
						 | 
					22b927d666 | ||
| 
						 | 
					709d6be2e3 | ||
| 
						 | 
					64f54d9aaa | ||
| 
						 | 
					fbda9ca418 | ||
| 
						 | 
					4e97e3763e | ||
| 
						 | 
					4c9c52d27d | ||
| 
						 | 
					87bcd3e471 | ||
| 
						 | 
					7e9b01b56d | ||
| 
						 | 
					713763fc21 | ||
| 
						 | 
					5b7ab1bfcb | ||
| 
						 | 
					8712adbf8d | ||
| 
						 | 
					4b0d19b615 | ||
| 
						 | 
					90e5d259af | ||
| 
						 | 
					af3a331f57 | ||
| 
						 | 
					67c60a4aa8 | ||
| 
						 | 
					62de16bb8e | ||
| 
						 | 
					d9b71e754d | ||
| 
						 | 
					5fc950f09f | ||
| 
						 | 
					0725c7b160 | ||
| 
						 | 
					469dbbcccc | ||
| 
						 | 
					ffdd661b1f | ||
| 
						 | 
					81922f5a3e | ||
| 
						 | 
					7e25366897 | ||
| 
						 | 
					8ab61b5468 | ||
| 
						 | 
					8239f6dd60 | ||
| 
						 | 
					45dce18e4d | ||
| 
						 | 
					a428ad0655 | ||
| 
						 | 
					1b54d51e4a | ||
| 
						 | 
					eb1354d229 | ||
| 
						 | 
					4d21f9e80c | ||
| 
						 | 
					62f46baacf | ||
| 
						 | 
					a3090796d2 | ||
| 
						 | 
					c34c5d64f9 | ||
| 
						 | 
					66228f5858 | ||
| 
						 | 
					ac378cfe6d | ||
| 
						 | 
					7ecf8b755e | ||
| 
						 | 
					141107f1f3 | ||
| 
						 | 
					b5277dee53 | ||
| 
						 | 
					4b593c1c96 | ||
| 
						 | 
					50ce1b94c8 | ||
| 
						 | 
					8bf27a83ec | ||
| 
						 | 
					389f0d3d23 | ||
| 
						 | 
					b966601e6a | ||
| 
						 | 
					f2a0881821 | ||
| 
						 | 
					50a49eae43 | ||
| 
						 | 
					1c04561004 | ||
| 
						 | 
					b80d94d260 | ||
| 
						 | 
					87012e23e7 | ||
| 
						 | 
					f39758b103 | ||
| 
						 | 
					697bbf428e | ||
| 
						 | 
					c7444a2605 | ||
| 
						 | 
					3a5f4d33d2 | ||
| 
						 | 
					c3dc62523b | ||
| 
						 | 
					424622061a | ||
| 
						 | 
					a3b021b11d | ||
| 
						 | 
					b60ad8b143 | ||
| 
						 | 
					e376efc579 | ||
| 
						 | 
					382035a1d4 | ||
| 
						 | 
					542e22fe0e | ||
| 
						 | 
					af37d57779 | ||
| 
						 | 
					fbef0b0186 | ||
| 
						 | 
					9e67d6add8 | ||
| 
						 | 
					25c702ad2b | ||
| 
						 | 
					6516597c93 | ||
| 
						 | 
					1df9c38a8c | ||
| 
						 | 
					bd7217145a | ||
| 
						 | 
					569fef38a4 | ||
| 
						 | 
					f21c89cf1a | ||
| 
						 | 
					02cc418969 | ||
| 
						 | 
					4faba159c0 | ||
| 
						 | 
					29816e6c5e | ||
| 
						 | 
					5317a11c39 | ||
| 
						 | 
					27c53b3241 | ||
| 
						 | 
					919befa961 | ||
| 
						 | 
					f9c02ed099 | ||
| 
						 | 
					b35c325f43 | ||
| 
						 | 
					b82f1128fe | ||
| 
						 | 
					178feb7330 | ||
| 
						 | 
					0118a5bf4c | ||
| 
						 | 
					e0087bd142 | ||
| 
						 | 
					c2d3e7900e | ||
| 
						 | 
					fb8312110b | ||
| 
						 | 
					16de57342e | ||
| 
						 | 
					ad6e041c04 | ||
| 
						 | 
					e22e3e88a0 | ||
| 
						 | 
					dc8a50965c | ||
| 
						 | 
					1914de7ddf | ||
| 
						 | 
					2e505cfb1f | ||
| 
						 | 
					ab49aca815 | ||
| 
						 | 
					c96968e476 | ||
| 
						 | 
					8f050516ec | ||
| 
						 | 
					27d2b244a4 | ||
| 
						 | 
					be2f2c6271 | ||
| 
						 | 
					8dc2797b16 | ||
| 
						 | 
					7ca8dabc44 | ||
| 
						 | 
					baeb55e217 | ||
| 
						 | 
					a8502fcc11 | ||
| 
						 | 
					9f5bc5b196 | ||
| 
						 | 
					7556ab9506 | ||
| 
						 | 
					bf176ac314 | ||
| 
						 | 
					9903e22eaa | ||
| 
						 | 
					1e0f7d9629 | ||
| 
						 | 
					e8a140af44 | ||
| 
						 | 
					b091d4f298 | ||
| 
						 | 
					35cf3063cb | ||
| 
						 | 
					7141ef17be | ||
| 
						 | 
					be2c68c0bb | ||
| 
						 | 
					7c944d3767 | ||
| 
						 | 
					1d4f02df2e | ||
| 
						 | 
					2007a74a20 | ||
| 
						 | 
					8c0839ad57 | ||
| 
						 | 
					516b9a54c4 | ||
| 
						 | 
					0d3e730c9c | ||
| 
						 | 
					c7a87d02b2 | ||
| 
						 | 
					dd082c204b | ||
| 
						 | 
					c4af3d1579 | ||
| 
						 | 
					10eadbcbbb | ||
| 
						 | 
					17141824f7 | ||
| 
						 | 
					4cfd6c010f | ||
| 
						 | 
					daa9024bff | ||
| 
						 | 
					e96aca90fe | ||
| 
						 | 
					0580a31961 | ||
| 
						 | 
					5c42c5130c | ||
| 
						 | 
					72d1e37a23 | ||
| 
						 | 
					61c9072a08 | ||
| 
						 | 
					08b25f9c2a | ||
| 
						 | 
					1a03b49700 | ||
| 
						 | 
					2d4a8e2e45 | ||
| 
						 | 
					8486377604 | ||
| 
						 | 
					3a4e9b6856 | ||
| 
						 | 
					5f5ac5419b | ||
| 
						 | 
					92b7a3b477 | ||
| 
						 | 
					4326519a3f | ||
| 
						 | 
					00837acdfc | ||
| 
						 | 
					7704be12b1 | ||
| 
						 | 
					712ddb531b | ||
| 
						 | 
					d52afc3f71 | ||
| 
						 | 
					92f6083e0b | ||
| 
						 | 
					5751fdbe56 | ||
| 
						 | 
					962b30adb9 | ||
| 
						 | 
					3b5b3f3bb6 | ||
| 
						 | 
					1a6d96cf3a | ||
| 
						 | 
					034fd9b4df | ||
| 
						 | 
					eb79a1e7d7 | ||
| 
						 | 
					e25d4f17aa | ||
| 
						 | 
					ccde9cceee | ||
| 
						 | 
					578d3c4260 | ||
| 
						 | 
					bfdc9a3d86 | ||
| 
						 | 
					5315545a4d | ||
| 
						 | 
					82a3b9d80f | ||
| 
						 | 
					3de985a3b8 | ||
| 
						 | 
					567ee8000d | ||
| 
						 | 
					03939001b2 | ||
| 
						 | 
					30d18050d1 | ||
| 
						 | 
					95caf8c7df | ||
| 
						 | 
					6c1f328d71 | ||
| 
						 | 
					bb20ab8c2c | ||
| 
						 | 
					29eb73176a | ||
| 
						 | 
					17ad3a87f3 | ||
| 
						 | 
					ed7c9c33b9 | ||
| 
						 | 
					59b66219cb | ||
| 
						 | 
					1e2c1d1464 | ||
| 
						 | 
					5b86b1277f | ||
| 
						 | 
					41fdf31e34 | ||
| 
						 | 
					9bef5c2af9 | ||
| 
						 | 
					ed1a69071b | ||
| 
						 | 
					56d328b4db | ||
| 
						 | 
					33c7e0fa2d | ||
| 
						 | 
					4f1cf1110f | ||
| 
						 | 
					a434bfd944 | ||
| 
						 | 
					21ed8e4206 | ||
| 
						 | 
					169d782580 | ||
| 
						 | 
					8a015f4e38 | ||
| 
						 | 
					cbb08c6202 | ||
| 
						 | 
					6301bc713c | ||
| 
						 | 
					a5d7043ce4 | ||
| 
						 | 
					912d2cbd79 | ||
| 
						 | 
					48ee3a34eb | ||
| 
						 | 
					21263a1ffb | ||
| 
						 | 
					db59e138e9 | ||
| 
						 | 
					bc8012dcc9 | ||
| 
						 | 
					d8b43597a0 | ||
| 
						 | 
					d3bf0da289 | ||
| 
						 | 
					871949e760 | ||
| 
						 | 
					4fb42d3545 | ||
| 
						 | 
					2e58d6656c | ||
| 
						 | 
					a3024b38e9 | ||
| 
						 | 
					85f2016371 | ||
| 
						 | 
					1ce3347c2e | ||
| 
						 | 
					4f8415e8a7 | ||
| 
						 | 
					b202a36feb | ||
| 
						 | 
					7e3e224746 | ||
| 
						 | 
					503a7979d0 | ||
| 
						 | 
					f3ba6e7996 | ||
| 
						 | 
					f13dcb4139 | ||
| 
						 | 
					e8dc61ec36 | ||
| 
						 | 
					88c59c5c13 | ||
| 
						 | 
					fd06d434f2 | ||
| 
						 | 
					85f80ff863 | ||
| 
						 | 
					d56abe6b72 | ||
| 
						 | 
					d24d29e42f | ||
| 
						 | 
					bc14b8468d | ||
| 
						 | 
					f924f81ec1 | ||
| 
						 | 
					3a6382df55 | ||
| 
						 | 
					1dba049038 | ||
| 
						 | 
					f539516252 | ||
| 
						 | 
					abd02eda0f | ||
| 
						 | 
					99695d6cb3 | ||
| 
						 | 
					cb1c2b59df | ||
| 
						 | 
					8368f977b9 | ||
| 
						 | 
					e05595f318 | ||
| 
						 | 
					11cf2ec39d | ||
| 
						 | 
					e5c43fcfcd | ||
| 
						 | 
					520581c165 | ||
| 
						 | 
					d1119a3b61 | ||
| 
						 | 
					5dd029cc05 | ||
| 
						 | 
					510e010f97 | ||
| 
						 | 
					1300cffa3b | ||
| 
						 | 
					8fbcbb0b68 | ||
| 
						 | 
					e02a47a16a | ||
| 
						 | 
					7b26c1ffcb | ||
| 
						 | 
					d3e62454a5 | ||
| 
						 | 
					6b8f4e92a7 | ||
| 
						 | 
					b590b21183 | ||
| 
						 | 
					a08484f450 | ||
| 
						 | 
					2978ca13c5 | ||
| 
						 | 
					31c0850b14 | ||
| 
						 | 
					1d85f0717a | ||
| 
						 | 
					795c16a941 | ||
| 
						 | 
					55c8589841 | ||
| 
						 | 
					4687add37a | ||
| 
						 | 
					c25e23ccd6 | ||
| 
						 | 
					e42ddb8f0f | ||
| 
						 | 
					705c0e58fc | ||
| 
						 | 
					7427e17926 | ||
| 
						 | 
					2c4b31dcaa | ||
| 
						 | 
					ae8671af96 | ||
| 
						 | 
					f5ff55abc5 | ||
| 
						 | 
					b662512995 | ||
| 
						 | 
					64c3fb1723 | ||
| 
						 | 
					fb99dc4cd0 | ||
| 
						 | 
					e08a0c44ba | ||
| 
						 | 
					68935d46ce | ||
| 
						 | 
					141c8c5192 | ||
| 
						 | 
					7ca5467f4c | ||
| 
						 | 
					5de53964d9 | ||
| 
						 | 
					8d8807e659 | ||
| 
						 | 
					9347944cbd | ||
| 
						 | 
					480448acbb | ||
| 
						 | 
					202fa82646 | ||
| 
						 | 
					feecc9f838 | ||
| 
						 | 
					2f9e667517 | ||
| 
						 | 
					5547bc7356 | ||
| 
						 | 
					eb4ae926b7 | ||
| 
						 | 
					b239ec2b71 | ||
| 
						 | 
					e9cac94aee | ||
| 
						 | 
					5289cd3af1 | ||
| 
						 | 
					45a5c1c235 | ||
| 
						 | 
					db3709952c | ||
| 
						 | 
					447932eedb | ||
| 
						 | 
					10cc3bdd3f | ||
| 
						 | 
					6ee2bfed36 | ||
| 
						 | 
					01efb831b7 | ||
| 
						 | 
					9e1e20bd94 | ||
| 
						 | 
					869ace74ad | ||
| 
						 | 
					94d56367fc | ||
| 
						 | 
					68a5ba668e | ||
| 
						 | 
					b2b590cf67 | ||
| 
						 | 
					6f7c071769 | ||
| 
						 | 
					c1a7164ce7 | ||
| 
						 | 
					b77839c139 | ||
| 
						 | 
					e2f2a9322c | ||
| 
						 | 
					e4bd6c885d | ||
| 
						 | 
					8201701d17 | ||
| 
						 | 
					a5e6b78e1d | ||
| 
						 | 
					027eccba06 | ||
| 
						 | 
					12f10513f0 | ||
| 
						 | 
					9907ed51f0 | ||
| 
						 | 
					90e9f79841 | ||
| 
						 | 
					c30b9cdfcf | ||
| 
						 | 
					7e1fa0cf38 | ||
| 
						 | 
					b6587488d4 | ||
| 
						 | 
					552eeeddf6 | ||
| 
						 | 
					cbc150bad2 | ||
| 
						 | 
					8a4ed121b5 | ||
| 
						 | 
					a9793dc0a5 | ||
| 
						 | 
					c9deef84ca | ||
| 
						 | 
					1582aaeb4c | ||
| 
						 | 
					707520c15c | ||
| 
						 | 
					d5de435f06 | ||
| 
						 | 
					9e3dfaa400 | ||
| 
						 | 
					7aa92ec249 | ||
| 
						 | 
					2fdcd40f00 | ||
| 
						 | 
					3b15b786ff | ||
| 
						 | 
					b212b30e58 | ||
| 
						 | 
					6fd89f8585 | ||
| 
						 | 
					0406d21703 | ||
| 
						 | 
					293f89a07b | ||
| 
						 | 
					520a0b4075 | ||
| 
						 | 
					488602e232 | ||
| 
						 | 
					1e8d353162 | ||
| 
						 | 
					b3718b8b4a | ||
| 
						 | 
					097cba5c60 | ||
| 
						 | 
					fa6d8d0891 | ||
| 
						 | 
					31797c55df | ||
| 
						 | 
					56a23c5c3d | ||
| 
						 | 
					adc89f1487 | ||
| 
						 | 
					7facc375bc | ||
| 
						 | 
					91d3fb0ea8 | ||
| 
						 | 
					4ab0047dc1 | ||
| 
						 | 
					279eeaa442 | ||
| 
						 | 
					d4d0fb2a03 | ||
| 
						 | 
					d56fe8a542 | ||
| 
						 | 
					292701925d | ||
| 
						 | 
					52fc854cc3 | ||
| 
						 | 
					90ca039768 | ||
| 
						 | 
					9e81055070 | ||
| 
						 | 
					cea402ebf8 | ||
| 
						 | 
					6b939b95c0 | ||
| 
						 | 
					b24621d1ea | ||
| 
						 | 
					cc0fde2c08 | ||
| 
						 | 
					3732998fb7 | ||
| 
						 | 
					c699e265ef | ||
| 
						 | 
					98bb726f1a | ||
| 
						 | 
					c132e7ed85 | ||
| 
						 | 
					298cebe17f | ||
| 
						 | 
					d03825d200 | ||
| 
						 | 
					d9ab9db211 | ||
| 
						 | 
					58a607561a | ||
| 
						 | 
					0ae1f11ffc | ||
| 
						 | 
					db48c5a6a3 | ||
| 
						 | 
					effefdbff1 | ||
| 
						 | 
					233c969402 | ||
| 
						 | 
					3b885dd01f | ||
| 
						 | 
					52c8554d89 | ||
| 
						 | 
					b55baef985 | ||
| 
						 | 
					b593b15f27 | ||
| 
						 | 
					00669ac0c3 | ||
| 
						 | 
					33a4258c06 | ||
| 
						 | 
					d4a8fcbe03 | ||
| 
						 | 
					36c3b938ce | ||
| 
						 | 
					bf2fad2a2a | ||
| 
						 | 
					9cbd49b867 | ||
| 
						 | 
					9de59131f4 | ||
| 
						 | 
					7f44e89829 | ||
| 
						 | 
					572e4457b3 | ||
| 
						 | 
					a5bcf87c08 | ||
| 
						 | 
					8ca5b7528b | ||
| 
						 | 
					d951e68c10 | ||
| 
						 | 
					32e8d2043c | ||
| 
						 | 
					bf028915ec | ||
| 
						 | 
					b03f483e4f | ||
| 
						 | 
					6f6202eb69 | ||
| 
						 | 
					7ab2d1496e | ||
| 
						 | 
					acc229a7e1 | ||
| 
						 | 
					64ffa86fe3 | ||
| 
						 | 
					8b77024fb9 | ||
| 
						 | 
					42aa18ac16 | ||
| 
						 | 
					54d21666d0 | ||
| 
						 | 
					aac00a5e78 | ||
| 
						 | 
					63d93f2a36 | ||
| 
						 | 
					a9f453ea36 | ||
| 
						 | 
					d248de92e5 | ||
| 
						 | 
					0ed483ba51 | ||
| 
						 | 
					68fbadf21b | ||
| 
						 | 
					ac66079d41 | ||
| 
						 | 
					56c681bcf8 | ||
| 
						 | 
					c5c4253760 | ||
| 
						 | 
					84e6f2fc4f | ||
| 
						 | 
					8cedaae645 | ||
| 
						 | 
					e350ba4726 | ||
| 
						 | 
					1b7742ef7f | ||
| 
						 | 
					0c6bf701c7 | ||
| 
						 | 
					05e2e305e4 | ||
| 
						 | 
					5523cd6203 | ||
| 
						 | 
					50da4bcd37 | ||
| 
						 | 
					b99072d986 | ||
| 
						 | 
					b9a7a7c422 | ||
| 
						 | 
					88ccbcd883 | ||
| 
						 | 
					b5bb6c6fe5 | ||
| 
						 | 
					19a3810168 | ||
| 
						 | 
					8ccc38eb00 | ||
| 
						 | 
					70146a08c1 | ||
| 
						 | 
					19d50b9c92 | ||
| 
						 | 
					05c1328ca7 | ||
| 
						 | 
					99c2dd9765 | ||
| 
						 | 
					edbe6851f7 | ||
| 
						 | 
					a7867a9253 | ||
| 
						 | 
					94e70f81ed | ||
| 
						 | 
					3d8654253a | ||
| 
						 | 
					69dbcec678 | ||
| 
						 | 
					de8b0ba8c5 | ||
| 
						 | 
					730cd9f983 | ||
| 
						 | 
					67d8765624 | ||
| 
						 | 
					39bd07de73 | ||
| 
						 | 
					3202ea55d2 | ||
| 
						 | 
					329a8c0c90 | ||
| 
						 | 
					c05824c641 | ||
| 
						 | 
					3abdffda9c | ||
| 
						 | 
					67da851efc | ||
| 
						 | 
					5463a27255 | ||
| 
						 | 
					ec0434c9b0 | ||
| 
						 | 
					7d8cb5c863 | ||
| 
						 | 
					4f01348ffb | ||
| 
						 | 
					2af3400464 | ||
| 
						 | 
					b6e220a4c5 | ||
| 
						 | 
					d5d45f100e | ||
| 
						 | 
					6b9ca60c47 | ||
| 
						 | 
					bc445a1e27 | ||
| 
						 | 
					a087b4c43e | ||
| 
						 | 
					8f67ddf968 | ||
| 
						 | 
					9ef07484dd | 
@@ -1,5 +1,5 @@
 | 
			
		||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
 | 
			
		||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.11
 | 
			
		||||
FROM mcr.microsoft.com/devcontainers/python:3.12
 | 
			
		||||
 | 
			
		||||
ENV \
 | 
			
		||||
  DEBIAN_FRONTEND=noninteractive \
 | 
			
		||||
 
 | 
			
		||||
@@ -115,6 +115,7 @@
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "unused-imports/no-unused-imports": "error",
 | 
			
		||||
    "lit/attribute-names": "warn",
 | 
			
		||||
    "lit/attribute-value-entities": "off",
 | 
			
		||||
    "lit/no-template-map": "off",
 | 
			
		||||
    "lit/no-native-attributes": "warn",
 | 
			
		||||
@@ -125,6 +126,5 @@
 | 
			
		||||
    "lit-a11y/anchor-is-valid": "warn",
 | 
			
		||||
    "lit-a11y/role-has-required-aria-attrs": "warn"
 | 
			
		||||
  },
 | 
			
		||||
  "plugins": ["disable", "unused-imports"],
 | 
			
		||||
  "processor": "disable/disable"
 | 
			
		||||
  "plugins": ["unused-imports"]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							@@ -21,7 +21,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        with:
 | 
			
		||||
          ref: dev
 | 
			
		||||
 | 
			
		||||
@@ -57,7 +57,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        with:
 | 
			
		||||
          ref: master
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							@@ -24,7 +24,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
        with:
 | 
			
		||||
@@ -37,7 +37,7 @@ jobs:
 | 
			
		||||
      - name: Build resources
 | 
			
		||||
        run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
 | 
			
		||||
      - name: Setup lint cache
 | 
			
		||||
        uses: actions/cache@v4.0.0
 | 
			
		||||
        uses: actions/cache@v4.0.2
 | 
			
		||||
        with:
 | 
			
		||||
          path: |
 | 
			
		||||
            node_modules/.cache/prettier
 | 
			
		||||
@@ -58,7 +58,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
        with:
 | 
			
		||||
@@ -76,7 +76,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
        with:
 | 
			
		||||
@@ -89,7 +89,7 @@ jobs:
 | 
			
		||||
        env:
 | 
			
		||||
          IS_TEST: "true"
 | 
			
		||||
      - name: Upload bundle stats
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.1
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.3
 | 
			
		||||
        with:
 | 
			
		||||
          name: frontend-bundle-stats
 | 
			
		||||
          path: build/stats/*.json
 | 
			
		||||
@@ -100,7 +100,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
        with:
 | 
			
		||||
@@ -113,7 +113,7 @@ jobs:
 | 
			
		||||
        env:
 | 
			
		||||
          IS_TEST: "true"
 | 
			
		||||
      - name: Upload bundle stats
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.1
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.3
 | 
			
		||||
        with:
 | 
			
		||||
          name: supervisor-bundle-stats
 | 
			
		||||
          path: build/stats/*.json
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							@@ -23,7 +23,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout repository
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        with:
 | 
			
		||||
          # We must fetch at least the immediate parents so that if this is
 | 
			
		||||
          # a pull request then we can checkout the head.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							@@ -22,7 +22,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        with:
 | 
			
		||||
          ref: dev
 | 
			
		||||
 | 
			
		||||
@@ -58,7 +58,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        with:
 | 
			
		||||
          ref: master
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							@@ -16,7 +16,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							@@ -21,7 +21,7 @@ jobs:
 | 
			
		||||
    if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							@@ -6,7 +6,7 @@ on:
 | 
			
		||||
    - cron: "0 1 * * *"
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  PYTHON_VERSION: "3.11"
 | 
			
		||||
  PYTHON_VERSION: "3.12"
 | 
			
		||||
  NODE_OPTIONS: --max_old_space_size=6144
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
@@ -20,7 +20,7 @@ jobs:
 | 
			
		||||
      contents: write
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
 | 
			
		||||
      - name: Set up Python ${{ env.PYTHON_VERSION }}
 | 
			
		||||
        uses: actions/setup-python@v5
 | 
			
		||||
@@ -57,14 +57,14 @@ jobs:
 | 
			
		||||
        run: tar -czvf translations.tar.gz translations
 | 
			
		||||
 | 
			
		||||
      - name: Upload build artifacts
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.1
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.3
 | 
			
		||||
        with:
 | 
			
		||||
          name: wheels
 | 
			
		||||
          path: dist/home_assistant_frontend*.whl
 | 
			
		||||
          if-no-files-found: error
 | 
			
		||||
 | 
			
		||||
      - name: Upload translations
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.1
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.3
 | 
			
		||||
        with:
 | 
			
		||||
          name: translations
 | 
			
		||||
          path: translations.tar.gz
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							@@ -17,7 +17,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Send bundle stats and build information to RelativeCI
 | 
			
		||||
        uses: relative-ci/agent-action@v2.1.10
 | 
			
		||||
        uses: relative-ci/agent-action@v2.1.11
 | 
			
		||||
        with:
 | 
			
		||||
          key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
 | 
			
		||||
          token: ${{ github.token }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							@@ -6,7 +6,7 @@ on:
 | 
			
		||||
      - published
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  PYTHON_VERSION: "3.11"
 | 
			
		||||
  PYTHON_VERSION: "3.12"
 | 
			
		||||
  NODE_OPTIONS: --max_old_space_size=6144
 | 
			
		||||
 | 
			
		||||
# Set default workflow permissions
 | 
			
		||||
@@ -23,7 +23,7 @@ jobs:
 | 
			
		||||
      contents: write # Required to upload release assets
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
 | 
			
		||||
      - name: Verify version
 | 
			
		||||
        uses: home-assistant/actions/helpers/verify-version@master
 | 
			
		||||
@@ -55,7 +55,7 @@ jobs:
 | 
			
		||||
          script/release
 | 
			
		||||
 | 
			
		||||
      - name: Upload release assets
 | 
			
		||||
        uses: softprops/action-gh-release@v0.1.15
 | 
			
		||||
        uses: softprops/action-gh-release@v2.0.6
 | 
			
		||||
        with:
 | 
			
		||||
          files: |
 | 
			
		||||
            dist/*.whl
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							@@ -13,7 +13,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
 | 
			
		||||
      - name: Upload Translations
 | 
			
		||||
        run: |
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
diff --git a/simple-tooltip.js b/simple-tooltip.js
 | 
			
		||||
index 78a87f6a223925f0e29fbedb268c85a142ec6985..3d686dd6a3d5a93342b4b01408089fc316b408ca 100644
 | 
			
		||||
--- a/simple-tooltip.js
 | 
			
		||||
+++ b/simple-tooltip.js
 | 
			
		||||
@@ -195,6 +195,8 @@ class SimpleTooltip extends LitElement {
 | 
			
		||||
         .hidden {
 | 
			
		||||
           position: absolute;
 | 
			
		||||
           left: -10000px;
 | 
			
		||||
+          inset-inline-start: -10000px;
 | 
			
		||||
+          inset-inline-end: initial;
 | 
			
		||||
           top: auto;
 | 
			
		||||
           width: 1px;
 | 
			
		||||
           height: 1px;
 | 
			
		||||
							
								
								
									
										18
									
								
								.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
diff --git a/dist/hls.light.mjs b/dist/hls.light.mjs
 | 
			
		||||
index eed9d788fafdb159975e1a2eb08ac88ba9c9ac33..ace881935e6665946f1c8110ebd2f739cde4427e 100644
 | 
			
		||||
--- a/dist/hls.light.mjs
 | 
			
		||||
+++ b/dist/hls.light.mjs
 | 
			
		||||
@@ -20523,9 +20523,9 @@ class Hls {
 | 
			
		||||
 }
 | 
			
		||||
 Hls.defaultConfig = void 0;
 | 
			
		||||
 
 | 
			
		||||
-var KeySystemFormats = empty.KeySystemFormats;
 | 
			
		||||
-var KeySystems = empty.KeySystems;
 | 
			
		||||
-var SubtitleStreamController = empty.SubtitleStreamController;
 | 
			
		||||
-var TimelineController = empty.TimelineController;
 | 
			
		||||
+var KeySystemFormats = empty;
 | 
			
		||||
+var KeySystems = empty;
 | 
			
		||||
+var SubtitleStreamController = empty;
 | 
			
		||||
+var TimelineController = empty;
 | 
			
		||||
 export { AbrController, AttrList, Cues as AudioStreamController, Cues as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, Cues as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, Cues as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, Cues as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
 | 
			
		||||
 //# sourceMappingURL=hls.light.mjs.map
 | 
			
		||||
							
								
								
									
										893
									
								
								.yarn/releases/yarn-4.1.0.cjs
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										893
									
								
								.yarn/releases/yarn-4.1.0.cjs
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										894
									
								
								.yarn/releases/yarn-4.3.1.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										894
									
								
								.yarn/releases/yarn-4.3.1.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -6,4 +6,4 @@ enableGlobalCache: false
 | 
			
		||||
 | 
			
		||||
nodeLinker: node-modules
 | 
			
		||||
 | 
			
		||||
yarnPath: .yarn/releases/yarn-4.1.0.cjs
 | 
			
		||||
yarnPath: .yarn/releases/yarn-4.3.1.cjs
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,56 @@
 | 
			
		||||
import defineProvider from "@babel/helper-define-polyfill-provider";
 | 
			
		||||
import { join } from "node:path";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
 | 
			
		||||
const POLYFILL_DIR = join(paths.polymer_dir, "src/resources/polyfills");
 | 
			
		||||
 | 
			
		||||
// List of polyfill keys with supported browser targets for the functionality
 | 
			
		||||
const PolyfillSupport = {
 | 
			
		||||
  // Note states and shadowRoot properties should be supported.
 | 
			
		||||
  "element-internals": {
 | 
			
		||||
    android: 90,
 | 
			
		||||
    chrome: 90,
 | 
			
		||||
    edge: 90,
 | 
			
		||||
    firefox: 126,
 | 
			
		||||
    ios: 17.4,
 | 
			
		||||
    opera: 76,
 | 
			
		||||
    opera_mobile: 64,
 | 
			
		||||
    safari: 17.4,
 | 
			
		||||
    samsung: 15.0,
 | 
			
		||||
  },
 | 
			
		||||
  "element-append": {
 | 
			
		||||
    android: 54,
 | 
			
		||||
    chrome: 54,
 | 
			
		||||
    edge: 17,
 | 
			
		||||
    firefox: 49,
 | 
			
		||||
    ios: 10.0,
 | 
			
		||||
    opera: 41,
 | 
			
		||||
    opera_mobile: 41,
 | 
			
		||||
    safari: 10.0,
 | 
			
		||||
    samsung: 6.0,
 | 
			
		||||
  },
 | 
			
		||||
  "element-getattributenames": {
 | 
			
		||||
    android: 61,
 | 
			
		||||
    chrome: 61,
 | 
			
		||||
    edge: 18,
 | 
			
		||||
    firefox: 45,
 | 
			
		||||
    ios: 10.3,
 | 
			
		||||
    opera: 48,
 | 
			
		||||
    opera_mobile: 45,
 | 
			
		||||
    safari: 10.1,
 | 
			
		||||
    samsung: 8.0,
 | 
			
		||||
  },
 | 
			
		||||
  "element-toggleattribute": {
 | 
			
		||||
    android: 69,
 | 
			
		||||
    chrome: 69,
 | 
			
		||||
    edge: 18,
 | 
			
		||||
    firefox: 63,
 | 
			
		||||
    ios: 12.0,
 | 
			
		||||
    opera: 56,
 | 
			
		||||
    opera_mobile: 48,
 | 
			
		||||
    safari: 12.0,
 | 
			
		||||
    samsung: 10.0,
 | 
			
		||||
  },
 | 
			
		||||
  fetch: {
 | 
			
		||||
    android: 42,
 | 
			
		||||
    chrome: 42,
 | 
			
		||||
@@ -13,6 +62,31 @@ const PolyfillSupport = {
 | 
			
		||||
    safari: 10.1,
 | 
			
		||||
    samsung: 4.0,
 | 
			
		||||
  },
 | 
			
		||||
  "intl-getcanonicallocales": {
 | 
			
		||||
    android: 54,
 | 
			
		||||
    chrome: 54,
 | 
			
		||||
    edge: 16,
 | 
			
		||||
    firefox: 48,
 | 
			
		||||
    ios: 10.3,
 | 
			
		||||
    opera: 41,
 | 
			
		||||
    opera_mobile: 41,
 | 
			
		||||
    safari: 10.1,
 | 
			
		||||
    samsung: 6.0,
 | 
			
		||||
  },
 | 
			
		||||
  "intl-locale": {
 | 
			
		||||
    android: 74,
 | 
			
		||||
    chrome: 74,
 | 
			
		||||
    edge: 79,
 | 
			
		||||
    firefox: 75,
 | 
			
		||||
    ios: 14.0,
 | 
			
		||||
    opera: 62,
 | 
			
		||||
    opera_mobile: 53,
 | 
			
		||||
    safari: 14.0,
 | 
			
		||||
    samsung: 11.0,
 | 
			
		||||
  },
 | 
			
		||||
  "intl-other": {
 | 
			
		||||
    // Not specified (i.e. always try polyfill) since compatibility depends on supported locales
 | 
			
		||||
  },
 | 
			
		||||
  proxy: {
 | 
			
		||||
    android: 49,
 | 
			
		||||
    chrome: 49,
 | 
			
		||||
@@ -24,17 +98,67 @@ const PolyfillSupport = {
 | 
			
		||||
    safari: 10.0,
 | 
			
		||||
    samsung: 5.0,
 | 
			
		||||
  },
 | 
			
		||||
  "resize-observer": {
 | 
			
		||||
    android: 64,
 | 
			
		||||
    chrome: 64,
 | 
			
		||||
    edge: 79,
 | 
			
		||||
    firefox: 69,
 | 
			
		||||
    ios: 13.4,
 | 
			
		||||
    opera: 51,
 | 
			
		||||
    opera_mobile: 47,
 | 
			
		||||
    safari: 13.1,
 | 
			
		||||
    samsung: 9.0,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Map of global variables and/or instance and static properties to the
 | 
			
		||||
// corresponding polyfill key and actual module to import
 | 
			
		||||
const polyfillMap = {
 | 
			
		||||
  global: {
 | 
			
		||||
    Proxy: { key: "proxy", module: "proxy-polyfill" },
 | 
			
		||||
    fetch: { key: "fetch", module: "unfetch/polyfill" },
 | 
			
		||||
    Proxy: { key: "proxy", module: "proxy-polyfill" },
 | 
			
		||||
    ResizeObserver: {
 | 
			
		||||
      key: "resize-observer",
 | 
			
		||||
      module: join(POLYFILL_DIR, "resize-observer.ts"),
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  instance: {
 | 
			
		||||
    attachInternals: {
 | 
			
		||||
      key: "element-internals",
 | 
			
		||||
      module: "element-internals-polyfill",
 | 
			
		||||
    },
 | 
			
		||||
    ...Object.fromEntries(
 | 
			
		||||
      ["append", "getAttributeNames", "toggleAttribute"].map((prop) => {
 | 
			
		||||
        const key = `element-${prop.toLowerCase()}`;
 | 
			
		||||
        return [prop, { key, module: join(POLYFILL_DIR, `${key}.ts`) }];
 | 
			
		||||
      })
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  static: {
 | 
			
		||||
    Intl: {
 | 
			
		||||
      getCanonicalLocales: {
 | 
			
		||||
        key: "intl-getcanonicallocales",
 | 
			
		||||
        module: join(POLYFILL_DIR, "intl-polyfill.ts"),
 | 
			
		||||
      },
 | 
			
		||||
      Locale: {
 | 
			
		||||
        key: "intl-locale",
 | 
			
		||||
        module: join(POLYFILL_DIR, "intl-polyfill.ts"),
 | 
			
		||||
      },
 | 
			
		||||
      ...Object.fromEntries(
 | 
			
		||||
        [
 | 
			
		||||
          "DateTimeFormat",
 | 
			
		||||
          "DisplayNames",
 | 
			
		||||
          "ListFormat",
 | 
			
		||||
          "NumberFormat",
 | 
			
		||||
          "PluralRules",
 | 
			
		||||
          "RelativeTimeFormat",
 | 
			
		||||
        ].map((obj) => [
 | 
			
		||||
          obj,
 | 
			
		||||
          { key: "intl-other", module: join(POLYFILL_DIR, "intl-polyfill.ts") },
 | 
			
		||||
        ])
 | 
			
		||||
      ),
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  instance: {},
 | 
			
		||||
  static: {},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Create plugin using the same factory as for CoreJS
 | 
			
		||||
@@ -42,14 +166,16 @@ export default defineProvider(
 | 
			
		||||
  ({ createMetaResolver, debug, shouldInjectPolyfill }) => {
 | 
			
		||||
    const resolvePolyfill = createMetaResolver(polyfillMap);
 | 
			
		||||
    return {
 | 
			
		||||
      name: "HA Custom",
 | 
			
		||||
      name: "custom-polyfill",
 | 
			
		||||
      polyfills: PolyfillSupport,
 | 
			
		||||
      usageGlobal(meta, utils) {
 | 
			
		||||
        const polyfill = resolvePolyfill(meta);
 | 
			
		||||
        if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) {
 | 
			
		||||
          debug(polyfill.desc.key);
 | 
			
		||||
          utils.injectGlobalImport(polyfill.desc.module);
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ const env = require("./env.cjs");
 | 
			
		||||
const paths = require("./paths.cjs");
 | 
			
		||||
const { dependencies } = require("../package.json");
 | 
			
		||||
 | 
			
		||||
const BABEL_PLUGINS = path.join(__dirname, "babel-plugins");
 | 
			
		||||
 | 
			
		||||
// GitHub base URL to use for production source maps
 | 
			
		||||
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
 | 
			
		||||
module.exports.sourceMapURL = () => {
 | 
			
		||||
@@ -90,8 +92,8 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
 | 
			
		||||
    [
 | 
			
		||||
      "@babel/preset-env",
 | 
			
		||||
      {
 | 
			
		||||
        useBuiltIns: latestBuild ? false : "usage",
 | 
			
		||||
        corejs: latestBuild ? false : dependencies["core-js"],
 | 
			
		||||
        useBuiltIns: "usage",
 | 
			
		||||
        corejs: dependencies["core-js"],
 | 
			
		||||
        bugfixes: true,
 | 
			
		||||
        shippedProposals: true,
 | 
			
		||||
      },
 | 
			
		||||
@@ -100,22 +102,12 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
 | 
			
		||||
  ],
 | 
			
		||||
  plugins: [
 | 
			
		||||
    [
 | 
			
		||||
      path.resolve(
 | 
			
		||||
        paths.polymer_dir,
 | 
			
		||||
        "build-scripts/babel-plugins/inline-constants-plugin.cjs"
 | 
			
		||||
      ),
 | 
			
		||||
      path.join(BABEL_PLUGINS, "inline-constants-plugin.cjs"),
 | 
			
		||||
      {
 | 
			
		||||
        modules: ["@mdi/js"],
 | 
			
		||||
        ignoreModuleNotFound: true,
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    [
 | 
			
		||||
      path.resolve(
 | 
			
		||||
        paths.polymer_dir,
 | 
			
		||||
        "build-scripts/babel-plugins/custom-polyfill-plugin.js"
 | 
			
		||||
      ),
 | 
			
		||||
      { method: "usage-global" },
 | 
			
		||||
    ],
 | 
			
		||||
    // Minify template literals for production
 | 
			
		||||
    isProdBuild && [
 | 
			
		||||
      "template-html-minifier",
 | 
			
		||||
@@ -153,6 +145,27 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
 | 
			
		||||
  ],
 | 
			
		||||
  sourceMaps: !isTestBuild,
 | 
			
		||||
  overrides: [
 | 
			
		||||
    {
 | 
			
		||||
      // Add plugin to inject various polyfills, excluding the polyfills
 | 
			
		||||
      // themselves to prevent self-injection.
 | 
			
		||||
      plugins: [
 | 
			
		||||
        [
 | 
			
		||||
          path.join(BABEL_PLUGINS, "custom-polyfill-plugin.js"),
 | 
			
		||||
          { method: "usage-global" },
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      exclude: [
 | 
			
		||||
        path.join(paths.polymer_dir, "src/resources/polyfills"),
 | 
			
		||||
        ...[
 | 
			
		||||
          "@formatjs/(?:ecma402-abstract|intl-\\w+)",
 | 
			
		||||
          "@lit-labs/virtualizer/polyfills",
 | 
			
		||||
          "@webcomponents/scoped-custom-element-registry",
 | 
			
		||||
          "element-internals-polyfill",
 | 
			
		||||
          "proxy-polyfill",
 | 
			
		||||
          "unfetch",
 | 
			
		||||
        ].map((p) => new RegExp(`/node_modules/${p}/`)),
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      // Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
 | 
			
		||||
      // Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import gulp from "gulp";
 | 
			
		||||
import jszip from "jszip";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import process from "process";
 | 
			
		||||
import tar from "tar";
 | 
			
		||||
import { extract } from "tar";
 | 
			
		||||
 | 
			
		||||
const MAX_AGE = 24; // hours
 | 
			
		||||
const OWNER = "home-assistant";
 | 
			
		||||
@@ -156,7 +156,7 @@ gulp.task("fetch-nightly-translations", async function () {
 | 
			
		||||
  console.log("Unpacking downloaded translations...");
 | 
			
		||||
  const zip = await jszip.loadAsync(downloadResponse.data);
 | 
			
		||||
  await deleteCurrent;
 | 
			
		||||
  const extractStream = zip.file(/.*/)[0].nodeStream().pipe(tar.extract());
 | 
			
		||||
  const extractStream = zip.file(/.*/)[0].nodeStream().pipe(extract());
 | 
			
		||||
  await new Promise((resolve, reject) => {
 | 
			
		||||
    extractStream.on("close", resolve).on("error", reject);
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -1,92 +1,112 @@
 | 
			
		||||
import { createHash } from "crypto";
 | 
			
		||||
import { deleteSync } from "del";
 | 
			
		||||
import { mkdirSync, readdirSync, readFileSync, renameSync } from "fs";
 | 
			
		||||
import { writeFile } from "node:fs/promises";
 | 
			
		||||
/* eslint-disable max-classes-per-file */
 | 
			
		||||
 | 
			
		||||
import { deleteAsync } from "del";
 | 
			
		||||
import { glob } from "glob";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import flatmap from "gulp-flatmap";
 | 
			
		||||
import transform from "gulp-json-transform";
 | 
			
		||||
import merge from "gulp-merge-json";
 | 
			
		||||
import rename from "gulp-rename";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import vinylBuffer from "vinyl-buffer";
 | 
			
		||||
import source from "vinyl-source-stream";
 | 
			
		||||
import merge from "lodash.merge";
 | 
			
		||||
import { createHash } from "node:crypto";
 | 
			
		||||
import { mkdir, readFile } from "node:fs/promises";
 | 
			
		||||
import { basename, join } from "node:path";
 | 
			
		||||
import { PassThrough, Transform } from "node:stream";
 | 
			
		||||
import { finished } from "node:stream/promises";
 | 
			
		||||
import env from "../env.cjs";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import { mapFiles } from "../util.cjs";
 | 
			
		||||
import "./fetch-nightly-translations.js";
 | 
			
		||||
 | 
			
		||||
const inFrontendDir = "translations/frontend";
 | 
			
		||||
const inBackendDir = "translations/backend";
 | 
			
		||||
const workDir = "build/translations";
 | 
			
		||||
const fullDir = workDir + "/full";
 | 
			
		||||
const coreDir = workDir + "/core";
 | 
			
		||||
const outDir = workDir + "/output";
 | 
			
		||||
const outDir = join(workDir, "output");
 | 
			
		||||
const EN_SRC = join(paths.translations_src, "en.json");
 | 
			
		||||
const TEST_LOCALE = "en-x-test";
 | 
			
		||||
 | 
			
		||||
let mergeBackend = false;
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "translations-enable-merge-backend",
 | 
			
		||||
  gulp.parallel((done) => {
 | 
			
		||||
  gulp.parallel(async () => {
 | 
			
		||||
    mergeBackend = true;
 | 
			
		||||
    done();
 | 
			
		||||
  }, "allow-setup-fetch-nightly-translations")
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Panel translations which should be split from the core translations.
 | 
			
		||||
const TRANSLATION_FRAGMENTS = Object.keys(
 | 
			
		||||
  JSON.parse(
 | 
			
		||||
    readFileSync(
 | 
			
		||||
      path.resolve(paths.polymer_dir, "src/translations/en.json"),
 | 
			
		||||
      "utf-8"
 | 
			
		||||
    )
 | 
			
		||||
  ).ui.panel
 | 
			
		||||
);
 | 
			
		||||
// Transform stream to apply a function on Vinyl JSON files (buffer mode only).
 | 
			
		||||
// The provided function can either return a new object, or an array of
 | 
			
		||||
// [object, subdirectory] pairs for fragmentizing the JSON.
 | 
			
		||||
class CustomJSON extends Transform {
 | 
			
		||||
  constructor(func, reviver = null) {
 | 
			
		||||
    super({ objectMode: true });
 | 
			
		||||
    this._func = func;
 | 
			
		||||
    this._reviver = reviver;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
function recursiveFlatten(prefix, data) {
 | 
			
		||||
  let output = {};
 | 
			
		||||
  Object.keys(data).forEach((key) => {
 | 
			
		||||
    if (typeof data[key] === "object") {
 | 
			
		||||
      output = {
 | 
			
		||||
        ...output,
 | 
			
		||||
        ...recursiveFlatten(prefix + key + ".", data[key]),
 | 
			
		||||
      };
 | 
			
		||||
  async _transform(file, _, callback) {
 | 
			
		||||
    try {
 | 
			
		||||
      let obj = JSON.parse(file.contents.toString(), this._reviver);
 | 
			
		||||
      if (this._func) obj = this._func(obj, file.path);
 | 
			
		||||
      for (const [outObj, dir] of Array.isArray(obj) ? obj : [[obj, ""]]) {
 | 
			
		||||
        const outFile = file.clone({ contents: false });
 | 
			
		||||
        outFile.contents = Buffer.from(JSON.stringify(outObj));
 | 
			
		||||
        outFile.dirname += `/${dir}`;
 | 
			
		||||
        this.push(outFile);
 | 
			
		||||
      }
 | 
			
		||||
      callback(null);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      callback(err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Transform stream to merge Vinyl JSON files (buffer mode only).
 | 
			
		||||
class MergeJSON extends Transform {
 | 
			
		||||
  _objects = [];
 | 
			
		||||
 | 
			
		||||
  constructor(stem, startObj = {}, reviver = null) {
 | 
			
		||||
    super({ objectMode: true, allowHalfOpen: false });
 | 
			
		||||
    this._stem = stem;
 | 
			
		||||
    this._startObj = structuredClone(startObj);
 | 
			
		||||
    this._reviver = reviver;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _transform(file, _, callback) {
 | 
			
		||||
    try {
 | 
			
		||||
      this._objects.push(JSON.parse(file.contents.toString(), this._reviver));
 | 
			
		||||
      if (!this._outFile) this._outFile = file.clone({ contents: false });
 | 
			
		||||
      callback(null);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      callback(err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _flush(callback) {
 | 
			
		||||
    try {
 | 
			
		||||
      const mergedObj = merge(this._startObj, ...this._objects);
 | 
			
		||||
      this._outFile.contents = Buffer.from(JSON.stringify(mergedObj));
 | 
			
		||||
      this._outFile.stem = this._stem;
 | 
			
		||||
      callback(null, this._outFile);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      callback(err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Utility to flatten object keys to single level using separator
 | 
			
		||||
const flatten = (data, prefix = "", sep = ".") => {
 | 
			
		||||
  const output = {};
 | 
			
		||||
  for (const [key, value] of Object.entries(data)) {
 | 
			
		||||
    if (typeof value === "object") {
 | 
			
		||||
      Object.assign(output, flatten(value, prefix + key + sep, sep));
 | 
			
		||||
    } else {
 | 
			
		||||
      output[prefix + key] = data[key];
 | 
			
		||||
      output[prefix + key] = value;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  }
 | 
			
		||||
  return output;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function flatten(data) {
 | 
			
		||||
  return recursiveFlatten("", data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function emptyFilter(data) {
 | 
			
		||||
  const newData = {};
 | 
			
		||||
  Object.keys(data).forEach((key) => {
 | 
			
		||||
    if (data[key]) {
 | 
			
		||||
      if (typeof data[key] === "object") {
 | 
			
		||||
        newData[key] = emptyFilter(data[key]);
 | 
			
		||||
      } else {
 | 
			
		||||
        newData[key] = data[key];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  return newData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function recursiveEmpty(data) {
 | 
			
		||||
  const newData = {};
 | 
			
		||||
  Object.keys(data).forEach((key) => {
 | 
			
		||||
    if (data[key]) {
 | 
			
		||||
      if (typeof data[key] === "object") {
 | 
			
		||||
        newData[key] = recursiveEmpty(data[key]);
 | 
			
		||||
      } else {
 | 
			
		||||
        newData[key] = "TRANSLATED";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  return newData;
 | 
			
		||||
}
 | 
			
		||||
// Filter functions that can be passed directly to JSON.parse()
 | 
			
		||||
const emptyReviver = (_key, value) => value || undefined;
 | 
			
		||||
const testReviver = (_key, value) =>
 | 
			
		||||
  value && typeof value === "string" ? "TRANSLATED" : value;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Replace Lokalise key placeholders with their actual values.
 | 
			
		||||
@@ -95,60 +115,44 @@ function recursiveEmpty(data) {
 | 
			
		||||
 * be included in src/translations/en.json, but still be usable while
 | 
			
		||||
 * developing locally.
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing
 | 
			
		||||
 * @link https://docs.lokalise.com/en/articles/1400528-key-referencing
 | 
			
		||||
 */
 | 
			
		||||
const re_key_reference = /\[%key:([^%]+)%\]/;
 | 
			
		||||
function lokaliseTransform(data, original, file) {
 | 
			
		||||
const KEY_REFERENCE = /\[%key:([^%]+)%\]/;
 | 
			
		||||
const lokaliseTransform = (data, path, original = data) => {
 | 
			
		||||
  const output = {};
 | 
			
		||||
  Object.entries(data).forEach(([key, value]) => {
 | 
			
		||||
    if (value instanceof Object) {
 | 
			
		||||
      output[key] = lokaliseTransform(value, original, file);
 | 
			
		||||
  for (const [key, value] of Object.entries(data)) {
 | 
			
		||||
    if (typeof value === "object") {
 | 
			
		||||
      output[key] = lokaliseTransform(value, path, original);
 | 
			
		||||
    } else {
 | 
			
		||||
      output[key] = value.replace(re_key_reference, (_match, lokalise_key) => {
 | 
			
		||||
      output[key] = value.replace(KEY_REFERENCE, (_match, lokalise_key) => {
 | 
			
		||||
        const replace = lokalise_key.split("::").reduce((tr, k) => {
 | 
			
		||||
          if (!tr) {
 | 
			
		||||
            throw Error(
 | 
			
		||||
              `Invalid key placeholder ${lokalise_key} in ${file.path}`
 | 
			
		||||
            );
 | 
			
		||||
            throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`);
 | 
			
		||||
          }
 | 
			
		||||
          return tr[k];
 | 
			
		||||
        }, original);
 | 
			
		||||
        if (typeof replace !== "string") {
 | 
			
		||||
          throw Error(
 | 
			
		||||
            `Invalid key placeholder ${lokalise_key} in ${file.path}`
 | 
			
		||||
          );
 | 
			
		||||
          throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`);
 | 
			
		||||
        }
 | 
			
		||||
        return replace;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  }
 | 
			
		||||
  return output;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("clean-translations", async () => deleteSync([workDir]));
 | 
			
		||||
gulp.task("clean-translations", () => deleteAsync([workDir]));
 | 
			
		||||
 | 
			
		||||
gulp.task("ensure-translations-build-dir", async () => {
 | 
			
		||||
  mkdirSync(workDir, { recursive: true });
 | 
			
		||||
});
 | 
			
		||||
const makeWorkDir = () => mkdir(workDir, { recursive: true });
 | 
			
		||||
 | 
			
		||||
gulp.task("create-test-metadata", () =>
 | 
			
		||||
  env.isProdBuild()
 | 
			
		||||
    ? Promise.resolve()
 | 
			
		||||
    : writeFile(
 | 
			
		||||
        workDir + "/testMetadata.json",
 | 
			
		||||
        JSON.stringify({ test: { nativeName: "Test" } })
 | 
			
		||||
      )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task("create-test-translation", () =>
 | 
			
		||||
const createTestTranslation = () =>
 | 
			
		||||
  env.isProdBuild()
 | 
			
		||||
    ? Promise.resolve()
 | 
			
		||||
    : gulp
 | 
			
		||||
        .src(path.join(paths.translations_src, "en.json"))
 | 
			
		||||
        .pipe(transform((data, _file) => recursiveEmpty(data)))
 | 
			
		||||
        .pipe(rename("test.json"))
 | 
			
		||||
        .pipe(gulp.dest(workDir))
 | 
			
		||||
);
 | 
			
		||||
        .src(EN_SRC)
 | 
			
		||||
        .pipe(new CustomJSON(null, testReviver))
 | 
			
		||||
        .pipe(rename(`${TEST_LOCALE}.json`))
 | 
			
		||||
        .pipe(gulp.dest(workDir));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This task will build a master translation file, to be used as the base for
 | 
			
		||||
@@ -159,279 +163,164 @@ gulp.task("create-test-translation", () =>
 | 
			
		||||
 * project is buildable immediately after merging new translation keys, since
 | 
			
		||||
 * the Lokalise update to translations/en.json will not happen immediately.
 | 
			
		||||
 */
 | 
			
		||||
gulp.task("build-master-translation", () => {
 | 
			
		||||
  const src = [path.join(paths.translations_src, "en.json")];
 | 
			
		||||
const createMasterTranslation = () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
 | 
			
		||||
    .pipe(new CustomJSON(lokaliseTransform))
 | 
			
		||||
    .pipe(new MergeJSON("en"))
 | 
			
		||||
    .pipe(gulp.dest(workDir));
 | 
			
		||||
 | 
			
		||||
  if (mergeBackend) {
 | 
			
		||||
    src.push(path.join(inBackendDir, "en.json"));
 | 
			
		||||
const FRAGMENTS = ["base"];
 | 
			
		||||
 | 
			
		||||
const toggleSupervisorFragment = async () => {
 | 
			
		||||
  FRAGMENTS[0] = "supervisor";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const panelFragment = (fragment) =>
 | 
			
		||||
  fragment !== "base" && fragment !== "supervisor";
 | 
			
		||||
 | 
			
		||||
const HASHES = new Map();
 | 
			
		||||
 | 
			
		||||
const createTranslations = async () => {
 | 
			
		||||
  // Parse and store the master to avoid repeating this for each locale, then
 | 
			
		||||
  // add the panel fragments when processing the app.
 | 
			
		||||
  const enMaster = JSON.parse(await readFile(`${workDir}/en.json`, "utf-8"));
 | 
			
		||||
  if (FRAGMENTS[0] === "base") {
 | 
			
		||||
    FRAGMENTS.push(...Object.keys(enMaster.ui.panel));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return gulp
 | 
			
		||||
    .src(src)
 | 
			
		||||
    .pipe(transform((data, file) => lokaliseTransform(data, data, file)))
 | 
			
		||||
  // The downstream pipeline is setup first.  It hashes the merged data for
 | 
			
		||||
  // each locale, then fragmentizes and flattens the data for final output.
 | 
			
		||||
  const translationFiles = await glob([
 | 
			
		||||
    `${inFrontendDir}/!(en).json`,
 | 
			
		||||
    ...(env.isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
 | 
			
		||||
  ]);
 | 
			
		||||
  const hashStream = new Transform({
 | 
			
		||||
    objectMode: true,
 | 
			
		||||
    transform: async (file, _, callback) => {
 | 
			
		||||
      const hash = env.isProdBuild()
 | 
			
		||||
        ? createHash("md5").update(file.contents).digest("hex")
 | 
			
		||||
        : "dev";
 | 
			
		||||
      HASHES.set(file.stem, hash);
 | 
			
		||||
      file.stem += `-${hash}`;
 | 
			
		||||
      callback(null, file);
 | 
			
		||||
    },
 | 
			
		||||
  }).setMaxListeners(translationFiles.length + 1);
 | 
			
		||||
  const fragmentsStream = hashStream
 | 
			
		||||
    .pipe(
 | 
			
		||||
      merge({
 | 
			
		||||
        fileName: "en.json",
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(fullDir));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
gulp.task("build-merged-translations", () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([
 | 
			
		||||
      inFrontendDir + "/*.json",
 | 
			
		||||
      "!" + inFrontendDir + "/en.json",
 | 
			
		||||
      ...(env.isProdBuild() ? [] : [workDir + "/test.json"]),
 | 
			
		||||
    ])
 | 
			
		||||
    .pipe(transform((data, file) => lokaliseTransform(data, data, file)))
 | 
			
		||||
    .pipe(
 | 
			
		||||
      flatmap((stream, file) => {
 | 
			
		||||
        // For each language generate a merged json file. It begins with the master
 | 
			
		||||
        // translation as a failsafe for untranslated strings, and merges all parent
 | 
			
		||||
        // tags into one file for each specific subtag
 | 
			
		||||
        //
 | 
			
		||||
        // TODO: This is a naive interpretation of BCP47 that should be improved.
 | 
			
		||||
        //       Will be OK for now as long as we don't have anything more complicated
 | 
			
		||||
        //       than a base translation + region.
 | 
			
		||||
        const tr = path.basename(file.history[0], ".json");
 | 
			
		||||
        const subtags = tr.split("-");
 | 
			
		||||
        const src = [fullDir + "/en.json"];
 | 
			
		||||
        for (let i = 1; i <= subtags.length; i++) {
 | 
			
		||||
          const lang = subtags.slice(0, i).join("-");
 | 
			
		||||
          if (lang === "test") {
 | 
			
		||||
            src.push(workDir + "/test.json");
 | 
			
		||||
          } else if (lang !== "en") {
 | 
			
		||||
            src.push(inFrontendDir + "/" + lang + ".json");
 | 
			
		||||
            if (mergeBackend) {
 | 
			
		||||
              src.push(inBackendDir + "/" + lang + ".json");
 | 
			
		||||
            }
 | 
			
		||||
      new CustomJSON((data) =>
 | 
			
		||||
        FRAGMENTS.map((fragment) => {
 | 
			
		||||
          switch (fragment) {
 | 
			
		||||
            case "base":
 | 
			
		||||
              // Remove the panels and supervisor to create the base translations
 | 
			
		||||
              return [
 | 
			
		||||
                flatten({
 | 
			
		||||
                  ...data,
 | 
			
		||||
                  ui: { ...data.ui, panel: undefined },
 | 
			
		||||
                  supervisor: undefined,
 | 
			
		||||
                }),
 | 
			
		||||
                "",
 | 
			
		||||
              ];
 | 
			
		||||
            case "supervisor":
 | 
			
		||||
              // Supervisor key is at the top level
 | 
			
		||||
              return [flatten(data.supervisor), ""];
 | 
			
		||||
            default:
 | 
			
		||||
              // Create a fragment with only the given panel
 | 
			
		||||
              return [
 | 
			
		||||
                flatten(data.ui.panel[fragment], `ui.panel.${fragment}.`),
 | 
			
		||||
                fragment,
 | 
			
		||||
              ];
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return gulp
 | 
			
		||||
          .src(src, { allowEmpty: true })
 | 
			
		||||
          .pipe(transform((data) => emptyFilter(data)))
 | 
			
		||||
          .pipe(
 | 
			
		||||
            merge({
 | 
			
		||||
              fileName: tr + ".json",
 | 
			
		||||
            })
 | 
			
		||||
          )
 | 
			
		||||
          .pipe(gulp.dest(fullDir));
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
let taskName;
 | 
			
		||||
 | 
			
		||||
const splitTasks = [];
 | 
			
		||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
 | 
			
		||||
  taskName = "build-translation-fragment-" + fragment;
 | 
			
		||||
  gulp.task(taskName, () =>
 | 
			
		||||
    // Return only the translations for this fragment.
 | 
			
		||||
    gulp
 | 
			
		||||
      .src(fullDir + "/*.json")
 | 
			
		||||
      .pipe(
 | 
			
		||||
        transform((data) => ({
 | 
			
		||||
          ui: {
 | 
			
		||||
            panel: {
 | 
			
		||||
              [fragment]: data.ui.panel[fragment],
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        }))
 | 
			
		||||
      )
 | 
			
		||||
      .pipe(gulp.dest(workDir + "/" + fragment))
 | 
			
		||||
  );
 | 
			
		||||
  splitTasks.push(taskName);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
taskName = "build-translation-core";
 | 
			
		||||
gulp.task(taskName, () =>
 | 
			
		||||
  // Remove the fragment translations from the core translation.
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(fullDir + "/*.json")
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data, _file) => {
 | 
			
		||||
        TRANSLATION_FRAGMENTS.forEach((fragment) => {
 | 
			
		||||
          delete data.ui.panel[fragment];
 | 
			
		||||
        });
 | 
			
		||||
        delete data.supervisor;
 | 
			
		||||
        return data;
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(coreDir))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
splitTasks.push(taskName);
 | 
			
		||||
 | 
			
		||||
gulp.task("build-flattened-translations", () =>
 | 
			
		||||
  // Flatten the split versions of our translations, and move them into outDir
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(
 | 
			
		||||
      TRANSLATION_FRAGMENTS.map(
 | 
			
		||||
        (fragment) => workDir + "/" + fragment + "/*.json"
 | 
			
		||||
      ).concat(coreDir + "/*.json"),
 | 
			
		||||
      { base: workDir }
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data) =>
 | 
			
		||||
        // Polymer.AppLocalizeBehavior requires flattened json
 | 
			
		||||
        flatten(data)
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(
 | 
			
		||||
      rename((filePath) => {
 | 
			
		||||
        if (filePath.dirname === "core") {
 | 
			
		||||
          filePath.dirname = "";
 | 
			
		||||
    .pipe(gulp.dest(outDir));
 | 
			
		||||
 | 
			
		||||
  // Send the English master downstream first, then for each other locale
 | 
			
		||||
  // generate merged JSON data to continue piping. It begins with the master
 | 
			
		||||
  // translation as a failsafe for untranslated strings, and merges all parent
 | 
			
		||||
  // tags into one file for each specific subtag
 | 
			
		||||
  //
 | 
			
		||||
  // TODO: This is a naive interpretation of BCP47 that should be improved.
 | 
			
		||||
  //       Will be OK for now as long as we don't have anything more complicated
 | 
			
		||||
  // than a base translation + region.
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(`${workDir}/en.json`)
 | 
			
		||||
    .pipe(new PassThrough({ objectMode: true }))
 | 
			
		||||
    .pipe(hashStream, { end: false });
 | 
			
		||||
  const mergesFinished = [];
 | 
			
		||||
  for (const translationFile of translationFiles) {
 | 
			
		||||
    const locale = basename(translationFile, ".json");
 | 
			
		||||
    const subtags = locale.split("-");
 | 
			
		||||
    const mergeFiles = [];
 | 
			
		||||
    for (let i = 1; i <= subtags.length; i++) {
 | 
			
		||||
      const lang = subtags.slice(0, i).join("-");
 | 
			
		||||
      if (lang === TEST_LOCALE) {
 | 
			
		||||
        mergeFiles.push(`${workDir}/${TEST_LOCALE}.json`);
 | 
			
		||||
      } else if (lang !== "en") {
 | 
			
		||||
        mergeFiles.push(`${inFrontendDir}/${lang}.json`);
 | 
			
		||||
        if (mergeBackend) {
 | 
			
		||||
          mergeFiles.push(`${inBackendDir}/${lang}.json`);
 | 
			
		||||
        }
 | 
			
		||||
        // In dev we create the file with the fake hash in the filename
 | 
			
		||||
        if (!env.isProdBuild()) {
 | 
			
		||||
          filePath.basename += "-dev";
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(outDir))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const fingerprints = {};
 | 
			
		||||
 | 
			
		||||
gulp.task("build-translation-fingerprints", () => {
 | 
			
		||||
  // Fingerprint full file of each language
 | 
			
		||||
  const files = readdirSync(fullDir);
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < files.length; i++) {
 | 
			
		||||
    fingerprints[files[i].split(".")[0]] = {
 | 
			
		||||
      // In dev we create fake hashes
 | 
			
		||||
      hash: env.isProdBuild()
 | 
			
		||||
        ? createHash("md5")
 | 
			
		||||
            .update(readFileSync(path.join(fullDir, files[i]), "utf-8"))
 | 
			
		||||
            .digest("hex")
 | 
			
		||||
        : "dev",
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // In dev we create the file with the fake hash in the filename
 | 
			
		||||
  if (env.isProdBuild()) {
 | 
			
		||||
    mapFiles(outDir, ".json", (filename) => {
 | 
			
		||||
      const parsed = path.parse(filename);
 | 
			
		||||
 | 
			
		||||
      // nl.json -> nl-<hash>.json
 | 
			
		||||
      if (!(parsed.name in fingerprints)) {
 | 
			
		||||
        throw new Error(`Unable to find hash for ${filename}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      renameSync(
 | 
			
		||||
        filename,
 | 
			
		||||
        `${parsed.dir}/${parsed.name}-${fingerprints[parsed.name].hash}${
 | 
			
		||||
          parsed.ext
 | 
			
		||||
        }`
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
    }
 | 
			
		||||
    const mergeStream = gulp
 | 
			
		||||
      .src(mergeFiles, { allowEmpty: true })
 | 
			
		||||
      .pipe(new MergeJSON(locale, enMaster, emptyReviver));
 | 
			
		||||
    mergesFinished.push(finished(mergeStream));
 | 
			
		||||
    mergeStream.pipe(hashStream, { end: false });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const stream = source("translationFingerprints.json");
 | 
			
		||||
  stream.write(JSON.stringify(fingerprints));
 | 
			
		||||
  process.nextTick(() => stream.end());
 | 
			
		||||
  return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
 | 
			
		||||
});
 | 
			
		||||
  // Wait for all merges to finish, then it's safe to end writing to the
 | 
			
		||||
  // downstream pipeline and wait for all fragments to finish writing.
 | 
			
		||||
  await Promise.all(mergesFinished);
 | 
			
		||||
  hashStream.end();
 | 
			
		||||
  await finished(fragmentsStream);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("build-translation-fragment-supervisor", () =>
 | 
			
		||||
const writeTranslationMetaData = () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(fullDir + "/*.json")
 | 
			
		||||
    .pipe(transform((data) => data.supervisor))
 | 
			
		||||
    .src([`${paths.translations_src}/translationMetadata.json`])
 | 
			
		||||
    .pipe(
 | 
			
		||||
      rename((filePath) => {
 | 
			
		||||
        // In dev we create the file with the fake hash in the filename
 | 
			
		||||
      new CustomJSON((meta) => {
 | 
			
		||||
        // Add the test translation in development.
 | 
			
		||||
        if (!env.isProdBuild()) {
 | 
			
		||||
          filePath.basename += "-dev";
 | 
			
		||||
          meta[TEST_LOCALE] = { nativeName: "Translation Test" };
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(workDir + "/supervisor"))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task("build-translation-flatten-supervisor", () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(workDir + "/supervisor/*.json")
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data) =>
 | 
			
		||||
        // Polymer.AppLocalizeBehavior requires flattened json
 | 
			
		||||
        flatten(data)
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(outDir))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task("build-translation-write-metadata", () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([
 | 
			
		||||
      path.join(paths.translations_src, "translationMetadata.json"),
 | 
			
		||||
      ...(env.isProdBuild() ? [] : [workDir + "/testMetadata.json"]),
 | 
			
		||||
      workDir + "/translationFingerprints.json",
 | 
			
		||||
    ])
 | 
			
		||||
    .pipe(merge({}))
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data) => {
 | 
			
		||||
        const newData = {};
 | 
			
		||||
        Object.entries(data).forEach(([key, value]) => {
 | 
			
		||||
          // Filter out translations without native name.
 | 
			
		||||
          if (value.nativeName) {
 | 
			
		||||
            newData[key] = value;
 | 
			
		||||
          } else {
 | 
			
		||||
        // Filter out locales without a native name, and add the hashes.
 | 
			
		||||
        for (const locale of Object.keys(meta)) {
 | 
			
		||||
          if (!meta[locale].nativeName) {
 | 
			
		||||
            meta[locale] = undefined;
 | 
			
		||||
            console.warn(
 | 
			
		||||
              `Skipping language ${key}. Native name was not translated.`
 | 
			
		||||
              `Skipping locale ${locale} because native name is not translated.`
 | 
			
		||||
            );
 | 
			
		||||
          } else {
 | 
			
		||||
            meta[locale].hash = HASHES.get(locale);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
        return newData;
 | 
			
		||||
        }
 | 
			
		||||
        return {
 | 
			
		||||
          fragments: FRAGMENTS.filter(panelFragment),
 | 
			
		||||
          translations: meta,
 | 
			
		||||
        };
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data) => ({
 | 
			
		||||
        fragments: TRANSLATION_FRAGMENTS,
 | 
			
		||||
        translations: data,
 | 
			
		||||
      }))
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(rename("translationMetadata.json"))
 | 
			
		||||
    .pipe(gulp.dest(workDir))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "create-translations",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    gulp.parallel("create-test-metadata", "create-test-translation"),
 | 
			
		||||
    "build-master-translation",
 | 
			
		||||
    "build-merged-translations",
 | 
			
		||||
    gulp.parallel(...splitTasks),
 | 
			
		||||
    "build-flattened-translations"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
    .pipe(gulp.dest(workDir));
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-translations",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    gulp.parallel(
 | 
			
		||||
      "fetch-nightly-translations",
 | 
			
		||||
      gulp.series("clean-translations", "ensure-translations-build-dir")
 | 
			
		||||
      gulp.series("clean-translations", makeWorkDir)
 | 
			
		||||
    ),
 | 
			
		||||
    "create-translations",
 | 
			
		||||
    "build-translation-fingerprints",
 | 
			
		||||
    "build-translation-write-metadata"
 | 
			
		||||
    createTestTranslation,
 | 
			
		||||
    createMasterTranslation,
 | 
			
		||||
    createTranslations,
 | 
			
		||||
    writeTranslationMetaData
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-supervisor-translations",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    gulp.parallel(
 | 
			
		||||
      "fetch-nightly-translations",
 | 
			
		||||
      gulp.series("clean-translations", "ensure-translations-build-dir")
 | 
			
		||||
    ),
 | 
			
		||||
    gulp.parallel("create-test-metadata", "create-test-translation"),
 | 
			
		||||
    "build-master-translation",
 | 
			
		||||
    "build-merged-translations",
 | 
			
		||||
    "build-translation-fragment-supervisor",
 | 
			
		||||
    "build-translation-flatten-supervisor",
 | 
			
		||||
    "build-translation-fingerprints",
 | 
			
		||||
    "build-translation-write-metadata"
 | 
			
		||||
  )
 | 
			
		||||
  gulp.series(toggleSupervisorFragment, "build-translations")
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -99,7 +99,7 @@ gulp.task("webpack-watch-app", () => {
 | 
			
		||||
  ).watch({ poll: isWsl }, doneHandler());
 | 
			
		||||
  gulp.watch(
 | 
			
		||||
    path.join(paths.translations_src, "en.json"),
 | 
			
		||||
    gulp.series("create-translations", "copy-translations-app")
 | 
			
		||||
    gulp.series("build-translations", "copy-translations-app")
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
const path = require("path");
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
 | 
			
		||||
// Helper function to map recursively over files in a folder and it's subfolders
 | 
			
		||||
module.exports.mapFiles = function mapFiles(startPath, filter, mapFunc) {
 | 
			
		||||
  const files = fs.readdirSync(startPath);
 | 
			
		||||
  for (let i = 0; i < files.length; i++) {
 | 
			
		||||
    const filename = path.join(startPath, files[i]);
 | 
			
		||||
    const stat = fs.lstatSync(filename);
 | 
			
		||||
    if (stat.isDirectory()) {
 | 
			
		||||
      mapFiles(filename, filter, mapFunc);
 | 
			
		||||
    } else if (filename.indexOf(filter) >= 0) {
 | 
			
		||||
      mapFunc(filename);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -10,6 +10,7 @@ const WebpackBar = require("webpackbar");
 | 
			
		||||
const {
 | 
			
		||||
  TransformAsyncModulesPlugin,
 | 
			
		||||
} = require("transform-async-modules-webpack-plugin");
 | 
			
		||||
const { dependencies } = require("../package.json");
 | 
			
		||||
const paths = require("./paths.cjs");
 | 
			
		||||
const bundle = require("./bundle.cjs");
 | 
			
		||||
 | 
			
		||||
@@ -156,11 +157,15 @@ const createWebpackConfig = ({
 | 
			
		||||
          transform: (stats) => JSON.stringify(filterStats(stats)),
 | 
			
		||||
        }),
 | 
			
		||||
      !latestBuild &&
 | 
			
		||||
        new TransformAsyncModulesPlugin({ browserslistEnv: "legacy" }),
 | 
			
		||||
        new TransformAsyncModulesPlugin({
 | 
			
		||||
          browserslistEnv: "legacy",
 | 
			
		||||
          runtime: { version: dependencies["@babel/runtime"] },
 | 
			
		||||
        }),
 | 
			
		||||
    ].filter(Boolean),
 | 
			
		||||
    resolve: {
 | 
			
		||||
      extensions: [".ts", ".js", ".json"],
 | 
			
		||||
      alias: {
 | 
			
		||||
        "lit/static-html$": "lit/static-html.js",
 | 
			
		||||
        "lit/decorators$": "lit/decorators.js",
 | 
			
		||||
        "lit/directive$": "lit/directive.js",
 | 
			
		||||
        "lit/directives/until$": "lit/directives/until.js",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import "@material/mwc-button/mwc-button";
 | 
			
		||||
import { mdiCast, mdiCastConnected } from "@mdi/js";
 | 
			
		||||
import "@polymer/paper-item/paper-icon-item";
 | 
			
		||||
import "@polymer/paper-listbox/paper-listbox";
 | 
			
		||||
import { ActionDetail } from "@material/mwc-list/mwc-list";
 | 
			
		||||
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
 | 
			
		||||
import { Auth, Connection } from "home-assistant-js-websocket";
 | 
			
		||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
@@ -28,6 +27,7 @@ import { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view";
 | 
			
		||||
import "../../../../src/layouts/hass-loading-screen";
 | 
			
		||||
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
 | 
			
		||||
import "./hc-layout";
 | 
			
		||||
import "../../../../src/components/ha-list-item";
 | 
			
		||||
 | 
			
		||||
@customElement("hc-cast")
 | 
			
		||||
class HcCast extends LitElement {
 | 
			
		||||
@@ -83,34 +83,37 @@ class HcCast extends LitElement {
 | 
			
		||||
              `
 | 
			
		||||
            : html`
 | 
			
		||||
                <div class="section-header">PICK A VIEW</div>
 | 
			
		||||
                <paper-listbox
 | 
			
		||||
                  attr-for-selected="data-path"
 | 
			
		||||
                  .selected=${this.castManager.status.lovelacePath || ""}
 | 
			
		||||
                >
 | 
			
		||||
                <mwc-list @action=${this._handlePickView} activatable>
 | 
			
		||||
                  ${(
 | 
			
		||||
                    this.lovelaceViews ?? [
 | 
			
		||||
                      generateDefaultViewConfig({}, {}, {}, {}, () => ""),
 | 
			
		||||
                    ]
 | 
			
		||||
                  ).map(
 | 
			
		||||
                    (view, idx) => html`
 | 
			
		||||
                      <paper-icon-item
 | 
			
		||||
                        @click=${this._handlePickView}
 | 
			
		||||
                        data-path=${view.path || idx}
 | 
			
		||||
                    (view, idx) =>
 | 
			
		||||
                      html`<ha-list-item
 | 
			
		||||
                        graphic="avatar"
 | 
			
		||||
                        .activated=${this.castManager.status?.lovelacePath ===
 | 
			
		||||
                        (view.path ?? idx)}
 | 
			
		||||
                        .selected=${this.castManager.status?.lovelacePath ===
 | 
			
		||||
                        (view.path ?? idx)}
 | 
			
		||||
                      >
 | 
			
		||||
                        ${view.title || view.path || "Unnamed view"}
 | 
			
		||||
                        ${view.icon
 | 
			
		||||
                          ? html`
 | 
			
		||||
                              <ha-icon
 | 
			
		||||
                                .icon=${view.icon}
 | 
			
		||||
                                slot="item-icon"
 | 
			
		||||
                                slot="graphic"
 | 
			
		||||
                              ></ha-icon>
 | 
			
		||||
                            `
 | 
			
		||||
                          : ""}
 | 
			
		||||
                        ${view.title || view.path}
 | 
			
		||||
                      </paper-icon-item>
 | 
			
		||||
                    `
 | 
			
		||||
                  )}
 | 
			
		||||
                </paper-listbox>
 | 
			
		||||
                          : html`<ha-svg-icon
 | 
			
		||||
                              slot="item-icon"
 | 
			
		||||
                              .path=${mdiViewDashboard}
 | 
			
		||||
                            ></ha-svg-icon>`}</ha-list-item
 | 
			
		||||
                      > `
 | 
			
		||||
                  )}</mwc-list
 | 
			
		||||
                >
 | 
			
		||||
              `}
 | 
			
		||||
 | 
			
		||||
        <div class="card-actions">
 | 
			
		||||
          ${this.castManager.status
 | 
			
		||||
            ? html`
 | 
			
		||||
@@ -182,8 +185,8 @@ class HcCast extends LitElement {
 | 
			
		||||
    this.castManager.requestSession();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _handlePickView(ev: Event) {
 | 
			
		||||
    const path = (ev.currentTarget as any).getAttribute("data-path");
 | 
			
		||||
  private async _handlePickView(ev: CustomEvent<ActionDetail>) {
 | 
			
		||||
    const path = this.lovelaceViews![ev.detail.index].path ?? ev.detail.index;
 | 
			
		||||
    await ensureConnectedCastSession(this.castManager!, this.auth!);
 | 
			
		||||
    castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
 | 
			
		||||
  }
 | 
			
		||||
@@ -246,25 +249,14 @@ class HcCast extends LitElement {
 | 
			
		||||
        height: 18px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      paper-listbox {
 | 
			
		||||
        padding-top: 0;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      paper-listbox ha-icon {
 | 
			
		||||
      ha-list-item ha-icon,
 | 
			
		||||
      ha-list-item ha-svg-icon {
 | 
			
		||||
        padding: 12px;
 | 
			
		||||
        color: var(--secondary-text-color);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      paper-icon-item {
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      paper-icon-item[disabled] {
 | 
			
		||||
        cursor: initial;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      :host([hide-icons]) paper-icon-item {
 | 
			
		||||
        --paper-item-icon-width: 0px;
 | 
			
		||||
      :host([hide-icons]) ha-icon {
 | 
			
		||||
        display: none;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .spacer {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ class HcLaunchScreen extends LitElement {
 | 
			
		||||
      :host {
 | 
			
		||||
        display: block;
 | 
			
		||||
        height: 100vh;
 | 
			
		||||
        background-color: white;
 | 
			
		||||
        background-color: #f2f4f9;
 | 
			
		||||
        font-size: 24px;
 | 
			
		||||
      }
 | 
			
		||||
      .container {
 | 
			
		||||
@@ -43,6 +43,9 @@ class HcLaunchScreen extends LitElement {
 | 
			
		||||
        max-width: 80%;
 | 
			
		||||
        object-fit: cover;
 | 
			
		||||
      }
 | 
			
		||||
      .status {
 | 
			
		||||
        color: #1d2126;
 | 
			
		||||
      }
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
			
		||||
import { customElement, property, query } from "lit/decorators";
 | 
			
		||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
			
		||||
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
 | 
			
		||||
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
 | 
			
		||||
import { Lovelace } from "../../../../src/panels/lovelace/types";
 | 
			
		||||
import "../../../../src/panels/lovelace/views/hui-view";
 | 
			
		||||
import { HomeAssistant } from "../../../../src/types";
 | 
			
		||||
@@ -61,7 +62,12 @@ class HcLovelace extends LitElement {
 | 
			
		||||
      const index = this._viewIndex;
 | 
			
		||||
 | 
			
		||||
      if (index !== undefined) {
 | 
			
		||||
        const dashboardTitle = this.lovelaceConfig.title || this.urlPath;
 | 
			
		||||
        const title = getPanelTitleFromUrlPath(
 | 
			
		||||
          this.hass,
 | 
			
		||||
          this.urlPath || "lovelace"
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const dashboardTitle = title || this.urlPath;
 | 
			
		||||
 | 
			
		||||
        const viewTitle =
 | 
			
		||||
          this.lovelaceConfig.views[index].title ||
 | 
			
		||||
@@ -80,10 +86,17 @@ class HcLovelace extends LitElement {
 | 
			
		||||
          this.lovelaceConfig.views[index].background ||
 | 
			
		||||
          this.lovelaceConfig.background;
 | 
			
		||||
 | 
			
		||||
        if (configBackground) {
 | 
			
		||||
        const backgroundStyle =
 | 
			
		||||
          typeof configBackground === "string"
 | 
			
		||||
            ? configBackground
 | 
			
		||||
            : configBackground?.image
 | 
			
		||||
              ? `center / cover no-repeat url('${configBackground.image}')`
 | 
			
		||||
              : undefined;
 | 
			
		||||
 | 
			
		||||
        if (backgroundStyle) {
 | 
			
		||||
          this._huiView!.style.setProperty(
 | 
			
		||||
            "--lovelace-background",
 | 
			
		||||
            configBackground
 | 
			
		||||
            backgroundStyle
 | 
			
		||||
          );
 | 
			
		||||
        } else {
 | 
			
		||||
          this._huiView!.style.removeProperty("--lovelace-background");
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/lo
 | 
			
		||||
import { HassElement } from "../../../../src/state/hass-element";
 | 
			
		||||
import { castContext } from "../cast_context";
 | 
			
		||||
import "./hc-launch-screen";
 | 
			
		||||
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
 | 
			
		||||
 | 
			
		||||
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
 | 
			
		||||
  strategy: {
 | 
			
		||||
@@ -359,7 +360,11 @@ export class HcMain extends HassElement {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
 | 
			
		||||
    castContext.setApplicationState(lovelaceConfig.title || "");
 | 
			
		||||
    const title = getPanelTitleFromUrlPath(
 | 
			
		||||
      this.hass!,
 | 
			
		||||
      this._urlPath || "lovelace"
 | 
			
		||||
    );
 | 
			
		||||
    castContext.setApplicationState(title || "");
 | 
			
		||||
    this._lovelaceConfig = lovelaceConfig;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import { energyEntities } from "../stubs/entities";
 | 
			
		||||
import { DemoConfig } from "./types";
 | 
			
		||||
 | 
			
		||||
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
 | 
			
		||||
  () => import("./sections").then((mod) => mod.demoSections),
 | 
			
		||||
  () => import("./arsaboo").then((mod) => mod.demoArsaboo),
 | 
			
		||||
  () => import("./teachingbirds").then((mod) => mod.demoTeachingbirds),
 | 
			
		||||
  () => import("./kernehed").then((mod) => mod.demoKernehed),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								demo/src/configs/sections/description.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								demo/src/configs/sections/description.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { DemoConfig } from "../types";
 | 
			
		||||
 | 
			
		||||
export const demoLovelaceDescription: DemoConfig["description"] = (
 | 
			
		||||
  localize
 | 
			
		||||
) => html`
 | 
			
		||||
  <p>
 | 
			
		||||
    ${localize("ui.panel.page-demo.config.sections.description", {
 | 
			
		||||
      blog_post: html`<a
 | 
			
		||||
        href="https://www.home-assistant.io/blog/2024/03/04/dashboard-chapter-1/"
 | 
			
		||||
        target="_blank"
 | 
			
		||||
        >${localize("ui.panel.page-demo.config.sections.description_blog_post")}
 | 
			
		||||
      </a>`,
 | 
			
		||||
    })}
 | 
			
		||||
  </p>
 | 
			
		||||
`;
 | 
			
		||||
							
								
								
									
										474
									
								
								demo/src/configs/sections/entities.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										474
									
								
								demo/src/configs/sections/entities.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,474 @@
 | 
			
		||||
import { convertEntities } from "../../../../src/fake_data/entity";
 | 
			
		||||
import { DemoConfig } from "../types";
 | 
			
		||||
 | 
			
		||||
export const demoEntitiesSections: DemoConfig["entities"] = () =>
 | 
			
		||||
  convertEntities({
 | 
			
		||||
    "cover.living_room_garden_shutter": {
 | 
			
		||||
      entity_id: "cover.living_room_garden_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Living room garden shutter",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "cover.living_room_graveyard_shutter": {
 | 
			
		||||
      entity_id: "cover.living_room_graveyard_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Living room graveyard shutter",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "cover.living_room_left_shutter": {
 | 
			
		||||
      entity_id: "cover.living_room_left_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Living room left shutter",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "cover.living_room_right_shutter": {
 | 
			
		||||
      entity_id: "cover.living_room_right_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Living room right shutter",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.floor_lamp": {
 | 
			
		||||
      entity_id: "light.floor_lamp",
 | 
			
		||||
      state: "on",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        min_color_temp_kelvin: 2000,
 | 
			
		||||
        max_color_temp_kelvin: 6535,
 | 
			
		||||
        min_mireds: 153,
 | 
			
		||||
        max_mireds: 500,
 | 
			
		||||
        supported_color_modes: ["color_temp", "xy"],
 | 
			
		||||
        color_mode: "color_temp",
 | 
			
		||||
        brightness: 178,
 | 
			
		||||
        color_temp_kelvin: 2583,
 | 
			
		||||
        color_temp: 387,
 | 
			
		||||
        hs_color: [28.664, 69.597],
 | 
			
		||||
        rgb_color: [255, 162, 77],
 | 
			
		||||
        xy_color: [0.538, 0.389],
 | 
			
		||||
        icon: "mdi:floor-lamp",
 | 
			
		||||
        friendly_name: "Floor lamp",
 | 
			
		||||
        supported_features: 44,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.living_room_spotlights": {
 | 
			
		||||
      entity_id: "light.living_room_spotlights",
 | 
			
		||||
      state: "on",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        color_mode: "brightness",
 | 
			
		||||
        brightness: 126,
 | 
			
		||||
        icon: "mdi:ceiling-light-multiple",
 | 
			
		||||
        friendly_name: "Living room spotlights",
 | 
			
		||||
        supported_features: 32,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.bar_lamp": {
 | 
			
		||||
      entity_id: "light.bar_lamp",
 | 
			
		||||
      state: "on",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        min_color_temp_kelvin: 2202,
 | 
			
		||||
        max_color_temp_kelvin: 4504,
 | 
			
		||||
        min_mireds: 222,
 | 
			
		||||
        max_mireds: 454,
 | 
			
		||||
        effect_list: ["None", "candle"],
 | 
			
		||||
        supported_color_modes: ["color_temp"],
 | 
			
		||||
        effect: null,
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: null,
 | 
			
		||||
        color_temp_kelvin: null,
 | 
			
		||||
        color_temp: null,
 | 
			
		||||
        hs_color: null,
 | 
			
		||||
        rgb_color: null,
 | 
			
		||||
        xy_color: null,
 | 
			
		||||
        mode: "normal",
 | 
			
		||||
        dynamics: "none",
 | 
			
		||||
        icon: "mdi:lightbulb-variant",
 | 
			
		||||
        friendly_name: "Bar lamp",
 | 
			
		||||
        supported_features: 44,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.living_room_temperature": {
 | 
			
		||||
      entity_id: "sensor.living_room_temperature",
 | 
			
		||||
      state: "22.8",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        unit_of_measurement: "°C",
 | 
			
		||||
        device_class: "temperature",
 | 
			
		||||
        friendly_name: "Living room Temperature",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "media_player.living_room_nest_mini": {
 | 
			
		||||
      entity_id: "media_player.living_room_nest_mini",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        device_class: "speaker",
 | 
			
		||||
        friendly_name: "Living room Nest Mini",
 | 
			
		||||
        supported_features: 152461,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "cover.kitchen_shutter": {
 | 
			
		||||
      entity_id: "cover.kitchen_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Kitchen shutter ",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.kitchen_spotlights": {
 | 
			
		||||
      entity_id: "light.kitchen_spotlights",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: null,
 | 
			
		||||
        icon: "mdi:ceiling-light-multiple",
 | 
			
		||||
        friendly_name: "Kitchen spotlights ",
 | 
			
		||||
        supported_features: 32,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.worktop_spotlights": {
 | 
			
		||||
      entity_id: "light.worktop_spotlights",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: null,
 | 
			
		||||
        icon: "mdi:ceiling-light-multiple",
 | 
			
		||||
        friendly_name: "Worktop spotlights ",
 | 
			
		||||
        supported_features: 32,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "binary_sensor.fridge_door": {
 | 
			
		||||
      entity_id: "binary_sensor.fridge_door",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        device_class: "door",
 | 
			
		||||
        icon: "mdi:fridge",
 | 
			
		||||
        friendly_name: "Fridge door",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "media_player.kitchen_nest_audio": {
 | 
			
		||||
      entity_id: "media_player.kitchen_nest_audio",
 | 
			
		||||
      state: "on",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        device_class: "speaker",
 | 
			
		||||
        friendly_name: "Kitchen Nest Audio",
 | 
			
		||||
        supported_features: 152461,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "binary_sensor.tesla_wall_connector_vehicle_connected": {
 | 
			
		||||
      entity_id: "binary_sensor.tesla_wall_connector_vehicle_connected",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        device_class: "plug",
 | 
			
		||||
        friendly_name: "Wall Connector Vehicle connected",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.tesla_wall_connector_session_energy": {
 | 
			
		||||
      entity_id: "sensor.tesla_wall_connector_session_energy",
 | 
			
		||||
      state: "16.3",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "total_increasing",
 | 
			
		||||
        unit_of_measurement: "kWh",
 | 
			
		||||
        device_class: "energy",
 | 
			
		||||
        friendly_name: "Tesla Wall Connector Session energy",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.electric_meter_power": {
 | 
			
		||||
      entity_id: "sensor.electric_meter_power",
 | 
			
		||||
      state: "797.86",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        unit_of_measurement: "W",
 | 
			
		||||
        device_class: "power",
 | 
			
		||||
        icon: "mdi:meter-electric",
 | 
			
		||||
        friendly_name: "Electric meter Power",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.eletric_meter_voltage": {
 | 
			
		||||
      entity_id: "sensor.eletric_meter_voltage",
 | 
			
		||||
      state: "232.19",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        unit_of_measurement: "V",
 | 
			
		||||
        device_class: "voltage",
 | 
			
		||||
        friendly_name: "Electric meter voltage",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.electricity_maps_grid_fossil_fuel_percentage": {
 | 
			
		||||
      entity_id: "sensor.electricity_maps_grid_fossil_fuel_percentage",
 | 
			
		||||
      state: "9.84",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        country_code: "FR",
 | 
			
		||||
        unit_of_measurement: "%",
 | 
			
		||||
        attribution: "Data provided by Electricity Maps",
 | 
			
		||||
        icon: "mdi:barrel",
 | 
			
		||||
        friendly_name: "Electricity Maps Grid fossil fuel percentage",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.electricity_maps_co2_intensity": {
 | 
			
		||||
      entity_id: "sensor.electricity_maps_co2_intensity",
 | 
			
		||||
      state: "62.0",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        country_code: "FR",
 | 
			
		||||
        unit_of_measurement: "gCO2eq/kWh",
 | 
			
		||||
        attribution: "Data provided by Electricity Maps",
 | 
			
		||||
        friendly_name: "Electricity Maps CO2 intensity",
 | 
			
		||||
        icon: "mdi:molecule-co2",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sun.sun": {
 | 
			
		||||
      entity_id: "sun.sun",
 | 
			
		||||
      state: "above_horizon",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        next_dawn: "2024-03-05T05:50:21.964405+00:00",
 | 
			
		||||
        next_dusk: "2024-03-04T18:08:54.311334+00:00",
 | 
			
		||||
        next_midnight: "2024-03-05T00:00:00+00:00",
 | 
			
		||||
        next_noon: "2024-03-05T12:00:05+00:00",
 | 
			
		||||
        next_rising: "2024-03-05T06:23:42.739159+00:00",
 | 
			
		||||
        next_setting: "2024-03-04T17:35:26.271171+00:00",
 | 
			
		||||
        elevation: 30.38,
 | 
			
		||||
        azimuth: 204.42,
 | 
			
		||||
        rising: false,
 | 
			
		||||
        friendly_name: "Sun",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.rain": {
 | 
			
		||||
      entity_id: "sensor.moon_phase",
 | 
			
		||||
      state: "7.2",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "total_increasing",
 | 
			
		||||
        unit_of_measurement: "mm",
 | 
			
		||||
        device_class: "precipitation",
 | 
			
		||||
        friendly_name: "Rain",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "climate.ground_floor": {
 | 
			
		||||
      entity_id: "climate.ground_floor",
 | 
			
		||||
      state: "heat",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        hvac_modes: ["auto", "heat", "off"],
 | 
			
		||||
        min_temp: 7,
 | 
			
		||||
        max_temp: 35,
 | 
			
		||||
        preset_modes: [
 | 
			
		||||
          "comfort",
 | 
			
		||||
          "away",
 | 
			
		||||
          "eco",
 | 
			
		||||
          "frost_protection",
 | 
			
		||||
          "external",
 | 
			
		||||
          "home",
 | 
			
		||||
        ],
 | 
			
		||||
        current_temperature: 20.8,
 | 
			
		||||
        temperature: 21,
 | 
			
		||||
        preset_mode: "comfort",
 | 
			
		||||
        icon: "mdi:home-floor-0",
 | 
			
		||||
        friendly_name: "Ground floor Thermostat",
 | 
			
		||||
        supported_features: 401,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "climate.first_floor": {
 | 
			
		||||
      entity_id: "climate.first_floor",
 | 
			
		||||
      state: "heat",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        hvac_modes: ["auto", "heat", "off"],
 | 
			
		||||
        min_temp: 7,
 | 
			
		||||
        max_temp: 35,
 | 
			
		||||
        preset_modes: [
 | 
			
		||||
          "comfort",
 | 
			
		||||
          "away",
 | 
			
		||||
          "eco",
 | 
			
		||||
          "frost_protection",
 | 
			
		||||
          "external",
 | 
			
		||||
          "home",
 | 
			
		||||
        ],
 | 
			
		||||
        current_temperature: 21.7,
 | 
			
		||||
        temperature: 21,
 | 
			
		||||
        preset_mode: "comfort",
 | 
			
		||||
        icon: "mdi:home-floor-1",
 | 
			
		||||
        friendly_name: "First floor Thermostat",
 | 
			
		||||
        supported_features: 401,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "cover.study_shutter": {
 | 
			
		||||
      entity_id: "cover.study_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Study shutter",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.study_spotlights": {
 | 
			
		||||
      entity_id: "light.study_spotlights",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: null,
 | 
			
		||||
        icon: "mdi:ceiling-light-multiple",
 | 
			
		||||
        friendly_name: "Study spotlights",
 | 
			
		||||
        supported_features: 32,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "media_player.study_nest_hub": {
 | 
			
		||||
      entity_id: "media_player.study_nest_hub",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        friendly_name: "Study Nest Hub",
 | 
			
		||||
        supported_features: 152461,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.standing_desk_height": {
 | 
			
		||||
      entity_id: "sensor.standing_desk_height",
 | 
			
		||||
      state: "72",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        unit_of_measurement: "cm",
 | 
			
		||||
        icon: "mdi:tape-measure",
 | 
			
		||||
        friendly_name: "Standing desk Height",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.outdoor_light": {
 | 
			
		||||
      entity_id: "light.outdoor_light",
 | 
			
		||||
      state: "on",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: 255,
 | 
			
		||||
        icon: "mdi:outdoor-lamp",
 | 
			
		||||
        friendly_name: "Outdoor light",
 | 
			
		||||
        supported_features: 32,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.flood_light": {
 | 
			
		||||
      entity_id: "light.flood_light",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        effect_list: ["None", "candle"],
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        effect: null,
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: null,
 | 
			
		||||
        mode: "normal",
 | 
			
		||||
        dynamics: "none",
 | 
			
		||||
        icon: "mdi:light-flood-down",
 | 
			
		||||
        friendly_name: "Flood light",
 | 
			
		||||
        supported_features: 44,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.outdoor_motion_sensor_temperature": {
 | 
			
		||||
      entity_id: "sensor.outdoor_motion_sensor_temperature",
 | 
			
		||||
      state: "10.2",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        unit_of_measurement: "°C",
 | 
			
		||||
        device_class: "temperature",
 | 
			
		||||
        friendly_name: "Outdoor motion sensor Temperature",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "binary_sensor.outdoor_motion_sensor_motion": {
 | 
			
		||||
      entity_id: "binary_sensor.outdoor_motion_sensor_motion",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        device_class: "motion",
 | 
			
		||||
        friendly_name: "Outdoor motion sensor Motion",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.outdoor_motion_sensor_illuminance": {
 | 
			
		||||
      entity_id: "sensor.outdoor_motion_sensor_illuminance",
 | 
			
		||||
      state: "555",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        light_level: 27444,
 | 
			
		||||
        unit_of_measurement: "lx",
 | 
			
		||||
        device_class: "illuminance",
 | 
			
		||||
        friendly_name: "Outdoor motion sensor Illuminance",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "automation.home_assistant_auto_update": {
 | 
			
		||||
      entity_id: "automation.home_assistant_auto_update",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        id: "1700669321947",
 | 
			
		||||
        last_triggered: "2024-02-29T18:02:05.343139+00:00",
 | 
			
		||||
        mode: "queued",
 | 
			
		||||
        current: 0,
 | 
			
		||||
        max: 50,
 | 
			
		||||
        icon: "mdi:auto-mode",
 | 
			
		||||
        friendly_name: "Home Assistant Auto-update",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "update.home_assistant_operating_system_update": {
 | 
			
		||||
      entity_id: "update.home_assistant_operating_system_update",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        auto_update: false,
 | 
			
		||||
        installed_version: "12.1",
 | 
			
		||||
        in_progress: false,
 | 
			
		||||
        latest_version: "12.1",
 | 
			
		||||
        release_summary: null,
 | 
			
		||||
        release_url:
 | 
			
		||||
          "https://github.com/home-assistant/operating-system/commits/dev",
 | 
			
		||||
        skipped_version: null,
 | 
			
		||||
        title: "Home Assistant Operating System",
 | 
			
		||||
        entity_picture:
 | 
			
		||||
          "https://brands.home-assistant.io/homeassistant/icon.png",
 | 
			
		||||
        friendly_name: "Home Assistant Operating System Update",
 | 
			
		||||
        supported_features: 3,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "update.home_assistant_supervisor_update": {
 | 
			
		||||
      entity_id: "update.home_assistant_supervisor_update",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        auto_update: true,
 | 
			
		||||
        installed_version: "2024.02.2",
 | 
			
		||||
        in_progress: false,
 | 
			
		||||
        latest_version: "2024.02.2",
 | 
			
		||||
        release_summary: null,
 | 
			
		||||
        release_url:
 | 
			
		||||
          "https://github.com/home-assistant/supervisor/commits/main",
 | 
			
		||||
        skipped_version: null,
 | 
			
		||||
        title: "Home Assistant Supervisor",
 | 
			
		||||
        entity_picture: "https://brands.home-assistant.io/hassio/icon.png",
 | 
			
		||||
        friendly_name: "Home Assistant Supervisor Update",
 | 
			
		||||
        supported_features: 1,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "update.home_assistant_core_update": {
 | 
			
		||||
      entity_id: "update.home_assistant_supervisor_update",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        auto_update: false,
 | 
			
		||||
        installed_version: "2024.4.0",
 | 
			
		||||
        in_progress: false,
 | 
			
		||||
        latest_version: "2024.4.0",
 | 
			
		||||
        release_summary: null,
 | 
			
		||||
        release_url: "https://github.com/home-assistant/core/commits/dev",
 | 
			
		||||
        skipped_version: null,
 | 
			
		||||
        title: "Home Assistant Core",
 | 
			
		||||
        entity_picture:
 | 
			
		||||
          "https://brands.home-assistant.io/homeassistant/icon.png",
 | 
			
		||||
        friendly_name: "Home Assistant Core Update",
 | 
			
		||||
        supported_features: 11,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
							
								
								
									
										14
									
								
								demo/src/configs/sections/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								demo/src/configs/sections/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import { DemoConfig } from "../types";
 | 
			
		||||
import { demoLovelaceDescription } from "./description";
 | 
			
		||||
import { demoEntitiesSections } from "./entities";
 | 
			
		||||
import { demoLovelaceSections } from "./lovelace";
 | 
			
		||||
 | 
			
		||||
export const demoSections: DemoConfig = {
 | 
			
		||||
  authorName: "Home Assistant",
 | 
			
		||||
  authorUrl: "https://github.com/home-assistant/frontend/",
 | 
			
		||||
  name: "Home Demo",
 | 
			
		||||
  description: demoLovelaceDescription,
 | 
			
		||||
  lovelace: demoLovelaceSections,
 | 
			
		||||
  entities: demoEntitiesSections,
 | 
			
		||||
  theme: () => ({}),
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										281
									
								
								demo/src/configs/sections/lovelace.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								demo/src/configs/sections/lovelace.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,281 @@
 | 
			
		||||
import { DemoConfig } from "../types";
 | 
			
		||||
 | 
			
		||||
export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
 | 
			
		||||
  title: "Home Assistant Demo",
 | 
			
		||||
  views: [
 | 
			
		||||
    {
 | 
			
		||||
      type: "sections",
 | 
			
		||||
      title: "Demo",
 | 
			
		||||
      path: "home",
 | 
			
		||||
      icon: "mdi:home-assistant",
 | 
			
		||||
      sections: [
 | 
			
		||||
        {
 | 
			
		||||
          title: "Welcome 👋",
 | 
			
		||||
          cards: [{ type: "custom:ha-demo-card" }],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "cover.living_room_garden_shutter",
 | 
			
		||||
              name: "Garden",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "cover.living_room_graveyard_shutter",
 | 
			
		||||
              name: "Rear",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "cover.living_room_left_shutter",
 | 
			
		||||
              name: "Left",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "cover.living_room_right_shutter",
 | 
			
		||||
              name: "Right",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.floor_lamp",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.living_room_spotlights",
 | 
			
		||||
              name: "Spotlights",
 | 
			
		||||
              features: [
 | 
			
		||||
                {
 | 
			
		||||
                  type: "light-brightness",
 | 
			
		||||
                },
 | 
			
		||||
              ],
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.bar_lamp",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              graph: "line",
 | 
			
		||||
              type: "sensor",
 | 
			
		||||
              entity: "sensor.living_room_temperature",
 | 
			
		||||
              detail: 1,
 | 
			
		||||
              name: "Temperature",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "media_player.living_room_nest_mini",
 | 
			
		||||
              name: "Nest Mini",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "🛋️ Living room ",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "cover.kitchen_shutter",
 | 
			
		||||
              name: "Shutter",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.kitchen_spotlights",
 | 
			
		||||
              name: "Spotlights",
 | 
			
		||||
              features: [
 | 
			
		||||
                {
 | 
			
		||||
                  type: "light-brightness",
 | 
			
		||||
                },
 | 
			
		||||
              ],
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.worktop_spotlights",
 | 
			
		||||
              name: "Worktop",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "binary_sensor.fridge_door",
 | 
			
		||||
              name: "Fridge",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "media_player.kitchen_nest_audio",
 | 
			
		||||
              name: "Nest Audio",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "👩🍳 Kitchen",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "binary_sensor.tesla_wall_connector_vehicle_connected",
 | 
			
		||||
              name: "EV",
 | 
			
		||||
              icon: "mdi:car",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.tesla_wall_connector_session_energy",
 | 
			
		||||
              name: "Last charge",
 | 
			
		||||
              color: "green",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.electric_meter_power",
 | 
			
		||||
              color: "deep-orange",
 | 
			
		||||
              name: "Home power",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.eletric_meter_voltage",
 | 
			
		||||
              name: "Voltage",
 | 
			
		||||
              color: "deep-orange",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.electricity_maps_grid_fossil_fuel_percentage",
 | 
			
		||||
              name: "Fossil fuel",
 | 
			
		||||
              color: "brown",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.electricity_maps_co2_intensity",
 | 
			
		||||
              name: "CO2 Intensity",
 | 
			
		||||
              color: "dark-grey",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "⚡️ Energy",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sun.sun",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.rain",
 | 
			
		||||
              color: "blue",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              features: [
 | 
			
		||||
                {
 | 
			
		||||
                  type: "target-temperature",
 | 
			
		||||
                },
 | 
			
		||||
              ],
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              name: "Downstairs",
 | 
			
		||||
              entity: "climate.ground_floor",
 | 
			
		||||
              state_content: ["preset_mode", "current_temperature"],
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              features: [
 | 
			
		||||
                {
 | 
			
		||||
                  type: "target-temperature",
 | 
			
		||||
                },
 | 
			
		||||
              ],
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              name: "Upstairs",
 | 
			
		||||
              entity: "climate.first_floor",
 | 
			
		||||
              state_content: ["preset_mode", "current_temperature"],
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "🌤️ Climate",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "cover.study_shutter",
 | 
			
		||||
              name: "Shutter",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.study_spotlights",
 | 
			
		||||
              name: "Spotlights",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "media_player.study_nest_hub",
 | 
			
		||||
              name: "Nest Hub",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.standing_desk_height",
 | 
			
		||||
              name: "Desk",
 | 
			
		||||
              color: "brown",
 | 
			
		||||
              icon: "mdi:desk",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "🧑💻 Study",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.outdoor_light",
 | 
			
		||||
              name: "Door light",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.flood_light",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              graph: "line",
 | 
			
		||||
              type: "sensor",
 | 
			
		||||
              entity: "sensor.outdoor_motion_sensor_temperature",
 | 
			
		||||
              detail: 1,
 | 
			
		||||
              name: "Temperature",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "binary_sensor.outdoor_motion_sensor_motion",
 | 
			
		||||
              name: "Motion",
 | 
			
		||||
              color: "blue",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.outdoor_motion_sensor_illuminance",
 | 
			
		||||
              color: "amber",
 | 
			
		||||
              name: "Illuminance",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "🌳 Outdoor",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "automation.home_assistant_auto_update",
 | 
			
		||||
              name: "Auto-update",
 | 
			
		||||
              color: "green",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "update.home_assistant_operating_system_update",
 | 
			
		||||
              name: "OS",
 | 
			
		||||
              icon: "mdi:home-assistant",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "update.home_assistant_supervisor_update",
 | 
			
		||||
              icon: "mdi:home-assistant",
 | 
			
		||||
              name: "Supervisor",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "update.home_assistant_core_update",
 | 
			
		||||
              name: "Core",
 | 
			
		||||
              icon: "mdi:home-assistant",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "🎉 Updates",
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
});
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import { TemplateResult } from "lit";
 | 
			
		||||
import { LocalizeFunc } from "../../../src/common/translations/localize";
 | 
			
		||||
import { LovelaceConfig } from "../../../src/data/lovelace/config/types";
 | 
			
		||||
import { Entity } from "../../../src/fake_data/entity";
 | 
			
		||||
@@ -7,6 +8,9 @@ export interface DemoConfig {
 | 
			
		||||
  name: string;
 | 
			
		||||
  authorName: string;
 | 
			
		||||
  authorUrl: string;
 | 
			
		||||
  description?:
 | 
			
		||||
    | string
 | 
			
		||||
    | ((localize: LocalizeFunc) => string | TemplateResult<1>);
 | 
			
		||||
  lovelace: (localize: LocalizeFunc) => LovelaceConfig;
 | 
			
		||||
  entities: (localize: LocalizeFunc) => Entity[];
 | 
			
		||||
  theme: () => Record<string, string> | null;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
import { until } from "lit/directives/until";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../src/components/ha-card";
 | 
			
		||||
import "../../../src/components/ha-button";
 | 
			
		||||
import "../../../src/components/ha-circular-progress";
 | 
			
		||||
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
 | 
			
		||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
@@ -11,7 +12,6 @@ import {
 | 
			
		||||
  demoConfigs,
 | 
			
		||||
  selectedDemoConfig,
 | 
			
		||||
  selectedDemoConfigIndex,
 | 
			
		||||
  setDemoConfig,
 | 
			
		||||
} from "../configs/demo-configs";
 | 
			
		||||
 | 
			
		||||
@customElement("ha-demo-card")
 | 
			
		||||
@@ -39,38 +39,57 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
        <div class="picker">
 | 
			
		||||
          <div class="label">
 | 
			
		||||
            ${this._switching
 | 
			
		||||
              ? html`<ha-circular-progress
 | 
			
		||||
                  indeterminate
 | 
			
		||||
                ></ha-circular-progress>`
 | 
			
		||||
              ? html`
 | 
			
		||||
                  <ha-circular-progress indeterminate></ha-circular-progress>
 | 
			
		||||
                `
 | 
			
		||||
              : until(
 | 
			
		||||
                  selectedDemoConfig.then(
 | 
			
		||||
                    (conf) => html`
 | 
			
		||||
                      ${conf.name}
 | 
			
		||||
                      <small>
 | 
			
		||||
                        <a target="_blank" href=${conf.authorUrl}>
 | 
			
		||||
                          ${this.hass.localize(
 | 
			
		||||
                            "ui.panel.page-demo.cards.demo.demo_by",
 | 
			
		||||
                            { name: conf.authorName }
 | 
			
		||||
                          )}
 | 
			
		||||
                        </a>
 | 
			
		||||
                        ${this.hass.localize(
 | 
			
		||||
                          "ui.panel.page-demo.cards.demo.demo_by",
 | 
			
		||||
                          {
 | 
			
		||||
                            name: html`
 | 
			
		||||
                              <a target="_blank" href=${conf.authorUrl}>
 | 
			
		||||
                                ${conf.authorName}
 | 
			
		||||
                              </a>
 | 
			
		||||
                            `,
 | 
			
		||||
                          }
 | 
			
		||||
                        )}
 | 
			
		||||
                      </small>
 | 
			
		||||
                    `
 | 
			
		||||
                  ),
 | 
			
		||||
                  ""
 | 
			
		||||
                )}
 | 
			
		||||
          </div>
 | 
			
		||||
          <mwc-button @click=${this._nextConfig} .disabled=${this._switching}>
 | 
			
		||||
 | 
			
		||||
          <ha-button @click=${this._nextConfig} .disabled=${this._switching}>
 | 
			
		||||
            ${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
          </ha-button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="content small-hidden">
 | 
			
		||||
          ${this.hass.localize("ui.panel.page-demo.cards.demo.introduction")}
 | 
			
		||||
        <div class="content">
 | 
			
		||||
          <p class="small-hidden">
 | 
			
		||||
            ${this.hass.localize("ui.panel.page-demo.cards.demo.introduction")}
 | 
			
		||||
          </p>
 | 
			
		||||
          ${until(
 | 
			
		||||
            selectedDemoConfig.then((conf) => {
 | 
			
		||||
              if (typeof conf.description === "function") {
 | 
			
		||||
                return conf.description(this.hass.localize);
 | 
			
		||||
              }
 | 
			
		||||
              if (conf.description) {
 | 
			
		||||
                return html`<p>${conf.description}</p>`;
 | 
			
		||||
              }
 | 
			
		||||
              return nothing;
 | 
			
		||||
            }),
 | 
			
		||||
            nothing
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="actions small-hidden">
 | 
			
		||||
          <a href="https://www.home-assistant.io" target="_blank">
 | 
			
		||||
            <mwc-button>
 | 
			
		||||
            <ha-button>
 | 
			
		||||
              ${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
 | 
			
		||||
            </mwc-button>
 | 
			
		||||
            </ha-button>
 | 
			
		||||
          </a>
 | 
			
		||||
        </div>
 | 
			
		||||
      </ha-card>
 | 
			
		||||
@@ -94,13 +113,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
 | 
			
		||||
  private async _updateConfig(index: number) {
 | 
			
		||||
    this._switching = true;
 | 
			
		||||
    try {
 | 
			
		||||
      await setDemoConfig(this.hass, this.lovelace!, index);
 | 
			
		||||
    } catch (err: any) {
 | 
			
		||||
      alert("Failed to switch config :-(");
 | 
			
		||||
    } finally {
 | 
			
		||||
      this._switching = false;
 | 
			
		||||
    }
 | 
			
		||||
    fireEvent(this, "set-demo-config" as any, { index });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get styles(): CSSResultGroup {
 | 
			
		||||
@@ -108,6 +121,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
      css`
 | 
			
		||||
        a {
 | 
			
		||||
          color: var(--primary-color);
 | 
			
		||||
          display: inline-block;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .actions a {
 | 
			
		||||
@@ -115,7 +129,11 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .content {
 | 
			
		||||
          padding: 16px;
 | 
			
		||||
          padding: 0 16px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .content p {
 | 
			
		||||
          margin: 16px 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .picker {
 | 
			
		||||
@@ -125,7 +143,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
          height: 60px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .picker mwc-button {
 | 
			
		||||
        .picker ha-button {
 | 
			
		||||
          margin-right: 8px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -138,9 +156,8 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .actions {
 | 
			
		||||
          padding-left: 8px;
 | 
			
		||||
          padding: 0px 8px 4px 8px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @media only screen and (max-width: 500px) {
 | 
			
		||||
          .small-hidden {
 | 
			
		||||
            display: none;
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import {
 | 
			
		||||
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
 | 
			
		||||
import { HomeAssistant } from "../../src/types";
 | 
			
		||||
import { selectedDemoConfig } from "./configs/demo-configs";
 | 
			
		||||
import { mockAreaRegistry } from "./stubs/area_registry";
 | 
			
		||||
import { mockAuth } from "./stubs/auth";
 | 
			
		||||
import { mockConfigEntries } from "./stubs/config_entries";
 | 
			
		||||
import { mockEnergy } from "./stubs/energy";
 | 
			
		||||
@@ -23,10 +24,10 @@ import { mockLovelace } from "./stubs/lovelace";
 | 
			
		||||
import { mockMediaPlayer } from "./stubs/media_player";
 | 
			
		||||
import { mockPersistentNotification } from "./stubs/persistent_notification";
 | 
			
		||||
import { mockRecorder } from "./stubs/recorder";
 | 
			
		||||
import { mockTodo } from "./stubs/todo";
 | 
			
		||||
import { mockSensor } from "./stubs/sensor";
 | 
			
		||||
import { mockSystemLog } from "./stubs/system_log";
 | 
			
		||||
import { mockTemplate } from "./stubs/template";
 | 
			
		||||
import { mockTodo } from "./stubs/todo";
 | 
			
		||||
import { mockTranslations } from "./stubs/translations";
 | 
			
		||||
 | 
			
		||||
@customElement("ha-demo")
 | 
			
		||||
@@ -62,6 +63,7 @@ export class HaDemo extends HomeAssistantAppEl {
 | 
			
		||||
    mockEnergy(hass);
 | 
			
		||||
    mockPersistentNotification(hass);
 | 
			
		||||
    mockConfigEntries(hass);
 | 
			
		||||
    mockAreaRegistry(hass);
 | 
			
		||||
    mockEntityRegistry(hass, [
 | 
			
		||||
      {
 | 
			
		||||
        config_entry_id: "co2signal",
 | 
			
		||||
@@ -72,6 +74,8 @@ export class HaDemo extends HomeAssistantAppEl {
 | 
			
		||||
        id: "sensor.co2_intensity",
 | 
			
		||||
        name: null,
 | 
			
		||||
        icon: null,
 | 
			
		||||
        labels: [],
 | 
			
		||||
        categories: {},
 | 
			
		||||
        platform: "co2signal",
 | 
			
		||||
        hidden_by: null,
 | 
			
		||||
        entity_category: null,
 | 
			
		||||
@@ -88,6 +92,8 @@ export class HaDemo extends HomeAssistantAppEl {
 | 
			
		||||
        id: "sensor.co2_intensity",
 | 
			
		||||
        name: null,
 | 
			
		||||
        icon: null,
 | 
			
		||||
        labels: [],
 | 
			
		||||
        categories: {},
 | 
			
		||||
        platform: "co2signal",
 | 
			
		||||
        hidden_by: null,
 | 
			
		||||
        entity_category: null,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,4 +4,11 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
export const mockAreaRegistry = (
 | 
			
		||||
  hass: MockHomeAssistant,
 | 
			
		||||
  data: AreaRegistryEntry[] = []
 | 
			
		||||
) => hass.mockWS("config/area_registry/list", () => data);
 | 
			
		||||
) => {
 | 
			
		||||
  hass.mockWS("config/area_registry/list", () => data);
 | 
			
		||||
  const areas = {};
 | 
			
		||||
  data.forEach((area) => {
 | 
			
		||||
    areas[area.area_id] = area;
 | 
			
		||||
  });
 | 
			
		||||
  hass.updateHass({ areas });
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => {
 | 
			
		||||
    supports_options: false,
 | 
			
		||||
    supports_remove_device: false,
 | 
			
		||||
    supports_unload: true,
 | 
			
		||||
    supports_reconfigure: true,
 | 
			
		||||
    pref_disable_new_entities: false,
 | 
			
		||||
    pref_disable_polling: false,
 | 
			
		||||
    disabled_by: null,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,4 +4,11 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
export const mockDeviceRegistry = (
 | 
			
		||||
  hass: MockHomeAssistant,
 | 
			
		||||
  data: DeviceRegistryEntry[] = []
 | 
			
		||||
) => hass.mockWS("config/device_registry/list", () => data);
 | 
			
		||||
) => {
 | 
			
		||||
  hass.mockWS("config/device_registry/list", () => data);
 | 
			
		||||
  const devices = {};
 | 
			
		||||
  data.forEach((device) => {
 | 
			
		||||
    devices[device.id] = device;
 | 
			
		||||
  });
 | 
			
		||||
  hass.updateHass({ devices });
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
 | 
			
		||||
import { format, startOfToday, startOfTomorrow } from "date-fns";
 | 
			
		||||
import {
 | 
			
		||||
  EnergyInfo,
 | 
			
		||||
  EnergyPreferences,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								demo/src/stubs/floor_registry.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								demo/src/stubs/floor_registry.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { FloorRegistryEntry } from "../../../src/data/floor_registry";
 | 
			
		||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
 | 
			
		||||
export const mockFloorRegistry = (
 | 
			
		||||
  hass: MockHomeAssistant,
 | 
			
		||||
  data: FloorRegistryEntry[] = []
 | 
			
		||||
) => hass.mockWS("config/floor_registry/list", () => data);
 | 
			
		||||
							
								
								
									
										7
									
								
								demo/src/stubs/label_registry.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								demo/src/stubs/label_registry.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { LabelRegistryEntry } from "../../../src/data/label_registry";
 | 
			
		||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
 | 
			
		||||
export const mockLabelRegistry = (
 | 
			
		||||
  hass: MockHomeAssistant,
 | 
			
		||||
  data: LabelRegistryEntry[] = []
 | 
			
		||||
) => hass.mockWS("config/label_registry/list", () => data);
 | 
			
		||||
@@ -1,9 +1,12 @@
 | 
			
		||||
import type { LocalizeFunc } from "../../../src/common/translations/localize";
 | 
			
		||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
import { selectedDemoConfig } from "../configs/demo-configs";
 | 
			
		||||
import {
 | 
			
		||||
  selectedDemoConfig,
 | 
			
		||||
  selectedDemoConfigIndex,
 | 
			
		||||
  setDemoConfig,
 | 
			
		||||
} from "../configs/demo-configs";
 | 
			
		||||
import "../custom-cards/cast-demo-row";
 | 
			
		||||
import "../custom-cards/ha-demo-card";
 | 
			
		||||
import type { HADemoCard } from "../custom-cards/ha-demo-card";
 | 
			
		||||
 | 
			
		||||
export const mockLovelace = (
 | 
			
		||||
  hass: MockHomeAssistant,
 | 
			
		||||
@@ -19,17 +22,22 @@ export const mockLovelace = (
 | 
			
		||||
  hass.mockWS("lovelace/resources", () => Promise.resolve([]));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
customElements.whenDefined("hui-view").then(() => {
 | 
			
		||||
customElements.whenDefined("hui-root").then(() => {
 | 
			
		||||
  // eslint-disable-next-line
 | 
			
		||||
  const HUIView = customElements.get("hui-view");
 | 
			
		||||
  // Patch HUI-VIEW to make the lovelace object available to the demo card
 | 
			
		||||
  const oldCreateCard = HUIView!.prototype.createCardElement;
 | 
			
		||||
  const HUIRoot = customElements.get("hui-root")!;
 | 
			
		||||
 | 
			
		||||
  HUIView!.prototype.createCardElement = function (config) {
 | 
			
		||||
    const el = oldCreateCard.call(this, config);
 | 
			
		||||
    if (el.tagName === "HA-DEMO-CARD") {
 | 
			
		||||
      (el as HADemoCard).lovelace = this.lovelace;
 | 
			
		||||
    }
 | 
			
		||||
    return el;
 | 
			
		||||
  const oldFirstUpdated = HUIRoot.prototype.firstUpdated;
 | 
			
		||||
 | 
			
		||||
  HUIRoot.prototype.firstUpdated = function (changedProperties) {
 | 
			
		||||
    oldFirstUpdated.call(this, changedProperties);
 | 
			
		||||
    this.addEventListener("set-demo-config", async (ev) => {
 | 
			
		||||
      const index = (ev as CustomEvent).detail.index;
 | 
			
		||||
      try {
 | 
			
		||||
        await setDemoConfig(this.hass, this.lovelace!, index);
 | 
			
		||||
      } catch (err: any) {
 | 
			
		||||
        setDemoConfig(this.hass, this.lovelace!, selectedDemoConfigIndex);
 | 
			
		||||
        alert("Failed to switch config :-(");
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
import { load } from "js-yaml";
 | 
			
		||||
import { html, css, LitElement, PropertyValues } from "lit";
 | 
			
		||||
import { LitElement, PropertyValueMap, css, html, nothing } from "lit";
 | 
			
		||||
import { customElement, property, query, state } from "lit/decorators";
 | 
			
		||||
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import "../../../src/panels/lovelace/cards/hui-card";
 | 
			
		||||
import type { HuiCard } from "../../../src/panels/lovelace/cards/hui-card";
 | 
			
		||||
import { HomeAssistant } from "../../../src/types";
 | 
			
		||||
 | 
			
		||||
export interface DemoCardConfig {
 | 
			
		||||
@@ -19,7 +21,12 @@ class DemoCard extends LitElement {
 | 
			
		||||
 | 
			
		||||
  @state() private _size?: number;
 | 
			
		||||
 | 
			
		||||
  @query("#card") private _card!: HTMLElement;
 | 
			
		||||
  @query("hui-card", false) private _card?: HuiCard;
 | 
			
		||||
 | 
			
		||||
  private _config = memoizeOne((config: string) => {
 | 
			
		||||
    const c = (load(config) as any)[0];
 | 
			
		||||
    return c;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return html`
 | 
			
		||||
@@ -30,63 +37,32 @@ class DemoCard extends LitElement {
 | 
			
		||||
          : ""}
 | 
			
		||||
      </h2>
 | 
			
		||||
      <div class="root">
 | 
			
		||||
        <div id="card"></div>
 | 
			
		||||
        ${this.showConfig ? html`<pre>${this.config.config.trim()}</pre>` : ""}
 | 
			
		||||
        <hui-card
 | 
			
		||||
          .config=${this._config(this.config.config)}
 | 
			
		||||
          .hass=${this.hass}
 | 
			
		||||
          @card-updated=${this._cardUpdated}
 | 
			
		||||
        ></hui-card>
 | 
			
		||||
        ${this.showConfig
 | 
			
		||||
          ? html`<pre>${this.config.config.trim()}</pre>`
 | 
			
		||||
          : nothing}
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updated(changedProps: PropertyValues) {
 | 
			
		||||
    super.updated(changedProps);
 | 
			
		||||
 | 
			
		||||
    if (changedProps.has("config")) {
 | 
			
		||||
      const card = this._card;
 | 
			
		||||
      while (card.lastChild) {
 | 
			
		||||
        card.removeChild(card.lastChild);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const el = this._createCardElement((load(this.config.config) as any)[0]);
 | 
			
		||||
      card.appendChild(el);
 | 
			
		||||
      this._getSize(el);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changedProps.has("hass")) {
 | 
			
		||||
      const card = this._card.lastChild;
 | 
			
		||||
      if (card) {
 | 
			
		||||
        (card as any).hass = this.hass;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  private async _cardUpdated(ev) {
 | 
			
		||||
    ev.stopPropagation();
 | 
			
		||||
    this._updateSize();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _getSize(el) {
 | 
			
		||||
    await customElements.whenDefined(el.localName);
 | 
			
		||||
 | 
			
		||||
    if (!("getCardSize" in el)) {
 | 
			
		||||
      this._size = undefined;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this._size = await el.getCardSize();
 | 
			
		||||
  private async _updateSize() {
 | 
			
		||||
    this._size = await this._card?.getCardSize();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _createCardElement(cardConfig) {
 | 
			
		||||
    const element = createCardElement(cardConfig);
 | 
			
		||||
    if (this.hass) {
 | 
			
		||||
      element.hass = this.hass;
 | 
			
		||||
    }
 | 
			
		||||
    element.addEventListener(
 | 
			
		||||
      "ll-rebuild",
 | 
			
		||||
      (ev) => {
 | 
			
		||||
        ev.stopPropagation();
 | 
			
		||||
        this._rebuildCard(element, cardConfig);
 | 
			
		||||
      },
 | 
			
		||||
      { once: true }
 | 
			
		||||
    );
 | 
			
		||||
    return element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _rebuildCard(cardElToReplace, config) {
 | 
			
		||||
    const newCardEl = this._createCardElement(config);
 | 
			
		||||
    cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
 | 
			
		||||
  protected update(
 | 
			
		||||
    _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
 | 
			
		||||
  ): void {
 | 
			
		||||
    super.update(_changedProperties);
 | 
			
		||||
    this._updateSize();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static styles = css`
 | 
			
		||||
@@ -101,7 +77,7 @@ class DemoCard extends LitElement {
 | 
			
		||||
      font-size: 0.5em;
 | 
			
		||||
      color: var(--primary-text-color);
 | 
			
		||||
    }
 | 
			
		||||
    #card {
 | 
			
		||||
    hui-card {
 | 
			
		||||
      max-width: 400px;
 | 
			
		||||
      width: 100vw;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ export const basicTrace: DemoTrace = {
 | 
			
		||||
        {
 | 
			
		||||
          path: "trigger/0",
 | 
			
		||||
          timestamp: "2021-03-25T04:36:51.223693+00:00",
 | 
			
		||||
          changed_variables: {},
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      "condition/0": [
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ export const motionLightTrace: DemoTrace = {
 | 
			
		||||
        {
 | 
			
		||||
          path: "trigger/0",
 | 
			
		||||
          timestamp: "2021-03-25T04:36:51.223693+00:00",
 | 
			
		||||
          changed_variables: {},
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      "action/0": [
 | 
			
		||||
 
 | 
			
		||||
@@ -64,6 +64,12 @@ const ACTIONS = [
 | 
			
		||||
      entity_id: "input_boolean.toggle_4",
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    sequence: [
 | 
			
		||||
      { scene: "scene.kitchen_morning" },
 | 
			
		||||
      { service: "light.turn_off", target: { entity_id: "light.kitchen" } },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    parallel: [
 | 
			
		||||
      { scene: "scene.kitchen_morning" },
 | 
			
		||||
@@ -136,7 +142,7 @@ export class DemoAutomationDescribeAction extends LitElement {
 | 
			
		||||
        <div class="action">
 | 
			
		||||
          <span>
 | 
			
		||||
            ${this._action
 | 
			
		||||
              ? describeAction(this.hass, [], this._action)
 | 
			
		||||
              ? describeAction(this.hass, [], [], [], this._action)
 | 
			
		||||
              : "<invalid YAML>"}
 | 
			
		||||
          </span>
 | 
			
		||||
          <ha-yaml-editor
 | 
			
		||||
@@ -149,7 +155,7 @@ export class DemoAutomationDescribeAction extends LitElement {
 | 
			
		||||
        ${ACTIONS.map(
 | 
			
		||||
          (conf) => html`
 | 
			
		||||
            <div class="action">
 | 
			
		||||
              <span>${describeAction(this.hass, [], conf as any)}</span>
 | 
			
		||||
              <span>${describeAction(this.hass, [], [], [], conf as any)}</span>
 | 
			
		||||
              <pre>${dump(conf)}</pre>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
 
 | 
			
		||||
@@ -21,10 +21,10 @@ const ENTITIES = [
 | 
			
		||||
  }),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const conditions = [
 | 
			
		||||
  { condition: "and" },
 | 
			
		||||
  { condition: "not" },
 | 
			
		||||
  { condition: "or" },
 | 
			
		||||
const conditions: Condition[] = [
 | 
			
		||||
  { condition: "and", conditions: [] },
 | 
			
		||||
  { condition: "not", conditions: [] },
 | 
			
		||||
  { condition: "or", conditions: [] },
 | 
			
		||||
  { condition: "state", entity_id: "light.kitchen", state: "on" },
 | 
			
		||||
  {
 | 
			
		||||
    condition: "numeric_state",
 | 
			
		||||
@@ -34,11 +34,11 @@ const conditions = [
 | 
			
		||||
    above: 20,
 | 
			
		||||
  },
 | 
			
		||||
  { condition: "sun", after: "sunset" },
 | 
			
		||||
  { condition: "sun", after: "sunrise", offset: "-01:00" },
 | 
			
		||||
  { condition: "sun", after: "sunrise", before_offset: 3600 },
 | 
			
		||||
  { condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" },
 | 
			
		||||
  { condition: "trigger", id: "motion" },
 | 
			
		||||
  { condition: "time" },
 | 
			
		||||
  { condition: "template" },
 | 
			
		||||
  { condition: "template", value_template: "" },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const initialCondition: Condition = {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation
 | 
			
		||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
 | 
			
		||||
import { Action } from "../../../../src/data/script";
 | 
			
		||||
import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition";
 | 
			
		||||
import { HaSequenceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-sequence";
 | 
			
		||||
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
 | 
			
		||||
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
 | 
			
		||||
import { HaStopAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-stop";
 | 
			
		||||
@@ -39,6 +40,7 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
 | 
			
		||||
  { name: "If-Then", actions: [HaIfAction.defaultConfig] },
 | 
			
		||||
  { name: "Choose", actions: [HaChooseAction.defaultConfig] },
 | 
			
		||||
  { name: "Variables", actions: [{ variables: { hello: "1" } }] },
 | 
			
		||||
  { name: "Sequence", actions: [HaSequenceAction.defaultConfig] },
 | 
			
		||||
  { name: "Parallel", actions: [HaParallelAction.defaultConfig] },
 | 
			
		||||
  { name: "Stop", actions: [HaStopAction.defaultConfig] },
 | 
			
		||||
];
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,7 @@ export class DemoAutomationTraceTimeline extends LitElement {
 | 
			
		||||
    super.firstUpdated(changedProps);
 | 
			
		||||
    const hass = provideHass(this);
 | 
			
		||||
    hass.updateTranslations(null, "en");
 | 
			
		||||
    hass.updateTranslations("config", "en");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get styles() {
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,7 @@ export class DemoAutomationTrace extends LitElement {
 | 
			
		||||
    super.firstUpdated(changedProps);
 | 
			
		||||
    const hass = provideHass(this);
 | 
			
		||||
    hass.updateTranslations(null, "en");
 | 
			
		||||
    hass.updateTranslations("config", "en");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get styles() {
 | 
			
		||||
 
 | 
			
		||||
@@ -162,7 +162,7 @@ export class DemoHaBarButton extends LitElement {
 | 
			
		||||
      }
 | 
			
		||||
      .custom-group {
 | 
			
		||||
        --control-button-group-thickness: 100px;
 | 
			
		||||
        --control-button-group-border-radius: 18px;
 | 
			
		||||
        --control-button-group-border-radius: 36px;
 | 
			
		||||
        --control-button-group-spacing: 20px;
 | 
			
		||||
      }
 | 
			
		||||
      .custom-group ha-control-button {
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ export class DemoHarControlNumberButtons extends LitElement {
 | 
			
		||||
        --control-number-buttons-background-color: #2196f3;
 | 
			
		||||
        --control-number-buttons-background-opacity: 0.1;
 | 
			
		||||
        --control-number-buttons-thickness: 100px;
 | 
			
		||||
        --control-number-buttons-border-radius: 24px;
 | 
			
		||||
        --control-number-buttons-border-radius: 36px;
 | 
			
		||||
      }
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -186,8 +186,8 @@ export class DemoHaControlSelect extends LitElement {
 | 
			
		||||
      .custom {
 | 
			
		||||
        --mdc-icon-size: 24px;
 | 
			
		||||
        --control-select-color: var(--state-fan-active-color);
 | 
			
		||||
        --control-select-thickness: 100px;
 | 
			
		||||
        --control-select-border-radius: 24px;
 | 
			
		||||
        --control-select-thickness: 130px;
 | 
			
		||||
        --control-select-border-radius: 36px;
 | 
			
		||||
      }
 | 
			
		||||
      .vertical-selects {
 | 
			
		||||
        height: 300px;
 | 
			
		||||
 
 | 
			
		||||
@@ -150,8 +150,8 @@ export class DemoHaBarSlider extends LitElement {
 | 
			
		||||
        --control-slider-color: #ffcf4c;
 | 
			
		||||
        --control-slider-background: #ffcf4c;
 | 
			
		||||
        --control-slider-background-opacity: 0.2;
 | 
			
		||||
        --control-slider-thickness: 100px;
 | 
			
		||||
        --control-slider-border-radius: 24px;
 | 
			
		||||
        --control-slider-thickness: 130px;
 | 
			
		||||
        --control-slider-border-radius: 36px;
 | 
			
		||||
      }
 | 
			
		||||
      .vertical-sliders {
 | 
			
		||||
        height: 300px;
 | 
			
		||||
 
 | 
			
		||||
@@ -117,8 +117,8 @@ export class DemoHaControlSwitch extends LitElement {
 | 
			
		||||
      .custom {
 | 
			
		||||
        --control-switch-on-color: var(--green-color);
 | 
			
		||||
        --control-switch-off-color: var(--red-color);
 | 
			
		||||
        --control-switch-thickness: 100px;
 | 
			
		||||
        --control-switch-border-radius: 24px;
 | 
			
		||||
        --control-switch-thickness: 130px;
 | 
			
		||||
        --control-switch-border-radius: 36px;
 | 
			
		||||
        --control-switch-padding: 6px;
 | 
			
		||||
        --mdc-icon-size: 24px;
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,7 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "backyard",
 | 
			
		||||
@@ -77,6 +78,7 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: null,
 | 
			
		||||
@@ -95,30 +97,37 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const AREAS: AreaRegistryEntry[] = [
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "backyard",
 | 
			
		||||
    floor_id: null,
 | 
			
		||||
    name: "Backyard",
 | 
			
		||||
    icon: null,
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "bedroom",
 | 
			
		||||
    floor_id: null,
 | 
			
		||||
    name: "Bedroom",
 | 
			
		||||
    icon: "mdi:bed",
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "livingroom",
 | 
			
		||||
    floor_id: null,
 | 
			
		||||
    name: "Livingroom",
 | 
			
		||||
    icon: "mdi:sofa",
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,10 @@ import { provideHass } from "../../../../src/fake_data/provide_hass";
 | 
			
		||||
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
 | 
			
		||||
import type { HomeAssistant } from "../../../../src/types";
 | 
			
		||||
import "../../components/demo-black-white-row";
 | 
			
		||||
import { FloorRegistryEntry } from "../../../../src/data/floor_registry";
 | 
			
		||||
import { LabelRegistryEntry } from "../../../../src/data/label_registry";
 | 
			
		||||
import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry";
 | 
			
		||||
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
 | 
			
		||||
 | 
			
		||||
const ENTITIES = [
 | 
			
		||||
  getEntity("alarm_control_panel", "alarm", "disarmed", {
 | 
			
		||||
@@ -55,6 +59,7 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "backyard",
 | 
			
		||||
@@ -73,6 +78,7 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: null,
 | 
			
		||||
@@ -91,30 +97,78 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const AREAS: AreaRegistryEntry[] = [
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "backyard",
 | 
			
		||||
    floor_id: "ground",
 | 
			
		||||
    name: "Backyard",
 | 
			
		||||
    icon: null,
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "bedroom",
 | 
			
		||||
    floor_id: "first",
 | 
			
		||||
    name: "Bedroom",
 | 
			
		||||
    icon: "mdi:bed",
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "livingroom",
 | 
			
		||||
    floor_id: "ground",
 | 
			
		||||
    name: "Livingroom",
 | 
			
		||||
    icon: "mdi:sofa",
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const FLOORS: FloorRegistryEntry[] = [
 | 
			
		||||
  {
 | 
			
		||||
    floor_id: "ground",
 | 
			
		||||
    name: "Ground floor",
 | 
			
		||||
    level: 0,
 | 
			
		||||
    icon: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    floor_id: "first",
 | 
			
		||||
    name: "First floor",
 | 
			
		||||
    level: 1,
 | 
			
		||||
    icon: "mdi:numeric-1",
 | 
			
		||||
    aliases: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    floor_id: "second",
 | 
			
		||||
    name: "Second floor",
 | 
			
		||||
    level: 2,
 | 
			
		||||
    icon: "mdi:numeric-2",
 | 
			
		||||
    aliases: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const LABELS: LabelRegistryEntry[] = [
 | 
			
		||||
  {
 | 
			
		||||
    label_id: "energy",
 | 
			
		||||
    name: "Energy",
 | 
			
		||||
    icon: null,
 | 
			
		||||
    color: "yellow",
 | 
			
		||||
    description: null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label_id: "entertainment",
 | 
			
		||||
    name: "Entertainment",
 | 
			
		||||
    icon: "mdi:popcorn",
 | 
			
		||||
    color: "blue",
 | 
			
		||||
    description: null,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@@ -125,7 +179,12 @@ const SCHEMAS: {
 | 
			
		||||
  {
 | 
			
		||||
    name: "One of each",
 | 
			
		||||
    input: {
 | 
			
		||||
      label: { name: "Label", selector: { label: {} } },
 | 
			
		||||
      floor: { name: "Floor", selector: { floor: {} } },
 | 
			
		||||
      area: { name: "Area", selector: { area: {} } },
 | 
			
		||||
      device: { name: "Device", selector: { device: {} } },
 | 
			
		||||
      entity: { name: "Entity", selector: { entity: {} } },
 | 
			
		||||
      target: { name: "Target", selector: { target: {} } },
 | 
			
		||||
      state: {
 | 
			
		||||
        name: "State",
 | 
			
		||||
        selector: { state: { entity_id: "alarm_control_panel.alarm" } },
 | 
			
		||||
@@ -134,15 +193,12 @@ const SCHEMAS: {
 | 
			
		||||
        name: "Attribute",
 | 
			
		||||
        selector: { attribute: { entity_id: "" } },
 | 
			
		||||
      },
 | 
			
		||||
      device: { name: "Device", selector: { device: {} } },
 | 
			
		||||
      config_entry: {
 | 
			
		||||
        name: "Integration",
 | 
			
		||||
        selector: { config_entry: {} },
 | 
			
		||||
      },
 | 
			
		||||
      duration: { name: "Duration", selector: { duration: {} } },
 | 
			
		||||
      addon: { name: "Addon", selector: { addon: {} } },
 | 
			
		||||
      area: { name: "Area", selector: { area: {} } },
 | 
			
		||||
      target: { name: "Target", selector: { target: {} } },
 | 
			
		||||
      number_box: {
 | 
			
		||||
        name: "Number Box",
 | 
			
		||||
        selector: {
 | 
			
		||||
@@ -291,6 +347,8 @@ const SCHEMAS: {
 | 
			
		||||
      entity: { name: "Entity", selector: { entity: { multiple: true } } },
 | 
			
		||||
      device: { name: "Device", selector: { device: { multiple: true } } },
 | 
			
		||||
      area: { name: "Area", selector: { area: { multiple: true } } },
 | 
			
		||||
      floor: { name: "Floor", selector: { floor: { multiple: true } } },
 | 
			
		||||
      label: { name: "Label", selector: { label: { multiple: true } } },
 | 
			
		||||
      select: {
 | 
			
		||||
        name: "Select Multiple",
 | 
			
		||||
        selector: {
 | 
			
		||||
@@ -347,6 +405,8 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
 | 
			
		||||
    mockDeviceRegistry(hass, DEVICES);
 | 
			
		||||
    mockConfigEntries(hass);
 | 
			
		||||
    mockAreaRegistry(hass, AREAS);
 | 
			
		||||
    mockFloorRegistry(hass, FLOORS);
 | 
			
		||||
    mockLabelRegistry(hass, LABELS);
 | 
			
		||||
    mockHassioSupervisor(hass);
 | 
			
		||||
    hass.mockWS("auth/sign_path", (params) => params);
 | 
			
		||||
    hass.mockWS("media_player/browse_media", this._browseMedia);
 | 
			
		||||
 
 | 
			
		||||
@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeNumeric extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateTimeNumeric(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.language,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateTimeNumeric(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.am_pm,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateTimeNumeric(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.twenty_four,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
          ([key, value]) => html`
 | 
			
		||||
            <div class="container">
 | 
			
		||||
              <div>${value.nativeName}</div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateTimeNumeric(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.language,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            `
 | 
			
		||||
          )}
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateTimeNumeric(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.am_pm,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateTimeNumeric(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.twenty_four,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
        )}
 | 
			
		||||
      </mwc-list>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeSeconds extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateTimeWithSeconds(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.language,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateTimeWithSeconds(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.am_pm,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateTimeWithSeconds(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.twenty_four,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
          ([key, value]) => html`
 | 
			
		||||
            <div class="container">
 | 
			
		||||
              <div>${value.nativeName}</div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateTimeWithSeconds(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.language,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            `
 | 
			
		||||
          )}
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateTimeWithSeconds(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.am_pm,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateTimeWithSeconds(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.twenty_four,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
        )}
 | 
			
		||||
      </mwc-list>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeShortYear extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatShortDateTimeWithYear(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.language,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatShortDateTimeWithYear(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.am_pm,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatShortDateTimeWithYear(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.twenty_four,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
          ([key, value]) => html`
 | 
			
		||||
            <div class="container">
 | 
			
		||||
              <div>${value.nativeName}</div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatShortDateTimeWithYear(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.language,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            `
 | 
			
		||||
          )}
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatShortDateTimeWithYear(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.am_pm,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatShortDateTimeWithYear(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.twenty_four,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
        )}
 | 
			
		||||
      </mwc-list>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeShort extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatShortDateTime(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.language,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatShortDateTime(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.am_pm,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatShortDateTime(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.twenty_four,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
          ([key, value]) => html`
 | 
			
		||||
            <div class="container">
 | 
			
		||||
              <div>${value.nativeName}</div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatShortDateTime(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.language,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            `
 | 
			
		||||
          )}
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatShortDateTime(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.am_pm,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatShortDateTime(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.twenty_four,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
        )}
 | 
			
		||||
      </mwc-list>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,48 +56,46 @@ export class DemoDateTimeDateTime extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateTime(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.language,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateTime(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.am_pm,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateTime(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.twenty_four,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
          ([key, value]) => html`
 | 
			
		||||
            <div class="container">
 | 
			
		||||
              <div>${value.nativeName}</div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateTime(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.language,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            `
 | 
			
		||||
          )}
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateTime(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.am_pm,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateTime(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.twenty_four,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
        )}
 | 
			
		||||
      </mwc-list>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -35,59 +35,57 @@ export class DemoDateTimeDate extends LitElement {
 | 
			
		||||
          <div class="center">Month-Day-Year</div>
 | 
			
		||||
          <div class="center">Year-Month-Day</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateNumeric(
 | 
			
		||||
                    date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      date_format: DateFormat.language,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateNumeric(
 | 
			
		||||
                    date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      date_format: DateFormat.DMY,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateNumeric(
 | 
			
		||||
                    date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      date_format: DateFormat.MDY,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatDateNumeric(
 | 
			
		||||
                    date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      date_format: DateFormat.YMD,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
          ([key, value]) => html`
 | 
			
		||||
            <div class="container">
 | 
			
		||||
              <div>${value.nativeName}</div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateNumeric(
 | 
			
		||||
                  date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    date_format: DateFormat.language,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            `
 | 
			
		||||
          )}
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateNumeric(
 | 
			
		||||
                  date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    date_format: DateFormat.DMY,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateNumeric(
 | 
			
		||||
                  date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    date_format: DateFormat.MDY,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatDateNumeric(
 | 
			
		||||
                  date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    date_format: DateFormat.YMD,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
        )}
 | 
			
		||||
      </mwc-list>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,48 +56,46 @@ export class DemoDateTimeTimeSeconds extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatTimeWithSeconds(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.language,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatTimeWithSeconds(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.am_pm,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatTimeWithSeconds(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.twenty_four,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
          ([key, value]) => html`
 | 
			
		||||
            <div class="container">
 | 
			
		||||
              <div>${value.nativeName}</div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatTimeWithSeconds(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.language,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            `
 | 
			
		||||
          )}
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatTimeWithSeconds(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.am_pm,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatTimeWithSeconds(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.twenty_four,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
        )}
 | 
			
		||||
      </mwc-list>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,48 +56,46 @@ export class DemoDateTimeTimeWeekday extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatTimeWeekday(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.language,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatTimeWeekday(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.am_pm,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatTimeWeekday(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.twenty_four,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
          ([key, value]) => html`
 | 
			
		||||
            <div class="container">
 | 
			
		||||
              <div>${value.nativeName}</div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatTimeWeekday(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.language,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            `
 | 
			
		||||
          )}
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatTimeWeekday(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.am_pm,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatTimeWeekday(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.twenty_four,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
        )}
 | 
			
		||||
      </mwc-list>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,48 +56,46 @@ export class DemoDateTimeTime extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatTime(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.language,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatTime(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.am_pm,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="center">
 | 
			
		||||
                  ${formatTime(
 | 
			
		||||
                    this.date,
 | 
			
		||||
                    {
 | 
			
		||||
                      ...defaultLocale,
 | 
			
		||||
                      language: key,
 | 
			
		||||
                      time_format: TimeFormat.twenty_four,
 | 
			
		||||
                    },
 | 
			
		||||
                    demoConfig
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
          ([key, value]) => html`
 | 
			
		||||
            <div class="container">
 | 
			
		||||
              <div>${value.nativeName}</div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatTime(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.language,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            `
 | 
			
		||||
          )}
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatTime(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.am_pm,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="center">
 | 
			
		||||
                ${formatTime(
 | 
			
		||||
                  this.date,
 | 
			
		||||
                  {
 | 
			
		||||
                    ...defaultLocale,
 | 
			
		||||
                    language: key,
 | 
			
		||||
                    time_format: TimeFormat.twenty_four,
 | 
			
		||||
                  },
 | 
			
		||||
                  demoConfig
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
        )}
 | 
			
		||||
      </mwc-list>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ const ENTITIES = [
 | 
			
		||||
    latitude: 32.877105,
 | 
			
		||||
    longitude: 117.232185,
 | 
			
		||||
    gps_accuracy: 91,
 | 
			
		||||
    battery: 71,
 | 
			
		||||
    battery: 25,
 | 
			
		||||
    friendly_name: "Paulus",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("device_tracker", "demo_anne_therese", "school", {
 | 
			
		||||
@@ -19,7 +19,7 @@ const ENTITIES = [
 | 
			
		||||
    latitude: 32.877105,
 | 
			
		||||
    longitude: 117.232185,
 | 
			
		||||
    gps_accuracy: 91,
 | 
			
		||||
    battery: 71,
 | 
			
		||||
    battery: 50,
 | 
			
		||||
    friendly_name: "Anne Therese",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("device_tracker", "demo_home_boy", "home", {
 | 
			
		||||
@@ -27,7 +27,7 @@ const ENTITIES = [
 | 
			
		||||
    latitude: 32.877105,
 | 
			
		||||
    longitude: 117.232185,
 | 
			
		||||
    gps_accuracy: 91,
 | 
			
		||||
    battery: 71,
 | 
			
		||||
    battery: 75,
 | 
			
		||||
    friendly_name: "Home Boy",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("light", "bed_light", "on", {
 | 
			
		||||
@@ -39,21 +39,53 @@ const ENTITIES = [
 | 
			
		||||
  getEntity("light", "ceiling_lights", "off", {
 | 
			
		||||
    friendly_name: "Ceiling Lights",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("sensor", "battery_1", 20, {
 | 
			
		||||
    device_class: "battery",
 | 
			
		||||
    friendly_name: "Battery 1",
 | 
			
		||||
    unit_of_measurement: "%",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("sensor", "battery_2", 35, {
 | 
			
		||||
    device_class: "battery",
 | 
			
		||||
    friendly_name: "Battery 2",
 | 
			
		||||
    unit_of_measurement: "%",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("sensor", "battery_3", 40, {
 | 
			
		||||
    device_class: "battery",
 | 
			
		||||
    friendly_name: "Battery 3",
 | 
			
		||||
    unit_of_measurement: "%",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("sensor", "battery_4", 80, {
 | 
			
		||||
    device_class: "battery",
 | 
			
		||||
    friendly_name: "Battery 4",
 | 
			
		||||
    unit_of_measurement: "%",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("input_number", "min_battery_level", 30, {
 | 
			
		||||
    mode: "slider",
 | 
			
		||||
    step: 10,
 | 
			
		||||
    min: 0,
 | 
			
		||||
    max: 100,
 | 
			
		||||
    icon: "mdi:battery-alert-variant",
 | 
			
		||||
    friendly_name: "Minimum Battery Level",
 | 
			
		||||
    unit_of_measurement: "%",
 | 
			
		||||
  }),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const CONFIGS = [
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Unfiltered controller",
 | 
			
		||||
    heading: "Unfiltered entities",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entities
 | 
			
		||||
  entities:
 | 
			
		||||
  - light.bed_light
 | 
			
		||||
  - light.ceiling_lights
 | 
			
		||||
  - light.kitchen_lights
 | 
			
		||||
    - device_tracker.demo_anne_therese
 | 
			
		||||
    - device_tracker.demo_home_boy
 | 
			
		||||
    - device_tracker.demo_paulus
 | 
			
		||||
    - light.bed_light
 | 
			
		||||
    - light.ceiling_lights
 | 
			
		||||
    - light.kitchen_lights
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Filtered entities card",
 | 
			
		||||
    heading: "On and home entities",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
@@ -63,9 +95,28 @@ const CONFIGS = [
 | 
			
		||||
    - light.bed_light
 | 
			
		||||
    - light.ceiling_lights
 | 
			
		||||
    - light.kitchen_lights
 | 
			
		||||
  state_filter:
 | 
			
		||||
    - "on"
 | 
			
		||||
    - home
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: state
 | 
			
		||||
      state:
 | 
			
		||||
        - "on"
 | 
			
		||||
        - home
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Same state as Bed Light",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - device_tracker.demo_anne_therese
 | 
			
		||||
    - device_tracker.demo_home_boy
 | 
			
		||||
    - device_tracker.demo_paulus
 | 
			
		||||
    - light.bed_light
 | 
			
		||||
    - light.ceiling_lights
 | 
			
		||||
    - light.kitchen_lights
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: state
 | 
			
		||||
      state:
 | 
			
		||||
        - light.bed_light
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
@@ -79,9 +130,11 @@ const CONFIGS = [
 | 
			
		||||
    - light.bed_light
 | 
			
		||||
    - light.ceiling_lights
 | 
			
		||||
    - light.kitchen_lights
 | 
			
		||||
  state_filter:
 | 
			
		||||
    - "on"
 | 
			
		||||
    - not_home
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: state
 | 
			
		||||
      state:
 | 
			
		||||
        - "on"
 | 
			
		||||
        - home
 | 
			
		||||
  card:
 | 
			
		||||
    type: entities
 | 
			
		||||
    title: Custom Title
 | 
			
		||||
@@ -99,15 +152,101 @@ const CONFIGS = [
 | 
			
		||||
    - light.bed_light
 | 
			
		||||
    - light.ceiling_lights
 | 
			
		||||
    - light.kitchen_lights
 | 
			
		||||
  state_filter:
 | 
			
		||||
    - "on"
 | 
			
		||||
    - not_home
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: state
 | 
			
		||||
      state:
 | 
			
		||||
        - "on"
 | 
			
		||||
        - home
 | 
			
		||||
  card:
 | 
			
		||||
    type: glance
 | 
			
		||||
    show_state: true
 | 
			
		||||
    title: Custom Title
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading:
 | 
			
		||||
      "Filtered entities by battery attribute (< '30') using state filter",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - device_tracker.demo_anne_therese
 | 
			
		||||
    - device_tracker.demo_home_boy
 | 
			
		||||
    - device_tracker.demo_paulus
 | 
			
		||||
  state_filter:
 | 
			
		||||
    - operator: <
 | 
			
		||||
      attribute: battery
 | 
			
		||||
      value: "30"
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Unfiltered number entities",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entities
 | 
			
		||||
  entities:
 | 
			
		||||
    - input_number.min_battery_level
 | 
			
		||||
    - sensor.battery_1
 | 
			
		||||
    - sensor.battery_3
 | 
			
		||||
    - sensor.battery_2
 | 
			
		||||
    - sensor.battery_4
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Battery lower than 50%",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - sensor.battery_1
 | 
			
		||||
    - sensor.battery_3
 | 
			
		||||
    - sensor.battery_2
 | 
			
		||||
    - sensor.battery_4
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: numeric_state
 | 
			
		||||
      below: 50
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Battery lower than min battery level",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - sensor.battery_1
 | 
			
		||||
    - sensor.battery_3
 | 
			
		||||
    - sensor.battery_2
 | 
			
		||||
    - sensor.battery_4
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: numeric_state
 | 
			
		||||
      below: input_number.min_battery_level
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Battery between min battery level and 70%",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - sensor.battery_1
 | 
			
		||||
    - sensor.battery_3
 | 
			
		||||
    - sensor.battery_2
 | 
			
		||||
    - sensor.battery_4
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: numeric_state
 | 
			
		||||
      above: input_number.min_battery_level
 | 
			
		||||
      below: 70
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Error: Entities must be specified",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Error: Incorrect filter config",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - sensor.gas_station_lowest_price
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@customElement("demo-lovelace-entity-filter-card")
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,45 @@ const ENTITIES = [
 | 
			
		||||
    friendly_name: "Nest",
 | 
			
		||||
    supported_features: 43,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "overkiz_radiator", "heat", {
 | 
			
		||||
    current_temperature: 18,
 | 
			
		||||
    min_temp: 7,
 | 
			
		||||
    max_temp: 35,
 | 
			
		||||
    temperature: 20,
 | 
			
		||||
    hvac_modes: ["heat", "auto", "off"],
 | 
			
		||||
    friendly_name: "Overkiz radiator",
 | 
			
		||||
    supported_features: 17,
 | 
			
		||||
    preset_mode: "comfort",
 | 
			
		||||
    preset_modes: [
 | 
			
		||||
      "none",
 | 
			
		||||
      "frost_protection",
 | 
			
		||||
      "eco",
 | 
			
		||||
      "comfort",
 | 
			
		||||
      "comfort-1",
 | 
			
		||||
      "comfort-2",
 | 
			
		||||
      "auto",
 | 
			
		||||
      "boost",
 | 
			
		||||
      "external",
 | 
			
		||||
      "prog",
 | 
			
		||||
    ],
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "overkiz_towel_dryer", "heat", {
 | 
			
		||||
    current_temperature: null,
 | 
			
		||||
    min_temp: 7,
 | 
			
		||||
    max_temp: 35,
 | 
			
		||||
    hvac_modes: ["heat", "off"],
 | 
			
		||||
    friendly_name: "Overkiz towel dryer",
 | 
			
		||||
    supported_features: 16,
 | 
			
		||||
    preset_mode: "eco",
 | 
			
		||||
    preset_modes: [
 | 
			
		||||
      "none",
 | 
			
		||||
      "frost_protection",
 | 
			
		||||
      "eco",
 | 
			
		||||
      "comfort",
 | 
			
		||||
      "comfort-1",
 | 
			
		||||
      "comfort-2",
 | 
			
		||||
    ],
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "sensibo", "fan_only", {
 | 
			
		||||
    current_temperature: null,
 | 
			
		||||
    temperature: null,
 | 
			
		||||
@@ -46,7 +85,9 @@ const ENTITIES = [
 | 
			
		||||
    friendly_name: "Sensibo purifier",
 | 
			
		||||
    fan_modes: ["low", "high"],
 | 
			
		||||
    fan_mode: "low",
 | 
			
		||||
    supported_features: 9,
 | 
			
		||||
    swing_modes: ["on", "off", "both", "vertical", "horizontal"],
 | 
			
		||||
    swing_mode: "vertical",
 | 
			
		||||
    supported_features: 41,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "unavailable", "unavailable", {
 | 
			
		||||
    supported_features: 43,
 | 
			
		||||
@@ -59,8 +100,6 @@ const CONFIGS = [
 | 
			
		||||
    config: `
 | 
			
		||||
- type: thermostat
 | 
			
		||||
  entity: climate.ecobee
 | 
			
		||||
- type: thermostat
 | 
			
		||||
  entity: climate.nest
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
@@ -70,6 +109,66 @@ const CONFIGS = [
 | 
			
		||||
  entity: climate.nest
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Feature example",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: thermostat
 | 
			
		||||
  entity: climate.overkiz_radiator
 | 
			
		||||
  features:
 | 
			
		||||
    - type: climate-hvac-modes
 | 
			
		||||
      hvac_modes:
 | 
			
		||||
        - heat
 | 
			
		||||
        - 'off'
 | 
			
		||||
        - auto
 | 
			
		||||
    - type: climate-preset-modes
 | 
			
		||||
      style: icons
 | 
			
		||||
      preset_modes:
 | 
			
		||||
        - none
 | 
			
		||||
        - frost_protection
 | 
			
		||||
        - eco
 | 
			
		||||
        - comfort
 | 
			
		||||
        - comfort-1
 | 
			
		||||
        - comfort-2
 | 
			
		||||
        - auto
 | 
			
		||||
        - boost
 | 
			
		||||
        - external
 | 
			
		||||
        - prog
 | 
			
		||||
    - type: climate-preset-modes
 | 
			
		||||
      style: dropdown
 | 
			
		||||
      preset_modes:
 | 
			
		||||
        - none
 | 
			
		||||
        - frost_protection
 | 
			
		||||
        - eco
 | 
			
		||||
        - comfort
 | 
			
		||||
        - comfort-1
 | 
			
		||||
        - comfort-2
 | 
			
		||||
        - auto
 | 
			
		||||
        - boost
 | 
			
		||||
        - external
 | 
			
		||||
        - prog
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Preset only example",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: thermostat
 | 
			
		||||
  entity: climate.overkiz_towel_dryer
 | 
			
		||||
  features:
 | 
			
		||||
    - type: climate-hvac-modes
 | 
			
		||||
      hvac_modes:
 | 
			
		||||
        - heat
 | 
			
		||||
        - 'off'
 | 
			
		||||
    - type: climate-preset-modes
 | 
			
		||||
      style: icons
 | 
			
		||||
      preset_modes:
 | 
			
		||||
        - none
 | 
			
		||||
        - frost_protection
 | 
			
		||||
        - eco
 | 
			
		||||
        - comfort
 | 
			
		||||
        - comfort-1
 | 
			
		||||
        - comfort-2
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Fan only example",
 | 
			
		||||
    config: `
 | 
			
		||||
@@ -85,6 +184,14 @@ const CONFIGS = [
 | 
			
		||||
      fan_modes:
 | 
			
		||||
        - low
 | 
			
		||||
        - high
 | 
			
		||||
    - type: climate-swing-modes
 | 
			
		||||
      style: icons
 | 
			
		||||
      swing_modes:
 | 
			
		||||
        - 'on'
 | 
			
		||||
        - 'off'
 | 
			
		||||
        - 'both'
 | 
			
		||||
        - 'vertical'
 | 
			
		||||
        - 'horizontal'
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
 | 
			
		||||
import { customElement, query } from "lit/decorators";
 | 
			
		||||
import { CoverEntityFeature } from "../../../../src/data/cover";
 | 
			
		||||
import { LightColorMode } from "../../../../src/data/light";
 | 
			
		||||
import { LockEntityFeature } from "../../../../src/data/lock";
 | 
			
		||||
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
 | 
			
		||||
import { getEntity } from "../../../../src/fake_data/entity";
 | 
			
		||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
 | 
			
		||||
@@ -20,6 +21,11 @@ const ENTITIES = [
 | 
			
		||||
  getEntity("light", "unavailable", "unavailable", {
 | 
			
		||||
    friendly_name: "Unavailable entity",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("lock", "front_door", "locked", {
 | 
			
		||||
    friendly_name: "Front Door Lock",
 | 
			
		||||
    device_class: "lock",
 | 
			
		||||
    supported_features: LockEntityFeature.OPEN,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "thermostat", "heat", {
 | 
			
		||||
    current_temperature: 73,
 | 
			
		||||
    min_temp: 45,
 | 
			
		||||
@@ -138,6 +144,24 @@ const CONFIGS = [
 | 
			
		||||
    - type: "color-temp"
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Lock commands feature",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: tile
 | 
			
		||||
  entity: lock.front_door
 | 
			
		||||
  features:
 | 
			
		||||
    - type: "lock-commands"
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Lock open door feature",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: tile
 | 
			
		||||
  entity: lock.front_door
 | 
			
		||||
  features:
 | 
			
		||||
    - type: "lock-open-door"
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Vacuum commands feature",
 | 
			
		||||
    config: `
 | 
			
		||||
 
 | 
			
		||||
@@ -368,6 +368,7 @@ export class DemoEntityState extends LitElement {
 | 
			
		||||
              hass.localize,
 | 
			
		||||
              entry.stateObj,
 | 
			
		||||
              hass.locale,
 | 
			
		||||
              [], // numericDeviceClasses
 | 
			
		||||
              hass.config,
 | 
			
		||||
              hass.entities
 | 
			
		||||
            )}`,
 | 
			
		||||
@@ -406,6 +407,7 @@ export class DemoEntityState extends LitElement {
 | 
			
		||||
          entity_id: "select.speed",
 | 
			
		||||
          translation_key: "speed",
 | 
			
		||||
          platform: "demo",
 | 
			
		||||
          labels: [],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -31,10 +31,13 @@ const createConfigEntry = (
 | 
			
		||||
  supports_options: false,
 | 
			
		||||
  supports_remove_device: false,
 | 
			
		||||
  supports_unload: true,
 | 
			
		||||
  supports_reconfigure: true,
 | 
			
		||||
  disabled_by: null,
 | 
			
		||||
  pref_disable_new_entities: false,
 | 
			
		||||
  pref_disable_polling: false,
 | 
			
		||||
  reason: null,
 | 
			
		||||
  error_reason_translation_key: null,
 | 
			
		||||
  error_reason_translation_placeholders: null,
 | 
			
		||||
  ...override,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -198,6 +201,8 @@ const createEntityRegistryEntries = (
 | 
			
		||||
    has_entity_name: false,
 | 
			
		||||
    unique_id: "updater",
 | 
			
		||||
    options: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
    categories: {},
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@@ -221,6 +226,7 @@ const createDeviceRegistryEntries = (
 | 
			
		||||
    name_by_user: null,
 | 
			
		||||
    disabled_by: null,
 | 
			
		||||
    configuration_url: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import "../../components/demo-more-infos";
 | 
			
		||||
import { ClimateEntityFeature } from "../../../../src/data/climate";
 | 
			
		||||
 | 
			
		||||
const ENTITIES = [
 | 
			
		||||
  getEntity("climate", "thermostat", "heat", {
 | 
			
		||||
  getEntity("climate", "radiator", "heat", {
 | 
			
		||||
    friendly_name: "Basic heater",
 | 
			
		||||
    hvac_modes: ["heat", "off"],
 | 
			
		||||
    hvac_mode: "heat",
 | 
			
		||||
@@ -80,6 +80,24 @@ const ENTITIES = [
 | 
			
		||||
    max_humidity: 100,
 | 
			
		||||
    humidity: 50,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "towel_dryer", "heat", {
 | 
			
		||||
    friendly_name: "Preset only heater",
 | 
			
		||||
    hvac_modes: ["heat", "off"],
 | 
			
		||||
    hvac_mode: "heat",
 | 
			
		||||
    preset_modes: [
 | 
			
		||||
      "none",
 | 
			
		||||
      "frost_protection",
 | 
			
		||||
      "eco",
 | 
			
		||||
      "comfort",
 | 
			
		||||
      "comfort-1",
 | 
			
		||||
      "comfort-2",
 | 
			
		||||
    ],
 | 
			
		||||
    preset_mode: "eco",
 | 
			
		||||
    current_temperature: null,
 | 
			
		||||
    min_temp: 7,
 | 
			
		||||
    max_temp: 35,
 | 
			
		||||
    supported_features: ClimateEntityFeature.PRESET_MODE,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "unavailable", "unavailable", {
 | 
			
		||||
    friendly_name: "Unavailable heater",
 | 
			
		||||
    hvac_modes: ["heat", "off"],
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,7 @@
 | 
			
		||||
import { globIterate } from "glob";
 | 
			
		||||
import { availableParallelism } from "node:os";
 | 
			
		||||
 | 
			
		||||
process.env.UV_THREADPOOL_SIZE = availableParallelism();
 | 
			
		||||
 | 
			
		||||
const gulpImports = [];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
import { mdiStorePlus, mdiUpdate } from "@mdi/js";
 | 
			
		||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
			
		||||
import { mdiRefresh, mdiStorePlus } from "@mdi/js";
 | 
			
		||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators";
 | 
			
		||||
import { atLeastVersion } from "../../../src/common/config/version";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../src/components/ha-fab";
 | 
			
		||||
import { reloadHassioAddons } from "../../../src/data/hassio/addon";
 | 
			
		||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
 | 
			
		||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
 | 
			
		||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
 | 
			
		||||
import "../../../src/layouts/hass-subpage";
 | 
			
		||||
import "../../../src/layouts/hass-tabs-subpage";
 | 
			
		||||
import { haStyle } from "../../../src/resources/styles";
 | 
			
		||||
import { HomeAssistant, Route } from "../../../src/types";
 | 
			
		||||
import { supervisorTabs } from "../hassio-tabs";
 | 
			
		||||
import "./hassio-addons";
 | 
			
		||||
import "../../../src/layouts/hass-subpage";
 | 
			
		||||
import { reloadHassioAddons } from "../../../src/data/hassio/addon";
 | 
			
		||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
 | 
			
		||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
 | 
			
		||||
@customElement("hassio-dashboard")
 | 
			
		||||
class HassioDashboard extends LitElement {
 | 
			
		||||
@@ -43,7 +43,7 @@ class HassioDashboard extends LitElement {
 | 
			
		||||
        <ha-icon-button
 | 
			
		||||
          slot="toolbar-icon"
 | 
			
		||||
          @click=${this._handleCheckUpdates}
 | 
			
		||||
          .path=${mdiUpdate}
 | 
			
		||||
          .path=${mdiRefresh}
 | 
			
		||||
          .label=${this.supervisor.localize("store.check_updates")}
 | 
			
		||||
        ></ha-icon-button>
 | 
			
		||||
        <hassio-addons
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										169
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								package.json
									
									
									
									
									
								
							@@ -25,24 +25,24 @@
 | 
			
		||||
  "license": "Apache-2.0",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@babel/runtime": "7.23.9",
 | 
			
		||||
    "@braintree/sanitize-url": "7.0.0",
 | 
			
		||||
    "@codemirror/autocomplete": "6.12.0",
 | 
			
		||||
    "@codemirror/commands": "6.3.3",
 | 
			
		||||
    "@codemirror/language": "6.10.1",
 | 
			
		||||
    "@codemirror/legacy-modes": "6.3.3",
 | 
			
		||||
    "@babel/runtime": "7.24.7",
 | 
			
		||||
    "@braintree/sanitize-url": "7.0.3",
 | 
			
		||||
    "@codemirror/autocomplete": "6.16.3",
 | 
			
		||||
    "@codemirror/commands": "6.6.0",
 | 
			
		||||
    "@codemirror/language": "6.10.2",
 | 
			
		||||
    "@codemirror/legacy-modes": "6.4.0",
 | 
			
		||||
    "@codemirror/search": "6.5.6",
 | 
			
		||||
    "@codemirror/state": "6.4.1",
 | 
			
		||||
    "@codemirror/view": "6.24.1",
 | 
			
		||||
    "@codemirror/view": "6.28.2",
 | 
			
		||||
    "@egjs/hammerjs": "2.0.17",
 | 
			
		||||
    "@formatjs/intl-datetimeformat": "6.12.2",
 | 
			
		||||
    "@formatjs/intl-displaynames": "6.6.6",
 | 
			
		||||
    "@formatjs/intl-datetimeformat": "6.12.5",
 | 
			
		||||
    "@formatjs/intl-displaynames": "6.6.8",
 | 
			
		||||
    "@formatjs/intl-getcanonicallocales": "2.3.0",
 | 
			
		||||
    "@formatjs/intl-listformat": "7.5.5",
 | 
			
		||||
    "@formatjs/intl-locale": "3.4.5",
 | 
			
		||||
    "@formatjs/intl-numberformat": "8.10.0",
 | 
			
		||||
    "@formatjs/intl-pluralrules": "5.2.12",
 | 
			
		||||
    "@formatjs/intl-relativetimeformat": "11.2.12",
 | 
			
		||||
    "@formatjs/intl-listformat": "7.5.7",
 | 
			
		||||
    "@formatjs/intl-locale": "4.0.0",
 | 
			
		||||
    "@formatjs/intl-numberformat": "8.10.3",
 | 
			
		||||
    "@formatjs/intl-pluralrules": "5.2.14",
 | 
			
		||||
    "@formatjs/intl-relativetimeformat": "11.2.14",
 | 
			
		||||
    "@fullcalendar/core": "6.1.11",
 | 
			
		||||
    "@fullcalendar/daygrid": "6.1.11",
 | 
			
		||||
    "@fullcalendar/interaction": "6.1.11",
 | 
			
		||||
@@ -53,8 +53,8 @@
 | 
			
		||||
    "@lit-labs/context": "0.4.1",
 | 
			
		||||
    "@lit-labs/motion": "1.0.7",
 | 
			
		||||
    "@lit-labs/observers": "2.0.2",
 | 
			
		||||
    "@lit-labs/virtualizer": "2.0.12",
 | 
			
		||||
    "@lrnwebcomponents/simple-tooltip": "patch:@lrnwebcomponents/simple-tooltip@npm%3A8.0.0#~/.yarn/patches/@lrnwebcomponents-simple-tooltip-npm-8.0.0-77591f2e0c.patch",
 | 
			
		||||
    "@lit-labs/virtualizer": "2.0.13",
 | 
			
		||||
    "@lrnwebcomponents/simple-tooltip": "8.0.2",
 | 
			
		||||
    "@material/chips": "=14.0.0-canary.53b3cad2f.0",
 | 
			
		||||
    "@material/data-table": "=14.0.0-canary.53b3cad2f.0",
 | 
			
		||||
    "@material/mwc-base": "0.27.0",
 | 
			
		||||
@@ -70,8 +70,8 @@
 | 
			
		||||
    "@material/mwc-list": "0.27.0",
 | 
			
		||||
    "@material/mwc-menu": "0.27.0",
 | 
			
		||||
    "@material/mwc-radio": "0.27.0",
 | 
			
		||||
    "@material/mwc-ripple": "0.27.0",
 | 
			
		||||
    "@material/mwc-select": "0.27.0",
 | 
			
		||||
    "@material/mwc-snackbar": "0.27.0",
 | 
			
		||||
    "@material/mwc-switch": "0.27.0",
 | 
			
		||||
    "@material/mwc-tab": "0.27.0",
 | 
			
		||||
    "@material/mwc-tab-bar": "0.27.0",
 | 
			
		||||
@@ -80,17 +80,16 @@
 | 
			
		||||
    "@material/mwc-top-app-bar": "0.27.0",
 | 
			
		||||
    "@material/mwc-top-app-bar-fixed": "0.27.0",
 | 
			
		||||
    "@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
 | 
			
		||||
    "@material/web": "=1.3.0",
 | 
			
		||||
    "@material/web": "1.5.0",
 | 
			
		||||
    "@mdi/js": "7.4.47",
 | 
			
		||||
    "@mdi/svg": "7.4.47",
 | 
			
		||||
    "@polymer/paper-item": "3.0.1",
 | 
			
		||||
    "@polymer/paper-listbox": "3.0.1",
 | 
			
		||||
    "@polymer/paper-tabs": "3.1.0",
 | 
			
		||||
    "@polymer/paper-toast": "3.0.1",
 | 
			
		||||
    "@polymer/polymer": "3.5.1",
 | 
			
		||||
    "@thomasloven/round-slider": "0.6.0",
 | 
			
		||||
    "@vaadin/combo-box": "24.3.6",
 | 
			
		||||
    "@vaadin/vaadin-themable-mixin": "24.3.6",
 | 
			
		||||
    "@vaadin/combo-box": "24.4.0",
 | 
			
		||||
    "@vaadin/vaadin-themable-mixin": "24.4.0",
 | 
			
		||||
    "@vibrant/color": "3.2.1-alpha.1",
 | 
			
		||||
    "@vibrant/core": "3.2.1-alpha.1",
 | 
			
		||||
    "@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
 | 
			
		||||
@@ -98,28 +97,28 @@
 | 
			
		||||
    "@webcomponents/scoped-custom-element-registry": "0.0.9",
 | 
			
		||||
    "@webcomponents/webcomponentsjs": "2.8.0",
 | 
			
		||||
    "app-datepicker": "5.1.1",
 | 
			
		||||
    "chart.js": "4.4.1",
 | 
			
		||||
    "chart.js": "4.4.3",
 | 
			
		||||
    "color-name": "2.0.0",
 | 
			
		||||
    "comlink": "4.4.1",
 | 
			
		||||
    "core-js": "3.36.0",
 | 
			
		||||
    "cropperjs": "1.6.1",
 | 
			
		||||
    "date-fns": "2.30.0",
 | 
			
		||||
    "date-fns-tz": "2.0.0",
 | 
			
		||||
    "core-js": "3.37.1",
 | 
			
		||||
    "cropperjs": "1.6.2",
 | 
			
		||||
    "date-fns": "3.6.0",
 | 
			
		||||
    "date-fns-tz": "3.1.3",
 | 
			
		||||
    "deep-clone-simple": "1.1.1",
 | 
			
		||||
    "deep-freeze": "0.0.1",
 | 
			
		||||
    "element-internals-polyfill": "1.3.10",
 | 
			
		||||
    "element-internals-polyfill": "1.3.11",
 | 
			
		||||
    "fuse.js": "7.0.0",
 | 
			
		||||
    "google-timezones-json": "1.2.0",
 | 
			
		||||
    "hls.js": "1.5.6",
 | 
			
		||||
    "home-assistant-js-websocket": "9.1.0",
 | 
			
		||||
    "hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
 | 
			
		||||
    "home-assistant-js-websocket": "9.4.0",
 | 
			
		||||
    "idb-keyval": "6.2.1",
 | 
			
		||||
    "intl-messageformat": "10.5.11",
 | 
			
		||||
    "intl-messageformat": "10.5.14",
 | 
			
		||||
    "js-yaml": "4.1.0",
 | 
			
		||||
    "leaflet": "1.9.4",
 | 
			
		||||
    "leaflet-draw": "1.0.4",
 | 
			
		||||
    "lit": "2.8.0",
 | 
			
		||||
    "luxon": "3.4.4",
 | 
			
		||||
    "marked": "12.0.0",
 | 
			
		||||
    "marked": "12.0.2",
 | 
			
		||||
    "memoize-one": "6.0.0",
 | 
			
		||||
    "node-vibrant": "3.2.1-alpha.1",
 | 
			
		||||
    "proxy-polyfill": "0.3.2",
 | 
			
		||||
@@ -130,125 +129,122 @@
 | 
			
		||||
    "rrule": "2.8.1",
 | 
			
		||||
    "sortablejs": "1.15.2",
 | 
			
		||||
    "stacktrace-js": "2.0.2",
 | 
			
		||||
    "superstruct": "1.0.3",
 | 
			
		||||
    "superstruct": "1.0.4",
 | 
			
		||||
    "tinykeys": "2.1.0",
 | 
			
		||||
    "tsparticles-engine": "2.12.0",
 | 
			
		||||
    "tsparticles-preset-links": "2.12.0",
 | 
			
		||||
    "ua-parser-js": "1.0.37",
 | 
			
		||||
    "ua-parser-js": "1.0.38",
 | 
			
		||||
    "unfetch": "5.0.0",
 | 
			
		||||
    "vis-data": "7.1.9",
 | 
			
		||||
    "vis-network": "9.1.9",
 | 
			
		||||
    "vue": "2.7.16",
 | 
			
		||||
    "vue2-daterange-picker": "0.6.8",
 | 
			
		||||
    "weekstart": "2.0.0",
 | 
			
		||||
    "workbox-cacheable-response": "7.0.0",
 | 
			
		||||
    "workbox-core": "7.0.0",
 | 
			
		||||
    "workbox-expiration": "7.0.0",
 | 
			
		||||
    "workbox-precaching": "7.0.0",
 | 
			
		||||
    "workbox-routing": "7.0.0",
 | 
			
		||||
    "workbox-strategies": "7.0.0",
 | 
			
		||||
    "xss": "1.0.14"
 | 
			
		||||
    "workbox-cacheable-response": "7.1.0",
 | 
			
		||||
    "workbox-core": "7.1.0",
 | 
			
		||||
    "workbox-expiration": "7.1.0",
 | 
			
		||||
    "workbox-precaching": "7.1.0",
 | 
			
		||||
    "workbox-routing": "7.1.0",
 | 
			
		||||
    "workbox-strategies": "7.1.0",
 | 
			
		||||
    "xss": "1.0.15"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/core": "7.23.9",
 | 
			
		||||
    "@babel/helper-define-polyfill-provider": "0.5.0",
 | 
			
		||||
    "@babel/plugin-proposal-decorators": "7.23.9",
 | 
			
		||||
    "@babel/plugin-transform-runtime": "7.23.9",
 | 
			
		||||
    "@babel/preset-env": "7.23.9",
 | 
			
		||||
    "@babel/preset-typescript": "7.23.3",
 | 
			
		||||
    "@bundle-stats/plugin-webpack-filter": "4.10.1",
 | 
			
		||||
    "@babel/core": "7.24.7",
 | 
			
		||||
    "@babel/helper-define-polyfill-provider": "0.6.2",
 | 
			
		||||
    "@babel/plugin-proposal-decorators": "7.24.7",
 | 
			
		||||
    "@babel/plugin-transform-runtime": "7.24.7",
 | 
			
		||||
    "@babel/preset-env": "7.24.7",
 | 
			
		||||
    "@babel/preset-typescript": "7.24.7",
 | 
			
		||||
    "@bundle-stats/plugin-webpack-filter": "4.13.2",
 | 
			
		||||
    "@koa/cors": "5.0.0",
 | 
			
		||||
    "@lokalise/node-api": "12.1.0",
 | 
			
		||||
    "@octokit/auth-oauth-device": "6.0.1",
 | 
			
		||||
    "@octokit/plugin-retry": "6.0.1",
 | 
			
		||||
    "@octokit/rest": "20.0.2",
 | 
			
		||||
    "@lokalise/node-api": "12.5.0",
 | 
			
		||||
    "@octokit/auth-oauth-device": "7.1.1",
 | 
			
		||||
    "@octokit/plugin-retry": "7.1.1",
 | 
			
		||||
    "@octokit/rest": "21.0.0",
 | 
			
		||||
    "@open-wc/dev-server-hmr": "0.1.4",
 | 
			
		||||
    "@rollup/plugin-babel": "6.0.4",
 | 
			
		||||
    "@rollup/plugin-commonjs": "25.0.7",
 | 
			
		||||
    "@rollup/plugin-commonjs": "26.0.1",
 | 
			
		||||
    "@rollup/plugin-json": "6.1.0",
 | 
			
		||||
    "@rollup/plugin-node-resolve": "15.2.3",
 | 
			
		||||
    "@rollup/plugin-replace": "5.0.5",
 | 
			
		||||
    "@rollup/plugin-replace": "5.0.7",
 | 
			
		||||
    "@types/babel__plugin-transform-runtime": "7.9.5",
 | 
			
		||||
    "@types/chromecast-caf-receiver": "6.0.13",
 | 
			
		||||
    "@types/chromecast-caf-sender": "1.0.8",
 | 
			
		||||
    "@types/color-name": "1.1.3",
 | 
			
		||||
    "@types/chromecast-caf-receiver": "6.0.15",
 | 
			
		||||
    "@types/chromecast-caf-sender": "1.0.10",
 | 
			
		||||
    "@types/color-name": "1.1.4",
 | 
			
		||||
    "@types/glob": "8.1.0",
 | 
			
		||||
    "@types/html-minifier-terser": "7.0.2",
 | 
			
		||||
    "@types/js-yaml": "4.0.9",
 | 
			
		||||
    "@types/leaflet": "1.9.8",
 | 
			
		||||
    "@types/leaflet": "1.9.12",
 | 
			
		||||
    "@types/leaflet-draw": "1.0.11",
 | 
			
		||||
    "@types/lodash.merge": "4.6.9",
 | 
			
		||||
    "@types/luxon": "3.4.2",
 | 
			
		||||
    "@types/mocha": "10.0.6",
 | 
			
		||||
    "@types/qrcode": "1.5.5",
 | 
			
		||||
    "@types/serve-handler": "6.1.4",
 | 
			
		||||
    "@types/sortablejs": "1.15.8",
 | 
			
		||||
    "@types/tar": "6.1.11",
 | 
			
		||||
    "@types/tar": "6.1.13",
 | 
			
		||||
    "@types/ua-parser-js": "0.7.39",
 | 
			
		||||
    "@types/webspeechapi": "0.0.29",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "7.0.2",
 | 
			
		||||
    "@typescript-eslint/parser": "7.0.2",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "7.13.1",
 | 
			
		||||
    "@typescript-eslint/parser": "7.13.1",
 | 
			
		||||
    "@web/dev-server": "0.1.38",
 | 
			
		||||
    "@web/dev-server-rollup": "0.4.1",
 | 
			
		||||
    "babel-loader": "9.1.3",
 | 
			
		||||
    "babel-plugin-template-html-minifier": "4.1.0",
 | 
			
		||||
    "chai": "5.1.0",
 | 
			
		||||
    "chai": "5.1.1",
 | 
			
		||||
    "del": "7.1.0",
 | 
			
		||||
    "eslint": "8.57.0",
 | 
			
		||||
    "eslint-config-airbnb-base": "15.0.0",
 | 
			
		||||
    "eslint-config-airbnb-typescript": "17.1.0",
 | 
			
		||||
    "eslint-config-airbnb-typescript": "18.0.0",
 | 
			
		||||
    "eslint-config-prettier": "9.1.0",
 | 
			
		||||
    "eslint-import-resolver-webpack": "0.13.8",
 | 
			
		||||
    "eslint-plugin-disable": "2.0.3",
 | 
			
		||||
    "eslint-plugin-import": "2.29.1",
 | 
			
		||||
    "eslint-plugin-lit": "1.11.0",
 | 
			
		||||
    "eslint-plugin-lit": "1.14.0",
 | 
			
		||||
    "eslint-plugin-lit-a11y": "4.1.2",
 | 
			
		||||
    "eslint-plugin-unused-imports": "3.1.0",
 | 
			
		||||
    "eslint-plugin-wc": "2.0.4",
 | 
			
		||||
    "eslint-plugin-unused-imports": "4.0.0",
 | 
			
		||||
    "eslint-plugin-wc": "2.1.0",
 | 
			
		||||
    "fancy-log": "2.0.0",
 | 
			
		||||
    "fs-extra": "11.2.0",
 | 
			
		||||
    "glob": "10.3.10",
 | 
			
		||||
    "gulp": "4.0.2",
 | 
			
		||||
    "gulp-flatmap": "1.0.2",
 | 
			
		||||
    "glob": "10.4.2",
 | 
			
		||||
    "gulp": "5.0.0",
 | 
			
		||||
    "gulp-json-transform": "0.5.0",
 | 
			
		||||
    "gulp-merge-json": "2.1.2",
 | 
			
		||||
    "gulp-rename": "2.0.0",
 | 
			
		||||
    "gulp-zopfli-green": "6.0.1",
 | 
			
		||||
    "html-minifier-terser": "7.2.0",
 | 
			
		||||
    "husky": "9.0.11",
 | 
			
		||||
    "instant-mocha": "1.5.2",
 | 
			
		||||
    "jszip": "3.10.1",
 | 
			
		||||
    "lint-staged": "15.2.2",
 | 
			
		||||
    "lint-staged": "15.2.7",
 | 
			
		||||
    "lit-analyzer": "2.0.3",
 | 
			
		||||
    "lodash.merge": "4.6.2",
 | 
			
		||||
    "lodash.template": "4.5.0",
 | 
			
		||||
    "magic-string": "0.30.7",
 | 
			
		||||
    "magic-string": "0.30.10",
 | 
			
		||||
    "map-stream": "0.0.7",
 | 
			
		||||
    "mocha": "10.3.0",
 | 
			
		||||
    "mocha": "10.4.0",
 | 
			
		||||
    "object-hash": "3.0.0",
 | 
			
		||||
    "open": "10.0.3",
 | 
			
		||||
    "open": "10.1.0",
 | 
			
		||||
    "pinst": "3.0.0",
 | 
			
		||||
    "prettier": "3.2.5",
 | 
			
		||||
    "prettier": "3.3.2",
 | 
			
		||||
    "rollup": "2.79.1",
 | 
			
		||||
    "rollup-plugin-string": "3.0.0",
 | 
			
		||||
    "rollup-plugin-terser": "7.0.2",
 | 
			
		||||
    "rollup-plugin-visualizer": "5.12.0",
 | 
			
		||||
    "serve-handler": "6.1.5",
 | 
			
		||||
    "sinon": "17.0.1",
 | 
			
		||||
    "sinon": "18.0.0",
 | 
			
		||||
    "source-map-url": "0.4.1",
 | 
			
		||||
    "systemjs": "6.14.3",
 | 
			
		||||
    "tar": "6.2.0",
 | 
			
		||||
    "systemjs": "6.15.1",
 | 
			
		||||
    "tar": "7.4.0",
 | 
			
		||||
    "terser-webpack-plugin": "5.3.10",
 | 
			
		||||
    "transform-async-modules-webpack-plugin": "1.0.2",
 | 
			
		||||
    "transform-async-modules-webpack-plugin": "1.1.1",
 | 
			
		||||
    "ts-lit-plugin": "2.0.2",
 | 
			
		||||
    "typescript": "5.3.3",
 | 
			
		||||
    "vinyl-buffer": "1.0.1",
 | 
			
		||||
    "vinyl-source-stream": "2.0.0",
 | 
			
		||||
    "webpack": "5.90.3",
 | 
			
		||||
    "typescript": "5.4.5",
 | 
			
		||||
    "webpack": "5.92.1",
 | 
			
		||||
    "webpack-cli": "5.1.4",
 | 
			
		||||
    "webpack-dev-server": "5.0.2",
 | 
			
		||||
    "webpack-dev-server": "5.0.4",
 | 
			
		||||
    "webpack-manifest-plugin": "5.0.0",
 | 
			
		||||
    "webpack-stats-plugin": "1.1.3",
 | 
			
		||||
    "webpackbar": "6.0.1",
 | 
			
		||||
    "workbox-build": "7.0.0"
 | 
			
		||||
    "workbox-build": "7.1.1"
 | 
			
		||||
  },
 | 
			
		||||
  "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
 | 
			
		||||
  "resolutions": {
 | 
			
		||||
@@ -257,8 +253,9 @@
 | 
			
		||||
    "lit": "2.8.0",
 | 
			
		||||
    "clean-css": "5.3.3",
 | 
			
		||||
    "@lit/reactive-element": "1.6.3",
 | 
			
		||||
    "@fullcalendar/daygrid": "6.1.11",
 | 
			
		||||
    "sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
 | 
			
		||||
    "leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
 | 
			
		||||
  },
 | 
			
		||||
  "packageManager": "yarn@4.1.0"
 | 
			
		||||
  "packageManager": "yarn@4.3.1"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								public/static/images/appstore.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/static/images/appstore.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 7.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/static/images/logo_apple_home.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/static/images/logo_apple_home.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 19 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/static/images/logo_google_home.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/static/images/logo_google_home.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 8.6 KiB  | 
							
								
								
									
										1
									
								
								public/static/images/playstore.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/static/images/playstore.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 6.2 KiB  | 
							
								
								
									
										1
									
								
								public/static/images/qr-appstore.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/static/images/qr-appstore.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 52 KiB  | 
							
								
								
									
										1
									
								
								public/static/images/qr-playstore.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/static/images/qr-playstore.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 69 KiB  | 
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
 | 
			
		||||
 | 
			
		||||
[project]
 | 
			
		||||
name         = "home-assistant-frontend"
 | 
			
		||||
version      = "20240228.1"
 | 
			
		||||
version      = "20240610.0"
 | 
			
		||||
license      = {text = "Apache-2.0"}
 | 
			
		||||
description  = "The Home Assistant frontend"
 | 
			
		||||
readme       = "README.md"
 | 
			
		||||
authors      = [
 | 
			
		||||
    {name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
 | 
			
		||||
]
 | 
			
		||||
requires-python = ">=3.10.0"
 | 
			
		||||
requires-python = ">=3.11.0"
 | 
			
		||||
 | 
			
		||||
[project.urls]
 | 
			
		||||
"Homepage" = "https://github.com/home-assistant/frontend"
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,11 @@
 | 
			
		||||
      "matchPackageNames": ["tsparticles-engine"],
 | 
			
		||||
      "matchPackagePrefixes": ["tsparticles-preset-"]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "description": "Group date-fns with dependent timezone package",
 | 
			
		||||
      "groupName": "date-fns",
 | 
			
		||||
      "matchPackageNames": ["date-fns", "date-fns-tz"]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "description": "Group and temporarily disable WDS packages",
 | 
			
		||||
      "groupName": "Web Dev Server",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
import { theme2hex } from "./convert-color";
 | 
			
		||||
 | 
			
		||||
export const COLORS = [
 | 
			
		||||
  "#44739e",
 | 
			
		||||
  "#984ea3",
 | 
			
		||||
@@ -65,10 +67,10 @@ export function getColorByIndex(index: number) {
 | 
			
		||||
export function getGraphColorByIndex(
 | 
			
		||||
  index: number,
 | 
			
		||||
  style: CSSStyleDeclaration
 | 
			
		||||
) {
 | 
			
		||||
): string {
 | 
			
		||||
  // The CSS vars for the colors use range 1..n, so we need to adjust the index from the internal 0..n color index range.
 | 
			
		||||
  return (
 | 
			
		||||
  const themeColor =
 | 
			
		||||
    style.getPropertyValue(`--graph-color-${index + 1}`) ||
 | 
			
		||||
    getColorByIndex(index)
 | 
			
		||||
  );
 | 
			
		||||
    getColorByIndex(index);
 | 
			
		||||
  return theme2hex(themeColor);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import colors from "color-name";
 | 
			
		||||
import { expandHex } from "./hex";
 | 
			
		||||
 | 
			
		||||
const rgb_hex = (component: number): string => {
 | 
			
		||||
@@ -126,3 +127,18 @@ export const rgb2hs = (rgb: [number, number, number]): [number, number] =>
 | 
			
		||||
 | 
			
		||||
export const hs2rgb = (hs: [number, number]): [number, number, number] =>
 | 
			
		||||
  hsv2rgb([hs[0], hs[1], 255]);
 | 
			
		||||
 | 
			
		||||
export function theme2hex(themeColor: string): string {
 | 
			
		||||
  if (themeColor.startsWith("#")) {
 | 
			
		||||
    return themeColor;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const rgbFromColorName = colors[themeColor];
 | 
			
		||||
  if (!rgbFromColorName) {
 | 
			
		||||
    // We have a named color, and there's nothing in the table,
 | 
			
		||||
    // so nothing further we can do with it.
 | 
			
		||||
    // Compare/border/background color will all be the same.
 | 
			
		||||
    return themeColor;
 | 
			
		||||
  }
 | 
			
		||||
  return rgb2hex(rgbFromColorName);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ import {
 | 
			
		||||
  mdiFormatListBulleted,
 | 
			
		||||
  mdiFormatListCheckbox,
 | 
			
		||||
  mdiFormTextbox,
 | 
			
		||||
  mdiForumOutline,
 | 
			
		||||
  mdiGauge,
 | 
			
		||||
  mdiGoogleAssistant,
 | 
			
		||||
  mdiGoogleCirclesCommunities,
 | 
			
		||||
@@ -98,7 +99,7 @@ export const FIXED_DOMAIN_ICONS = {
 | 
			
		||||
  calendar: mdiCalendar,
 | 
			
		||||
  climate: mdiThermostat,
 | 
			
		||||
  configurator: mdiCog,
 | 
			
		||||
  conversation: mdiMicrophoneMessage,
 | 
			
		||||
  conversation: mdiForumOutline,
 | 
			
		||||
  counter: mdiCounter,
 | 
			
		||||
  date: mdiCalendar,
 | 
			
		||||
  datetime: mdiCalendarClock,
 | 
			
		||||
@@ -231,9 +232,12 @@ export const SENSOR_ENTITIES = [
 | 
			
		||||
  "calendar",
 | 
			
		||||
  "camera",
 | 
			
		||||
  "device_tracker",
 | 
			
		||||
  "image",
 | 
			
		||||
  "weather",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const ASSIST_ENTITIES = ["conversation", "stt", "tts"];
 | 
			
		||||
 | 
			
		||||
/** Domains that render an input element instead of a text value when displayed in a row.
 | 
			
		||||
 *  Those rows should then not show a cursor pointer when hovered (which would normally
 | 
			
		||||
 *  be the default) unless the element itself enforces it (e.g. a button). Also those elements
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz";
 | 
			
		||||
import { toZonedTime, fromZonedTime } from "date-fns-tz";
 | 
			
		||||
import { HassConfig } from "home-assistant-js-websocket";
 | 
			
		||||
import { FrontendLocaleData, TimeZone } from "../../data/translation";
 | 
			
		||||
 | 
			
		||||
@@ -8,10 +8,10 @@ const calcZonedDate = (
 | 
			
		||||
  fn: (date: Date, options?: any) => Date | number | boolean,
 | 
			
		||||
  options?
 | 
			
		||||
) => {
 | 
			
		||||
  const inputZoned = utcToZonedTime(date, tz);
 | 
			
		||||
  const inputZoned = toZonedTime(date, tz);
 | 
			
		||||
  const fnZoned = fn(inputZoned, options);
 | 
			
		||||
  if (fnZoned instanceof Date) {
 | 
			
		||||
    return zonedTimeToUtc(fnZoned, tz) as Date;
 | 
			
		||||
    return fromZonedTime(fnZoned, tz) as Date;
 | 
			
		||||
  }
 | 
			
		||||
  return fnZoned;
 | 
			
		||||
};
 | 
			
		||||
@@ -37,3 +37,20 @@ export const calcDateProperty = (
 | 
			
		||||
  locale.time_zone === TimeZone.server
 | 
			
		||||
    ? (calcZonedDate(date, config.time_zone, fn, options) as number | boolean)
 | 
			
		||||
    : fn(date, options);
 | 
			
		||||
 | 
			
		||||
export const calcDateDifferenceProperty = (
 | 
			
		||||
  endDate: Date,
 | 
			
		||||
  startDate: Date,
 | 
			
		||||
  fn: (date: Date, options?: any) => boolean | number,
 | 
			
		||||
  locale: FrontendLocaleData,
 | 
			
		||||
  config: HassConfig
 | 
			
		||||
) =>
 | 
			
		||||
  calcDateProperty(
 | 
			
		||||
    endDate,
 | 
			
		||||
    fn,
 | 
			
		||||
    locale,
 | 
			
		||||
    config,
 | 
			
		||||
    locale.time_zone === TimeZone.server
 | 
			
		||||
      ? toZonedTime(startDate, config.time_zone)
 | 
			
		||||
      : startDate
 | 
			
		||||
  );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
import { getWeekStartByLocale } from "weekstart";
 | 
			
		||||
import { FrontendLocaleData, FirstWeekday } from "../../data/translation";
 | 
			
		||||
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
 | 
			
		||||
export const weekdays = [
 | 
			
		||||
  "sunday",
 | 
			
		||||
  "monday",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import { HassConfig } from "home-assistant-js-websocket";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import { DateFormat, FrontendLocaleData } from "../../data/translation";
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
import { resolveTimeZone } from "./resolve-time-zone";
 | 
			
		||||
 | 
			
		||||
// Tuesday, August 10
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import { HassConfig } from "home-assistant-js-websocket";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import { FrontendLocaleData } from "../../data/translation";
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
import { formatDateNumeric } from "./format_date";
 | 
			
		||||
import { formatTime } from "./format_time";
 | 
			
		||||
import { resolveTimeZone } from "./resolve-time-zone";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import { HaDurationData } from "../../components/ha-duration-input";
 | 
			
		||||
import { FrontendLocaleData } from "../../data/translation";
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
 | 
			
		||||
const leftPad = (num: number) => (num < 10 ? `0${num}` : num);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import { HassConfig } from "home-assistant-js-websocket";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import { FrontendLocaleData } from "../../data/translation";
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
import { resolveTimeZone } from "./resolve-time-zone";
 | 
			
		||||
import { useAmPm } from "./use_am_pm";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
 | 
			
		||||
export const localizeWeekdays = memoizeOne(
 | 
			
		||||
  (language: string, short: boolean): string[] => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import { FrontendLocaleData } from "../../data/translation";
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
import { selectUnit } from "../util/select-unit";
 | 
			
		||||
 | 
			
		||||
const formatRelTimeMem = memoizeOne(
 | 
			
		||||
 
 | 
			
		||||
@@ -108,6 +108,8 @@ export const storage =
 | 
			
		||||
    subscribe?: boolean;
 | 
			
		||||
    state?: boolean;
 | 
			
		||||
    stateOptions?: InternalPropertyDeclaration;
 | 
			
		||||
    serializer?: (value: any) => any;
 | 
			
		||||
    deserializer?: (value: any) => any;
 | 
			
		||||
  }): any =>
 | 
			
		||||
  (clsElement: ClassElement) => {
 | 
			
		||||
    const storageName = options.storage || "localStorage";
 | 
			
		||||
@@ -141,7 +143,9 @@ export const storage =
 | 
			
		||||
 | 
			
		||||
    const getValue = (): any =>
 | 
			
		||||
      storageInstance.hasKey(storageKey!)
 | 
			
		||||
        ? storageInstance.getValue(storageKey!)
 | 
			
		||||
        ? options.deserializer
 | 
			
		||||
          ? options.deserializer(storageInstance.getValue(storageKey!))
 | 
			
		||||
          : storageInstance.getValue(storageKey!)
 | 
			
		||||
        : initVal;
 | 
			
		||||
 | 
			
		||||
    const setValue = (el: ReactiveElement, value: any) => {
 | 
			
		||||
@@ -149,7 +153,10 @@ export const storage =
 | 
			
		||||
      if (options.state) {
 | 
			
		||||
        oldValue = getValue();
 | 
			
		||||
      }
 | 
			
		||||
      storageInstance.setValue(storageKey!, value);
 | 
			
		||||
      storageInstance.setValue(
 | 
			
		||||
        storageKey!,
 | 
			
		||||
        options.serializer ? options.serializer(value) : value
 | 
			
		||||
      );
 | 
			
		||||
      if (options.state) {
 | 
			
		||||
        el.requestUpdate(clsElement.key, oldValue);
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
export type MediaQueriesListener = () => void;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Attach a media query. Listener is called right away and when it matches.
 | 
			
		||||
 * @param mediaQuery media query to match.
 | 
			
		||||
@@ -7,7 +9,7 @@
 | 
			
		||||
export const listenMediaQuery = (
 | 
			
		||||
  mediaQuery: string,
 | 
			
		||||
  matchesChanged: (matches: boolean) => void
 | 
			
		||||
) => {
 | 
			
		||||
): MediaQueriesListener => {
 | 
			
		||||
  const mql = matchMedia(mediaQuery);
 | 
			
		||||
  const listener = (e) => matchesChanged(e.matches);
 | 
			
		||||
  mql.addListener(listener);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								src/common/dom/prevent_default.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/common/dom/prevent_default.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export const preventDefault = (ev) => ev.preventDefault();
 | 
			
		||||
@@ -19,28 +19,11 @@ import { blankBeforeUnit } from "../translations/blank_before_unit";
 | 
			
		||||
import { LocalizeFunc } from "../translations/localize";
 | 
			
		||||
import { computeDomain } from "./compute_domain";
 | 
			
		||||
 | 
			
		||||
export const computeStateDisplaySingleEntity = (
 | 
			
		||||
  localize: LocalizeFunc,
 | 
			
		||||
  stateObj: HassEntity,
 | 
			
		||||
  locale: FrontendLocaleData,
 | 
			
		||||
  config: HassConfig,
 | 
			
		||||
  entity: EntityRegistryDisplayEntry | undefined,
 | 
			
		||||
  state?: string
 | 
			
		||||
): string =>
 | 
			
		||||
  computeStateDisplayFromEntityAttributes(
 | 
			
		||||
    localize,
 | 
			
		||||
    locale,
 | 
			
		||||
    config,
 | 
			
		||||
    entity,
 | 
			
		||||
    stateObj.entity_id,
 | 
			
		||||
    stateObj.attributes,
 | 
			
		||||
    state !== undefined ? state : stateObj.state
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
export const computeStateDisplay = (
 | 
			
		||||
  localize: LocalizeFunc,
 | 
			
		||||
  stateObj: HassEntity,
 | 
			
		||||
  locale: FrontendLocaleData,
 | 
			
		||||
  sensorNumericDeviceClasses: string[],
 | 
			
		||||
  config: HassConfig,
 | 
			
		||||
  entities: HomeAssistant["entities"],
 | 
			
		||||
  state?: string
 | 
			
		||||
@@ -52,6 +35,7 @@ export const computeStateDisplay = (
 | 
			
		||||
  return computeStateDisplayFromEntityAttributes(
 | 
			
		||||
    localize,
 | 
			
		||||
    locale,
 | 
			
		||||
    sensorNumericDeviceClasses,
 | 
			
		||||
    config,
 | 
			
		||||
    entity,
 | 
			
		||||
    stateObj.entity_id,
 | 
			
		||||
@@ -63,6 +47,7 @@ export const computeStateDisplay = (
 | 
			
		||||
export const computeStateDisplayFromEntityAttributes = (
 | 
			
		||||
  localize: LocalizeFunc,
 | 
			
		||||
  locale: FrontendLocaleData,
 | 
			
		||||
  sensorNumericDeviceClasses: string[],
 | 
			
		||||
  config: HassConfig,
 | 
			
		||||
  entity: EntityRegistryDisplayEntry | undefined,
 | 
			
		||||
  entityId: string,
 | 
			
		||||
@@ -73,8 +58,15 @@ export const computeStateDisplayFromEntityAttributes = (
 | 
			
		||||
    return localize(`state.default.${state}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const domain = computeDomain(entityId);
 | 
			
		||||
 | 
			
		||||
  // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
 | 
			
		||||
  if (isNumericFromAttributes(attributes)) {
 | 
			
		||||
  if (
 | 
			
		||||
    isNumericFromAttributes(
 | 
			
		||||
      attributes,
 | 
			
		||||
      domain === "sensor" ? sensorNumericDeviceClasses : []
 | 
			
		||||
    )
 | 
			
		||||
  ) {
 | 
			
		||||
    // state is duration
 | 
			
		||||
    if (
 | 
			
		||||
      attributes.device_class === "duration" &&
 | 
			
		||||
@@ -120,8 +112,6 @@ export const computeStateDisplayFromEntityAttributes = (
 | 
			
		||||
    return value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const domain = computeDomain(entityId);
 | 
			
		||||
 | 
			
		||||
  if (domain === "datetime") {
 | 
			
		||||
    const time = new Date(state);
 | 
			
		||||
    return formatDateTime(time, locale, config);
 | 
			
		||||
@@ -187,11 +177,14 @@ export const computeStateDisplayFromEntityAttributes = (
 | 
			
		||||
  if (
 | 
			
		||||
    [
 | 
			
		||||
      "button",
 | 
			
		||||
      "conversation",
 | 
			
		||||
      "event",
 | 
			
		||||
      "image",
 | 
			
		||||
      "input_button",
 | 
			
		||||
      "notify",
 | 
			
		||||
      "scene",
 | 
			
		||||
      "stt",
 | 
			
		||||
      "tag",
 | 
			
		||||
      "tts",
 | 
			
		||||
      "wake_word",
 | 
			
		||||
    ].includes(domain) ||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,15 @@ export const FIXED_DOMAIN_STATES = {
 | 
			
		||||
  input_button: [],
 | 
			
		||||
  lawn_mower: ["error", "paused", "mowing", "docked"],
 | 
			
		||||
  light: ["on", "off"],
 | 
			
		||||
  lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
 | 
			
		||||
  lock: [
 | 
			
		||||
    "jammed",
 | 
			
		||||
    "locked",
 | 
			
		||||
    "locking",
 | 
			
		||||
    "unlocked",
 | 
			
		||||
    "unlocking",
 | 
			
		||||
    "opening",
 | 
			
		||||
    "open",
 | 
			
		||||
  ],
 | 
			
		||||
  media_player: [
 | 
			
		||||
    "off",
 | 
			
		||||
    "on",
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user