mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 17:26:42 +00:00
Entity Card: Card Addition (#4971)
* Review to changed Src translations * Reviews * side by side theme * Allow user to specify unit * Add unit back update for headerfooter and editor * Clean * Unavailable for attribute that doesn't exist * fix merge * Fix from rebasing * reviews * Localize State * fix for localize * css * reviews * Break the rules
This commit is contained in:
parent
5daa6dbd25
commit
158eddfd44
242
src/panels/lovelace/cards/hui-entity-card.ts
Normal file
242
src/panels/lovelace/cards/hui-entity-card.ts
Normal file
@ -0,0 +1,242 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateIcon } from "../../../common/entity/state_icon";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import "../components/hui-warning";
|
||||
|
||||
import {
|
||||
LovelaceCard,
|
||||
LovelaceCardEditor,
|
||||
LovelaceHeaderFooter,
|
||||
} from "../types";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { EntityCardConfig } from "./types";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
|
||||
import { UNKNOWN, UNAVAILABLE } from "../../../data/entity";
|
||||
import { HuiErrorCard } from "./hui-error-card";
|
||||
|
||||
@customElement("hui-entity-card")
|
||||
class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(
|
||||
/* webpackChunkName: "hui-entity-card-editor" */ "../editor/config-elements/hui-entity-card-editor"
|
||||
);
|
||||
return document.createElement("hui-entity-card-editor");
|
||||
}
|
||||
|
||||
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] || "",
|
||||
};
|
||||
}
|
||||
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property() private _config?: EntityCardConfig;
|
||||
private _footerElement?: HuiErrorCard | LovelaceHeaderFooter;
|
||||
|
||||
public setConfig(config: EntityCardConfig): void {
|
||||
if (config.entity && !isValidEntityId(config.entity)) {
|
||||
throw new Error("Invalid Entity");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
|
||||
if (this._config.footer) {
|
||||
this._footerElement = createHeaderFooterElement(this._config.footer);
|
||||
} else if (this._footerElement) {
|
||||
this._footerElement = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
return 1 + (this._config?.footer ? 1 : 0);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._config || !this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity];
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
<hui-warning
|
||||
>${this.hass.localize(
|
||||
"ui.panel.lovelace.warning.entity_not_found",
|
||||
"entity",
|
||||
this._config.entity
|
||||
)}</hui-warning
|
||||
>
|
||||
`;
|
||||
}
|
||||
|
||||
const showUnit = this._config.attribute
|
||||
? this._config.attribute in stateObj.attributes
|
||||
: stateObj.state !== UNKNOWN && stateObj.state !== UNAVAILABLE;
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<div
|
||||
@action=${this._handleClick}
|
||||
.actionHandler=${actionHandler()}
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="header">
|
||||
<div class="name">
|
||||
${this._config.name || computeStateName(stateObj)}
|
||||
</div>
|
||||
<div class="icon">
|
||||
<ha-icon
|
||||
.icon=${this._config.icon || stateIcon(stateObj)}
|
||||
></ha-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<span class="value"
|
||||
>${"attribute" in this._config
|
||||
? stateObj.attributes[this._config.attribute!] ||
|
||||
this.hass.localize("state.default.unknown")
|
||||
: this.hass.localize(`state.default.${stateObj.state}`) ||
|
||||
this.hass.localize(
|
||||
`state.${this._config.entity.split(".")[0]}.${
|
||||
stateObj.state
|
||||
}`
|
||||
) ||
|
||||
stateObj.state}</span
|
||||
>${showUnit
|
||||
? html`
|
||||
<span class="measurement"
|
||||
>${this._config.unit ||
|
||||
(this._config.attribute
|
||||
? ""
|
||||
: stateObj.attributes.unit_of_measurement)}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
${this._footerElement}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-card > div {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.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 {
|
||||
color: var(--state-icon-color, #44739e);
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 0px 16px 16px;
|
||||
margin-top: -4px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.measurement {
|
||||
font-size: 18px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-entity-card": HuiEntityCard;
|
||||
}
|
||||
}
|
@ -22,6 +22,11 @@ export interface EmptyStateCardConfig extends LovelaceCardConfig {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface EntityCardConfig extends LovelaceCardConfig {
|
||||
attribute?: string;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export interface EntitiesCardEntityConfig extends EntityConfig {
|
||||
type?: string;
|
||||
secondary_info?: "entity-id" | "last-changed";
|
||||
|
@ -1,4 +1,5 @@
|
||||
import "../cards/hui-entities-card";
|
||||
import "../cards/hui-entity-card";
|
||||
import "../cards/hui-button-card";
|
||||
import "../cards/hui-entity-button-card";
|
||||
import "../cards/hui-glance-card";
|
||||
@ -16,6 +17,7 @@ import {
|
||||
} from "./create-element-base";
|
||||
|
||||
const ALWAYS_LOADED_TYPES = new Set([
|
||||
"entity",
|
||||
"entities",
|
||||
"button",
|
||||
"entity-button",
|
||||
|
@ -28,6 +28,7 @@ const previewCards: string[] = [
|
||||
"alarm-panel",
|
||||
"button",
|
||||
"entities",
|
||||
"entity",
|
||||
"gauge",
|
||||
"glance",
|
||||
"history-graph",
|
||||
|
@ -0,0 +1,189 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../components/hui-action-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
|
||||
import { struct } from "../../common/structs/struct";
|
||||
import { EntitiesEditorEvent, EditorTarget } from "../types";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { EntityCardConfig } from "../../cards/types";
|
||||
import { headerFooterConfigStructs } from "../../header-footer/types";
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
entity: "string?",
|
||||
name: "string?",
|
||||
icon: "string?",
|
||||
attribute: "string?",
|
||||
unit: "string?",
|
||||
theme: "string?",
|
||||
header: struct.optional(headerFooterConfigStructs),
|
||||
footer: struct.optional(headerFooterConfigStructs),
|
||||
});
|
||||
|
||||
@customElement("hui-entity-card-editor")
|
||||
export class HuiEntityCardEditor extends LitElement
|
||||
implements LovelaceCardEditor {
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@property() private _config?: EntityCardConfig;
|
||||
|
||||
public setConfig(config: EntityCardConfig): void {
|
||||
config = cardConfigStruct(config);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
get _entity(): string {
|
||||
return this._config!.entity || "";
|
||||
}
|
||||
|
||||
get _name(): string {
|
||||
return this._config!.name || "";
|
||||
}
|
||||
|
||||
get _icon(): string {
|
||||
return this._config!.icon || "";
|
||||
}
|
||||
|
||||
get _attribute(): string {
|
||||
return this._config!.attribute || "";
|
||||
}
|
||||
|
||||
get _unit(): string {
|
||||
return this._config!.unit || "";
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
return this._config!.theme || "default";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${configElementStyle}
|
||||
<div class="card-config">
|
||||
<ha-entity-picker
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.entity"
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.hass=${this.hass}
|
||||
.value=${this._entity}
|
||||
.configValue=${"entity"}
|
||||
@change=${this._valueChanged}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.name"
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.icon"
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.attribute"
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value=${this._attribute}
|
||||
.configValue=${"attribute"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.unit"
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value=${this._unit}
|
||||
.configValue=${"unit"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
</div>
|
||||
<hui-theme-select-editor
|
||||
.hass=${this.hass}
|
||||
.value=${this._theme}
|
||||
.configValue=${"theme"}
|
||||
@theme-changed=${this._valueChanged}
|
||||
></hui-theme-select-editor>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === target.value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
let newValue: string | undefined;
|
||||
if (
|
||||
target.configValue === "icon_height" &&
|
||||
!isNaN(Number(target.value))
|
||||
) {
|
||||
newValue = `${String(target.value)}px`;
|
||||
}
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue!]:
|
||||
target.checked !== undefined
|
||||
? target.checked
|
||||
: newValue !== undefined
|
||||
? newValue
|
||||
: target.value
|
||||
? target.value
|
||||
: target.config,
|
||||
};
|
||||
}
|
||||
}
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-entity-card-editor": HuiEntityCardEditor;
|
||||
}
|
||||
}
|
@ -2021,6 +2021,9 @@
|
||||
"toggle": "Toggle entities.",
|
||||
"description": "The Entities card is the most common type of card. It groups items together into lists."
|
||||
},
|
||||
"entity": {
|
||||
"name": "Entity"
|
||||
},
|
||||
"button": {
|
||||
"name": "Button",
|
||||
"description": "The Button card allows you to add buttons to perform tasks."
|
||||
@ -2062,6 +2065,7 @@
|
||||
},
|
||||
"generic": {
|
||||
"aspect_ratio": "Aspect Ratio",
|
||||
"attribute": "Attribute",
|
||||
"camera_image": "Camera Entity",
|
||||
"camera_view": "Camera View",
|
||||
"entities": "Entities",
|
||||
|
Loading…
x
Reference in New Issue
Block a user