mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-15 20:29:26 +00:00
Compare commits
73 Commits
20250509.0
...
remove-pad
Author | SHA1 | Date | |
---|---|---|---|
![]() |
957bf875ae | ||
![]() |
3804a4c7cb | ||
![]() |
b823a3b139 | ||
![]() |
47c9a407e6 | ||
![]() |
c0ba48beb6 | ||
![]() |
075e1df204 | ||
![]() |
22c57853b4 | ||
![]() |
fe824062a5 | ||
![]() |
92bf9b4979 | ||
![]() |
ac616a4d3d | ||
![]() |
1aa1bfda2c | ||
![]() |
38a5035d68 | ||
![]() |
042cd0d3a3 | ||
![]() |
00d708fbd4 | ||
![]() |
852278e8aa | ||
![]() |
15dcdffe55 | ||
![]() |
0729aaacb8 | ||
![]() |
92b8cd8f45 | ||
![]() |
ad8d3dd598 | ||
![]() |
d618c25095 | ||
![]() |
83289bdd41 | ||
![]() |
c7882f3926 | ||
![]() |
7434b12d9f | ||
![]() |
9081441d95 | ||
![]() |
d63f610023 | ||
![]() |
e069875432 | ||
![]() |
91026b0986 | ||
![]() |
4ec5fbc9a4 | ||
![]() |
fb3a59272d | ||
![]() |
9155c85509 | ||
![]() |
9d74cd7561 | ||
![]() |
22ddcca954 | ||
![]() |
8f422357f1 | ||
![]() |
44f5f7bdb5 | ||
![]() |
83819a9be0 | ||
![]() |
3c9dce20e2 | ||
![]() |
a19e7002ea | ||
![]() |
75608db9b8 | ||
![]() |
08f30b714b | ||
![]() |
9983129e26 | ||
![]() |
40fe62c2ec | ||
![]() |
d7660370ab | ||
![]() |
bdad76937e | ||
![]() |
d2822308ec | ||
![]() |
99b94e799d | ||
![]() |
1fb28df1a6 | ||
![]() |
b4e8c56f58 | ||
![]() |
17cc63deba | ||
![]() |
b4f1c8755d | ||
![]() |
b0d4c699db | ||
![]() |
c07bf68161 | ||
![]() |
d1a0eaece5 | ||
![]() |
f608783551 | ||
![]() |
dddba58d38 | ||
![]() |
ebc16d6520 | ||
![]() |
4ed8ecad01 | ||
![]() |
c26fb1713d | ||
![]() |
2b7b17625e | ||
![]() |
cd3e4f55e2 | ||
![]() |
1c12aea8f6 | ||
![]() |
3722f971ca | ||
![]() |
409f665641 | ||
![]() |
5b3b17ef6d | ||
![]() |
05b49e8c80 | ||
![]() |
2dbdbb4b64 | ||
![]() |
a825b632bf | ||
![]() |
f8d706277d | ||
![]() |
221bc732fb | ||
![]() |
055c18463c | ||
![]() |
ddd51ff097 | ||
![]() |
55c75096d0 | ||
![]() |
fd3502f3bc | ||
![]() |
7d6bec01ae |
32
package.json
32
package.json
@@ -26,7 +26,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.27.0",
|
||||
"@babel/runtime": "7.27.1",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@codemirror/autocomplete": "6.18.6",
|
||||
"@codemirror/commands": "6.8.1",
|
||||
@@ -34,7 +34,7 @@
|
||||
"@codemirror/legacy-modes": "6.5.1",
|
||||
"@codemirror/search": "6.5.10",
|
||||
"@codemirror/state": "6.5.2",
|
||||
"@codemirror/view": "6.36.6",
|
||||
"@codemirror/view": "6.36.7",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.18.0",
|
||||
"@formatjs/intl-displaynames": "6.8.11",
|
||||
@@ -89,8 +89,8 @@
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "3.8.1",
|
||||
"@tsparticles/preset-links": "3.2.0",
|
||||
"@vaadin/combo-box": "24.7.4",
|
||||
"@vaadin/vaadin-themable-mixin": "24.7.4",
|
||||
"@vaadin/combo-box": "24.7.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.7.5",
|
||||
"@vibrant/color": "4.0.0",
|
||||
"@vue/web-component-wrapper": "1.3.0",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
@@ -99,7 +99,7 @@
|
||||
"barcode-detector": "3.0.1",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.2",
|
||||
"core-js": "3.41.0",
|
||||
"core-js": "3.42.0",
|
||||
"cropperjs": "1.6.2",
|
||||
"date-fns": "4.1.0",
|
||||
"date-fns-tz": "3.2.0",
|
||||
@@ -131,7 +131,7 @@
|
||||
"qrcode": "1.5.4",
|
||||
"roboto-fontface": "0.10.0",
|
||||
"rrule": "2.8.1",
|
||||
"sortablejs": "patch:sortablejs@npm%3A1.15.3#~/.yarn/patches/sortablejs-npm-1.15.3-3235a8f83b.patch",
|
||||
"sortablejs": "patch:sortablejs@npm%3A1.15.6#~/.yarn/patches/sortablejs-npm-1.15.6-3235a8f83b.patch",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "3.0.0",
|
||||
@@ -150,18 +150,18 @@
|
||||
"xss": "1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.26.10",
|
||||
"@babel/core": "7.27.1",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.4",
|
||||
"@babel/plugin-transform-runtime": "7.26.10",
|
||||
"@babel/preset-env": "7.26.9",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.19.1",
|
||||
"@lokalise/node-api": "14.4.0",
|
||||
"@babel/plugin-transform-runtime": "7.27.1",
|
||||
"@babel/preset-env": "7.27.1",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.20.0",
|
||||
"@lokalise/node-api": "14.5.0",
|
||||
"@octokit/auth-oauth-device": "7.1.5",
|
||||
"@octokit/plugin-retry": "7.2.1",
|
||||
"@octokit/rest": "21.1.1",
|
||||
"@rsdoctor/rspack-plugin": "1.0.2",
|
||||
"@rspack/cli": "1.3.7",
|
||||
"@rspack/core": "1.3.7",
|
||||
"@rspack/cli": "1.3.8",
|
||||
"@rspack/core": "1.3.8",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.21",
|
||||
"@types/chromecast-caf-sender": "1.0.11",
|
||||
@@ -185,7 +185,7 @@
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.3",
|
||||
"del": "8.0.0",
|
||||
"eslint": "9.25.1",
|
||||
"eslint": "9.26.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "10.1.2",
|
||||
"eslint-import-resolver-webpack": "0.13.10",
|
||||
@@ -193,7 +193,7 @@
|
||||
"eslint-plugin-lit": "2.1.1",
|
||||
"eslint-plugin-lit-a11y": "4.1.4",
|
||||
"eslint-plugin-unused-imports": "4.1.4",
|
||||
"eslint-plugin-wc": "3.0.0",
|
||||
"eslint-plugin-wc": "3.0.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.0",
|
||||
"glob": "11.0.2",
|
||||
@@ -219,7 +219,7 @@
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.31.0",
|
||||
"typescript-eslint": "8.31.1",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "3.1.2",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
|
@@ -1,13 +1,13 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M76.9105 39.4999C77.739 39.4999 78.4105 38.8283 78.4105 37.9999C78.4105 37.1715 77.739 36.4999 76.9105 36.4999V39.4999ZM37.5 39.4999L76.9105 39.4999V36.4999L37.5 36.4999L37.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M76.9105 39.4999C77.739 39.4999 78.4105 38.8284 78.4105 37.9999C78.4105 37.1715 77.739 36.4999 76.9105 36.4999V39.4999ZM37.5 37.9999L37.5 39.4999L76.9105 39.4999V37.9999V36.4999L37.5 36.4999L37.5 37.9999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M30.8239 22.3365L38.8239 38.8365L30.3239 50.3365" stroke="black" stroke-opacity="0.12" stroke-width="3" stroke-linecap="round"/>
|
||||
<mask id="mask0_1110_23734" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||
<mask id="mask0_2_779" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||
<path d="M45.75 42.075C45.75 42.4462 45.4462 42.75 45.075 42.75H32.925C32.5538 42.75 32.25 42.4462 32.25 42.075V36.675C32.25 36.3037 32.4649 35.7851 32.7276 35.5224L38.5224 29.7275C38.7851 29.4649 39.2143 29.4649 39.477 29.7275L45.2724 35.523C45.5351 35.7857 45.75 36.3043 45.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1110_23734)">
|
||||
<rect x="30" y="27" width="18" height="18" fill="#212121"/>
|
||||
<g mask="url(#mask0_2_779)">
|
||||
<rect x="30" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||
</g>
|
||||
<path d="M82 37.9999C82 36.343 83.3431 34.9999 85 34.9999C86.6569 34.9999 88 36.343 88 37.9999C88 39.6567 86.6569 40.9999 85 40.9999C83.3431 40.9999 82 39.6567 82 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
<path d="M85 34.9999C86.6569 34.9999 88 36.343 88 37.9999C88 39.6567 86.6569 40.9999 85 40.9999C83.3431 40.9999 82 39.6567 82 37.9999C82 36.343 83.3431 34.9999 85 34.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
<rect x="23" y="11" width="8" height="8" rx="4" fill="black" fill-opacity="0.32"/>
|
||||
<rect x="22" y="52" width="8" height="8" rx="4" fill="black" fill-opacity="0.32"/>
|
||||
<path d="M21.5715 19.5C17.4983 23.801 15 29.6087 15 36C15 41.9085 17.1351 47.3183 20.6759 51.5" stroke="black" stroke-opacity="0.12" stroke-width="3" stroke-linecap="round"/>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,19 +1,19 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M63.1358 38.5084C63.9608 38.4334 64.5688 37.7037 64.4938 36.8787C64.4188 36.0537 63.6892 35.4457 62.8642 35.5207L63.1358 38.5084ZM46.6358 40.0084L63.1358 38.5084L62.8642 35.5207L46.3642 37.0207L46.6358 40.0084Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<circle cx="47" cy="36" r="34" fill="white"/>
|
||||
<circle cx="47" cy="36" r="33.5" stroke="black" stroke-opacity="0.12"/>
|
||||
<path d="M41.8777 12.5216C43.4905 12.1798 45.1631 12 46.8777 12C58.2401 12 67.7582 19.8959 70.2445 30.5M40 59C42.1788 59.6506 44.4874 60 46.8777 60C56.9498 60 65.5728 53.7955 69.1332 45" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<mask id="mask0_1110_23775" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="38" y="27" width="18" height="18">
|
||||
<path d="M53.75 42.075C53.75 42.4462 53.4462 42.75 53.075 42.75H40.925C40.5538 42.75 40.25 42.4462 40.25 42.075V36.675C40.25 36.3037 40.4649 35.7851 40.7276 35.5224L46.5224 29.7275C46.7851 29.4649 47.2143 29.4649 47.477 29.7275L53.2724 35.523C53.5351 35.7857 53.75 36.3043 53.75 36.6755V42.075Z" fill="black"/>
|
||||
<path d="M55.1358 38.5084C55.9608 38.4334 56.5688 37.7038 56.4938 36.8788C56.4188 36.0538 55.6892 35.4457 54.8642 35.5207L55.1358 38.5084ZM38.5 38.5146L38.6358 40.0084L55.1358 38.5084L55 37.0146L54.8642 35.5207L38.3642 37.0207L38.5 38.5146Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M30.5 22L37.9722 37.4115C38.2967 38.0807 38.223 38.8747 37.781 39.4728L30 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<circle cx="39" cy="36" r="34" fill="white"/>
|
||||
<circle cx="39" cy="36" r="33.5" stroke="black" stroke-opacity="0.12"/>
|
||||
<path d="M33.8777 12.5216C35.4905 12.1798 37.1631 12 38.8777 12C50.2401 12 59.7582 19.8959 62.2445 30.5M32 59C34.1788 59.6506 36.4874 60 38.8777 60C48.9498 60 57.5728 53.7955 61.1332 45" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M30.5 22L37.9722 37.4115C38.2967 38.0807 38.223 38.8747 37.781 39.4728L30 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<mask id="mask0_2_810" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||
<path d="M45.75 42.075C45.75 42.4462 45.4463 42.75 45.075 42.75H32.925C32.5538 42.75 32.25 42.4462 32.25 42.075V36.675C32.25 36.3037 32.4649 35.7851 32.7276 35.5224L38.5224 29.7275C38.7851 29.4649 39.2143 29.4649 39.477 29.7275L45.2724 35.523C45.5351 35.7857 45.75 36.3043 45.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1110_23775)">
|
||||
<rect x="38" y="27" width="18" height="18" fill="#212121"/>
|
||||
<g mask="url(#mask0_2_810)">
|
||||
<rect x="30" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||
</g>
|
||||
<path d="M63.5 39.4999C64.3284 39.4999 65 38.8283 65 37.9999C65 37.1715 64.3284 36.4999 63.5 36.4999L63.5 39.4999ZM49.5 39.4999L63.5 39.4999L63.5 36.4999L49.5 36.4999L49.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<rect x="31" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<rect x="30" y="52" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<path d="M29.5715 19.5C25.4983 23.801 23 29.6087 23 36C23 41.9085 25.1351 47.3183 28.6759 51.5" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M68 37.9999C68 36.343 69.3431 34.9999 71 34.9999C72.6569 34.9999 74 36.343 74 37.9999C74 39.6567 72.6569 40.9999 71 40.9999C69.3431 40.9999 68 39.6567 68 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
<path d="M55.5 39.4999C56.3284 39.4999 57 38.8283 57 37.9999C57 37.1715 56.3284 36.4999 55.5 36.4999L55.5 39.4999ZM41.5 37.9999L41.5 39.4999L55.5 39.4999L55.5 37.9999L55.5 36.4999L41.5 36.4999L41.5 37.9999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<rect x="23" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<rect x="22" y="52" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<path d="M21.5715 19.5C17.4983 23.801 15 29.6087 15 36C15 41.9085 17.1351 47.3183 20.6759 51.5" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M63 34.9999C64.6569 34.9999 66 36.343 66 37.9999C66 39.6567 64.6569 40.9999 63 40.9999C61.3431 40.9999 60 39.6567 60 37.9999C60 36.343 61.3431 34.9999 63 34.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,19 +1,18 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M63.1358 38.5084C63.9608 38.4334 64.5688 37.7037 64.4938 36.8787C64.4188 36.0537 63.6892 35.4457 62.8642 35.5207L63.1358 38.5084ZM46.6358 40.0084L63.1358 38.5084L62.8642 35.5207L46.3642 37.0207L46.6358 40.0084Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<circle cx="47" cy="36" r="34" fill="#1C1C1C"/>
|
||||
<circle cx="47" cy="36" r="33.5" stroke="white" stroke-opacity="0.24"/>
|
||||
<path d="M41.8777 12.5216C43.4905 12.1798 45.1631 12 46.8777 12C58.2401 12 67.7582 19.8959 70.2445 30.5M40 59C42.1788 59.6506 44.4874 60 46.8777 60C56.9498 60 65.5728 53.7955 69.1332 45" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<mask id="mask0_1180_4965" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="38" y="27" width="18" height="18">
|
||||
<path d="M53.75 42.075C53.75 42.4462 53.4462 42.75 53.075 42.75H40.925C40.5538 42.75 40.25 42.4462 40.25 42.075V36.675C40.25 36.3037 40.4649 35.7851 40.7276 35.5224L46.5224 29.7275C46.7851 29.4649 47.2143 29.4649 47.477 29.7275L53.2724 35.523C53.5351 35.7857 53.75 36.3043 53.75 36.6755V42.075Z" fill="black"/>
|
||||
<path d="M55.1358 38.5084C55.9608 38.4334 56.5688 37.7038 56.4938 36.8788C56.4188 36.0538 55.6892 35.4457 54.8642 35.5207L55.1358 38.5084ZM38.5 38.5146L38.6358 40.0084L55.1358 38.5084L55 37.0146L54.8642 35.5207L38.3642 37.0207L38.5 38.5146Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M30.5 22L37.9722 37.4115C38.2967 38.0807 38.223 38.8747 37.781 39.4728L30 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<circle cx="39" cy="36" r="33.5" stroke="white" stroke-opacity="0.24"/>
|
||||
<path d="M33.8777 12.5216C35.4905 12.1798 37.1631 12 38.8777 12C50.2401 12 59.7582 19.8959 62.2445 30.5M32 59C34.1788 59.6506 36.4874 60 38.8777 60C48.9498 60 57.5728 53.7955 61.1332 45" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M30.5 22L37.9722 37.4115C38.2967 38.0807 38.223 38.8747 37.781 39.4728L30 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<mask id="mask0_2_810" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||
<path d="M45.75 42.075C45.75 42.4462 45.4463 42.75 45.075 42.75H32.925C32.5538 42.75 32.25 42.4462 32.25 42.075V36.675C32.25 36.3037 32.4649 35.7851 32.7276 35.5224L38.5224 29.7275C38.7851 29.4649 39.2143 29.4649 39.477 29.7275L45.2724 35.523C45.5351 35.7857 45.75 36.3043 45.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1180_4965)">
|
||||
<rect x="38" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||
<g mask="url(#mask0_2_810)">
|
||||
<rect x="30" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||
</g>
|
||||
<path d="M63.5 39.4999C64.3284 39.4999 65 38.8283 65 37.9999C65 37.1715 64.3284 36.4999 63.5 36.4999L63.5 39.4999ZM49.5 39.4999L63.5 39.4999L63.5 36.4999L49.5 36.4999L49.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<rect x="31" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<rect x="30" y="52" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<path d="M29.5715 19.5C25.4983 23.801 23 29.6087 23 36C23 41.9085 25.1351 47.3183 28.6759 51.5" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M68 37.9999C68 36.343 69.3431 34.9999 71 34.9999C72.6569 34.9999 74 36.343 74 37.9999C74 39.6567 72.6569 40.9999 71 40.9999C69.3431 40.9999 68 39.6567 68 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
<path d="M55.5 39.4999C56.3284 39.4999 57 38.8283 57 37.9999C57 37.1715 56.3284 36.4999 55.5 36.4999L55.5 39.4999ZM41.5 37.9999L41.5 39.4999L55.5 39.4999L55.5 37.9999L55.5 36.4999L41.5 36.4999L41.5 37.9999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<rect x="23" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<rect x="22" y="52" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<path d="M21.5715 19.5C17.4983 23.801 15 29.6087 15 36C15 41.9085 17.1351 47.3183 20.6759 51.5" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M63 34.9999C64.6569 34.9999 66 36.343 66 37.9999C66 39.6567 64.6569 40.9999 63 40.9999C61.3431 40.9999 60 39.6567 60 37.9999C60 36.343 61.3431 34.9999 63 34.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -9,6 +9,8 @@ import type { LitElement } from "lit";
|
||||
*/
|
||||
export interface DragScrollControllerConfig {
|
||||
selector: string;
|
||||
enabled?: boolean;
|
||||
trackScroll?: boolean;
|
||||
}
|
||||
|
||||
export class DragScrollController implements ReactiveController {
|
||||
@@ -22,38 +24,109 @@ export class DragScrollController implements ReactiveController {
|
||||
|
||||
public scrollLeft = 0;
|
||||
|
||||
public scrolledStart = false;
|
||||
|
||||
public scrolledEnd = false;
|
||||
|
||||
private _host: ReactiveControllerHost & LitElement;
|
||||
|
||||
private _selector: string;
|
||||
|
||||
private _scrollContainer?: HTMLElement | null;
|
||||
|
||||
private _enabled = true;
|
||||
|
||||
private _trackScroll = false;
|
||||
|
||||
public get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
public set enabled(value: boolean) {
|
||||
if (value === this._enabled) {
|
||||
return;
|
||||
}
|
||||
this._enabled = value;
|
||||
if (this._enabled) {
|
||||
this._attach();
|
||||
} else {
|
||||
this._detach();
|
||||
}
|
||||
this._host.requestUpdate();
|
||||
}
|
||||
|
||||
constructor(
|
||||
host: ReactiveControllerHost & LitElement,
|
||||
{ selector }: DragScrollControllerConfig
|
||||
{ selector, enabled, trackScroll }: DragScrollControllerConfig
|
||||
) {
|
||||
this._selector = selector;
|
||||
this._host = host;
|
||||
this._trackScroll = trackScroll ?? false;
|
||||
this.enabled = enabled ?? true;
|
||||
host.addController(this);
|
||||
}
|
||||
|
||||
hostUpdated() {
|
||||
if (this._scrollContainer) {
|
||||
if (!this.enabled || this._scrollContainer) {
|
||||
return;
|
||||
}
|
||||
this._attach();
|
||||
}
|
||||
|
||||
hostDisconnected() {
|
||||
this._detach();
|
||||
}
|
||||
|
||||
private _attach() {
|
||||
this._scrollContainer = this._host.renderRoot?.querySelector(
|
||||
this._selector
|
||||
);
|
||||
if (this._scrollContainer) {
|
||||
this._scrollContainer.addEventListener("mousedown", this._mouseDown);
|
||||
if (this._trackScroll) {
|
||||
this._scrollContainer.addEventListener("scroll", this._onScroll);
|
||||
this.scrolledStart = this._scrollContainer.scrollLeft > 0;
|
||||
this.scrolledEnd =
|
||||
this._scrollContainer.scrollLeft + this._scrollContainer.offsetWidth <
|
||||
this._scrollContainer.scrollWidth;
|
||||
this._host.requestUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hostDisconnected() {
|
||||
private _detach() {
|
||||
window.removeEventListener("mousemove", this._mouseMove);
|
||||
window.removeEventListener("mouseup", this._mouseUp);
|
||||
if (this._scrollContainer) {
|
||||
this._scrollContainer.removeEventListener("mousedown", this._mouseDown);
|
||||
this._scrollContainer.removeEventListener("scroll", this._onScroll);
|
||||
this._scrollContainer = undefined;
|
||||
}
|
||||
this.scrolled = false;
|
||||
this.scrolling = false;
|
||||
this.scrolledStart = false;
|
||||
this.scrolledEnd = false;
|
||||
this.mouseIsDown = false;
|
||||
this.scrollStartX = 0;
|
||||
this.scrollLeft = 0;
|
||||
}
|
||||
|
||||
private _onScroll = (event: Event) => {
|
||||
const oldScrolledStart = this.scrolledStart;
|
||||
const oldScrolledEnd = this.scrolledEnd;
|
||||
|
||||
const container = event.currentTarget as HTMLElement;
|
||||
this.scrolledStart = container.scrollLeft > 0;
|
||||
this.scrolledEnd =
|
||||
container.scrollLeft + container.offsetWidth < container.scrollWidth;
|
||||
if (
|
||||
this.scrolledStart !== oldScrolledStart ||
|
||||
this.scrolledEnd !== oldScrolledEnd
|
||||
) {
|
||||
this._host.requestUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
private _mouseDown = (event: MouseEvent) => {
|
||||
const scrollContainer = this._scrollContainer;
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { ReactiveElement } from "lit";
|
||||
import type { InternalPropertyDeclaration } from "lit/decorators";
|
||||
import type { ReactiveElement } from "lit";
|
||||
|
||||
type Callback = (oldValue: any, newValue: any) => void;
|
||||
|
||||
@@ -108,7 +107,6 @@ export function storage(options: {
|
||||
storage?: "localStorage" | "sessionStorage";
|
||||
subscribe?: boolean;
|
||||
state?: boolean;
|
||||
stateOptions?: InternalPropertyDeclaration;
|
||||
serializer?: (value: any) => any;
|
||||
deserializer?: (value: any) => any;
|
||||
}) {
|
||||
@@ -174,7 +172,7 @@ export function storage(options: {
|
||||
performUpdate.call(this);
|
||||
};
|
||||
|
||||
if (options.state && options.subscribe) {
|
||||
if (options.subscribe) {
|
||||
const connectedCallback = proto.connectedCallback;
|
||||
const disconnectedCallback = proto.disconnectedCallback;
|
||||
|
||||
@@ -192,12 +190,6 @@ export function storage(options: {
|
||||
el.__unbsubLocalStorage = undefined;
|
||||
};
|
||||
}
|
||||
if (options.state) {
|
||||
ReactiveElement.createProperty(propertyKey, {
|
||||
noAccessor: true,
|
||||
...options.stateOptions,
|
||||
});
|
||||
}
|
||||
|
||||
const descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey);
|
||||
let newDescriptor: PropertyDescriptor;
|
||||
|
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
ReactiveElement,
|
||||
type PropertyDeclaration,
|
||||
type PropertyValues,
|
||||
} from "lit";
|
||||
import { shallowEqual } from "../util/shallow-equal";
|
||||
|
||||
import type { ReactiveElement, PropertyValues } from "lit";
|
||||
/**
|
||||
* Transform function type.
|
||||
*/
|
||||
@@ -23,7 +17,6 @@ type ReactiveTransformElement = ReactiveElement & {
|
||||
export function transform<T, V>(config: {
|
||||
transformer: Transformer<T, V>;
|
||||
watch?: PropertyKey[];
|
||||
propertyOptions?: PropertyDeclaration;
|
||||
}) {
|
||||
return <ElemClass extends ReactiveElement>(
|
||||
proto: ElemClass,
|
||||
@@ -84,11 +77,6 @@ export function transform<T, V>(config: {
|
||||
curWatch.add(propertyKey);
|
||||
});
|
||||
}
|
||||
ReactiveElement.createProperty(propertyKey, {
|
||||
noAccessor: true,
|
||||
hasChanged: (v: any, o: any) => !shallowEqual(v, o),
|
||||
...config.propertyOptions,
|
||||
});
|
||||
|
||||
const descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey);
|
||||
let newDescriptor: PropertyDescriptor;
|
||||
|
@@ -1,5 +1,11 @@
|
||||
export const canOverrideAlphanumericInput = (composedPath: EventTarget[]) => {
|
||||
if (composedPath.some((el) => "tagName" in el && el.tagName === "HA-MENU")) {
|
||||
if (
|
||||
composedPath.some(
|
||||
(el) =>
|
||||
"tagName" in el &&
|
||||
(el.tagName === "HA-MENU" || el.tagName === "HA-CODE-EDITOR")
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -600,12 +600,32 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
|
||||
private _getSeries() {
|
||||
if (!Array.isArray(this.data)) {
|
||||
return this.data;
|
||||
}
|
||||
return this.data.filter(
|
||||
const series = ensureArray(this.data).filter(
|
||||
(d) => !this._hiddenDatasets.has(String(d.name ?? d.id))
|
||||
);
|
||||
const yAxis = (this.options?.yAxis?.[0] ?? this.options?.yAxis) as
|
||||
| YAXisOption
|
||||
| undefined;
|
||||
if (yAxis?.type === "log") {
|
||||
// set <=0 values to null so they render as gaps on a log graph
|
||||
return series.map((d) =>
|
||||
d.type === "line"
|
||||
? {
|
||||
...d,
|
||||
data: d.data?.map((v) =>
|
||||
Array.isArray(v)
|
||||
? [
|
||||
v[0],
|
||||
typeof v[1] !== "number" || v[1] > 0 ? v[1] : null,
|
||||
...v.slice(2),
|
||||
]
|
||||
: v
|
||||
),
|
||||
}
|
||||
: d
|
||||
);
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
private _getDefaultHeight() {
|
||||
|
@@ -603,7 +603,7 @@ export class HaDataTable extends LitElement {
|
||||
.map(
|
||||
([key2, column2], i) =>
|
||||
html`${i !== 0
|
||||
? " ⸱ "
|
||||
? " · "
|
||||
: nothing}${column2.template
|
||||
? column2.template(row)
|
||||
: row[key2]}`
|
||||
|
@@ -73,16 +73,20 @@ class HaEntityAttributePicker extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this.entityId!] as HassEntity | undefined;
|
||||
|
||||
return html`
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.value=${this.value
|
||||
? computeAttributeNameDisplay(
|
||||
this.hass.localize,
|
||||
this.hass.states[this.entityId!],
|
||||
this.hass.entities,
|
||||
this.value
|
||||
)
|
||||
? stateObj
|
||||
? computeAttributeNameDisplay(
|
||||
this.hass.localize,
|
||||
stateObj,
|
||||
this.hass.entities,
|
||||
this.value
|
||||
)
|
||||
: this.value
|
||||
: ""}
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label ??
|
||||
|
@@ -5,7 +5,6 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||
@@ -30,28 +29,17 @@ import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
|
||||
const FAKE_ENTITY: HassEntity = {
|
||||
entity_id: "",
|
||||
state: "",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
attributes: {},
|
||||
};
|
||||
|
||||
interface EntityComboBoxItem extends HassEntity {
|
||||
interface EntityComboBoxItem {
|
||||
// Force empty label to always display empty value by default in the search field
|
||||
id: string;
|
||||
label: "";
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
translated_domain?: string;
|
||||
show_entity_id?: boolean;
|
||||
entity_name?: string;
|
||||
area_name?: string;
|
||||
device_name?: string;
|
||||
friendly_name?: string;
|
||||
domain_name?: string;
|
||||
search_labels?: string[];
|
||||
sorting_label?: string;
|
||||
icon_path?: string;
|
||||
stateObj?: HassEntity;
|
||||
}
|
||||
|
||||
export type HaEntityComboBoxEntityFilterFunc = (entity: HassEntity) => boolean;
|
||||
@@ -59,22 +47,6 @@ export type HaEntityComboBoxEntityFilterFunc = (entity: HassEntity) => boolean;
|
||||
const CREATE_ID = "___create-new-entity___";
|
||||
const NO_ENTITIES_ID = "___no-entities___";
|
||||
|
||||
const DOMAIN_STYLE = styleMap({
|
||||
fontSize: "var(--ha-font-size-s)",
|
||||
fontWeight: "var(--ha-font-weight-normal)",
|
||||
lineHeight: "var(--ha-line-height-normal)",
|
||||
alignSelf: "flex-end",
|
||||
maxWidth: "30%",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
});
|
||||
|
||||
const ENTITY_ID_STYLE = styleMap({
|
||||
fontFamily: "var(--code-font-family, monospace)",
|
||||
fontSize: "var(--ha-font-size-xs)",
|
||||
});
|
||||
|
||||
@customElement("ha-entity-combo-box")
|
||||
export class HaEntityComboBox extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -177,33 +149,41 @@ export class HaEntityComboBox extends LitElement {
|
||||
private _rowRenderer: ComboBoxLitRenderer<EntityComboBoxItem> = (
|
||||
item,
|
||||
{ index }
|
||||
) => html`
|
||||
<ha-combo-box-item type="button" compact .borderTop=${index !== 0}>
|
||||
${item.icon_path
|
||||
? html`<ha-svg-icon slot="start" .path=${item.icon_path}></ha-svg-icon>`
|
||||
: html`
|
||||
<state-badge
|
||||
slot="start"
|
||||
.stateObj=${item}
|
||||
.hass=${this.hass}
|
||||
></state-badge>
|
||||
`}
|
||||
<span slot="headline">${item.primary}</span>
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
${item.entity_id && item.show_entity_id
|
||||
? html`<span slot="supporting-text" style=${ENTITY_ID_STYLE}
|
||||
>${item.entity_id}</span
|
||||
>`
|
||||
: nothing}
|
||||
${item.translated_domain && !item.show_entity_id
|
||||
? html`<div slot="trailing-supporting-text" style=${DOMAIN_STYLE}>
|
||||
${item.translated_domain}
|
||||
</div>`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
) => {
|
||||
const showEntityId = this.hass.userData?.showEntityIdPicker;
|
||||
|
||||
return html`
|
||||
<ha-combo-box-item type="button" compact .borderTop=${index !== 0}>
|
||||
${item.icon_path
|
||||
? html`
|
||||
<ha-svg-icon slot="start" .path=${item.icon_path}></ha-svg-icon>
|
||||
`
|
||||
: html`
|
||||
<state-badge
|
||||
slot="start"
|
||||
.stateObj=${item.stateObj}
|
||||
.hass=${this.hass}
|
||||
></state-badge>
|
||||
`}
|
||||
<span slot="headline">${item.primary}</span>
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
${item.stateObj && showEntityId
|
||||
? html`
|
||||
<span slot="supporting-text" class="code">
|
||||
${item.stateObj.entity_id}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
${item.domain_name && !showEntityId
|
||||
? html`
|
||||
<div slot="trailing-supporting-text">${item.domain_name}</div>
|
||||
`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
};
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
(
|
||||
@@ -218,7 +198,7 @@ export class HaEntityComboBox extends LitElement {
|
||||
excludeEntities: this["excludeEntities"],
|
||||
createDomains: this["createDomains"]
|
||||
): EntityComboBoxItem[] => {
|
||||
let states: EntityComboBoxItem[] = [];
|
||||
let items: EntityComboBoxItem[] = [];
|
||||
|
||||
let entityIds = Object.keys(hass.states);
|
||||
|
||||
@@ -236,9 +216,8 @@ export class HaEntityComboBox extends LitElement {
|
||||
);
|
||||
|
||||
return {
|
||||
...FAKE_ENTITY,
|
||||
id: CREATE_ID + domain,
|
||||
label: "",
|
||||
entity_id: CREATE_ID + domain,
|
||||
primary: primary,
|
||||
secondary: this.hass.localize(
|
||||
"ui.components.entity.entity-picker.new_entity"
|
||||
@@ -251,9 +230,8 @@ export class HaEntityComboBox extends LitElement {
|
||||
if (!entityIds.length) {
|
||||
return [
|
||||
{
|
||||
...FAKE_ENTITY,
|
||||
id: NO_ENTITIES_ID,
|
||||
label: "",
|
||||
entity_id: NO_ENTITIES_ID,
|
||||
primary: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_entities"
|
||||
),
|
||||
@@ -289,7 +267,7 @@ export class HaEntityComboBox extends LitElement {
|
||||
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
states = entityIds
|
||||
items = entityIds
|
||||
.map<EntityComboBoxItem>((entityId) => {
|
||||
const stateObj = hass!.states[entityId];
|
||||
|
||||
@@ -300,30 +278,32 @@ export class HaEntityComboBox extends LitElement {
|
||||
const deviceName = device ? computeDeviceName(device) : undefined;
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
|
||||
const domainName = domainToName(
|
||||
this.hass.localize,
|
||||
computeDomain(entityId)
|
||||
);
|
||||
|
||||
const primary = entityName || deviceName || entityId;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(isRTL ? " ◂ " : " ▸ ");
|
||||
|
||||
const translatedDomain = domainToName(
|
||||
this.hass.localize,
|
||||
computeDomain(entityId)
|
||||
);
|
||||
|
||||
return {
|
||||
...hass!.states[entityId],
|
||||
id: entityId,
|
||||
label: "",
|
||||
primary: primary,
|
||||
secondary:
|
||||
secondary ||
|
||||
this.hass.localize("ui.components.device-picker.no_area"),
|
||||
translated_domain: translatedDomain,
|
||||
sorting_label: [deviceName, entityName].filter(Boolean).join("-"),
|
||||
entity_name: entityName || deviceName,
|
||||
area_name: areaName,
|
||||
device_name: deviceName,
|
||||
friendly_name: friendlyName,
|
||||
show_entity_id: hass.userData?.showEntityIdPicker,
|
||||
secondary: secondary,
|
||||
domain_name: domainName,
|
||||
sorting_label: [deviceName, entityName].filter(Boolean).join("_"),
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
domainName,
|
||||
friendlyName,
|
||||
entityId,
|
||||
].filter(Boolean) as string[],
|
||||
stateObj: stateObj,
|
||||
};
|
||||
})
|
||||
.sort((entityA, entityB) =>
|
||||
@@ -335,41 +315,43 @@ export class HaEntityComboBox extends LitElement {
|
||||
);
|
||||
|
||||
if (includeDeviceClasses) {
|
||||
states = states.filter(
|
||||
(stateObj) =>
|
||||
items = items.filter(
|
||||
(item) =>
|
||||
// We always want to include the entity of the current value
|
||||
stateObj.entity_id === this.value ||
|
||||
(stateObj.attributes.device_class &&
|
||||
includeDeviceClasses.includes(stateObj.attributes.device_class))
|
||||
item.id === this.value ||
|
||||
(item.stateObj?.attributes.device_class &&
|
||||
includeDeviceClasses.includes(
|
||||
item.stateObj.attributes.device_class
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
if (includeUnitOfMeasurement) {
|
||||
states = states.filter(
|
||||
(stateObj) =>
|
||||
items = items.filter(
|
||||
(item) =>
|
||||
// We always want to include the entity of the current value
|
||||
stateObj.entity_id === this.value ||
|
||||
(stateObj.attributes.unit_of_measurement &&
|
||||
item.id === this.value ||
|
||||
(item.stateObj?.attributes.unit_of_measurement &&
|
||||
includeUnitOfMeasurement.includes(
|
||||
stateObj.attributes.unit_of_measurement
|
||||
item.stateObj.attributes.unit_of_measurement
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
if (entityFilter) {
|
||||
states = states.filter(
|
||||
(stateObj) =>
|
||||
items = items.filter(
|
||||
(item) =>
|
||||
// We always want to include the entity of the current value
|
||||
stateObj.entity_id === this.value || entityFilter!(stateObj)
|
||||
item.id === this.value ||
|
||||
(item.stateObj && entityFilter!(item.stateObj))
|
||||
);
|
||||
}
|
||||
|
||||
if (!states.length) {
|
||||
if (!items.length) {
|
||||
return [
|
||||
{
|
||||
...FAKE_ENTITY,
|
||||
id: NO_ENTITIES_ID,
|
||||
label: "",
|
||||
entity_id: NO_ENTITIES_ID,
|
||||
primary: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
),
|
||||
@@ -380,10 +362,10 @@ export class HaEntityComboBox extends LitElement {
|
||||
}
|
||||
|
||||
if (createItems?.length) {
|
||||
states.push(...createItems);
|
||||
items.push(...createItems);
|
||||
}
|
||||
|
||||
return states;
|
||||
return items;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -426,7 +408,7 @@ export class HaEntityComboBox extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-combo-box
|
||||
item-value-path="entity_id"
|
||||
item-value-path="id"
|
||||
.hass=${this.hass}
|
||||
.value=${this._value}
|
||||
.label=${this.label === undefined
|
||||
@@ -478,17 +460,7 @@ export class HaEntityComboBox extends LitElement {
|
||||
}
|
||||
|
||||
private _fuseIndex = memoizeOne((states: EntityComboBoxItem[]) =>
|
||||
Fuse.createIndex(
|
||||
[
|
||||
"entity_name",
|
||||
"device_name",
|
||||
"area_name",
|
||||
"translated_domain",
|
||||
"friendly_name", // for backwards compatibility
|
||||
"entity_id", // for technical search
|
||||
],
|
||||
states
|
||||
)
|
||||
Fuse.createIndex(["search_labels"], states)
|
||||
);
|
||||
|
||||
private _filterChanged(ev: CustomEvent): void {
|
||||
@@ -505,9 +477,8 @@ export class HaEntityComboBox extends LitElement {
|
||||
if (results.length === 0) {
|
||||
target.filteredItems = [
|
||||
{
|
||||
...FAKE_ENTITY,
|
||||
id: NO_ENTITIES_ID,
|
||||
label: "",
|
||||
entity_id: NO_ENTITIES_ID,
|
||||
primary: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
),
|
||||
|
@@ -162,10 +162,7 @@ export class HaEntityPicker extends LitElement {
|
||||
slot="start"
|
||||
></state-badge>
|
||||
<span slot="headline">${primary}</span>
|
||||
<span slot="supporting-text">
|
||||
${secondary ||
|
||||
this.hass.localize("ui.components.device-picker.no_area")}
|
||||
</span>
|
||||
<span slot="supporting-text">${secondary}</span>
|
||||
${showClearIcon
|
||||
? html`<ha-icon-button
|
||||
class="clear"
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import { mdiChartLine, mdiShape } from "@mdi/js";
|
||||
import { mdiChartLine, mdiHelpCircle, mdiShape } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import Fuse from "fuse.js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -26,31 +25,27 @@ import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-combo-box-item";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
|
||||
type StatisticItemType = "entity" | "external" | "no_state";
|
||||
|
||||
interface StatisticItem {
|
||||
// Force empty label to always display empty value by default in the search field
|
||||
id: string;
|
||||
statistic_id?: string;
|
||||
label: "";
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
show_entity_id?: boolean;
|
||||
entity_name?: string;
|
||||
area_name?: string;
|
||||
device_name?: string;
|
||||
friendly_name?: string;
|
||||
search_labels?: string[];
|
||||
sorting_label?: string;
|
||||
state?: HassEntity;
|
||||
icon_path?: string;
|
||||
type?: StatisticItemType;
|
||||
iconPath?: string;
|
||||
stateObj?: HassEntity;
|
||||
}
|
||||
|
||||
const TYPE_ORDER = ["entity", "external", "no_state"] as StatisticItemType[];
|
||||
const MISSING_ID = "___missing-entity___";
|
||||
|
||||
const ENTITY_ID_STYLE = styleMap({
|
||||
fontFamily: "var(--code-font-family, monospace)",
|
||||
fontSize: "11px",
|
||||
});
|
||||
const TYPE_ORDER = ["entity", "external", "no_state"] as StatisticItemType[];
|
||||
|
||||
@customElement("ha-statistic-combo-box")
|
||||
export class HaStatisticComboBox extends LitElement {
|
||||
@@ -131,37 +126,39 @@ export class HaStatisticComboBox extends LitElement {
|
||||
private _rowRenderer: ComboBoxLitRenderer<StatisticItem> = (
|
||||
item,
|
||||
{ index }
|
||||
) => html`
|
||||
<ha-combo-box-item type="button" compact .borderTop=${index !== 0}>
|
||||
${!item.state
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
style="margin: 0 4px"
|
||||
slot="start"
|
||||
.path=${item.iconPath}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: html`
|
||||
<state-badge
|
||||
slot="start"
|
||||
.stateObj=${item.state}
|
||||
.hass=${this.hass}
|
||||
></state-badge>
|
||||
`}
|
||||
|
||||
<span slot="headline">${item.primary} </span>
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
${item.id && item.show_entity_id
|
||||
? html`
|
||||
<span slot="supporting-text" style=${ENTITY_ID_STYLE}>
|
||||
${item.id}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
) => {
|
||||
const showEntityId = this.hass.userData?.showEntityIdPicker;
|
||||
return html`
|
||||
<ha-combo-box-item type="button" compact .borderTop=${index !== 0}>
|
||||
${item.icon_path
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
style="margin: 0 4px"
|
||||
slot="start"
|
||||
.path=${item.icon_path}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: item.stateObj
|
||||
? html`
|
||||
<state-badge
|
||||
slot="start"
|
||||
.stateObj=${item.stateObj}
|
||||
.hass=${this.hass}
|
||||
></state-badge>
|
||||
`
|
||||
: nothing}
|
||||
<span slot="headline">${item.primary} </span>
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
${item.id && showEntityId
|
||||
? html`<span slot="supporting-text" class="code">
|
||||
${item.statistic_id}
|
||||
</span>`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
};
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
(
|
||||
@@ -249,19 +246,22 @@ export class HaStatisticComboBox extends LitElement {
|
||||
label: "",
|
||||
type,
|
||||
sorting_label: label,
|
||||
iconPath: mdiShape,
|
||||
search_labels: [label, id],
|
||||
icon_path: mdiShape,
|
||||
});
|
||||
} else if (type === "external") {
|
||||
const domain = id.split(":")[0];
|
||||
const domainName = domainToName(this.hass.localize, domain);
|
||||
output.push({
|
||||
id,
|
||||
statistic_id: id,
|
||||
primary: label,
|
||||
secondary: domainName,
|
||||
label: "",
|
||||
type,
|
||||
sorting_label: label,
|
||||
iconPath: mdiChartLine,
|
||||
search_labels: [label, domainName, id],
|
||||
icon_path: mdiChartLine,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -283,17 +283,20 @@ export class HaStatisticComboBox extends LitElement {
|
||||
|
||||
output.push({
|
||||
id,
|
||||
statistic_id: id,
|
||||
label: "",
|
||||
primary,
|
||||
secondary,
|
||||
label: "",
|
||||
state: stateObj,
|
||||
stateObj: stateObj,
|
||||
type: "entity",
|
||||
sorting_label: [deviceName, entityName].join("_"),
|
||||
entity_name: entityName || deviceName,
|
||||
area_name: areaName,
|
||||
device_name: deviceName,
|
||||
friendly_name: friendlyName,
|
||||
show_entity_id: hass.userData?.showEntityIdPicker,
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
friendlyName,
|
||||
id,
|
||||
].filter(Boolean) as string[],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -323,11 +326,12 @@ export class HaStatisticComboBox extends LitElement {
|
||||
}
|
||||
|
||||
output.push({
|
||||
id: "__missing",
|
||||
id: MISSING_ID,
|
||||
primary: this.hass.localize(
|
||||
"ui.components.statistic-picker.missing_entity"
|
||||
),
|
||||
label: "",
|
||||
icon_path: mdiHelpCircle,
|
||||
});
|
||||
|
||||
return output;
|
||||
@@ -422,8 +426,12 @@ export class HaStatisticComboBox extends LitElement {
|
||||
private _statisticChanged(ev: ValueChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
let newValue = ev.detail.value;
|
||||
if (newValue === "__missing") {
|
||||
if (newValue === MISSING_ID) {
|
||||
newValue = "";
|
||||
window.open(
|
||||
documentationUrl(this.hass, this.helpMissingEntityUrl),
|
||||
"_blank"
|
||||
);
|
||||
}
|
||||
|
||||
if (newValue !== this._value) {
|
||||
@@ -436,16 +444,7 @@ export class HaStatisticComboBox extends LitElement {
|
||||
}
|
||||
|
||||
private _fuseIndex = memoizeOne((states: StatisticItem[]) =>
|
||||
Fuse.createIndex(
|
||||
[
|
||||
"entity_name",
|
||||
"device_name",
|
||||
"area_name",
|
||||
"friendly_name", // for backwards compatibility
|
||||
"id", // for technical search
|
||||
],
|
||||
states
|
||||
)
|
||||
Fuse.createIndex(["search_labels"], states)
|
||||
);
|
||||
|
||||
private _filterChanged(ev: CustomEvent): void {
|
||||
|
@@ -35,6 +35,20 @@ export class HaComboBoxItem extends HaMdListItem {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
::slotted(.code) {
|
||||
font-family: var(--ha-font-family-code);
|
||||
font-size: var(--ha-font-size-xs);
|
||||
}
|
||||
[slot="trailing-supporting-text"] {
|
||||
font-size: var(--ha-font-size-s);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
line-height: var(--ha-line-height-normal);
|
||||
align-self: flex-end;
|
||||
max-width: 30%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -30,8 +30,9 @@ class HaLabeledSlider extends LitElement {
|
||||
@property({ type: Number }) public value?: number;
|
||||
|
||||
protected render() {
|
||||
const title = this._getTitle();
|
||||
return html`
|
||||
<div class="title">${this._getTitle()}</div>
|
||||
${title ? html`<div class="title">${title}</div>` : nothing}
|
||||
<div class="extra-container"><slot name="extra"></slot></div>
|
||||
<div class="slider-container">
|
||||
${this.icon ? html`<ha-icon icon=${this.icon}></ha-icon>` : nothing}
|
||||
@@ -73,17 +74,20 @@ class HaLabeledSlider extends LitElement {
|
||||
|
||||
.slider-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ha-icon {
|
||||
margin-top: 8px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-slider {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
background-image: var(--ha-slider-background);
|
||||
border-radius: 4px;
|
||||
height: 32px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -102,7 +102,7 @@ export class HaLanguagePicker extends LitElement {
|
||||
localeChanged
|
||||
) {
|
||||
this._select.layoutOptions();
|
||||
if (this._select.value !== this.value) {
|
||||
if (!this.disabled && this._select.value !== this.value) {
|
||||
fireEvent(this, "value-changed", { value: this._select.value });
|
||||
}
|
||||
if (!this.value) {
|
||||
@@ -141,7 +141,10 @@ export class HaLanguagePicker extends LitElement {
|
||||
);
|
||||
|
||||
const value =
|
||||
this.value ?? (this.required ? languageOptions[0]?.value : this.value);
|
||||
this.value ??
|
||||
(this.required && !this.disabled
|
||||
? languageOptions[0]?.value
|
||||
: this.value);
|
||||
|
||||
return html`
|
||||
<ha-select
|
||||
@@ -182,7 +185,7 @@ export class HaLanguagePicker extends LitElement {
|
||||
|
||||
private _changed(ev): void {
|
||||
const target = ev.target as HaSelect;
|
||||
if (target.value === "" || target.value === this.value) {
|
||||
if (this.disabled || target.value === "" || target.value === this.value) {
|
||||
return;
|
||||
}
|
||||
this.value = target.value;
|
||||
|
@@ -6,6 +6,13 @@ import { customElement } from "lit/decorators";
|
||||
|
||||
@customElement("ha-outlined-icon-button")
|
||||
export class HaOutlinedIconButton extends IconButton {
|
||||
protected override getRenderClasses() {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
outlined: true,
|
||||
};
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
css`
|
||||
.icon-button {
|
||||
|
@@ -210,6 +210,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _unsubPersistentNotifications: UnsubscribeFunc | undefined;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "sidebarPanelOrder",
|
||||
state: true,
|
||||
@@ -217,6 +218,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
private _panelOrder: string[] = [];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "sidebarHiddenPanels",
|
||||
state: true,
|
||||
@@ -850,8 +852,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
.title {
|
||||
margin-left: 19px;
|
||||
margin-inline-start: 19px;
|
||||
margin-left: 3px;
|
||||
margin-inline-start: 3px;
|
||||
margin-inline-end: initial;
|
||||
width: 100%;
|
||||
display: none;
|
||||
@@ -938,7 +940,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
|
||||
ha-md-list-item .item-text {
|
||||
display: none;
|
||||
max-width: calc(100% - 56px);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@@ -24,6 +24,10 @@ export class HaToast extends Snackbar {
|
||||
max-width: 650px;
|
||||
}
|
||||
|
||||
.mdc-snackbar__actions {
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
}
|
||||
|
||||
/* Revert the default styles set by mwc-snackbar */
|
||||
@media (max-width: 480px), (max-width: 344px) {
|
||||
.mdc-snackbar__surface {
|
||||
|
@@ -2,6 +2,7 @@ import "@material/mwc-button/mwc-button";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { mdiContentCopy } from "@mdi/js";
|
||||
import { storage } from "../../common/decorators/storage";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
@@ -17,6 +18,8 @@ import "../ha-language-picker";
|
||||
import "../ha-tts-voice-picker";
|
||||
import "../ha-card";
|
||||
import { fetchCloudStatus } from "../../data/cloud";
|
||||
import { copyToClipboard } from "../../common/util/copy-clipboard";
|
||||
import { showToast } from "../../util/toast";
|
||||
|
||||
export interface TtsMediaPickedEvent {
|
||||
item: MediaPlayerItem;
|
||||
@@ -42,6 +45,7 @@ class BrowseMediaTTS extends LitElement {
|
||||
|
||||
@state() private _provider?: TTSEngine;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "TtsMessage",
|
||||
state: true,
|
||||
@@ -50,50 +54,69 @@ class BrowseMediaTTS extends LitElement {
|
||||
private _message?: string;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-card>
|
||||
<div class="card-content">
|
||||
<ha-textarea
|
||||
autogrow
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.media-browser.tts.message"
|
||||
)}
|
||||
.value=${this._message ||
|
||||
this.hass.localize(
|
||||
"ui.components.media-browser.tts.example_message",
|
||||
{
|
||||
name: this.hass.user?.name || "Alice",
|
||||
}
|
||||
)}
|
||||
>
|
||||
</ha-textarea>
|
||||
${this._provider?.supported_languages?.length
|
||||
? html` <div class="options">
|
||||
<ha-language-picker
|
||||
.hass=${this.hass}
|
||||
.languages=${this._provider.supported_languages}
|
||||
.value=${this._language}
|
||||
required
|
||||
@value-changed=${this._languageChanged}
|
||||
></ha-language-picker>
|
||||
<ha-tts-voice-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._voice}
|
||||
.engineId=${this._provider.engine_id}
|
||||
.language=${this._language}
|
||||
required
|
||||
@value-changed=${this._voiceChanged}
|
||||
></ha-tts-voice-picker>
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._ttsClicked}>
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.tts.action_${this.action}`
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card> `;
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<ha-textarea
|
||||
autogrow
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.media-browser.tts.message"
|
||||
)}
|
||||
.value=${this._message ||
|
||||
this.hass.localize(
|
||||
"ui.components.media-browser.tts.example_message",
|
||||
{
|
||||
name: this.hass.user?.name || "Alice",
|
||||
}
|
||||
)}
|
||||
>
|
||||
</ha-textarea>
|
||||
${this._provider?.supported_languages?.length
|
||||
? html` <div class="options">
|
||||
<ha-language-picker
|
||||
.hass=${this.hass}
|
||||
.languages=${this._provider.supported_languages}
|
||||
.value=${this._language}
|
||||
required
|
||||
@value-changed=${this._languageChanged}
|
||||
></ha-language-picker>
|
||||
<ha-tts-voice-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._voice}
|
||||
.engineId=${this._provider.engine_id}
|
||||
.language=${this._language}
|
||||
required
|
||||
@value-changed=${this._voiceChanged}
|
||||
></ha-tts-voice-picker>
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._ttsClicked}>
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.tts.action_${this.action}`
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
${this._voice
|
||||
? html`
|
||||
<div class="footer">
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.tts.selected_voice_id`
|
||||
)}
|
||||
<code>${this._voice || "-"}</code>
|
||||
<ha-icon-button
|
||||
.path=${mdiContentCopy}
|
||||
@click=${this._copyVoiceId}
|
||||
title=${this.hass.localize(
|
||||
"ui.components.media-browser.tts.copy_voice_id"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
protected override willUpdate(changedProps: PropertyValues): void {
|
||||
@@ -196,6 +219,14 @@ class BrowseMediaTTS extends LitElement {
|
||||
fireEvent(this, "tts-picked", { item });
|
||||
}
|
||||
|
||||
private async _copyVoiceId(ev) {
|
||||
ev.preventDefault();
|
||||
await copyToClipboard(this._voice);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
buttonLinkStyle,
|
||||
css`
|
||||
@@ -217,6 +248,23 @@ class BrowseMediaTTS extends LitElement {
|
||||
button.link {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.footer {
|
||||
font-size: var(--ha-font-size-s);
|
||||
color: var(--secondary-text-color);
|
||||
margin: 16px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.footer code {
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
}
|
||||
.footer {
|
||||
--mdc-icon-size: 14px;
|
||||
--mdc-icon-button-size: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -890,12 +890,18 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
margin-right: 48px;
|
||||
margin-inline-end: 48px;
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.highlight-add-button ha-svg-icon {
|
||||
position: relative;
|
||||
top: -0.5em;
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
transform: scaleX(var(--scale-direction));
|
||||
}
|
||||
|
||||
.content {
|
||||
|
@@ -85,7 +85,6 @@ class SearchInputOutlined extends LitElement {
|
||||
display: inline-flex;
|
||||
/* For iOS */
|
||||
z-index: 0;
|
||||
--mdc-icon-button-size: 24px;
|
||||
}
|
||||
ha-outlined-text-field {
|
||||
display: block;
|
||||
@@ -94,6 +93,8 @@ class SearchInputOutlined extends LitElement {
|
||||
}
|
||||
ha-svg-icon,
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
height: var(--mdc-icon-button-size);
|
||||
display: flex;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
@@ -11,8 +11,8 @@ export class HaTimeline extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public raised = false;
|
||||
|
||||
@property({ attribute: false, reflect: true, type: Boolean }) notEnabled =
|
||||
false;
|
||||
@property({ attribute: "not-enabled", reflect: true, type: Boolean })
|
||||
notEnabled = false;
|
||||
|
||||
@property({ attribute: "last-item", type: Boolean }) public lastItem = false;
|
||||
|
||||
@@ -82,7 +82,7 @@ export class HaTimeline extends LitElement {
|
||||
margin-inline-start: initial;
|
||||
width: 24px;
|
||||
}
|
||||
:host([notEnabled]) ha-svg-icon {
|
||||
:host([not-enabled]) ha-svg-icon {
|
||||
opacity: 0.5;
|
||||
}
|
||||
ha-svg-icon {
|
||||
|
@@ -17,8 +17,8 @@ export class HatGraphNode extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public error = false;
|
||||
|
||||
@property({ attribute: false, reflect: true, type: Boolean }) notEnabled =
|
||||
false;
|
||||
@property({ attribute: "not-enabled", reflect: true, type: Boolean })
|
||||
notEnabled = false;
|
||||
|
||||
@property({ attribute: "graph-start", reflect: true, type: Boolean })
|
||||
graphStart = false;
|
||||
@@ -127,13 +127,13 @@ export class HatGraphNode extends LitElement {
|
||||
--stroke-clr: var(--hover-clr);
|
||||
--icon-clr: var(--default-icon-clr);
|
||||
}
|
||||
:host([notEnabled]) circle {
|
||||
:host([not-enabled]) circle {
|
||||
--stroke-clr: var(--disabled-clr);
|
||||
}
|
||||
:host([notEnabled][active]) circle {
|
||||
:host([not-enabled][active]) circle {
|
||||
--stroke-clr: var(--disabled-active-clr);
|
||||
}
|
||||
:host([notEnabled]:hover) circle {
|
||||
:host([not-enabled]:hover) circle {
|
||||
--stroke-clr: var(--disabled-hover-clr);
|
||||
}
|
||||
svg:not(.safari) {
|
||||
|
@@ -492,6 +492,25 @@ export const getAutomationEditorInitData = () => {
|
||||
return data;
|
||||
};
|
||||
|
||||
export const isTrigger = (config: unknown): boolean => {
|
||||
if (!config || typeof config !== "object") {
|
||||
return false;
|
||||
}
|
||||
const trigger = config as Record<string, unknown>;
|
||||
return (
|
||||
("trigger" in trigger && typeof trigger.trigger === "string") ||
|
||||
("platform" in trigger && typeof trigger.platform === "string")
|
||||
);
|
||||
};
|
||||
|
||||
export const isCondition = (config: unknown): boolean => {
|
||||
if (!config || typeof config !== "object") {
|
||||
return false;
|
||||
}
|
||||
const condition = config as Record<string, unknown>;
|
||||
return "condition" in condition && typeof condition.condition === "string";
|
||||
};
|
||||
|
||||
export const subscribeTrigger = (
|
||||
hass: HomeAssistant,
|
||||
onChange: (result: {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import type { HassConfig, HassEntity } from "home-assistant-js-websocket";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import {
|
||||
formatDurationLong,
|
||||
@@ -155,7 +155,7 @@ const tryDescribeTrigger = (
|
||||
|
||||
const stateObj = Array.isArray(trigger.entity_id)
|
||||
? hass.states[trigger.entity_id[0]]
|
||||
: hass.states[trigger.entity_id];
|
||||
: (hass.states[trigger.entity_id] as HassEntity | undefined);
|
||||
|
||||
if (Array.isArray(trigger.entity_id)) {
|
||||
for (const entity of trigger.entity_id.values()) {
|
||||
@@ -172,12 +172,14 @@ const tryDescribeTrigger = (
|
||||
}
|
||||
|
||||
const attribute = trigger.attribute
|
||||
? computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
trigger.attribute
|
||||
)
|
||||
? stateObj
|
||||
? computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
trigger.attribute
|
||||
)
|
||||
: trigger.attribute
|
||||
: undefined;
|
||||
|
||||
const duration = trigger.for
|
||||
@@ -232,13 +234,15 @@ const tryDescribeTrigger = (
|
||||
if (trigger.attribute) {
|
||||
const stateObj = Array.isArray(trigger.entity_id)
|
||||
? hass.states[trigger.entity_id[0]]
|
||||
: hass.states[trigger.entity_id];
|
||||
attribute = computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
trigger.attribute
|
||||
);
|
||||
: (hass.states[trigger.entity_id] as HassEntity | undefined);
|
||||
attribute = stateObj
|
||||
? computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
trigger.attribute
|
||||
)
|
||||
: trigger.attribute;
|
||||
}
|
||||
|
||||
const entityArray: string[] = ensureArray(trigger.entity_id);
|
||||
@@ -250,7 +254,7 @@ const tryDescribeTrigger = (
|
||||
}
|
||||
}
|
||||
|
||||
const stateObj = hass.states[entityArray[0]];
|
||||
const stateObj = hass.states[entityArray[0]] as HassEntity | undefined;
|
||||
|
||||
let fromChoice = "other";
|
||||
let fromString = "";
|
||||
@@ -266,15 +270,17 @@ const tryDescribeTrigger = (
|
||||
const from: string[] = [];
|
||||
for (const state of fromArray) {
|
||||
from.push(
|
||||
trigger.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
trigger.attribute,
|
||||
state
|
||||
)
|
||||
.toString()
|
||||
: hass.formatEntityState(stateObj, state)
|
||||
stateObj
|
||||
? trigger.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
trigger.attribute,
|
||||
state
|
||||
)
|
||||
.toString()
|
||||
: hass.formatEntityState(stateObj, state)
|
||||
: state
|
||||
);
|
||||
}
|
||||
if (from.length !== 0) {
|
||||
@@ -298,15 +304,17 @@ const tryDescribeTrigger = (
|
||||
const to: string[] = [];
|
||||
for (const state of toArray) {
|
||||
to.push(
|
||||
trigger.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
trigger.attribute,
|
||||
state
|
||||
)
|
||||
.toString()
|
||||
: hass.formatEntityState(stateObj, state).toString()
|
||||
stateObj
|
||||
? trigger.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
trigger.attribute,
|
||||
state
|
||||
)
|
||||
.toString()
|
||||
: hass.formatEntityState(stateObj, state).toString()
|
||||
: state
|
||||
);
|
||||
}
|
||||
if (to.length !== 0) {
|
||||
@@ -725,7 +733,9 @@ const tryDescribeTrigger = (
|
||||
if (localized) {
|
||||
return localized;
|
||||
}
|
||||
const stateObj = hass.states[config.entity_id as string];
|
||||
const stateObj = hass.states[config.entity_id as string] as
|
||||
| HassEntity
|
||||
| undefined;
|
||||
return `${stateObj ? computeStateName(stateObj) : config.entity_id} ${
|
||||
config.type
|
||||
}`;
|
||||
@@ -894,13 +904,15 @@ const tryDescribeCondition = (
|
||||
if (condition.attribute) {
|
||||
const stateObj = Array.isArray(condition.entity_id)
|
||||
? hass.states[condition.entity_id[0]]
|
||||
: hass.states[condition.entity_id];
|
||||
attribute = computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
condition.attribute
|
||||
);
|
||||
: (hass.states[condition.entity_id] as HassEntity | undefined);
|
||||
attribute = stateObj
|
||||
? computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
condition.attribute
|
||||
)
|
||||
: condition.attribute;
|
||||
}
|
||||
|
||||
const entities: string[] = [];
|
||||
@@ -919,37 +931,40 @@ const tryDescribeCondition = (
|
||||
}
|
||||
|
||||
const states: string[] = [];
|
||||
const stateObj =
|
||||
hass.states[
|
||||
Array.isArray(condition.entity_id)
|
||||
? condition.entity_id[0]
|
||||
: condition.entity_id
|
||||
];
|
||||
const stateObj = hass.states[
|
||||
Array.isArray(condition.entity_id)
|
||||
? condition.entity_id[0]
|
||||
: condition.entity_id
|
||||
] as HassEntity | undefined;
|
||||
if (Array.isArray(condition.state)) {
|
||||
for (const state of condition.state.values()) {
|
||||
states.push(
|
||||
condition.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
condition.attribute,
|
||||
state
|
||||
)
|
||||
.toString()
|
||||
: hass.formatEntityState(stateObj, state)
|
||||
stateObj
|
||||
? condition.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
condition.attribute,
|
||||
state
|
||||
)
|
||||
.toString()
|
||||
: hass.formatEntityState(stateObj, state)
|
||||
: state
|
||||
);
|
||||
}
|
||||
} else if (condition.state !== "") {
|
||||
states.push(
|
||||
condition.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
condition.attribute,
|
||||
condition.state
|
||||
)
|
||||
.toString()
|
||||
: hass.formatEntityState(stateObj, condition.state.toString())
|
||||
stateObj
|
||||
? condition.attribute
|
||||
? hass
|
||||
.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
condition.attribute,
|
||||
condition.state
|
||||
)
|
||||
.toString()
|
||||
: hass.formatEntityState(stateObj, condition.state.toString())
|
||||
: condition.state.toString()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -979,7 +994,7 @@ const tryDescribeCondition = (
|
||||
// Numeric State Condition
|
||||
if (condition.condition === "numeric_state" && condition.entity_id) {
|
||||
const entity_ids = ensureArray(condition.entity_id);
|
||||
const stateObj = hass.states[entity_ids[0]];
|
||||
const stateObj = hass.states[entity_ids[0]] as HassEntity | undefined;
|
||||
const entity = formatListWithAnds(
|
||||
hass.locale,
|
||||
entity_ids.map((id) =>
|
||||
@@ -988,12 +1003,14 @@ const tryDescribeCondition = (
|
||||
);
|
||||
|
||||
const attribute = condition.attribute
|
||||
? computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
condition.attribute
|
||||
)
|
||||
? stateObj
|
||||
? computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
condition.attribute
|
||||
)
|
||||
: condition.attribute
|
||||
: undefined;
|
||||
|
||||
if (condition.above !== undefined && condition.below !== undefined) {
|
||||
@@ -1187,7 +1204,9 @@ const tryDescribeCondition = (
|
||||
if (localized) {
|
||||
return localized;
|
||||
}
|
||||
const stateObj = hass.states[config.entity_id as string];
|
||||
const stateObj = hass.states[config.entity_id as string] as
|
||||
| HassEntity
|
||||
| undefined;
|
||||
return `${stateObj ? computeStateName(stateObj) : config.entity_id} ${
|
||||
config.type
|
||||
}`;
|
||||
|
@@ -959,21 +959,13 @@ const computeConsumptionDataPartial = (
|
||||
};
|
||||
|
||||
data.timestamps.forEach((t) => {
|
||||
const used_total =
|
||||
(data.from_grid?.[t] || 0) +
|
||||
(data.solar?.[t] || 0) +
|
||||
(data.from_battery?.[t] || 0) -
|
||||
(data.to_grid?.[t] || 0) -
|
||||
(data.to_battery?.[t] || 0);
|
||||
|
||||
outData.used_total[t] = used_total;
|
||||
outData.total.used_total += used_total;
|
||||
const {
|
||||
grid_to_battery,
|
||||
battery_to_grid,
|
||||
used_solar,
|
||||
used_grid,
|
||||
used_battery,
|
||||
used_total,
|
||||
solar_to_battery,
|
||||
solar_to_grid,
|
||||
} = computeConsumptionSingle({
|
||||
@@ -984,6 +976,8 @@ const computeConsumptionDataPartial = (
|
||||
from_battery: data.from_battery && (data.from_battery[t] ?? 0),
|
||||
});
|
||||
|
||||
outData.used_total[t] = used_total;
|
||||
outData.total.used_total += used_total;
|
||||
outData.grid_to_battery[t] = grid_to_battery;
|
||||
outData.total.grid_to_battery += grid_to_battery;
|
||||
outData.battery_to_grid![t] = battery_to_grid;
|
||||
@@ -1017,12 +1011,20 @@ export const computeConsumptionSingle = (data: {
|
||||
used_solar: number;
|
||||
used_grid: number;
|
||||
used_battery: number;
|
||||
used_total: number;
|
||||
} => {
|
||||
const to_grid = data.to_grid;
|
||||
const to_battery = data.to_battery;
|
||||
const solar = data.solar;
|
||||
const from_grid = data.from_grid;
|
||||
const from_battery = data.from_battery;
|
||||
let to_grid = Math.max(data.to_grid || 0, 0);
|
||||
let to_battery = Math.max(data.to_battery || 0, 0);
|
||||
let solar = Math.max(data.solar || 0, 0);
|
||||
let from_grid = Math.max(data.from_grid || 0, 0);
|
||||
let from_battery = Math.max(data.from_battery || 0, 0);
|
||||
|
||||
const used_total =
|
||||
(from_grid || 0) +
|
||||
(solar || 0) +
|
||||
(from_battery || 0) -
|
||||
(to_grid || 0) -
|
||||
(to_battery || 0);
|
||||
|
||||
let used_solar = 0;
|
||||
let grid_to_battery = 0;
|
||||
@@ -1031,41 +1033,57 @@ export const computeConsumptionSingle = (data: {
|
||||
let solar_to_grid = 0;
|
||||
let used_battery = 0;
|
||||
let used_grid = 0;
|
||||
if ((to_grid != null || to_battery != null) && solar != null) {
|
||||
used_solar = (solar || 0) - (to_grid || 0) - (to_battery || 0);
|
||||
if (used_solar < 0) {
|
||||
if (to_battery != null) {
|
||||
grid_to_battery = used_solar * -1;
|
||||
if (grid_to_battery > (from_grid || 0)) {
|
||||
battery_to_grid = grid_to_battery - (from_grid || 0);
|
||||
grid_to_battery = from_grid || 0;
|
||||
}
|
||||
}
|
||||
used_solar = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (from_battery != null) {
|
||||
used_battery = (from_battery || 0) - battery_to_grid;
|
||||
}
|
||||
let used_total_remaining = Math.max(used_total, 0);
|
||||
// Consumption Priority
|
||||
// Battery_Out -> Grid_Out
|
||||
// Solar -> Grid_Out
|
||||
// Solar -> Battery_In
|
||||
// Grid_In -> Battery_In
|
||||
// Solar -> Consumption
|
||||
// Battery_Out -> Consumption
|
||||
// Grid_In -> Consumption
|
||||
|
||||
if (from_grid != null) {
|
||||
used_grid = from_grid - grid_to_battery;
|
||||
}
|
||||
// Battery_Out -> Grid_Out
|
||||
battery_to_grid = Math.min(from_battery, to_grid);
|
||||
from_battery -= battery_to_grid;
|
||||
to_grid -= battery_to_grid;
|
||||
|
||||
if (solar != null) {
|
||||
if (to_battery != null) {
|
||||
solar_to_battery = Math.max(0, (to_battery || 0) - grid_to_battery);
|
||||
}
|
||||
if (to_grid != null) {
|
||||
solar_to_grid = Math.max(0, (to_grid || 0) - battery_to_grid);
|
||||
}
|
||||
}
|
||||
// Solar -> Grid_Out
|
||||
solar_to_grid = Math.min(solar, to_grid);
|
||||
to_grid -= solar_to_grid;
|
||||
solar -= solar_to_grid;
|
||||
|
||||
// Solar -> Battery_In
|
||||
solar_to_battery = Math.min(solar, to_battery);
|
||||
to_battery -= solar_to_battery;
|
||||
solar -= solar_to_battery;
|
||||
|
||||
// Grid_In -> Battery_In
|
||||
grid_to_battery = Math.min(from_grid, to_battery);
|
||||
from_grid -= grid_to_battery;
|
||||
to_battery -= grid_to_battery;
|
||||
|
||||
// Solar -> Consumption
|
||||
used_solar = Math.min(used_total_remaining, solar);
|
||||
used_total_remaining -= used_solar;
|
||||
solar -= used_solar;
|
||||
|
||||
// Battery_Out -> Consumption
|
||||
used_battery = Math.min(from_battery, used_total_remaining);
|
||||
from_battery -= used_battery;
|
||||
used_total_remaining -= used_battery;
|
||||
|
||||
// Grid_In -> Consumption
|
||||
used_grid = Math.min(used_total_remaining, from_grid);
|
||||
from_grid -= used_grid;
|
||||
used_total_remaining -= from_grid;
|
||||
|
||||
return {
|
||||
used_solar,
|
||||
used_grid,
|
||||
used_battery,
|
||||
used_total,
|
||||
grid_to_battery,
|
||||
battery_to_grid,
|
||||
solar_to_battery,
|
||||
|
@@ -107,3 +107,70 @@ export const DOMAIN_ATTRIBUTES_FORMATERS: Record<
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const NON_NUMERIC_ATTRIBUTES = [
|
||||
"access_token",
|
||||
"auto_update",
|
||||
"available_modes",
|
||||
"away_mode",
|
||||
"changed_by",
|
||||
"code_format",
|
||||
"color_modes",
|
||||
"current_activity",
|
||||
"device_class",
|
||||
"editable",
|
||||
"effect_list",
|
||||
"effect",
|
||||
"entity_picture",
|
||||
"event_type",
|
||||
"event_types",
|
||||
"fan_mode",
|
||||
"fan_modes",
|
||||
"fan_speed_list",
|
||||
"forecast",
|
||||
"friendly_name",
|
||||
"frontend_stream_type",
|
||||
"has_date",
|
||||
"has_time",
|
||||
"hs_color",
|
||||
"hvac_mode",
|
||||
"hvac_modes",
|
||||
"icon",
|
||||
"media_album_name",
|
||||
"media_artist",
|
||||
"media_content_type",
|
||||
"media_position_updated_at",
|
||||
"media_title",
|
||||
"next_dawn",
|
||||
"next_dusk",
|
||||
"next_midnight",
|
||||
"next_noon",
|
||||
"next_rising",
|
||||
"next_setting",
|
||||
"operation_list",
|
||||
"operation_mode",
|
||||
"options",
|
||||
"preset_mode",
|
||||
"preset_modes",
|
||||
"release_notes",
|
||||
"release_summary",
|
||||
"release_url",
|
||||
"restored",
|
||||
"rgb_color",
|
||||
"rgbw_color",
|
||||
"shuffle",
|
||||
"sound_mode_list",
|
||||
"sound_mode",
|
||||
"source_list",
|
||||
"source_type",
|
||||
"source",
|
||||
"state_class",
|
||||
"supported_features",
|
||||
"swing_mode",
|
||||
"swing_mode",
|
||||
"swing_modes",
|
||||
"title",
|
||||
"token",
|
||||
"unit_of_measurement",
|
||||
"xy_color",
|
||||
];
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiClose, mdiHelpCircle } from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
@@ -177,6 +176,17 @@ class DataEntryFlowDialog extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const showDocumentationLink =
|
||||
([
|
||||
"form",
|
||||
"menu",
|
||||
"external",
|
||||
"progress",
|
||||
"data_entry_flow_progressed",
|
||||
].includes(this._step?.type as any) &&
|
||||
this._params.manifest?.is_built_in) ||
|
||||
!!this._params.manifest?.documentation;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@@ -191,7 +201,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
<step-flow-loading
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.hass=${this.hass}
|
||||
.loadingReason=${this._loading}
|
||||
.loadingReason=${this._loading!}
|
||||
.handler=${this._handler}
|
||||
.step=${this._step}
|
||||
></step-flow-loading>
|
||||
@@ -199,26 +209,18 @@ class DataEntryFlowDialog extends LitElement {
|
||||
: this._step === undefined
|
||||
? // When we are going to next step, we render 1 round of empty
|
||||
// to reset the element.
|
||||
""
|
||||
nothing
|
||||
: html`
|
||||
<div class="dialog-actions">
|
||||
${([
|
||||
"form",
|
||||
"menu",
|
||||
"external",
|
||||
"progress",
|
||||
"data_entry_flow_progressed",
|
||||
].includes(this._step?.type as any) &&
|
||||
this._params.manifest?.is_built_in) ||
|
||||
this._params.manifest?.documentation
|
||||
${showDocumentationLink
|
||||
? html`
|
||||
<a
|
||||
href=${this._params.manifest.is_built_in
|
||||
href=${this._params.manifest!.is_built_in
|
||||
? documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${this._params.manifest.domain}`
|
||||
`/integrations/${this._params.manifest!.domain}`
|
||||
)
|
||||
: this._params?.manifest?.documentation}
|
||||
: this._params.manifest!.documentation}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
@@ -229,7 +231,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
</ha-icon-button
|
||||
></a>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
@@ -242,6 +244,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
.increasePaddingEnd=${showDocumentationLink}
|
||||
></step-flow-form>
|
||||
`
|
||||
: this._step.type === "external"
|
||||
@@ -250,6 +253,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
.increasePaddingEnd=${showDocumentationLink}
|
||||
></step-flow-external>
|
||||
`
|
||||
: this._step.type === "abort"
|
||||
@@ -261,6 +265,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
.handler=${this._step.handler}
|
||||
.domain=${this._params.domain ??
|
||||
this._step.handler}
|
||||
.increasePaddingEnd=${showDocumentationLink}
|
||||
></step-flow-abort>
|
||||
`
|
||||
: this._step.type === "progress"
|
||||
@@ -270,6 +275,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
.progress=${this._progress}
|
||||
.increasePaddingEnd=${showDocumentationLink}
|
||||
></step-flow-progress>
|
||||
`
|
||||
: this._step.type === "menu"
|
||||
@@ -278,6 +284,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
.increasePaddingEnd=${showDocumentationLink}
|
||||
></step-flow-menu>
|
||||
`
|
||||
: html`
|
||||
@@ -286,7 +293,8 @@ class DataEntryFlowDialog extends LitElement {
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
.navigateToResult=${this._params
|
||||
.navigateToResult}
|
||||
.navigateToResult ?? false}
|
||||
.increasePaddingEnd=${showDocumentationLink}
|
||||
></step-flow-create-entry>
|
||||
`}
|
||||
`}
|
||||
|
@@ -22,6 +22,9 @@ class StepFlowAbort extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public handler!: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "increase-padding-end" })
|
||||
public increasePaddingEnd = false;
|
||||
|
||||
protected firstUpdated(changed: PropertyValues) {
|
||||
super.firstUpdated(changed);
|
||||
if (this.step.reason === "missing_credentials") {
|
||||
@@ -34,7 +37,7 @@ class StepFlowAbort extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<h2>
|
||||
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
|
||||
${this.params.flowConfig.renderAbortHeader
|
||||
? this.params.flowConfig.renderAbortHeader(this.hass, this.step)
|
||||
: this.hass.localize(`component.${this.domain}.title`)}
|
||||
|
@@ -36,6 +36,9 @@ class StepFlowCreateEntry extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public step!: DataEntryFlowStepCreateEntry;
|
||||
|
||||
@property({ type: Boolean, attribute: "increase-padding-end" })
|
||||
public increasePaddingEnd = false;
|
||||
|
||||
public navigateToResult = false;
|
||||
|
||||
@state() private _deviceUpdate: Record<
|
||||
@@ -113,7 +116,7 @@ class StepFlowCreateEntry extends LitElement {
|
||||
this.step.result?.entry_id
|
||||
);
|
||||
return html`
|
||||
<h2>
|
||||
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
|
||||
${devices.length
|
||||
? localize("ui.panel.config.integrations.config_flow.assign_area", {
|
||||
number: devices.length,
|
||||
@@ -129,70 +132,73 @@ class StepFlowCreateEntry extends LitElement {
|
||||
)}</span
|
||||
>`
|
||||
: nothing}
|
||||
${devices.length === 0
|
||||
? html`<p>
|
||||
${localize(
|
||||
"ui.panel.config.integrations.config_flow.created_config",
|
||||
{ name: this.step.title }
|
||||
)}
|
||||
</p>`
|
||||
: html`
|
||||
<div class="devices">
|
||||
${devices.map(
|
||||
(device) => html`
|
||||
<div class="device">
|
||||
<div class="device-info">
|
||||
${this.step.result?.domain
|
||||
? html`<img
|
||||
slot="graphic"
|
||||
alt=${domainToName(
|
||||
this.hass.localize,
|
||||
this.step.result.domain
|
||||
)}
|
||||
src=${brandsUrl({
|
||||
domain: this.step.result.domain,
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>`
|
||||
: nothing}
|
||||
<div class="device-info-details">
|
||||
<span>${device.model || device.manufacturer}</span>
|
||||
${device.model
|
||||
? html`<span class="secondary">
|
||||
${device.manufacturer}
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
<ha-textfield
|
||||
.label=${localize(
|
||||
"ui.panel.config.integrations.config_flow.device_name"
|
||||
)}
|
||||
.placeholder=${computeDeviceNameDisplay(
|
||||
device,
|
||||
this.hass
|
||||
)}
|
||||
.value=${this._deviceUpdate[device.id]?.name ??
|
||||
computeDeviceName(device)}
|
||||
@change=${this._deviceNameChanged}
|
||||
.device=${device.id}
|
||||
></ha-textfield>
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.device=${device.id}
|
||||
.value=${this._deviceUpdate[device.id]?.area ??
|
||||
device.area_id ??
|
||||
undefined}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>
|
||||
</div>
|
||||
`
|
||||
${devices.length === 0 &&
|
||||
["options_flow", "repair_flow"].includes(this.flowConfig.flowType)
|
||||
? nothing
|
||||
: devices.length === 0
|
||||
? html`<p>
|
||||
${localize(
|
||||
"ui.panel.config.integrations.config_flow.created_config",
|
||||
{ name: this.step.title }
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</p>`
|
||||
: html`
|
||||
<div class="devices">
|
||||
${devices.map(
|
||||
(device) => html`
|
||||
<div class="device">
|
||||
<div class="device-info">
|
||||
${this.step.result?.domain
|
||||
? html`<img
|
||||
slot="graphic"
|
||||
alt=${domainToName(
|
||||
this.hass.localize,
|
||||
this.step.result.domain
|
||||
)}
|
||||
src=${brandsUrl({
|
||||
domain: this.step.result.domain,
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>`
|
||||
: nothing}
|
||||
<div class="device-info-details">
|
||||
<span>${device.model || device.manufacturer}</span>
|
||||
${device.model
|
||||
? html`<span class="secondary">
|
||||
${device.manufacturer}
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
<ha-textfield
|
||||
.label=${localize(
|
||||
"ui.panel.config.integrations.config_flow.device_name"
|
||||
)}
|
||||
.placeholder=${computeDeviceNameDisplay(
|
||||
device,
|
||||
this.hass
|
||||
)}
|
||||
.value=${this._deviceUpdate[device.id]?.name ??
|
||||
computeDeviceName(device)}
|
||||
@change=${this._deviceNameChanged}
|
||||
.device=${device.id}
|
||||
></ha-textfield>
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.device=${device.id}
|
||||
.value=${this._deviceUpdate[device.id]?.area ??
|
||||
device.area_id ??
|
||||
undefined}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<mwc-button @click=${this._flowDone}
|
||||
|
@@ -15,11 +15,16 @@ class StepFlowExternal extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public step!: DataEntryFlowStepExternal;
|
||||
|
||||
@property({ type: Boolean, attribute: "increase-padding-end" })
|
||||
public increasePaddingEnd = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const localize = this.hass.localize;
|
||||
|
||||
return html`
|
||||
<h2>${this.flowConfig.renderExternalStepHeader(this.hass, this.step)}</h2>
|
||||
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
|
||||
${this.flowConfig.renderExternalStepHeader(this.hass, this.step)}
|
||||
</h2>
|
||||
<div class="content">
|
||||
${this.flowConfig.renderExternalStepDescription(this.hass, this.step)}
|
||||
<div class="open-button">
|
||||
@@ -51,6 +56,9 @@ class StepFlowExternal extends LitElement {
|
||||
.open-button a {
|
||||
text-decoration: none;
|
||||
}
|
||||
h2.end-space {
|
||||
padding-inline-end: 72px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -27,6 +27,9 @@ class StepFlowForm extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, attribute: "increase-padding-end" })
|
||||
public increasePaddingEnd = false;
|
||||
|
||||
@state() private _loading = false;
|
||||
|
||||
@state() private _stepData?: Record<string, any>;
|
||||
@@ -43,7 +46,9 @@ class StepFlowForm extends LitElement {
|
||||
const stepData = this._stepDataProcessed;
|
||||
|
||||
return html`
|
||||
<h2>${this.flowConfig.renderShowFormStepHeader(this.hass, this.step)}</h2>
|
||||
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
|
||||
${this.flowConfig.renderShowFormStepHeader(this.hass, this.step)}
|
||||
</h2>
|
||||
<div class="content" @click=${this._clickHandler}>
|
||||
${this.flowConfig.renderShowFormStepDescription(this.hass, this.step)}
|
||||
${this._errorMsg
|
||||
@@ -278,8 +283,6 @@ class StepFlowForm extends LitElement {
|
||||
}
|
||||
h2 {
|
||||
word-break: break-word;
|
||||
padding-inline-end: 72px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -17,6 +17,9 @@ class StepFlowMenu extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public step!: DataEntryFlowStepMenu;
|
||||
|
||||
@property({ type: Boolean, attribute: "increase-padding-end" })
|
||||
public increasePaddingEnd = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
let options: string[];
|
||||
let translations: Record<string, string>;
|
||||
@@ -42,7 +45,9 @@ class StepFlowMenu extends LitElement {
|
||||
);
|
||||
|
||||
return html`
|
||||
<h2>${this.flowConfig.renderMenuHeader(this.hass, this.step)}</h2>
|
||||
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
|
||||
${this.flowConfig.renderMenuHeader(this.hass, this.step)}
|
||||
</h2>
|
||||
${description ? html`<div class="content">${description}</div>` : ""}
|
||||
<div class="options">
|
||||
${options.map(
|
||||
|
@@ -24,9 +24,12 @@ class StepFlowProgress extends LitElement {
|
||||
@property({ type: Number })
|
||||
public progress?: number;
|
||||
|
||||
@property({ type: Boolean, attribute: "increase-padding-end" })
|
||||
public increasePaddingEnd = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<h2>
|
||||
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
|
||||
${this.flowConfig.renderShowFormProgressHeader(this.hass, this.step)}
|
||||
</h2>
|
||||
<div class="content">
|
||||
|
@@ -22,6 +22,9 @@ export const configFlowContentStyles = css`
|
||||
text-transform: var(--mdc-typography-headline6-text-transform, inherit);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
h2.end-space {
|
||||
padding-inline-end: 72px;
|
||||
}
|
||||
|
||||
.content,
|
||||
.preview {
|
||||
|
@@ -57,7 +57,7 @@ class MoreInfoCover extends LitElement {
|
||||
);
|
||||
|
||||
if (positionStateDisplay) {
|
||||
return `${stateDisplay} ⸱ ${positionStateDisplay}`;
|
||||
return `${stateDisplay} · ${positionStateDisplay}`;
|
||||
}
|
||||
return stateDisplay;
|
||||
}
|
||||
|
@@ -57,7 +57,7 @@ class MoreInfoValve extends LitElement {
|
||||
);
|
||||
|
||||
if (positionStateDisplay) {
|
||||
return `${stateDisplay} ⸱ ${positionStateDisplay}`;
|
||||
return `${stateDisplay} · ${positionStateDisplay}`;
|
||||
}
|
||||
return stateDisplay;
|
||||
}
|
||||
|
@@ -15,24 +15,26 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import Fuse from "fuse.js";
|
||||
import { canShowPage } from "../../common/config/can_show_page";
|
||||
import { componentsWithService } from "../../common/config/components_with_service";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDeviceNameDisplay } from "../../common/entity/compute_device_name";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import {
|
||||
computeDeviceName,
|
||||
computeDeviceNameDisplay,
|
||||
} from "../../common/entity/compute_device_name";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
||||
import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-label";
|
||||
import "../../components/ha-list";
|
||||
import "../../components/ha-list-item";
|
||||
import "../../components/ha-spinner";
|
||||
import "../../components/ha-textfield";
|
||||
import "../../components/ha-tip";
|
||||
import "../../components/ha-md-list-item";
|
||||
import { fetchHassioAddonsInfo } from "../../data/hassio/addon";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { getPanelNameTranslationKey } from "../../data/panel";
|
||||
@@ -44,6 +46,13 @@ import type { HomeAssistant } from "../../types";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import { showShortcutsDialog } from "../shortcuts/show-shortcuts-dialog";
|
||||
import { QuickBarMode, type QuickBarParams } from "./show-dialog-quick-bar";
|
||||
import { getEntityContext } from "../../common/entity/context/get_entity_context";
|
||||
import { computeEntityName } from "../../common/entity/compute_entity_name";
|
||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { HaFuse } from "../../resources/fuse";
|
||||
|
||||
interface QuickBarItem extends ScorableTextItem {
|
||||
primaryText: string;
|
||||
@@ -59,6 +68,9 @@ interface CommandItem extends QuickBarItem {
|
||||
interface EntityItem extends QuickBarItem {
|
||||
altText: string;
|
||||
icon?: TemplateResult;
|
||||
translatedDomain: string;
|
||||
entityId: string;
|
||||
friendlyName: string;
|
||||
}
|
||||
|
||||
interface DeviceItem extends QuickBarItem {
|
||||
@@ -82,6 +94,7 @@ type BaseNavigationCommand = Pick<
|
||||
QuickBarNavigationItem,
|
||||
"primaryText" | "path"
|
||||
>;
|
||||
|
||||
@customElement("ha-quick-bar")
|
||||
export class QuickBar extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -139,6 +152,11 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.hass.loadBackendTranslation("title");
|
||||
}
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
(
|
||||
mode: QuickBarMode,
|
||||
@@ -323,61 +341,67 @@ export class QuickBar extends LitElement {
|
||||
|
||||
private _renderDeviceItem(item: DeviceItem, index?: number) {
|
||||
return html`
|
||||
<ha-list-item
|
||||
.twoline=${Boolean(item.area)}
|
||||
<ha-md-list-item
|
||||
class="two-line"
|
||||
.item=${item}
|
||||
index=${ifDefined(index)}
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span>${item.primaryText}</span>
|
||||
<span slot="headline">${item.primaryText}</span>
|
||||
${item.area
|
||||
? html`
|
||||
<span slot="secondary" class="item-text secondary"
|
||||
>${item.area}</span
|
||||
>
|
||||
`
|
||||
? html` <span slot="supporting-text">${item.area}</span> `
|
||||
: nothing}
|
||||
</ha-list-item>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderEntityItem(item: EntityItem, index?: number) {
|
||||
const showEntityId = this.hass.userData?.showEntityIdPicker;
|
||||
|
||||
return html`
|
||||
<ha-list-item
|
||||
.twoline=${Boolean(item.altText)}
|
||||
<ha-md-list-item
|
||||
class=${showEntityId ? "three-line" : "two-line"}
|
||||
.item=${item}
|
||||
index=${ifDefined(index)}
|
||||
graphic="icon"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
${item.iconPath
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${item.iconPath}
|
||||
class="entity"
|
||||
slot="graphic"
|
||||
slot="start"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: html`<span slot="graphic">${item.icon}</span>`}
|
||||
<span>${item.primaryText}</span>
|
||||
: html`<span slot="start">${item.icon}</span>`}
|
||||
<span slot="headline">${item.primaryText}</span>
|
||||
${item.altText
|
||||
? html` <span slot="supporting-text">${item.altText}</span> `
|
||||
: nothing}
|
||||
${item.entityId && showEntityId
|
||||
? html`
|
||||
<span slot="secondary" class="item-text secondary"
|
||||
>${item.altText}</span
|
||||
>
|
||||
<span slot="supporting-text" class="code">${item.entityId}</span>
|
||||
`
|
||||
: nothing}
|
||||
</ha-list-item>
|
||||
${item.translatedDomain && !showEntityId
|
||||
? html`<div slot="trailing-supporting-text">
|
||||
${item.translatedDomain}
|
||||
</div>`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderCommandItem(item: CommandItem, index?: number) {
|
||||
return html`
|
||||
<ha-list-item
|
||||
<ha-md-list-item
|
||||
.item=${item}
|
||||
index=${ifDefined(index)}
|
||||
hasMeta
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<ha-label
|
||||
@@ -386,7 +410,10 @@ export class QuickBar extends LitElement {
|
||||
>
|
||||
${item.iconPath
|
||||
? html`
|
||||
<ha-svg-icon .path=${item.iconPath} slot="icon"></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
.path=${item.iconPath}
|
||||
slot="start"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
${item.categoryText}
|
||||
@@ -394,7 +421,7 @@ export class QuickBar extends LitElement {
|
||||
</span>
|
||||
|
||||
<span class="command-text">${item.primaryText}</span>
|
||||
</ha-list-item>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -421,7 +448,7 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
private _getItemAtIndex(index: number): ListItem | null {
|
||||
return this.renderRoot.querySelector(`ha-list-item[index="${index}"]`);
|
||||
return this.renderRoot.querySelector(`ha-md-list-item[index="${index}"]`);
|
||||
}
|
||||
|
||||
private _addSpinnerToCommandItem(index: number): void {
|
||||
@@ -519,7 +546,7 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
private _handleItemClick(ev) {
|
||||
const listItem = ev.target.closest("ha-list-item");
|
||||
const listItem = ev.target.closest("ha-md-list-item");
|
||||
this._processItemAndCloseDialog(
|
||||
listItem.item,
|
||||
Number(listItem.getAttribute("index"))
|
||||
@@ -555,18 +582,41 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
private _generateEntityItems(): EntityItem[] {
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
return Object.keys(this.hass.states)
|
||||
.map((entityId) => {
|
||||
const entityState = this.hass.states[entityId];
|
||||
const stateObj = this.hass.states[entityId];
|
||||
|
||||
const { area, device } = getEntityContext(stateObj, this.hass);
|
||||
|
||||
const friendlyName = computeStateName(stateObj); // Keep this for search
|
||||
const entityName = computeEntityName(stateObj, this.hass);
|
||||
const deviceName = device ? computeDeviceName(device) : undefined;
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
|
||||
const primary = entityName || deviceName || entityId;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(isRTL ? " ◂ " : " ▸ ");
|
||||
|
||||
const translatedDomain = domainToName(
|
||||
this.hass.localize,
|
||||
computeDomain(entityId)
|
||||
);
|
||||
|
||||
const entityItem = {
|
||||
primaryText: computeStateName(entityState),
|
||||
altText: entityId,
|
||||
primaryText: primary,
|
||||
altText: secondary,
|
||||
icon: html`
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${entityState}
|
||||
.stateObj=${stateObj}
|
||||
></ha-state-icon>
|
||||
`,
|
||||
translatedDomain: translatedDomain,
|
||||
entityId: entityId,
|
||||
friendlyName: friendlyName,
|
||||
action: () => fireEvent(this, "hass-more-info", { entityId }),
|
||||
};
|
||||
|
||||
@@ -846,9 +896,30 @@ export class QuickBar extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _fuseIndex = memoizeOne((items: QuickBarItem[]) =>
|
||||
Fuse.createIndex(
|
||||
[
|
||||
"primaryText",
|
||||
"altText",
|
||||
"friendlyName",
|
||||
"translatedDomain",
|
||||
"entityId", // for technical search
|
||||
],
|
||||
items
|
||||
)
|
||||
);
|
||||
|
||||
private _filterItems = memoizeOne(
|
||||
(items: QuickBarItem[], filter: string): QuickBarItem[] =>
|
||||
fuzzyFilterSort<QuickBarItem>(filter.trimLeft(), items)
|
||||
(items: QuickBarItem[], filter: string): QuickBarItem[] => {
|
||||
const index = this._fuseIndex(items);
|
||||
const fuse = new HaFuse(items, {}, index);
|
||||
|
||||
const results = fuse.multiTermsSearch(filter.trim());
|
||||
if (!results || !results.length) {
|
||||
return items;
|
||||
}
|
||||
return results.map((result) => result.item);
|
||||
}
|
||||
);
|
||||
|
||||
static get styles() {
|
||||
@@ -930,9 +1001,41 @@ export class QuickBar extends LitElement {
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
ha-list-item {
|
||||
ha-md-list-item {
|
||||
width: 100%;
|
||||
--mdc-list-item-graphic-margin: 20px;
|
||||
}
|
||||
|
||||
/* Fixed height for items because we are use virtualizer */
|
||||
ha-md-list-item.two-line {
|
||||
--md-list-item-one-line-container-height: 64px;
|
||||
--md-list-item-two-line-container-height: 64px;
|
||||
--md-list-item-top-space: 8px;
|
||||
--md-list-item-bottom-space: 8px;
|
||||
}
|
||||
|
||||
ha-md-list-item.three-line {
|
||||
width: 100%;
|
||||
--md-list-item-one-line-container-height: 72px;
|
||||
--md-list-item-two-line-container-height: 72px;
|
||||
--md-list-item-three-line-container-height: 72px;
|
||||
--md-list-item-top-space: 8px;
|
||||
--md-list-item-bottom-space: 8px;
|
||||
}
|
||||
|
||||
ha-md-list-item .code {
|
||||
font-family: var(--ha-font-family-code);
|
||||
font-size: var(--ha-font-size-xs);
|
||||
}
|
||||
|
||||
ha-md-list-item [slot="trailing-supporting-text"] {
|
||||
font-size: var(--ha-font-size-s);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
line-height: var(--ha-line-height-normal);
|
||||
align-self: flex-end;
|
||||
max-width: 30%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
ha-tip {
|
||||
|
@@ -69,12 +69,17 @@ const _SHORTCUTS: Section[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "ui.dialogs.shortcuts.automations.title",
|
||||
key: "ui.dialogs.shortcuts.automation_script.title",
|
||||
items: [
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: [{ key: "ui.dialogs.shortcuts.shortcuts.ctrl_cmd" }, "V"],
|
||||
key: "ui.dialogs.shortcuts.automations.paste",
|
||||
key: "ui.dialogs.shortcuts.automation_script.paste",
|
||||
},
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: [{ key: "ui.dialogs.shortcuts.shortcuts.ctrl_cmd" }, "S"],
|
||||
key: "ui.dialogs.shortcuts.automation_script.save",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -407,6 +407,7 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
|
||||
align-items: center;
|
||||
margin-right: 12px;
|
||||
margin-inline-end: 12px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -36,6 +36,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "AssistPipelineId",
|
||||
state: true,
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import "@material/mwc-button";
|
||||
import "../components/ha-spinner";
|
||||
|
||||
class HaInitPage extends LitElement {
|
||||
@property({ type: Boolean }) public error = false;
|
||||
|
@@ -42,6 +42,7 @@ class PanelCalendar extends LitElement {
|
||||
|
||||
@state() private _error?: string = undefined;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "deSelectedCalendars",
|
||||
state: true,
|
||||
|
@@ -69,6 +69,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
})
|
||||
private _activeHiddenColumns?: string[];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "application-credentials-table-search",
|
||||
|
@@ -36,6 +36,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
|
||||
@state() private _showReorder = false;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: true,
|
||||
|
@@ -36,6 +36,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
|
||||
@state() private _showReorder = false;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: true,
|
||||
|
@@ -18,6 +18,7 @@ import "../../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||
import type { NumericStateCondition } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { NON_NUMERIC_ATTRIBUTES } from "../../../../../data/entity_attributes";
|
||||
|
||||
const numericStateConditionStruct = object({
|
||||
alias: optional(string()),
|
||||
@@ -85,72 +86,7 @@ export default class HaNumericStateCondition extends LitElement {
|
||||
name: "attribute",
|
||||
selector: {
|
||||
attribute: {
|
||||
hide_attributes: [
|
||||
"access_token",
|
||||
"auto_update",
|
||||
"available_modes",
|
||||
"away_mode",
|
||||
"changed_by",
|
||||
"code_format",
|
||||
"color_modes",
|
||||
"current_activity",
|
||||
"device_class",
|
||||
"editable",
|
||||
"effect_list",
|
||||
"effect",
|
||||
"entity_picture",
|
||||
"event_type",
|
||||
"event_types",
|
||||
"fan_mode",
|
||||
"fan_modes",
|
||||
"fan_speed_list",
|
||||
"forecast",
|
||||
"friendly_name",
|
||||
"frontend_stream_type",
|
||||
"has_date",
|
||||
"has_time",
|
||||
"hs_color",
|
||||
"hvac_mode",
|
||||
"hvac_modes",
|
||||
"icon",
|
||||
"media_album_name",
|
||||
"media_artist",
|
||||
"media_content_type",
|
||||
"media_position_updated_at",
|
||||
"media_title",
|
||||
"next_dawn",
|
||||
"next_dusk",
|
||||
"next_midnight",
|
||||
"next_noon",
|
||||
"next_rising",
|
||||
"next_setting",
|
||||
"operation_list",
|
||||
"operation_mode",
|
||||
"options",
|
||||
"preset_mode",
|
||||
"preset_modes",
|
||||
"release_notes",
|
||||
"release_summary",
|
||||
"release_url",
|
||||
"restored",
|
||||
"rgb_color",
|
||||
"rgbw_color",
|
||||
"shuffle",
|
||||
"sound_mode_list",
|
||||
"sound_mode",
|
||||
"source_list",
|
||||
"source_type",
|
||||
"source",
|
||||
"state_class",
|
||||
"supported_features",
|
||||
"swing_mode",
|
||||
"swing_mode",
|
||||
"swing_modes",
|
||||
"title",
|
||||
"token",
|
||||
"unit_of_measurement",
|
||||
"xy_color",
|
||||
],
|
||||
hide_attributes: NON_NUMERIC_ATTRIBUTES,
|
||||
},
|
||||
},
|
||||
context: {
|
||||
|
@@ -135,6 +135,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
|
||||
@state() private _blueprintConfig?: BlueprintAutomationConfig;
|
||||
|
||||
@state()
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
@transform<EntityRegistryEntry[], EntityRegistryEntry>({
|
||||
transformer: function (this: HaAutomationEditor, value) {
|
||||
|
@@ -138,6 +138,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _filteredAutomations?: string[] | null;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "automation-table-search",
|
||||
@@ -146,6 +147,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "automation-table-filters-full",
|
||||
|
@@ -26,8 +26,12 @@ import type {
|
||||
ManualAutomationConfig,
|
||||
Trigger,
|
||||
} from "../../../data/automation";
|
||||
import { normalizeAutomationConfig } from "../../../data/automation";
|
||||
import type { Action } from "../../../data/script";
|
||||
import {
|
||||
isCondition,
|
||||
isTrigger,
|
||||
normalizeAutomationConfig,
|
||||
} from "../../../data/automation";
|
||||
import { getActionType, type Action } from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
@@ -310,55 +314,119 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const loaded: any = load(paste);
|
||||
if (loaded) {
|
||||
let normalized: AutomationConfig | undefined;
|
||||
let loaded: any;
|
||||
try {
|
||||
loaded = load(paste);
|
||||
} catch (_err: any) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.paste_invalid_yaml"
|
||||
),
|
||||
duration: 4000,
|
||||
dismissable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
normalized = normalizeAutomationConfig(loaded);
|
||||
} catch (_err: any) {
|
||||
return;
|
||||
if (!loaded || typeof loaded !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
let config = loaded;
|
||||
|
||||
if ("automation" in config) {
|
||||
config = config.automation;
|
||||
if (Array.isArray(config)) {
|
||||
config = config[0];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
assert(normalized, automationConfigStruct);
|
||||
} catch (_err: any) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.paste_invalid_config"
|
||||
),
|
||||
duration: 4000,
|
||||
dismissable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalized) {
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.dirty) {
|
||||
const result = await new Promise<boolean>((resolve) => {
|
||||
showPasteReplaceDialog(this, {
|
||||
domain: "automation",
|
||||
pastedConfig: normalized,
|
||||
onClose: () => resolve(false),
|
||||
onAppend: () => {
|
||||
this._appendToExistingConfig(normalized);
|
||||
resolve(false);
|
||||
},
|
||||
onReplace: () => resolve(true),
|
||||
});
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
if (Array.isArray(config)) {
|
||||
if (config.length === 1) {
|
||||
config = config[0];
|
||||
} else {
|
||||
const newConfig: AutomationConfig = {
|
||||
triggers: [],
|
||||
conditions: [],
|
||||
actions: [],
|
||||
};
|
||||
let found = false;
|
||||
config.forEach((cfg: any) => {
|
||||
if (isTrigger(cfg)) {
|
||||
found = true;
|
||||
(newConfig.triggers as Trigger[]).push(cfg);
|
||||
}
|
||||
if (isCondition(cfg)) {
|
||||
found = true;
|
||||
(newConfig.conditions as Condition[]).push(cfg);
|
||||
}
|
||||
if (getActionType(cfg) !== "unknown") {
|
||||
found = true;
|
||||
(newConfig.actions as Action[]).push(cfg);
|
||||
}
|
||||
});
|
||||
if (found) {
|
||||
config = newConfig;
|
||||
}
|
||||
|
||||
// replace the config completely
|
||||
this._replaceExistingConfig(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
if (isTrigger(config)) {
|
||||
config = { triggers: [config] };
|
||||
}
|
||||
if (isCondition(config)) {
|
||||
config = { conditions: [config] };
|
||||
}
|
||||
if (getActionType(config) !== "unknown") {
|
||||
config = { actions: [config] };
|
||||
}
|
||||
|
||||
let normalized: AutomationConfig;
|
||||
|
||||
try {
|
||||
normalized = normalizeAutomationConfig(config);
|
||||
} catch (_err: any) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
assert(normalized, automationConfigStruct);
|
||||
} catch (_err: any) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.paste_invalid_config"
|
||||
),
|
||||
duration: 4000,
|
||||
dismissable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalized) {
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.dirty) {
|
||||
const result = await new Promise<boolean>((resolve) => {
|
||||
showPasteReplaceDialog(this, {
|
||||
domain: "automation",
|
||||
pastedConfig: normalized,
|
||||
onClose: () => resolve(false),
|
||||
onAppend: () => {
|
||||
this._appendToExistingConfig(normalized);
|
||||
resolve(false);
|
||||
},
|
||||
onReplace: () => resolve(true),
|
||||
});
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// replace the config completely
|
||||
this._replaceExistingConfig(normalized);
|
||||
}
|
||||
};
|
||||
|
||||
private _appendToExistingConfig(config: ManualAutomationConfig) {
|
||||
|
@@ -29,6 +29,7 @@ export default class HaAutomationOption extends LitElement {
|
||||
|
||||
@state() private _showReorder = false;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: true,
|
||||
|
@@ -38,6 +38,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
@state() private _showReorder = false;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: true,
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { mdiCog, mdiDelete, mdiHarddisk, mdiNas } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { css, html, LitElement, nothing, type TemplateResult } from "lit";
|
||||
import { join } from "lit/directives/join";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
@@ -57,26 +58,51 @@ class HaBackupConfigAgents extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
const texts: (TemplateResult | string)[] = [];
|
||||
|
||||
if (isNetworkMountAgent(agentId)) {
|
||||
texts.push(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.backup.agents.network_mount_agent_description"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const encryptionTurnedOff =
|
||||
this.agentsConfig?.[agentId]?.protected === false;
|
||||
|
||||
if (encryptionTurnedOff) {
|
||||
return html`
|
||||
<span class="dot warning"></span>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.agents.encryption_turned_off"
|
||||
)}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
if (isNetworkMountAgent(agentId)) {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.agents.network_mount_agent_description"
|
||||
texts.push(
|
||||
html`<div class="unencrypted-warning">
|
||||
<span class="dot warning"></span>
|
||||
<span>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.agents.encryption_turned_off"
|
||||
)}
|
||||
</span>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
return "";
|
||||
|
||||
const retention = this.agentsConfig?.[agentId]?.retention;
|
||||
|
||||
if (retention) {
|
||||
if (retention.copies === null && retention.days === null) {
|
||||
texts.push(
|
||||
this.hass.localize("ui.panel.config.backup.agents.retention_all")
|
||||
);
|
||||
} else {
|
||||
texts.push(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.backup.agents.retention_${retention.copies ? "backups" : "days"}`,
|
||||
{
|
||||
count: retention.copies || retention.days,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return join(texts, html`<span class="separator"> · </span>`);
|
||||
}
|
||||
|
||||
private _availableAgents = memoizeOne(
|
||||
@@ -287,6 +313,11 @@ class HaBackupConfigAgents extends LitElement {
|
||||
gap: 8px;
|
||||
line-height: normal;
|
||||
}
|
||||
.unencrypted-warning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.dot {
|
||||
display: block;
|
||||
position: relative;
|
||||
@@ -294,11 +325,22 @@ class HaBackupConfigAgents extends LitElement {
|
||||
height: 8px;
|
||||
background-color: var(--disabled-color);
|
||||
border-radius: 50%;
|
||||
flex: none;
|
||||
}
|
||||
.dot.warning {
|
||||
background-color: var(--warning-color);
|
||||
}
|
||||
@media all and (max-width: 500px) {
|
||||
.separator {
|
||||
display: none;
|
||||
}
|
||||
ha-md-list-item [slot="supporting-text"] {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -98,6 +98,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _selected: string[] = [];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "backups-table-filters",
|
||||
|
@@ -118,19 +118,17 @@ class HaConfigBackupDetails extends LitElement {
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
: this.config?.agents[this.agentId]
|
||||
? html`<ha-backup-config-retention
|
||||
location-specific
|
||||
.headline=${this.hass.localize(
|
||||
`ui.panel.config.backup.location.retention_for_${isLocalAgent(this.agentId) ? "this_system" : "location"}`,
|
||||
{ location: agentName }
|
||||
)}
|
||||
.hass=${this.hass}
|
||||
.retention=${this.config?.agents[this.agentId]
|
||||
?.retention}
|
||||
@value-changed=${this._retentionChanged}
|
||||
></ha-backup-config-retention>`
|
||||
: nothing}
|
||||
: html`<ha-backup-config-retention
|
||||
location-specific
|
||||
.headline=${this.hass.localize(
|
||||
`ui.panel.config.backup.location.retention_for_${isLocalAgent(this.agentId) ? "this_system" : "location"}`,
|
||||
{ location: agentName }
|
||||
)}
|
||||
.hass=${this.hass}
|
||||
.retention=${this.config?.agents[this.agentId]
|
||||
?.retention}
|
||||
@value-changed=${this._retentionChanged}
|
||||
></ha-backup-config-retention>`}
|
||||
</ha-card>
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
|
@@ -9,7 +9,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
@@ -118,6 +118,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
})
|
||||
private _activeHiddenColumns?: string[];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "blueprint-table-search",
|
||||
@@ -499,9 +500,11 @@ class HaBlueprintOverview extends LitElement {
|
||||
list: html`<ul>
|
||||
${[...(related.automation || []), ...(related.script || [])].map(
|
||||
(item) => {
|
||||
const state = this.hass.states[item];
|
||||
const automationState = this.hass.states[item];
|
||||
return html`<li>
|
||||
${state ? `${computeStateName(state)} (${item})` : item}
|
||||
${automationState
|
||||
? `${computeStateName(automationState)} (${item})`
|
||||
: item}
|
||||
</li>`;
|
||||
}
|
||||
)}
|
||||
|
@@ -106,7 +106,7 @@ export class HaDeviceCard extends LitElement {
|
||||
<div class="extra-info">
|
||||
${type === "bluetooth" &&
|
||||
isComponentLoaded(this.hass, "bluetooth")
|
||||
? html`${titleCase(type)}
|
||||
? html`${titleCase(type)}:
|
||||
<a
|
||||
href="/config/bluetooth/advertisement-monitor?${createSearchParam(
|
||||
{ address: value }
|
||||
@@ -114,7 +114,7 @@ export class HaDeviceCard extends LitElement {
|
||||
>${value.toUpperCase()}</a
|
||||
>`
|
||||
: type === "mac" && isComponentLoaded(this.hass, "dhcp")
|
||||
? html`${titleCase(type)}
|
||||
? html`MAC:
|
||||
<a
|
||||
href="/config/dhcp?${createSearchParam({
|
||||
mac_address: value,
|
||||
|
@@ -1559,6 +1559,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
padding-inline-start: 8px;
|
||||
padding-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
|
@@ -120,6 +120,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _selected: string[] = [];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "devices-table-search",
|
||||
@@ -128,6 +129,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
private _filter: string = history.state?.filter || "";
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "devices-table-filters-full",
|
||||
|
@@ -159,6 +159,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entities!: EntityRegistryEntry[];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "entities-table-search",
|
||||
@@ -169,6 +170,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "entities-table-filters",
|
||||
|
@@ -168,6 +168,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
private _activeCollapsed?: string;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "helpers-table-search",
|
||||
|
@@ -156,7 +156,7 @@ class HaConfigInfo extends LitElement {
|
||||
)}
|
||||
</span>
|
||||
<span class="version">
|
||||
${JS_VERSION}${JS_TYPE !== "modern" ? ` ⸱ ${JS_TYPE}` : ""}
|
||||
${JS_VERSION}${JS_TYPE !== "modern" ? ` · ${JS_TYPE}` : ""}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
@@ -210,6 +210,9 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
|
||||
.route=${this.route}
|
||||
.columns=${this._columns(this.hass.localize)}
|
||||
.data=${this._dataWithNamedSourceAndIds(this._data)}
|
||||
.noDataText=${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.no_advertisements_found"
|
||||
)}
|
||||
@row-click=${this._handleRowClicked}
|
||||
.initialGroupColumn=${this._activeGrouping}
|
||||
.initialCollapsedGroups=${this._activeCollapsed}
|
||||
|
@@ -96,6 +96,9 @@ export class DHCPConfigPanel extends SubscribeMixin(LitElement) {
|
||||
.route=${this.route}
|
||||
.columns=${this._columns(this.hass.localize)}
|
||||
.data=${this._dataWithIds(this._data)}
|
||||
.noDataText=${this.hass.localize(
|
||||
"ui.panel.config.dhcp.no_devices_found"
|
||||
)}
|
||||
filter=${this._macAddress || ""}
|
||||
></hass-tabs-subpage-data-table>
|
||||
`;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { storage } from "../../../../../common/decorators/storage";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-code-editor";
|
||||
@@ -23,6 +23,7 @@ export class MQTTConfigPanel extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-mqtt-topic-ls",
|
||||
state: true,
|
||||
@@ -30,6 +31,7 @@ export class MQTTConfigPanel extends LitElement {
|
||||
})
|
||||
private _topic = "";
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-mqtt-payload-ls",
|
||||
state: true,
|
||||
@@ -37,6 +39,7 @@ export class MQTTConfigPanel extends LitElement {
|
||||
})
|
||||
private _payload = "";
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-mqtt-qos-ls",
|
||||
state: true,
|
||||
@@ -44,6 +47,7 @@ export class MQTTConfigPanel extends LitElement {
|
||||
})
|
||||
private _qos = "0";
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-mqtt-retain-ls",
|
||||
state: true,
|
||||
@@ -51,6 +55,7 @@ export class MQTTConfigPanel extends LitElement {
|
||||
})
|
||||
private _retain = false;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-mqtt-allow-template-ls",
|
||||
state: true,
|
||||
|
@@ -21,6 +21,7 @@ const qosLevel = ["0", "1", "2"];
|
||||
class MqttSubscribeCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-mqtt-topic-subscribe",
|
||||
state: true,
|
||||
@@ -28,6 +29,7 @@ class MqttSubscribeCard extends LitElement {
|
||||
})
|
||||
private _topic = "";
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-mqtt-qos-subscribe",
|
||||
state: true,
|
||||
@@ -35,6 +37,7 @@ class MqttSubscribeCard extends LitElement {
|
||||
})
|
||||
private _qos = "0";
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-mqtt-json-format",
|
||||
state: true,
|
||||
|
@@ -105,6 +105,9 @@ export class SSDPConfigPanel extends SubscribeMixin(LitElement) {
|
||||
@grouping-changed=${this._handleGroupingChanged}
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
.data=${this._dataWithIds(this._data)}
|
||||
.noDataText=${this.hass.localize(
|
||||
"ui.panel.config.ssdp.no_devices_found"
|
||||
)}
|
||||
@row-click=${this._handleRowClicked}
|
||||
clickable
|
||||
></hass-tabs-subpage-data-table>
|
||||
|
@@ -112,6 +112,9 @@ export class ZeroconfConfigPanel extends SubscribeMixin(LitElement) {
|
||||
@grouping-changed=${this._handleGroupingChanged}
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
.data=${this._dataWithIds(this._data)}
|
||||
.noDataText=${this.hass.localize(
|
||||
"ui.panel.config.zeroconf.no_devices_found"
|
||||
)}
|
||||
@row-click=${this._handleRowClicked}
|
||||
clickable
|
||||
></hass-tabs-subpage-data-table>
|
||||
|
@@ -55,6 +55,7 @@ export class HaConfigLabels extends LitElement {
|
||||
|
||||
@state() private _labels: LabelRegistryEntry[] = [];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "labels-table-search",
|
||||
|
@@ -70,7 +70,7 @@ class DownloadLogsDialog extends LitElement {
|
||||
<span slot="subtitle">
|
||||
${this._dialogParams.header}${this._dialogParams.boot === 0
|
||||
? ""
|
||||
: ` ⸱ ${this._dialogParams.boot === -1 ? this.hass.localize("ui.panel.config.logs.previous") : this.hass.localize("ui.panel.config.logs.startups_ago", { boot: this._dialogParams.boot * -1 })}`}
|
||||
: ` · ${this._dialogParams.boot === -1 ? this.hass.localize("ui.panel.config.logs.previous") : this.hass.localize("ui.panel.config.logs.startups_ago", { boot: this._dialogParams.boot * -1 })}`}
|
||||
</span>
|
||||
</ha-dialog-header>
|
||||
<div slot="content" class="content">
|
||||
|
@@ -74,6 +74,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
|
||||
@state() private _dashboards: LovelaceDashboard[] = [];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "lovelace-dashboards-table-search",
|
||||
@@ -161,7 +162,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
placement="right"
|
||||
>
|
||||
<ha-svg-icon
|
||||
style="padding-left: 10px; padding-inline-start: 10px; direction: var(--direction);"
|
||||
style="padding-left: 10px; padding-inline-start: 10px; padding-inline-end: initial; direction: var(--direction);"
|
||||
.path=${mdiCheckCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-tooltip>
|
||||
|
@@ -46,6 +46,7 @@ export class HaConfigLovelaceRescources extends LitElement {
|
||||
|
||||
@state() private _resources: LovelaceResource[] = [];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "lovelace-resources-table-search",
|
||||
|
@@ -1,66 +0,0 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("ha-config-network-ssdp")
|
||||
class ConfigNetworkSSDP extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
header=${this.hass.localize("ui.panel.config.network.discovery.ssdp")}
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize("ui.panel.config.network.discovery.ssdp_info")}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<a
|
||||
href="/config/ssdp"
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.network.discovery.ssdp_browser"
|
||||
)}
|
||||
>
|
||||
<ha-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.discovery.ssdp_browser"
|
||||
)}
|
||||
</ha-button>
|
||||
</a>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`, // row-reverse so we tab first to "save"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-network-ssdp": ConfigNetworkSSDP;
|
||||
}
|
||||
}
|
@@ -1,70 +0,0 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("ha-config-network-zeroconf")
|
||||
class ConfigNetworkZeroconf extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
header=${this.hass.localize(
|
||||
"ui.panel.config.network.discovery.zeroconf"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.discovery.zeroconf_info"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<a
|
||||
href="/config/zeroconf"
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.network.discovery.zeroconf_browser"
|
||||
)}
|
||||
>
|
||||
<ha-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.discovery.zeroconf_browser"
|
||||
)}
|
||||
</ha-button>
|
||||
</a>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`, // row-reverse so we tab first to "save"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-network-zeroconf": ConfigNetworkZeroconf;
|
||||
}
|
||||
}
|
@@ -3,14 +3,18 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-icon-next";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import "./ha-config-network";
|
||||
import "./ha-config-network-ssdp";
|
||||
import "./ha-config-network-zeroconf";
|
||||
import "./ha-config-url-form";
|
||||
import "./supervisor-hostname";
|
||||
import "./supervisor-network";
|
||||
|
||||
const NETWORK_BROWSERS = ["dhcp", "ssdp", "zeroconf"] as const;
|
||||
|
||||
@customElement("ha-config-section-network")
|
||||
class HaConfigSectionNetwork extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -37,15 +41,38 @@ class HaConfigSectionNetwork extends LitElement {
|
||||
: ""}
|
||||
<ha-config-url-form .hass=${this.hass}></ha-config-url-form>
|
||||
<ha-config-network .hass=${this.hass}></ha-config-network>
|
||||
${isComponentLoaded(this.hass, "ssdp")
|
||||
? html`<ha-config-network-ssdp
|
||||
.hass=${this.hass}
|
||||
></ha-config-network-ssdp>`
|
||||
: ""}
|
||||
${isComponentLoaded(this.hass, "zeroconf")
|
||||
? html`<ha-config-network-zeroconf
|
||||
.hass=${this.hass}
|
||||
></ha-config-network-zeroconf>`
|
||||
${NETWORK_BROWSERS.some((component) =>
|
||||
isComponentLoaded(this.hass, component)
|
||||
)
|
||||
? html`
|
||||
<ha-card
|
||||
outlined
|
||||
class="discovery-card"
|
||||
header=${this.hass.localize(
|
||||
"ui.panel.config.network.discovery.title"
|
||||
)}
|
||||
>
|
||||
<ha-md-list>
|
||||
${NETWORK_BROWSERS.map(
|
||||
(domain) => html`
|
||||
<ha-md-list-item type="link" href="/config/${domain}">
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.network.discovery.${domain}`
|
||||
)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.network.discovery.${domain}_info`
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-md-list>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</hass-subpage>
|
||||
@@ -62,13 +89,15 @@ class HaConfigSectionNetwork extends LitElement {
|
||||
supervisor-network,
|
||||
ha-config-url-form,
|
||||
ha-config-network,
|
||||
ha-config-network-ssdp,
|
||||
ha-config-network-zeroconf {
|
||||
.discovery-card {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 24px;
|
||||
max-width: 600px;
|
||||
}
|
||||
.discovery-card ha-md-list {
|
||||
padding-top: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,7 @@ class DialogRepairsIssueSubtitle extends LitElement {
|
||||
protected render() {
|
||||
const domainName = domainToName(this.hass.localize, this.issue.domain);
|
||||
const reportedBy = domainName
|
||||
? ` ⸱ ${this.hass.localize("ui.panel.config.repairs.reported_by", {
|
||||
? ` · ${this.hass.localize("ui.panel.config.repairs.reported_by", {
|
||||
integration: domainName,
|
||||
})}`
|
||||
: "";
|
||||
|
@@ -100,13 +100,13 @@ class HaConfigRepairs extends LitElement {
|
||||
${(issue.severity === "critical" ||
|
||||
issue.severity === "error") &&
|
||||
issue.created
|
||||
? " ⸱ "
|
||||
? " · "
|
||||
: ""}
|
||||
${createdBy
|
||||
? html`<span .title=${createdBy}>${createdBy}</span>`
|
||||
: nothing}
|
||||
${issue.ignored
|
||||
? ` ⸱ ${this.hass.localize(
|
||||
? ` · ${this.hass.localize(
|
||||
"ui.panel.config.repairs.dialog.ignored_in_version_short",
|
||||
{ version: issue.dismissed_version }
|
||||
)}`
|
||||
|
@@ -133,6 +133,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _filteredScenes?: string[] | null;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "scene-table-search",
|
||||
@@ -141,6 +142,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "scene-table-filters-full",
|
||||
|
@@ -105,6 +105,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
|
||||
@state() private _readOnly = false;
|
||||
|
||||
@state()
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
@transform<EntityRegistryEntry[], EntityRegistryEntry>({
|
||||
transformer: function (this: HaScriptEditor, value) {
|
||||
|
@@ -138,6 +138,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _filteredScripts?: string[] | null;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "script-table-search",
|
||||
@@ -146,6 +147,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
private _filter = "";
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "script-table-filters-full",
|
||||
|
@@ -24,7 +24,11 @@ import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-markdown";
|
||||
import type { Action, Fields, ScriptConfig } from "../../../data/script";
|
||||
import { MODES, normalizeScriptConfig } from "../../../data/script";
|
||||
import {
|
||||
getActionType,
|
||||
MODES,
|
||||
normalizeScriptConfig,
|
||||
} from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
@@ -234,54 +238,90 @@ export class HaManualScriptEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const loaded: any = load(paste);
|
||||
if (loaded) {
|
||||
let normalized: ScriptConfig | undefined;
|
||||
let loaded: any;
|
||||
try {
|
||||
loaded = load(paste);
|
||||
} catch (_err: any) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.script.editor.paste_invalid_config"
|
||||
),
|
||||
duration: 4000,
|
||||
dismissable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
normalized = normalizeScriptConfig(loaded);
|
||||
} catch (_err: any) {
|
||||
return;
|
||||
if (!loaded || typeof loaded !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
let config = loaded;
|
||||
|
||||
if ("script" in config) {
|
||||
config = config.script;
|
||||
if (Object.keys(config).length) {
|
||||
config = config[Object.keys(config)[0]];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
assert(normalized, scriptConfigStruct);
|
||||
} catch (_err: any) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.script.editor.paste_invalid_config"
|
||||
),
|
||||
duration: 4000,
|
||||
dismissable: true,
|
||||
});
|
||||
return;
|
||||
if (Array.isArray(config)) {
|
||||
if (config.length === 1) {
|
||||
config = config[0];
|
||||
} else {
|
||||
config = { sequence: config };
|
||||
}
|
||||
}
|
||||
|
||||
if (normalized) {
|
||||
ev.preventDefault();
|
||||
if (!["sequence", "unknown"].includes(getActionType(config))) {
|
||||
config = { sequence: [config] };
|
||||
}
|
||||
|
||||
if (this.dirty) {
|
||||
const result = await new Promise<boolean>((resolve) => {
|
||||
showPasteReplaceDialog(this, {
|
||||
domain: "script",
|
||||
pastedConfig: normalized,
|
||||
onClose: () => resolve(false),
|
||||
onAppend: () => {
|
||||
this._appendToExistingConfig(normalized);
|
||||
resolve(false);
|
||||
},
|
||||
onReplace: () => resolve(true),
|
||||
});
|
||||
let normalized: ScriptConfig | undefined;
|
||||
|
||||
try {
|
||||
normalized = normalizeScriptConfig(config);
|
||||
} catch (_err: any) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
assert(normalized, scriptConfigStruct);
|
||||
} catch (_err: any) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.script.editor.paste_invalid_config"
|
||||
),
|
||||
duration: 4000,
|
||||
dismissable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalized) {
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.dirty) {
|
||||
const result = await new Promise<boolean>((resolve) => {
|
||||
showPasteReplaceDialog(this, {
|
||||
domain: "script",
|
||||
pastedConfig: normalized,
|
||||
onClose: () => resolve(false),
|
||||
onAppend: () => {
|
||||
this._appendToExistingConfig(normalized);
|
||||
resolve(false);
|
||||
},
|
||||
onReplace: () => resolve(true),
|
||||
});
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
// replace the config completely
|
||||
this._replaceExistingConfig(normalized);
|
||||
}
|
||||
|
||||
// replace the config completely
|
||||
this._replaceExistingConfig(normalized);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -62,6 +62,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
return this.hass.auth.external?.config.canWriteTag;
|
||||
}
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "tags-table-search",
|
||||
|
@@ -76,6 +76,7 @@ export class VoiceAssistantsExpose extends LitElement {
|
||||
|
||||
@state() private _extEntities?: Record<string, ExtEntityRegistryEntry>;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "voice-expose-table-search",
|
||||
|
@@ -52,6 +52,7 @@ class HaPanelDevAction extends LitElement {
|
||||
|
||||
private _yamlValid = true;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-action-state-service-data",
|
||||
state: true,
|
||||
@@ -59,6 +60,7 @@ class HaPanelDevAction extends LitElement {
|
||||
})
|
||||
private _serviceData?: ServiceAction = { action: "", target: {}, data: {} };
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "panel-dev-action-state-yaml-mode",
|
||||
state: true,
|
||||
|
@@ -33,6 +33,7 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() supportedLanguages?: string[];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "assist_debug_language",
|
||||
state: true,
|
||||
|
@@ -62,6 +62,7 @@ class HaPanelDevState extends LitElement {
|
||||
|
||||
@state() private _validJSON = true;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "devToolsShowAttributes",
|
||||
state: true,
|
||||
|
@@ -22,11 +22,14 @@ import type {
|
||||
DeviceConsumptionEnergyPreference,
|
||||
} from "../../data/energy";
|
||||
import {
|
||||
computeConsumptionData,
|
||||
getEnergyDataCollection,
|
||||
getEnergyGasUnit,
|
||||
getEnergyWaterUnit,
|
||||
getSummedData,
|
||||
} from "../../data/energy";
|
||||
import { fileDownload } from "../../util/file_download";
|
||||
import type { StatisticValue } from "../../data/recorder";
|
||||
|
||||
const ENERGY_LOVELACE_CONFIG: LovelaceConfig = {
|
||||
views: [
|
||||
@@ -177,18 +180,20 @@ class PanelEnergy extends LitElement {
|
||||
const csv: string[] = [];
|
||||
csv[0] = headers;
|
||||
|
||||
const processStat = function (stat: string, type: string, unit: string) {
|
||||
const processCsvRow = function (
|
||||
id: string,
|
||||
type: string,
|
||||
unit: string,
|
||||
data: StatisticValue[]
|
||||
) {
|
||||
let n = 0;
|
||||
const row: string[] = [];
|
||||
if (!stats[stat]) {
|
||||
return;
|
||||
}
|
||||
row.push(stat);
|
||||
row.push(id);
|
||||
row.push(type);
|
||||
row.push(unit.normalize("NFKD"));
|
||||
times.forEach((t) => {
|
||||
if (n < stats[stat].length && stats[stat][n].start === t) {
|
||||
row.push((stats[stat][n].change ?? "").toString());
|
||||
if (n < data.length && data[n].start === t) {
|
||||
row.push((data[n].change ?? "").toString());
|
||||
n++;
|
||||
} else {
|
||||
row.push("");
|
||||
@@ -197,6 +202,14 @@ class PanelEnergy extends LitElement {
|
||||
csv.push(row.join(",") + "\n");
|
||||
};
|
||||
|
||||
const processStat = function (stat: string, type: string, unit: string) {
|
||||
if (!stats[stat]) {
|
||||
return;
|
||||
}
|
||||
|
||||
processCsvRow(stat, type, unit, stats[stat]);
|
||||
};
|
||||
|
||||
const currency = this.hass.config.currency;
|
||||
|
||||
const printCategory = function (
|
||||
@@ -335,6 +348,99 @@ class PanelEnergy extends LitElement {
|
||||
|
||||
printCategory("device_consumption", devices, electricUnit);
|
||||
|
||||
const { summedData, compareSummedData: _ } = getSummedData(
|
||||
energyData.state
|
||||
);
|
||||
const { consumption, compareConsumption: __ } = computeConsumptionData(
|
||||
summedData,
|
||||
undefined
|
||||
);
|
||||
|
||||
const processConsumptionData = function (
|
||||
type: string,
|
||||
unit: string,
|
||||
data: Record<number, number>
|
||||
) {
|
||||
const data2: StatisticValue[] = [];
|
||||
|
||||
Object.entries(data).forEach(([t, value]) => {
|
||||
data2.push({
|
||||
start: Number(t),
|
||||
end: NaN,
|
||||
change: value,
|
||||
});
|
||||
});
|
||||
|
||||
processCsvRow("", type, unit, data2);
|
||||
};
|
||||
|
||||
const hasSolar = !!solar_productions.length;
|
||||
const hasBattery = !!battery_ins.length;
|
||||
const hasGridReturn = !!grid_productions.length;
|
||||
const hasGridSource = !!grid_consumptions.length;
|
||||
|
||||
if (hasGridSource) {
|
||||
processConsumptionData(
|
||||
"calculated_consumed_grid",
|
||||
electricUnit,
|
||||
consumption.used_grid
|
||||
);
|
||||
if (hasBattery) {
|
||||
processConsumptionData(
|
||||
"calculated_grid_to_battery",
|
||||
electricUnit,
|
||||
consumption.grid_to_battery
|
||||
);
|
||||
}
|
||||
}
|
||||
if (hasGridReturn && hasBattery) {
|
||||
processConsumptionData(
|
||||
"calculated_battery_to_grid",
|
||||
electricUnit,
|
||||
consumption.battery_to_grid
|
||||
);
|
||||
}
|
||||
if (hasBattery) {
|
||||
processConsumptionData(
|
||||
"calculated_consumed_battery",
|
||||
electricUnit,
|
||||
consumption.used_battery
|
||||
);
|
||||
}
|
||||
|
||||
if (hasSolar) {
|
||||
processConsumptionData(
|
||||
"calculated_consumed_solar",
|
||||
electricUnit,
|
||||
consumption.used_solar
|
||||
);
|
||||
if (hasBattery) {
|
||||
processConsumptionData(
|
||||
"calculated_solar_to_battery",
|
||||
electricUnit,
|
||||
consumption.solar_to_battery
|
||||
);
|
||||
}
|
||||
if (hasGridReturn) {
|
||||
processConsumptionData(
|
||||
"calculated_solar_to_grid",
|
||||
electricUnit,
|
||||
consumption.solar_to_grid
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(hasGridSource ? 1 : 0) + (hasSolar ? 1 : 0) + (hasBattery ? 1 : 0) >
|
||||
1
|
||||
) {
|
||||
processConsumptionData(
|
||||
"calculated_total_consumption",
|
||||
electricUnit,
|
||||
consumption.used_total
|
||||
);
|
||||
}
|
||||
|
||||
const blob = new Blob(csv, {
|
||||
type: "text/csv",
|
||||
});
|
||||
|
@@ -63,6 +63,7 @@ class HaPanelHistory extends LitElement {
|
||||
|
||||
@state() private _endDate: Date;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "historyPickedValue",
|
||||
state: true,
|
||||
|
@@ -39,6 +39,7 @@ export class HaPanelLogbook extends LitElement {
|
||||
@state()
|
||||
private _showBack?: boolean;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "logbookPickedValue",
|
||||
state: true,
|
||||
|
@@ -210,10 +210,6 @@ export class HuiViewBadges extends LitElement {
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.badges > *:last-child:not(.add) {
|
||||
padding-right: var(--badges-padding-right, 0);
|
||||
}
|
||||
|
||||
hui-badge-edit-mode {
|
||||
display: block;
|
||||
position: relative;
|
||||
|
@@ -62,6 +62,7 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
|
||||
@state() private _compareEnd?: Date;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "energy-devices-hidden-stats",
|
||||
state: true,
|
||||
|
@@ -86,6 +86,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@state() private _config?: ButtonCardConfig;
|
||||
|
||||
@state()
|
||||
@consume<any>({ context: statesContext, subscribe: true })
|
||||
@transform({
|
||||
transformer: function (this: HuiButtonCard, value: HassEntities) {
|
||||
@@ -111,6 +112,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
@consume({ context: configContext, subscribe: true })
|
||||
_hassConfig!: HassConfig;
|
||||
|
||||
@state()
|
||||
@consume<any>({ context: entitiesContext, subscribe: true })
|
||||
@transform<HomeAssistant["entities"], EntityRegistryDisplayEntry>({
|
||||
transformer: function (this: HuiButtonCard, value) {
|
||||
|
@@ -109,12 +109,18 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
if (isNaN(entityState)) {
|
||||
const valueToDisplay = this._config.attribute
|
||||
? stateObj.attributes[this._config.attribute]
|
||||
: stateObj.state;
|
||||
|
||||
if (isNaN(valueToDisplay)) {
|
||||
return html`
|
||||
<hui-warning
|
||||
>${this.hass.localize(
|
||||
"ui.panel.lovelace.warning.entity_non_numeric",
|
||||
{ entity: this._config.entity }
|
||||
this._config.attribute
|
||||
? "ui.panel.lovelace.warning.attribute_not_numeric"
|
||||
: "ui.panel.lovelace.warning.entity_non_numeric",
|
||||
{ entity: this._config.entity, attribute: this._config.attribute }
|
||||
)}</hui-warning
|
||||
>
|
||||
`;
|
||||
@@ -141,7 +147,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
<ha-gauge
|
||||
.min=${this._config.min!}
|
||||
.max=${this._config.max!}
|
||||
.value=${stateObj.state}
|
||||
.value=${valueToDisplay}
|
||||
.formatOptions=${getNumberFormatOptions(
|
||||
stateObj,
|
||||
this.hass.entities[stateObj.entity_id]
|
||||
|
@@ -268,6 +268,7 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
||||
<div
|
||||
class="content ${classMap({
|
||||
"has-header": !!this._config.title,
|
||||
"has-rows": !!this._config.grid_options?.rows,
|
||||
})}"
|
||||
>
|
||||
<statistics-chart
|
||||
@@ -389,6 +390,9 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
||||
statistics-chart {
|
||||
height: 100%;
|
||||
}
|
||||
.has-rows {
|
||||
--chart-max-height: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -233,6 +233,7 @@ export interface GaugeSegment {
|
||||
|
||||
export interface GaugeCardConfig extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
attribute?: string;
|
||||
name?: string;
|
||||
unit?: string;
|
||||
min?: number;
|
||||
|
@@ -163,6 +163,7 @@ export class HuiGenericEntityRow extends LitElement {
|
||||
@touchend=${stopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@click=${stopPropagation}
|
||||
@action=${stopPropagation}
|
||||
></slot>`}
|
||||
</div>
|
||||
`;
|
||||
|
@@ -43,6 +43,7 @@ export class HuiBadgePicker extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public suggestedBadges?: string[];
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "dashboardBadgeClipboard",
|
||||
state: true,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user