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:
Zack Arnett 2020-03-30 19:49:02 -04:00 committed by GitHub
parent 5daa6dbd25
commit 158eddfd44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 443 additions and 0 deletions

View 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;
}
}

View File

@ -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";

View File

@ -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",

View File

@ -28,6 +28,7 @@ const previewCards: string[] = [
"alarm-panel",
"button",
"entities",
"entity",
"gauge",
"glance",
"history-graph",

View File

@ -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;
}
}

View File

@ -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",