From 326d912b84daea14c49e0a16d2326a9a3d2da802 Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Tue, 16 Nov 2021 19:34:31 -0600 Subject: [PATCH] First commit --- src/panels/lovelace/cards/hui-person-card.ts | 200 ++++++++++++++++++ src/panels/lovelace/cards/types.ts | 4 + .../create-element/create-card-element.ts | 1 + .../config-elements/hui-person-card-editor.ts | 110 ++++++++++ src/panels/lovelace/editor/lovelace-cards.ts | 4 + 5 files changed, 319 insertions(+) create mode 100644 src/panels/lovelace/cards/hui-person-card.ts create mode 100644 src/panels/lovelace/editor/config-elements/hui-person-card-editor.ts diff --git a/src/panels/lovelace/cards/hui-person-card.ts b/src/panels/lovelace/cards/hui-person-card.ts new file mode 100644 index 0000000000..fbdef6f3e5 --- /dev/null +++ b/src/panels/lovelace/cards/hui-person-card.ts @@ -0,0 +1,200 @@ +import { mdiBattery70 } from "@mdi/js"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; +import { computeStateDisplay } from "../../../common/entity/compute_state_display"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import "../../../components/ha-card"; +import "../../../components/ha-svg-icon"; +import { computeUserInitials } from "../../../data/user"; +import { HomeAssistant } from "../../../types"; +// eslint-disable-next-line import/no-duplicates +import "../components/hui-warning"; +// eslint-disable-next-line import/no-duplicates +import { createEntityNotFoundWarning } from "../components/hui-warning"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; +import { PersonCardConfig } from "./types"; + +@customElement("hui-person-card") +export class HuiPersonCard extends LitElement implements LovelaceCard { + public static async getConfigElement(): Promise { + await import("../editor/config-elements/hui-person-card-editor"); + return document.createElement("hui-person-card-editor"); + } + + public static getStubConfig(): PersonCardConfig { + return { + type: "person", + }; + } + + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() protected _config?: PersonCardConfig; + + public getCardSize(): number { + return 5; + } + + public setConfig(config: PersonCardConfig): void { + if (!config || !config.entity) { + throw new Error("Entity required"); + } + + this._config = config; + } + + protected shouldUpdate(changedProps: PropertyValues): boolean { + if (changedProps.size === 1 && changedProps.has("hass")) { + return !changedProps.get("hass"); + } + return true; + } + + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + if (!this._config || !this.hass) { + return; + } + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; + const oldConfig = changedProps.get("_config") as + | PersonCardConfig + | undefined; + + if ( + !oldHass || + !oldConfig || + oldHass.themes !== this.hass.themes || + oldConfig.theme !== this._config.theme + ) { + applyThemesOnElement(this, this.hass.themes, this._config.theme); + } + } + + protected render(): TemplateResult { + if (!this._config || !this.hass) { + return html``; + } + + const stateObj = this.hass.states[this._config.entity!]; + + if (!stateObj) { + return html` + + ${createEntityNotFoundWarning(this.hass, this._config.entity!)} + + `; + } + + let backgroundColor: string | undefined; + let foregroundColor = ""; + let image = stateObj.attributes.entity_picture; + let batteryPercent: number | undefined; + + if (stateObj.attributes.source) { + batteryPercent = + this.hass.states[stateObj.attributes.source].attributes.battery; + } + + if (!image) { + if (backgroundColor === undefined) { + const computedStyle = getComputedStyle(document.body); + backgroundColor = encodeURIComponent( + computedStyle.getPropertyValue("--light-primary-color").trim() + ); + foregroundColor = encodeURIComponent( + ( + computedStyle.getPropertyValue("--text-light-primary-color") || + computedStyle.getPropertyValue("--primary-text-color") + ).trim() + ); + } + const initials = computeUserInitials( + stateObj.attributes.friendly_name || "" + ); + image = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50' width='50' height='50' style='background-color:${backgroundColor}'%3E%3Cg%3E%3Ctext font-family='roboto' x='50%25' y='50%25' text-anchor='middle' stroke='${foregroundColor}' font-size='1.3em' dy='.3em'%3E${initials}%3C/text%3E%3C/g%3E%3C/svg%3E`; + } + + return html` + + +
${computeStateName(stateObj)}
+
+ ${computeStateDisplay( + this.hass!.localize, + stateObj, + this.hass.locale + )} +
+
+ ${batteryPercent} % +
+
+ `; + } + + static get styles(): CSSResultGroup { + return css` + ha-card { + overflow: hidden; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 16px; + } + + ha-card.clickable { + cursor: pointer; + } + + hui-image { + display: block; + width: 100%; + max-width: 250px; + border-radius: 50%; + overflow: hidden; + } + + .name { + font-size: 24px; + padding-top: 16px; + font-weight: 500; + text-align: center; + } + + .state { + color: var(--disabled-text-color); + padding-top: 24px; + } + + .battery { + border: 1px solid var(--divider-color); + border-radius: 4px; + padding: 4px; + font-size: 16px; + background-color: var( + --ha-card-background, + var(--card-background-color, white) + ); + display: flex; + justify-content: center; + align-items: center; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-person-card": HuiPersonCard; + } +} diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 80a70b4378..f735246fb7 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -288,6 +288,10 @@ export interface PictureCardConfig extends LovelaceCardConfig { double_tap_action?: ActionConfig; theme?: string; } +export interface PersonCardConfig extends LovelaceCardConfig { + entity?: string; + theme?: string; +} export interface PictureElementsCardConfig extends LovelaceCardConfig { title?: string; diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index 6c8c470e35..977514530a 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -59,6 +59,7 @@ const LAZY_LOAD_TYPES = { "entity-filter": () => import("../cards/hui-entity-filter-card"), humidifier: () => import("../cards/hui-humidifier-card"), "media-control": () => import("../cards/hui-media-control-card"), + person: () => import("../cards/hui-person-card"), "picture-elements": () => import("../cards/hui-picture-elements-card"), "picture-entity": () => import("../cards/hui-picture-entity-card"), "picture-glance": () => import("../cards/hui-picture-glance-card"), diff --git a/src/panels/lovelace/editor/config-elements/hui-person-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-person-card-editor.ts new file mode 100644 index 0000000000..826c533d69 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-person-card-editor.ts @@ -0,0 +1,110 @@ +import "@polymer/paper-input/paper-input"; +import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { assert, assign, object, optional, string } from "superstruct"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-icon-picker"; +import { HomeAssistant } from "../../../../types"; +import { PersonCardConfig } from "../../cards/types"; +import "../../components/hui-action-editor"; +import "../../components/hui-entity-editor"; +import "../../components/hui-theme-select-editor"; +import { LovelaceCardEditor } from "../../types"; +import { baseLovelaceCardConfig } from "../structs/base-card-struct"; +import { EditorTarget } from "../types"; +import { configElementStyle } from "./config-elements-style"; + +const cardConfigStruct = assign( + baseLovelaceCardConfig, + object({ + entity: optional(string()), + theme: optional(string()), + }) +); + +@customElement("hui-person-card-editor") +export class HuiPersonCardEditor + extends LitElement + implements LovelaceCardEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: PersonCardConfig; + + public setConfig(config: PersonCardConfig): void { + assert(config, cardConfigStruct); + this._config = config; + } + + get _theme(): string { + return this._config!.theme || ""; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + return html` +
+ + + +
+ `; + } + + private _valueChanged(ev: CustomEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + const value = ev.detail.value; + + if (this[`_${target.configValue}`] === value) { + return; + } + if (target.configValue) { + if (value !== false && !value) { + this._config = { ...this._config }; + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: value, + }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } + + static get styles(): CSSResultGroup { + return configElementStyle; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-person-card-editor": HuiPersonCardEditor; + } +} diff --git a/src/panels/lovelace/editor/lovelace-cards.ts b/src/panels/lovelace/editor/lovelace-cards.ts index 2a9beb0371..28920d2acc 100644 --- a/src/panels/lovelace/editor/lovelace-cards.ts +++ b/src/panels/lovelace/editor/lovelace-cards.ts @@ -57,6 +57,10 @@ export const coreCards: Card[] = [ type: "media-control", showElement: true, }, + { + type: "person", + showElement: true, + }, { type: "picture", showElement: true,