mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-30 22:19:55 +00:00 
			
		
		
		
	Compare commits
	
		
			622 Commits
		
	
	
		
			allow-part
			...
			persistent
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | ecba22d301 | ||
|   | 72172cabc2 | ||
|   | eb552530e2 | ||
|   | 1fe5d66a68 | ||
|   | 7faa165558 | ||
|   | 752bc192cd | ||
|   | e9961b93f9 | ||
|   | bbdcc021d4 | ||
|   | 3d6cfc4037 | ||
|   | 9b35c06eef | ||
|   | b46c74fe76 | ||
|   | 5aa6ffe2e4 | ||
|   | 4ffd31974c | ||
|   | 1faef71dcb | ||
|   | 1e5c35c158 | ||
|   | 2e3ce4ae9e | ||
|   | 07d37dd89f | ||
|   | 33d6ad1b0b | ||
|   | 386ed2167f | ||
|   | 221f4f34a7 | ||
|   | c63c717d9f | ||
|   | 1cb1bcf274 | ||
|   | 30f2a49fbf | ||
|   | 1cf24ffc8d | ||
|   | 13b864e261 | ||
|   | 7bc2ca3b65 | ||
|   | 101e9323a7 | ||
|   | 922e95b895 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | baaa012101 | ||
|   | 3888b1c48b | ||
|   | 332af4003e | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 044a44e114 | ||
|   | 13c932a8f8 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | be1089302f | ||
|   | 540df024d9 | ||
|   | e7c8bd4c41 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 7c15a65bba | ||
|   | 5381a467e5 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | a96d3594ba | ||
|   | 4526a46a56 | ||
|   | 1d0d4755d0 | ||
|   | fa75b18a6b | ||
|   | cdd29c8bf7 | ||
|   | 215f5e341a | ||
|   | 8abb58ae7d | ||
|   | 40c8301df0 | ||
|   | 80f3d6aacb | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 41310007fe | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 195b1eef02 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 8e9b5ea66b | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 827d89628d | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | cac341a938 | ||
|   | 2b51228665 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 79c010eb7b | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 2a4356ce86 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | afdeb36258 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d4f4ee1e59 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | bcceef30bb | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 356935fefc | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 49f59d7162 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 67e8357bb9 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | b8da712186 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | a409f494a2 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 3b32825e2a | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 12b7b903bc | ||
|   | 3be601a3b9 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d0641d64bd | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 3a64f64894 | ||
|   | 7182abfec5 | ||
|   | 2076a083d3 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 73317a48ee | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | b891c53994 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | c1c18affbc | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 3bea2cf7f9 | ||
|   | fa1a6affa7 | ||
|   | 197638b282 | ||
|   | 6e3cf0975b | ||
|   | 9875cb2723 | ||
|   | f8ea7e0ef2 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | c821f4296e | ||
|   | 5fc4e7a95d | ||
|   | 49fa7ec4ed | ||
|   | 780de42e4b | ||
|   | f7722a270f | ||
|   | e3faa618bf | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 655b630fa5 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 983bba357a | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 17a2560d94 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | eaffed9ff8 | ||
|   | 273992c8e9 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | c77905bd22 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 15132783d4 | ||
|   | 2b6cf55638 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | e4eaa52d53 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | e1f73dac02 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 36de0e5c8c | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 549e4e7fb3 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | dd9c4e35bf | ||
|   | 8580d3f9bf | ||
|   | 6d29b764d3 | ||
|   | 78cff3a921 | ||
|   | e3c312feaf | ||
|   | 31e4166248 | ||
|   | 0442e3e06e | ||
|   | 0a8252c16a | ||
|   | 0a62d711f2 | ||
|   | d7c3ff3e9d | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 2767f866f3 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 040d5af0aa | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 06c6e312b0 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 841dffe563 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | a41e0d446f | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 9a0f24cd8b | ||
|   | 2e531a9006 | ||
|   | 76255f2efb | ||
|   | 19fc92419a | ||
|   | e7c2625cf1 | ||
|   | c39fdcda6e | ||
|   | fd1381ab3b | ||
|   | 7b8f4d1e72 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | b0a278df97 | ||
|   | 93e31df106 | ||
|   | 47fdae764f | ||
|   | b8efc06caa | ||
|   | fcacdf6534 | ||
|   | 45d260f0ce | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d5f46a69b0 | ||
|   | fe8eb333b9 | ||
|   | 677cd2de10 | ||
|   | 1470eb484f | ||
|   | 10ee8fda5b | ||
|   | e044ddcb57 | ||
|   | 29c564bb69 | ||
|   | 1bf03f020e | ||
|   | 6c684fd8ee | ||
|   | ddaf403378 | ||
|   | b337074758 | ||
|   | a96eff4d25 | ||
|   | e6bdc3a15e | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 33e15eec22 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 3c0afd6cde | ||
|   | 31a3fa02d9 | ||
|   | 71954f545c | ||
|   | 9f3e8abe69 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 21f983572c | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 5667d71b02 | ||
|   | 0d0e5fdaaa | ||
|   | b1f5ff26d9 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 27451ca30e | ||
|   | 928b4e6f1e | ||
|   | b63a32109e | ||
|   | efb0098eac | ||
|   | a67b845812 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d29b7626f3 | ||
|   | 47ac7062dc | ||
|   | e7f5d927b1 | ||
|   | 6b06393559 | ||
|   | efa02c309b | ||
|   | 9b2e77e781 | ||
|   | 24b4060c97 | ||
|   | 5e4c1ab4fc | ||
|   | 287e6dbb60 | ||
|   | 40c9292e16 | ||
|   | d51dd00ec7 | ||
|   | 0db50d13d3 | ||
|   | 9eb3618d97 | ||
|   | 03eee9c7d5 | ||
|   | a49d59f4c6 | ||
|   | 990ade4294 | ||
|   | a4c57f09ad | ||
|   | c1fa6d6f8c | ||
|   | 3bfcb808f5 | ||
|   | 9a30fe81c6 | ||
|   | c864edee72 | ||
|   | 36268d5048 | ||
|   | 5217d427e9 | ||
|   | aea668e754 | ||
|   | 2219c9bbd3 | ||
|   | 9b896c63b6 | ||
|   | 30af576ff5 | ||
|   | 585db6ab3f | ||
|   | 046475e7ac | ||
|   | 648383addd | ||
|   | 10018b4c32 | ||
|   | 872d3e4875 | ||
|   | 881f5e5012 | ||
|   | 9797950f32 | ||
|   | 25986f239e | ||
|   | b586210ff1 | ||
|   | 748925ede9 | ||
|   | a9f1c4a198 | ||
|   | d914fb0cfc | ||
|   | 5218e1352e | ||
|   | eb0759a3b3 | ||
|   | 88522ed155 | ||
|   | 64e7fc6591 | ||
|   | 2ad6253b72 | ||
|   | 7e5a85dbe7 | ||
|   | 0771a780d9 | ||
|   | 7b350e31dd | ||
|   | 70fbf68603 | ||
|   | 2b4f199337 | ||
|   | a70d7d8de3 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | e0c1f98803 | ||
|   | 4b7a517d98 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 26d4839dfd | ||
|   | c358d724a7 | ||
|   | 0ab7934c09 | ||
|   | 6eac229901 | ||
|   | 5006dfc138 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d9d38efd93 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 9fe3a500d5 | ||
|   | 3df7c50690 | ||
|   | 38cf774a0d | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 98d22d38c3 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 357dbb591b | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | aa5df06bc3 | ||
|   | 07a66b4bff | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | f466196fa8 | ||
|   | 04ae0b34af | ||
|   | 6f48267dae | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | be1f5d99c8 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 27b9b4dcc5 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 08b57d6168 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 3e474daa32 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 48c1e8b56c | ||
|   | fd0c0a95ca | ||
|   | 83570f2419 | ||
|   | 633c6cfc9b | ||
|   | 54b4c9f347 | ||
|   | 85e7986b44 | ||
|   | da0cf9d950 | ||
|   | ef51336770 | ||
|   | a09db75980 | ||
|   | 77d1b19ecb | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | fba12f35ac | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 00198c6937 | ||
|   | 71bb540352 | ||
|   | c4ff1a8646 | ||
|   | a9c27ad8dd | ||
|   | dacdc62672 | ||
|   | cccce5711c | ||
|   | 50bd9da94c | ||
|   | dde27c3524 | ||
|   | 85acafe8a8 | ||
|   | c1748138a8 | ||
|   | 9111f58e52 | ||
|   | cefaaadf95 | ||
|   | 29846a168e | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 31c89d70c5 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d5cea75fe4 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 35d212e850 | ||
|   | aea098ff17 | ||
|   | c5205ae8db | ||
|   | 303087d049 | ||
|   | e9d9d89d79 | ||
|   | a5b5e61ed4 | ||
|   | 22dc757382 | ||
|   | 4ccfd6a3fc | ||
|   | 5a36f100a9 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 847c8407de | ||
|   | ed1784b70f | ||
|   | e8521c59eb | ||
|   | 34e59e543b | ||
|   | 330aa23801 | ||
|   | 06db338a15 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 8cff4cda47 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | f7e7c916ba | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 13ab5ab70a | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | cc61131e4b | ||
|   | 099aa54b80 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | e35d795ba8 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 39de8cd6ba | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | fdddf27162 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | cec7a24234 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | ea2e9de37a | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d9cb288c1d | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 53bd4298e7 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 5bea929c1e | ||
|   | eb220fa1a0 | ||
|   | 67b64b2d5c | ||
|   | 42aa4ab7c2 | ||
|   | d873f26db3 | ||
|   | e354f5de12 | ||
|   | 92ed62985d | ||
|   | cbcca6e190 | ||
|   | 2ed48d67c6 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 8fb2bf472a | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 51bdf85642 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | b7a4eb33cf | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 3c2d326b54 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 24e6d19e18 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 9370411019 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d5c7e7849e | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | c5e62248b9 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | c5e0efb8e0 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 82f88ca7d7 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | c676cddb13 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 036e2a9e46 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 319d8bec29 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | aa864cf0b6 | ||
|   | eabd8b54f4 | ||
|   | aa063d0a3e | ||
|   | 75f080ee85 | ||
|   | 557fe33807 | ||
|   | 5bd49c18d1 | ||
|   | a461d58df2 | ||
|   | ae8eafe8f9 | ||
|   | 450565799e | ||
|   | 72403f4276 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d66ca0467e | ||
|   | d46201ebd8 | ||
|   | 689d123890 | ||
|   | bcdb24849d | ||
|   | 3838d76094 | ||
|   | 3325cbfa28 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 24fc2a0ccc | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d6ea6f8041 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 8384ef7a5c | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 2869c5f26b | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | c098c55b8f | ||
|   | fec974712f | ||
|   | 0be5aa108c | ||
|   | ec36d4a381 | ||
|   | ccf3238318 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | bcfa6c5a4b | ||
|   | e371f5d406 | ||
|   | 6d4e3a0de3 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | b845c54948 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 87951ebf82 | ||
|   | 586079917d | ||
|   | aa3fd70966 | ||
|   | a5ba2499c0 | ||
|   | 36a87da1fe | ||
|   | 8f75c314f5 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d04fc53f08 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | c65c6f8767 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | b81668fbce | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 2d8e23ba48 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | e947f8120c | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 6424020f64 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | a611930fa7 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | f0e990f330 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 8c1d2362a4 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 69067010a3 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 0b0ba97aee | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 792fe15dac | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d5b8fa482c | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 2fefbbfe24 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 22c509533a | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 544dfa0973 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | aac3ec353b | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | efbc6d9069 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 4e390b4c57 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 849e65a2b3 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | a6fdb54697 | ||
|   | aa196704b7 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | c93fcb8a2a | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 5768764282 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | e6c8881b21 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | ac30335fc2 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 1e5c30a259 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 1da009b473 | ||
|   | bc81499e7c | ||
|   | c31378ad75 | ||
|   | 02542e528e | ||
|   | 8ce8b63cfc | ||
|   | c7ec6903fe | ||
|   | ecb2a73e2b | ||
|   | 59db31cdb5 | ||
|   | 643556ded3 | ||
|   | 4e5a18d272 | ||
|   | ab237f19c0 | ||
|   | 3fa3d333c4 | ||
|   | 1cb7e798da | ||
|   | 11640b31f6 | ||
|   | 4740ce685f | ||
|   | d2321b535c | ||
|   | f185e886c3 | ||
|   | 8695bbc490 | ||
|   | 11f77b09e6 | ||
|   | ba7d7556a8 | ||
|   | 31da0d8678 | ||
|   | 9f195534b5 | ||
|   | 060f6ce3d8 | ||
|   | ed19cfeaaa | ||
|   | 79d6453c7e | ||
|   | 694fc7d4ba | ||
|   | a293e7b91c | ||
|   | 8205a30baf | ||
|   | 25af9a9770 | ||
|   | 6c25e23ad2 | ||
|   | a39c2a314a | ||
|   | 1ea70f3191 | ||
|   | 02a94c04af | ||
|   | fc97ca324c | ||
|   | 4a0d84d2f6 | ||
|   | b550c67a9f | ||
|   | a3ec83a684 | ||
|   | 822f47143b | ||
|   | da1df9d8cc | ||
|   | aa155261f5 | ||
|   | 29aa762f7c | ||
|   | d56e4afe92 | ||
|   | e766c277f5 | ||
|   | 6c0011fb45 | ||
|   | a6e71f4c0a | ||
|   | d6382e59c6 | ||
|   | ab308af61f | ||
|   | 15eab18e07 | ||
|   | c8e0227a5c | ||
|   | f2a8528429 | ||
|   | 3ed3dab0a1 | ||
|   | d0c7f65256 | ||
|   | f99f554f19 | ||
|   | e069b5eed1 | ||
|   | 3a481ebb1a | ||
|   | a209fadf18 | ||
|   | 9f1bd1e085 | ||
|   | edc6da04f7 | ||
|   | 2fb1dd0ec1 | ||
|   | 3f2aac0842 | ||
|   | 71dd822978 | ||
|   | d1877595a5 | ||
|   | 6379713f57 | ||
|   | 3b33195ff6 | ||
|   | c7f1f1bcd1 | ||
|   | c50aad8403 | ||
|   | fac4795f14 | ||
|   | 062e402ef1 | ||
|   | 29be64a858 | ||
|   | b3b74b8328 | ||
|   | 37ba34cb0d | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 8ecdde3507 | ||
|   | 04d34aa80c | ||
|   | 26bb1ba146 | ||
|   | b7667d2cbf | ||
|   | 0c36600a81 | ||
|   | feaf61a0ae | ||
|   | 3e2844a65a | ||
|   | 3a2d7baa25 | ||
|   | 349cca5ff2 | ||
|   | 5cd3ce66f6 | ||
|   | 8cf8c41698 | ||
|   | ff4c01e15c | ||
|   | addb66f21d | ||
|   | a5759e36b2 | ||
|   | 9852186ff7 | ||
|   | 19c9486351 | ||
|   | 8c06712ab7 | ||
|   | 63de324224 | ||
|   | 10d476195d | ||
|   | e0c4b85ef1 | ||
|   | 3441a86613 | ||
|   | 643b168c69 | ||
|   | db0e5a8a41 | ||
|   | 64a693332b | ||
|   | 327927baa7 | ||
|   | ce8fc17ef8 | ||
|   | 62ed1d54b0 | ||
|   | 2498f1db41 | ||
|   | 8a50bb058d | ||
|   | e793675c47 | ||
|   | 07cef18918 | ||
|   | a0263f25c4 | ||
|   | 52546ab567 | ||
|   | c0ec7e4f09 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 8b61390e19 | ||
|   | 9ba114777e | ||
|   | 4e1e76ccc2 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 609300f40b | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | eac9ac4757 | ||
|   | 708d1b81da | ||
|   | 35baf4c779 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 8d1ae71741 | ||
|   | 9b32c9c6b4 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 439f34f724 | ||
|   | cef3b99e16 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 9dbdf611c5 | ||
|   | 86f8d2d737 | ||
|   | 1ded47d368 | ||
|   | 85a27e8bb1 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 878f3b8df4 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 50c25a8276 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | c0de3b8269 | ||
|   | 09f4e19d4c | ||
|   | 6e91ac2a34 | ||
|   | 49b0c7c3d1 | ||
|   | be1867900e | ||
|   | a43f49f4af | ||
|   | ea0f29782d | ||
|   | 65161ce581 | ||
|   | 088cc69083 | ||
|   | be005b4c88 | ||
|   | aac28efd32 | ||
|   | 0d020e0300 | ||
|   | eeb84f65b9 | ||
|   | 22f5d6cacb | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 52a7b41096 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | f9f87d1147 | ||
|   | 0b3dff00df | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | d48a4ab00a | ||
|   | d89ac0f30d | ||
|   | 3cb3f8d352 | ||
|   | f507a7b8b3 | ||
|   | 910244f751 | ||
|   | 0ce3757b80 | ||
|   | c470ced308 | ||
|   | d8cb5a6a42 | ||
|   | afa071465c | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 4ff450c5c3 | ||
|   | 790faa9c31 | ||
|   | 52f609f742 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 51157caf22 | ||
|   | 8edb48eedc | ||
|   | e32771fb14 | ||
|   | b2f66aa51c | ||
|   | bf70427760 | ||
|   | ac1e6b87ae | ||
|   | 7fa4a75009 | ||
|   | 0f622589a3 | ||
|   | dae107d3e3 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 09ff5cdb58 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 1d42821ff5 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 6d0a468e09 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 6df02130d9 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | f9c790e0bb | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 9d46f0cecd | ||
|   | e4302a0bb5 | ||
|   | b1f9469002 | ||
|   | 0478aed28c | ||
|   | 3a1fff81b8 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | fa4550a848 | ||
|   | 689bc48f31 | ||
|   | a998465600 | ||
|   | b21605e260 | ||
|   | 178ad2dffa | ||
|   | 2e7d973597 | ||
|   | a741faced1 | ||
|   | 5e352f6194 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 639c120b56 | ||
|   | 7f4dadfc20 | ||
|   | ddb523c133 | ||
|   | 21645c2361 | ||
|   | e781be885d | ||
|   | b15b0e25d6 | ||
|   | 2d74873db0 | ||
|   | fbb59b1459 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 1a9b99dd72 | ||
|   | 442f73b8c5 | ||
|   | a5edb4caaf | ||
|   | 49a14a7265 | ||
|   | 6a5568ad4f | ||
|   | deb53eb180 | ||
|   | b076301513 | ||
|   | d50f2bf38c | ||
|   | a1180b7118 | ||
|   | 0f45d2e842 | ||
|   | 770675ab27 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 8a8da45761 | ||
|   | 97f983e34a | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 45d9b33c75 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | f3a1b6763f | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 8c6d84e6b0 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 2774db1b93 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | da58aed64c | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 88951a0a70 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 54b4efee45 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 43bfe76f63 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 657ae1a7fa | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | ac95820350 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | bd45aa38b4 | ||
|   | 494a9f8475 | ||
|   | 067cf19889 | ||
|   | cc4e815a58 | ||
|   | de00842f22 | ||
|   | 3d200da438 | ||
|   | 0364c5e493 | ||
|   | 2750837d1e | ||
|   | d1ad72c6ff | ||
|   | 723b3844ac | ||
|   | 54f8d33b1f | ||
|   | 4c702ac7c2 | ||
|   | c7b3c4df27 | ||
|   | 56cc4e8d4d | ||
|   | 0e8c280763 | ||
|   | ff715c6cb7 | ||
|   | d41cf17932 | ||
|   | 81ebdf1448 | ||
|   | c640d9edf2 | ||
|   | 6d29b1cfb8 | ||
|   | e784205e1e | ||
|   | 1596578f96 | ||
|   | 28304bb1dc | ||
|   | 32bbc9421b | ||
|   | 6e35f841cc | ||
|   | 99e6547807 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 9764a0f23f | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 12918580ac | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 61a7652ae1 | ||
|   | c885d08a32 | ||
|   | 322cc26ecc | ||
|   | 6068d5e5cd | ||
|   | 890be2c177 | ||
|   | 423709dd23 | ||
|   | 4b73baa098 | ||
|   | dba16edabc | ||
|   | 975f371ba8 | ||
|   | 36b2a1bca3 | ||
|   | fdf36adc3c | ||
|   | b506791535 | ||
| ![renovate[bot]](/assets/img/avatar_default.png)  | 206574eb9f | ||
|   | 19ab29d130 | ||
|   | f61f0e4e52 | ||
|   | a32e6a9ac9 | ||
|   | d7b8823234 | ||
|   | d9b0d5765a | ||
|   | 6eb3fb1076 | ||
|   | 8e7fc7b9c5 | ||
|   | e302c6c408 | ||
|   | 4483a8b9a2 | ||
|   | a4e36d6145 | 
							
								
								
									
										39
									
								
								.browserslistrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.browserslistrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| [modern] | ||||
| # Support for dynamic import is the main litmus test for serving modern builds. | ||||
| # Although officially a ES2020 feature, browsers implemented it early, so this | ||||
| # enables all of ES2017 and some features in ES2018. | ||||
| supports es6-module-dynamic-import | ||||
|  | ||||
| # Exclude Safari 11-12 because of a bug in tagged template literals | ||||
| # https://bugs.webkit.org/show_bug.cgi?id=190756 | ||||
| # Note: Dropping version 11 also enables several more ES2018 features | ||||
| not Safari < 13 | ||||
| not iOS < 13 | ||||
|  | ||||
| # Exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data | ||||
| # Babel ignores these automatically, but we need here for Webpack to output ESM with dynamic imports | ||||
| not KaiOS > 0 | ||||
| not QQAndroid > 0 | ||||
| not UCAndroid > 0 | ||||
|  | ||||
| # Exclude unsupported browsers | ||||
| not dead | ||||
|  | ||||
| [legacy] | ||||
| # Legacy builds are served when modern requirements are not met and support browsers: | ||||
| # - released in the last 7 years + current alpha/beta versionss | ||||
| # - with global utilization above 0.05% | ||||
| # The lattermost query ensures that support for popular old browsers is not dropped too early | ||||
| # (e.g. IE 11, Android 4.4, or Samsung 4). | ||||
| # | ||||
| # In addition, legacy browsers must support some minimum features that cannot be polyfilled: | ||||
| # - ES5 (strict mode) | ||||
| # - web sockets to communicate with backend | ||||
| # - inline SVG used widely in buttons, widgets, etc. | ||||
| # - custom events used for most user interactions | ||||
| # - CSS flexbox used in the majority of the layout | ||||
| # Nearly all of these are redundant with the above rules. | ||||
| # As of May 2023, only web sockets must be added to the query. | ||||
| unreleased versions | ||||
| last 7 years | ||||
| > 0.05% and supports websockets | ||||
| @@ -1,13 +1,7 @@ | ||||
| # 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.10 | ||||
| FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.11 | ||||
|  | ||||
| ENV \ | ||||
|   DEBIAN_FRONTEND=noninteractive \ | ||||
|   DEVCONTAINER=true \ | ||||
|   PATH=$PATH:./node_modules/.bin | ||||
|  | ||||
| # Install nvm | ||||
| COPY .nvmrc /tmp/.nvmrc | ||||
| RUN \ | ||||
|   su vscode -c \ | ||||
|     "source /usr/local/share/nvm/nvm.sh && nvm install $(cat /tmp/.nvmrc) 2>&1" | ||||
| @@ -5,7 +5,7 @@ | ||||
|     "context": ".." | ||||
|   }, | ||||
|   "appPort": "8124:8123", | ||||
|   "postCreateCommand": "script/bootstrap", | ||||
|   "postStartCommand": "script/bootstrap", | ||||
|   "containerEnv": { | ||||
|     "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" | ||||
|   }, | ||||
|   | ||||
							
								
								
									
										5
									
								
								.github/release-drafter.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/release-drafter.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,8 @@ | ||||
