diff --git a/src/common/const.ts b/src/common/const.ts index 0a1943b601..1222fa9ebf 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -144,6 +144,7 @@ export const FIXED_DEVICE_CLASS_ICONS = { /** Domains that have a state card. */ export const DOMAINS_WITH_CARD = [ + "button", "climate", "cover", "configurator", diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 1836d5d6a5..9e4d8d87e4 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -116,6 +116,11 @@ export const computeStateDisplay = ( return formatNumber(compareState, locale); } + // state of button is a timestamp + if (domain === "button") { + return formatDateTime(new Date(compareState), locale); + } + return ( // Return device class translation (stateObj.attributes.device_class && diff --git a/src/panels/lovelace/create-element/create-row-element.ts b/src/panels/lovelace/create-element/create-row-element.ts index f1b63c717c..b4c9afc556 100644 --- a/src/panels/lovelace/create-element/create-row-element.ts +++ b/src/panels/lovelace/create-element/create-row-element.ts @@ -24,6 +24,7 @@ const ALWAYS_LOADED_TYPES = new Set([ "call-service", ]); const LAZY_LOAD_TYPES = { + "button-entity": () => import("../entity-rows/hui-button-entity-row"), "climate-entity": () => import("../entity-rows/hui-climate-entity-row"), "cover-entity": () => import("../entity-rows/hui-cover-entity-row"), "group-entity": () => import("../entity-rows/hui-group-entity-row"), @@ -53,6 +54,7 @@ const DOMAIN_TO_ELEMENT_TYPE = { _domain_not_found: "text", alert: "toggle", automation: "toggle", + button: "button", climate: "climate", cover: "cover", fan: "toggle", diff --git a/src/panels/lovelace/entity-rows/hui-button-entity-row.ts b/src/panels/lovelace/entity-rows/hui-button-entity-row.ts new file mode 100644 index 0000000000..1184fb67ce --- /dev/null +++ b/src/panels/lovelace/entity-rows/hui-button-entity-row.ts @@ -0,0 +1,82 @@ +import "@material/mwc-button/mwc-button"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { UNAVAILABLE } from "../../../data/entity"; +import { HomeAssistant } from "../../../types"; +import { hasConfigOrEntityChanged } from "../common/has-changed"; +import "../components/hui-generic-entity-row"; +import { createEntityNotFoundWarning } from "../components/hui-warning"; +import { ActionRowConfig, LovelaceRow } from "./types"; + +@customElement("hui-button-entity-row") +class HuiButtonEntityRow extends LitElement implements LovelaceRow { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _config?: ActionRowConfig; + + public setConfig(config: ActionRowConfig): void { + if (!config) { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + protected shouldUpdate(changedProps: PropertyValues): boolean { + return hasConfigOrEntityChanged(this, changedProps); + } + + 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)} + + `; + } + + return html` + + + ${this.hass.localize("ui.card.button.press")} + + + `; + } + + static get styles(): CSSResultGroup { + return css` + mwc-button:last-child { + margin-right: -0.57em; + } + `; + } + + private _pressButton(ev): void { + ev.stopPropagation(); + this.hass.callService("button", "press", { + entity_id: this._config!.entity, + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-button-entity-row": HuiButtonEntityRow; + } +} diff --git a/src/state-summary/state-card-button.ts b/src/state-summary/state-card-button.ts new file mode 100644 index 0000000000..3ce46e43ff --- /dev/null +++ b/src/state-summary/state-card-button.ts @@ -0,0 +1,54 @@ +import "@material/mwc-button"; +import { HassEntity } from "home-assistant-js-websocket"; +import { CSSResultGroup, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../components/entity/ha-entity-toggle"; +import "../components/entity/state-info"; +import { UNAVAILABLE } from "../data/entity"; +import { haStyle } from "../resources/styles"; +import { HomeAssistant } from "../types"; + +@customElement("state-card-button") +export class StateCardButton extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public stateObj!: HassEntity; + + @property({ type: Boolean }) public inDialog = false; + + protected render() { + const stateObj = this.stateObj; + return html` +
+ + + ${this.hass.localize("ui.card.button.press")} + +
+ `; + } + + private _pressButton(ev: Event) { + ev.stopPropagation(); + this.hass.callService("button", "press", { + entity_id: this.stateObj.entity_id, + }); + } + + static get styles(): CSSResultGroup { + return haStyle; + } +} + +declare global { + interface HTMLElementTagNameMap { + "state-card-button": StateCardButton; + } +} diff --git a/src/state-summary/state-card-content.js b/src/state-summary/state-card-content.js index f3ee74bbf3..f5c1a34a0b 100644 --- a/src/state-summary/state-card-content.js +++ b/src/state-summary/state-card-content.js @@ -2,6 +2,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import dynamicContentUpdater from "../common/dom/dynamic_content_updater"; import { stateCardType } from "../common/entity/state_card_type"; +import "./state-card-button"; import "./state-card-climate"; import "./state-card-configurator"; import "./state-card-cover"; diff --git a/src/translations/en.json b/src/translations/en.json index e0ff0ec0a4..132847b37d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -120,6 +120,9 @@ "last_triggered": "Last triggered", "trigger": "Run Actions" }, + "button": { + "press": "Press" + }, "camera": { "not_available": "Image not available" },