2022-11-29 15:22:50 +01:00
|
|
|
import { HassEntity } from "home-assistant-js-websocket";
|
2020-03-31 01:49:02 +02:00
|
|
|
import {
|
2020-04-14 18:05:45 +02:00
|
|
|
css,
|
2021-05-07 22:16:14 +02:00
|
|
|
CSSResultGroup,
|
2020-03-31 01:49:02 +02:00
|
|
|
html,
|
|
|
|
LitElement,
|
2023-02-28 11:02:47 +01:00
|
|
|
nothing,
|
2023-06-21 17:22:33 +02:00
|
|
|
PropertyValues,
|
2021-05-18 16:37:53 +02:00
|
|
|
} from "lit";
|
|
|
|
import { customElement, property, state } from "lit/decorators";
|
2021-09-06 11:26:16 +02:00
|
|
|
import { ifDefined } from "lit/directives/if-defined";
|
2022-11-29 15:22:50 +01:00
|
|
|
import { styleMap } from "lit/directives/style-map";
|
2020-03-31 01:49:02 +02:00
|
|
|
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
2020-04-14 18:05:45 +02:00
|
|
|
import { fireEvent } from "../../../common/dom/fire_event";
|
2023-06-21 17:22:33 +02:00
|
|
|
import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display";
|
2020-04-24 15:49:25 +02:00
|
|
|
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
2021-09-06 11:26:16 +02:00
|
|
|
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
2020-03-31 01:49:02 +02:00
|
|
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
2022-12-09 16:04:14 +01:00
|
|
|
import { stateColorCss } from "../../../common/entity/state_color";
|
2020-04-14 18:05:45 +02:00
|
|
|
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
2021-11-01 09:32:22 +01:00
|
|
|
import {
|
|
|
|
formatNumber,
|
2022-10-26 13:27:14 +02:00
|
|
|
getNumberFormatOptions,
|
2021-11-01 09:32:22 +01:00
|
|
|
isNumericState,
|
|
|
|
} from "../../../common/number/format_number";
|
2021-09-06 11:26:16 +02:00
|
|
|
import { iconColorCSS } from "../../../common/style/icon_color_css";
|
2020-03-31 01:49:02 +02:00
|
|
|
import "../../../components/ha-card";
|
|
|
|
import "../../../components/ha-icon";
|
2023-01-23 09:28:38 +01:00
|
|
|
import { HVAC_ACTION_TO_MODE } from "../../../data/climate";
|
2022-12-14 19:39:10 +01:00
|
|
|
import { isUnavailableState } from "../../../data/entity";
|
2022-12-13 17:23:49 +01:00
|
|
|
import { LightEntity } from "../../../data/light";
|
2020-04-14 18:05:45 +02:00
|
|
|
import { HomeAssistant } from "../../../types";
|
2021-01-13 17:17:12 +01:00
|
|
|
import { computeCardSize } from "../common/compute-card-size";
|
2021-02-07 14:37:35 +01:00
|
|
|
import { findEntities } from "../common/find-entities";
|
2020-04-14 18:05:45 +02:00
|
|
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
2020-05-29 06:09:26 +02:00
|
|
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
2020-04-14 18:05:45 +02:00
|
|
|
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
|
2023-06-21 17:22:33 +02:00
|
|
|
import { LovelaceCard, LovelaceHeaderFooter } from "../types";
|
2020-03-31 01:49:02 +02:00
|
|
|
import { HuiErrorCard } from "./hui-error-card";
|
2020-04-14 18:05:45 +02:00
|
|
|
import { EntityCardConfig } from "./types";
|
2020-03-31 01:49:02 +02:00
|
|
|
|
|
|
|
@customElement("hui-entity-card")
|
2020-04-01 18:12:19 +02:00
|
|
|
export class HuiEntityCard extends LitElement implements LovelaceCard {
|
2020-03-31 01:49:02 +02:00
|
|
|
public static getStubConfig(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entities: string[],
|
|
|
|
entitiesFill: string[]
|
|
|
|
) {
|
|
|
|
const includeDomains = ["sensor", "light", "switch"];
|
|
|
|
const maxEntities = 1;
|
|
|
|
const foundEntities = findEntities(
|
|
|
|
hass,
|
|
|
|
maxEntities,
|
|
|
|
entities,
|
|
|
|
entitiesFill,
|
|
|
|
includeDomains
|
|
|
|
);
|
|
|
|
|
|
|
|
return {
|
|
|
|
entity: foundEntities[0] || "",
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-06-21 17:22:33 +02:00
|
|
|
public static async getConfigForm() {
|
|
|
|
return (await import("../editor/config-elements/hui-entity-card-editor"))
|
|
|
|
.default;
|
|
|
|
}
|
|
|
|
|
2020-07-15 06:38:36 +02:00
|
|
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
2020-04-14 18:05:45 +02:00
|
|
|
|
2021-05-07 22:16:14 +02:00
|
|
|
@state() private _config?: EntityCardConfig;
|
2020-04-14 18:05:45 +02:00
|
|
|
|
2020-03-31 01:49:02 +02:00
|
|
|
private _footerElement?: HuiErrorCard | LovelaceHeaderFooter;
|
|
|
|
|
2022-12-09 16:04:14 +01:00
|
|
|
private getStateColor(stateObj: HassEntity, config: EntityCardConfig) {
|
|
|
|
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
|
|
|
return (
|
|
|
|
config &&
|
|
|
|
(config.state_color ||
|
|
|
|
(domain === "light" && config.state_color !== false))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-03-31 01:49:02 +02:00
|
|
|
public setConfig(config: EntityCardConfig): void {
|
2020-11-21 14:08:43 +01:00
|
|
|
if (!config.entity) {
|
|
|
|
throw new Error("Entity must be specified");
|
|
|
|
}
|
2020-03-31 01:49:02 +02:00
|
|
|
if (config.entity && !isValidEntityId(config.entity)) {
|
2020-11-21 14:08:43 +01:00
|
|
|
throw new Error("Invalid entity");
|
2020-03-31 01:49:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
this._config = config;
|
|
|
|
|
|
|
|
if (this._config.footer) {
|
|
|
|
this._footerElement = createHeaderFooterElement(this._config.footer);
|
|
|
|
} else if (this._footerElement) {
|
|
|
|
this._footerElement = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-01 16:08:27 +02:00
|
|
|
public async getCardSize(): Promise<number> {
|
|
|
|
let size = 2;
|
|
|
|
if (this._footerElement) {
|
|
|
|
const footerSize = computeCardSize(this._footerElement);
|
|
|
|
size += footerSize instanceof Promise ? await footerSize : footerSize;
|
|
|
|
}
|
|
|
|
return size;
|
2020-03-31 01:49:02 +02:00
|
|
|
}
|
|
|
|
|
2023-02-28 11:02:47 +01:00
|
|
|
protected render() {
|
2020-03-31 01:49:02 +02:00
|
|
|
if (!this._config || !this.hass) {
|
2023-02-28 11:02:47 +01:00
|
|
|
return nothing;
|
2020-03-31 01:49:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const stateObj = this.hass.states[this._config.entity];
|
|
|
|
|
|
|
|
if (!stateObj) {
|
|
|
|
return html`
|
2020-05-29 06:09:26 +02:00
|
|
|
<hui-warning>
|
|
|
|
${createEntityNotFoundWarning(this.hass, this._config.entity)}
|
|
|
|
</hui-warning>
|
2020-03-31 01:49:02 +02:00
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
2021-09-06 11:26:16 +02:00
|
|
|
const domain = computeStateDomain(stateObj);
|
2020-03-31 01:49:02 +02:00
|
|
|
const showUnit = this._config.attribute
|
|
|
|
? this._config.attribute in stateObj.attributes
|
2022-12-14 19:39:10 +01:00
|
|
|
: !isUnavailableState(stateObj.state);
|
2020-03-31 01:49:02 +02:00
|
|
|
|
2021-10-06 17:41:37 +02:00
|
|
|
const name = this._config.name || computeStateName(stateObj);
|
|
|
|
|
2023-01-24 13:45:47 +01:00
|
|
|
const colored = stateObj && this.getStateColor(stateObj, this._config);
|
2022-11-29 15:22:50 +01:00
|
|
|
|
2020-03-31 01:49:02 +02:00
|
|
|
return html`
|
2020-05-13 12:47:09 +02:00
|
|
|
<ha-card @click=${this._handleClick} tabindex="0">
|
|
|
|
<div class="header">
|
2021-10-06 17:41:37 +02:00
|
|
|
<div class="name" .title=${name}>${name}</div>
|
2020-05-13 12:47:09 +02:00
|
|
|
<div class="icon">
|
2021-10-20 11:10:16 +02:00
|
|
|
<ha-state-icon
|
|
|
|
.icon=${this._config.icon}
|
|
|
|
.state=${stateObj}
|
2022-11-29 15:22:50 +01:00
|
|
|
data-domain=${ifDefined(domain)}
|
|
|
|
data-state=${stateObj.state}
|
|
|
|
style=${styleMap({
|
2022-12-09 16:04:14 +01:00
|
|
|
color: colored ? this._computeColor(stateObj) : undefined,
|
2022-12-13 17:23:49 +01:00
|
|
|
filter: colored ? this._computeBrightness(stateObj) : undefined,
|
2022-11-29 15:22:50 +01:00
|
|
|
height: this._config.icon_height
|
|
|
|
? this._config.icon_height
|
|
|
|
: "",
|
|
|
|
})}
|
2021-10-20 11:10:16 +02:00
|
|
|
></ha-state-icon>
|
2020-03-31 01:49:02 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2020-05-13 12:47:09 +02:00
|
|
|
<div class="info">
|
|
|
|
<span class="value"
|
|
|
|
>${"attribute" in this._config
|
2021-05-26 00:08:41 +02:00
|
|
|
? stateObj.attributes[this._config.attribute!] !== undefined
|
2023-03-17 11:43:59 +01:00
|
|
|
? computeAttributeValueDisplay(
|
|
|
|
this.hass.localize,
|
|
|
|
stateObj,
|
|
|
|
this.hass.locale,
|
2023-06-13 12:12:13 +02:00
|
|
|
this.hass.config,
|
2023-03-17 11:43:59 +01:00
|
|
|
this.hass.entities,
|
|
|
|
this._config.attribute!
|
2021-05-26 00:08:41 +02:00
|
|
|
)
|
|
|
|
: this.hass.localize("state.default.unknown")
|
2022-10-17 11:15:37 +02:00
|
|
|
: isNumericState(stateObj) || this._config.unit
|
2022-10-26 13:27:14 +02:00
|
|
|
? formatNumber(
|
|
|
|
stateObj.state,
|
|
|
|
this.hass.locale,
|
2023-02-08 18:20:58 +01:00
|
|
|
getNumberFormatOptions(
|
|
|
|
stateObj,
|
|
|
|
this.hass.entities[this._config.entity]
|
|
|
|
)
|
2022-10-26 13:27:14 +02:00
|
|
|
)
|
2020-05-13 12:47:09 +02:00
|
|
|
: computeStateDisplay(
|
|
|
|
this.hass.localize,
|
|
|
|
stateObj,
|
2022-12-01 12:53:44 +01:00
|
|
|
this.hass.locale,
|
2023-06-13 12:12:13 +02:00
|
|
|
this.hass.config,
|
2022-12-01 12:53:44 +01:00
|
|
|
this.hass.entities
|
2020-05-13 12:47:09 +02:00
|
|
|
)}</span
|
|
|
|
>${showUnit
|
|
|
|
? html`
|
|
|
|
<span class="measurement"
|
|
|
|
>${this._config.unit ||
|
|
|
|
(this._config.attribute
|
|
|
|
? ""
|
|
|
|
: stateObj.attributes.unit_of_measurement)}</span
|
|
|
|
>
|
|
|
|
`
|
|
|
|
: ""}
|
|
|
|
</div>
|
2020-03-31 01:49:02 +02:00
|
|
|
${this._footerElement}
|
|
|
|
</ha-card>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
2022-12-13 16:49:12 +01:00
|
|
|
private _computeColor(stateObj: HassEntity): string | undefined {
|
|
|
|
if (stateObj.attributes.hvac_action) {
|
|
|
|
const hvacAction = stateObj.attributes.hvac_action;
|
2023-02-03 10:00:54 +01:00
|
|
|
if (hvacAction in HVAC_ACTION_TO_MODE) {
|
2023-01-23 09:28:38 +01:00
|
|
|
return stateColorCss(stateObj, HVAC_ACTION_TO_MODE[hvacAction]);
|
2022-12-13 16:49:12 +01:00
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
}
|
2023-01-24 13:45:47 +01:00
|
|
|
if (stateObj.attributes.rgb_color) {
|
2022-12-13 17:23:49 +01:00
|
|
|
return `rgb(${stateObj.attributes.rgb_color.join(",")})`;
|
|
|
|
}
|
2022-12-09 16:04:14 +01:00
|
|
|
const iconColor = stateColorCss(stateObj);
|
2022-11-29 15:22:50 +01:00
|
|
|
if (iconColor) {
|
2023-01-23 09:28:38 +01:00
|
|
|
return iconColor;
|
2022-11-29 15:22:50 +01:00
|
|
|
}
|
2022-12-12 15:52:33 +01:00
|
|
|
return undefined;
|
2022-11-29 15:22:50 +01:00
|
|
|
}
|
|
|
|
|
2022-12-13 17:23:49 +01:00
|
|
|
private _computeBrightness(stateObj: HassEntity | LightEntity): string {
|
2023-01-24 13:45:47 +01:00
|
|
|
if (stateObj.attributes.brightness) {
|
2022-12-13 17:23:49 +01:00
|
|
|
const brightness = stateObj.attributes.brightness;
|
|
|
|
return `brightness(${(brightness + 245) / 5}%)`;
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2020-03-31 01:49:02 +02:00
|
|
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
|
|
|
// Side Effect used to update footer hass while keeping optimizations
|
|
|
|
if (this._footerElement) {
|
|
|
|
this._footerElement.hass = this.hass;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hasConfigOrEntityChanged(this, changedProps);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected updated(changedProps: PropertyValues) {
|
|
|
|
super.updated(changedProps);
|
|
|
|
if (!this._config || !this.hass) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
|
|
|
const oldConfig = changedProps.get("_config") as
|
|
|
|
| EntityCardConfig
|
|
|
|
| undefined;
|
|
|
|
|
|
|
|
if (
|
|
|
|
!oldHass ||
|
|
|
|
!oldConfig ||
|
|
|
|
oldHass.themes !== this.hass.themes ||
|
|
|
|
oldConfig.theme !== this._config.theme
|
|
|
|
) {
|
|
|
|
applyThemesOnElement(this, this.hass.themes, this._config!.theme);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private _handleClick(): void {
|
|
|
|
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
|
|
|
}
|
|
|
|
|
2021-05-07 22:16:14 +02:00
|
|
|
static get styles(): CSSResultGroup {
|
2021-09-06 11:26:16 +02:00
|
|
|
return [
|
|
|
|
iconColorCSS,
|
|
|
|
css`
|
|
|
|
ha-card {
|
|
|
|
height: 100%;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: space-between;
|
|
|
|
cursor: pointer;
|
|
|
|
outline: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
.header {
|
|
|
|
display: flex;
|
|
|
|
padding: 8px 16px 0;
|
|
|
|
justify-content: space-between;
|
|
|
|
}
|
|
|
|
|
|
|
|
.name {
|
|
|
|
color: var(--secondary-text-color);
|
|
|
|
line-height: 40px;
|
|
|
|
font-weight: 500;
|
|
|
|
font-size: 16px;
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
}
|
|
|
|
|
|
|
|
.icon {
|
2023-01-24 13:45:47 +01:00
|
|
|
color: var(--paper-item-icon-color, #44739e);
|
|
|
|
--state-inactive-color: var(--paper-item-icon-color, #44739e);
|
2021-09-06 11:26:16 +02:00
|
|
|
line-height: 40px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.info {
|
|
|
|
padding: 0px 16px 16px;
|
|
|
|
margin-top: -4px;
|
|
|
|
overflow: hidden;
|
|
|
|
white-space: nowrap;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
line-height: 28px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.value {
|
|
|
|
font-size: 28px;
|
|
|
|
margin-right: 4px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.measurement {
|
|
|
|
font-size: 18px;
|
|
|
|
color: var(--secondary-text-color);
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
];
|
2020-03-31 01:49:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
interface HTMLElementTagNameMap {
|
|
|
|
"hui-entity-card": HuiEntityCard;
|
|
|
|
}
|
|
|
|
}
|