| categories: | ||||
|   - title: 'Dependency updates' | ||||
|     collapse-after: 3 | ||||
|     labels: | ||||
|       - 'dependencies' | ||||
| template: | | ||||
|   ## What's Changed | ||||
|  | ||||
|   | ||||
							
								
								
									
										13
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -9,7 +9,6 @@ on: | ||||
|       - master | ||||
|  | ||||
| env: | ||||
|   NODE_VERSION: 16 | ||||
|   NODE_OPTIONS: --max_old_space_size=6144 | ||||
|  | ||||
| jobs: | ||||
| @@ -22,14 +21,14 @@ jobs: | ||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|         with: | ||||
|           ref: dev | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|  | ||||
|       - name: Install dependencies | ||||
| @@ -58,14 +57,14 @@ jobs: | ||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|         with: | ||||
|           ref: master | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|  | ||||
|       - name: Install dependencies | ||||
|   | ||||
							
								
								
									
										25
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,6 @@ on: | ||||
|       - master | ||||
|  | ||||
| env: | ||||
|   NODE_VERSION: 16 | ||||
|   NODE_OPTIONS: --max_old_space_size=6144 | ||||
|   GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|  | ||||
| @@ -25,11 +24,11 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|       - name: Install dependencies | ||||
|         run: yarn install --immutable | ||||
| @@ -48,11 +47,11 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|       - name: Install dependencies | ||||
|         run: yarn install --immutable | ||||
| @@ -66,11 +65,11 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|       - name: Install dependencies | ||||
|         run: yarn install --immutable | ||||
| @@ -84,11 +83,11 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|       - name: Install dependencies | ||||
|         run: yarn install --immutable | ||||
|   | ||||
							
								
								
									
										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@v3.5.0 | ||||
|       uses: actions/checkout@v3.5.3 | ||||
|       with: | ||||
|         # We must fetch at least the immediate parents so that if this is | ||||
|         # a pull request then we can checkout the head. | ||||
|   | ||||
							
								
								
									
										17
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -10,27 +10,26 @@ on: | ||||
|       - master | ||||
|  | ||||
| env: | ||||
|   NODE_VERSION: 16 | ||||
|   NODE_OPTIONS: --max_old_space_size=6144 | ||||
|  | ||||
| jobs: | ||||
|   deploy_dev: | ||||
|     runs-on: ubuntu-latest | ||||
|     name: Demo Development | ||||
|     if: github.event_name != 'push' || github.ref != 'master' | ||||
|     if: github.event_name != 'push' || github.ref_name != 'master' | ||||
|     environment: | ||||
|       name: Demo Development | ||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|         with: | ||||
|           ref: dev | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|  | ||||
|       - name: Install dependencies | ||||
| @@ -53,20 +52,20 @@ jobs: | ||||
|   deploy_master: | ||||
|     runs-on: ubuntu-latest | ||||
|     name: Demo Production | ||||
|     if: github.event_name == 'push' && github.ref == 'master' | ||||
|     if: github.event_name == 'push' && github.ref_name == 'master' | ||||
|     environment: | ||||
|       name: Demo Production | ||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|         with: | ||||
|           ref: master | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|  | ||||
|       - name: Install dependencies | ||||
|   | ||||
							
								
								
									
										7
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -6,7 +6,6 @@ on: | ||||
|     - cron: "0 0 * * *" | ||||
|  | ||||
| env: | ||||
|   NODE_VERSION: 16 | ||||
|   NODE_OPTIONS: --max_old_space_size=6144 | ||||
|  | ||||
| jobs: | ||||
| @@ -17,12 +16,12 @@ jobs: | ||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|  | ||||
|       - name: Install dependencies | ||||
|   | ||||
							
								
								
									
										7
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,6 @@ on: | ||||
|       - dev | ||||
|  | ||||
| env: | ||||
|   NODE_VERSION: 16 | ||||
|   NODE_OPTIONS: --max_old_space_size=6144 | ||||
|  | ||||
| jobs: | ||||
| @@ -22,12 +21,12 @@ 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@v3.5.0 | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|  | ||||
|       - name: Install dependencies | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,7 +9,7 @@ jobs: | ||||
|   lock: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: dessant/lock-threads@v4.0.0 | ||||
|       - uses: dessant/lock-threads@v4.0.1 | ||||
|         with: | ||||
|           github-token: ${{ github.token }} | ||||
|           issue-lock-inactive-days: "30" | ||||
|   | ||||
							
								
								
									
										9
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -6,8 +6,7 @@ on: | ||||
|     - cron: "0 1 * * *" | ||||
|  | ||||
| env: | ||||
|   PYTHON_VERSION: "3.10" | ||||
|   NODE_VERSION: 16 | ||||
|   PYTHON_VERSION: "3.11" | ||||
|   NODE_OPTIONS: --max_old_space_size=6144 | ||||
|  | ||||
| permissions: | ||||
| @@ -21,17 +20,17 @@ jobs: | ||||
|       contents: write | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|  | ||||
|       - name: Set up Python ${{ env.PYTHON_VERSION }} | ||||
|         uses: actions/setup-python@v4 | ||||
|         with: | ||||
|           python-version: ${{ env.PYTHON_VERSION }} | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|  | ||||
|       - name: Install dependencies | ||||
|   | ||||
							
								
								
									
										9
									
								
								.github/workflows/release-drafter.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/release-drafter.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -5,8 +5,17 @@ on: | ||||
|     branches: | ||||
|       - dev | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
|  | ||||
| jobs: | ||||
|   update_release_draft: | ||||
|     permissions: | ||||
|       # write permission for contents is required to create a github release | ||||
|       contents: write | ||||
|       # write permission for pull-requests is required for autolabeler | ||||
|       # otherwise, read permission is required at least | ||||
|       pull-requests: read | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: release-drafter/release-drafter@v5 | ||||
|   | ||||
							
								
								
									
										13
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -6,8 +6,7 @@ on: | ||||
|       - published | ||||
|  | ||||
| env: | ||||
|   PYTHON_VERSION: "3.10" | ||||
|   NODE_VERSION: 16 | ||||
|   PYTHON_VERSION: "3.11" | ||||
|   NODE_OPTIONS: --max_old_space_size=6144 | ||||
|  | ||||
| # Set default workflow permissions | ||||
| @@ -24,7 +23,7 @@ jobs: | ||||
|       contents: write # Required to upload release assets | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|  | ||||
|       - name: Verify version | ||||
|         uses: home-assistant/actions/helpers/verify-version@master | ||||
| @@ -34,10 +33,10 @@ jobs: | ||||
|         with: | ||||
|           python-version: ${{ env.PYTHON_VERSION }} | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@v3.6.0 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
|  | ||||
|       - name: Install dependencies | ||||
| @@ -75,9 +74,9 @@ jobs: | ||||
|           echo "home-assistant-frontend==$version" > ./requirements.txt | ||||
|  | ||||
|       - name: Build wheels | ||||
|         uses: home-assistant/wheels@2022.10.1 | ||||
|         uses: home-assistant/wheels@2023.04.0 | ||||
|         with: | ||||
|           abi: cp310 | ||||
|           abi: cp311 | ||||
|           tag: musllinux_1_2 | ||||
|           arch: amd64 | ||||
|           wheels-key: ${{ secrets.WHEELS_KEY }} | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -7,19 +7,15 @@ on: | ||||
|     paths: | ||||
|       - src/translations/en.json | ||||
|  | ||||
| env: | ||||
|   NODE_VERSION: 16 | ||||
|  | ||||
| jobs: | ||||
|   upload: | ||||
|     name: Upload | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@v3.5.0 | ||||
|         uses: actions/checkout@v3.5.3 | ||||
|  | ||||
|       - name: Upload Translations | ||||
|         run: | | ||||
|           export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}" | ||||
|  | ||||
|           ./script/translations_upload_base | ||||
|   | ||||
							
								
								
									
										39
									
								
								.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| diff --git a/modular/sortable.complete.esm.js b/modular/sortable.complete.esm.js | ||||
