mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
20250502.1 (#25323)
This commit is contained in:
commit
7b8b8f9d0b
@ -1,13 +1,13 @@
|
|||||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<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"/>
|
<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"/>
|
<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>
|
</mask>
|
||||||
<g mask="url(#mask0_1110_23734)">
|
<g mask="url(#mask0_2_779)">
|
||||||
<rect x="30" y="27" width="18" height="18" fill="#212121"/>
|
<rect x="30" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||||
</g>
|
</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="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"/>
|
<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"/>
|
<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">
|
<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="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="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"/>
|
<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="47" cy="36" r="34" fill="white"/>
|
<circle cx="39" cy="36" r="34" fill="white"/>
|
||||||
<circle cx="47" cy="36" r="33.5" stroke="black" stroke-opacity="0.12"/>
|
<circle cx="39" 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="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="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"/>
|
<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_1110_23775" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="38" y="27" width="18" height="18">
|
<mask id="mask0_2_810" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" 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="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>
|
</mask>
|
||||||
<g mask="url(#mask0_1110_23775)">
|
<g mask="url(#mask0_2_810)">
|
||||||
<rect x="38" y="27" width="18" height="18" fill="#212121"/>
|
<rect x="30" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||||
</g>
|
</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"/>
|
<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="31" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
<rect x="23" 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"/>
|
<rect x="22" 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="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="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="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>
|
</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">
|
<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="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="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"/>
|
<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="47" cy="36" r="34" fill="#1C1C1C"/>
|
<circle cx="39" cy="36" r="33.5" stroke="white" stroke-opacity="0.24"/>
|
||||||
<circle cx="47" 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="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="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"/>
|
||||||
<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_2_810" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||||
<mask id="mask0_1180_4965" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="38" 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"/>
|
||||||
<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"/>
|
|
||||||
</mask>
|
</mask>
|
||||||
<g mask="url(#mask0_1180_4965)">
|
<g mask="url(#mask0_2_810)">
|
||||||
<rect x="38" y="27" width="18" height="18" fill="#00AFFF"/>
|
<rect x="30" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||||
</g>
|
</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"/>
|
<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="31" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
<rect x="23" 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"/>
|
<rect x="22" 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="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="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="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>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20250430.2"
|
version = "20250502.1"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
license-files = ["LICENSE*"]
|
license-files = ["LICENSE*"]
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { ReactiveElement } from "lit";
|
import type { ReactiveElement } from "lit";
|
||||||
import type { InternalPropertyDeclaration } from "lit/decorators";
|
|
||||||
|
|
||||||
type Callback = (oldValue: any, newValue: any) => void;
|
type Callback = (oldValue: any, newValue: any) => void;
|
||||||
|
|
||||||
@ -108,7 +107,6 @@ export function storage(options: {
|
|||||||
storage?: "localStorage" | "sessionStorage";
|
storage?: "localStorage" | "sessionStorage";
|
||||||
subscribe?: boolean;
|
subscribe?: boolean;
|
||||||
state?: boolean;
|
state?: boolean;
|
||||||
stateOptions?: InternalPropertyDeclaration;
|
|
||||||
serializer?: (value: any) => any;
|
serializer?: (value: any) => any;
|
||||||
deserializer?: (value: any) => any;
|
deserializer?: (value: any) => any;
|
||||||
}) {
|
}) {
|
||||||
@ -174,7 +172,7 @@ export function storage(options: {
|
|||||||
performUpdate.call(this);
|
performUpdate.call(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.state && options.subscribe) {
|
if (options.subscribe) {
|
||||||
const connectedCallback = proto.connectedCallback;
|
const connectedCallback = proto.connectedCallback;
|
||||||
const disconnectedCallback = proto.disconnectedCallback;
|
const disconnectedCallback = proto.disconnectedCallback;
|
||||||
|
|
||||||
@ -192,12 +190,6 @@ export function storage(options: {
|
|||||||
el.__unbsubLocalStorage = undefined;
|
el.__unbsubLocalStorage = undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (options.state) {
|
|
||||||
ReactiveElement.createProperty(propertyKey, {
|
|
||||||
noAccessor: true,
|
|
||||||
...options.stateOptions,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey);
|
const descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey);
|
||||||
let newDescriptor: PropertyDescriptor;
|
let newDescriptor: PropertyDescriptor;
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
import {
|
import type { ReactiveElement, PropertyValues } from "lit";
|
||||||
ReactiveElement,
|
|
||||||
type PropertyDeclaration,
|
|
||||||
type PropertyValues,
|
|
||||||
} from "lit";
|
|
||||||
import { shallowEqual } from "../util/shallow-equal";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform function type.
|
* Transform function type.
|
||||||
*/
|
*/
|
||||||
@ -23,7 +17,6 @@ type ReactiveTransformElement = ReactiveElement & {
|
|||||||
export function transform<T, V>(config: {
|
export function transform<T, V>(config: {
|
||||||
transformer: Transformer<T, V>;
|
transformer: Transformer<T, V>;
|
||||||
watch?: PropertyKey[];
|
watch?: PropertyKey[];
|
||||||
propertyOptions?: PropertyDeclaration;
|
|
||||||
}) {
|
}) {
|
||||||
return <ElemClass extends ReactiveElement>(
|
return <ElemClass extends ReactiveElement>(
|
||||||
proto: ElemClass,
|
proto: ElemClass,
|
||||||
@ -84,11 +77,6 @@ export function transform<T, V>(config: {
|
|||||||
curWatch.add(propertyKey);
|
curWatch.add(propertyKey);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ReactiveElement.createProperty(propertyKey, {
|
|
||||||
noAccessor: true,
|
|
||||||
hasChanged: (v: any, o: any) => !shallowEqual(v, o),
|
|
||||||
...config.propertyOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
const descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey);
|
const descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey);
|
||||||
let newDescriptor: PropertyDescriptor;
|
let newDescriptor: PropertyDescriptor;
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
export const canOverrideAlphanumericInput = (composedPath: EventTarget[]) => {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,16 +73,20 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stateObj = this.hass.states[this.entityId!] as HassEntity | undefined;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-combo-box
|
<ha-combo-box
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value
|
.value=${this.value
|
||||||
|
? stateObj
|
||||||
? computeAttributeNameDisplay(
|
? computeAttributeNameDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
this.hass.states[this.entityId!],
|
stateObj,
|
||||||
this.hass.entities,
|
this.hass.entities,
|
||||||
this.value
|
this.value
|
||||||
)
|
)
|
||||||
|
: this.value
|
||||||
: ""}
|
: ""}
|
||||||
.autofocus=${this.autofocus}
|
.autofocus=${this.autofocus}
|
||||||
.label=${this.label ??
|
.label=${this.label ??
|
||||||
|
@ -314,9 +314,7 @@ export class HaEntityComboBox extends LitElement {
|
|||||||
...hass!.states[entityId],
|
...hass!.states[entityId],
|
||||||
label: "",
|
label: "",
|
||||||
primary: primary,
|
primary: primary,
|
||||||
secondary:
|
secondary: secondary,
|
||||||
secondary ||
|
|
||||||
this.hass.localize("ui.components.device-picker.no_area"),
|
|
||||||
translated_domain: translatedDomain,
|
translated_domain: translatedDomain,
|
||||||
sorting_label: [deviceName, entityName].filter(Boolean).join("-"),
|
sorting_label: [deviceName, entityName].filter(Boolean).join("-"),
|
||||||
entity_name: entityName || deviceName,
|
entity_name: entityName || deviceName,
|
||||||
|
@ -162,10 +162,7 @@ export class HaEntityPicker extends LitElement {
|
|||||||
slot="start"
|
slot="start"
|
||||||
></state-badge>
|
></state-badge>
|
||||||
<span slot="headline">${primary}</span>
|
<span slot="headline">${primary}</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">${secondary}</span>
|
||||||
${secondary ||
|
|
||||||
this.hass.localize("ui.components.device-picker.no_area")}
|
|
||||||
</span>
|
|
||||||
${showClearIcon
|
${showClearIcon
|
||||||
? html`<ha-icon-button
|
? html`<ha-icon-button
|
||||||
class="clear"
|
class="clear"
|
||||||
|
@ -30,8 +30,9 @@ class HaLabeledSlider extends LitElement {
|
|||||||
@property({ type: Number }) public value?: number;
|
@property({ type: Number }) public value?: number;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
const title = this._getTitle();
|
||||||
return html`
|
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="extra-container"><slot name="extra"></slot></div>
|
||||||
<div class="slider-container">
|
<div class="slider-container">
|
||||||
${this.icon ? html`<ha-icon icon=${this.icon}></ha-icon>` : nothing}
|
${this.icon ? html`<ha-icon icon=${this.icon}></ha-icon>` : nothing}
|
||||||
@ -73,17 +74,20 @@ class HaLabeledSlider extends LitElement {
|
|||||||
|
|
||||||
.slider-container {
|
.slider-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon {
|
ha-icon {
|
||||||
margin-top: 8px;
|
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-slider {
|
ha-slider {
|
||||||
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
align-items: center;
|
||||||
background-image: var(--ha-slider-background);
|
background-image: var(--ha-slider-background);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
height: 32px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ export class HaLanguagePicker extends LitElement {
|
|||||||
localeChanged
|
localeChanged
|
||||||
) {
|
) {
|
||||||
this._select.layoutOptions();
|
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 });
|
fireEvent(this, "value-changed", { value: this._select.value });
|
||||||
}
|
}
|
||||||
if (!this.value) {
|
if (!this.value) {
|
||||||
@ -141,7 +141,10 @@ export class HaLanguagePicker extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const value =
|
const value =
|
||||||
this.value ?? (this.required ? languageOptions[0]?.value : this.value);
|
this.value ??
|
||||||
|
(this.required && !this.disabled
|
||||||
|
? languageOptions[0]?.value
|
||||||
|
: this.value);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-select
|
<ha-select
|
||||||
@ -182,7 +185,7 @@ export class HaLanguagePicker extends LitElement {
|
|||||||
|
|
||||||
private _changed(ev): void {
|
private _changed(ev): void {
|
||||||
const target = ev.target as HaSelect;
|
const target = ev.target as HaSelect;
|
||||||
if (target.value === "" || target.value === this.value) {
|
if (this.disabled || target.value === "" || target.value === this.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.value = target.value;
|
this.value = target.value;
|
||||||
|
@ -210,6 +210,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _unsubPersistentNotifications: UnsubscribeFunc | undefined;
|
private _unsubPersistentNotifications: UnsubscribeFunc | undefined;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "sidebarPanelOrder",
|
key: "sidebarPanelOrder",
|
||||||
state: true,
|
state: true,
|
||||||
@ -217,6 +218,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _panelOrder: string[] = [];
|
private _panelOrder: string[] = [];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "sidebarHiddenPanels",
|
key: "sidebarHiddenPanels",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -42,6 +42,7 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
|
|
||||||
@state() private _provider?: TTSEngine;
|
@state() private _provider?: TTSEngine;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "TtsMessage",
|
key: "TtsMessage",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -890,12 +890,18 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
margin-right: 48px;
|
margin-right: 48px;
|
||||||
|
margin-inline-end: 48px;
|
||||||
|
margin-inline-start: initial;
|
||||||
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight-add-button ha-svg-icon {
|
.highlight-add-button ha-svg-icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -0.5em;
|
top: -0.5em;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
margin-inline-start: 8px;
|
||||||
|
margin-inline-end: initial;
|
||||||
|
transform: scaleX(var(--scale-direction));
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
@ -11,8 +11,8 @@ export class HaTimeline extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public raised = false;
|
@property({ type: Boolean, reflect: true }) public raised = false;
|
||||||
|
|
||||||
@property({ attribute: false, reflect: true, type: Boolean }) notEnabled =
|
@property({ attribute: "not-enabled", reflect: true, type: Boolean })
|
||||||
false;
|
notEnabled = false;
|
||||||
|
|
||||||
@property({ attribute: "last-item", type: Boolean }) public lastItem = false;
|
@property({ attribute: "last-item", type: Boolean }) public lastItem = false;
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ export class HaTimeline extends LitElement {
|
|||||||
margin-inline-start: initial;
|
margin-inline-start: initial;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
:host([notEnabled]) ha-svg-icon {
|
:host([not-enabled]) ha-svg-icon {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
ha-svg-icon {
|
ha-svg-icon {
|
||||||
|
@ -17,8 +17,8 @@ export class HatGraphNode extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public error = false;
|
@property({ type: Boolean }) public error = false;
|
||||||
|
|
||||||
@property({ attribute: false, reflect: true, type: Boolean }) notEnabled =
|
@property({ attribute: "not-enabled", reflect: true, type: Boolean })
|
||||||
false;
|
notEnabled = false;
|
||||||
|
|
||||||
@property({ attribute: "graph-start", reflect: true, type: Boolean })
|
@property({ attribute: "graph-start", reflect: true, type: Boolean })
|
||||||
graphStart = false;
|
graphStart = false;
|
||||||
@ -127,13 +127,13 @@ export class HatGraphNode extends LitElement {
|
|||||||
--stroke-clr: var(--hover-clr);
|
--stroke-clr: var(--hover-clr);
|
||||||
--icon-clr: var(--default-icon-clr);
|
--icon-clr: var(--default-icon-clr);
|
||||||
}
|
}
|
||||||
:host([notEnabled]) circle {
|
:host([not-enabled]) circle {
|
||||||
--stroke-clr: var(--disabled-clr);
|
--stroke-clr: var(--disabled-clr);
|
||||||
}
|
}
|
||||||
:host([notEnabled][active]) circle {
|
:host([not-enabled][active]) circle {
|
||||||
--stroke-clr: var(--disabled-active-clr);
|
--stroke-clr: var(--disabled-active-clr);
|
||||||
}
|
}
|
||||||
:host([notEnabled]:hover) circle {
|
:host([not-enabled]:hover) circle {
|
||||||
--stroke-clr: var(--disabled-hover-clr);
|
--stroke-clr: var(--disabled-hover-clr);
|
||||||
}
|
}
|
||||||
svg:not(.safari) {
|
svg:not(.safari) {
|
||||||
|
@ -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 { ensureArray } from "../common/array/ensure-array";
|
||||||
import {
|
import {
|
||||||
formatDurationLong,
|
formatDurationLong,
|
||||||
@ -155,7 +155,7 @@ const tryDescribeTrigger = (
|
|||||||
|
|
||||||
const stateObj = Array.isArray(trigger.entity_id)
|
const stateObj = Array.isArray(trigger.entity_id)
|
||||||
? hass.states[trigger.entity_id[0]]
|
? 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)) {
|
if (Array.isArray(trigger.entity_id)) {
|
||||||
for (const entity of trigger.entity_id.values()) {
|
for (const entity of trigger.entity_id.values()) {
|
||||||
@ -172,12 +172,14 @@ const tryDescribeTrigger = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const attribute = trigger.attribute
|
const attribute = trigger.attribute
|
||||||
|
? stateObj
|
||||||
? computeAttributeNameDisplay(
|
? computeAttributeNameDisplay(
|
||||||
hass.localize,
|
hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
hass.entities,
|
hass.entities,
|
||||||
trigger.attribute
|
trigger.attribute
|
||||||
)
|
)
|
||||||
|
: trigger.attribute
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const duration = trigger.for
|
const duration = trigger.for
|
||||||
@ -232,13 +234,15 @@ const tryDescribeTrigger = (
|
|||||||
if (trigger.attribute) {
|
if (trigger.attribute) {
|
||||||
const stateObj = Array.isArray(trigger.entity_id)
|
const stateObj = Array.isArray(trigger.entity_id)
|
||||||
? hass.states[trigger.entity_id[0]]
|
? hass.states[trigger.entity_id[0]]
|
||||||
: hass.states[trigger.entity_id];
|
: (hass.states[trigger.entity_id] as HassEntity | undefined);
|
||||||
attribute = computeAttributeNameDisplay(
|
attribute = stateObj
|
||||||
|
? computeAttributeNameDisplay(
|
||||||
hass.localize,
|
hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
hass.entities,
|
hass.entities,
|
||||||
trigger.attribute
|
trigger.attribute
|
||||||
);
|
)
|
||||||
|
: trigger.attribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entityArray: string[] = ensureArray(trigger.entity_id);
|
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 fromChoice = "other";
|
||||||
let fromString = "";
|
let fromString = "";
|
||||||
@ -266,7 +270,8 @@ const tryDescribeTrigger = (
|
|||||||
const from: string[] = [];
|
const from: string[] = [];
|
||||||
for (const state of fromArray) {
|
for (const state of fromArray) {
|
||||||
from.push(
|
from.push(
|
||||||
trigger.attribute
|
stateObj
|
||||||
|
? trigger.attribute
|
||||||
? hass
|
? hass
|
||||||
.formatEntityAttributeValue(
|
.formatEntityAttributeValue(
|
||||||
stateObj,
|
stateObj,
|
||||||
@ -275,6 +280,7 @@ const tryDescribeTrigger = (
|
|||||||
)
|
)
|
||||||
.toString()
|
.toString()
|
||||||
: hass.formatEntityState(stateObj, state)
|
: hass.formatEntityState(stateObj, state)
|
||||||
|
: state
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (from.length !== 0) {
|
if (from.length !== 0) {
|
||||||
@ -298,7 +304,8 @@ const tryDescribeTrigger = (
|
|||||||
const to: string[] = [];
|
const to: string[] = [];
|
||||||
for (const state of toArray) {
|
for (const state of toArray) {
|
||||||
to.push(
|
to.push(
|
||||||
trigger.attribute
|
stateObj
|
||||||
|
? trigger.attribute
|
||||||
? hass
|
? hass
|
||||||
.formatEntityAttributeValue(
|
.formatEntityAttributeValue(
|
||||||
stateObj,
|
stateObj,
|
||||||
@ -307,6 +314,7 @@ const tryDescribeTrigger = (
|
|||||||
)
|
)
|
||||||
.toString()
|
.toString()
|
||||||
: hass.formatEntityState(stateObj, state).toString()
|
: hass.formatEntityState(stateObj, state).toString()
|
||||||
|
: state
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (to.length !== 0) {
|
if (to.length !== 0) {
|
||||||
@ -725,7 +733,9 @@ const tryDescribeTrigger = (
|
|||||||
if (localized) {
|
if (localized) {
|
||||||
return 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} ${
|
return `${stateObj ? computeStateName(stateObj) : config.entity_id} ${
|
||||||
config.type
|
config.type
|
||||||
}`;
|
}`;
|
||||||
@ -894,13 +904,15 @@ const tryDescribeCondition = (
|
|||||||
if (condition.attribute) {
|
if (condition.attribute) {
|
||||||
const stateObj = Array.isArray(condition.entity_id)
|
const stateObj = Array.isArray(condition.entity_id)
|
||||||
? hass.states[condition.entity_id[0]]
|
? hass.states[condition.entity_id[0]]
|
||||||
: hass.states[condition.entity_id];
|
: (hass.states[condition.entity_id] as HassEntity | undefined);
|
||||||
attribute = computeAttributeNameDisplay(
|
attribute = stateObj
|
||||||
|
? computeAttributeNameDisplay(
|
||||||
hass.localize,
|
hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
hass.entities,
|
hass.entities,
|
||||||
condition.attribute
|
condition.attribute
|
||||||
);
|
)
|
||||||
|
: condition.attribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entities: string[] = [];
|
const entities: string[] = [];
|
||||||
@ -919,16 +931,16 @@ const tryDescribeCondition = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const states: string[] = [];
|
const states: string[] = [];
|
||||||
const stateObj =
|
const stateObj = hass.states[
|
||||||
hass.states[
|
|
||||||
Array.isArray(condition.entity_id)
|
Array.isArray(condition.entity_id)
|
||||||
? condition.entity_id[0]
|
? condition.entity_id[0]
|
||||||
: condition.entity_id
|
: condition.entity_id
|
||||||
];
|
] as HassEntity | undefined;
|
||||||
if (Array.isArray(condition.state)) {
|
if (Array.isArray(condition.state)) {
|
||||||
for (const state of condition.state.values()) {
|
for (const state of condition.state.values()) {
|
||||||
states.push(
|
states.push(
|
||||||
condition.attribute
|
stateObj
|
||||||
|
? condition.attribute
|
||||||
? hass
|
? hass
|
||||||
.formatEntityAttributeValue(
|
.formatEntityAttributeValue(
|
||||||
stateObj,
|
stateObj,
|
||||||
@ -937,11 +949,13 @@ const tryDescribeCondition = (
|
|||||||
)
|
)
|
||||||
.toString()
|
.toString()
|
||||||
: hass.formatEntityState(stateObj, state)
|
: hass.formatEntityState(stateObj, state)
|
||||||
|
: state
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (condition.state !== "") {
|
} else if (condition.state !== "") {
|
||||||
states.push(
|
states.push(
|
||||||
condition.attribute
|
stateObj
|
||||||
|
? condition.attribute
|
||||||
? hass
|
? hass
|
||||||
.formatEntityAttributeValue(
|
.formatEntityAttributeValue(
|
||||||
stateObj,
|
stateObj,
|
||||||
@ -950,6 +964,7 @@ const tryDescribeCondition = (
|
|||||||
)
|
)
|
||||||
.toString()
|
.toString()
|
||||||
: hass.formatEntityState(stateObj, condition.state.toString())
|
: hass.formatEntityState(stateObj, condition.state.toString())
|
||||||
|
: condition.state.toString()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -979,7 +994,7 @@ const tryDescribeCondition = (
|
|||||||
// Numeric State Condition
|
// Numeric State Condition
|
||||||
if (condition.condition === "numeric_state" && condition.entity_id) {
|
if (condition.condition === "numeric_state" && condition.entity_id) {
|
||||||
const entity_ids = ensureArray(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(
|
const entity = formatListWithAnds(
|
||||||
hass.locale,
|
hass.locale,
|
||||||
entity_ids.map((id) =>
|
entity_ids.map((id) =>
|
||||||
@ -988,12 +1003,14 @@ const tryDescribeCondition = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const attribute = condition.attribute
|
const attribute = condition.attribute
|
||||||
|
? stateObj
|
||||||
? computeAttributeNameDisplay(
|
? computeAttributeNameDisplay(
|
||||||
hass.localize,
|
hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
hass.entities,
|
hass.entities,
|
||||||
condition.attribute
|
condition.attribute
|
||||||
)
|
)
|
||||||
|
: condition.attribute
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (condition.above !== undefined && condition.below !== undefined) {
|
if (condition.above !== undefined && condition.below !== undefined) {
|
||||||
@ -1187,7 +1204,9 @@ const tryDescribeCondition = (
|
|||||||
if (localized) {
|
if (localized) {
|
||||||
return 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} ${
|
return `${stateObj ? computeStateName(stateObj) : config.entity_id} ${
|
||||||
config.type
|
config.type
|
||||||
}`;
|
}`;
|
||||||
|
@ -959,21 +959,13 @@ const computeConsumptionDataPartial = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
data.timestamps.forEach((t) => {
|
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 {
|
const {
|
||||||
grid_to_battery,
|
grid_to_battery,
|
||||||
battery_to_grid,
|
battery_to_grid,
|
||||||
used_solar,
|
used_solar,
|
||||||
used_grid,
|
used_grid,
|
||||||
used_battery,
|
used_battery,
|
||||||
|
used_total,
|
||||||
solar_to_battery,
|
solar_to_battery,
|
||||||
solar_to_grid,
|
solar_to_grid,
|
||||||
} = computeConsumptionSingle({
|
} = computeConsumptionSingle({
|
||||||
@ -984,6 +976,8 @@ const computeConsumptionDataPartial = (
|
|||||||
from_battery: data.from_battery && (data.from_battery[t] ?? 0),
|
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.grid_to_battery[t] = grid_to_battery;
|
||||||
outData.total.grid_to_battery += grid_to_battery;
|
outData.total.grid_to_battery += grid_to_battery;
|
||||||
outData.battery_to_grid![t] = battery_to_grid;
|
outData.battery_to_grid![t] = battery_to_grid;
|
||||||
@ -1017,12 +1011,20 @@ export const computeConsumptionSingle = (data: {
|
|||||||
used_solar: number;
|
used_solar: number;
|
||||||
used_grid: number;
|
used_grid: number;
|
||||||
used_battery: number;
|
used_battery: number;
|
||||||
|
used_total: number;
|
||||||
} => {
|
} => {
|
||||||
const to_grid = data.to_grid;
|
let to_grid = Math.max(data.to_grid || 0, 0);
|
||||||
const to_battery = data.to_battery;
|
let to_battery = Math.max(data.to_battery || 0, 0);
|
||||||
const solar = data.solar;
|
let solar = Math.max(data.solar || 0, 0);
|
||||||
const from_grid = data.from_grid;
|
let from_grid = Math.max(data.from_grid || 0, 0);
|
||||||
const from_battery = data.from_battery;
|
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 used_solar = 0;
|
||||||
let grid_to_battery = 0;
|
let grid_to_battery = 0;
|
||||||
@ -1031,41 +1033,57 @@ export const computeConsumptionSingle = (data: {
|
|||||||
let solar_to_grid = 0;
|
let solar_to_grid = 0;
|
||||||
let used_battery = 0;
|
let used_battery = 0;
|
||||||
let used_grid = 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) {
|
let used_total_remaining = Math.max(used_total, 0);
|
||||||
used_battery = (from_battery || 0) - battery_to_grid;
|
// 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) {
|
// Battery_Out -> Grid_Out
|
||||||
used_grid = from_grid - grid_to_battery;
|
battery_to_grid = Math.min(from_battery, to_grid);
|
||||||
}
|
from_battery -= battery_to_grid;
|
||||||
|
to_grid -= battery_to_grid;
|
||||||
|
|
||||||
if (solar != null) {
|
// Solar -> Grid_Out
|
||||||
if (to_battery != null) {
|
solar_to_grid = Math.min(solar, to_grid);
|
||||||
solar_to_battery = Math.max(0, (to_battery || 0) - grid_to_battery);
|
to_grid -= solar_to_grid;
|
||||||
}
|
solar -= solar_to_grid;
|
||||||
if (to_grid != null) {
|
|
||||||
solar_to_grid = Math.max(0, (to_grid || 0) - battery_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 {
|
return {
|
||||||
used_solar,
|
used_solar,
|
||||||
used_grid,
|
used_grid,
|
||||||
used_battery,
|
used_battery,
|
||||||
|
used_total,
|
||||||
grid_to_battery,
|
grid_to_battery,
|
||||||
battery_to_grid,
|
battery_to_grid,
|
||||||
solar_to_battery,
|
solar_to_battery,
|
||||||
|
@ -129,7 +129,10 @@ class StepFlowCreateEntry extends LitElement {
|
|||||||
)}</span
|
)}</span
|
||||||
>`
|
>`
|
||||||
: nothing}
|
: nothing}
|
||||||
${devices.length === 0
|
${devices.length === 0 &&
|
||||||
|
["options_flow", "repair_flow"].includes(this.flowConfig.flowType)
|
||||||
|
? nothing
|
||||||
|
: devices.length === 0
|
||||||
? html`<p>
|
? html`<p>
|
||||||
${localize(
|
${localize(
|
||||||
"ui.panel.config.integrations.config_flow.created_config",
|
"ui.panel.config.integrations.config_flow.created_config",
|
||||||
|
@ -278,8 +278,6 @@ class StepFlowForm extends LitElement {
|
|||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
padding-inline-end: 72px;
|
|
||||||
direction: var(--direction);
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -15,24 +15,26 @@ import { customElement, property, query, state } from "lit/decorators";
|
|||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import Fuse from "fuse.js";
|
||||||
import { canShowPage } from "../../common/config/can_show_page";
|
import { canShowPage } from "../../common/config/can_show_page";
|
||||||
import { componentsWithService } from "../../common/config/components_with_service";
|
import { componentsWithService } from "../../common/config/components_with_service";
|
||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeDeviceNameDisplay } from "../../common/entity/compute_device_name";
|
import {
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
computeDeviceName,
|
||||||
|
computeDeviceNameDisplay,
|
||||||
|
} from "../../common/entity/compute_device_name";
|
||||||
import { navigate } from "../../common/navigate";
|
import { navigate } from "../../common/navigate";
|
||||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||||
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
||||||
import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching";
|
|
||||||
import { debounce } from "../../common/util/debounce";
|
import { debounce } from "../../common/util/debounce";
|
||||||
import "../../components/ha-icon-button";
|
import "../../components/ha-icon-button";
|
||||||
import "../../components/ha-label";
|
import "../../components/ha-label";
|
||||||
import "../../components/ha-list";
|
import "../../components/ha-list";
|
||||||
import "../../components/ha-list-item";
|
|
||||||
import "../../components/ha-spinner";
|
import "../../components/ha-spinner";
|
||||||
import "../../components/ha-textfield";
|
import "../../components/ha-textfield";
|
||||||
import "../../components/ha-tip";
|
import "../../components/ha-tip";
|
||||||
|
import "../../components/ha-md-list-item";
|
||||||
import { fetchHassioAddonsInfo } from "../../data/hassio/addon";
|
import { fetchHassioAddonsInfo } from "../../data/hassio/addon";
|
||||||
import { domainToName } from "../../data/integration";
|
import { domainToName } from "../../data/integration";
|
||||||
import { getPanelNameTranslationKey } from "../../data/panel";
|
import { getPanelNameTranslationKey } from "../../data/panel";
|
||||||
@ -44,6 +46,13 @@ import type { HomeAssistant } from "../../types";
|
|||||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||||
import { showShortcutsDialog } from "../shortcuts/show-shortcuts-dialog";
|
import { showShortcutsDialog } from "../shortcuts/show-shortcuts-dialog";
|
||||||
import { QuickBarMode, type QuickBarParams } from "./show-dialog-quick-bar";
|
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 {
|
interface QuickBarItem extends ScorableTextItem {
|
||||||
primaryText: string;
|
primaryText: string;
|
||||||
@ -59,6 +68,9 @@ interface CommandItem extends QuickBarItem {
|
|||||||
interface EntityItem extends QuickBarItem {
|
interface EntityItem extends QuickBarItem {
|
||||||
altText: string;
|
altText: string;
|
||||||
icon?: TemplateResult;
|
icon?: TemplateResult;
|
||||||
|
translatedDomain: string;
|
||||||
|
entityId: string;
|
||||||
|
friendlyName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeviceItem extends QuickBarItem {
|
interface DeviceItem extends QuickBarItem {
|
||||||
@ -82,6 +94,23 @@ type BaseNavigationCommand = Pick<
|
|||||||
QuickBarNavigationItem,
|
QuickBarNavigationItem,
|
||||||
"primaryText" | "path"
|
"primaryText" | "path"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
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(--ha-font-family-code)",
|
||||||
|
fontSize: "var(--ha-font-size-xs)",
|
||||||
|
});
|
||||||
|
|
||||||
@customElement("ha-quick-bar")
|
@customElement("ha-quick-bar")
|
||||||
export class QuickBar extends LitElement {
|
export class QuickBar extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -139,6 +168,11 @@ export class QuickBar extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this.hass.loadBackendTranslation("title");
|
||||||
|
}
|
||||||
|
|
||||||
private _getItems = memoizeOne(
|
private _getItems = memoizeOne(
|
||||||
(
|
(
|
||||||
mode: QuickBarMode,
|
mode: QuickBarMode,
|
||||||
@ -323,61 +357,67 @@ export class QuickBar extends LitElement {
|
|||||||
|
|
||||||
private _renderDeviceItem(item: DeviceItem, index?: number) {
|
private _renderDeviceItem(item: DeviceItem, index?: number) {
|
||||||
return html`
|
return html`
|
||||||
<ha-list-item
|
<ha-md-list-item
|
||||||
.twoline=${Boolean(item.area)}
|
class="two-line"
|
||||||
.item=${item}
|
.item=${item}
|
||||||
index=${ifDefined(index)}
|
index=${ifDefined(index)}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<span>${item.primaryText}</span>
|
<span slot="headline">${item.primaryText}</span>
|
||||||
${item.area
|
${item.area
|
||||||
? html`
|
? html` <span slot="supporting-text">${item.area}</span> `
|
||||||
<span slot="secondary" class="item-text secondary"
|
|
||||||
>${item.area}</span
|
|
||||||
>
|
|
||||||
`
|
|
||||||
: nothing}
|
: nothing}
|
||||||
</ha-list-item>
|
</ha-md-list-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderEntityItem(item: EntityItem, index?: number) {
|
private _renderEntityItem(item: EntityItem, index?: number) {
|
||||||
|
const showEntityId = this.hass.userData?.showEntityIdPicker;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-list-item
|
<ha-md-list-item
|
||||||
.twoline=${Boolean(item.altText)}
|
class=${showEntityId ? "three-line" : "two-line"}
|
||||||
.item=${item}
|
.item=${item}
|
||||||
index=${ifDefined(index)}
|
index=${ifDefined(index)}
|
||||||
graphic="icon"
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
${item.iconPath
|
${item.iconPath
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
.path=${item.iconPath}
|
.path=${item.iconPath}
|
||||||
class="entity"
|
class="entity"
|
||||||
slot="graphic"
|
slot="start"
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
`
|
`
|
||||||
: html`<span slot="graphic">${item.icon}</span>`}
|
: html`<span slot="start">${item.icon}</span>`}
|
||||||
<span>${item.primaryText}</span>
|
<span slot="headline">${item.primaryText}</span>
|
||||||
${item.altText
|
${item.altText
|
||||||
? html`
|
? html` <span slot="supporting-text">${item.altText}</span> `
|
||||||
<span slot="secondary" class="item-text secondary"
|
|
||||||
>${item.altText}</span
|
|
||||||
>
|
|
||||||
`
|
|
||||||
: nothing}
|
: nothing}
|
||||||
</ha-list-item>
|
${item.entityId && showEntityId
|
||||||
|
? html`<span slot="supporting-text" style=${ENTITY_ID_STYLE}
|
||||||
|
>${item.entityId}</span
|
||||||
|
>`
|
||||||
|
: nothing}
|
||||||
|
${item.translatedDomain && !showEntityId
|
||||||
|
? html`<div slot="trailing-supporting-text" style=${DOMAIN_STYLE}>
|
||||||
|
${item.translatedDomain}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
</ha-md-list-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderCommandItem(item: CommandItem, index?: number) {
|
private _renderCommandItem(item: CommandItem, index?: number) {
|
||||||
return html`
|
return html`
|
||||||
<ha-list-item
|
<ha-md-list-item
|
||||||
.item=${item}
|
.item=${item}
|
||||||
index=${ifDefined(index)}
|
index=${ifDefined(index)}
|
||||||
hasMeta
|
hasMeta
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<ha-label
|
<ha-label
|
||||||
@ -386,7 +426,10 @@ export class QuickBar extends LitElement {
|
|||||||
>
|
>
|
||||||
${item.iconPath
|
${item.iconPath
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon .path=${item.iconPath} slot="icon"></ha-svg-icon>
|
<ha-svg-icon
|
||||||
|
.path=${item.iconPath}
|
||||||
|
slot="start"
|
||||||
|
></ha-svg-icon>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${item.categoryText}
|
${item.categoryText}
|
||||||
@ -394,7 +437,7 @@ export class QuickBar extends LitElement {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="command-text">${item.primaryText}</span>
|
<span class="command-text">${item.primaryText}</span>
|
||||||
</ha-list-item>
|
</ha-md-list-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,7 +464,7 @@ export class QuickBar extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _getItemAtIndex(index: number): ListItem | null {
|
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 {
|
private _addSpinnerToCommandItem(index: number): void {
|
||||||
@ -519,7 +562,7 @@ export class QuickBar extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleItemClick(ev) {
|
private _handleItemClick(ev) {
|
||||||
const listItem = ev.target.closest("ha-list-item");
|
const listItem = ev.target.closest("ha-md-list-item");
|
||||||
this._processItemAndCloseDialog(
|
this._processItemAndCloseDialog(
|
||||||
listItem.item,
|
listItem.item,
|
||||||
Number(listItem.getAttribute("index"))
|
Number(listItem.getAttribute("index"))
|
||||||
@ -555,18 +598,41 @@ export class QuickBar extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _generateEntityItems(): EntityItem[] {
|
private _generateEntityItems(): EntityItem[] {
|
||||||
|
const isRTL = computeRTL(this.hass);
|
||||||
|
|
||||||
return Object.keys(this.hass.states)
|
return Object.keys(this.hass.states)
|
||||||
.map((entityId) => {
|
.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 = {
|
const entityItem = {
|
||||||
primaryText: computeStateName(entityState),
|
primaryText: primary,
|
||||||
altText: entityId,
|
altText: secondary,
|
||||||
icon: html`
|
icon: html`
|
||||||
<ha-state-icon
|
<ha-state-icon
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.stateObj=${entityState}
|
.stateObj=${stateObj}
|
||||||
></ha-state-icon>
|
></ha-state-icon>
|
||||||
`,
|
`,
|
||||||
|
translatedDomain: translatedDomain,
|
||||||
|
entityId: entityId,
|
||||||
|
friendlyName: friendlyName,
|
||||||
action: () => fireEvent(this, "hass-more-info", { entityId }),
|
action: () => fireEvent(this, "hass-more-info", { entityId }),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -846,9 +912,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(
|
private _filterItems = memoizeOne(
|
||||||
(items: QuickBarItem[], filter: string): QuickBarItem[] =>
|
(items: QuickBarItem[], filter: string): QuickBarItem[] => {
|
||||||
fuzzyFilterSort<QuickBarItem>(filter.trimLeft(), items)
|
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() {
|
static get styles() {
|
||||||
@ -930,9 +1017,25 @@ export class QuickBar extends LitElement {
|
|||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-list-item {
|
ha-md-list-item {
|
||||||
width: 100%;
|
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-tip {
|
ha-tip {
|
||||||
|
@ -69,12 +69,17 @@ const _SHORTCUTS: Section[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "ui.dialogs.shortcuts.automations.title",
|
key: "ui.dialogs.shortcuts.automation_script.title",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: "shortcut",
|
type: "shortcut",
|
||||||
shortcut: [{ key: "ui.dialogs.shortcuts.shortcuts.ctrl_cmd" }, "V"],
|
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;
|
align-items: center;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
margin-inline-end: 12px;
|
margin-inline-end: 12px;
|
||||||
|
margin-inline-start: initial;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -36,6 +36,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
|
|
||||||
@state() private _opened = false;
|
@state() private _opened = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "AssistPipelineId",
|
key: "AssistPipelineId",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import "../components/ha-spinner";
|
||||||
|
|
||||||
class HaInitPage extends LitElement {
|
class HaInitPage extends LitElement {
|
||||||
@property({ type: Boolean }) public error = false;
|
@property({ type: Boolean }) public error = false;
|
||||||
|
@ -42,6 +42,7 @@ class PanelCalendar extends LitElement {
|
|||||||
|
|
||||||
@state() private _error?: string = undefined;
|
@state() private _error?: string = undefined;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "deSelectedCalendars",
|
key: "deSelectedCalendars",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -69,6 +69,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
})
|
})
|
||||||
private _activeHiddenColumns?: string[];
|
private _activeHiddenColumns?: string[];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "application-credentials-table-search",
|
key: "application-credentials-table-search",
|
||||||
|
@ -36,6 +36,7 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
|
|
||||||
@state() private _showReorder = false;
|
@state() private _showReorder = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "automationClipboard",
|
key: "automationClipboard",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -36,6 +36,7 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
|
|
||||||
@state() private _showReorder = false;
|
@state() private _showReorder = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "automationClipboard",
|
key: "automationClipboard",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -135,6 +135,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
|||||||
|
|
||||||
@state() private _blueprintConfig?: BlueprintAutomationConfig;
|
@state() private _blueprintConfig?: BlueprintAutomationConfig;
|
||||||
|
|
||||||
|
@state()
|
||||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||||
@transform<EntityRegistryEntry[], EntityRegistryEntry>({
|
@transform<EntityRegistryEntry[], EntityRegistryEntry>({
|
||||||
transformer: function (this: HaAutomationEditor, value) {
|
transformer: function (this: HaAutomationEditor, value) {
|
||||||
|
@ -138,6 +138,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _filteredAutomations?: string[] | null;
|
@state() private _filteredAutomations?: string[] | null;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "automation-table-search",
|
key: "automation-table-search",
|
||||||
@ -146,6 +147,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _filter = "";
|
private _filter = "";
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "automation-table-filters-full",
|
key: "automation-table-filters-full",
|
||||||
|
@ -29,6 +29,7 @@ export default class HaAutomationOption extends LitElement {
|
|||||||
|
|
||||||
@state() private _showReorder = false;
|
@state() private _showReorder = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "automationClipboard",
|
key: "automationClipboard",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -38,6 +38,7 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
|
|
||||||
@state() private _showReorder = false;
|
@state() private _showReorder = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "automationClipboard",
|
key: "automationClipboard",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -98,6 +98,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _selected: string[] = [];
|
@state() private _selected: string[] = [];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "backups-table-filters",
|
key: "backups-table-filters",
|
||||||
|
@ -118,8 +118,7 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: this.config?.agents[this.agentId]
|
: html`<ha-backup-config-retention
|
||||||
? html`<ha-backup-config-retention
|
|
||||||
location-specific
|
location-specific
|
||||||
.headline=${this.hass.localize(
|
.headline=${this.hass.localize(
|
||||||
`ui.panel.config.backup.location.retention_for_${isLocalAgent(this.agentId) ? "this_system" : "location"}`,
|
`ui.panel.config.backup.location.retention_for_${isLocalAgent(this.agentId) ? "this_system" : "location"}`,
|
||||||
@ -129,8 +128,7 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
.retention=${this.config?.agents[this.agentId]
|
.retention=${this.config?.agents[this.agentId]
|
||||||
?.retention}
|
?.retention}
|
||||||
@value-changed=${this._retentionChanged}
|
@value-changed=${this._retentionChanged}
|
||||||
></ha-backup-config-retention>`
|
></ha-backup-config-retention>`}
|
||||||
: nothing}
|
|
||||||
</ha-card>
|
</ha-card>
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||||
import { LitElement, html } 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 memoizeOne from "memoize-one";
|
||||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
@ -118,6 +118,7 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
})
|
})
|
||||||
private _activeHiddenColumns?: string[];
|
private _activeHiddenColumns?: string[];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "blueprint-table-search",
|
key: "blueprint-table-search",
|
||||||
@ -499,9 +500,11 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
list: html`<ul>
|
list: html`<ul>
|
||||||
${[...(related.automation || []), ...(related.script || [])].map(
|
${[...(related.automation || []), ...(related.script || [])].map(
|
||||||
(item) => {
|
(item) => {
|
||||||
const state = this.hass.states[item];
|
const automationState = this.hass.states[item];
|
||||||
return html`<li>
|
return html`<li>
|
||||||
${state ? `${computeStateName(state)} (${item})` : item}
|
${automationState
|
||||||
|
? `${computeStateName(automationState)} (${item})`
|
||||||
|
: item}
|
||||||
</li>`;
|
</li>`;
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
@ -106,7 +106,7 @@ export class HaDeviceCard extends LitElement {
|
|||||||
<div class="extra-info">
|
<div class="extra-info">
|
||||||
${type === "bluetooth" &&
|
${type === "bluetooth" &&
|
||||||
isComponentLoaded(this.hass, "bluetooth")
|
isComponentLoaded(this.hass, "bluetooth")
|
||||||
? html`${titleCase(type)}
|
? html`${titleCase(type)}:
|
||||||
<a
|
<a
|
||||||
href="/config/bluetooth/advertisement-monitor?${createSearchParam(
|
href="/config/bluetooth/advertisement-monitor?${createSearchParam(
|
||||||
{ address: value }
|
{ address: value }
|
||||||
@ -114,7 +114,7 @@ export class HaDeviceCard extends LitElement {
|
|||||||
>${value.toUpperCase()}</a
|
>${value.toUpperCase()}</a
|
||||||
>`
|
>`
|
||||||
: type === "mac" && isComponentLoaded(this.hass, "dhcp")
|
: type === "mac" && isComponentLoaded(this.hass, "dhcp")
|
||||||
? html`${titleCase(type)}
|
? html`MAC:
|
||||||
<a
|
<a
|
||||||
href="/config/dhcp?${createSearchParam({
|
href="/config/dhcp?${createSearchParam({
|
||||||
mac_address: value,
|
mac_address: value,
|
||||||
|
@ -1559,6 +1559,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
padding-inline-start: 8px;
|
padding-inline-start: 8px;
|
||||||
|
padding-inline-end: initial;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +120,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _selected: string[] = [];
|
@state() private _selected: string[] = [];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "devices-table-search",
|
key: "devices-table-search",
|
||||||
@ -128,6 +129,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _filter: string = history.state?.filter || "";
|
private _filter: string = history.state?.filter || "";
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "devices-table-filters-full",
|
key: "devices-table-filters-full",
|
||||||
|
@ -159,6 +159,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||||
_entities!: EntityRegistryEntry[];
|
_entities!: EntityRegistryEntry[];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "entities-table-search",
|
key: "entities-table-search",
|
||||||
@ -169,6 +170,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "entities-table-filters",
|
key: "entities-table-filters",
|
||||||
|
@ -168,6 +168,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _activeCollapsed?: string;
|
private _activeCollapsed?: string;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "helpers-table-search",
|
key: "helpers-table-search",
|
||||||
|
@ -210,6 +210,9 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
|
|||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.columns=${this._columns(this.hass.localize)}
|
.columns=${this._columns(this.hass.localize)}
|
||||||
.data=${this._dataWithNamedSourceAndIds(this._data)}
|
.data=${this._dataWithNamedSourceAndIds(this._data)}
|
||||||
|
.noDataText=${this.hass.localize(
|
||||||
|
"ui.panel.config.bluetooth.no_advertisements_found"
|
||||||
|
)}
|
||||||
@row-click=${this._handleRowClicked}
|
@row-click=${this._handleRowClicked}
|
||||||
.initialGroupColumn=${this._activeGrouping}
|
.initialGroupColumn=${this._activeGrouping}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
|
@ -96,6 +96,9 @@ export class DHCPConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.columns=${this._columns(this.hass.localize)}
|
.columns=${this._columns(this.hass.localize)}
|
||||||
.data=${this._dataWithIds(this._data)}
|
.data=${this._dataWithIds(this._data)}
|
||||||
|
.noDataText=${this.hass.localize(
|
||||||
|
"ui.panel.config.dhcp.no_devices_found"
|
||||||
|
)}
|
||||||
filter=${this._macAddress || ""}
|
filter=${this._macAddress || ""}
|
||||||
></hass-tabs-subpage-data-table>
|
></hass-tabs-subpage-data-table>
|
||||||
`;
|
`;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement } 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 { storage } from "../../../../../common/decorators/storage";
|
||||||
import "../../../../../components/ha-card";
|
import "../../../../../components/ha-card";
|
||||||
import "../../../../../components/ha-code-editor";
|
import "../../../../../components/ha-code-editor";
|
||||||
@ -23,6 +23,7 @@ export class MQTTConfigPanel extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-mqtt-topic-ls",
|
key: "panel-dev-mqtt-topic-ls",
|
||||||
state: true,
|
state: true,
|
||||||
@ -30,6 +31,7 @@ export class MQTTConfigPanel extends LitElement {
|
|||||||
})
|
})
|
||||||
private _topic = "";
|
private _topic = "";
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-mqtt-payload-ls",
|
key: "panel-dev-mqtt-payload-ls",
|
||||||
state: true,
|
state: true,
|
||||||
@ -37,6 +39,7 @@ export class MQTTConfigPanel extends LitElement {
|
|||||||
})
|
})
|
||||||
private _payload = "";
|
private _payload = "";
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-mqtt-qos-ls",
|
key: "panel-dev-mqtt-qos-ls",
|
||||||
state: true,
|
state: true,
|
||||||
@ -44,6 +47,7 @@ export class MQTTConfigPanel extends LitElement {
|
|||||||
})
|
})
|
||||||
private _qos = "0";
|
private _qos = "0";
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-mqtt-retain-ls",
|
key: "panel-dev-mqtt-retain-ls",
|
||||||
state: true,
|
state: true,
|
||||||
@ -51,6 +55,7 @@ export class MQTTConfigPanel extends LitElement {
|
|||||||
})
|
})
|
||||||
private _retain = false;
|
private _retain = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-mqtt-allow-template-ls",
|
key: "panel-dev-mqtt-allow-template-ls",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -21,6 +21,7 @@ const qosLevel = ["0", "1", "2"];
|
|||||||
class MqttSubscribeCard extends LitElement {
|
class MqttSubscribeCard extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-mqtt-topic-subscribe",
|
key: "panel-dev-mqtt-topic-subscribe",
|
||||||
state: true,
|
state: true,
|
||||||
@ -28,6 +29,7 @@ class MqttSubscribeCard extends LitElement {
|
|||||||
})
|
})
|
||||||
private _topic = "";
|
private _topic = "";
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-mqtt-qos-subscribe",
|
key: "panel-dev-mqtt-qos-subscribe",
|
||||||
state: true,
|
state: true,
|
||||||
@ -35,6 +37,7 @@ class MqttSubscribeCard extends LitElement {
|
|||||||
})
|
})
|
||||||
private _qos = "0";
|
private _qos = "0";
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-mqtt-json-format",
|
key: "panel-dev-mqtt-json-format",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -105,6 +105,9 @@ export class SSDPConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
@grouping-changed=${this._handleGroupingChanged}
|
@grouping-changed=${this._handleGroupingChanged}
|
||||||
@collapsed-changed=${this._handleCollapseChanged}
|
@collapsed-changed=${this._handleCollapseChanged}
|
||||||
.data=${this._dataWithIds(this._data)}
|
.data=${this._dataWithIds(this._data)}
|
||||||
|
.noDataText=${this.hass.localize(
|
||||||
|
"ui.panel.config.ssdp.no_devices_found"
|
||||||
|
)}
|
||||||
@row-click=${this._handleRowClicked}
|
@row-click=${this._handleRowClicked}
|
||||||
clickable
|
clickable
|
||||||
></hass-tabs-subpage-data-table>
|
></hass-tabs-subpage-data-table>
|
||||||
|
@ -112,6 +112,9 @@ export class ZeroconfConfigPanel extends SubscribeMixin(LitElement) {
|
|||||||
@grouping-changed=${this._handleGroupingChanged}
|
@grouping-changed=${this._handleGroupingChanged}
|
||||||
@collapsed-changed=${this._handleCollapseChanged}
|
@collapsed-changed=${this._handleCollapseChanged}
|
||||||
.data=${this._dataWithIds(this._data)}
|
.data=${this._dataWithIds(this._data)}
|
||||||
|
.noDataText=${this.hass.localize(
|
||||||
|
"ui.panel.config.zeroconf.no_devices_found"
|
||||||
|
)}
|
||||||
@row-click=${this._handleRowClicked}
|
@row-click=${this._handleRowClicked}
|
||||||
clickable
|
clickable
|
||||||
></hass-tabs-subpage-data-table>
|
></hass-tabs-subpage-data-table>
|
||||||
|
@ -55,6 +55,7 @@ export class HaConfigLabels extends LitElement {
|
|||||||
|
|
||||||
@state() private _labels: LabelRegistryEntry[] = [];
|
@state() private _labels: LabelRegistryEntry[] = [];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "labels-table-search",
|
key: "labels-table-search",
|
||||||
|
@ -74,6 +74,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
|
|
||||||
@state() private _dashboards: LovelaceDashboard[] = [];
|
@state() private _dashboards: LovelaceDashboard[] = [];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "lovelace-dashboards-table-search",
|
key: "lovelace-dashboards-table-search",
|
||||||
@ -161,7 +162,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
placement="right"
|
placement="right"
|
||||||
>
|
>
|
||||||
<ha-svg-icon
|
<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}
|
.path=${mdiCheckCircleOutline}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</ha-tooltip>
|
</ha-tooltip>
|
||||||
|
@ -46,6 +46,7 @@ export class HaConfigLovelaceRescources extends LitElement {
|
|||||||
|
|
||||||
@state() private _resources: LovelaceResource[] = [];
|
@state() private _resources: LovelaceResource[] = [];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "lovelace-resources-table-search",
|
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-dhcp")
|
|
||||||
class ConfigNetworkDHCP extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
return html`
|
|
||||||
<ha-card
|
|
||||||
outlined
|
|
||||||
header=${this.hass.localize("ui.panel.config.network.discovery.dhcp")}
|
|
||||||
>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
${this.hass.localize("ui.panel.config.network.discovery.dhcp_info")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<a
|
|
||||||
href="/config/dhcp"
|
|
||||||
aria-label=${this.hass.localize(
|
|
||||||
"ui.panel.config.network.discovery.dhcp_browser"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ha-button>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.network.discovery.dhcp_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-dhcp": ConfigNetworkDHCP;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,15 +3,18 @@ import { css, html, LitElement } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import "../../../layouts/hass-subpage";
|
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 type { HomeAssistant, Route } from "../../../types";
|
||||||
import "./ha-config-network";
|
import "./ha-config-network";
|
||||||
import "./ha-config-network-dhcp";
|
|
||||||
import "./ha-config-network-ssdp";
|
|
||||||
import "./ha-config-network-zeroconf";
|
|
||||||
import "./ha-config-url-form";
|
import "./ha-config-url-form";
|
||||||
import "./supervisor-hostname";
|
import "./supervisor-hostname";
|
||||||
import "./supervisor-network";
|
import "./supervisor-network";
|
||||||
|
|
||||||
|
const NETWORK_BROWSERS = ["dhcp", "ssdp", "zeroconf"] as const;
|
||||||
|
|
||||||
@customElement("ha-config-section-network")
|
@customElement("ha-config-section-network")
|
||||||
class HaConfigSectionNetwork extends LitElement {
|
class HaConfigSectionNetwork extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -38,20 +41,38 @@ class HaConfigSectionNetwork extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
<ha-config-url-form .hass=${this.hass}></ha-config-url-form>
|
<ha-config-url-form .hass=${this.hass}></ha-config-url-form>
|
||||||
<ha-config-network .hass=${this.hass}></ha-config-network>
|
<ha-config-network .hass=${this.hass}></ha-config-network>
|
||||||
${isComponentLoaded(this.hass, "dhcp")
|
${NETWORK_BROWSERS.some((component) =>
|
||||||
? html`<ha-config-network-dhcp
|
isComponentLoaded(this.hass, component)
|
||||||
.hass=${this.hass}
|
)
|
||||||
></ha-config-network-dhcp>`
|
? html`
|
||||||
: ""}
|
<ha-card
|
||||||
${isComponentLoaded(this.hass, "ssdp")
|
outlined
|
||||||
? html`<ha-config-network-ssdp
|
class="discovery-card"
|
||||||
.hass=${this.hass}
|
header=${this.hass.localize(
|
||||||
></ha-config-network-ssdp>`
|
"ui.panel.config.network.discovery.title"
|
||||||
: ""}
|
)}
|
||||||
${isComponentLoaded(this.hass, "zeroconf")
|
>
|
||||||
? html`<ha-config-network-zeroconf
|
<ha-md-list>
|
||||||
.hass=${this.hass}
|
${NETWORK_BROWSERS.map(
|
||||||
></ha-config-network-zeroconf>`
|
(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>
|
</div>
|
||||||
</hass-subpage>
|
</hass-subpage>
|
||||||
@ -68,14 +89,15 @@ class HaConfigSectionNetwork extends LitElement {
|
|||||||
supervisor-network,
|
supervisor-network,
|
||||||
ha-config-url-form,
|
ha-config-url-form,
|
||||||
ha-config-network,
|
ha-config-network,
|
||||||
ha-config-network-dhcp,
|
.discovery-card {
|
||||||
ha-config-network-ssdp,
|
|
||||||
ha-config-network-zeroconf {
|
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
.discovery-card ha-md-list {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +133,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _filteredScenes?: string[] | null;
|
@state() private _filteredScenes?: string[] | null;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "scene-table-search",
|
key: "scene-table-search",
|
||||||
@ -141,6 +142,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _filter = "";
|
private _filter = "";
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "scene-table-filters-full",
|
key: "scene-table-filters-full",
|
||||||
|
@ -105,6 +105,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
|||||||
|
|
||||||
@state() private _readOnly = false;
|
@state() private _readOnly = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||||
@transform<EntityRegistryEntry[], EntityRegistryEntry>({
|
@transform<EntityRegistryEntry[], EntityRegistryEntry>({
|
||||||
transformer: function (this: HaScriptEditor, value) {
|
transformer: function (this: HaScriptEditor, value) {
|
||||||
|
@ -138,6 +138,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _filteredScripts?: string[] | null;
|
@state() private _filteredScripts?: string[] | null;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "script-table-search",
|
key: "script-table-search",
|
||||||
@ -146,6 +147,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _filter = "";
|
private _filter = "";
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "script-table-filters-full",
|
key: "script-table-filters-full",
|
||||||
|
@ -62,6 +62,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
|||||||
return this.hass.auth.external?.config.canWriteTag;
|
return this.hass.auth.external?.config.canWriteTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "tags-table-search",
|
key: "tags-table-search",
|
||||||
|
@ -76,6 +76,7 @@ export class VoiceAssistantsExpose extends LitElement {
|
|||||||
|
|
||||||
@state() private _extEntities?: Record<string, ExtEntityRegistryEntry>;
|
@state() private _extEntities?: Record<string, ExtEntityRegistryEntry>;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "voice-expose-table-search",
|
key: "voice-expose-table-search",
|
||||||
|
@ -52,6 +52,7 @@ class HaPanelDevAction extends LitElement {
|
|||||||
|
|
||||||
private _yamlValid = true;
|
private _yamlValid = true;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-action-state-service-data",
|
key: "panel-dev-action-state-service-data",
|
||||||
state: true,
|
state: true,
|
||||||
@ -59,6 +60,7 @@ class HaPanelDevAction extends LitElement {
|
|||||||
})
|
})
|
||||||
private _serviceData?: ServiceAction = { action: "", target: {}, data: {} };
|
private _serviceData?: ServiceAction = { action: "", target: {}, data: {} };
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-action-state-yaml-mode",
|
key: "panel-dev-action-state-yaml-mode",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -33,6 +33,7 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() supportedLanguages?: string[];
|
@state() supportedLanguages?: string[];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "assist_debug_language",
|
key: "assist_debug_language",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -62,6 +62,7 @@ class HaPanelDevState extends LitElement {
|
|||||||
|
|
||||||
@state() private _validJSON = true;
|
@state() private _validJSON = true;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "devToolsShowAttributes",
|
key: "devToolsShowAttributes",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -22,11 +22,14 @@ import type {
|
|||||||
DeviceConsumptionEnergyPreference,
|
DeviceConsumptionEnergyPreference,
|
||||||
} from "../../data/energy";
|
} from "../../data/energy";
|
||||||
import {
|
import {
|
||||||
|
computeConsumptionData,
|
||||||
getEnergyDataCollection,
|
getEnergyDataCollection,
|
||||||
getEnergyGasUnit,
|
getEnergyGasUnit,
|
||||||
getEnergyWaterUnit,
|
getEnergyWaterUnit,
|
||||||
|
getSummedData,
|
||||||
} from "../../data/energy";
|
} from "../../data/energy";
|
||||||
import { fileDownload } from "../../util/file_download";
|
import { fileDownload } from "../../util/file_download";
|
||||||
|
import type { StatisticValue } from "../../data/recorder";
|
||||||
|
|
||||||
const ENERGY_LOVELACE_CONFIG: LovelaceConfig = {
|
const ENERGY_LOVELACE_CONFIG: LovelaceConfig = {
|
||||||
views: [
|
views: [
|
||||||
@ -177,18 +180,20 @@ class PanelEnergy extends LitElement {
|
|||||||
const csv: string[] = [];
|
const csv: string[] = [];
|
||||||
csv[0] = headers;
|
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;
|
let n = 0;
|
||||||
const row: string[] = [];
|
const row: string[] = [];
|
||||||
if (!stats[stat]) {
|
row.push(id);
|
||||||
return;
|
|
||||||
}
|
|
||||||
row.push(stat);
|
|
||||||
row.push(type);
|
row.push(type);
|
||||||
row.push(unit.normalize("NFKD"));
|
row.push(unit.normalize("NFKD"));
|
||||||
times.forEach((t) => {
|
times.forEach((t) => {
|
||||||
if (n < stats[stat].length && stats[stat][n].start === t) {
|
if (n < data.length && data[n].start === t) {
|
||||||
row.push((stats[stat][n].change ?? "").toString());
|
row.push((data[n].change ?? "").toString());
|
||||||
n++;
|
n++;
|
||||||
} else {
|
} else {
|
||||||
row.push("");
|
row.push("");
|
||||||
@ -197,6 +202,14 @@ class PanelEnergy extends LitElement {
|
|||||||
csv.push(row.join(",") + "\n");
|
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 currency = this.hass.config.currency;
|
||||||
|
|
||||||
const printCategory = function (
|
const printCategory = function (
|
||||||
@ -335,6 +348,99 @@ class PanelEnergy extends LitElement {
|
|||||||
|
|
||||||
printCategory("device_consumption", devices, electricUnit);
|
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, {
|
const blob = new Blob(csv, {
|
||||||
type: "text/csv",
|
type: "text/csv",
|
||||||
});
|
});
|
||||||
|
@ -63,6 +63,7 @@ class HaPanelHistory extends LitElement {
|
|||||||
|
|
||||||
@state() private _endDate: Date;
|
@state() private _endDate: Date;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "historyPickedValue",
|
key: "historyPickedValue",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -39,6 +39,7 @@ export class HaPanelLogbook extends LitElement {
|
|||||||
@state()
|
@state()
|
||||||
private _showBack?: boolean;
|
private _showBack?: boolean;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "logbookPickedValue",
|
key: "logbookPickedValue",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -62,6 +62,7 @@ export class HuiEnergyDevicesDetailGraphCard
|
|||||||
|
|
||||||
@state() private _compareEnd?: Date;
|
@state() private _compareEnd?: Date;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "energy-devices-hidden-stats",
|
key: "energy-devices-hidden-stats",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -86,6 +86,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
@state() private _config?: ButtonCardConfig;
|
@state() private _config?: ButtonCardConfig;
|
||||||
|
|
||||||
|
@state()
|
||||||
@consume<any>({ context: statesContext, subscribe: true })
|
@consume<any>({ context: statesContext, subscribe: true })
|
||||||
@transform({
|
@transform({
|
||||||
transformer: function (this: HuiButtonCard, value: HassEntities) {
|
transformer: function (this: HuiButtonCard, value: HassEntities) {
|
||||||
@ -111,6 +112,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
|||||||
@consume({ context: configContext, subscribe: true })
|
@consume({ context: configContext, subscribe: true })
|
||||||
_hassConfig!: HassConfig;
|
_hassConfig!: HassConfig;
|
||||||
|
|
||||||
|
@state()
|
||||||
@consume<any>({ context: entitiesContext, subscribe: true })
|
@consume<any>({ context: entitiesContext, subscribe: true })
|
||||||
@transform<HomeAssistant["entities"], EntityRegistryDisplayEntry>({
|
@transform<HomeAssistant["entities"], EntityRegistryDisplayEntry>({
|
||||||
transformer: function (this: HuiButtonCard, value) {
|
transformer: function (this: HuiButtonCard, value) {
|
||||||
|
@ -163,6 +163,7 @@ export class HuiGenericEntityRow extends LitElement {
|
|||||||
@touchend=${stopPropagation}
|
@touchend=${stopPropagation}
|
||||||
@keydown=${stopPropagation}
|
@keydown=${stopPropagation}
|
||||||
@click=${stopPropagation}
|
@click=${stopPropagation}
|
||||||
|
@action=${stopPropagation}
|
||||||
></slot>`}
|
></slot>`}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -43,6 +43,7 @@ export class HuiBadgePicker extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public suggestedBadges?: string[];
|
@property({ attribute: false }) public suggestedBadges?: string[];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "dashboardBadgeClipboard",
|
key: "dashboardBadgeClipboard",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -4,11 +4,7 @@ import { css, html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { cache } from "lit/directives/cache";
|
import { cache } from "lit/directives/cache";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import memoize from "memoize-one";
|
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
|
||||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
|
||||||
import type { DataTableRowData } from "../../../../components/data-table/ha-data-table";
|
|
||||||
import "../../../../components/ha-dialog";
|
import "../../../../components/ha-dialog";
|
||||||
import "../../../../components/ha-dialog-header";
|
import "../../../../components/ha-dialog-header";
|
||||||
import "../../../../components/sl-tab-group";
|
import "../../../../components/sl-tab-group";
|
||||||
@ -137,7 +133,6 @@ export class HuiCreateDialogBadge
|
|||||||
no-label-float
|
no-label-float
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${true}
|
.narrow=${true}
|
||||||
.entities=${this._allEntities(this.hass.states)}
|
|
||||||
@selected-changed=${this._handleSelectedChanged}
|
@selected-changed=${this._handleSelectedChanged}
|
||||||
></hui-entity-picker-table>
|
></hui-entity-picker-table>
|
||||||
`
|
`
|
||||||
@ -276,20 +271,6 @@ export class HuiCreateDialogBadge
|
|||||||
|
|
||||||
this.closeDialog();
|
this.closeDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _allEntities = memoize((entities) =>
|
|
||||||
Object.keys(entities).map((entity) => {
|
|
||||||
const stateObj = this.hass.states[entity];
|
|
||||||
return {
|
|
||||||
icon: "",
|
|
||||||
entity_id: entity,
|
|
||||||
stateObj,
|
|
||||||
name: computeStateName(stateObj),
|
|
||||||
domain: computeDomain(entity),
|
|
||||||
last_changed: stateObj!.last_changed,
|
|
||||||
} as DataTableRowData;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -42,6 +42,7 @@ export class HuiCardPicker extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public suggestedCards?: string[];
|
@property({ attribute: false }) public suggestedCards?: string[];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "dashboardCardClipboard",
|
key: "dashboardCardClipboard",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -5,11 +5,7 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import { cache } from "lit/directives/cache";
|
import { cache } from "lit/directives/cache";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import memoize from "memoize-one";
|
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
|
||||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
|
||||||
import type { DataTableRowData } from "../../../../components/data-table/ha-data-table";
|
|
||||||
import "../../../../components/ha-dialog";
|
import "../../../../components/ha-dialog";
|
||||||
import "../../../../components/ha-dialog-header";
|
import "../../../../components/ha-dialog-header";
|
||||||
import "../../../../components/sl-tab-group";
|
import "../../../../components/sl-tab-group";
|
||||||
@ -157,7 +153,6 @@ export class HuiCreateDialogCard
|
|||||||
no-label-float
|
no-label-float
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
narrow
|
narrow
|
||||||
.entities=${this._allEntities(this.hass.states)}
|
|
||||||
@selected-changed=${this._handleSelectedChanged}
|
@selected-changed=${this._handleSelectedChanged}
|
||||||
></hui-entity-picker-table>
|
></hui-entity-picker-table>
|
||||||
`
|
`
|
||||||
@ -340,20 +335,6 @@ export class HuiCreateDialogCard
|
|||||||
|
|
||||||
this.closeDialog();
|
this.closeDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _allEntities = memoize((entities) =>
|
|
||||||
Object.keys(entities).map((entity) => {
|
|
||||||
const stateObj = this.hass.states[entity];
|
|
||||||
return {
|
|
||||||
icon: "",
|
|
||||||
entity_id: entity,
|
|
||||||
stateObj,
|
|
||||||
name: computeStateName(stateObj),
|
|
||||||
domain: computeDomain(entity),
|
|
||||||
last_changed: stateObj!.last_changed,
|
|
||||||
} as DataTableRowData;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
import type { TemplateResult } from "lit";
|
import type { PropertyValues, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { computeAreaName } from "../../../../common/entity/compute_area_name";
|
||||||
|
import { computeDeviceName } from "../../../../common/entity/compute_device_name";
|
||||||
|
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||||
|
import { computeEntityName } from "../../../../common/entity/compute_entity_name";
|
||||||
|
import { getEntityContext } from "../../../../common/entity/context/get_entity_context";
|
||||||
|
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||||
|
import { computeRTL } from "../../../../common/util/compute_rtl";
|
||||||
import "../../../../components/data-table/ha-data-table";
|
import "../../../../components/data-table/ha-data-table";
|
||||||
import type {
|
import type {
|
||||||
DataTableColumnContainer,
|
DataTableColumnContainer,
|
||||||
@ -12,8 +20,26 @@ import type {
|
|||||||
} from "../../../../components/data-table/ha-data-table";
|
} from "../../../../components/data-table/ha-data-table";
|
||||||
import "../../../../components/entity/state-badge";
|
import "../../../../components/entity/state-badge";
|
||||||
import "../../../../components/ha-relative-time";
|
import "../../../../components/ha-relative-time";
|
||||||
|
import { domainToName } from "../../../../data/integration";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
|
const ENTITY_ID_STYLE = styleMap({
|
||||||
|
fontFamily: "var(--ha-font-family-code)",
|
||||||
|
fontSize: "var(--ha-font-size-xs)",
|
||||||
|
});
|
||||||
|
|
||||||
|
interface EntityPickerTableRowData extends DataTableRowData {
|
||||||
|
icon: string;
|
||||||
|
entity_id: string;
|
||||||
|
stateObj: any;
|
||||||
|
name: string;
|
||||||
|
entity_name?: string;
|
||||||
|
device_name?: string;
|
||||||
|
area_name?: string;
|
||||||
|
domain_name: string;
|
||||||
|
last_changed: string;
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("hui-entity-picker-table")
|
@customElement("hui-entity-picker-table")
|
||||||
export class HuiEntityPickerTable extends LitElement {
|
export class HuiEntityPickerTable extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -23,16 +49,69 @@ export class HuiEntityPickerTable extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "no-label-float" })
|
@property({ type: Boolean, attribute: "no-label-float" })
|
||||||
public noLabelFloat? = false;
|
public noLabelFloat? = false;
|
||||||
|
|
||||||
@property({ type: Array }) public entities!: DataTableRowData[];
|
@property({ type: Array }) public entities?: string[];
|
||||||
|
|
||||||
|
protected firstUpdated(_changedProperties: PropertyValues): void {
|
||||||
|
super.firstUpdated(_changedProperties);
|
||||||
|
this.hass.loadBackendTranslation("title");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _data = memoizeOne(
|
||||||
|
(
|
||||||
|
states: HomeAssistant["states"],
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
entities?: string[]
|
||||||
|
): EntityPickerTableRowData[] =>
|
||||||
|
(entities || Object.keys(states)).map<EntityPickerTableRowData>(
|
||||||
|
(entity) => {
|
||||||
|
const stateObj = this.hass.states[entity];
|
||||||
|
|
||||||
|
const { area, device } = getEntityContext(stateObj, this.hass);
|
||||||
|
|
||||||
|
const entityName = computeEntityName(stateObj, this.hass);
|
||||||
|
const deviceName = device ? computeDeviceName(device) : undefined;
|
||||||
|
const areaName = area ? computeAreaName(area) : undefined;
|
||||||
|
const name = [deviceName, entityName].filter(Boolean).join(" ");
|
||||||
|
const domain = computeDomain(entity);
|
||||||
|
|
||||||
|
return {
|
||||||
|
icon: "",
|
||||||
|
entity_id: entity,
|
||||||
|
stateObj,
|
||||||
|
name: name,
|
||||||
|
entity_name: entityName,
|
||||||
|
device_name: deviceName,
|
||||||
|
area_name: areaName,
|
||||||
|
domain_name: domainToName(localize, domain),
|
||||||
|
last_changed: stateObj!.last_changed,
|
||||||
|
} satisfies EntityPickerTableRowData;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
const data = this._data(
|
||||||
|
this.hass.states,
|
||||||
|
this.hass.localize,
|
||||||
|
this.entities
|
||||||
|
);
|
||||||
|
|
||||||
|
const showEntityId = Boolean(this.hass.userData?.showEntityIdPicker);
|
||||||
|
|
||||||
|
const columns = this._columns(
|
||||||
|
this.narrow,
|
||||||
|
computeRTL(this.hass),
|
||||||
|
showEntityId
|
||||||
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-data-table
|
<ha-data-table
|
||||||
|
class=${showEntityId ? "show-entity-id" : ""}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
selectable
|
selectable
|
||||||
.id=${"entity_id"}
|
.id=${"entity_id"}
|
||||||
.columns=${this._columns(this.narrow!)}
|
.columns=${columns}
|
||||||
.data=${this.entities}
|
.data=${data}
|
||||||
.searchLabel=${this.hass.localize(
|
.searchLabel=${this.hass.localize(
|
||||||
"ui.panel.lovelace.unused_entities.search"
|
"ui.panel.lovelace.unused_entities.search"
|
||||||
)}
|
)}
|
||||||
@ -45,7 +124,8 @@ export class HuiEntityPickerTable extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _columns = memoizeOne((narrow: boolean) => {
|
private _columns = memoizeOne(
|
||||||
|
(narrow: boolean, isRTL: boolean, showEntityId: boolean) => {
|
||||||
const columns: DataTableColumnContainer = {
|
const columns: DataTableColumnContainer = {
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
@ -62,35 +142,74 @@ export class HuiEntityPickerTable extends LitElement {
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity"),
|
title: this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.unused_entities.entity"
|
||||||
|
),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
flex: 2,
|
flex: 2,
|
||||||
main: true,
|
main: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
template: (entity: any) => html`
|
template: (entity: any) => {
|
||||||
|
const primary =
|
||||||
|
entity.entity_name || entity.device_name || entity.entity_id;
|
||||||
|
const secondary = [
|
||||||
|
entity.area_name,
|
||||||
|
entity.entity_name ? entity.device_name : undefined,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(isRTL ? " ◂ " : " ▸ ");
|
||||||
|
return html`
|
||||||
<div @click=${this._handleEntityClicked} style="cursor: pointer;">
|
<div @click=${this._handleEntityClicked} style="cursor: pointer;">
|
||||||
${entity.name}
|
${primary}
|
||||||
${narrow
|
${secondary
|
||||||
? html` <div class="secondary">${entity.entity_id}</div> `
|
? html`<div class="secondary">${secondary}</div>`
|
||||||
: ""}
|
: nothing}
|
||||||
|
${narrow && showEntityId
|
||||||
|
? html`
|
||||||
|
<div class="secondary" style=${ENTITY_ID_STYLE}>
|
||||||
|
${entity.entity_id}
|
||||||
</div>
|
</div>
|
||||||
`,
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
columns.entity_id = {
|
columns.entity_name = {
|
||||||
title: this.hass!.localize("ui.panel.lovelace.unused_entities.entity_id"),
|
title: "entity_name",
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
filterable: true,
|
||||||
hidden: narrow,
|
hidden: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
columns.domain = {
|
columns.device_name = {
|
||||||
|
title: "device_name",
|
||||||
|
filterable: true,
|
||||||
|
hidden: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
columns.area_name = {
|
||||||
|
title: "area_name",
|
||||||
|
filterable: true,
|
||||||
|
hidden: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
columns.entity_id = {
|
||||||
|
title: this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.unused_entities.entity_id"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
hidden: narrow || !showEntityId,
|
||||||
|
};
|
||||||
|
|
||||||
|
columns.domain_name = {
|
||||||
title: this.hass!.localize("ui.panel.lovelace.unused_entities.domain"),
|
title: this.hass!.localize("ui.panel.lovelace.unused_entities.domain"),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
hidden: narrow,
|
hidden: narrow || showEntityId,
|
||||||
};
|
};
|
||||||
|
|
||||||
columns.last_changed = {
|
columns.last_changed = {
|
||||||
@ -110,7 +229,8 @@ export class HuiEntityPickerTable extends LitElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return columns;
|
return columns;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private _handleSelectionChanged(
|
private _handleSelectionChanged(
|
||||||
ev: HASSDomEvent<SelectionChangedEvent>
|
ev: HASSDomEvent<SelectionChangedEvent>
|
||||||
@ -134,6 +254,9 @@ export class HuiEntityPickerTable extends LitElement {
|
|||||||
--data-table-border-width: 0;
|
--data-table-border-width: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
ha-data-table.show-entity-id {
|
||||||
|
--data-table-row-height: 64px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +210,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
|||||||
|
|
||||||
.entity ha-entity-picker {
|
.entity ha-entity-picker {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.special-row {
|
.special-row {
|
||||||
|
@ -3,22 +3,19 @@ import type { PropertyValues } from "lit";
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
|
||||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
|
||||||
import type { DataTableRowData } from "../../../../components/data-table/ha-data-table";
|
|
||||||
import "../../../../components/ha-fab";
|
import "../../../../components/ha-fab";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
|
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { computeUnusedEntities } from "../../common/compute-unused-entities";
|
import { computeUnusedEntities } from "../../common/compute-unused-entities";
|
||||||
import type { Lovelace } from "../../types";
|
|
||||||
import "../card-editor/hui-entity-picker-table";
|
|
||||||
import { showSuggestCardDialog } from "../card-editor/show-suggest-card-dialog";
|
|
||||||
import { showSelectViewDialog } from "../select-view/show-select-view-dialog";
|
|
||||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
|
||||||
import {
|
import {
|
||||||
computeCards,
|
computeCards,
|
||||||
computeSection,
|
computeSection,
|
||||||
} from "../../common/generate-lovelace-config";
|
} from "../../common/generate-lovelace-config";
|
||||||
|
import type { Lovelace } from "../../types";
|
||||||
|
import "../card-editor/hui-entity-picker-table";
|
||||||
|
import { showSuggestCardDialog } from "../card-editor/show-suggest-card-dialog";
|
||||||
|
import { showSelectViewDialog } from "../select-view/show-select-view-dialog";
|
||||||
|
|
||||||
@customElement("hui-unused-entities")
|
@customElement("hui-unused-entities")
|
||||||
export class HuiUnusedEntities extends LitElement {
|
export class HuiUnusedEntities extends LitElement {
|
||||||
@ -80,17 +77,7 @@ export class HuiUnusedEntities extends LitElement {
|
|||||||
<hui-entity-picker-table
|
<hui-entity-picker-table
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.entities=${this._unusedEntities.map((entity) => {
|
.entities=${this._unusedEntities}
|
||||||
const stateObj = this.hass!.states[entity];
|
|
||||||
return {
|
|
||||||
icon: "",
|
|
||||||
entity_id: entity,
|
|
||||||
stateObj,
|
|
||||||
name: stateObj ? computeStateName(stateObj) : "Unavailable",
|
|
||||||
domain: computeDomain(entity),
|
|
||||||
last_changed: stateObj?.last_changed,
|
|
||||||
};
|
|
||||||
}) as DataTableRowData[]}
|
|
||||||
@selected-changed=${this._handleSelectedChanged}
|
@selected-changed=${this._handleSelectedChanged}
|
||||||
></hui-entity-picker-table>
|
></hui-entity-picker-table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -300,6 +300,12 @@ class HUIRoot extends LitElement {
|
|||||||
|
|
||||||
const background = curViewConfig?.background || this.config.background;
|
const background = curViewConfig?.background || this.config.background;
|
||||||
|
|
||||||
|
const _isTabHiddenForUser = (view: LovelaceViewConfig) =>
|
||||||
|
view.visible !== undefined &&
|
||||||
|
((Array.isArray(view.visible) &&
|
||||||
|
!view.visible.some((e) => e.user === this.hass!.user?.id)) ||
|
||||||
|
view.visible === false);
|
||||||
|
|
||||||
const tabs = html`<sl-tab-group @sl-tab-show=${this._handleViewSelected}>
|
const tabs = html`<sl-tab-group @sl-tab-show=${this._handleViewSelected}>
|
||||||
${views.map(
|
${views.map(
|
||||||
(view, index) => html`
|
(view, index) => html`
|
||||||
@ -311,13 +317,7 @@ class HUIRoot extends LitElement {
|
|||||||
class=${classMap({
|
class=${classMap({
|
||||||
icon: Boolean(view.icon),
|
icon: Boolean(view.icon),
|
||||||
"hide-tab": Boolean(
|
"hide-tab": Boolean(
|
||||||
!this._editMode &&
|
!this._editMode && (view.subview || _isTabHiddenForUser(view))
|
||||||
view.visible !== undefined &&
|
|
||||||
((Array.isArray(view.visible) &&
|
|
||||||
!view.visible.some(
|
|
||||||
(e) => e.user === this.hass!.user?.id
|
|
||||||
)) ||
|
|
||||||
view.visible === false)
|
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
@ -64,6 +64,7 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
|
|
||||||
@state() _currentItem?: MediaPlayerItem;
|
@state() _currentItem?: MediaPlayerItem;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "mediaBrowserPreferredLayout",
|
key: "mediaBrowserPreferredLayout",
|
||||||
state: true,
|
state: true,
|
||||||
@ -78,6 +79,7 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "mediaBrowseEntityId",
|
key: "mediaBrowseEntityId",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -16,6 +16,11 @@ import "../../layouts/hass-error-screen";
|
|||||||
import type { HomeAssistant, Route } from "../../types";
|
import type { HomeAssistant, Route } from "../../types";
|
||||||
import { documentationUrl } from "../../util/documentation-url";
|
import { documentationUrl } from "../../util/documentation-url";
|
||||||
|
|
||||||
|
// When a user presses "m", the user is redirected to the first redirect
|
||||||
|
// for which holds true currentPath.startsWith(redirect.redirect)
|
||||||
|
// That's why redirects should be sorted with more specific ones first
|
||||||
|
// Or else pressing "M" will link to the higher level page.
|
||||||
|
|
||||||
export const getMyRedirects = (): Redirects => ({
|
export const getMyRedirects = (): Redirects => ({
|
||||||
application_credentials: {
|
application_credentials: {
|
||||||
redirect: "/config/application_credentials",
|
redirect: "/config/application_credentials",
|
||||||
@ -73,15 +78,15 @@ export const getMyRedirects = (): Redirects => ({
|
|||||||
brand: "string",
|
brand: "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
integrations: {
|
|
||||||
redirect: "/config/integrations",
|
|
||||||
},
|
|
||||||
integration: {
|
integration: {
|
||||||
redirect: "/config/integrations/integration",
|
redirect: "/config/integrations/integration",
|
||||||
params: {
|
params: {
|
||||||
domain: "string",
|
domain: "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
integrations: {
|
||||||
|
redirect: "/config/integrations",
|
||||||
|
},
|
||||||
config_mqtt: {
|
config_mqtt: {
|
||||||
component: "mqtt",
|
component: "mqtt",
|
||||||
redirect: "/config/mqtt",
|
redirect: "/config/mqtt",
|
||||||
@ -106,6 +111,14 @@ export const getMyRedirects = (): Redirects => ({
|
|||||||
component: "matter",
|
component: "matter",
|
||||||
redirect: "/config/matter/add",
|
redirect: "/config/matter/add",
|
||||||
},
|
},
|
||||||
|
bluetooth_advertisement_monitor: {
|
||||||
|
component: "bluetooth",
|
||||||
|
redirect: "/config/bluetooth/advertisement-monitor",
|
||||||
|
},
|
||||||
|
bluetooth_connection_monitor: {
|
||||||
|
component: "bluetooth",
|
||||||
|
redirect: "/config/bluetooth/connection-monitor",
|
||||||
|
},
|
||||||
config_bluetooth: {
|
config_bluetooth: {
|
||||||
component: "bluetooth",
|
component: "bluetooth",
|
||||||
redirect: "/config/bluetooth",
|
redirect: "/config/bluetooth",
|
||||||
@ -244,6 +257,9 @@ export const getMyRedirects = (): Redirects => ({
|
|||||||
// customize was removed in 2021.12, fallback to dashboard
|
// customize was removed in 2021.12, fallback to dashboard
|
||||||
redirect: "/config/dashboard",
|
redirect: "/config/dashboard",
|
||||||
},
|
},
|
||||||
|
profile_security: {
|
||||||
|
redirect: "/profile/security",
|
||||||
|
},
|
||||||
profile: {
|
profile: {
|
||||||
redirect: "/profile",
|
redirect: "/profile",
|
||||||
},
|
},
|
||||||
@ -259,10 +275,6 @@ export const getMyRedirects = (): Redirects => ({
|
|||||||
component: "media_source",
|
component: "media_source",
|
||||||
redirect: "/media-browser",
|
redirect: "/media-browser",
|
||||||
},
|
},
|
||||||
backup: {
|
|
||||||
component: "backup",
|
|
||||||
redirect: "/config/backup",
|
|
||||||
},
|
|
||||||
backup_list: {
|
backup_list: {
|
||||||
component: "backup",
|
component: "backup",
|
||||||
redirect: "/config/backup/backups",
|
redirect: "/config/backup/backups",
|
||||||
@ -271,6 +283,10 @@ export const getMyRedirects = (): Redirects => ({
|
|||||||
component: "backup",
|
component: "backup",
|
||||||
redirect: "/config/backup/settings",
|
redirect: "/config/backup/settings",
|
||||||
},
|
},
|
||||||
|
backup: {
|
||||||
|
component: "backup",
|
||||||
|
redirect: "/config/backup",
|
||||||
|
},
|
||||||
supervisor_snapshots: {
|
supervisor_snapshots: {
|
||||||
component: "backup",
|
component: "backup",
|
||||||
redirect: "/config/backup",
|
redirect: "/config/backup",
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||||
import { storage } from "../../common/decorators/storage";
|
import { storage } from "../../common/decorators/storage";
|
||||||
@ -55,6 +55,7 @@ class PanelTodo extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public mobile = false;
|
@property({ type: Boolean, reflect: true }) public mobile = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "selectedTodoEntity",
|
key: "selectedTodoEntity",
|
||||||
state: true,
|
state: true,
|
||||||
|
@ -15,6 +15,8 @@ import type { HassElement } from "./hass-element";
|
|||||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||||
import { showVoiceCommandDialog } from "../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
|
import { showVoiceCommandDialog } from "../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
|
||||||
import { canOverrideAlphanumericInput } from "../common/dom/can-override-input";
|
import { canOverrideAlphanumericInput } from "../common/dom/can-override-input";
|
||||||
|
import { showShortcutsDialog } from "../dialogs/shortcuts/show-shortcuts-dialog";
|
||||||
|
import type { Redirects } from "../panels/my/ha-panel-my";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -51,6 +53,8 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
|
|||||||
case "a":
|
case "a":
|
||||||
this._showVoiceCommandDialog(ev.detail);
|
this._showVoiceCommandDialog(ev.detail);
|
||||||
break;
|
break;
|
||||||
|
case "?":
|
||||||
|
this._showShortcutDialog(ev.detail);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -65,6 +69,8 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
|
|||||||
m: (ev) => this._createMyLink(ev),
|
m: (ev) => this._createMyLink(ev),
|
||||||
a: (ev) => this._showVoiceCommandDialog(ev),
|
a: (ev) => this._showVoiceCommandDialog(ev),
|
||||||
d: (ev) => this._showQuickBar(ev, QuickBarMode.Device),
|
d: (ev) => this._showQuickBar(ev, QuickBarMode.Device),
|
||||||
|
// Workaround see https://github.com/jamiebuilds/tinykeys/issues/130
|
||||||
|
"Shift+?": (ev) => this._showShortcutDialog(ev),
|
||||||
// Those are fallbacks for non-latin keyboards that don't have e, c, m keys (qwerty-based shortcuts)
|
// Those are fallbacks for non-latin keyboards that don't have e, c, m keys (qwerty-based shortcuts)
|
||||||
KeyE: (ev) => this._showQuickBar(ev),
|
KeyE: (ev) => this._showQuickBar(ev),
|
||||||
KeyC: (ev) => this._showQuickBar(ev, QuickBarMode.Command),
|
KeyC: (ev) => this._showQuickBar(ev, QuickBarMode.Command),
|
||||||
@ -111,6 +117,19 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
|
|||||||
showQuickBar(this, { mode });
|
showQuickBar(this, { mode });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _showShortcutDialog(e: KeyboardEvent) {
|
||||||
|
if (!this._canShowQuickBar(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
showShortcutsDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
private async _createMyLink(e: KeyboardEvent) {
|
private async _createMyLink(e: KeyboardEvent) {
|
||||||
if (
|
if (
|
||||||
!this.hass?.enableShortcuts ||
|
!this.hass?.enableShortcuts ||
|
||||||
@ -125,35 +144,26 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const targetPath = mainWindow.location.pathname;
|
const targetPath = mainWindow.location.pathname;
|
||||||
const isHassio = isComponentLoaded(this.hass, "hassio");
|
|
||||||
const myParams = new URLSearchParams();
|
const myParams = new URLSearchParams();
|
||||||
|
|
||||||
if (isHassio && targetPath.startsWith("/hassio")) {
|
let redirects: Redirects;
|
||||||
|
|
||||||
|
if (targetPath.startsWith("/hassio")) {
|
||||||
const myPanelSupervisor = await import(
|
const myPanelSupervisor = await import(
|
||||||
"../../hassio/src/hassio-my-redirect"
|
"../../hassio/src/hassio-my-redirect"
|
||||||
);
|
);
|
||||||
for (const [slug, redirect] of Object.entries(
|
redirects = myPanelSupervisor.REDIRECTS;
|
||||||
myPanelSupervisor.REDIRECTS
|
} else {
|
||||||
)) {
|
|
||||||
if (targetPath.startsWith(redirect.redirect)) {
|
|
||||||
myParams.append("redirect", slug);
|
|
||||||
if (redirect.redirect === "/hassio/addon") {
|
|
||||||
myParams.append("addon", targetPath.split("/")[3]);
|
|
||||||
}
|
|
||||||
window.open(
|
|
||||||
`https://my.home-assistant.io/create-link/?${myParams.toString()}`,
|
|
||||||
"_blank"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const myPanel = await import("../panels/my/ha-panel-my");
|
const myPanel = await import("../panels/my/ha-panel-my");
|
||||||
|
redirects = myPanel.getMyRedirects();
|
||||||
|
}
|
||||||
|
|
||||||
for (const [slug, redirect] of Object.entries(myPanel.getMyRedirects())) {
|
for (const [slug, redirect] of Object.entries(redirects)) {
|
||||||
if (targetPath.startsWith(redirect.redirect)) {
|
if (!targetPath.startsWith(redirect.redirect)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
myParams.append("redirect", slug);
|
myParams.append("redirect", slug);
|
||||||
|
|
||||||
if (redirect.params) {
|
if (redirect.params) {
|
||||||
const params = extractSearchParamsObject();
|
const params = extractSearchParamsObject();
|
||||||
for (const key of Object.keys(redirect.params)) {
|
for (const key of Object.keys(redirect.params)) {
|
||||||
@ -162,13 +172,17 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (redirect.redirect === "/config/integrations/integration") {
|
||||||
|
myParams.append("domain", targetPath.split("/")[4]);
|
||||||
|
} else if (redirect.redirect === "/hassio/addon") {
|
||||||
|
myParams.append("addon", targetPath.split("/")[3]);
|
||||||
|
}
|
||||||
window.open(
|
window.open(
|
||||||
`https://my.home-assistant.io/create-link/?${myParams.toString()}`,
|
`https://my.home-assistant.io/create-link/?${myParams.toString()}`,
|
||||||
"_blank"
|
"_blank"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
showToast(this, {
|
showToast(this, {
|
||||||
message: this.hass.localize(
|
message: this.hass.localize(
|
||||||
"ui.notification_toast.no_matching_link_found",
|
"ui.notification_toast.no_matching_link_found",
|
||||||
|
@ -1938,9 +1938,10 @@
|
|||||||
"title": "Assist",
|
"title": "Assist",
|
||||||
"open_assist": "open Assist dialog"
|
"open_assist": "open Assist dialog"
|
||||||
},
|
},
|
||||||
"automations": {
|
"automation_script": {
|
||||||
"title": "Automations",
|
"title": "Automations / Scripts",
|
||||||
"paste": "to paste automation YAML from clipboard to automation editor"
|
"paste": "to paste automation/script YAML from clipboard to editor",
|
||||||
|
"save": "to save automation/script"
|
||||||
},
|
},
|
||||||
"charts": {
|
"charts": {
|
||||||
"title": "Charts",
|
"title": "Charts",
|
||||||
@ -5508,6 +5509,7 @@
|
|||||||
"connection_monitor": "Connection monitor",
|
"connection_monitor": "Connection monitor",
|
||||||
"used_connection_slot_allocations": "Used connection slot allocations",
|
"used_connection_slot_allocations": "Used connection slot allocations",
|
||||||
"no_connections": "No active connections",
|
"no_connections": "No active connections",
|
||||||
|
"no_advertisements_found": "No matching Bluetooth advertisements found",
|
||||||
"no_connection_slot_allocations": "No connection slot allocations information available",
|
"no_connection_slot_allocations": "No connection slot allocations information available",
|
||||||
"no_active_connection_support": "This adapter does not support making active (GATT) connections.",
|
"no_active_connection_support": "This adapter does not support making active (GATT) connections.",
|
||||||
"address": "Address",
|
"address": "Address",
|
||||||
@ -5528,7 +5530,8 @@
|
|||||||
"title": "DHCP discovery",
|
"title": "DHCP discovery",
|
||||||
"mac_address": "MAC Address",
|
"mac_address": "MAC Address",
|
||||||
"hostname": "Hostname",
|
"hostname": "Hostname",
|
||||||
"ip_address": "IP Address"
|
"ip_address": "IP Address",
|
||||||
|
"no_devices_found": "No recent DHCP requests found; no matching discoveries detected"
|
||||||
},
|
},
|
||||||
"thread": {
|
"thread": {
|
||||||
"other_networks": "Other networks",
|
"other_networks": "Other networks",
|
||||||
@ -5577,7 +5580,8 @@
|
|||||||
"ssdp_headers": "SSDP Headers",
|
"ssdp_headers": "SSDP Headers",
|
||||||
"upnp": "Universal Plug and Play (UPnP)",
|
"upnp": "Universal Plug and Play (UPnP)",
|
||||||
"discovery_information": "Discovery information",
|
"discovery_information": "Discovery information",
|
||||||
"copy_to_clipboard": "Copy to clipboard"
|
"copy_to_clipboard": "Copy to clipboard",
|
||||||
|
"no_devices_found": "No matching SSDP/UPnP discoveries found"
|
||||||
},
|
},
|
||||||
"zeroconf": {
|
"zeroconf": {
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@ -5586,7 +5590,8 @@
|
|||||||
"ip_addresses": "IP Addresses",
|
"ip_addresses": "IP Addresses",
|
||||||
"properties": "Properties",
|
"properties": "Properties",
|
||||||
"discovery_information": "Discovery information",
|
"discovery_information": "Discovery information",
|
||||||
"copy_to_clipboard": "Copy to clipboard"
|
"copy_to_clipboard": "Copy to clipboard",
|
||||||
|
"no_devices_found": "No matching Zeroconf discoveries found"
|
||||||
},
|
},
|
||||||
"zha": {
|
"zha": {
|
||||||
"common": {
|
"common": {
|
||||||
@ -6394,15 +6399,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"discovery": {
|
"discovery": {
|
||||||
|
"title": "Network discovery",
|
||||||
|
"description": "Explore what data Home Assistant can see on the network.",
|
||||||
"dhcp": "DHCP browser",
|
"dhcp": "DHCP browser",
|
||||||
"dhcp_info": "The DHCP browser displays devices discovered by Home Assistant via DHCP, ARP+PTR lookups, and router-based device trackers. All detected devices by these methods will appear here.",
|
"dhcp_info": "Show devices detected by Home Assistant using methods like DHCP, ARP+PTR lookups, and router-based device trackers. DHCP (Dynamic Host Configuration Protocol) data is received when devices join the network and request an IP address.",
|
||||||
"dhcp_browser": "View DHCP browser",
|
|
||||||
"ssdp": "SSDP browser",
|
"ssdp": "SSDP browser",
|
||||||
"ssdp_info": "The SSDP browser shows devices discovered by Home Assistant using SSDP/UPnP. Devices that Home Assistant has discovered will appear here.",
|
"ssdp_info": "Show devices discovered by Home Assistant using SSDP/UPnP. Devices that Home Assistant has discovered will appear here.",
|
||||||
"ssdp_browser": "View SSDP browser",
|
|
||||||
"zeroconf": "Zeroconf browser",
|
"zeroconf": "Zeroconf browser",
|
||||||
"zeroconf_info": "The Zeroconf browser shows devices discovered by Home Assistant using mDNS. Only devices that Home Assistant is actively searching for will appear here.",
|
"zeroconf_info": "Show devices discovered by Home Assistant using mDNS. Only devices that Home Assistant is actively searching for will appear here."
|
||||||
"zeroconf_browser": "View Zeroconf browser"
|
|
||||||
},
|
},
|
||||||
"network_adapter": "Network adapter",
|
"network_adapter": "Network adapter",
|
||||||
"network_adapter_info": "Configure which network adapters integrations will use. Currently this setting only affects multicast traffic. A restart is required for these settings to apply.",
|
"network_adapter_info": "Configure which network adapters integrations will use. Currently this setting only affects multicast traffic. A restart is required for these settings to apply.",
|
||||||
|
@ -14,6 +14,47 @@ import {
|
|||||||
} from "../../src/data/energy";
|
} from "../../src/data/energy";
|
||||||
import type { HomeAssistant } from "../../src/types";
|
import type { HomeAssistant } from "../../src/types";
|
||||||
|
|
||||||
|
const checkConsumptionResult = (
|
||||||
|
input: {
|
||||||
|
from_grid: number | undefined;
|
||||||
|
to_grid: number | undefined;
|
||||||
|
solar: number | undefined;
|
||||||
|
to_battery: number | undefined;
|
||||||
|
from_battery: number | undefined;
|
||||||
|
},
|
||||||
|
exact = true
|
||||||
|
): {
|
||||||
|
grid_to_battery: number;
|
||||||
|
battery_to_grid: number;
|
||||||
|
solar_to_battery: number;
|
||||||
|
solar_to_grid: number;
|
||||||
|
used_solar: number;
|
||||||
|
used_grid: number;
|
||||||
|
used_battery: number;
|
||||||
|
used_total: number;
|
||||||
|
} => {
|
||||||
|
const result = computeConsumptionSingle(input);
|
||||||
|
if (exact) {
|
||||||
|
assert.equal(
|
||||||
|
result.used_total,
|
||||||
|
result.used_solar + result.used_battery + result.used_grid
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
input.to_grid || 0,
|
||||||
|
result.solar_to_grid + result.battery_to_grid
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
input.to_battery || 0,
|
||||||
|
result.grid_to_battery + result.solar_to_battery
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
input.solar || 0,
|
||||||
|
result.solar_to_battery + result.solar_to_grid + result.used_solar
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
describe("Energy Short Format Test", () => {
|
describe("Energy Short Format Test", () => {
|
||||||
// Create default to not have to specify a not relevant TimeFormat over and over again.
|
// Create default to not have to specify a not relevant TimeFormat over and over again.
|
||||||
const defaultLocale: FrontendLocaleData = {
|
const defaultLocale: FrontendLocaleData = {
|
||||||
@ -88,7 +129,7 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
it("Consuming Energy From the Grid", () => {
|
it("Consuming Energy From the Grid", () => {
|
||||||
[0, 5, 1000].forEach((x) => {
|
[0, 5, 1000].forEach((x) => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: x,
|
from_grid: x,
|
||||||
to_grid: undefined,
|
to_grid: undefined,
|
||||||
solar: undefined,
|
solar: undefined,
|
||||||
@ -101,6 +142,7 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
used_solar: 0,
|
used_solar: 0,
|
||||||
used_grid: x,
|
used_grid: x,
|
||||||
used_battery: 0,
|
used_battery: 0,
|
||||||
|
used_total: x,
|
||||||
solar_to_battery: 0,
|
solar_to_battery: 0,
|
||||||
solar_to_grid: 0,
|
solar_to_grid: 0,
|
||||||
}
|
}
|
||||||
@ -108,61 +150,78 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("Solar production, consuming some and returning the remainder to grid.", () => {
|
it("Solar production, consuming some and returning the remainder to grid.", () => {
|
||||||
[2.99, 3, 10, 100].forEach((s) => {
|
(
|
||||||
|
[
|
||||||
|
[2.99, false], // unsolveable : solar < to_grid
|
||||||
|
[3, true],
|
||||||
|
[10, true],
|
||||||
|
[100, true],
|
||||||
|
] as any
|
||||||
|
).forEach(([s, exact]) => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult(
|
||||||
|
{
|
||||||
from_grid: 0,
|
from_grid: 0,
|
||||||
to_grid: 3,
|
to_grid: 3,
|
||||||
solar: s,
|
solar: s,
|
||||||
to_battery: undefined,
|
to_battery: undefined,
|
||||||
from_battery: undefined,
|
from_battery: undefined,
|
||||||
}),
|
},
|
||||||
|
exact
|
||||||
|
),
|
||||||
{
|
{
|
||||||
grid_to_battery: 0,
|
grid_to_battery: 0,
|
||||||
battery_to_grid: 0,
|
battery_to_grid: 0,
|
||||||
used_solar: Math.max(0, s - 3),
|
used_solar: Math.min(s, Math.max(0, s - 3)),
|
||||||
used_grid: 0,
|
used_grid: 0,
|
||||||
used_battery: 0,
|
used_battery: 0,
|
||||||
|
used_total: s - 3,
|
||||||
solar_to_battery: 0,
|
solar_to_battery: 0,
|
||||||
solar_to_grid: 3,
|
solar_to_grid: Math.min(3, s),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("Solar production with simultaneous grid consumption. Excess solar returned to the grid.", () => {
|
it("Solar production with simultaneous grid consumption. Excess solar returned to the grid.", () => {
|
||||||
|
(
|
||||||
[
|
[
|
||||||
[0, 0],
|
[0, 0, true],
|
||||||
[3, 0],
|
[3, 0, true],
|
||||||
[0, 3],
|
[0, 3, true],
|
||||||
[5, 4],
|
[5, 4, true],
|
||||||
[4, 5],
|
[4, 5, true],
|
||||||
[10, 3],
|
[10, 3, true],
|
||||||
[3, 7],
|
[3, 7, true],
|
||||||
[3, 7.1],
|
[3, 7.1, false], // unsolveable: to_grid > solar
|
||||||
].forEach(([from_grid, to_grid]) => {
|
] as any
|
||||||
|
).forEach(([from_grid, to_grid, exact]) => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult(
|
||||||
|
{
|
||||||
from_grid,
|
from_grid,
|
||||||
to_grid,
|
to_grid,
|
||||||
solar: 7,
|
solar: 7,
|
||||||
to_battery: undefined,
|
to_battery: undefined,
|
||||||
from_battery: undefined,
|
from_battery: undefined,
|
||||||
}),
|
},
|
||||||
|
exact
|
||||||
|
),
|
||||||
{
|
{
|
||||||
grid_to_battery: 0,
|
grid_to_battery: 0,
|
||||||
battery_to_grid: 0,
|
battery_to_grid: 0,
|
||||||
used_solar: Math.max(0, 7 - to_grid),
|
used_solar: Math.max(0, 7 - to_grid),
|
||||||
used_grid: from_grid,
|
used_grid: from_grid - Math.max(0, to_grid - 7),
|
||||||
|
used_total: from_grid - to_grid + 7,
|
||||||
used_battery: 0,
|
used_battery: 0,
|
||||||
solar_to_battery: 0,
|
solar_to_battery: 0,
|
||||||
solar_to_grid: to_grid,
|
solar_to_grid: Math.min(7, to_grid),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("Charging the battery from the grid", () => {
|
it("Charging the battery from the grid", () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 5,
|
from_grid: 5,
|
||||||
to_grid: 0,
|
to_grid: 0,
|
||||||
solar: 0,
|
solar: 0,
|
||||||
@ -175,6 +234,7 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
used_solar: 0,
|
used_solar: 0,
|
||||||
used_grid: 2,
|
used_grid: 2,
|
||||||
used_battery: 0,
|
used_battery: 0,
|
||||||
|
used_total: 2,
|
||||||
solar_to_battery: 0,
|
solar_to_battery: 0,
|
||||||
solar_to_grid: 0,
|
solar_to_grid: 0,
|
||||||
}
|
}
|
||||||
@ -182,7 +242,7 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
});
|
});
|
||||||
it("Consuming from the grid and battery simultaneously", () => {
|
it("Consuming from the grid and battery simultaneously", () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 5,
|
from_grid: 5,
|
||||||
to_grid: 0,
|
to_grid: 0,
|
||||||
solar: 0,
|
solar: 0,
|
||||||
@ -195,6 +255,7 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
used_solar: 0,
|
used_solar: 0,
|
||||||
used_grid: 5,
|
used_grid: 5,
|
||||||
used_battery: 5,
|
used_battery: 5,
|
||||||
|
used_total: 10,
|
||||||
solar_to_battery: 0,
|
solar_to_battery: 0,
|
||||||
solar_to_grid: 0,
|
solar_to_grid: 0,
|
||||||
}
|
}
|
||||||
@ -202,7 +263,7 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
});
|
});
|
||||||
it("Consuming some battery and returning some battery to the grid", () => {
|
it("Consuming some battery and returning some battery to the grid", () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 0,
|
from_grid: 0,
|
||||||
to_grid: 4,
|
to_grid: 4,
|
||||||
solar: 0,
|
solar: 0,
|
||||||
@ -215,15 +276,15 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
used_solar: 0,
|
used_solar: 0,
|
||||||
used_grid: 0,
|
used_grid: 0,
|
||||||
used_battery: 1,
|
used_battery: 1,
|
||||||
|
used_total: 1,
|
||||||
solar_to_battery: 0,
|
solar_to_battery: 0,
|
||||||
solar_to_grid: 0,
|
solar_to_grid: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
/* Fails
|
|
||||||
it("Charging and discharging the battery to/from the grid in the same interval.", () => {
|
it("Charging and discharging the battery to/from the grid in the same interval.", () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 5,
|
from_grid: 5,
|
||||||
to_grid: 1,
|
to_grid: 1,
|
||||||
solar: 0,
|
solar: 0,
|
||||||
@ -234,15 +295,18 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
grid_to_battery: 3,
|
grid_to_battery: 3,
|
||||||
battery_to_grid: 1,
|
battery_to_grid: 1,
|
||||||
used_solar: 0,
|
used_solar: 0,
|
||||||
used_grid: 1,
|
used_grid: 2,
|
||||||
used_battery: 0,
|
used_battery: 0,
|
||||||
|
used_total: 2,
|
||||||
|
solar_to_battery: 0,
|
||||||
|
solar_to_grid: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}); */
|
});
|
||||||
/* Test does not pass, battery is not really correct when solar is not present
|
|
||||||
it("Charging the battery with no solar sensor.", () => {
|
it("Charging the battery with no solar sensor.", () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 5,
|
from_grid: 5,
|
||||||
to_grid: 0,
|
to_grid: 0,
|
||||||
solar: undefined,
|
solar: undefined,
|
||||||
@ -255,13 +319,15 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
used_solar: 0,
|
used_solar: 0,
|
||||||
used_grid: 2,
|
used_grid: 2,
|
||||||
used_battery: 0,
|
used_battery: 0,
|
||||||
|
used_total: 2,
|
||||||
|
solar_to_battery: 0,
|
||||||
|
solar_to_grid: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}); */
|
});
|
||||||
/* Test does not pass
|
|
||||||
it("Discharging battery to grid while also consuming from grid.", () => {
|
it("Discharging battery to grid while also consuming from grid.", () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 5,
|
from_grid: 5,
|
||||||
to_grid: 4,
|
to_grid: 4,
|
||||||
solar: 0,
|
solar: 0,
|
||||||
@ -274,14 +340,16 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
used_solar: 0,
|
used_solar: 0,
|
||||||
used_grid: 5,
|
used_grid: 5,
|
||||||
used_battery: 0,
|
used_battery: 0,
|
||||||
|
used_total: 5,
|
||||||
|
solar_to_grid: 0,
|
||||||
|
solar_to_battery: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
it("Grid, solar, and battery", () => {
|
it("Grid, solar, and battery", () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 5,
|
from_grid: 5,
|
||||||
to_grid: 3,
|
to_grid: 3,
|
||||||
solar: 7,
|
solar: 7,
|
||||||
@ -294,12 +362,13 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
used_solar: 1,
|
used_solar: 1,
|
||||||
used_grid: 5,
|
used_grid: 5,
|
||||||
used_battery: 0,
|
used_battery: 0,
|
||||||
|
used_total: 6,
|
||||||
solar_to_battery: 3,
|
solar_to_battery: 3,
|
||||||
solar_to_grid: 3,
|
solar_to_grid: 3,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 5,
|
from_grid: 5,
|
||||||
to_grid: 3,
|
to_grid: 3,
|
||||||
solar: 7,
|
solar: 7,
|
||||||
@ -308,16 +377,17 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
grid_to_battery: 0,
|
grid_to_battery: 0,
|
||||||
battery_to_grid: 0,
|
battery_to_grid: 3,
|
||||||
used_solar: 1,
|
used_solar: 4,
|
||||||
used_grid: 5,
|
used_grid: 5,
|
||||||
used_battery: 10,
|
used_battery: 7,
|
||||||
|
used_total: 16,
|
||||||
solar_to_battery: 3,
|
solar_to_battery: 3,
|
||||||
solar_to_grid: 3,
|
solar_to_grid: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 2,
|
from_grid: 2,
|
||||||
to_grid: 7,
|
to_grid: 7,
|
||||||
solar: 7,
|
solar: 7,
|
||||||
@ -325,17 +395,18 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
from_battery: 1,
|
from_battery: 1,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
grid_to_battery: 1,
|
grid_to_battery: 0,
|
||||||
battery_to_grid: 0,
|
battery_to_grid: 1,
|
||||||
used_solar: 0,
|
used_solar: 0,
|
||||||
used_grid: 1,
|
used_grid: 2,
|
||||||
used_battery: 1,
|
used_battery: 0,
|
||||||
solar_to_battery: 0,
|
used_total: 2,
|
||||||
solar_to_grid: 7,
|
solar_to_battery: 1,
|
||||||
|
solar_to_grid: 6,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 2,
|
from_grid: 2,
|
||||||
to_grid: 7,
|
to_grid: 7,
|
||||||
solar: 9,
|
solar: 9,
|
||||||
@ -344,17 +415,17 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
grid_to_battery: 0,
|
grid_to_battery: 0,
|
||||||
battery_to_grid: 0,
|
battery_to_grid: 1,
|
||||||
used_solar: 1,
|
used_solar: 2,
|
||||||
used_grid: 2,
|
used_grid: 2,
|
||||||
used_battery: 1,
|
used_battery: 0,
|
||||||
|
used_total: 4,
|
||||||
solar_to_battery: 1,
|
solar_to_battery: 1,
|
||||||
solar_to_grid: 7,
|
solar_to_grid: 6,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
/* Test does not pass
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
computeConsumptionSingle({
|
checkConsumptionResult({
|
||||||
from_grid: 5,
|
from_grid: 5,
|
||||||
to_grid: 3,
|
to_grid: 3,
|
||||||
solar: 1,
|
solar: 1,
|
||||||
@ -367,8 +438,71 @@ describe("Energy Usage Calculation Tests", () => {
|
|||||||
used_solar: 0,
|
used_solar: 0,
|
||||||
used_grid: 5,
|
used_grid: 5,
|
||||||
used_battery: 0,
|
used_battery: 0,
|
||||||
|
used_total: 5,
|
||||||
|
solar_to_battery: 0,
|
||||||
|
solar_to_grid: 1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
checkConsumptionResult({
|
||||||
|
from_grid: 6,
|
||||||
|
to_grid: 0,
|
||||||
|
solar: 3,
|
||||||
|
to_battery: 6,
|
||||||
|
from_battery: 6,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
grid_to_battery: 3,
|
||||||
|
battery_to_grid: 0,
|
||||||
|
used_solar: 0,
|
||||||
|
used_grid: 3,
|
||||||
|
used_battery: 6,
|
||||||
|
solar_to_battery: 3,
|
||||||
|
solar_to_grid: 0,
|
||||||
|
used_total: 9,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("Solar -> Battery -> Grid", () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
checkConsumptionResult({
|
||||||
|
from_grid: 0,
|
||||||
|
to_grid: 1,
|
||||||
|
solar: 1,
|
||||||
|
to_battery: 1,
|
||||||
|
from_battery: 1,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
grid_to_battery: 0,
|
||||||
|
battery_to_grid: 1,
|
||||||
|
used_solar: 0,
|
||||||
|
used_grid: 0,
|
||||||
|
used_battery: 0,
|
||||||
|
solar_to_battery: 1,
|
||||||
|
solar_to_grid: 0,
|
||||||
|
used_total: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("Solar -> Grid && Grid -> Battery", () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
checkConsumptionResult({
|
||||||
|
from_grid: 1,
|
||||||
|
to_grid: 1,
|
||||||
|
solar: 1,
|
||||||
|
to_battery: 1,
|
||||||
|
from_battery: 0,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
grid_to_battery: 1,
|
||||||
|
battery_to_grid: 0,
|
||||||
|
used_solar: 0,
|
||||||
|
used_grid: 0,
|
||||||
|
used_battery: 0,
|
||||||
|
solar_to_battery: 0,
|
||||||
|
solar_to_grid: 1,
|
||||||
|
used_total: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
*/
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user