mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-22 17:27:17 +00:00
Compare commits
12 Commits
update-dro
...
card-energ
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b533aa1c59 | ||
|
|
790075d9bb | ||
|
|
36ff43608b | ||
|
|
531bd49436 | ||
|
|
cfb09dfe31 | ||
|
|
d88977fbdc | ||
|
|
e93cb5dc55 | ||
|
|
2dbae06393 | ||
|
|
913e98138f | ||
|
|
db5c4df70f | ||
|
|
db3659f498 | ||
|
|
8b164b7603 |
@@ -0,0 +1,489 @@
|
|||||||
|
import type { CSSResultGroup } from "lit";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import "../../../../components/ha-alert";
|
||||||
|
import "../../../../components/ha-icon";
|
||||||
|
import "../../../../components/ha-icon-button-arrow-prev";
|
||||||
|
import "../../../../components/ha-md-list";
|
||||||
|
import "../../../../components/ha-md-list-item";
|
||||||
|
import "../../../../components/ha-md-divider";
|
||||||
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import type {
|
||||||
|
LovelaceCard,
|
||||||
|
LovelaceCardEditor,
|
||||||
|
LovelaceGridOptions,
|
||||||
|
} from "../../types";
|
||||||
|
import type { EnergyBreakdownUsageCardConfig } from "../types";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { computeAreaName } from "../../../../common/entity/compute_area_name";
|
||||||
|
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
|
||||||
|
import {
|
||||||
|
formatNumber,
|
||||||
|
isNumericState,
|
||||||
|
} from "../../../../common/number/format_number";
|
||||||
|
import { haStyleScrollbar } from "../../../../resources/styles";
|
||||||
|
|
||||||
|
interface Breakdown {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("hui-energy-breakdown-usage-card")
|
||||||
|
export class HuiEnergyBreakdownUsageCard
|
||||||
|
extends LitElement
|
||||||
|
implements LovelaceCard
|
||||||
|
{
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _config?: EnergyBreakdownUsageCardConfig;
|
||||||
|
|
||||||
|
@state() private _navigationStack: {
|
||||||
|
type: "area" | "entity";
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
@state() private _currentView: "areas" | "entities" = "areas";
|
||||||
|
|
||||||
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
|
await import(
|
||||||
|
"../../editor/config-elements/hui-energy-breakdown-usage-card-editor"
|
||||||
|
);
|
||||||
|
return document.createElement("hui-energy-breakdown-usage-card-editor");
|
||||||
|
}
|
||||||
|
|
||||||
|
public setConfig(config: EnergyBreakdownUsageCardConfig): void {
|
||||||
|
this._config = {
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getStubConfig(
|
||||||
|
hass: HomeAssistant
|
||||||
|
): Promise<EnergyBreakdownUsageCardConfig> {
|
||||||
|
const powerSensors = Object.keys(hass.states).filter(
|
||||||
|
generateEntityFilter(hass, {
|
||||||
|
domain: "sensor",
|
||||||
|
device_class: "power",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (powerSensors.length === 0) {
|
||||||
|
return { type: "energy-breakdown-usage" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const validStates = powerSensors
|
||||||
|
.map((id) => hass.states[id])
|
||||||
|
.filter((st) => st && isNumericState(st) && !isNaN(Number(st.state)));
|
||||||
|
if (validStates.length === 0) {
|
||||||
|
return { type: "energy-breakdown-usage" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const highest = validStates.reduce((best, st) =>
|
||||||
|
Number(st.state) > Number(best.state) ? st : best
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "energy-breakdown-usage",
|
||||||
|
power_entity: highest.entity_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCardSize(): number {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGridOptions(): LovelaceGridOptions {
|
||||||
|
return {
|
||||||
|
columns: 6,
|
||||||
|
rows: 3,
|
||||||
|
min_columns: 4,
|
||||||
|
min_rows: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._config || !this.hass) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityId = this._config.power_entity;
|
||||||
|
const stateObj = entityId ? this.hass.states[entityId] : undefined;
|
||||||
|
|
||||||
|
const powerEntityCompatible = stateObj
|
||||||
|
? stateObj.attributes.device_class === "power"
|
||||||
|
: true;
|
||||||
|
if (!powerEntityCompatible) {
|
||||||
|
return html`<ha-alert alert-type="error">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.energy-breakdown-usage.power_entity_invalid"
|
||||||
|
)}
|
||||||
|
</ha-alert>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uom = stateObj?.attributes.unit_of_measurement;
|
||||||
|
const powerEntityIcon = this._config.power_icon?.length
|
||||||
|
? this._config.power_icon
|
||||||
|
: "mdi:lightning-bolt";
|
||||||
|
|
||||||
|
const _computeBreakdown = memoizeOne(
|
||||||
|
(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
powerEntityId?: string,
|
||||||
|
powerEntityState?: string
|
||||||
|
): Breakdown[] => {
|
||||||
|
const breakdowns = Object.values(hass.areas)
|
||||||
|
.map((area): Breakdown | null => {
|
||||||
|
const areaName = computeAreaName(area);
|
||||||
|
if (!areaName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const powerEntityIds = Object.keys(hass.states).filter(
|
||||||
|
generateEntityFilter(hass, {
|
||||||
|
domain: "sensor",
|
||||||
|
device_class: "power",
|
||||||
|
area: area.area_id,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const validPowerEntities = powerEntityIds
|
||||||
|
.map((id) => hass.states[id])
|
||||||
|
.filter(
|
||||||
|
(st) =>
|
||||||
|
st &&
|
||||||
|
st.entity_id !== powerEntityId &&
|
||||||
|
isNumericState(st) &&
|
||||||
|
!isNaN(Number(st.state))
|
||||||
|
);
|
||||||
|
if (validPowerEntities.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sum = validPowerEntities.reduce(
|
||||||
|
(acc, entity) => acc + Number(entity.state),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
if (isNaN(sum) || sum === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: area.area_id,
|
||||||
|
name: areaName,
|
||||||
|
value: sum,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((bd): bd is Breakdown => bd !== null);
|
||||||
|
|
||||||
|
if (powerEntityState) {
|
||||||
|
breakdowns.push({
|
||||||
|
id: "untracked",
|
||||||
|
name: hass.localize("ui.common.untracked"),
|
||||||
|
value:
|
||||||
|
Number(powerEntityState) -
|
||||||
|
breakdowns.reduce((acc, bd) => acc + bd.value, 0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return breakdowns;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const breakdown = _computeBreakdown(this.hass, entityId, stateObj?.state);
|
||||||
|
const gridRows = Number(this._config.grid_options?.rows ?? 3);
|
||||||
|
|
||||||
|
const _computeEntityBreakdown = memoizeOne(
|
||||||
|
(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
areaId: string,
|
||||||
|
powerEntityId?: string
|
||||||
|
): Breakdown[] => {
|
||||||
|
const powerEntityIds = Object.keys(hass.states).filter(
|
||||||
|
generateEntityFilter(hass, {
|
||||||
|
domain: "sensor",
|
||||||
|
device_class: "power",
|
||||||
|
area: areaId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const validPowerEntities = powerEntityIds
|
||||||
|
.map((id) => hass.states[id])
|
||||||
|
.filter(
|
||||||
|
(st) =>
|
||||||
|
st &&
|
||||||
|
st.entity_id !== powerEntityId &&
|
||||||
|
isNumericState(st) &&
|
||||||
|
!isNaN(Number(st.state))
|
||||||
|
);
|
||||||
|
|
||||||
|
return validPowerEntities
|
||||||
|
.map((entity) => ({
|
||||||
|
id: entity.entity_id,
|
||||||
|
name: hass.states[entity.entity_id]?.attributes.friendly_name ?? "",
|
||||||
|
value: Number(entity.state),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.value - a.value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentNavigation =
|
||||||
|
this._navigationStack[this._navigationStack.length - 1];
|
||||||
|
const showBackButton =
|
||||||
|
this._currentView === "entities" && currentNavigation;
|
||||||
|
|
||||||
|
return html`<ha-card>
|
||||||
|
${stateObj
|
||||||
|
? html`
|
||||||
|
<div
|
||||||
|
class=${classMap({
|
||||||
|
heading: true,
|
||||||
|
"reduced-padding": gridRows < 3,
|
||||||
|
"single-row": gridRows === 1,
|
||||||
|
})}
|
||||||
|
@click=${this._handleHeadingClick}
|
||||||
|
>
|
||||||
|
<ha-icon class="icon" .icon=${powerEntityIcon}></ha-icon>
|
||||||
|
<span class="value"
|
||||||
|
>${formatNumber(stateObj?.state, this.hass.locale, {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
})}</span
|
||||||
|
>
|
||||||
|
<span class="measurement">${uom}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
${gridRows > 1 || !stateObj
|
||||||
|
? html`
|
||||||
|
<div class="breakdown ha-scrollbar">
|
||||||
|
${showBackButton
|
||||||
|
? html`
|
||||||
|
<div class="navigation-header">
|
||||||
|
<ha-icon-button-arrow-prev
|
||||||
|
@click=${this._goBack}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-icon-button-arrow-prev>
|
||||||
|
<span class="navigation-title"
|
||||||
|
>${currentNavigation?.name}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
<ha-md-list>
|
||||||
|
${this._currentView === "areas"
|
||||||
|
? breakdown.map(
|
||||||
|
(area, idx) => html`
|
||||||
|
${breakdown.length > 1 &&
|
||||||
|
idx === breakdown.length - 1 &&
|
||||||
|
area.id !== "untracked"
|
||||||
|
? html`<ha-md-divider
|
||||||
|
role="separator"
|
||||||
|
tabindex="-1"
|
||||||
|
></ha-md-divider>`
|
||||||
|
: nothing}
|
||||||
|
<ha-md-list-item
|
||||||
|
class=${classMap({
|
||||||
|
"untracked-item": area.id === "untracked",
|
||||||
|
})}
|
||||||
|
type="button"
|
||||||
|
@click=${area.id !== "untracked"
|
||||||
|
? this._createAreaClickHandler(area)
|
||||||
|
: undefined}
|
||||||
|
>
|
||||||
|
<span slot="headline">${area.name}</span>
|
||||||
|
<span class="meta" slot="end"
|
||||||
|
>${formatNumber(area.value, this.hass.locale, {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
})}
|
||||||
|
W</span
|
||||||
|
>
|
||||||
|
</ha-md-list-item>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
: currentNavigation?.id
|
||||||
|
? _computeEntityBreakdown(
|
||||||
|
this.hass,
|
||||||
|
currentNavigation.id,
|
||||||
|
entityId
|
||||||
|
).map(
|
||||||
|
(entity) => html`
|
||||||
|
<ha-md-list-item
|
||||||
|
type="button"
|
||||||
|
@click=${this._createEntityClickHandler(entity)}
|
||||||
|
>
|
||||||
|
<span slot="headline">${entity.name}</span>
|
||||||
|
<span class="meta" slot="end"
|
||||||
|
>${formatNumber(entity.value, this.hass.locale, {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
})}
|
||||||
|
${uom ?? ""}</span
|
||||||
|
>
|
||||||
|
</ha-md-list-item>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
: nothing}
|
||||||
|
</ha-md-list>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</ha-card>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleHeadingClick(): void {
|
||||||
|
if (!this._config?.power_entity) return;
|
||||||
|
fireEvent(this, "hass-more-info", { entityId: this._config.power_entity });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleAreaClick(area: Breakdown): void {
|
||||||
|
if (area.id === "untracked") return;
|
||||||
|
|
||||||
|
this._navigationStack.push({
|
||||||
|
type: "area",
|
||||||
|
id: area.id,
|
||||||
|
name: area.name,
|
||||||
|
});
|
||||||
|
this._currentView = "entities";
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleEntityClick(entity: Breakdown): void {
|
||||||
|
if (!entity.id) return;
|
||||||
|
fireEvent(this, "hass-more-info", { entityId: entity.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createAreaClickHandler(area: Breakdown) {
|
||||||
|
return () => this._handleAreaClick(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createEntityClickHandler(entity: Breakdown) {
|
||||||
|
return () => this._handleEntityClick(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _goBack(): void {
|
||||||
|
if (this._navigationStack.length > 0) {
|
||||||
|
this._navigationStack.pop();
|
||||||
|
this._currentView = "areas";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyleScrollbar,
|
||||||
|
css`
|
||||||
|
ha-card {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
width: fit-content;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px 12px 0;
|
||||||
|
gap: 2px;
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: var(--ha-line-height-normal);
|
||||||
|
}
|
||||||
|
.heading.reduced-padding {
|
||||||
|
padding: 8px 8px 0;
|
||||||
|
}
|
||||||
|
.heading.reduced-padding {
|
||||||
|
line-height: var(--ha-line-height-condensed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading .icon {
|
||||||
|
--mdc-icon-size: var(--ha-font-size-3xl);
|
||||||
|
}
|
||||||
|
.heading.single-row .icon {
|
||||||
|
--mdc-icon-size: var(--ha-font-size-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading .value {
|
||||||
|
font-size: var(--ha-font-size-5xl);
|
||||||
|
font-weight: var(--ha-font-weight-light);
|
||||||
|
}
|
||||||
|
.heading.single-row .value {
|
||||||
|
font-size: var(--ha-font-size-4xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading .measurement {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
align-self: flex-end;
|
||||||
|
font-size: var(--ha-font-size-xl);
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
.heading.single-row .measurement {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
font-size: var(--ha-font-size-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown ha-md-list {
|
||||||
|
--md-list-item-label-text-line-height: var(
|
||||||
|
--ha-line-height-condensed
|
||||||
|
);
|
||||||
|
--md-list-item-one-line-container-height: 16px;
|
||||||
|
--md-list-item-top-space: 8px;
|
||||||
|
--md-list-item-bottom-space: 8px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown ha-md-list-item.untracked-item {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 8px 2px;
|
||||||
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
background-color: var(--card-background-color);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation-header ha-icon-button-arrow-prev {
|
||||||
|
--mdc-icon-button-size: 32px;
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation-title {
|
||||||
|
font-size: var(--ha-font-size-body-1);
|
||||||
|
font-weight: var(--ha-font-weight-medium);
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-energy-breakdown-usage-card": HuiEnergyBreakdownUsageCard;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -220,6 +220,12 @@ export interface EnergySankeyCardConfig extends EnergyCardBaseConfig {
|
|||||||
group_by_area?: boolean;
|
group_by_area?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EnergyBreakdownUsageCardConfig extends LovelaceCardConfig {
|
||||||
|
type: "energy-breakdown-usage";
|
||||||
|
power_entity?: string;
|
||||||
|
power_icon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
||||||
type: "entity-filter";
|
type: "entity-filter";
|
||||||
entities: (EntityFilterEntityConfig | string)[];
|
entities: (EntityFilterEntityConfig | string)[];
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ const LAZY_LOAD_TYPES = {
|
|||||||
"energy-usage-graph": () =>
|
"energy-usage-graph": () =>
|
||||||
import("../cards/energy/hui-energy-usage-graph-card"),
|
import("../cards/energy/hui-energy-usage-graph-card"),
|
||||||
"energy-sankey": () => import("../cards/energy/hui-energy-sankey-card"),
|
"energy-sankey": () => import("../cards/energy/hui-energy-sankey-card"),
|
||||||
|
"energy-breakdown-usage": () =>
|
||||||
|
import("../cards/energy/hui-energy-breakdown-usage-card"),
|
||||||
"entity-filter": () => import("../cards/hui-entity-filter-card"),
|
"entity-filter": () => import("../cards/hui-entity-filter-card"),
|
||||||
error: () => import("../cards/hui-error-card"),
|
error: () => import("../cards/hui-error-card"),
|
||||||
"home-summary": () => import("../cards/hui-home-summary-card"),
|
"home-summary": () => import("../cards/hui-home-summary-card"),
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { html, LitElement, nothing, css } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { assert, assign, object, optional, string } from "superstruct";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||||
|
import "../../../../components/ha-form/ha-form";
|
||||||
|
import type {
|
||||||
|
HaFormSchema,
|
||||||
|
SchemaUnion,
|
||||||
|
} from "../../../../components/ha-form/types";
|
||||||
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import type { EnergyBreakdownUsageCardConfig } from "../../cards/types";
|
||||||
|
import type { LovelaceCardEditor } from "../../types";
|
||||||
|
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||||
|
import { configElementStyle } from "./config-elements-style";
|
||||||
|
|
||||||
|
const cardConfigStruct = assign(
|
||||||
|
baseLovelaceCardConfig,
|
||||||
|
object({
|
||||||
|
power_entity: optional(string()),
|
||||||
|
power_icon: optional(string()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
@customElement("hui-energy-breakdown-usage-card-editor")
|
||||||
|
export class HuiEnergyBreakdownUsageCardEditor
|
||||||
|
extends LitElement
|
||||||
|
implements LovelaceCardEditor
|
||||||
|
{
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _config?: EnergyBreakdownUsageCardConfig;
|
||||||
|
|
||||||
|
private _schema = memoizeOne(
|
||||||
|
(_localize: LocalizeFunc) =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "power_entity",
|
||||||
|
selector: {
|
||||||
|
entity: {
|
||||||
|
filter: [{ domain: "sensor", device_class: "power" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "power_icon",
|
||||||
|
selector: {
|
||||||
|
icon: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as const satisfies readonly HaFormSchema[]
|
||||||
|
);
|
||||||
|
|
||||||
|
public setConfig(config: EnergyBreakdownUsageCardConfig): void {
|
||||||
|
assert(config, cardConfigStruct);
|
||||||
|
|
||||||
|
this._config = {
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.hass || !this._config) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = this._schema(this.hass.localize);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this._config}
|
||||||
|
.schema=${schema}
|
||||||
|
.computeLabel=${this._computeLabelCallback}
|
||||||
|
.computeHelper=${this._computeHelperCallback}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
const newConfig = ev.detail.value as EnergyBreakdownUsageCardConfig;
|
||||||
|
|
||||||
|
const config: EnergyBreakdownUsageCardConfig = {
|
||||||
|
...newConfig,
|
||||||
|
type: "energy-breakdown-usage",
|
||||||
|
};
|
||||||
|
|
||||||
|
fireEvent(this, "config-changed", { config });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeHelperCallback = (
|
||||||
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
|
): string | undefined => {
|
||||||
|
switch (schema.name) {
|
||||||
|
default:
|
||||||
|
return this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.energy-breakdown-usage.${schema.name}_helper`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private _computeLabelCallback = (
|
||||||
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
|
) => {
|
||||||
|
switch (schema.name) {
|
||||||
|
case "power_entity":
|
||||||
|
return this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.generic.entity"
|
||||||
|
);
|
||||||
|
case "power_icon":
|
||||||
|
return this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.generic.icon"
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [
|
||||||
|
configElementStyle,
|
||||||
|
css`
|
||||||
|
ha-form {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-energy-breakdown-usage-card-editor": HuiEnergyBreakdownUsageCardEditor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,6 +101,10 @@ export const coreCards: Card[] = [
|
|||||||
type: "area",
|
type: "area",
|
||||||
showElement: true,
|
showElement: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "energy-breakdown-usage",
|
||||||
|
showElement: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
showElement: true,
|
showElement: true,
|
||||||
|
|||||||
@@ -450,7 +450,8 @@
|
|||||||
"replace": "Replace",
|
"replace": "Replace",
|
||||||
"append": "Append",
|
"append": "Append",
|
||||||
"supports_markdown": "Supports {markdown_help_link}",
|
"supports_markdown": "Supports {markdown_help_link}",
|
||||||
"markdown": "Markdown"
|
"markdown": "Markdown",
|
||||||
|
"untracked": "Untracked"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"selectors": {
|
"selectors": {
|
||||||
@@ -7942,6 +7943,13 @@
|
|||||||
"description": "The Thermostat card gives control of a climate or water heater entity for heating or cooling, allowing you to set its mode and desired temperature.",
|
"description": "The Thermostat card gives control of a climate or water heater entity for heating or cooling, allowing you to set its mode and desired temperature.",
|
||||||
"show_current_as_primary": "Show current temperature as primary information"
|
"show_current_as_primary": "Show current temperature as primary information"
|
||||||
},
|
},
|
||||||
|
"energy-breakdown-usage": {
|
||||||
|
"name": "Current energy usage and breakdown",
|
||||||
|
"description": "The Current energy usage and breakdown card shows your home's current power usage at a glance and a breakdown of the usage by area.",
|
||||||
|
"power_entity_invalid": "Selected entity is not a power sensor (watts). Pick a sensor with the device class power.",
|
||||||
|
"power_entity_helper": "Entity for current usage",
|
||||||
|
"power_icon_helper": "Icon for current usage"
|
||||||
|
},
|
||||||
"tile": {
|
"tile": {
|
||||||
"name": "Tile",
|
"name": "Tile",
|
||||||
"description": "The Tile card gives you a quick overview of an entity. The card allows you to toggle the entity, show the More info dialog or trigger custom actions.",
|
"description": "The Tile card gives you a quick overview of an entity. The card allows you to toggle the entity, show the More info dialog or trigger custom actions.",
|
||||||
|
|||||||
Reference in New Issue
Block a user