| index 02e9f2d6bebeb430fe6e7c1cc3f9c3c9df051f14..bb8268b0844a1faa4108cc92c0be2a3dbaf23f83 100644 | ||||
| --- a/modular/sortable.complete.esm.js | ||||
| +++ b/modular/sortable.complete.esm.js | ||||
| @@ -1657,7 +1657,7 @@ Sortable.prototype = | ||||
|            target = parent; // store last element | ||||
|          } | ||||
|          /* jshint boss:true */ | ||||
| -        while (parent = parent.parentNode); | ||||
| +        while (parent = parent.parentNode || parent.getRootNode().host); | ||||
|        } | ||||
|   | ||||
|        _unhideGhostForTarget(); | ||||
| diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js | ||||
| index b04c8b4634f7c6b4ef1aadbb48afe6564306dea9..39a107163c8c336ebd669b5ea8a936af87e1c1e7 100644 | ||||
| --- a/modular/sortable.core.esm.js | ||||
| +++ b/modular/sortable.core.esm.js | ||||
| @@ -1657,7 +1657,7 @@ Sortable.prototype = | ||||
|            target = parent; // store last element | ||||
|          } | ||||
|          /* jshint boss:true */ | ||||
| -        while (parent = parent.parentNode); | ||||
| +        while (parent = parent.parentNode || parent.getRootNode().host); | ||||
|        } | ||||
|   | ||||
|        _unhideGhostForTarget(); | ||||
| diff --git a/modular/sortable.esm.js b/modular/sortable.esm.js | ||||
| index 6ec7ed1bb557e21c2578200161e989c65d23150b..0a05475a22904472fac6c13f524c674da76584b0 100644 | ||||
| --- a/modular/sortable.esm.js | ||||
| +++ b/modular/sortable.esm.js | ||||
| @@ -1657,7 +1657,7 @@ Sortable.prototype = | ||||
|            target = parent; // store last element | ||||
|          } | ||||
|          /* jshint boss:true */ | ||||
| -        while (parent = parent.parentNode); | ||||
| +        while (parent = parent.parentNode || parent.getRootNode().host); | ||||
|        } | ||||
|   | ||||
|        _unhideGhostForTarget(); | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -8,4 +8,4 @@ plugins: | ||||
|   - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs | ||||
|     spec: "@yarnpkg/plugin-interactive-tools" | ||||
|  | ||||
| yarnPath: .yarn/releases/yarn-3.5.0.cjs | ||||
| yarnPath: .yarn/releases/yarn-3.6.0.cjs | ||||
|   | ||||
| @@ -76,7 +76,8 @@ module.exports.htmlMinifierOptions = { | ||||
|  | ||||
| module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({ | ||||
|   safari10: !latestBuild, | ||||
|   ecma: latestBuild ? undefined : 5, | ||||
|   ecma: latestBuild ? 2015 : 5, | ||||
|   module: latestBuild, | ||||
|   format: { comments: false }, | ||||
|   sourceMap: !isTestBuild, | ||||
| }); | ||||
| @@ -84,17 +85,25 @@ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({ | ||||
| module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({ | ||||
|   babelrc: false, | ||||
|   compact: false, | ||||
|   assumptions: { | ||||
|     privateFieldsAsProperties: true, | ||||
|     setPublicClassFields: true, | ||||
|     setSpreadProperties: true, | ||||
|   }, | ||||
|   browserslistEnv: latestBuild ? "modern" : "legacy", | ||||
|   // Must be unambiguous because some dependencies are CommonJS only | ||||
|   sourceType: "unambiguous", | ||||
|   presets: [ | ||||
|     !latestBuild && [ | ||||
|     [ | ||||
|       "@babel/preset-env", | ||||
|       { | ||||
|         useBuiltIns: "entry", | ||||
|         corejs: { version: "3.29", proposals: true }, | ||||
|         useBuiltIns: latestBuild ? false : "entry", | ||||
|         corejs: latestBuild ? false : { version: "3.31", proposals: true }, | ||||
|         bugfixes: true, | ||||
|       }, | ||||
|     ], | ||||
|     "@babel/preset-typescript", | ||||
|   ].filter(Boolean), | ||||
|   ], | ||||
|   plugins: [ | ||||
|     [ | ||||
|       path.resolve( | ||||
| @@ -106,22 +115,6 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({ | ||||
|         ignoreModuleNotFound: true, | ||||
|       }, | ||||
|     ], | ||||
|     // Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2}) | ||||
|     !latestBuild && [ | ||||
|       "@babel/plugin-proposal-object-rest-spread", | ||||
|       { loose: true, useBuiltIns: true }, | ||||
|     ], | ||||
|     // Only support the syntax, Webpack will handle it. | ||||
|     "@babel/plugin-syntax-import-meta", | ||||
|     "@babel/plugin-syntax-dynamic-import", | ||||
|     "@babel/plugin-syntax-top-level-await", | ||||
|     // Support  various proposals | ||||
|     "@babel/plugin-proposal-optional-chaining", | ||||
|     "@babel/plugin-proposal-nullish-coalescing-operator", | ||||
|     ["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], | ||||
|     ["@babel/plugin-proposal-private-methods", { loose: true }], | ||||
|     ["@babel/plugin-proposal-private-property-in-object", { loose: true }], | ||||
|     ["@babel/plugin-proposal-class-properties", { loose: true }], | ||||
|     // Minify template literals for production | ||||
|     isProdBuild && [ | ||||
|       "template-html-minifier", | ||||
| @@ -139,6 +132,13 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({ | ||||
|         failOnError: true, // we can turn this off in case of false positives | ||||
|       }, | ||||
|     ], | ||||
|     // Import helpers and regenerator from runtime package | ||||
|     [ | ||||
|       "@babel/plugin-transform-runtime", | ||||
|       { version: require("../package.json").dependencies["@babel/runtime"] }, | ||||
|     ], | ||||
|     // Support  some proposals still in TC39 process | ||||
|     ["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], | ||||
|   ].filter(Boolean), | ||||
|   exclude: [ | ||||
|     // \\ for Windows, / for Mac OS and Linux | ||||
| @@ -157,27 +157,27 @@ const publicPath = (latestBuild, root = "") => | ||||
|   latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`; | ||||
|  | ||||
| /* | ||||
| BundleConfig { | ||||
|   // Object with entrypoints that need to be bundled | ||||
|   entry: { [name: string]: pathToFile }, | ||||
|   // Folder where bundled files need to be written | ||||
|   outputPath: string, | ||||
|   // absolute url-path where bundled files can be found | ||||
|   publicPath: string, | ||||
|   // extra definitions that we need to replace in source | ||||
|   defineOverlay: {[name: string]: value }, | ||||
|   // if this is a production build | ||||
|   isProdBuild: boolean, | ||||
|   // If we're targeting latest browsers | ||||
|   latestBuild: boolean, | ||||
|   // If we're doing a stats build (create nice chunk names) | ||||
|   isStatsBuild: boolean, | ||||
|   // If it's just a test build in CI, skip time on source map generation | ||||
|   isTestBuild: boolean, | ||||
|   // Names of entrypoints that should not be hashed | ||||
|   dontHash: Set<string> | ||||
| } | ||||
| */ | ||||
|   BundleConfig { | ||||
|     // Object with entrypoints that need to be bundled | ||||
|     entry: { [name: string]: pathToFile }, | ||||
|     // Folder where bundled files need to be written | ||||
|     outputPath: string, | ||||
|     // absolute url-path where bundled files can be found | ||||
|     publicPath: string, | ||||
|     // extra definitions that we need to replace in source | ||||
|     defineOverlay: {[name: string]: value }, | ||||
|     // if this is a production build | ||||
|     isProdBuild: boolean, | ||||
|     // If we're targeting latest browsers | ||||
|     latestBuild: boolean, | ||||
|     // If we're doing a stats build (create nice chunk names) | ||||
|     isStatsBuild: boolean, | ||||
|     // If it's just a test build in CI, skip time on source map generation | ||||
|     isTestBuild: boolean, | ||||
|     // Names of entrypoints that should not be hashed | ||||
|     dontHash: Set<string> | ||||
|   } | ||||
|   */ | ||||
|  | ||||
| module.exports.config = { | ||||
|   app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) { | ||||
| @@ -260,6 +260,7 @@ module.exports.config = { | ||||
|       isHassioBuild: true, | ||||
|       defineOverlay: { | ||||
|         __SUPERVISOR__: true, | ||||
|         __STATIC_PATH__: `"${paths.hassio_publicPath}/static/"`, | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
|   | ||||
| @@ -1,18 +1,16 @@ | ||||
| // Run HA develop mode
 | ||||
| 
 | ||||
| const gulp = require("gulp"); | ||||
| const env = require("../env.cjs"); | ||||
| require("./clean.cjs"); | ||||
| require("./translations.cjs"); | ||||
| require("./locale-data.cjs"); | ||||
| require("./gen-icons-json.cjs"); | ||||
| require("./gather-static.cjs"); | ||||
| require("./compress.cjs"); | ||||
| require("./webpack.cjs"); | ||||
| require("./service-worker.cjs"); | ||||
| require("./entry-html.cjs"); | ||||
| require("./rollup.cjs"); | ||||
| require("./wds.cjs"); | ||||
| import gulp from "gulp"; | ||||
| import env from "../env.cjs"; | ||||
| import "./clean.js"; | ||||
| import "./compress.js"; | ||||
| import "./entry-html.js"; | ||||
| import "./gather-static.js"; | ||||
| import "./gen-icons-json.js"; | ||||
| import "./locale-data.js"; | ||||
| import "./rollup.js"; | ||||
| import "./service-worker.js"; | ||||
| import "./translations.js"; | ||||
| import "./wds.js"; | ||||
| import "./webpack.js"; | ||||
| 
 | ||||
| gulp.task( | ||||
|   "develop-app", | ||||
| @@ -24,8 +22,7 @@ gulp.task( | ||||
|     gulp.parallel( | ||||
|       "gen-service-worker-app-dev", | ||||
|       "gen-icons-json", | ||||
|       "gen-pages-dev", | ||||
|       "gen-index-app-dev", | ||||
|       "gen-pages-app-dev", | ||||
|       "build-translations", | ||||
|       "build-locale-data" | ||||
|     ), | ||||
| @@ -50,10 +47,6 @@ gulp.task( | ||||
|     env.useRollup() ? "rollup-prod-app" : "webpack-prod-app", | ||||
|     // Don't compress running tests
 | ||||
|     ...(env.isTestBuild() ? [] : ["compress-app"]), | ||||
|     gulp.parallel( | ||||
|       "gen-pages-prod", | ||||
|       "gen-index-app-prod", | ||||
|       "gen-service-worker-app-prod" | ||||
|     ) | ||||
|     gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod") | ||||
|   ) | ||||
| ); | ||||
| @@ -1,13 +1,12 @@ | ||||
| const gulp = require("gulp"); | ||||
| const env = require("../env.cjs"); | ||||
| 
 | ||||
| require("./clean.cjs"); | ||||
| require("./translations.cjs"); | ||||
| require("./gather-static.cjs"); | ||||
| require("./webpack.cjs"); | ||||
| require("./service-worker.cjs"); | ||||
| require("./entry-html.cjs"); | ||||
| require("./rollup.cjs"); | ||||
| import gulp from "gulp"; | ||||
| import env from "../env.cjs"; | ||||
| import "./clean.js"; | ||||
| import "./entry-html.js"; | ||||
| import "./gather-static.js"; | ||||
| import "./rollup.js"; | ||||
| import "./service-worker.js"; | ||||
| import "./translations.js"; | ||||
| import "./webpack.js"; | ||||
| 
 | ||||
| gulp.task( | ||||
|   "develop-cast", | ||||
| @@ -19,7 +18,7 @@ gulp.task( | ||||
|     "translations-enable-merge-backend", | ||||
|     gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), | ||||
|     "copy-static-cast", | ||||
|     "gen-index-cast-dev", | ||||
|     "gen-pages-cast-dev", | ||||
|     env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast" | ||||
|   ) | ||||
| ); | ||||
| @@ -35,6 +34,6 @@ gulp.task( | ||||
|     gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), | ||||
|     "copy-static-cast", | ||||
|     env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast", | ||||
|     "gen-index-cast-prod" | ||||
|     "gen-pages-cast-prod" | ||||
|   ) | ||||
| ); | ||||
| @@ -1,37 +1,37 @@ | ||||
| const del = import("del"); | ||||
| const gulp = require("gulp"); | ||||
| const paths = require("../paths.cjs"); | ||||
| require("./translations.cjs"); | ||||
| import { deleteSync } from "del"; | ||||
| import gulp from "gulp"; | ||||
| import paths from "../paths.cjs"; | ||||
| import "./translations.js"; | ||||
| 
 | ||||
| gulp.task( | ||||
|   "clean", | ||||
|   gulp.parallel("clean-translations", async () => | ||||
|     (await del).deleteSync([paths.app_output_root, paths.build_dir]) | ||||
|     deleteSync([paths.app_output_root, paths.build_dir]) | ||||
|   ) | ||||
| ); | ||||
| 
 | ||||
| gulp.task( | ||||
|   "clean-demo", | ||||
|   gulp.parallel("clean-translations", async () => | ||||
|     (await del).deleteSync([paths.demo_output_root, paths.build_dir]) | ||||
|     deleteSync([paths.demo_output_root, paths.build_dir]) | ||||
|   ) | ||||
| ); | ||||
| 
 | ||||
| gulp.task( | ||||
|   "clean-cast", | ||||
|   gulp.parallel("clean-translations", async () => | ||||
|     (await del).deleteSync([paths.cast_output_root, paths.build_dir]) | ||||
|     deleteSync([paths.cast_output_root, paths.build_dir]) | ||||
|   ) | ||||
| ); | ||||
| 
 | ||||
| gulp.task("clean-hassio", async () => | ||||
|   (await del).deleteSync([paths.hassio_output_root, paths.build_dir]) | ||||
|   deleteSync([paths.hassio_output_root, paths.build_dir]) | ||||
| ); | ||||
| 
 | ||||
| gulp.task( | ||||
|   "clean-gallery", | ||||
|   gulp.parallel("clean-translations", async () => | ||||
|     (await del).deleteSync([ | ||||
|     deleteSync([ | ||||
|       paths.gallery_output_root, | ||||
|       paths.gallery_build, | ||||
|       paths.build_dir, | ||||
| @@ -1,10 +1,10 @@ | ||||
| // Tasks to compress
 | ||||
| 
 | ||||
| const gulp = require("gulp"); | ||||
| const zopfli = require("gulp-zopfli-green"); | ||||
| const merge = require("merge-stream"); | ||||
| const path = require("path"); | ||||
| const paths = require("../paths.cjs"); | ||||
| import gulp from "gulp"; | ||||
| import zopfli from "gulp-zopfli-green"; | ||||
| import merge from "merge-stream"; | ||||
| import path from "path"; | ||||
| import paths from "../paths.cjs"; | ||||
| 
 | ||||
| const zopfliOptions = { threshold: 150 }; | ||||
| 
 | ||||
| @@ -1,15 +1,13 @@ | ||||
| // Run demo develop mode
 | ||||
| const gulp = require("gulp"); | ||||
| const env = require("../env.cjs"); | ||||
| 
 | ||||
| require("./clean.cjs"); | ||||
| require("./translations.cjs"); | ||||
| require("./gen-icons-json.cjs"); | ||||
| require("./gather-static.cjs"); | ||||
| require("./webpack.cjs"); | ||||
| require("./service-worker.cjs"); | ||||
| require("./entry-html.cjs"); | ||||
| require("./rollup.cjs"); | ||||
| import gulp from "gulp"; | ||||
| import env from "../env.cjs"; | ||||
| import "./clean.js"; | ||||
| import "./entry-html.js"; | ||||
| import "./gather-static.js"; | ||||
| import "./gen-icons-json.js"; | ||||
| import "./rollup.js"; | ||||
| import "./service-worker.js"; | ||||
| import "./translations.js"; | ||||
| import "./webpack.js"; | ||||
| 
 | ||||
| gulp.task( | ||||
|   "develop-demo", | ||||
| @@ -21,7 +19,7 @@ gulp.task( | ||||
|     "translations-enable-merge-backend", | ||||
|     gulp.parallel( | ||||
|       "gen-icons-json", | ||||
|       "gen-index-demo-dev", | ||||
|       "gen-pages-demo-dev", | ||||
|       "build-translations", | ||||
|       "build-locale-data" | ||||
|     ), | ||||
| @@ -42,6 +40,6 @@ gulp.task( | ||||
|     gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), | ||||
|     "copy-static-demo", | ||||
|     env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo", | ||||
|     "gen-index-demo-prod" | ||||
|     "gen-pages-demo-prod" | ||||
|   ) | ||||
| ); | ||||
| @@ -1,6 +1,6 @@ | ||||
| const gulp = require("gulp"); | ||||
| const fs = require("fs/promises"); | ||||
| const mapStream = require("map-stream"); | ||||
| import fs from "fs/promises"; | ||||
| import gulp from "gulp"; | ||||
| import mapStream from "map-stream"; | ||||
| 
 | ||||
| const inDirFrontend = "translations/frontend"; | ||||
| const inDirBackend = "translations/backend"; | ||||
| @@ -1,351 +0,0 @@ | ||||
| // Tasks to generate entry HTML | ||||
| const gulp = require("gulp"); | ||||
| const fs = require("fs-extra"); | ||||
| const path = require("path"); | ||||
| const template = require("lodash.template"); | ||||
| const { minify } = require("html-minifier-terser"); | ||||
| const paths = require("../paths.cjs"); | ||||
| const env = require("../env.cjs"); | ||||
| const { htmlMinifierOptions, terserOptions } = require("../bundle.cjs"); | ||||
|  | ||||
| const templatePath = (tpl) => | ||||
|   path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`); | ||||
|  | ||||
| const readFile = (pth) => fs.readFileSync(pth).toString(); | ||||
|  | ||||
| const renderTemplate = (pth, data = {}, pathFunc = templatePath) => { | ||||
|   const compiled = template(readFile(pathFunc(pth))); | ||||
|   return compiled({ | ||||
|     ...data, | ||||
|     useRollup: env.useRollup(), | ||||
|     useWDS: env.useWDS(), | ||||
|     renderTemplate, | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const renderDemoTemplate = (pth, data = {}) => | ||||
|   renderTemplate(pth, data, (tpl) => | ||||
|     path.resolve(paths.demo_dir, "src/html/", `${tpl}.html.template`) | ||||
|   ); | ||||
|  | ||||
| const renderCastTemplate = (pth, data = {}) => | ||||
|   renderTemplate(pth, data, (tpl) => | ||||
|     path.resolve(paths.cast_dir, "src/html/", `${tpl}.html.template`) | ||||
|   ); | ||||
|  | ||||
| const renderGalleryTemplate = (pth, data = {}) => | ||||
|   renderTemplate(pth, data, (tpl) => | ||||
|     path.resolve(paths.gallery_dir, "src/html/", `${tpl}.html.template`) | ||||
|   ); | ||||
|  | ||||
| const minifyHtml = (content) => | ||||
|   minify(content, { | ||||
|     ...htmlMinifierOptions, | ||||
|     conservativeCollapse: false, | ||||
|     minifyJS: terserOptions({ | ||||
|       latestBuild: false, // Shared scripts should be ES5 | ||||
|       isTestBuild: true, // Don't need source maps | ||||
|     }), | ||||
|   }); | ||||
|  | ||||
| const PAGES = ["onboarding", "authorize"]; | ||||
|  | ||||
| gulp.task("gen-pages-dev", (done) => { | ||||
|   for (const page of PAGES) { | ||||
|     const content = renderTemplate(page, { | ||||
|       latestPageJS: `/frontend_latest/${page}.js`, | ||||
|  | ||||
|       es5PageJS: `/frontend_es5/${page}.js`, | ||||
|     }); | ||||
|  | ||||
|     fs.outputFileSync( | ||||
|       path.resolve(paths.app_output_root, `${page}.html`), | ||||
|       content | ||||
|     ); | ||||
|   } | ||||
|   done(); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-pages-prod", async () => { | ||||
|   const latestManifest = require(path.resolve( | ||||
|     paths.app_output_latest, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   const es5Manifest = require(path.resolve( | ||||
|     paths.app_output_es5, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|  | ||||
|   const minifiedHTML = []; | ||||
|   for (const page of PAGES) { | ||||
|     const content = renderTemplate(page, { | ||||
|       latestPageJS: latestManifest[`${page}.js`], | ||||
|       es5PageJS: es5Manifest[`${page}.js`], | ||||
|     }); | ||||
|  | ||||
|     minifiedHTML.push( | ||||
|       minifyHtml(content).then((minified) => | ||||
|         fs.outputFileSync( | ||||
|           path.resolve(paths.app_output_root, `${page}.html`), | ||||
|           minified | ||||
|         ) | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|   await Promise.all(minifiedHTML); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-app-dev", (done) => { | ||||
|   let latestAppJS; | ||||
|   let latestCoreJS; | ||||
|   let latestCustomPanelJS; | ||||
|  | ||||
|   if (env.useWDS()) { | ||||
|     latestAppJS = "http://localhost:8000/src/entrypoints/app.ts"; | ||||
|     latestCoreJS = "http://localhost:8000/src/entrypoints/core.ts"; | ||||
|     latestCustomPanelJS = | ||||
|       "http://localhost:8000/src/entrypoints/custom-panel.ts"; | ||||
|   } else { | ||||
|     latestAppJS = "/frontend_latest/app.js"; | ||||
|     latestCoreJS = "/frontend_latest/core.js"; | ||||
|     latestCustomPanelJS = "/frontend_latest/custom-panel.js"; | ||||
|   } | ||||
|  | ||||
|   const content = renderTemplate("index", { | ||||
|     latestAppJS, | ||||
|     latestCoreJS, | ||||
|     latestCustomPanelJS, | ||||
|  | ||||
|     es5AppJS: "/frontend_es5/app.js", | ||||
|     es5CoreJS: "/frontend_es5/core.js", | ||||
|     es5CustomPanelJS: "/frontend_es5/custom-panel.js", | ||||
|   }).replace(/#THEMEC/g, "{{ theme_color }}"); | ||||
|  | ||||
|   fs.outputFileSync(path.resolve(paths.app_output_root, "index.html"), content); | ||||
|   done(); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-app-prod", async () => { | ||||
|   const latestManifest = require(path.resolve( | ||||
|     paths.app_output_latest, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   const es5Manifest = require(path.resolve( | ||||
|     paths.app_output_es5, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   const content = renderTemplate("index", { | ||||
|     latestAppJS: latestManifest["app.js"], | ||||
|     latestCoreJS: latestManifest["core.js"], | ||||
|     latestCustomPanelJS: latestManifest["custom-panel.js"], | ||||
|  | ||||
|     es5AppJS: es5Manifest["app.js"], | ||||
|     es5CoreJS: es5Manifest["core.js"], | ||||
|     es5CustomPanelJS: es5Manifest["custom-panel.js"], | ||||
|   }); | ||||
|   const minified = (await minifyHtml(content)).replace( | ||||
|     /#THEMEC/g, | ||||
|     "{{ theme_color }}" | ||||
|   ); | ||||
|  | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.app_output_root, "index.html"), | ||||
|     minified | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-cast-dev", (done) => { | ||||
|   const contentReceiver = renderCastTemplate("receiver", { | ||||
|     latestReceiverJS: "/frontend_latest/receiver.js", | ||||
|   }); | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.cast_output_root, "receiver.html"), | ||||
|     contentReceiver | ||||
|   ); | ||||
|  | ||||
|   const contentMedia = renderCastTemplate("media", { | ||||
|     latestMediaJS: "/frontend_latest/media.js", | ||||
|     es5MediaJS: "/frontend_es5/media.js", | ||||
|   }); | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.cast_output_root, "media.html"), | ||||
|     contentMedia | ||||
|   ); | ||||
|  | ||||
|   const contentFAQ = renderCastTemplate("launcher-faq", { | ||||
|     latestLauncherJS: "/frontend_latest/launcher.js", | ||||
|     es5LauncherJS: "/frontend_es5/launcher.js", | ||||
|   }); | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.cast_output_root, "faq.html"), | ||||
|     contentFAQ | ||||
|   ); | ||||
|  | ||||
|   const contentLauncher = renderCastTemplate("launcher", { | ||||
|     latestLauncherJS: "/frontend_latest/launcher.js", | ||||
|     es5LauncherJS: "/frontend_es5/launcher.js", | ||||
|   }); | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.cast_output_root, "index.html"), | ||||
|     contentLauncher | ||||
|   ); | ||||
|   done(); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-cast-prod", (done) => { | ||||
|   const latestManifest = require(path.resolve( | ||||
|     paths.cast_output_latest, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   const es5Manifest = require(path.resolve( | ||||
|     paths.cast_output_es5, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|  | ||||
|   const contentReceiver = renderCastTemplate("receiver", { | ||||
|     latestReceiverJS: latestManifest["receiver.js"], | ||||
|   }); | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.cast_output_root, "receiver.html"), | ||||
|     contentReceiver | ||||
|   ); | ||||
|  | ||||
|   const contentMedia = renderCastTemplate("media", { | ||||
|     latestMediaJS: latestManifest["media.js"], | ||||
|     es5MediaJS: es5Manifest["media.js"], | ||||
|   }); | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.cast_output_root, "media.html"), | ||||
|     contentMedia | ||||
|   ); | ||||
|  | ||||
|   const contentFAQ = renderCastTemplate("launcher-faq", { | ||||
|     latestLauncherJS: latestManifest["launcher.js"], | ||||
|     es5LauncherJS: es5Manifest["launcher.js"], | ||||
|   }); | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.cast_output_root, "faq.html"), | ||||
|     contentFAQ | ||||
|   ); | ||||
|  | ||||
|   const contentLauncher = renderCastTemplate("launcher", { | ||||
|     latestLauncherJS: latestManifest["launcher.js"], | ||||
|     es5LauncherJS: es5Manifest["launcher.js"], | ||||
|   }); | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.cast_output_root, "index.html"), | ||||
|     contentLauncher | ||||
|   ); | ||||
|   done(); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-demo-dev", (done) => { | ||||
|   const content = renderDemoTemplate("index", { | ||||
|     latestDemoJS: "/frontend_latest/main.js", | ||||
|  | ||||
|     es5DemoJS: "/frontend_es5/main.js", | ||||
|   }); | ||||
|  | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.demo_output_root, "index.html"), | ||||
|     content | ||||
|   ); | ||||
|   done(); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-demo-prod", async () => { | ||||
|   const latestManifest = require(path.resolve( | ||||
|     paths.demo_output_latest, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   const es5Manifest = require(path.resolve( | ||||
|     paths.demo_output_es5, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   const content = renderDemoTemplate("index", { | ||||
|     latestDemoJS: latestManifest["main.js"], | ||||
|  | ||||
|     es5DemoJS: es5Manifest["main.js"], | ||||
|   }); | ||||
|   const minified = await minifyHtml(content); | ||||
|  | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.demo_output_root, "index.html"), | ||||
|     minified | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-gallery-dev", (done) => { | ||||
|   const content = renderGalleryTemplate("index", { | ||||
|     latestGalleryJS: "./frontend_latest/entrypoint.js", | ||||
|   }); | ||||
|  | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.gallery_output_root, "index.html"), | ||||
|     content | ||||
|   ); | ||||
|   done(); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-gallery-prod", async () => { | ||||
|   const latestManifest = require(path.resolve( | ||||
|     paths.gallery_output_latest, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   const content = renderGalleryTemplate("index", { | ||||
|     latestGalleryJS: latestManifest["entrypoint.js"], | ||||
|   }); | ||||
|   const minified = await minifyHtml(content); | ||||
|  | ||||
|   fs.outputFileSync( | ||||
|     path.resolve(paths.gallery_output_root, "index.html"), | ||||
|     minified | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-hassio-dev", async () => { | ||||
|   writeHassioEntrypoint( | ||||
|     `${paths.hassio_publicPath}/frontend_latest/entrypoint.js`, | ||||
|     `${paths.hassio_publicPath}/frontend_es5/entrypoint.js` | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-hassio-prod", async () => { | ||||
|   const latestManifest = require(path.resolve( | ||||
|     paths.hassio_output_latest, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   const es5Manifest = require(path.resolve( | ||||
|     paths.hassio_output_es5, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   writeHassioEntrypoint( | ||||
|     latestManifest["entrypoint.js"], | ||||
|     es5Manifest["entrypoint.js"] | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) { | ||||
|   fs.mkdirSync(paths.hassio_output_root, { recursive: true }); | ||||
|   // Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5 | ||||
|   fs.writeFileSync( | ||||
|     path.resolve(paths.hassio_output_root, "entrypoint.js"), | ||||
|     ` | ||||
| function loadES5() { | ||||
|   var el = document.createElement('script'); | ||||
|   el.src = '${es5Entrypoint}'; | ||||
|   document.body.appendChild(el); | ||||
| } | ||||
| if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) { | ||||
|     loadES5(); | ||||
| } else { | ||||
|   try { | ||||
|     new Function("import('${latestEntrypoint}')")(); | ||||
|   } catch (err) { | ||||
|     loadES5(); | ||||
|   } | ||||
| } | ||||
|   `, | ||||
|     { encoding: "utf-8" } | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										233
									
								
								build-scripts/gulp/entry-html.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								build-scripts/gulp/entry-html.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | ||||
| // Tasks to generate entry HTML | ||||
|  | ||||
| import fs from "fs-extra"; | ||||
| import gulp from "gulp"; | ||||
| import { minify } from "html-minifier-terser"; | ||||
| import template from "lodash.template"; | ||||
| import path from "path"; | ||||
| import { htmlMinifierOptions, terserOptions } from "../bundle.cjs"; | ||||
| import env from "../env.cjs"; | ||||
| import paths from "../paths.cjs"; | ||||
|  | ||||
| const renderTemplate = (templateFile, data = {}) => { | ||||
|   const compiled = template( | ||||
|     fs.readFileSync(templateFile, { encoding: "utf-8" }) | ||||
|   ); | ||||
|   return compiled({ | ||||
|     ...data, | ||||
|     useRollup: env.useRollup(), | ||||
|     useWDS: env.useWDS(), | ||||
|     // Resolve any child/nested templates relative to the parent and pass the same data | ||||
|     renderTemplate: (childTemplate) => | ||||
|       renderTemplate( | ||||
|         path.resolve(path.dirname(templateFile), childTemplate), | ||||
|         data | ||||
|       ), | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const WRAP_TAGS = { ".js": "script", ".css": "style" }; | ||||
|  | ||||
| const minifyHtml = (content, ext) => { | ||||
|   const wrapTag = WRAP_TAGS[ext] || ""; | ||||
|   const begTag = wrapTag && `<${wrapTag}>`; | ||||
|   const endTag = wrapTag && `</${wrapTag}>`; | ||||
|   return minify(begTag + content + endTag, { | ||||
|     ...htmlMinifierOptions, | ||||
|     conservativeCollapse: false, | ||||
|     minifyJS: terserOptions({ | ||||
|       latestBuild: false, // Shared scripts should be ES5 | ||||
|       isTestBuild: true, // Don't need source maps | ||||
|     }), | ||||
|   }).then((wrapped) => | ||||
|     wrapTag ? wrapped.slice(begTag.length, -endTag.length) : wrapped | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| // Function to generate a dev task for each project's configuration | ||||
| // Note Currently WDS paths are hard-coded to only work for app | ||||
| const genPagesDevTask = | ||||
|   ( | ||||
|     pageEntries, | ||||
|     inputRoot, | ||||
|     outputRoot, | ||||
|     useWDS = false, | ||||
|     inputSub = "src/html", | ||||
|     publicRoot = "" | ||||
|   ) => | ||||
|   async () => { | ||||
|     for (const [page, entries] of Object.entries(pageEntries)) { | ||||
|       const content = renderTemplate( | ||||
|         path.resolve(inputRoot, inputSub, `${page}.template`), | ||||
|         { | ||||
|           latestEntryJS: entries.map((entry) => | ||||
|             useWDS | ||||
|               ? `http://localhost:8000/src/entrypoints/${entry}.ts` | ||||
|               : `${publicRoot}/frontend_latest/${entry}.js` | ||||
|           ), | ||||
|           es5EntryJS: entries.map( | ||||
|             (entry) => `${publicRoot}/frontend_es5/${entry}.js` | ||||
|           ), | ||||
|           latestCustomPanelJS: useWDS | ||||
|             ? "http://localhost:8000/src/entrypoints/custom-panel.ts" | ||||
|             : `${publicRoot}/frontend_latest/custom-panel.js`, | ||||
|           es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`, | ||||
|         } | ||||
|       ); | ||||
|       fs.outputFileSync(path.resolve(outputRoot, page), content); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
| // Same as previous but for production builds | ||||
| // (includes minification and hashed file names from manifest) | ||||
| const genPagesProdTask = | ||||
|   ( | ||||
|     pageEntries, | ||||
|     inputRoot, | ||||
|     outputRoot, | ||||
|     outputLatest, | ||||
|     outputES5, | ||||
|     inputSub = "src/html" | ||||
|   ) => | ||||
|   async () => { | ||||
|     const latestManifest = fs.readJsonSync( | ||||
|       path.resolve(outputLatest, "manifest.json") | ||||
|     ); | ||||
|     const es5Manifest = outputES5 | ||||
|       ? fs.readJsonSync(path.resolve(outputES5, "manifest.json")) | ||||
|       : {}; | ||||
|     const minifiedHTML = []; | ||||
|     for (const [page, entries] of Object.entries(pageEntries)) { | ||||
|       const content = renderTemplate( | ||||
|         path.resolve(inputRoot, inputSub, `${page}.template`), | ||||
|         { | ||||
|           latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]), | ||||
|           es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]), | ||||
|           latestCustomPanelJS: latestManifest["custom-panel.js"], | ||||
|           es5CustomPanelJS: es5Manifest["custom-panel.js"], | ||||
|         } | ||||
|       ); | ||||
|       minifiedHTML.push( | ||||
|         minifyHtml(content, path.extname(page)).then((minified) => | ||||
|           fs.outputFileSync(path.resolve(outputRoot, page), minified) | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|     await Promise.all(minifiedHTML); | ||||
|   }; | ||||
|  | ||||
| // Map HTML pages to their required entrypoints | ||||
| const APP_PAGE_ENTRIES = { | ||||
|   "authorize.html": ["authorize"], | ||||
|   "onboarding.html": ["onboarding"], | ||||
|   "index.html": ["core", "app"], | ||||
| }; | ||||
|  | ||||
| gulp.task( | ||||
|   "gen-pages-app-dev", | ||||
|   genPagesDevTask( | ||||
|     APP_PAGE_ENTRIES, | ||||
|     paths.polymer_dir, | ||||
|     paths.app_output_root, | ||||
|     env.useWDS() | ||||
|   ) | ||||
| ); | ||||
|  | ||||
| gulp.task( | ||||
|   "gen-pages-app-prod", | ||||
|   genPagesProdTask( | ||||
|     APP_PAGE_ENTRIES, | ||||
|     paths.polymer_dir, | ||||
|     paths.app_output_root, | ||||
|     paths.app_output_latest, | ||||
|     paths.app_output_es5 | ||||
|   ) | ||||
| ); | ||||
|  | ||||
| const CAST_PAGE_ENTRIES = { | ||||
|   "faq.html": ["launcher"], | ||||
|   "index.html": ["launcher"], | ||||
|   "media.html": ["media"], | ||||
|   "receiver.html": ["receiver"], | ||||
| }; | ||||
|  | ||||
| gulp.task( | ||||
|   "gen-pages-cast-dev", | ||||
|   genPagesDevTask(CAST_PAGE_ENTRIES, paths.cast_dir, paths.cast_output_root) | ||||
| ); | ||||
|  | ||||
| gulp.task( | ||||
|   "gen-pages-cast-prod", | ||||
|   genPagesProdTask( | ||||
|     CAST_PAGE_ENTRIES, | ||||
|     paths.cast_dir, | ||||
|     paths.cast_output_root, | ||||
|     paths.cast_output_latest, | ||||
|     paths.cast_output_es5 | ||||
|   ) | ||||
| ); | ||||
|  | ||||
| const DEMO_PAGE_ENTRIES = { "index.html": ["main"] }; | ||||
|  | ||||
| gulp.task( | ||||
|   "gen-pages-demo-dev", | ||||
|   genPagesDevTask(DEMO_PAGE_ENTRIES, paths.demo_dir, paths.demo_output_root) | ||||
| ); | ||||
|  | ||||
| gulp.task( | ||||
|   "gen-pages-demo-prod", | ||||
|   genPagesProdTask( | ||||
|     DEMO_PAGE_ENTRIES, | ||||
|     paths.demo_dir, | ||||
|     paths.demo_output_root, | ||||
|     paths.demo_output_latest, | ||||
|     paths.demo_output_es5 | ||||
|   ) | ||||
| ); | ||||
|  | ||||
| const GALLERY_PAGE_ENTRIES = { "index.html": ["entrypoint"] }; | ||||
|  | ||||
| gulp.task( | ||||
|   "gen-pages-gallery-dev", | ||||
|   genPagesDevTask( | ||||
|     GALLERY_PAGE_ENTRIES, | ||||
|     paths.gallery_dir, | ||||
|     paths.gallery_output_root | ||||
|   ) | ||||
| ); | ||||
|  | ||||
| gulp.task( | ||||
|   "gen-pages-gallery-prod", | ||||
|   genPagesProdTask( | ||||
|     GALLERY_PAGE_ENTRIES, | ||||
|     paths.gallery_dir, | ||||
|     paths.gallery_output_root, | ||||
|     paths.gallery_output_latest | ||||
|   ) | ||||
| ); | ||||
|  | ||||
| const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] }; | ||||
|  | ||||
| gulp.task( | ||||
|   "gen-pages-hassio-dev", | ||||
|   genPagesDevTask( | ||||
|     HASSIO_PAGE_ENTRIES, | ||||
|     paths.hassio_dir, | ||||
|     paths.hassio_output_root, | ||||
|     undefined, | ||||
|     "src", | ||||
|     paths.hassio_publicPath | ||||
|   ) | ||||
| ); | ||||
|  | ||||
| gulp.task( | ||||
|   "gen-pages-hassio-prod", | ||||
|   genPagesProdTask( | ||||
|     HASSIO_PAGE_ENTRIES, | ||||
|     paths.hassio_dir, | ||||
|     paths.hassio_output_root, | ||||
|     paths.hassio_output_latest, | ||||
|     paths.hassio_output_es5, | ||||
|     "src" | ||||
|   ) | ||||
| ); | ||||
| @@ -1,14 +1,15 @@ | ||||
| // Task to download the latest Lokalise translations from the nightly workflow artifacts
 | ||||
| 
 | ||||
| const del = import("del"); | ||||
| const fs = require("fs/promises"); | ||||
| const path = require("path"); | ||||
| const process = require("process"); | ||||
| const gulp = require("gulp"); | ||||
| const jszip = require("jszip"); | ||||
| const tar = require("tar"); | ||||
| const { Octokit } = require("@octokit/rest"); | ||||
| const { createOAuthDeviceAuth } = require("@octokit/auth-oauth-device"); | ||||
| import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device"; | ||||
| import { retry } from "@octokit/plugin-retry"; | ||||
| import { Octokit } from "@octokit/rest"; | ||||
| import { deleteAsync } from "del"; | ||||
| import { mkdir, readFile, writeFile } from "fs/promises"; | ||||
| import gulp from "gulp"; | ||||
| import jszip from "jszip"; | ||||
| import path from "path"; | ||||
| import process from "process"; | ||||
| import tar from "tar"; | ||||
| 
 | ||||
| const MAX_AGE = 24; // hours
 | ||||
| const OWNER = "home-assistant"; | ||||
| @@ -37,7 +38,7 @@ gulp.task("fetch-nightly-translations", async function () { | ||||
|   // and stop if they are not old enough
 | ||||
|   let currentArtifact; | ||||
|   try { | ||||
|     currentArtifact = JSON.parse(await fs.readFile(ARTIFACT_FILE, "utf-8")); | ||||
|     currentArtifact = JSON.parse(await readFile(ARTIFACT_FILE, "utf-8")); | ||||
|     const currentAge = | ||||
|       (Date.now() - Date.parse(currentArtifact.created_at)) / 3600000; | ||||
|     if (currentAge < MAX_AGE) { | ||||
| @@ -52,7 +53,7 @@ gulp.task("fetch-nightly-translations", async function () { | ||||
|   } | ||||
| 
 | ||||
|   // To store file writing promises
 | ||||
|   const createExtractDir = fs.mkdir(EXTRACT_DIR, { recursive: true }); | ||||
|   const createExtractDir = mkdir(EXTRACT_DIR, { recursive: true }); | ||||
|   const writings = []; | ||||
| 
 | ||||
|   // Authenticate to GitHub using GitHub action token if it exists,
 | ||||
| @@ -62,7 +63,7 @@ gulp.task("fetch-nightly-translations", async function () { | ||||
|     tokenAuth = { token: process.env.GITHUB_TOKEN }; | ||||
|   } else { | ||||
|     try { | ||||
|       tokenAuth = JSON.parse(await fs.readFile(TOKEN_FILE, "utf-8")); | ||||
|       tokenAuth = JSON.parse(await readFile(TOKEN_FILE, "utf-8")); | ||||
|     } catch { | ||||
|       if (!allowTokenSetup) { | ||||
|         console.log("No token found so  build wil continue with English only"); | ||||
| @@ -87,7 +88,7 @@ gulp.task("fetch-nightly-translations", async function () { | ||||
|       tokenAuth = await auth({ type: "oauth" }); | ||||
|       writings.push( | ||||
|         createExtractDir.then( | ||||
|           fs.writeFile(TOKEN_FILE, JSON.stringify(tokenAuth, null, 2)) | ||||
|           writeFile(TOKEN_FILE, JSON.stringify(tokenAuth, null, 2)) | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
| @@ -95,7 +96,7 @@ gulp.task("fetch-nightly-translations", async function () { | ||||
| 
 | ||||
|   // Authenticate with token and request workflow runs from GitHub
 | ||||
|   console.log("Fetching new translations..."); | ||||
|   const octokit = new Octokit({ | ||||
|   const octokit = new (Octokit.plugin(retry))({ | ||||
|     userAgent: "Fetch Nightly Translations", | ||||
|     auth: tokenAuth.token, | ||||
|   }); | ||||
| @@ -131,17 +132,13 @@ gulp.task("fetch-nightly-translations", async function () { | ||||
|   } | ||||
|   writings.push( | ||||
|     createExtractDir.then( | ||||
|       fs.writeFile(ARTIFACT_FILE, JSON.stringify(latestArtifact, null, 2)) | ||||
|       writeFile(ARTIFACT_FILE, JSON.stringify(latestArtifact, null, 2)) | ||||
|     ) | ||||
|   ); | ||||
| 
 | ||||
|   // Remove the current translations
 | ||||
|   const deleteCurrent = Promise.all(writings).then( | ||||
|     (await del).deleteAsync([ | ||||
|       `${EXTRACT_DIR}/*`, | ||||
|       `!${ARTIFACT_FILE}`, | ||||
|       `!${TOKEN_FILE}`, | ||||
|     ]) | ||||
|     deleteAsync([`${EXTRACT_DIR}/*`, `!${ARTIFACT_FILE}`, `!${TOKEN_FILE}`]) | ||||
|   ); | ||||
| 
 | ||||
|   // Get the download URL and follow the redirect to download (stored as ArrayBuffer)
 | ||||
| @@ -1,22 +1,19 @@ | ||||
| // Run demo develop mode
 | ||||
| const gulp = require("gulp"); | ||||
| const fs = require("fs"); | ||||
| const path = require("path"); | ||||
| const { marked } = require("marked"); | ||||
| const glob = require("glob"); | ||||
| const yaml = require("js-yaml"); | ||||
| 
 | ||||
| const env = require("../env.cjs"); | ||||
| const paths = require("../paths.cjs"); | ||||
| 
 | ||||
| require("./clean.cjs"); | ||||
| require("./translations.cjs"); | ||||
| require("./gen-icons-json.cjs"); | ||||
| require("./gather-static.cjs"); | ||||
| require("./webpack.cjs"); | ||||
| require("./service-worker.cjs"); | ||||
| require("./entry-html.cjs"); | ||||
| require("./rollup.cjs"); | ||||
| import fs from "fs"; | ||||
| import { glob } from "glob"; | ||||
| import gulp from "gulp"; | ||||
| import yaml from "js-yaml"; | ||||
| import { marked } from "marked"; | ||||
| import path from "path"; | ||||
| import env from "../env.cjs"; | ||||
| import paths from "../paths.cjs"; | ||||
| import "./clean.js"; | ||||
| import "./entry-html.js"; | ||||
| import "./gather-static.js"; | ||||
| import "./gen-icons-json.js"; | ||||
| import "./rollup.js"; | ||||
| import "./service-worker.js"; | ||||
| import "./translations.js"; | ||||
| import "./webpack.js"; | ||||
| 
 | ||||
| gulp.task("gather-gallery-pages", async function gatherPages() { | ||||
|   const pageDir = path.resolve(paths.gallery_dir, "src/pages"); | ||||
| @@ -159,7 +156,7 @@ gulp.task( | ||||
|       "gather-gallery-pages" | ||||
|     ), | ||||
|     "copy-static-gallery", | ||||
|     "gen-index-gallery-dev", | ||||
|     "gen-pages-gallery-dev", | ||||
|     gulp.parallel( | ||||
|       env.useRollup() | ||||
|         ? "rollup-dev-server-gallery" | ||||
| @@ -193,6 +190,6 @@ gulp.task( | ||||
|     ), | ||||
|     "copy-static-gallery", | ||||
|     env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery", | ||||
|     "gen-index-gallery-prod" | ||||
|     "gen-pages-gallery-prod" | ||||
|   ) | ||||
| ); | ||||
| @@ -1,9 +1,9 @@ | ||||
| // Gulp task to gather all static files.
 | ||||
| 
 | ||||
| const gulp = require("gulp"); | ||||
| const path = require("path"); | ||||
| const fs = require("fs-extra"); | ||||
| const paths = require("../paths.cjs"); | ||||
| import fs from "fs-extra"; | ||||
| import gulp from "gulp"; | ||||
| import path from "path"; | ||||
| import paths from "../paths.cjs"; | ||||
| 
 | ||||
| const npmPath = (...parts) => | ||||
|   path.resolve(paths.polymer_dir, "node_modules", ...parts); | ||||
| @@ -111,9 +111,10 @@ gulp.task("copy-translations-supervisor", async () => { | ||||
|   copyTranslations(staticDir); | ||||
| }); | ||||
| 
 | ||||
| gulp.task("copy-locale-data-supervisor", async () => { | ||||
| gulp.task("copy-static-supervisor", async () => { | ||||
|   const staticDir = paths.hassio_output_static; | ||||
|   copyLocaleData(staticDir); | ||||
|   copyFonts(staticDir); | ||||
| }); | ||||
| 
 | ||||
| gulp.task("copy-static-app", async () => { | ||||
| @@ -1,17 +1,15 @@ | ||||
| const gulp = require("gulp"); | ||||
| const path = require("path"); | ||||
| const fs = require("fs"); | ||||
| const hash = require("object-hash"); | ||||
| import fs from "fs"; | ||||
| import gulp from "gulp"; | ||||
| import hash from "object-hash"; | ||||
| import path from "path"; | ||||
| import paths from "../paths.cjs"; | ||||
| 
 | ||||
| const ICON_PACKAGE_PATH = path.resolve( | ||||
|   __dirname, | ||||
|   "../../node_modules/@mdi/svg/" | ||||
| ); | ||||
| const ICON_PACKAGE_PATH = path.resolve("node_modules/@mdi/svg/"); | ||||
| const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json"); | ||||
| const PACKAGE_PATH = path.resolve(ICON_PACKAGE_PATH, "package.json"); | ||||
| const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg"); | ||||
| const OUTPUT_DIR = path.resolve(__dirname, "../../build/mdi"); | ||||
| const REMOVED_ICONS_PATH = path.resolve(__dirname, "../removedIcons.json"); | ||||
| const OUTPUT_DIR = path.resolve(paths.build_dir, "mdi"); | ||||
| const REMOVED_ICONS_PATH = new URL("../removedIcons.json", import.meta.url); | ||||
| 
 | ||||
| const encoding = "utf8"; | ||||
| 
 | ||||
| @@ -1,13 +1,13 @@ | ||||
| const gulp = require("gulp"); | ||||
| const env = require("../env.cjs"); | ||||
| require("./clean.cjs"); | ||||
| require("./compress.cjs"); | ||||
| require("./entry-html.cjs"); | ||||
| require("./gather-static.cjs"); | ||||
| require("./gen-icons-json.cjs"); | ||||
| require("./rollup.cjs"); | ||||
| require("./translations.cjs"); | ||||
| require("./webpack.cjs"); | ||||
| import gulp from "gulp"; | ||||
| import env from "../env.cjs"; | ||||
| import "./clean.js"; | ||||
| import "./compress.js"; | ||||
| import "./entry-html.js"; | ||||
| import "./gather-static.js"; | ||||
| import "./gen-icons-json.js"; | ||||
| import "./rollup.js"; | ||||
| import "./translations.js"; | ||||
| import "./webpack.js"; | ||||
| 
 | ||||
| gulp.task( | ||||
|   "develop-hassio", | ||||
| @@ -17,11 +17,11 @@ gulp.task( | ||||
|     }, | ||||
|     "clean-hassio", | ||||
|     "gen-dummy-icons-json", | ||||
|     "gen-index-hassio-dev", | ||||
|     "gen-pages-hassio-dev", | ||||
|     "build-supervisor-translations", | ||||
|     "copy-translations-supervisor", | ||||
|     "build-locale-data", | ||||
|     "copy-locale-data-supervisor", | ||||
|     "copy-static-supervisor", | ||||
|     env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio" | ||||
|   ) | ||||
| ); | ||||
| @@ -37,9 +37,9 @@ gulp.task( | ||||
|     "build-supervisor-translations", | ||||
|     "copy-translations-supervisor", | ||||
|     "build-locale-data", | ||||
|     "copy-locale-data-supervisor", | ||||
|     "copy-static-supervisor", | ||||
|     env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio", | ||||
|     "gen-index-hassio-prod", | ||||
|     "gen-pages-hassio-prod", | ||||
|     ...// Don't compress running tests
 | ||||
|     (env.isTestBuild() ? [] : ["compress-hassio"]) | ||||
|   ) | ||||
| @@ -1,24 +1,23 @@ | ||||
| const del = import("del"); | ||||
| const path = require("path"); | ||||
| const gulp = require("gulp"); | ||||
| const fs = require("fs"); | ||||
| const paths = require("../paths.cjs"); | ||||
| import { deleteSync } from "del"; | ||||
| import fs from "fs"; | ||||
| import gulp from "gulp"; | ||||
| import path from "path"; | ||||
| import paths from "../paths.cjs"; | ||||
| 
 | ||||
| const outDir = "build/locale-data"; | ||||
| 
 | ||||
| gulp.task("clean-locale-data", async () => (await del).deleteSync([outDir])); | ||||
| gulp.task("clean-locale-data", async () => deleteSync([outDir])); | ||||
| 
 | ||||
| gulp.task("ensure-locale-data-build-dir", (done) => { | ||||
|   if (!fs.existsSync(outDir)) { | ||||
|     fs.mkdirSync(outDir, { recursive: true }); | ||||
|   } | ||||
|   done(); | ||||
| gulp.task("ensure-locale-data-build-dir", async () => { | ||||
|   fs.mkdirSync(outDir, { recursive: true }); | ||||
| }); | ||||
| 
 | ||||
| const modules = { | ||||
|   "intl-relativetimeformat": "RelativeTimeFormat", | ||||
|   "intl-datetimeformat": "DateTimeFormat", | ||||
|   "intl-numberformat": "NumberFormat", | ||||
|   "intl-displaynames": "DisplayNames", | ||||
|   "intl-listformat": "ListFormat", | ||||
| }; | ||||
| 
 | ||||
| gulp.task("create-locale-data", (done) => { | ||||
| @@ -30,11 +29,14 @@ gulp.task("create-locale-data", (done) => { | ||||
|   Object.entries(modules).forEach(([module, className]) => { | ||||
|     Object.keys(translationMeta).forEach((lang) => { | ||||
|       try { | ||||
|         const localeData = String( | ||||
|           fs.readFileSync( | ||||
|             require.resolve(`@formatjs/${module}/locale-data/${lang}.js`) | ||||
|         const localeData = fs | ||||
|           .readFileSync( | ||||
|             path.resolve( | ||||
|               paths.polymer_dir, | ||||
|               `node_modules/@formatjs/${module}/locale-data/${lang}.js` | ||||
|             ), | ||||
|             "utf-8" | ||||
|           ) | ||||
|         ) | ||||
|           .replace( | ||||
|             new RegExp( | ||||
|               `\\/\\*\\s*@generated\\s*\\*\\/\\s*\\/\\/\\s*prettier-ignore\\s*if\\s*\\(Intl\\.${className}\\s*&&\\s*typeof\\s*Intl\\.${className}\\.__addLocaleData\\s*===\\s*'function'\\)\\s*{\\s*Intl\\.${className}\\.__addLocaleData\\(`, | ||||
| @@ -45,15 +47,13 @@ gulp.task("create-locale-data", (done) => { | ||||
|           .replace(/\)\s*}/im, ""); | ||||
|         // make sure we have valid JSON
 | ||||
|         JSON.parse(localeData); | ||||
|         if (!fs.existsSync(path.join(outDir, module))) { | ||||
|           fs.mkdirSync(path.join(outDir, module), { recursive: true }); | ||||
|         } | ||||
|         fs.mkdirSync(path.join(outDir, module), { recursive: true }); | ||||
|         fs.writeFileSync( | ||||
|           path.join(outDir, `${module}/${lang}.json`), | ||||
|           localeData | ||||
|         ); | ||||
|       } catch (e) { | ||||
|         if (e.code !== "MODULE_NOT_FOUND") { | ||||
|         if (e.code !== "ENOENT") { | ||||
|           throw e; | ||||
|         } | ||||
|       } | ||||
| @@ -1,13 +1,14 @@ | ||||
| // Tasks to run Rollup
 | ||||
| const path = require("path"); | ||||
| const gulp = require("gulp"); | ||||
| const rollup = require("rollup"); | ||||
| const handler = require("serve-handler"); | ||||
| const http = require("http"); | ||||
| const log = require("fancy-log"); | ||||
| const open = require("open"); | ||||
| const rollupConfig = require("../rollup.cjs"); | ||||
| const paths = require("../paths.cjs"); | ||||
| 
 | ||||
| import log from "fancy-log"; | ||||
| import gulp from "gulp"; | ||||
| import http from "http"; | ||||
| import open from "open"; | ||||
| import path from "path"; | ||||
| import { rollup } from "rollup"; | ||||
| import handler from "serve-handler"; | ||||
| import paths from "../paths.cjs"; | ||||
| import rollupConfig from "../rollup.cjs"; | ||||
| 
 | ||||
| const bothBuilds = (createConfigFunc, params) => | ||||
|   gulp.series( | ||||
| @@ -1,11 +1,12 @@ | ||||
| // Generate service worker.
 | ||||
| // Based on manifest, create a file with the content as service_worker.js
 | ||||
| const gulp = require("gulp"); | ||||
| const path = require("path"); | ||||
| const fs = require("fs-extra"); | ||||
| const workboxBuild = require("workbox-build"); | ||||
| const sourceMapUrl = require("source-map-url"); | ||||
| const paths = require("../paths.cjs"); | ||||
| 
 | ||||
| import fs from "fs-extra"; | ||||
| import gulp from "gulp"; | ||||
| import path from "path"; | ||||
| import sourceMapUrl from "source-map-url"; | ||||
| import workboxBuild from "workbox-build"; | ||||
| import paths from "../paths.cjs"; | ||||
| 
 | ||||
| const swDest = path.resolve(paths.app_output_root, "service_worker.js"); | ||||
| 
 | ||||
| @@ -28,10 +29,9 @@ self.addEventListener('install', (event) => { | ||||
| 
 | ||||
| gulp.task("gen-service-worker-app-prod", async () => { | ||||
|   // Read bundled source file
 | ||||
|   const bundleManifestLatest = require(path.resolve( | ||||
|     paths.app_output_latest, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   const bundleManifestLatest = fs.readJsonSync( | ||||
|     path.resolve(paths.app_output_latest, "manifest.json") | ||||
|   ); | ||||
|   let serviceWorkerContent = fs.readFileSync( | ||||
|     paths.app_output_root + bundleManifestLatest["service_worker.js"], | ||||
|     "utf-8" | ||||
| @@ -46,10 +46,9 @@ gulp.task("gen-service-worker-app-prod", async () => { | ||||
|   ); | ||||
| 
 | ||||
|   // Remove ES5
 | ||||
|   const bundleManifestES5 = require(path.resolve( | ||||
|     paths.app_output_es5, | ||||
|     "manifest.json" | ||||
|   )); | ||||
|   const bundleManifestES5 = fs.readJsonSync( | ||||
|     path.resolve(paths.app_output_es5, "manifest.json") | ||||
|   ); | ||||
|   fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]); | ||||
|   fs.removeSync( | ||||
|     paths.app_output_root + bundleManifestES5["service_worker.js.map"] | ||||
| @@ -1,19 +1,24 @@ | ||||
| const del = import("del"); | ||||
| const crypto = require("crypto"); | ||||
| const path = require("path"); | ||||
| const source = require("vinyl-source-stream"); | ||||
| const vinylBuffer = require("vinyl-buffer"); | ||||
| const gulp = require("gulp"); | ||||
| const fs = require("fs"); | ||||
| const flatmap = require("gulp-flatmap"); | ||||
| const merge = require("gulp-merge-json"); | ||||
| const rename = require("gulp-rename"); | ||||
| const transform = require("gulp-json-transform"); | ||||
| const { mapFiles } = require("../util.cjs"); | ||||
| const env = require("../env.cjs"); | ||||
| const paths = require("../paths.cjs"); | ||||
| 
 | ||||
| require("./fetch-nightly-translations.cjs"); | ||||
| import { createHash } from "crypto"; | ||||
| import { deleteSync } from "del"; | ||||
| import { | ||||
|   mkdirSync, | ||||
|   readdirSync, | ||||
|   readFileSync, | ||||
|   renameSync, | ||||
|   writeFile, | ||||
| } from "fs"; | ||||
| 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 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"; | ||||
| @@ -33,7 +38,12 @@ gulp.task( | ||||
| 
 | ||||
| // Panel translations which should be split from the core translations.
 | ||||
| const TRANSLATION_FRAGMENTS = Object.keys( | ||||
|   require("../../src/translations/en.json").ui.panel | ||||
|   JSON.parse( | ||||
|     readFileSync( | ||||
|       path.resolve(paths.polymer_dir, "src/translations/en.json"), | ||||
|       "utf-8" | ||||
|     ) | ||||
|   ).ui.panel | ||||
| ); | ||||
| 
 | ||||
| function recursiveFlatten(prefix, data) { | ||||
| @@ -120,17 +130,14 @@ function lokaliseTransform(data, original, file) { | ||||
|   return output; | ||||
| } | ||||
| 
 | ||||
| gulp.task("clean-translations", async () => (await del).deleteSync([workDir])); | ||||
| gulp.task("clean-translations", async () => deleteSync([workDir])); | ||||
| 
 | ||||
| gulp.task("ensure-translations-build-dir", (done) => { | ||||
|   if (!fs.existsSync(workDir)) { | ||||
|     fs.mkdirSync(workDir, { recursive: true }); | ||||
|   } | ||||
|   done(); | ||||
| gulp.task("ensure-translations-build-dir", async () => { | ||||
|   mkdirSync(workDir, { recursive: true }); | ||||
| }); | ||||
| 
 | ||||
| gulp.task("create-test-metadata", (cb) => { | ||||
|   fs.writeFile( | ||||
|   writeFile( | ||||
|     workDir + "/testMetadata.json", | ||||
|     JSON.stringify({ | ||||
|       test: { | ||||
| @@ -303,15 +310,14 @@ const fingerprints = {}; | ||||
| 
 | ||||
| gulp.task("build-translation-fingerprints", () => { | ||||
|   // Fingerprint full file of each language
 | ||||
|   const files = fs.readdirSync(fullDir); | ||||
|   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() | ||||
|         ? crypto | ||||
|             .createHash("md5") | ||||
|             .update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8")) | ||||
|         ? createHash("md5") | ||||
|             .update(readFileSync(path.join(fullDir, files[i]), "utf-8")) | ||||
|             .digest("hex") | ||||
|         : "dev", | ||||
|     }; | ||||
| @@ -327,7 +333,7 @@ gulp.task("build-translation-fingerprints", () => { | ||||
|         throw new Error(`Unable to find hash for ${filename}`); | ||||
|       } | ||||
| 
 | ||||
|       fs.renameSync( | ||||
|       renameSync( | ||||
|         filename, | ||||
|         `${parsed.dir}/${parsed.name}-${fingerprints[parsed.name].hash}${ | ||||
|           parsed.ext | ||||
| @@ -1,11 +0,0 @@ | ||||
| // Tasks to run Rollup | ||||
| const gulp = require("gulp"); | ||||
| const { startDevServer } = require("@web/dev-server"); | ||||
|  | ||||
| gulp.task("wds-watch-app", () => { | ||||
|   startDevServer({ | ||||
|     config: { | ||||
|       watch: true, | ||||
|     }, | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										10
									
								
								build-scripts/gulp/wds.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								build-scripts/gulp/wds.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| import gulp from "gulp"; | ||||
| import { startDevServer } from "@web/dev-server"; | ||||
|  | ||||
| gulp.task("wds-watch-app", async () => { | ||||
|   startDevServer({ | ||||
|     config: { | ||||
|       watch: true, | ||||
|     }, | ||||
|   }); | ||||
| }); | ||||
| @@ -1,19 +1,20 @@ | ||||
| // Tasks to run webpack.
 | ||||
| const fs = require("fs"); | ||||
| const gulp = require("gulp"); | ||||
| const webpack = require("webpack"); | ||||
| const WebpackDevServer = require("webpack-dev-server"); | ||||
| const log = require("fancy-log"); | ||||
| const path = require("path"); | ||||
| const env = require("../env.cjs"); | ||||
| const paths = require("../paths.cjs"); | ||||
| const { | ||||
| 
 | ||||
| import log from "fancy-log"; | ||||
| import fs from "fs"; | ||||
| import gulp from "gulp"; | ||||
| import path from "path"; | ||||
| import webpack from "webpack"; | ||||
| import WebpackDevServer from "webpack-dev-server"; | ||||
| import env from "../env.cjs"; | ||||
| import paths from "../paths.cjs"; | ||||
| import { | ||||
|   createAppConfig, | ||||
|   createDemoConfig, | ||||
|   createCastConfig, | ||||
|   createHassioConfig, | ||||
|   createDemoConfig, | ||||
|   createGalleryConfig, | ||||
| } = require("../webpack.cjs"); | ||||
|   createHassioConfig, | ||||
| } from "../webpack.cjs"; | ||||
| 
 | ||||
| const bothBuilds = (createConfigFunc, params) => [ | ||||
|   createConfigFunc({ ...params, latestBuild: true }), | ||||
							
								
								
									
										59
									
								
								build-scripts/list-plugins-and-polyfills.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										59
									
								
								build-scripts/list-plugins-and-polyfills.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| #!/usr/bin/env node | ||||
| // Script to print Babel plugins and Core JS polyfills that will be used by browserslist environments | ||||
|  | ||||
| import { version as babelVersion } from "@babel/core"; | ||||
| import presetEnv from "@babel/preset-env"; | ||||
| import compilationTargets from "@babel/helper-compilation-targets"; | ||||
| import coreJSCompat from "core-js-compat"; | ||||
| import { logPlugin } from "@babel/preset-env/lib/debug.js"; | ||||
| import { babelOptions } from "./bundle.cjs"; | ||||
|  | ||||
| const detailsOpen = (heading) => | ||||
|   `<details>\n<summary><h4>${heading}</h4></summary>\n`; | ||||
| const detailsClose = "</details>\n"; | ||||
|  | ||||
| const dummyAPI = { | ||||
|   version: babelVersion, | ||||
|   assertVersion: () => {}, | ||||
|   caller: (callback) => | ||||
|     callback({ | ||||
|       name: "Dummy Bundler", | ||||
|       supportsStaticESM: true, | ||||
|       supportsDynamicImport: true, | ||||
|       supportsTopLevelAwait: true, | ||||
|       supportsExportNamespaceFrom: true, | ||||
|     }), | ||||
|   targets: () => ({}), | ||||
| }; | ||||
|  | ||||
| for (const buildType of ["Modern", "Legacy"]) { | ||||
|   const browserslistEnv = buildType.toLowerCase(); | ||||
|   const babelOpts = babelOptions({ latestBuild: browserslistEnv === "modern" }); | ||||
|   const presetEnvOpts = babelOpts.presets[0][1]; | ||||
|  | ||||
|   // Invoking preset-env in debug mode will log the included plugins | ||||
|   console.log(detailsOpen(`${buildType} Build Babel Plugins`)); | ||||
|   presetEnv.default(dummyAPI, { | ||||
|     ...presetEnvOpts, | ||||
|     browserslistEnv, | ||||
|     debug: true, | ||||
|   }); | ||||
|   console.log(detailsClose); | ||||
|  | ||||
|   // Manually log the Core-JS polyfills using the same technique | ||||
|   if (presetEnvOpts.useBuiltIns) { | ||||
|     console.log(detailsOpen(`${buildType} Build Core-JS Polyfills`)); | ||||
|     const targets = compilationTargets.default(babelOpts?.targets, { | ||||
|       browserslistEnv, | ||||
|     }); | ||||
|     const polyfillList = coreJSCompat({ targets }).list; | ||||
|     console.log( | ||||
|       "The following %i polyfills may be injected by Babel:\n", | ||||
|       polyfillList.length | ||||
|     ); | ||||
|     for (const polyfill of polyfillList) { | ||||
|       logPlugin(polyfill, targets, coreJSCompat.data); | ||||
|     } | ||||
|     console.log(detailsClose); | ||||
|   } | ||||
| } | ||||
| @@ -41,7 +41,7 @@ const createWebpackConfig = ({ | ||||
|   return { | ||||
|     name, | ||||
|     mode: isProdBuild ? "production" : "development", | ||||
|     target: ["web", latestBuild ? "es2017" : "es5"], | ||||
|     target: `browserslist:${latestBuild ? "modern" : "legacy"}`, | ||||
|     // For tests/CI, source maps are skipped to gain build speed | ||||
|     // For production, generate source maps for accurate stack traces without source code | ||||
|     // For development, generate "cheap" versions that can map to original line numbers | ||||
| @@ -84,6 +84,13 @@ const createWebpackConfig = ({ | ||||
|       ], | ||||
|       moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", | ||||
|       chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", | ||||
|       splitChunks: { | ||||
|         // Disable splitting for web workers with ESM output | ||||
|         // Imports of external chunks are broken | ||||
|         chunks: latestBuild | ||||
|           ? (chunk) => !chunk.canBeInitial() && !/^.+-worker$/.test(chunk.name) | ||||
|           : undefined, | ||||
|       }, | ||||
|     }, | ||||
|     plugins: [ | ||||
|       !isStatsBuild && new WebpackBar({ fancy: !isProdBuild }), | ||||
| @@ -132,6 +139,17 @@ const createWebpackConfig = ({ | ||||
|         ), | ||||
|         path.resolve(paths.polymer_dir, "src/util/empty.js") | ||||
|       ), | ||||
|       // See `src/resources/intl-polyfill-legacy.ts` for explanation | ||||
|       !latestBuild && | ||||
|         new webpack.NormalModuleReplacementPlugin( | ||||
|           new RegExp( | ||||
|             path.resolve(paths.polymer_dir, "src/resources/intl-polyfill.ts") | ||||
|           ), | ||||
|           path.resolve( | ||||
|             paths.polymer_dir, | ||||
|             "src/resources/intl-polyfill-legacy.ts" | ||||
|           ) | ||||
|         ), | ||||
|       !isProdBuild && new LogStartCompilePlugin(), | ||||
|     ].filter(Boolean), | ||||
|     resolve: { | ||||
| @@ -149,9 +167,12 @@ const createWebpackConfig = ({ | ||||
|         "lit/polyfill-support$": "lit/polyfill-support.js", | ||||
|         "@lit-labs/virtualizer/layouts/grid": | ||||
|           "@lit-labs/virtualizer/layouts/grid.js", | ||||
|         "@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver": | ||||
|           "@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver.js", | ||||
|       }, | ||||
|     }, | ||||
|     output: { | ||||
|       module: latestBuild, | ||||
|       filename: ({ chunk }) => | ||||
|         !isProdBuild || isStatsBuild || dontHash.has(chunk.name) | ||||
|           ? "[name].js" | ||||
| @@ -185,7 +206,7 @@ const createWebpackConfig = ({ | ||||
|           : undefined, | ||||
|     }, | ||||
|     experiments: { | ||||
|       topLevelAwait: true, | ||||
|       outputModule: true, | ||||
|     }, | ||||
|   }; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										24
									
								
								cast/src/html/_social_meta.html.template
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								cast/src/html/_social_meta.html.template
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| <meta property="fb:app_id" content="338291289691179" /> | ||||
| <meta property="og:title" content="Home Assistant Cast" /> | ||||
| <meta property="og:site_name" content="Home Assistant Cast" /> | ||||
| <meta property="og:url" content="https://cast.home-assistant.io/" /> | ||||
| <meta property="og:type" content="website" /> | ||||
| <meta | ||||
|   property="og:description" | ||||
|   content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen." | ||||
| /> | ||||
| <meta | ||||
|   property="og:image" | ||||
|   content="https://cast.home-assistant.io/images/google-nest-hub.png" | ||||
| /> | ||||
| <meta name="twitter:card" content="summary_large_image" /> | ||||
| <meta name="twitter:site" content="@home_assistant" /> | ||||
| <meta name="twitter:title" content="Home Assistant Cast" /> | ||||
| <meta | ||||
|   name="twitter:description" | ||||
|   content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen." | ||||
| /> | ||||
| <meta | ||||
|   name="twitter:image" | ||||
|   content="https://cast.home-assistant.io/images/google-nest-hub.png" | ||||
| /> | ||||
| @@ -3,7 +3,7 @@ | ||||
|   <head> | ||||
|     <title>Home Assistant Cast - FAQ</title> | ||||
|     <link rel="icon" href="/images/ha-cast-icon.png" type="image/png" /> | ||||
|     <%= renderTemplate('_style_base') %> | ||||
|     <%= renderTemplate("../../../src/html/_style_base.html.template") %> | ||||
|     <style> | ||||
|       body { | ||||
|         background-color: #e5e5e5; | ||||
| @@ -35,25 +35,14 @@ | ||||
|     /> | ||||
|   </head> | ||||
|   <body> | ||||
|     <%= renderTemplate('_js_base') %> | ||||
| 
 | ||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||
|     <script> | ||||
|       import("<%= latestLauncherJS %>"); | ||||
|       <% for (const entry of latestEntryJS) { %> | ||||
|         import("<%= entry %>"); | ||||
|       <% } %> | ||||
|       window.latestJS = true; | ||||
|     </script> | ||||
| 
 | ||||
|     <script> | ||||
|       if (!window.latestJS) { | ||||
|         <% if (useRollup) { %> | ||||
|           _ls("/static/js/s.min.js").onload = function() { | ||||
|             System.import("<%= es5LauncherJS %>"); | ||||
|           }; | ||||
|         <% } else { %> | ||||
|           _ls("<%= es5LauncherJS %>"); | ||||
|         <% } %> | ||||
|       } | ||||
|     </script> | ||||
| 
 | ||||
|     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||
|     <hc-layout subtitle="FAQ"> | ||||
|       <style> | ||||
|         a { | ||||
							
								
								
									
										35
									
								
								cast/src/html/index.html.template
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								cast/src/html/index.html.template
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <title>Home Assistant Cast</title> | ||||
|     <link rel="manifest" href="/manifest.json" /> | ||||
|     <link rel="icon" href="/images/ha-cast-icon.png" type="image/png" /> | ||||
|     <%= renderTemplate("../../../src/html/_style_base.html.template") %> | ||||
|     <style> | ||||
|       body { | ||||
|         background-color: #e5e5e5; | ||||
|       } | ||||
|     </style> | ||||
|     <%= renderTemplate("_social_meta.html.template") %> | ||||
|   </head> | ||||
|   <body> | ||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||
|     <hc-connect></hc-connect> | ||||
|     <script> | ||||
|       <% for (const entry of latestEntryJS) { %> | ||||
|         import("<%= entry %>"); | ||||
|       <% } %> | ||||
|       window.latestJS = true; | ||||
|     </script> | ||||
|     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||
|     <script> | ||||
|     (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | ||||
|     (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | ||||
|     m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | ||||
|     })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); | ||||
|  | ||||
|     ga('create', 'UA-57927901-9', 'auto'); | ||||
|     ga('send', 'pageview', location.pathname.includes("auth_callback") === -1 ? location.pathname : "/"); | ||||
|     </script> | ||||
|   </body> | ||||
| </html> | ||||
| @@ -1,57 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <title>Home Assistant Cast</title> | ||||
|     <link rel="manifest" href="/manifest.json" /> | ||||
|     <link rel="icon" href="/images/ha-cast-icon.png" type="image/png" /> | ||||
|     <%= renderTemplate('_style_base') %> | ||||
|     <style> | ||||
|       body { | ||||
|         background-color: #e5e5e5; | ||||
|       } | ||||
|     </style> | ||||
|     <meta property="fb:app_id" content="338291289691179"> | ||||
|     <meta property="og:title" content="Home Assistant Cast"> | ||||
|     <meta property="og:site_name" content="Home Assistant Cast"> | ||||
|     <meta property="og:url" content="https://cast.home-assistant.io/"> | ||||
|     <meta property="og:type" content="website"> | ||||
|     <meta property="og:description" content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen."> | ||||
|     <meta property="og:image" content="https://cast.home-assistant.io/images/google-nest-hub.png"> | ||||
|     <meta name="twitter:card" content="summary_large_image"> | ||||
|     <meta name="twitter:site" content="@home_assistant"> | ||||
|     <meta name="twitter:title" content="Home Assistant Cast"> | ||||
|     <meta name="twitter:description" content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen."> | ||||
|     <meta name="twitter:image" content="https://cast.home-assistant.io/images/google-nest-hub.png"> | ||||
|   </head> | ||||
|   <body> | ||||
|     <%= renderTemplate('_js_base') %> | ||||
|  | ||||
|     <hc-connect></hc-connect> | ||||
|  | ||||
|     <script> | ||||
|       import("<%= latestLauncherJS %>"); | ||||
|       window.latestJS = true; | ||||
|     </script> | ||||
|  | ||||
|     <script> | ||||
|       if (!window.latestJS) { | ||||
|         <% if (useRollup) { %> | ||||
|           _ls("/static/js/s.min.js").onload = function() { | ||||
|             System.import("<%= es5LauncherJS %>"); | ||||
|           }; | ||||
|         <% } else { %> | ||||
|           _ls("<%= es5LauncherJS %>"); | ||||
|         <% } %> | ||||
|       } | ||||
|     </script> | ||||
|     <script> | ||||
|     (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | ||||
|     (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | ||||
|     m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | ||||
|     })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); | ||||
|  | ||||
|     ga('create', 'UA-57927901-9', 'auto'); | ||||
|     ga('send', 'pageview', location.pathname.includes("auth_callback") === -1 ? location.pathname : "/"); | ||||
|     </script> | ||||
|   </body> | ||||
| </html> | ||||
| @@ -22,25 +22,14 @@ | ||||
|     </script> | ||||
|   </head> | ||||
|   <body> | ||||
|     <%= renderTemplate('_js_base') %> | ||||
|  | ||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||
|     <cast-media-player></cast-media-player> | ||||
|  | ||||
|     <script> | ||||
|       import("<%= latestMediaJS %>"); | ||||
|       <% for (const entry of latestEntryJS) { %> | ||||
|         import("<%= entry %>"); | ||||
|       <% } %> | ||||
|       window.latestJS = true; | ||||
|     </script> | ||||
|  | ||||
|     <script> | ||||
|       if (!window.latestJS) { | ||||
|         <% if (useRollup) { %> | ||||
|           _ls("/static/js/s.min.js").onload = function() { | ||||
|             System.import("<%= es5MediaJS %>"); | ||||
|           }; | ||||
|         <% } else { %> | ||||
|           _ls("<%= es5MediaJS %>"); | ||||
|         <% } %> | ||||
|       } | ||||
|     </script> | ||||
|     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||
|   </body> | ||||
| </html> | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script> | ||||
|   <script type="module" src="<%= latestReceiverJS %>"></script> | ||||
|   <%= renderTemplate('_style_base') %> | ||||
|   <% for (const entry of latestEntryJS) { %> | ||||
|     <script type="module" src="<%= entry %>"></script> | ||||
|   <% } %> | ||||
|   <%= renderTemplate("../../../src/html/_style_base.html.template") %> | ||||
|   <style> | ||||
|     body { | ||||
|       background-color: white; | ||||
|   | ||||
| @@ -190,7 +190,7 @@ export class HcConnect extends LitElement { | ||||
|  | ||||
|   private _handleInputKeyDown(ev: KeyboardEvent) { | ||||
|     // Handle pressing enter. | ||||
|     if (ev.keyCode === 13) { | ||||
|     if (ev.key === "Enter") { | ||||
|       this._handleConnect(); | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import { cast } from "chromecast-caf-receiver"; | ||||
|  | ||||
| const castContext = cast.framework.CastReceiverContext.getInstance(); | ||||
|  | ||||
| const playerManager = castContext.getPlayerManager(); | ||||
|   | ||||
| @@ -1,2 +1,3 @@ | ||||
| /* eslint-disable no-undef */ | ||||
| import { cast } from "chromecast-caf-receiver"; | ||||
|  | ||||
| export const castContext = cast.framework.CastReceiverContext.getInstance(); | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| /* eslint-disable no-undef */ | ||||
| import { cast } from "chromecast-caf-receiver"; | ||||
| import { CAST_NS } from "../../../src/cast/const"; | ||||
| import { HassMessage } from "../../../src/cast/receiver_messages"; | ||||
| import "../../../src/resources/custom-card-support"; | ||||
|   | ||||
							
								
								
									
										26
									
								
								demo/src/html/_social_meta.html.template
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								demo/src/html/_social_meta.html.template
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| <meta property="fb:app_id" content="338291289691179" /> | ||||
| <meta property="og:title" content="Home Assistant Demo" /> | ||||
| <meta property="og:site_name" content="Home Assistant" /> | ||||
| <meta property="og:url" content="https://demo.home-assistant.io/" /> | ||||
| <meta property="og:type" content="website" /> | ||||
| <meta | ||||
|   property="og:description" | ||||
|   content="Open source home automation that puts local control and privacy first." | ||||
| /> | ||||
| <meta | ||||
|   property="og:image" | ||||
|   content="https://www.home-assistant.io/images/default-social.png" | ||||
| /> | ||||
|  | ||||
| <meta name="twitter:card" content="summary_large_image" /> | ||||
| <meta name="twitter:site" content="@home_assistant" /> | ||||
|  | ||||
| <meta name="twitter:title" content="Home Assistant" /> | ||||
| <meta | ||||
|   name="twitter:description" | ||||
|   content="Open source home automation that puts local control and privacy first." | ||||
| /> | ||||
| <meta | ||||
|   name="twitter:image" | ||||
|   content="https://www.home-assistant.io/images/default-social.png" | ||||
| /> | ||||
| @@ -1,9 +1,8 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" /> | ||||
|     <link rel="icon" href="/static/icons/favicon.ico" /> | ||||
|     <title>Home Assistant Demo</title> | ||||
|     <%= renderTemplate("../../../src/html/_header.html.template") %> | ||||
|     <link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4" /> | ||||
|     <link | ||||
|       rel="apple-touch-icon" | ||||
| @@ -35,33 +34,7 @@ | ||||
|       content="width=device-width, initial-scale=1, shrink-to-fit=no" | ||||
|     /> | ||||
|     <meta name="theme-color" content="#03a9f4" /> | ||||
|     <meta property="fb:app_id" content="338291289691179" /> | ||||
|     <meta property="og:title" content="Home Assistant Demo" /> | ||||
|     <meta property="og:site_name" content="Home Assistant" /> | ||||
|     <meta property="og:url" content="https://demo.home-assistant.io/" /> | ||||
|     <meta property="og:type" content="website" /> | ||||
|     <meta | ||||
|       property="og:description" | ||||
|       content="Open source home automation that puts local control and privacy first." | ||||
|     /> | ||||
|     <meta | ||||
|       property="og:image" | ||||
|       content="https://www.home-assistant.io/images/default-social.png" | ||||
|     /> | ||||
|  | ||||
|     <meta name="twitter:card" content="summary_large_image" /> | ||||
|     <meta name="twitter:site" content="@home_assistant" /> | ||||
|  | ||||
|     <meta name="twitter:title" content="Home Assistant" /> | ||||
|     <meta | ||||
|       name="twitter:description" | ||||
|       content="Open source home automation that puts local control and privacy first." | ||||
|     /> | ||||
|     <meta | ||||
|       name="twitter:image" | ||||
|       content="https://www.home-assistant.io/images/default-social.png" | ||||
|     /> | ||||
|     <title>Home Assistant Demo</title> | ||||
|     <%= renderTemplate("_social_meta.html.template") %> | ||||
|     <style> | ||||
|       html { | ||||
|         background-color: var(--primary-background-color, #fafafa); | ||||
| @@ -107,29 +80,19 @@ | ||||
|       </svg> | ||||
|       <div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div> | ||||
|     </div> | ||||
|  | ||||
|     <ha-demo></ha-demo> | ||||
|  | ||||
|     <%= renderTemplate('_js_base') %> | ||||
|     <%= renderTemplate('_preload_roboto') %> | ||||
|  | ||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||
|     <%= renderTemplate("../../../src/html/_preload_roboto.html.template") %> | ||||
|     <script> | ||||
|       import("<%= latestDemoJS %>"); | ||||
|       window.latestJS = true; | ||||
|     </script> | ||||
|  | ||||
|     <script> | ||||
|       if (!window.latestJS) { | ||||
|         <% if (useRollup) { %> | ||||
|           _ls("/static/js/s.min.js").onload = function() { | ||||
|             System.import("<%= es5DemoJS %>"); | ||||
|           }; | ||||
|         <% } else { %> | ||||
|           _ls("<%= es5DemoJS %>"); | ||||
|       // Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5 | ||||
|       if (!isS11_12) { | ||||
|         <% for (const entry of latestEntryJS) { %> | ||||
|           import("<%= entry %>"); | ||||
|         <% } %> | ||||
|         window.latestJS = true; | ||||
|       } | ||||
|     </script> | ||||
|  | ||||
|     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||
|     <script> | ||||
|       var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]]; | ||||
|       (function (d, t) { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; | ||||
|  | ||||
| export const mockConfigEntries = (hass: MockHomeAssistant) => { | ||||
|   hass.mockWS("config_entries/get", () => [ | ||||
|   hass.mockWS("config_entries/get_matching", () => [ | ||||
|     { | ||||
|       entry_id: "co2signal", | ||||
|       domain: "co2signal", | ||||
|   | ||||
| @@ -45,6 +45,10 @@ export default [ | ||||
|     header: "Users", | ||||
|     pages: ["user-types", "configuration-menu"], | ||||
|   }, | ||||
|   { | ||||
|     category: "date-time", | ||||
|     header: "Date and Time", | ||||
|   }, | ||||
|   { | ||||
|     category: "design.home-assistant.io", | ||||
|     header: "About", | ||||
|   | ||||
							
								
								
									
										24
									
								
								gallery/src/data/date-options.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								gallery/src/data/date-options.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| import type { ControlSelectOption } from "../../../src/components/ha-control-select"; | ||||
|  | ||||
| export const timeOptions: ControlSelectOption[] = [ | ||||
|   { | ||||
|     value: "now", | ||||
|     label: "Now", | ||||
|   }, | ||||
|   { | ||||
|     value: "00:15:30", | ||||
|     label: "12:15:30 AM", | ||||
|   }, | ||||
|   { | ||||
|     value: "06:15:30", | ||||
|     label: "06:15:30 AM", | ||||
|   }, | ||||
|   { | ||||
|     value: "12:15:30", | ||||
|     label: "12:15:30 PM", | ||||
|   }, | ||||
|   { | ||||
|     value: "18:15:30", | ||||
|     label: "06:15:30 PM", | ||||
|   }, | ||||
| ]; | ||||
| @@ -1,5 +1,3 @@ | ||||
| import "@polymer/polymer/lib/elements/dom-if"; | ||||
| import "@polymer/polymer/lib/elements/dom-repeat"; | ||||
| import "../../src/resources/ha-style"; | ||||
| import "../../src/resources/roboto"; | ||||
| import "./ha-gallery"; | ||||
|   | ||||
| @@ -8,8 +8,9 @@ | ||||
|     /> | ||||
|     <meta name="theme-color" content="#2157BC" /> | ||||
|     <title>Home Assistant Design</title> | ||||
|  | ||||
|     <script type="module" src="<%= latestGalleryJS %>"></script> | ||||
|     <% for (const entry of latestEntryJS) { %> | ||||
|       <script type="module" src="<%= entry %>"></script> | ||||
|     <% } %> | ||||
|     <style> | ||||
|       body { | ||||
|         font-family: Roboto, Noto, sans-serif; | ||||
|   | ||||
| @@ -63,7 +63,7 @@ export class DemoAutomationDescribeCondition extends LitElement { | ||||
|         <div class="condition"> | ||||
|           <span> | ||||
|             ${this._condition | ||||
|               ? describeCondition(this._condition, this.hass) | ||||
|               ? describeCondition(this._condition, this.hass, []) | ||||
|               : "<invalid YAML>"} | ||||
|           </span> | ||||
|           <ha-yaml-editor | ||||
| @@ -76,7 +76,7 @@ export class DemoAutomationDescribeCondition extends LitElement { | ||||
|         ${conditions.map( | ||||
|           (conf) => html` | ||||
|             <div class="condition"> | ||||
|               <span>${describeCondition(conf as any, this.hass)}</span> | ||||
|               <span>${describeCondition(conf as any, this.hass, [])}</span> | ||||
|               <pre>${dump(conf)}</pre> | ||||
|             </div> | ||||
|           ` | ||||
|   | ||||
| @@ -41,6 +41,7 @@ const triggers = [ | ||||
|   { platform: "sun", event: "sunset" }, | ||||
|   { platform: "time_pattern" }, | ||||
|   { platform: "webhook" }, | ||||
|   { platform: "persistent_notification" }, | ||||
|   { | ||||
|     platform: "zone", | ||||
|     entity_id: "person.person", | ||||
| @@ -74,7 +75,7 @@ export class DemoAutomationDescribeTrigger extends LitElement { | ||||
|         <div class="trigger"> | ||||
|           <span> | ||||
|             ${this._trigger | ||||
|               ? describeTrigger(this._trigger, this.hass) | ||||
|               ? describeTrigger(this._trigger, this.hass, []) | ||||
|               : "<invalid YAML>"} | ||||
|           </span> | ||||
|           <ha-yaml-editor | ||||
| @@ -86,7 +87,7 @@ export class DemoAutomationDescribeTrigger extends LitElement { | ||||
|         ${triggers.map( | ||||
|           (conf) => html` | ||||
|             <div class="trigger"> | ||||
|               <span>${describeTrigger(conf as any, this.hass)}</span> | ||||
|               <span>${describeTrigger(conf as any, this.hass, [])}</span> | ||||
|               <pre>${dump(conf)}</pre> | ||||
|             </div> | ||||
|           ` | ||||
|   | ||||
| @@ -19,6 +19,7 @@ import { HaTemplateTrigger } from "../../../../src/panels/config/automation/trig | ||||
| import { HaTimeTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-time"; | ||||
| import { HaTimePatternTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern"; | ||||
| import { HaWebhookTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-webhook"; | ||||
| import { HaPersistentNotificationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification"; | ||||
| import { HaZoneTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-zone"; | ||||
| import { HaDeviceTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-device"; | ||||
| import { HaStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state"; | ||||
| @@ -72,6 +73,16 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [ | ||||
|     triggers: [{ platform: "webhook", ...HaWebhookTrigger.defaultConfig }], | ||||
|   }, | ||||
|  | ||||
|   { | ||||
|     name: "Persistent Notification", | ||||
|     triggers: [ | ||||
|       { | ||||
|         platform: "persistent_notification", | ||||
|         ...HaPersistentNotificationTrigger.defaultConfig, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|  | ||||
|   { | ||||
|     name: "Zone", | ||||
|     triggers: [{ platform: "zone", ...HaZoneTrigger.defaultConfig }], | ||||
|   | ||||
| @@ -0,0 +1,3 @@ | ||||
| --- | ||||
| title: Control Circular Slider | ||||
| --- | ||||
							
								
								
									
										153
									
								
								gallery/src/pages/components/ha-control-circular-slider.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								gallery/src/pages/components/ha-control-circular-slider.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| import { css, html, LitElement, TemplateResult } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-circular-slider"; | ||||
| import "../../../../src/components/ha-slider"; | ||||
|  | ||||
| @customElement("demo-components-ha-control-circular-slider") | ||||
| export class DemoHaCircularSlider extends LitElement { | ||||
|   @state() | ||||
|   private current = 22; | ||||
|  | ||||
|   @state() | ||||
|   private value = 19; | ||||
|  | ||||
|   @state() | ||||
|   private high = 25; | ||||
|  | ||||
|   @state() | ||||
|   private changingValue?: number; | ||||
|  | ||||
|   @state() | ||||
|   private changingHigh?: number; | ||||
|  | ||||
|   private _valueChanged(ev) { | ||||
|     this.value = ev.detail.value; | ||||
|   } | ||||
|  | ||||
|   private _valueChanging(ev) { | ||||
|     this.changingValue = ev.detail.value; | ||||
|   } | ||||
|  | ||||
|   private _highChanged(ev) { | ||||
|     this.high = ev.detail.value; | ||||
|   } | ||||
|  | ||||
|   private _highChanging(ev) { | ||||
|     this.changingHigh = ev.detail.value; | ||||
|   } | ||||
|  | ||||
|   private _currentChanged(ev) { | ||||
|     this.current = ev.currentTarget.value; | ||||
|   } | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
|       <ha-card> | ||||
|         <div class="card-content"> | ||||
|           <p class="title"><b>Config</b></p> | ||||
|           <div class="field"> | ||||
|             <p>Current</p> | ||||
|             <ha-slider | ||||
|               min="10" | ||||
|               max="30" | ||||
|               .value=${this.current} | ||||
|               @change=${this._currentChanged} | ||||
|               pin | ||||
|             ></ha-slider> | ||||
|             <p>${this.current} °C</p> | ||||
|           </div> | ||||
|         </div> | ||||
|       </ha-card> | ||||
|       <ha-card> | ||||
|         <div class="card-content"> | ||||
|           <p class="title"><b>Single</b></p> | ||||
|           <ha-control-circular-slider | ||||
|             @value-changed=${this._valueChanged} | ||||
|             @value-changing=${this._valueChanging} | ||||
|             .value=${this.value} | ||||
|             .current=${this.current} | ||||
|             step="1" | ||||
|             min="10" | ||||
|             max="30" | ||||
|           ></ha-control-circular-slider> | ||||
|           <div> | ||||
|             Value: ${this.value} °C | ||||
|             <br /> | ||||
|             Changing: | ||||
|             ${this.changingValue != null ? `${this.changingValue} °C` : "-"} | ||||
|           </div> | ||||
|         </div> | ||||
|       </ha-card> | ||||
|       <ha-card> | ||||
|         <div class="card-content"> | ||||
|           <p class="title"><b>Dual</b></p> | ||||
|           <ha-control-circular-slider | ||||
|             dual | ||||
|             @low-changed=${this._valueChanged} | ||||
|             @low-changing=${this._valueChanging} | ||||
|             @high-changed=${this._highChanged} | ||||
|             @high-changing=${this._highChanging} | ||||
|             .low=${this.value} | ||||
|             .high=${this.high} | ||||
|             .current=${this.current} | ||||
|             step="1" | ||||
|             min="10" | ||||
|             max="30" | ||||
|           ></ha-control-circular-slider> | ||||
|           <div> | ||||
|             Low value: ${this.value} °C | ||||
|             <br /> | ||||
|             Low changing: | ||||
|             ${this.changingValue != null ? `${this.changingValue} °C` : "-"} | ||||
|             <br /> | ||||
|             High value: ${this.high} °C | ||||
|             <br /> | ||||
|             High changing: | ||||
|             ${this.changingHigh != null ? `${this.changingHigh} °C` : "-"} | ||||
|           </div> | ||||
|         </div> | ||||
|       </ha-card> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-card { | ||||
|         max-width: 600px; | ||||
|         margin: 24px auto; | ||||
|       } | ||||
|       pre { | ||||
|         margin-top: 0; | ||||
|         margin-bottom: 8px; | ||||
|       } | ||||
|       p { | ||||
|         margin: 0; | ||||
|       } | ||||
|       p.title { | ||||
|         margin-bottom: 12px; | ||||
|       } | ||||
|       ha-control-circular-slider { | ||||
|         --control-circular-slider-color: #ff9800; | ||||
|         --control-circular-slider-background: #ff9800; | ||||
|         --control-circular-slider-background-opacity: 0.3; | ||||
|       } | ||||
|       ha-control-circular-slider[dual] { | ||||
|         --control-circular-slider-high-color: #2196f3; | ||||
|         --control-circular-slider-low-color: #ff9800; | ||||
|         --control-circular-slider-background: var(--disabled-color); | ||||
|       } | ||||
|       .field { | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         align-items: center; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-components-ha-control-circular-slider": DemoHaCircularSlider; | ||||
|   } | ||||
| } | ||||
| @@ -336,7 +336,7 @@ const SCHEMAS: { | ||||
|           ["and", "another_one"], | ||||
|           ["option", "1000"], | ||||
|         ], | ||||
|         name: "select many otions", | ||||
|         name: "select many options", | ||||
|         default: "default", | ||||
|       }, | ||||
|     ], | ||||
| @@ -364,7 +364,7 @@ const SCHEMAS: { | ||||
|           and: "another_one", | ||||
|           option: "1000", | ||||
|         }, | ||||
|         name: "multi many otions", | ||||
|         name: "multi many options", | ||||
|         default: ["default"], | ||||
|       }, | ||||
|     ], | ||||
|   | ||||
							
								
								
									
										3
									
								
								gallery/src/pages/components/ha-hs-color-picker.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								gallery/src/pages/components/ha-hs-color-picker.markdown
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| --- | ||||
| title: HS Color Picker | ||||
| --- | ||||
							
								
								
									
										120
									
								
								gallery/src/pages/components/ha-hs-color-picker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								gallery/src/pages/components/ha-hs-color-picker.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| import "../../../../src/components/ha-hs-color-picker"; | ||||
|  | ||||
| import { css, html, LitElement, TemplateResult } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
|  | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-slider"; | ||||
| import { hsv2rgb } from "../../../../src/common/color/convert-color"; | ||||
|  | ||||
| @customElement("demo-components-ha-hs-color-picker") | ||||
| export class DemoHaHsColorPicker extends LitElement { | ||||
|   @state() | ||||
|   brightness = 255; | ||||
|  | ||||
|   @state() | ||||
|   value: [number, number] = [0, 0]; | ||||
|  | ||||
|   @state() | ||||
|   liveValue?: [number, number]; | ||||
|  | ||||
|   private _brightnessChanged(ev) { | ||||
|     this.brightness = Number(ev.target.value); | ||||
|   } | ||||
|  | ||||
|   private _hsColorCursor(ev) { | ||||
|     this.liveValue = ev.detail.value; | ||||
|   } | ||||
|  | ||||
|   private _hsColorChanged(ev) { | ||||
|     this.value = ev.detail.value; | ||||
|   } | ||||
|  | ||||
|   private _hueChanged(ev) { | ||||
|     this.value = [ev.target.value, this.value[1]]; | ||||
|   } | ||||
|  | ||||
|   private _saturationChanged(ev) { | ||||
|     this.value = [this.value[0], ev.target.value]; | ||||
|   } | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     const h = (this.liveValue ?? this.value)[0]; | ||||
|     const s = (this.liveValue ?? this.value)[1]; | ||||
|  | ||||
|     const rgb = hsv2rgb([h, s, this.brightness]); | ||||
|  | ||||
|     return html` | ||||
|       <ha-card> | ||||
|         <div class="card-content"> | ||||
|           <p class="value">${h}° - ${Math.round(s * 100)}%</p> | ||||
|           <p class="value">${rgb.map((v) => Math.round(v)).join(", ")}</p> | ||||
|           <ha-hs-color-picker | ||||
|             colorBrightness=${this.brightness} | ||||
|             .value=${this.value} | ||||
|             @value-changed=${this._hsColorChanged} | ||||
|             @cursor-moved=${this._hsColorCursor} | ||||
|           ></ha-hs-color-picker> | ||||
|           <p>Hue : ${this.value[0]}</p> | ||||
|           <ha-slider | ||||
|             step="1" | ||||
|             pin | ||||
|             min="0" | ||||
|             max="360" | ||||
|             .value=${this.value[0]} | ||||
|             @change=${this._hueChanged} | ||||
|           > | ||||
|           </ha-slider> | ||||
|           <p>Saturation : ${this.value[1]}</p> | ||||
|           <ha-slider | ||||
|             step="0.01" | ||||
|             pin | ||||
|             min="0" | ||||
|             max="1" | ||||
|             .value=${this.value[1]} | ||||
|             @change=${this._saturationChanged} | ||||
|           > | ||||
|           </ha-slider> | ||||
|           <p>Color Brighness : ${this.brightness}</p> | ||||
|           <ha-slider | ||||
|             step="1" | ||||
|             pin | ||||
|             min="0" | ||||
|             max="255" | ||||
|             .value=${this.brightness} | ||||
|             @change=${this._brightnessChanged} | ||||
|           > | ||||
|           </ha-slider> | ||||
|         </div> | ||||
|       </ha-card> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-card { | ||||
|         max-width: 600px; | ||||
|         margin: 24px auto; | ||||
|       } | ||||
|       .card-content { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         flex-direction: column; | ||||
|       } | ||||
|       ha-hs-color-picker { | ||||
|         width: 400px; | ||||
|       } | ||||
|       .value { | ||||
|         font-size: 22px; | ||||
|         font-weight: bold; | ||||
|         margin: 0 0 12px 0; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-components-ha-hs-color-picker": DemoHaHsColorPicker; | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,3 @@ | ||||
| --- | ||||
| title: Temp Color Picker | ||||
| --- | ||||
							
								
								
									
										117
									
								
								gallery/src/pages/components/ha-temp-color-picker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								gallery/src/pages/components/ha-temp-color-picker.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| import "../../../../src/components/ha-temp-color-picker"; | ||||
|  | ||||
| import { css, html, LitElement, TemplateResult } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
|  | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-slider"; | ||||
|  | ||||
| @customElement("demo-components-ha-temp-color-picker") | ||||
| export class DemoHaTempColorPicker extends LitElement { | ||||
|   @state() | ||||
|   min = 3000; | ||||
|  | ||||
|   @state() | ||||
|   max = 7000; | ||||
|  | ||||
|   @state() | ||||
|   value = 4000; | ||||
|  | ||||
|   @state() | ||||
|   liveValue?: number; | ||||
|  | ||||
|   private _minChanged(ev) { | ||||
|     this.min = Number(ev.target.value); | ||||
|   } | ||||
|  | ||||
|   private _maxChanged(ev) { | ||||
|     this.max = Number(ev.target.value); | ||||
|   } | ||||
|  | ||||
|   private _valueChanged(ev) { | ||||
|     this.value = Number(ev.target.value); | ||||
|   } | ||||
|  | ||||
|   private _tempColorCursor(ev) { | ||||
|     this.liveValue = ev.detail.value; | ||||
|   } | ||||
|  | ||||
|   private _tempColorChanged(ev) { | ||||
|     this.value = ev.detail.value; | ||||
|   } | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
|       <ha-card> | ||||
|         <div class="card-content"> | ||||
|           <p class="value">${this.liveValue ?? this.value} K</p> | ||||
|           <ha-temp-color-picker | ||||
|             .min=${this.min} | ||||
|             .max=${this.max} | ||||
|             .value=${this.value} | ||||
|             @value-changed=${this._tempColorChanged} | ||||
|             @cursor-moved=${this._tempColorCursor} | ||||
|           ></ha-temp-color-picker> | ||||
|           <p>Min temp : ${this.min} K</p> | ||||
|           <ha-slider | ||||
|             step="1" | ||||
|             pin | ||||
|             min="2000" | ||||
|             max="10000" | ||||
|             .value=${this.min} | ||||
|             @change=${this._minChanged} | ||||
|           > | ||||
|           </ha-slider> | ||||
|           <p>Max temp : ${this.max} K</p> | ||||
|           <ha-slider | ||||
|             step="1" | ||||
|             pin | ||||
|             min="2000" | ||||
|             max="10000" | ||||
|             .value=${this.max} | ||||
|             @change=${this._maxChanged} | ||||
|           > | ||||
|           </ha-slider> | ||||
|           <p>Value : ${this.value} K</p> | ||||
|           <ha-slider | ||||
|             step="1" | ||||
|             pin | ||||
|             min=${this.min} | ||||
|             max=${this.max} | ||||
|             .value=${this.value} | ||||
|             @change=${this._valueChanged} | ||||
|           > | ||||
|           </ha-slider> | ||||
|         </div> | ||||
|       </ha-card> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-card { | ||||
|         max-width: 600px; | ||||
|         margin: 24px auto; | ||||
|       } | ||||
|       .card-content { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         flex-direction: column; | ||||
|       } | ||||
|       ha-temp-color-picker { | ||||
|         width: 400px; | ||||
|       } | ||||
|       .value { | ||||
|         font-size: 22px; | ||||
|         font-weight: bold; | ||||
|         margin: 0 0 12px 0; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-components-ha-temp-color-picker": DemoHaTempColorPicker; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								gallery/src/pages/date-time/date-time-numeric.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								gallery/src/pages/date-time/date-time-numeric.markdown
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| title: Date-Time Format (Numeric) | ||||
| --- | ||||
|  | ||||
| This pages lists all supported languages with their available date-time formats. | ||||
|  | ||||
| Formatting function: `const formatDateTimeNumeric: (dateObj: Date, locale: FrontendLocaleData) => string` | ||||
							
								
								
									
										136
									
								
								gallery/src/pages/date-time/date-time-numeric.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								gallery/src/pages/date-time/date-time-numeric.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| import { html, css, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-select"; | ||||
| import { translationMetadata } from "../../../../src/resources/translations-metadata"; | ||||
| import { formatDateTimeNumeric } from "../../../../src/common/datetime/format_date_time"; | ||||
| import { timeOptions } from "../../data/date-options"; | ||||
| import { demoConfig } from "../../../../src/fake_data/demo_config"; | ||||
| import { | ||||
|   FrontendLocaleData, | ||||
|   NumberFormat, | ||||
|   TimeFormat, | ||||
|   DateFormat, | ||||
|   FirstWeekday, | ||||
|   TimeZone, | ||||
| } from "../../../../src/data/translation"; | ||||
|  | ||||
| @customElement("demo-date-time-date-time-numeric") | ||||
| export class DemoDateTimeDateTimeNumeric extends LitElement { | ||||
|   @state() private selection?: string = "now"; | ||||
|  | ||||
|   @state() private date: Date = new Date(); | ||||
|  | ||||
|   handleValueChanged(e: CustomEvent) { | ||||
|     this.selection = e.detail.value as string; | ||||
|     this.date = new Date(); | ||||
|     if (this.selection !== "now") { | ||||
|       const [hours, minutes, seconds] = this.selection.split(":").map(Number); | ||||
|       this.date.setHours(hours); | ||||
|       this.date.setMinutes(minutes); | ||||
|       this.date.setSeconds(seconds); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     const defaultLocale: FrontendLocaleData = { | ||||
|       language: "en", | ||||
|       number_format: NumberFormat.language, | ||||
|       time_format: TimeFormat.language, | ||||
|       date_format: DateFormat.language, | ||||
|       first_weekday: FirstWeekday.language, | ||||
|       time_zone: TimeZone.local, | ||||
|     }; | ||||
|     return html` | ||||
|       <ha-control-select | ||||
|         .value=${this.selection} | ||||
|         .options=${timeOptions} | ||||
|         @value-changed=${this.handleValueChanged} | ||||
|       > | ||||
|       </ha-control-select> | ||||
|       <mwc-list> | ||||
|         <div class="container header"> | ||||
|           <div>Language</div> | ||||
|           <div class="center">Default (lang)</div> | ||||
|           <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> | ||||
|               </div> | ||||
|             ` | ||||
|           )} | ||||
|       </mwc-list> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-control-select { | ||||
|         max-width: 800px; | ||||
|         margin: 12px auto; | ||||
|       } | ||||
|       .header { | ||||
|         font-weight: bold; | ||||
|       } | ||||
|       .center { | ||||
|         text-align: center; | ||||
|       } | ||||
|       .container { | ||||
|         max-width: 900px; | ||||
|         margin: 12px auto; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-evenly; | ||||
|       } | ||||
|  | ||||
|       .container > div { | ||||
|         flex-grow: 1; | ||||
|         width: 20%; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-date-time-date-time-numeric": DemoDateTimeDateTimeNumeric; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								gallery/src/pages/date-time/date-time-seconds.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								gallery/src/pages/date-time/date-time-seconds.markdown
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| title: Date-Time Format (Seconds) | ||||
| --- | ||||
|  | ||||
| This pages lists all supported languages with their available date-time formats. | ||||
|  | ||||
| Formatting function: `const formatDateTimeWithSeconds: (dateObj: Date, locale: FrontendLocaleData) => string` | ||||
							
								
								
									
										136
									
								
								gallery/src/pages/date-time/date-time-seconds.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								gallery/src/pages/date-time/date-time-seconds.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| import { html, css, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-select"; | ||||
| import { translationMetadata } from "../../../../src/resources/translations-metadata"; | ||||
| import { formatDateTimeWithSeconds } from "../../../../src/common/datetime/format_date_time"; | ||||
| import { timeOptions } from "../../data/date-options"; | ||||
| import { demoConfig } from "../../../../src/fake_data/demo_config"; | ||||
| import { | ||||
|   FrontendLocaleData, | ||||
|   NumberFormat, | ||||
|   TimeFormat, | ||||
|   DateFormat, | ||||
|   FirstWeekday, | ||||
|   TimeZone, | ||||
| } from "../../../../src/data/translation"; | ||||
|  | ||||
| @customElement("demo-date-time-date-time-seconds") | ||||
| export class DemoDateTimeDateTimeSeconds extends LitElement { | ||||
|   @state() private selection?: string = "now"; | ||||
|  | ||||
|   @state() private date: Date = new Date(); | ||||
|  | ||||
|   handleValueChanged(e: CustomEvent) { | ||||
|     this.selection = e.detail.value as string; | ||||
|     this.date = new Date(); | ||||
|     if (this.selection !== "now") { | ||||
|       const [hours, minutes, seconds] = this.selection.split(":").map(Number); | ||||
|       this.date.setHours(hours); | ||||
|       this.date.setMinutes(minutes); | ||||
|       this.date.setSeconds(seconds); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     const defaultLocale: FrontendLocaleData = { | ||||
|       language: "en", | ||||
|       number_format: NumberFormat.language, | ||||
|       time_format: TimeFormat.language, | ||||
|       date_format: DateFormat.language, | ||||
|       first_weekday: FirstWeekday.language, | ||||
|       time_zone: TimeZone.local, | ||||
|     }; | ||||
|     return html` | ||||
|       <ha-control-select | ||||
|         .value=${this.selection} | ||||
|         .options=${timeOptions} | ||||
|         @value-changed=${this.handleValueChanged} | ||||
|       > | ||||
|       </ha-control-select> | ||||
|       <mwc-list> | ||||
|         <div class="container header"> | ||||
|           <div>Language</div> | ||||
|           <div class="center">Default (lang)</div> | ||||
|           <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> | ||||
|               </div> | ||||
|             ` | ||||
|           )} | ||||
|       </mwc-list> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-control-select { | ||||
|         max-width: 800px; | ||||
|         margin: 12px auto; | ||||
|       } | ||||
|       .header { | ||||
|         font-weight: bold; | ||||
|       } | ||||
|       .center { | ||||
|         text-align: center; | ||||
|       } | ||||
|       .container { | ||||
|         max-width: 900px; | ||||
|         margin: 12px auto; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-evenly; | ||||
|       } | ||||
|  | ||||
|       .container > div { | ||||
|         flex-grow: 1; | ||||
|         width: 20%; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-date-time-date-time-seconds": DemoDateTimeDateTimeSeconds; | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| title: Date-Time Format (Short w/ Year) | ||||
| --- | ||||
|  | ||||
| This pages lists all supported languages with their available date-time formats. | ||||
|  | ||||
| Formatting function: `const formatShortDateTimeWithYear: (dateObj: Date, locale: FrontendLocaleData) => string` | ||||
							
								
								
									
										136
									
								
								gallery/src/pages/date-time/date-time-short-year.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								gallery/src/pages/date-time/date-time-short-year.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| import { html, css, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-select"; | ||||
| import { translationMetadata } from "../../../../src/resources/translations-metadata"; | ||||
| import { formatShortDateTimeWithYear } from "../../../../src/common/datetime/format_date_time"; | ||||
| import { timeOptions } from "../../data/date-options"; | ||||
| import { demoConfig } from "../../../../src/fake_data/demo_config"; | ||||
| import { | ||||
|   FrontendLocaleData, | ||||
|   NumberFormat, | ||||
|   TimeFormat, | ||||
|   DateFormat, | ||||
|   FirstWeekday, | ||||
|   TimeZone, | ||||
| } from "../../../../src/data/translation"; | ||||
|  | ||||
| @customElement("demo-date-time-date-time-short-year") | ||||
| export class DemoDateTimeDateTimeShortYear extends LitElement { | ||||
|   @state() private selection?: string = "now"; | ||||
|  | ||||
|   @state() private date: Date = new Date(); | ||||
|  | ||||
|   handleValueChanged(e: CustomEvent) { | ||||
|     this.selection = e.detail.value as string; | ||||
|     this.date = new Date(); | ||||
|     if (this.selection !== "now") { | ||||
|       const [hours, minutes, seconds] = this.selection.split(":").map(Number); | ||||
|       this.date.setHours(hours); | ||||
|       this.date.setMinutes(minutes); | ||||
|       this.date.setSeconds(seconds); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     const defaultLocale: FrontendLocaleData = { | ||||
|       language: "en", | ||||
|       number_format: NumberFormat.language, | ||||
|       time_format: TimeFormat.language, | ||||
|       date_format: DateFormat.language, | ||||
|       first_weekday: FirstWeekday.language, | ||||
|       time_zone: TimeZone.local, | ||||
|     }; | ||||
|     return html` | ||||
|       <ha-control-select | ||||
|         .value=${this.selection} | ||||
|         .options=${timeOptions} | ||||
|         @value-changed=${this.handleValueChanged} | ||||
|       > | ||||
|       </ha-control-select> | ||||
|       <mwc-list> | ||||
|         <div class="container header"> | ||||
|           <div>Language</div> | ||||
|           <div class="center">Default (lang)</div> | ||||
|           <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> | ||||
|               </div> | ||||
|             ` | ||||
|           )} | ||||
|       </mwc-list> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-control-select { | ||||
|         max-width: 800px; | ||||
|         margin: 12px auto; | ||||
|       } | ||||
|       .header { | ||||
|         font-weight: bold; | ||||
|       } | ||||
|       .center { | ||||
|         text-align: center; | ||||
|       } | ||||
|       .container { | ||||
|         max-width: 900px; | ||||
|         margin: 12px auto; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-evenly; | ||||
|       } | ||||
|  | ||||
|       .container > div { | ||||
|         flex-grow: 1; | ||||
|         width: 20%; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-date-time-date-time-short-year": DemoDateTimeDateTimeShortYear; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								gallery/src/pages/date-time/date-time-short.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								gallery/src/pages/date-time/date-time-short.markdown
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| title: Date-Time Format (Short) | ||||
| --- | ||||
|  | ||||
| This pages lists all supported languages with their available date-time formats. | ||||
|  | ||||
| Formatting function: `const formatShortDateTime: (dateObj: Date, locale: FrontendLocaleData) => string` | ||||
							
								
								
									
										136
									
								
								gallery/src/pages/date-time/date-time-short.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								gallery/src/pages/date-time/date-time-short.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| import { html, css, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-select"; | ||||
| import { translationMetadata } from "../../../../src/resources/translations-metadata"; | ||||
| import { formatShortDateTime } from "../../../../src/common/datetime/format_date_time"; | ||||
| import { timeOptions } from "../../data/date-options"; | ||||
| import { demoConfig } from "../../../../src/fake_data/demo_config"; | ||||
| import { | ||||
|   FrontendLocaleData, | ||||
|   NumberFormat, | ||||
|   TimeFormat, | ||||
|   DateFormat, | ||||
|   FirstWeekday, | ||||
|   TimeZone, | ||||
| } from "../../../../src/data/translation"; | ||||
|  | ||||
| @customElement("demo-date-time-date-time-short") | ||||
| export class DemoDateTimeDateTimeShort extends LitElement { | ||||
|   @state() private selection?: string = "now"; | ||||
|  | ||||
|   @state() private date: Date = new Date(); | ||||
|  | ||||
|   handleValueChanged(e: CustomEvent) { | ||||
|     this.selection = e.detail.value as string; | ||||
|     this.date = new Date(); | ||||
|     if (this.selection !== "now") { | ||||
|       const [hours, minutes, seconds] = this.selection.split(":").map(Number); | ||||
|       this.date.setHours(hours); | ||||
|       this.date.setMinutes(minutes); | ||||
|       this.date.setSeconds(seconds); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     const defaultLocale: FrontendLocaleData = { | ||||
|       language: "en", | ||||
|       number_format: NumberFormat.language, | ||||
|       time_format: TimeFormat.language, | ||||
|       date_format: DateFormat.language, | ||||
|       first_weekday: FirstWeekday.language, | ||||
|       time_zone: TimeZone.local, | ||||
|     }; | ||||
|     return html` | ||||
|       <ha-control-select | ||||
|         .value=${this.selection} | ||||
|         .options=${timeOptions} | ||||
|         @value-changed=${this.handleValueChanged} | ||||
|       > | ||||
|       </ha-control-select> | ||||
|       <mwc-list> | ||||
|         <div class="container header"> | ||||
|           <div>Language</div> | ||||
|           <div class="center">Default (lang)</div> | ||||
|           <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> | ||||
|               </div> | ||||
|             ` | ||||
|           )} | ||||
|       </mwc-list> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-control-select { | ||||
|         max-width: 800px; | ||||
|         margin: 12px auto; | ||||
|       } | ||||
|       .header { | ||||
|         font-weight: bold; | ||||
|       } | ||||
|       .center { | ||||
|         text-align: center; | ||||
|       } | ||||
|       .container { | ||||
|         max-width: 900px; | ||||
|         margin: 12px auto; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-evenly; | ||||
|       } | ||||
|  | ||||
|       .container > div { | ||||
|         flex-grow: 1; | ||||
|         width: 20%; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-date-time-date-time-short": DemoDateTimeDateTimeShort; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								gallery/src/pages/date-time/date-time.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								gallery/src/pages/date-time/date-time.markdown
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| title: Date-Time Format | ||||
| --- | ||||
|  | ||||
| This pages lists all supported languages with their available date-time formats. | ||||
|  | ||||
| Formatting function: `const formatDateTime: (dateObj: Date, locale: FrontendLocaleData) => string` | ||||
							
								
								
									
										136
									
								
								gallery/src/pages/date-time/date-time.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								gallery/src/pages/date-time/date-time.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| import { html, css, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-select"; | ||||
| import { translationMetadata } from "../../../../src/resources/translations-metadata"; | ||||
| import { formatDateTime } from "../../../../src/common/datetime/format_date_time"; | ||||
| import { timeOptions } from "../../data/date-options"; | ||||
| import { demoConfig } from "../../../../src/fake_data/demo_config"; | ||||
| import { | ||||
|   FrontendLocaleData, | ||||
|   NumberFormat, | ||||
|   TimeFormat, | ||||
|   DateFormat, | ||||
|   FirstWeekday, | ||||
|   TimeZone, | ||||
| } from "../../../../src/data/translation"; | ||||
|  | ||||
| @customElement("demo-date-time-date-time") | ||||
| export class DemoDateTimeDateTime extends LitElement { | ||||
|   @state() private selection?: string = "now"; | ||||
|  | ||||
|   @state() private date: Date = new Date(); | ||||
|  | ||||
|   handleValueChanged(e: CustomEvent) { | ||||
|     this.selection = e.detail.value as string; | ||||
|     this.date = new Date(); | ||||
|     if (this.selection !== "now") { | ||||
|       const [hours, minutes, seconds] = this.selection.split(":").map(Number); | ||||
|       this.date.setHours(hours); | ||||
|       this.date.setMinutes(minutes); | ||||
|       this.date.setSeconds(seconds); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     const defaultLocale: FrontendLocaleData = { | ||||
|       language: "en", | ||||
|       number_format: NumberFormat.language, | ||||
|       time_format: TimeFormat.language, | ||||
|       date_format: DateFormat.language, | ||||
|       first_weekday: FirstWeekday.language, | ||||
|       time_zone: TimeZone.local, | ||||
|     }; | ||||
|     return html` | ||||
|       <ha-control-select | ||||
|         .value=${this.selection} | ||||
|         .options=${timeOptions} | ||||
|         @value-changed=${this.handleValueChanged} | ||||
|       > | ||||
|       </ha-control-select> | ||||
|       <mwc-list> | ||||
|         <div class="container header"> | ||||
|           <div>Language</div> | ||||
|           <div class="center">Default (lang)</div> | ||||
|           <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> | ||||
|               </div> | ||||
|             ` | ||||
|           )} | ||||
|       </mwc-list> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-control-select { | ||||
|         max-width: 800px; | ||||
|         margin: 12px auto; | ||||
|       } | ||||
|       .header { | ||||
|         font-weight: bold; | ||||
|       } | ||||
|       .center { | ||||
|         text-align: center; | ||||
|       } | ||||
|       .container { | ||||
|         max-width: 900px; | ||||
|         margin: 12px auto; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-evenly; | ||||
|       } | ||||
|  | ||||
|       .container > div { | ||||
|         flex-grow: 1; | ||||
|         width: 20%; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-date-time-date-time": DemoDateTimeDateTime; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								gallery/src/pages/date-time/date.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								gallery/src/pages/date-time/date.markdown
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| title: Date Format (Numeric) | ||||
| --- | ||||
|  | ||||
| This pages lists all supported languages with their available (numeric) date formats. | ||||
|  | ||||
| Formatting function: `const formatDateNumeric: (dateObj: Date, locale: FrontendLocaleData) => string` | ||||
							
								
								
									
										123
									
								
								gallery/src/pages/date-time/date.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								gallery/src/pages/date-time/date.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| import "@material/mwc-list/mwc-list"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement } from "lit/decorators"; | ||||
| import { formatDateNumeric } from "../../../../src/common/datetime/format_date"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import { | ||||
|   DateFormat, | ||||
|   FirstWeekday, | ||||
|   FrontendLocaleData, | ||||
|   NumberFormat, | ||||
|   TimeFormat, | ||||
|   TimeZone, | ||||
| } from "../../../../src/data/translation"; | ||||
| import { demoConfig } from "../../../../src/fake_data/demo_config"; | ||||
| import { translationMetadata } from "../../../../src/resources/translations-metadata"; | ||||
|  | ||||
| @customElement("demo-date-time-date") | ||||
| export class DemoDateTimeDate extends LitElement { | ||||
|   protected render() { | ||||
|     const defaultLocale: FrontendLocaleData = { | ||||
|       language: "en", | ||||
|       number_format: NumberFormat.language, | ||||
|       time_format: TimeFormat.language, | ||||
|       date_format: DateFormat.language, | ||||
|       time_zone: TimeZone.local, | ||||
|       first_weekday: FirstWeekday.language, | ||||
|     }; | ||||
|     const date = new Date(); | ||||
|     return html` | ||||
|       <mwc-list> | ||||
|         <div class="container header"> | ||||
|           <div>Language</div> | ||||
|           <div class="center">Default (lang)</div> | ||||
|           <div class="center">Day-Month-Year</div> | ||||
|           <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> | ||||
|               </div> | ||||
|             ` | ||||
|           )} | ||||
|       </mwc-list> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       .header { | ||||
|         font-weight: bold; | ||||
|       } | ||||
|       .center { | ||||
|         text-align: center; | ||||
|       } | ||||
|       .container { | ||||
|         max-width: 600px; | ||||
|         margin: 12px auto; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-evenly; | ||||
|       } | ||||
|  | ||||
|       .container > div { | ||||
|         flex-grow: 1; | ||||
|         width: 20%; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-date-time-date": DemoDateTimeDate; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								gallery/src/pages/date-time/time-seconds.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								gallery/src/pages/date-time/time-seconds.markdown
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| title: Time Format (Seconds) | ||||
| --- | ||||
|  | ||||
| This pages lists all supported languages with their available time formats. | ||||
|  | ||||
| Formatting function: `const formatTimeWithSeconds: (dateObj: Date, locale: FrontendLocaleData) => string` | ||||
							
								
								
									
										135
									
								
								gallery/src/pages/date-time/time-seconds.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								gallery/src/pages/date-time/time-seconds.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| import { html, css, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import { translationMetadata } from "../../../../src/resources/translations-metadata"; | ||||
| import { formatTimeWithSeconds } from "../../../../src/common/datetime/format_time"; | ||||
| import { timeOptions } from "../../data/date-options"; | ||||
| import { demoConfig } from "../../../../src/fake_data/demo_config"; | ||||
| import { | ||||
|   FrontendLocaleData, | ||||
|   NumberFormat, | ||||
|   TimeFormat, | ||||
|   DateFormat, | ||||
|   FirstWeekday, | ||||
|   TimeZone, | ||||
| } from "../../../../src/data/translation"; | ||||
|  | ||||
| @customElement("demo-date-time-time-seconds") | ||||
| export class DemoDateTimeTimeSeconds extends LitElement { | ||||
|   @state() private selection?: string = "now"; | ||||
|  | ||||
|   @state() private date: Date = new Date(); | ||||
|  | ||||
|   handleValueChanged(e: CustomEvent) { | ||||
|     this.selection = e.detail.value as string; | ||||
|     this.date = new Date(); | ||||
|     if (this.selection !== "now") { | ||||
|       const [hours, minutes, seconds] = this.selection.split(":").map(Number); | ||||
|       this.date.setHours(hours); | ||||
|       this.date.setMinutes(minutes); | ||||
|       this.date.setSeconds(seconds); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     const defaultLocale: FrontendLocaleData = { | ||||
|       language: "en", | ||||
|       number_format: NumberFormat.language, | ||||
|       time_format: TimeFormat.language, | ||||
|       date_format: DateFormat.language, | ||||
|       first_weekday: FirstWeekday.language, | ||||
|       time_zone: TimeZone.local, | ||||
|     }; | ||||
|     return html` | ||||
|       <ha-control-select | ||||
|         .value=${this.selection} | ||||
|         .options=${timeOptions} | ||||
|         @value-changed=${this.handleValueChanged} | ||||
|       > | ||||
|       </ha-control-select> | ||||
|       <mwc-list> | ||||
|         <div class="container header"> | ||||
|           <div>Language</div> | ||||
|           <div class="center">Default (lang)</div> | ||||
|           <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> | ||||
|               </div> | ||||
|             ` | ||||
|           )} | ||||
|       </mwc-list> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-control-select { | ||||
|         max-width: 800px; | ||||
|         margin: 12px auto; | ||||
|       } | ||||
|       .header { | ||||
|         font-weight: bold; | ||||
|       } | ||||
|       .center { | ||||
|         text-align: center; | ||||
|       } | ||||
|       .container { | ||||
|         max-width: 600px; | ||||
|         margin: 12px auto; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-evenly; | ||||
|       } | ||||
|  | ||||
|       .container > div { | ||||
|         flex-grow: 1; | ||||
|         width: 20%; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-date-time-time-seconds": DemoDateTimeTimeSeconds; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								gallery/src/pages/date-time/time-weekday.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								gallery/src/pages/date-time/time-weekday.markdown
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| title: Time Format (Weekday) | ||||
| --- | ||||
|  | ||||
| This pages lists all supported languages with their available time formats. | ||||
|  | ||||
| Formatting function: `const formatTimeWeekday: (dateObj: Date, locale: FrontendLocaleData) => string` | ||||
							
								
								
									
										135
									
								
								gallery/src/pages/date-time/time-weekday.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								gallery/src/pages/date-time/time-weekday.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| import { html, css, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import { translationMetadata } from "../../../../src/resources/translations-metadata"; | ||||
| import { formatTimeWeekday } from "../../../../src/common/datetime/format_time"; | ||||
| import { timeOptions } from "../../data/date-options"; | ||||
| import { demoConfig } from "../../../../src/fake_data/demo_config"; | ||||
| import { | ||||
|   FrontendLocaleData, | ||||
|   NumberFormat, | ||||
|   TimeFormat, | ||||
|   DateFormat, | ||||
|   FirstWeekday, | ||||
|   TimeZone, | ||||
| } from "../../../../src/data/translation"; | ||||
|  | ||||
| @customElement("demo-date-time-time-weekday") | ||||
| export class DemoDateTimeTimeWeekday extends LitElement { | ||||
|   @state() private selection?: string = "now"; | ||||
|  | ||||
|   @state() private date: Date = new Date(); | ||||
|  | ||||
|   handleValueChanged(e: CustomEvent) { | ||||
|     this.selection = e.detail.value as string; | ||||
|     this.date = new Date(); | ||||
|     if (this.selection !== "now") { | ||||
|       const [hours, minutes, seconds] = this.selection.split(":").map(Number); | ||||
|       this.date.setHours(hours); | ||||
|       this.date.setMinutes(minutes); | ||||
|       this.date.setSeconds(seconds); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     const defaultLocale: FrontendLocaleData = { | ||||
|       language: "en", | ||||
|       number_format: NumberFormat.language, | ||||
|       time_format: TimeFormat.language, | ||||
|       date_format: DateFormat.language, | ||||
|       first_weekday: FirstWeekday.language, | ||||
|       time_zone: TimeZone.local, | ||||
|     }; | ||||
|     return html` | ||||
|       <ha-control-select | ||||
|         .value=${this.selection} | ||||
|         .options=${timeOptions} | ||||
|         @value-changed=${this.handleValueChanged} | ||||
|       > | ||||
|       </ha-control-select> | ||||
|       <mwc-list> | ||||
|         <div class="container header"> | ||||
|           <div>Language</div> | ||||
|           <div class="center">Default (lang)</div> | ||||
|           <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> | ||||
|               </div> | ||||
|             ` | ||||
|           )} | ||||
|       </mwc-list> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-control-select { | ||||
|         max-width: 800px; | ||||
|         margin: 12px auto; | ||||
|       } | ||||
|       .header { | ||||
|         font-weight: bold; | ||||
|       } | ||||
|       .center { | ||||
|         text-align: center; | ||||
|       } | ||||
|       .container { | ||||
|         max-width: 800px; | ||||
|         margin: 12px auto; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-evenly; | ||||
|       } | ||||
|  | ||||
|       .container > div { | ||||
|         flex-grow: 1; | ||||
|         width: 20%; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-date-time-time-weekday": DemoDateTimeTimeWeekday; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										7
									
								
								gallery/src/pages/date-time/time.markdown
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								gallery/src/pages/date-time/time.markdown
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| --- | ||||
| title: Time Format | ||||
| --- | ||||
|  | ||||
| This pages lists all supported languages with their available time formats. | ||||
|  | ||||
| Formatting function: `const formatTime: (dateObj: Date, locale: FrontendLocaleData) => string` | ||||
							
								
								
									
										136
									
								
								gallery/src/pages/date-time/time.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								gallery/src/pages/date-time/time.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| import { html, css, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-select"; | ||||
| import { translationMetadata } from "../../../../src/resources/translations-metadata"; | ||||
| import { formatTime } from "../../../../src/common/datetime/format_time"; | ||||
| import { timeOptions } from "../../data/date-options"; | ||||
| import { demoConfig } from "../../../../src/fake_data/demo_config"; | ||||
| import { | ||||
|   FrontendLocaleData, | ||||
|   NumberFormat, | ||||
|   TimeFormat, | ||||
|   DateFormat, | ||||
|   FirstWeekday, | ||||
|   TimeZone, | ||||
| } from "../../../../src/data/translation"; | ||||
|  | ||||
| @customElement("demo-date-time-time") | ||||
| export class DemoDateTimeTime extends LitElement { | ||||
|   @state() private selection?: string = "now"; | ||||
|  | ||||
|   @state() private date: Date = new Date(); | ||||
|  | ||||
|   handleValueChanged(e: CustomEvent) { | ||||
|     this.selection = e.detail.value as string; | ||||
|     this.date = new Date(); | ||||
|     if (this.selection !== "now") { | ||||
|       const [hours, minutes, seconds] = this.selection.split(":").map(Number); | ||||
|       this.date.setHours(hours); | ||||
|       this.date.setMinutes(minutes); | ||||
|       this.date.setSeconds(seconds); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     const defaultLocale: FrontendLocaleData = { | ||||
|       language: "en", | ||||
|       number_format: NumberFormat.language, | ||||
|       time_format: TimeFormat.language, | ||||
|       date_format: DateFormat.language, | ||||
|       first_weekday: FirstWeekday.language, | ||||
|       time_zone: TimeZone.local, | ||||
|     }; | ||||
|     return html` | ||||
|       <ha-control-select | ||||
|         .value=${this.selection} | ||||
|         .options=${timeOptions} | ||||
|         @value-changed=${this.handleValueChanged} | ||||
|       > | ||||
|       </ha-control-select> | ||||
|       <mwc-list> | ||||
|         <div class="container header"> | ||||
|           <div>Language</div> | ||||
|           <div class="center">Default (lang)</div> | ||||
|           <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> | ||||
|               </div> | ||||
|             ` | ||||
|           )} | ||||
|       </mwc-list> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       ha-control-select { | ||||
|         max-width: 800px; | ||||
|         margin: 12px auto; | ||||
|       } | ||||
|       .header { | ||||
|         font-weight: bold; | ||||
|       } | ||||
|       .center { | ||||
|         text-align: center; | ||||
|       } | ||||
|       .container { | ||||
|         max-width: 600px; | ||||
|         margin: 12px auto; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-evenly; | ||||
|       } | ||||
|  | ||||
|       .container > div { | ||||
|         flex-grow: 1; | ||||
|         width: 20%; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-date-time-time": DemoDateTimeTime; | ||||
|   } | ||||
| } | ||||
| @@ -9,7 +9,7 @@ const CONFIGS = [ | ||||
|     heading: "markdown-it demo", | ||||
|     config: ` | ||||
| - type: markdown | ||||
|   content: >- | ||||
|   content: | | ||||
|     # h1 Heading 8-) | ||||
|  | ||||
|     ## h2 Heading | ||||
| @@ -65,6 +65,15 @@ const CONFIGS = [ | ||||
|     >> ...by using additional greater-than signs right next to each other... | ||||
|     > > > ...or with spaces between arrows. | ||||
|  | ||||
|     > **Warning** Hey there | ||||
|     > This is a warning with a title | ||||
|  | ||||
|     > **Note** | ||||
|     > This is a note | ||||
|  | ||||
|     > **Note** | ||||
|     > This is a multiline note | ||||
|     > Lorem ipsum... | ||||
|  | ||||
|     ## Lists | ||||
|  | ||||
|   | ||||
| @@ -50,6 +50,7 @@ const SENSOR_DEVICE_CLASSES = [ | ||||
|   "temperature", | ||||
|   "timestamp", | ||||
|   "volatile_organic_compounds", | ||||
|   "volatile_organic_compounds_parts", | ||||
|   "voltage", | ||||
|   "volume", | ||||
|   "water", | ||||
| @@ -134,6 +135,9 @@ const ENTITIES: HassEntity[] = [ | ||||
|   createEntity("climate.fan_only", "fan_only"), | ||||
|   createEntity("climate.auto_idle", "auto", undefined, { hvac_action: "idle" }), | ||||
|   createEntity("climate.auto_off", "auto", undefined, { hvac_action: "off" }), | ||||
|   createEntity("climate.auto_preheating", "auto", undefined, { | ||||
|     hvac_action: "preheating", | ||||
|   }), | ||||
|   createEntity("climate.auto_heating", "auto", undefined, { | ||||
|     hvac_action: "heating", | ||||
|   }), | ||||
| @@ -353,6 +357,7 @@ export class DemoEntityState extends LitElement { | ||||
|               hass.localize, | ||||
|               entry.stateObj, | ||||
|               hass.locale, | ||||
|               hass.config, | ||||
|               hass.entities | ||||
|             )}`, | ||||
|         }, | ||||
|   | ||||
| @@ -130,6 +130,7 @@ export class HassioAddonRepositoryEl extends LitElement { | ||||
|       css` | ||||
|         ha-card { | ||||
|           cursor: pointer; | ||||
|           overflow: hidden; | ||||
|         } | ||||
|         .not_available { | ||||
|           opacity: 0.6; | ||||
|   | ||||
| @@ -70,7 +70,7 @@ export class HassioAddonStore extends LitElement { | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|     } finally { | ||||
|       await this._loadData(); | ||||
|       this._loadData(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -92,21 +92,17 @@ export class HassioAddonStore extends LitElement { | ||||
|         .route=${this.route} | ||||
|         .header=${this.supervisor.localize("panel.store")} | ||||
|       > | ||||
|         <ha-button-menu | ||||
|           corner="BOTTOM_START" | ||||
|           slot="toolbar-icon" | ||||
|           @action=${this._handleAction} | ||||
|         > | ||||
|         <ha-button-menu slot="toolbar-icon" @action=${this._handleAction}> | ||||
|           <ha-icon-button | ||||
|             .label=${this.supervisor.localize("common.menu")} | ||||
|             .path=${mdiDotsVertical} | ||||
|             slot="trigger" | ||||
|           ></ha-icon-button> | ||||
|           <mwc-list-item> | ||||
|             ${this.supervisor.localize("store.repositories")} | ||||
|             ${this.supervisor.localize("store.check_updates")} | ||||
|           </mwc-list-item> | ||||
|           <mwc-list-item> | ||||
|             ${this.supervisor.localize("store.check_updates")} | ||||
|             ${this.supervisor.localize("store.repositories")} | ||||
|           </mwc-list-item> | ||||
|           ${this.hass.userData?.showAdvanced && | ||||
|           atLeastVersion(this.hass.config.version, 0, 117) | ||||
| @@ -181,10 +177,10 @@ export class HassioAddonStore extends LitElement { | ||||
|   private _handleAction(ev: CustomEvent<ActionDetail>) { | ||||
|     switch (ev.detail.index) { | ||||
|       case 0: | ||||
|         this._manageRepositoriesClicked(); | ||||
|         this.refreshData(); | ||||
|         break; | ||||
|       case 1: | ||||
|         this.refreshData(); | ||||
|         this._manageRepositoriesClicked(); | ||||
|         break; | ||||
|       case 2: | ||||
|         this._manageRegistries(); | ||||
| @@ -202,25 +198,25 @@ export class HassioAddonStore extends LitElement { | ||||
|     this._manageRepositories(); | ||||
|   } | ||||
|  | ||||
|   private async _manageRepositories(url?: string) { | ||||
|   private _manageRepositories(url?: string) { | ||||
|     showRepositoriesDialog(this, { | ||||
|       supervisor: this.supervisor, | ||||
|       url, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   private async _manageRegistries() { | ||||
|   private _manageRegistries() { | ||||
|     showRegistriesDialog(this, { supervisor: this.supervisor }); | ||||
|   } | ||||
|  | ||||
|   private async _loadData() { | ||||
|   private _loadData() { | ||||
|     fireEvent(this, "supervisor-collection-refresh", { collection: "addon" }); | ||||
|     fireEvent(this, "supervisor-collection-refresh", { | ||||
|       collection: "supervisor", | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   private async _filterChanged(e) { | ||||
|   private _filterChanged(e) { | ||||
|     this._filter = e.detail.value; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -140,7 +140,12 @@ class HassioAddonConfig extends LitElement { | ||||
|           ? { | ||||
|               name: entry.name, | ||||
|               required: entry.required, | ||||
|               selector: { number: { mode: "box" } }, | ||||
|               selector: { | ||||
|                 number: { | ||||
|                   mode: "box", | ||||
|                   step: entry.type === "float" ? "any" : undefined, | ||||
|                 }, | ||||
|               }, | ||||
|             } | ||||
|           : entry | ||||
|       ) | ||||
| @@ -168,7 +173,7 @@ class HassioAddonConfig extends LitElement { | ||||
|             ${this.supervisor.localize("addon.configuration.options.header")} | ||||
|           </h2> | ||||
|           <div class="card-menu"> | ||||
|             <ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}> | ||||
|             <ha-button-menu @action=${this._handleAction}> | ||||
|               <ha-icon-button | ||||
|                 .label=${this.supervisor.localize("common.menu")} | ||||
|                 .path=${mdiDotsVertical} | ||||
|   | ||||
| @@ -44,7 +44,10 @@ class HassioAddonDocumentationDashboard extends LitElement { | ||||
|             : ""} | ||||
|           <div class="card-content"> | ||||
|             ${this._content | ||||
|               ? html`<ha-markdown .content=${this._content}></ha-markdown>` | ||||
|               ? html`<ha-markdown | ||||
|                   .content=${this._content} | ||||
|                   lazy-images | ||||
|                 ></ha-markdown>` | ||||
|               : html`<hass-loading-screen no-toolbar></hass-loading-screen>`} | ||||
|           </div> | ||||
|         </ha-card> | ||||
|   | ||||
| @@ -29,7 +29,6 @@ import memoizeOne from "memoize-one"; | ||||
| import { atLeastVersion } from "../../../../src/common/config/version"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import { navigate } from "../../../../src/common/navigate"; | ||||
| import "../../../../src/components/buttons/ha-call-api-button"; | ||||
| import "../../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../../src/components/ha-alert"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| @@ -47,6 +46,7 @@ import { | ||||
|   HassioAddonSetOptionParams, | ||||
|   HassioAddonSetSecurityParams, | ||||
|   installHassioAddon, | ||||
|   rebuildLocalAddon, | ||||
|   restartHassioAddon, | ||||
|   setHassioAddonOption, | ||||
|   setHassioAddonSecurity, | ||||
| @@ -640,13 +640,12 @@ class HassioAddonInfo extends LitElement { | ||||
|                   </ha-progress-button> | ||||
|                   ${this.addon.build | ||||
|                     ? html` | ||||
|                         <ha-call-api-button | ||||
|                         <ha-progress-button | ||||
|                           class="warning" | ||||
|                           .hass=${this.hass} | ||||
|                           .path="hassio/addons/${this.addon.slug}/rebuild" | ||||
|                           @click=${this._rebuildClicked} | ||||
|                         > | ||||
|                           ${this.supervisor.localize("addon.dashboard.rebuild")} | ||||
|                         </ha-call-api-button> | ||||
|                         </ha-progress-button> | ||||
|                       ` | ||||
|                     : ""}` | ||||
|               : ""} | ||||
| @@ -660,6 +659,7 @@ class HassioAddonInfo extends LitElement { | ||||
|               <div class="card-content"> | ||||
|                 <ha-markdown | ||||
|                   .content=${this.addon.long_description} | ||||
|                   lazy-images | ||||
|                 ></ha-markdown> | ||||
|               </div> | ||||
|             </ha-card> | ||||
| @@ -966,6 +966,21 @@ class HassioAddonInfo extends LitElement { | ||||
|     button.progress = false; | ||||
|   } | ||||
|  | ||||
|   private async _rebuildClicked(ev: CustomEvent): Promise<void> { | ||||
|     const button = ev.currentTarget as any; | ||||
|     button.progress = true; | ||||
|  | ||||
|     try { | ||||
|       await rebuildLocalAddon(this.hass, this.addon.slug); | ||||
|     } catch (err: any) { | ||||
|       showAlertDialog(this, { | ||||
|         title: this.supervisor.localize("addon.dashboard.action_error.rebuild"), | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|     } | ||||
|     button.progress = false; | ||||
|   } | ||||
|  | ||||
|   private async _startClicked(ev: CustomEvent): Promise<void> { | ||||
|     const button = ev.currentTarget as any; | ||||
|     button.progress = true; | ||||
| @@ -1124,10 +1139,6 @@ class HassioAddonInfo extends LitElement { | ||||
|         ha-svg-icon.stopped { | ||||
|           color: var(--error-color); | ||||
|         } | ||||
|         ha-call-api-button { | ||||
|           font-weight: 500; | ||||
|           color: var(--primary-color); | ||||
|         } | ||||
|         protection-enable mwc-button { | ||||
|           --mdc-theme-primary: white; | ||||
|         } | ||||
|   | ||||
| @@ -3,11 +3,11 @@ import { ActionDetail } from "@material/mwc-list"; | ||||
| import "@material/mwc-list/mwc-list-item"; | ||||
| import { mdiBackupRestore, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js"; | ||||
| import { | ||||
|   css, | ||||
|   CSSResultGroup, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   css, | ||||
|   html, | ||||
|   nothing, | ||||
| } from "lit"; | ||||
| import { customElement, property, query, state } from "lit/decorators"; | ||||
| @@ -26,9 +26,9 @@ import "../../../src/components/ha-fab"; | ||||
| import "../../../src/components/ha-icon-button"; | ||||
| import "../../../src/components/ha-svg-icon"; | ||||
| import { | ||||
|   HassioBackup, | ||||
|   fetchHassioBackups, | ||||
|   friendlyFolderName, | ||||
|   HassioBackup, | ||||
|   reloadHassioBackups, | ||||
|   removeBackup, | ||||
| } from "../../../src/data/hassio/backup"; | ||||
| @@ -43,6 +43,7 @@ import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subp | ||||
| import { haStyle } from "../../../src/resources/styles"; | ||||
| import { HomeAssistant, Route } from "../../../src/types"; | ||||
| import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload"; | ||||
| import { showHassioBackupLocationDialog } from "../dialogs/backup/show-dialog-hassio-backu-location"; | ||||
| import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup"; | ||||
| import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup"; | ||||
| import { supervisorTabs } from "../hassio-tabs"; | ||||
| @@ -135,6 +136,15 @@ export class HassioBackups extends LitElement { | ||||
|         sortable: true, | ||||
|         template: (entry: number) => Math.ceil(entry * 10) / 10 + " MB", | ||||
|       }, | ||||
|       location: { | ||||
|         title: this.supervisor.localize("backup.location"), | ||||
|         width: "15%", | ||||
|         hidden: narrow, | ||||
|         filterable: true, | ||||
|         sortable: true, | ||||
|         template: (entry: string | null) => | ||||
|           entry || this.supervisor.localize("backup.data_disk"), | ||||
|       }, | ||||
|       date: { | ||||
|         title: this.supervisor.localize("backup.created"), | ||||
|         width: "15%", | ||||
| @@ -195,11 +205,7 @@ export class HassioBackups extends LitElement { | ||||
|           : "/config"} | ||||
|         supervisor | ||||
|       > | ||||
|         <ha-button-menu | ||||
|           corner="BOTTOM_START" | ||||
|           slot="toolbar-icon" | ||||
|           @action=${this._handleAction} | ||||
|         > | ||||
|         <ha-button-menu slot="toolbar-icon" @action=${this._handleAction}> | ||||
|           <ha-icon-button | ||||
|             .label=${this.supervisor?.localize("common.menu")} | ||||
|             .path=${mdiDotsVertical} | ||||
| @@ -208,6 +214,9 @@ export class HassioBackups extends LitElement { | ||||
|           <mwc-list-item> | ||||
|             ${this.supervisor.localize("common.reload")} | ||||
|           </mwc-list-item> | ||||
|           <mwc-list-item> | ||||
|             ${this.supervisor.localize("dialog.backup_location.title")} | ||||
|           </mwc-list-item> | ||||
|           ${atLeastVersion(this.hass.config.version, 0, 116) | ||||
|             ? html`<mwc-list-item> | ||||
|                 ${this.supervisor.localize("backup.upload_backup")} | ||||
| @@ -274,6 +283,9 @@ export class HassioBackups extends LitElement { | ||||
|         this.refreshData(); | ||||
|         break; | ||||
|       case 1: | ||||
|         showHassioBackupLocationDialog(this, { supervisor: this.supervisor }); | ||||
|         break; | ||||
|       case 2: | ||||
|         this._showUploadBackupDialog(); | ||||
|         break; | ||||
|     } | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js"; | ||||
| import { PaperInputElement } from "@polymer/paper-input/paper-input"; | ||||
| import "@polymer/paper-input/paper-input"; | ||||
| import type { PaperInputElement } from "@polymer/paper-input/paper-input"; | ||||
| import { | ||||
|   css, | ||||
|   CSSResultGroup, | ||||
|   html, | ||||
|   LitElement, | ||||
|   TemplateResult, | ||||
|   nothing, | ||||
|   TemplateResult, | ||||
| } from "lit"; | ||||
| import { customElement, property, query } from "lit/decorators"; | ||||
| import { atLeastVersion } from "../../../src/common/config/version"; | ||||
| @@ -23,8 +24,11 @@ import { | ||||
|   HassioPartialBackupCreateParams, | ||||
| } from "../../../src/data/hassio/backup"; | ||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||
| import { PolymerChangedEvent } from "../../../src/polymer-types"; | ||||
| import { HomeAssistant, TranslationDict } from "../../../src/types"; | ||||
| import { | ||||
|   HomeAssistant, | ||||
|   TranslationDict, | ||||
|   ValueChangedEvent, | ||||
| } from "../../../src/types"; | ||||
| import "./supervisor-formfield-label"; | ||||
|  | ||||
| type BackupOrRestoreKey = keyof TranslationDict["supervisor"]["backup"] & | ||||
| @@ -139,7 +143,11 @@ export class SupervisorBackupContent extends LitElement { | ||||
|               : this._localize("partial_backup")} | ||||
|             (${Math.ceil(this.backup.size * 10) / 10 + " MB"})<br /> | ||||
|             ${this.hass | ||||
|               ? formatDateTime(new Date(this.backup.date), this.hass.locale) | ||||
|               ? formatDateTime( | ||||
|                   new Date(this.backup.date), | ||||
|                   this.hass.locale, | ||||
|                   this.hass.config | ||||
|                 ) | ||||
|               : this.backup.date} | ||||
|           </div>` | ||||
|         : html`<paper-input | ||||
| @@ -332,7 +340,9 @@ export class SupervisorBackupContent extends LitElement { | ||||
|     const data: any = {}; | ||||
|  | ||||
|     if (!this.backup) { | ||||
|       data.name = this.backupName || formatDate(new Date(), this.hass.locale); | ||||
|       data.name = | ||||
|         this.backupName || | ||||
|         formatDate(new Date(), this.hass.locale, this.hass.config); | ||||
|     } | ||||
|  | ||||
|     if (this.backupHasPassword) { | ||||
| @@ -416,7 +426,7 @@ export class SupervisorBackupContent extends LitElement { | ||||
|     this[input.name] = input.value; | ||||
|   } | ||||
|  | ||||
|   private _handleTextValueChanged(ev: PolymerChangedEvent<string>) { | ||||
|   private _handleTextValueChanged(ev: ValueChangedEvent<string>) { | ||||
|     const input = ev.currentTarget as PaperInputElement; | ||||
|     this[input.name!] = ev.detail.value; | ||||
|   } | ||||
|   | ||||
| @@ -1,10 +1,13 @@ | ||||
| import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js"; | ||||
| import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { atLeastVersion } from "../../../src/common/config/version"; | ||||
| import { navigate } from "../../../src/common/navigate"; | ||||
| import { caseInsensitiveStringCompare } from "../../../src/common/string/compare"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import "../../../src/components/search-input"; | ||||
| import { HassioAddonInfo } from "../../../src/data/hassio/addon"; | ||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||
| import { haStyle } from "../../../src/resources/styles"; | ||||
| import { HomeAssistant } from "../../../src/types"; | ||||
| @@ -17,11 +20,25 @@ class HassioAddons extends LitElement { | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|  | ||||
|   @property({ type: Boolean }) public narrow!: boolean; | ||||
|  | ||||
|   @state() private _filter?: string; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
|       <div class="search"> | ||||
|         <search-input | ||||
|           .hass=${this.hass} | ||||
|           suffix | ||||
|           .filter=${this._filter} | ||||
|           @value-changed=${this._handleSearchChange} | ||||
|           .label=${this.supervisor.localize("dashboard.search_addons")} | ||||
|         > | ||||
|         </search-input> | ||||
|       </div> | ||||
|       <div class="content"> | ||||
|         ${!atLeastVersion(this.hass.config.version, 2021, 12) | ||||
|           ? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> ` | ||||
|           ? html`<h1>${this.supervisor.localize("dashboard.addons")}</h1>` | ||||
|           : ""} | ||||
|         <div class="card-group"> | ||||
|           ${!this.supervisor.addon.addons.length | ||||
| @@ -34,67 +51,73 @@ class HassioAddons extends LitElement { | ||||
|                   </div> | ||||
|                 </ha-card> | ||||
|               ` | ||||
|             : this.supervisor.addon.addons | ||||
|                 .sort((a, b) => | ||||
|                   caseInsensitiveStringCompare( | ||||
|                     a.name, | ||||
|                     b.name, | ||||
|                     this.hass.locale.language | ||||
|                   ) | ||||
|                 ) | ||||
|                 .map( | ||||
|                   (addon) => html` | ||||
|                     <ha-card | ||||
|                       outlined | ||||
|                       .addon=${addon} | ||||
|                       @click=${this._addonTapped} | ||||
|                     > | ||||
|                       <div class="card-content"> | ||||
|                         <hassio-card-content | ||||
|                           .hass=${this.hass} | ||||
|                           .title=${addon.name} | ||||
|                           .description=${addon.description} | ||||
|                           available | ||||
|                           .showTopbar=${addon.update_available} | ||||
|                           topbarClass="update" | ||||
|                           .icon=${addon.update_available! | ||||
|                             ? mdiArrowUpBoldCircle | ||||
|                             : mdiPuzzle} | ||||
|                           .iconTitle=${addon.state !== "started" | ||||
|                             ? this.supervisor.localize( | ||||
|                                 "dashboard.addon_stopped" | ||||
|                               ) | ||||
|                             : addon.update_available! | ||||
|                             ? this.supervisor.localize( | ||||
|                                 "dashboard.addon_new_version" | ||||
|                               ) | ||||
|                             : this.supervisor.localize( | ||||
|                                 "dashboard.addon_running" | ||||
|                               )} | ||||
|                           .iconClass=${addon.update_available | ||||
|                             ? addon.state === "started" | ||||
|                               ? "update" | ||||
|                               : "update stopped" | ||||
|                             : addon.state === "started" | ||||
|                             ? "running" | ||||
|                             : "stopped"} | ||||
|                           .iconImage=${atLeastVersion( | ||||
|                             this.hass.config.version, | ||||
|                             0, | ||||
|                             105 | ||||
|                           ) && addon.icon | ||||
|                             ? `/api/hassio/addons/${addon.slug}/icon` | ||||
|                             : undefined} | ||||
|                         ></hassio-card-content> | ||||
|                       </div> | ||||
|                     </ha-card> | ||||
|                   ` | ||||
|                 )} | ||||
|             : this._getAddons(this.supervisor.addon.addons, this._filter).map( | ||||
|                 (addon) => html` | ||||
|                   <ha-card outlined .addon=${addon} @click=${this._addonTapped}> | ||||
|                     <div class="card-content"> | ||||
|                       <hassio-card-content | ||||
|                         .hass=${this.hass} | ||||
|                         .title=${addon.name} | ||||
|                         .description=${addon.description} | ||||
|                         available | ||||
|                         .showTopbar=${addon.update_available} | ||||
|                         topbarClass="update" | ||||
|                         .icon=${addon.update_available! | ||||
|                           ? mdiArrowUpBoldCircle | ||||
|                           : mdiPuzzle} | ||||
|                         .iconTitle=${addon.state !== "started" | ||||
|                           ? this.supervisor.localize("dashboard.addon_stopped") | ||||
|                           : addon.update_available! | ||||
|                           ? this.supervisor.localize( | ||||
|                               "dashboard.addon_new_version" | ||||
|                             ) | ||||
|                           : this.supervisor.localize("dashboard.addon_running")} | ||||
|                         .iconClass=${addon.update_available | ||||
|                           ? addon.state === "started" | ||||
|                             ? "update" | ||||
|                             : "update stopped" | ||||
|                           : addon.state === "started" | ||||
|                           ? "running" | ||||
|                           : "stopped"} | ||||
|                         .iconImage=${atLeastVersion( | ||||
|                           this.hass.config.version, | ||||
|                           0, | ||||
|                           105 | ||||
|                         ) && addon.icon | ||||
|                           ? `/api/hassio/addons/${addon.slug}/icon` | ||||
|                           : undefined} | ||||
|                       ></hassio-card-content> | ||||
|                     </div> | ||||
|                   </ha-card> | ||||
|                 ` | ||||
|               )} | ||||
|         </div> | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   private _getAddons = memoizeOne( | ||||
|     (addons: HassioAddonInfo[], filter?: string) => { | ||||
|       if (filter) { | ||||
|         addons = addons.filter((addon) => { | ||||
|           const lowerCaseFilter = filter.toLowerCase(); | ||||
|           return ( | ||||
|             addon.name.toLowerCase().includes(lowerCaseFilter) || | ||||
|             addon.description.toLowerCase().includes(lowerCaseFilter) || | ||||
|             addon.slug.toLowerCase().includes(lowerCaseFilter) | ||||
|           ); | ||||
|         }); | ||||
|       } | ||||
|       return addons.sort((a, b) => | ||||
|         caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language) | ||||
|       ); | ||||
|     } | ||||
|   ); | ||||
|  | ||||
|   private _handleSearchChange(ev: CustomEvent) { | ||||
|     this._filter = ev.detail.value; | ||||
|   } | ||||
|  | ||||
|   static get styles(): CSSResultGroup { | ||||
|     return [ | ||||
|       haStyle, | ||||
| @@ -102,6 +125,17 @@ class HassioAddons extends LitElement { | ||||
|       css` | ||||
|         ha-card { | ||||
|           cursor: pointer; | ||||
|           overflow: hidden; | ||||
|         } | ||||
|         .search { | ||||
|           position: sticky; | ||||
|           top: 0; | ||||
|           z-index: 2; | ||||
|         } | ||||
|         search-input { | ||||
|           display: block; | ||||
|           --mdc-text-field-fill-color: var(--sidebar-background-color); | ||||
|           --mdc-text-field-idle-line-color: var(--divider-color); | ||||
|         } | ||||
|       `, | ||||
|     ]; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { mdiStorePlus } from "@mdi/js"; | ||||
| import { mdiStorePlus, mdiUpdate } from "@mdi/js"; | ||||
| import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import { atLeastVersion } from "../../../src/common/config/version"; | ||||
| @@ -9,8 +9,11 @@ import { haStyle } from "../../../src/resources/styles"; | ||||
| import { HomeAssistant, Route } from "../../../src/types"; | ||||
| import { supervisorTabs } from "../hassio-tabs"; | ||||
| import "./hassio-addons"; | ||||
| import "./hassio-update"; | ||||
| 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 { | ||||
| @@ -22,6 +25,12 @@ class HassioDashboard extends LitElement { | ||||
|  | ||||
|   @property({ attribute: false }) public route!: Route; | ||||
|  | ||||
|   firstUpdated() { | ||||
|     if (!atLeastVersion(this.hass.config.version, 2022, 5)) { | ||||
|       import("./hassio-update"); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     if (atLeastVersion(this.hass.config.version, 2022, 5)) { | ||||
|       return html`<hass-subpage | ||||
| @@ -31,9 +40,16 @@ class HassioDashboard extends LitElement { | ||||
|         back-path="/config" | ||||
|         .header=${this.supervisor.localize("panel.addons")} | ||||
|       > | ||||
|         <ha-icon-button | ||||
|           slot="toolbar-icon" | ||||
|           @click=${this._handleCheckUpdates} | ||||
|           .path=${mdiUpdate} | ||||
|           .label=${this.supervisor.localize("store.check_updates")} | ||||
|         ></ha-icon-button> | ||||
|         <hassio-addons | ||||
|           .hass=${this.hass} | ||||
|           .supervisor=${this.supervisor} | ||||
|           .narrow=${this.narrow} | ||||
|         ></hassio-addons> | ||||
|         <a href="/hassio/store"> | ||||
|           <ha-fab | ||||
| @@ -44,7 +60,7 @@ class HassioDashboard extends LitElement { | ||||
|             <ha-svg-icon | ||||
|               slot="icon" | ||||
|               .path=${mdiStorePlus} | ||||
|             ></ha-svg-icon> </ha-fab | ||||
|             ></ha-svg-icon></ha-fab | ||||
|         ></a> | ||||
|       </hass-subpage>`; | ||||
|     } | ||||
| @@ -94,6 +110,18 @@ class HassioDashboard extends LitElement { | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   private async _handleCheckUpdates() { | ||||
|     try { | ||||
|       await reloadHassioAddons(this.hass); | ||||
|     } catch (err) { | ||||
|       showAlertDialog(this, { | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|     } finally { | ||||
|       fireEvent(this, "supervisor-collection-refresh", { collection: "addon" }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   static get styles(): CSSResultGroup { | ||||
|     return [ | ||||
|       haStyle, | ||||
|   | ||||
							
								
								
									
										154
									
								
								hassio/src/dialogs/backup/dialog-hassio-backup-location.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								hassio/src/dialogs/backup/dialog-hassio-backup-location.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
| import "@material/mwc-button/mwc-button"; | ||||
| import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import "../../../../src/components/ha-dialog"; | ||||
| import "../../../../src/components/ha-form/ha-form"; | ||||
| import type { SchemaUnion } from "../../../../src/components/ha-form/types"; | ||||
| import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; | ||||
| import { changeMountOptions } from "../../../../src/data/supervisor/mounts"; | ||||
| import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; | ||||
| import { HomeAssistant } from "../../../../src/types"; | ||||
| import { HassioBackupLocationDialogParams } from "./show-dialog-hassio-backu-location"; | ||||
|  | ||||
| const SCHEMA = memoizeOne( | ||||
|   () => | ||||
|     [ | ||||
|       { | ||||
|         name: "default_backup_mount", | ||||
|         required: true, | ||||
|         selector: { backup_location: {} }, | ||||
|       }, | ||||
|     ] as const | ||||
| ); | ||||
|  | ||||
| @customElement("dialog-hassio-backup-location") | ||||
| class HassioBackupLocationDialog extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) | ||||
|   public _dialogParams?: HassioBackupLocationDialogParams; | ||||
|  | ||||
|   @state() private _data?: { default_backup_mount: string | null }; | ||||
|  | ||||
|   @state() private _waiting?: boolean; | ||||
|  | ||||
|   @state() private _error?: string; | ||||
|  | ||||
|   public async showDialog( | ||||
|     dialogParams: HassioBackupLocationDialogParams | ||||
|   ): Promise<void> { | ||||
|     this._dialogParams = dialogParams; | ||||
|   } | ||||
|  | ||||
|   public closeDialog(): void { | ||||
|     this._data = undefined; | ||||
|     this._error = undefined; | ||||
|     this._waiting = undefined; | ||||
|     this._dialogParams = undefined; | ||||
|     fireEvent(this, "dialog-closed", { dialog: this.localName }); | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     if (!this._dialogParams) { | ||||
|       return nothing; | ||||
|     } | ||||
|     return html` | ||||
|       <ha-dialog | ||||
|         open | ||||
|         scrimClickAction | ||||
|         escapeKeyAction | ||||
|         .heading=${this._dialogParams.supervisor.localize( | ||||
|           "dialog.backup_location.title" | ||||
|         )} | ||||
|         @closed=${this.closeDialog} | ||||
|       > | ||||
|         ${this._error | ||||
|           ? html`<ha-alert alert-type="error">${this._error}</ha-alert>` | ||||
|           : nothing} | ||||
|  | ||||
|         <ha-form | ||||
|           .hass=${this.hass} | ||||
|           .data=${this._data} | ||||
|           .schema=${SCHEMA()} | ||||
|           .computeLabel=${this._computeLabelCallback} | ||||
|           .computeHelper=${this._computeHelperCallback} | ||||
|           @value-changed=${this._valueChanged} | ||||
|           dialogInitialFocus | ||||
|         ></ha-form> | ||||
|         <mwc-button | ||||
|           slot="secondaryAction" | ||||
|           @click=${this.closeDialog} | ||||
|           dialogInitialFocus | ||||
|         > | ||||
|           ${this._dialogParams.supervisor.localize("common.cancel")} | ||||
|         </mwc-button> | ||||
|         <mwc-button | ||||
|           .disabled=${this._waiting || !this._data} | ||||
|           slot="primaryAction" | ||||
|           @click=${this._changeMount} | ||||
|         > | ||||
|           ${this._dialogParams.supervisor.localize("common.save")} | ||||
|         </mwc-button> | ||||
|       </ha-dialog> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   private _computeLabelCallback = ( | ||||
|     // @ts-ignore | ||||
|     schema: SchemaUnion<ReturnType<typeof SCHEMA>> | ||||
|   ): string => | ||||
|     this._dialogParams!.supervisor.localize( | ||||
|       `dialog.backup_location.options.${schema.name}.name` | ||||
|     ) || schema.name; | ||||
|  | ||||
|   private _computeHelperCallback = ( | ||||
|     // @ts-ignore | ||||
|     schema: SchemaUnion<ReturnType<typeof SCHEMA>> | ||||
|   ): string => | ||||
|     this._dialogParams!.supervisor.localize( | ||||
|       `dialog.backup_location.options.${schema.name}.description` | ||||
|     ); | ||||
|  | ||||
|   private _valueChanged(ev: CustomEvent) { | ||||
|     const newLocation = ev.detail.value.default_backup_mount; | ||||
|     this._data = { | ||||
|       default_backup_mount: newLocation === "/backup" ? null : newLocation, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   private async _changeMount() { | ||||
|     if (!this._data) { | ||||
|       return; | ||||
|     } | ||||
|     this._error = undefined; | ||||
|     this._waiting = true; | ||||
|     try { | ||||
|       await changeMountOptions(this.hass, this._data); | ||||
|     } catch (err: any) { | ||||
|       this._error = extractApiErrorMessage(err); | ||||
|       this._waiting = false; | ||||
|       return; | ||||
|     } | ||||
|     this.closeDialog(); | ||||
|   } | ||||
|  | ||||
|   static get styles(): CSSResultGroup { | ||||
|     return [ | ||||
|       haStyle, | ||||
|       haStyleDialog, | ||||
|       css` | ||||
|         .delete-btn { | ||||
|           --mdc-theme-primary: var(--error-color); | ||||
|         } | ||||
|       `, | ||||
|     ]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "dialog-hassio-backup-location": HassioBackupLocationDialog; | ||||
|   } | ||||
| } | ||||
| @@ -7,6 +7,7 @@ import { atLeastVersion } from "../../../../src/common/config/version"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import { stopPropagation } from "../../../../src/common/dom/stop_propagation"; | ||||
| import { slugify } from "../../../../src/common/string/slugify"; | ||||
| import "../../../../src/components/ha-dialog"; | ||||
| import "../../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../../src/components/ha-alert"; | ||||
| import "../../../../src/components/ha-button-menu"; | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user