From ec5d7508c999944f461f2140358bab1405bf4934 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sun, 31 May 2020 22:35:58 +0300 Subject: [PATCH 01/24] Add humidifier entity integration --- src/common/const.ts | 1 + src/common/entity/domain_icon.ts | 1 + src/common/style/icon_color_css.ts | 1 + src/components/ha-humidifier-control.js | 142 +++++++ src/components/ha-humidifier-state.js | 83 ++++ src/components/state-history-chart-line.js | 22 + src/data/history.ts | 5 +- src/data/humidifier.ts | 19 + .../more-info/controls/more-info-content.ts | 1 + .../controls/more-info-humidifier.ts | 219 ++++++++++ .../lovelace/cards/hui-humidifier-card.ts | 397 ++++++++++++++++++ src/panels/lovelace/cards/types.ts | 6 + .../common/generate-lovelace-config.ts | 7 + .../create-element/create-card-element.ts | 2 + .../create-element/create-row-element.ts | 2 + .../hui-humidifier-card-editor.ts | 117 ++++++ src/panels/lovelace/editor/lovelace-cards.ts | 4 + .../entity-rows/hui-humidifier-entity-row.ts | 74 ++++ src/translations/en.json | 22 + src/util/hass-attributes-util.js | 3 +- 20 files changed, 1126 insertions(+), 2 deletions(-) create mode 100644 src/components/ha-humidifier-control.js create mode 100644 src/components/ha-humidifier-state.js create mode 100644 src/data/humidifier.ts create mode 100644 src/dialogs/more-info/controls/more-info-humidifier.ts create mode 100644 src/panels/lovelace/cards/hui-humidifier-card.ts create mode 100644 src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts create mode 100644 src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts diff --git a/src/common/const.ts b/src/common/const.ts index 503e866c4e..c1398fce5f 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -37,6 +37,7 @@ export const DOMAINS_WITH_MORE_INFO = [ "fan", "group", "history_graph", + "humidifier", "input_datetime", "light", "lock", diff --git a/src/common/entity/domain_icon.ts b/src/common/entity/domain_icon.ts index cf0617923b..632473bafd 100644 --- a/src/common/entity/domain_icon.ts +++ b/src/common/entity/domain_icon.ts @@ -22,6 +22,7 @@ const fixedIcons = { history_graph: "hass:chart-line", homeassistant: "hass:home-assistant", homekit: "hass:home-automation", + humidifier: "hass:air-humidifier", image_processing: "hass:image-filter-frames", input_boolean: "hass:toggle-switch-outline", input_datetime: "hass:calendar-clock", diff --git a/src/common/style/icon_color_css.ts b/src/common/style/icon_color_css.ts index 37af74d500..382745c298 100644 --- a/src/common/style/icon_color_css.ts +++ b/src/common/style/icon_color_css.ts @@ -8,6 +8,7 @@ export const iconColorCSS = css` ha-icon[data-domain="camera"][data-state="streaming"], ha-icon[data-domain="cover"][data-state="open"], ha-icon[data-domain="fan"][data-state="on"], + ha-icon[data-domain="humidifier"][data-state="on"], ha-icon[data-domain="light"][data-state="on"], ha-icon[data-domain="input_boolean"][data-state="on"], ha-icon[data-domain="lock"][data-state="unlocked"], diff --git a/src/components/ha-humidifier-control.js b/src/components/ha-humidifier-control.js new file mode 100644 index 0000000000..6746e61843 --- /dev/null +++ b/src/components/ha-humidifier-control.js @@ -0,0 +1,142 @@ +import "@polymer/iron-flex-layout/iron-flex-layout-classes"; +import "./ha-icon-button"; +import { html } from "@polymer/polymer/lib/utils/html-tag"; +/* eslint-plugin-disable lit */ +import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { EventsMixin } from "../mixins/events-mixin"; + +/* + * @appliesMixin EventsMixin + */ +class HaHumidifierControl extends EventsMixin(PolymerElement) { + static get template() { + return html` + + + + +
[[value]] [[units]]
+
+
+ +
+
+ +
+
+ `; + } + + static get properties() { + return { + value: { + type: Number, + observer: "valueChanged", + }, + units: { + type: String, + }, + min: { + type: Number, + }, + max: { + type: Number, + }, + step: { + type: Number, + value: 1, + }, + }; + } + + humidityStateInFlux(inFlux) { + this.$.humidity.classList.toggle("in-flux", inFlux); + } + + _round(val) { + // round value to precision derived from step + // insired by https://github.com/soundar24/roundSlider/blob/master/src/roundslider.js + const s = this.step.toString().split("."); + return s[1] ? parseFloat(val.toFixed(s[1].length)) : Math.round(val); + } + + incrementValue() { + const newval = this._round(this.value + this.step); + if (this.value < this.max) { + this.last_changed = Date.now(); + this.humidityStateInFlux(true); + } + if (newval <= this.max) { + // If no initial target_temp + // this forces control to start + // from the min configured instead of 0 + if (newval <= this.min) { + this.value = this.min; + } else { + this.value = newval; + } + } else { + this.value = this.max; + } + } + + decrementValue() { + const newval = this._round(this.value - this.step); + if (this.value > this.min) { + this.last_changed = Date.now(); + this.humidityStateInFlux(true); + } + if (newval >= this.min) { + this.value = newval; + } else { + this.value = this.min; + } + } + + valueChanged() { + // when the last_changed timestamp is changed, + // trigger a potential event fire in + // the future, as long as last changed is far enough in the + // past. + if (this.last_changed) { + window.setTimeout(() => { + const now = Date.now(); + if (now - this.last_changed >= 2000) { + this.fire("change"); + this.humidityStateInFlux(false); + this.last_changed = null; + } + }, 2010); + } + } +} + +customElements.define("ha-humidifier-control", HaHumidifierControl); diff --git a/src/components/ha-humidifier-state.js b/src/components/ha-humidifier-state.js new file mode 100644 index 0000000000..2731b6451e --- /dev/null +++ b/src/components/ha-humidifier-state.js @@ -0,0 +1,83 @@ +import { html } from "@polymer/polymer/lib/utils/html-tag"; +/* eslint-plugin-disable lit */ +import { PolymerElement } from "@polymer/polymer/polymer-element"; +import LocalizeMixin from "../mixins/localize-mixin"; + +/* + * @appliesMixin LocalizeMixin + */ +class HaHumidifierState extends LocalizeMixin(PolymerElement) { + static get template() { + return html` + + +
+ +
[[computeTarget(stateObj.attributes.humidity)]]
+
+ `; + } + + static get properties() { + return { + stateObj: Object, + }; + } + + computeTarget(humidity) { + if (humidity != null) { + return `${humidity} %`; + } + + return ""; + } + + _hasKnownState(state) { + return state !== "unknown"; + } + + _localizeState(localize, state) { + return localize(`state.default.${state}`) || state; + } + + _localizeMode(localize, mode) { + return localize(`state_attributes.humidifier.mode.${mode}`) || mode; + } + + _renderMode(attributes) { + return attributes.mode; + } +} +customElements.define("ha-humidifier-state", HaHumidifierState); diff --git a/src/components/state-history-chart-line.js b/src/components/state-history-chart-line.js index 4cc1321475..3a857b8663 100644 --- a/src/components/state-history-chart-line.js +++ b/src/components/state-history-chart-line.js @@ -262,6 +262,28 @@ class StateHistoryChartLine extends LocalizeMixin(PolymerElement) { pushData(new Date(state.last_changed), series); } }); + } else if (domain === "humidifier") { + addColumn( + `${this.hass.localize( + "ui.card.humidifier.target_humidity_entity", + "name", + name + )}`, + true + ); + addColumn( + `${this.hass.localize("ui.card.humidifier.on_entity", "name", name)}`, + true, + true + ); + + states.states.forEach((state) => { + if (!state.attributes) return; + const target = safeParseFloat(state.attributes.humidity); + const series = [target]; + series.push(state.state === "on" ? target : null); + pushData(new Date(state.last_changed), series); + }); } else { // Only disable interpolation for sensors const isStep = domain === "sensor"; diff --git a/src/data/history.ts b/src/data/history.ts index ee244a2e7b..0856e9eff3 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -5,13 +5,14 @@ import { computeStateName } from "../common/entity/compute_state_name"; import { LocalizeFunc } from "../common/translations/localize"; import { HomeAssistant } from "../types"; -const DOMAINS_USE_LAST_UPDATED = ["climate", "water_heater"]; +const DOMAINS_USE_LAST_UPDATED = ["climate", "humidifier", "water_heater"]; const LINE_ATTRIBUTES_TO_KEEP = [ "temperature", "current_temperature", "target_temp_low", "target_temp_high", "hvac_action", + "humidity", ]; export interface LineChartState { @@ -221,6 +222,8 @@ export const computeHistory = ( unit = hass.config.unit_system.temperature; } else if (computeStateDomain(stateInfo[0]) === "water_heater") { unit = hass.config.unit_system.temperature; + } else if (computeStateDomain(stateInfo[0]) === "humidifier") { + unit = "%"; } if (!unit) { diff --git a/src/data/humidifier.ts b/src/data/humidifier.ts new file mode 100644 index 0000000000..968aad1dcd --- /dev/null +++ b/src/data/humidifier.ts @@ -0,0 +1,19 @@ +import { + HassEntityAttributeBase, + HassEntityBase, +} from "home-assistant-js-websocket"; + +export type HumidifierEntity = HassEntityBase & { + attributes: HassEntityAttributeBase & { + humidity?: number; + min_humidity?: number; + max_humidity?: number; + mode?: string; + available_modes?: string[]; + }; +}; + +export const HUMIDIFIER_SUPPORT_MODES = 1; + +export const HUMIDIFIER_DEVICE_CLASS_HUMIDIFIER = "humidifier"; +export const HUMIDIFIER_DEVICE_CLASS_DEHUMIDIFIER = "dehumidifier"; diff --git a/src/dialogs/more-info/controls/more-info-content.ts b/src/dialogs/more-info/controls/more-info-content.ts index 896d1c576b..2f7ed3dbf7 100644 --- a/src/dialogs/more-info/controls/more-info-content.ts +++ b/src/dialogs/more-info/controls/more-info-content.ts @@ -14,6 +14,7 @@ import "./more-info-default"; import "./more-info-fan"; import "./more-info-group"; import "./more-info-history_graph"; +import "./more-info-humidifier"; import "./more-info-input_datetime"; import "./more-info-light"; import "./more-info-lock"; diff --git a/src/dialogs/more-info/controls/more-info-humidifier.ts b/src/dialogs/more-info/controls/more-info-humidifier.ts new file mode 100644 index 0000000000..e9fe98991b --- /dev/null +++ b/src/dialogs/more-info/controls/more-info-humidifier.ts @@ -0,0 +1,219 @@ +import "@polymer/iron-flex-layout/iron-flex-layout-classes"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-listbox/paper-listbox"; +import { + css, + CSSResult, + html, + LitElement, + property, + PropertyValues, + TemplateResult, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { supportsFeature } from "../../../common/entity/supports-feature"; +import { computeRTLDirection } from "../../../common/util/compute_rtl"; +import "../../../components/ha-humidifier-control"; +import "../../../components/ha-paper-dropdown-menu"; +import "../../../components/ha-paper-slider"; +import "../../../components/ha-switch"; +import { + HumidifierEntity, + HUMIDIFIER_SUPPORT_MODES, +} from "../../../data/humidifier"; +import { HomeAssistant } from "../../../types"; + +class MoreInfoHumidifier extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public stateObj?: HumidifierEntity; + + private _resizeDebounce?: number; + + protected render(): TemplateResult { + if (!this.stateObj) { + return html``; + } + + const hass = this.hass; + const stateObj = this.stateObj; + + const supportModes = supportsFeature(stateObj, HUMIDIFIER_SUPPORT_MODES); + + const rtlDirection = computeRTLDirection(hass); + + return html` +
+
+
${hass.localize("ui.card.humidifier.humidity")}
+
+
+ ${stateObj.attributes.humidity} % +
+ + +
+
+ + ${supportModes + ? html` +
+ + + ${stateObj.attributes.available_modes!.map( + (mode) => html` + + ${hass.localize( + `state_attributes.humidifier.mode.${mode}` + ) || mode} + + ` + )} + + +
+ ` + : ""} +
+ `; + } + + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + if (!changedProps.has("stateObj") || !this.stateObj) { + return; + } + + if (this._resizeDebounce) { + clearTimeout(this._resizeDebounce); + } + this._resizeDebounce = window.setTimeout(() => { + fireEvent(this, "iron-resize"); + this._resizeDebounce = undefined; + }, 500); + } + + private _targetHumiditySliderChanged(ev) { + const newVal = ev.target.value; + this._callServiceHelper( + this.stateObj!.attributes.humidity, + newVal, + "set_humidity", + { humidity: newVal } + ); + } + + private _handleModeChanged(ev) { + const newVal = ev.detail.value || null; + this._callServiceHelper( + this.stateObj!.attributes.mode, + newVal, + "set_mode", + { mode: newVal } + ); + } + + private async _callServiceHelper( + oldVal: unknown, + newVal: unknown, + service: string, + data: { + entity_id?: string; + [key: string]: unknown; + } + ) { + if (oldVal === newVal) { + return; + } + + data.entity_id = this.stateObj!.entity_id; + const curState = this.stateObj; + + await this.hass.callService("humidifier", service, data); + + // We reset stateObj to re-sync the inputs with the state. It will be out + // of sync if our service call did not result in the entity to be turned + // on. Since the state is not changing, the resync is not called automatic. + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // No need to resync if we received a new state. + if (this.stateObj !== curState) { + return; + } + + this.stateObj = undefined; + await this.updateComplete; + // Only restore if not set yet by a state change + if (this.stateObj === undefined) { + this.stateObj = curState; + } + } + + static get styles(): CSSResult { + return css` + :host { + color: var(--primary-text-color); + } + + ha-paper-dropdown-menu { + width: 100%; + } + + paper-item { + cursor: pointer; + } + + ha-paper-slider { + width: 100%; + } + + .container-humidity .single-row { + display: flex; + height: 50px; + } + + .target-humidity { + width: 90px; + font-size: 200%; + margin: auto; + direction: ltr; + } + + .humidity { + --paper-slider-active-color: var(--paper-blue-400); + --paper-slider-secondary-color: var(--paper-blue-400); + } + + .single-row { + padding: 8px 0; + } + `; + } +} + +customElements.define("more-info-humidifier", MoreInfoHumidifier); diff --git a/src/panels/lovelace/cards/hui-humidifier-card.ts b/src/panels/lovelace/cards/hui-humidifier-card.ts new file mode 100644 index 0000000000..db3000adcc --- /dev/null +++ b/src/panels/lovelace/cards/hui-humidifier-card.ts @@ -0,0 +1,397 @@ +import "../../../components/ha-icon-button"; +import "@thomasloven/round-slider"; +import { HassEntity } from "home-assistant-js-websocket"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + PropertyValues, + svg, + TemplateResult, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; +import { UNIT_F } from "../../../common/const"; +import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import { computeRTLDirection } from "../../../common/util/compute_rtl"; +import "../../../components/ha-card"; +import { HumidifierEntity } from "../../../data/humidifier"; +import { UNAVAILABLE } from "../../../data/entity"; +import { HomeAssistant } from "../../../types"; +import { actionHandler } from "../common/directives/action-handler-directive"; +import { findEntities } from "../common/find-entites"; +import { hasConfigOrEntityChanged } from "../common/has-changed"; +import { createEntityNotFoundWarning } from "../components/hui-warning"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; +import { HumidifierCardConfig } from "./types"; + +@customElement("hui-humidifier-card") +export class HuiHumidifierCard extends LitElement implements LovelaceCard { + public static async getConfigElement(): Promise { + await import( + /* webpackChunkName: "hui-humidifier-card-editor" */ "../editor/config-elements/hui-humidifier-card-editor" + ); + return document.createElement("hui-humidifier-card-editor"); + } + + public static getStubConfig( + hass: HomeAssistant, + entities: string[], + entitiesFallback: string[] + ): HumidifierCardConfig { + const includeDomains = ["humidifier"]; + const maxEntities = 1; + const foundEntities = findEntities( + hass, + maxEntities, + entities, + entitiesFallback, + includeDomains + ); + + return { type: "humidifier", entity: foundEntities[0] || "" }; + } + + @property() public hass?: HomeAssistant; + + @property() private _config?: HumidifierCardConfig; + + @property() private _setHum?: number | number[]; + + public getCardSize(): number { + return 5; + } + + public setConfig(config: HumidifierCardConfig): void { + if (!config.entity || config.entity.split(".")[0] !== "humidifier") { + throw new Error("Specify an entity from within the humidifier domain."); + } + + this._config = config; + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + const stateObj = this.hass.states[this._config.entity] as HumidifierEntity; + + if (!stateObj) { + return html` + + ${createEntityNotFoundWarning(this.hass, this._config.entity)} + + `; + } + + const name = + this._config!.name || + computeStateName(this.hass!.states[this._config!.entity]); + const targetHumidity = + stateObj.attributes.humidity !== null && + Number.isFinite(Number(stateObj.attributes.humidity)) + ? stateObj.attributes.humidity + : stateObj.attributes.min_humidity; + + const rtlDirection = computeRTLDirection(this.hass); + + const slider = + stateObj.state === UNAVAILABLE + ? html` ` + : html` + + `; + + const setValues = svg` + + + ${ + stateObj.state === UNAVAILABLE + ? this.hass.localize("state.default.unavailable") + : this._setHum === undefined || this._setHum === null + ? "" + : svg` + ${this._setHum.toFixed()} + ` + } + + % + + + + + + + ${this.hass!.localize(`state.default.${stateObj.state}`)} + ${ + stateObj.attributes.mode + ? html` + - + ${this.hass!.localize( + `state_attributes.humidifier.mode.${stateObj.attributes.mode}` + ) || stateObj.attributes.mode} + ` + : "" + } + + + + `; + + return html` + + + +
+
+
+ ${slider} +
+
+ ${setValues} +
+
+
+
+
+ ${name} +
+
+
+ `; + } + + protected shouldUpdate(changedProps: PropertyValues): boolean { + return hasConfigOrEntityChanged(this, changedProps); + } + + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + + if ( + !this._config || + !this.hass || + (!changedProps.has("hass") && !changedProps.has("_config")) + ) { + return; + } + + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; + const oldConfig = changedProps.get("_config") as + | HumidifierCardConfig + | undefined; + + if ( + !oldHass || + !oldConfig || + oldHass.themes !== this.hass.themes || + oldConfig.theme !== this._config.theme + ) { + applyThemesOnElement(this, this.hass.themes, this._config.theme); + } + + const stateObj = this.hass.states[this._config.entity]; + if (!stateObj) { + return; + } + + if (!oldHass || oldHass.states[this._config.entity] !== stateObj) { + this._setHum = this._getSetHum(stateObj); + this._rescale_svg(); + } + } + + private _rescale_svg() { + // Set the viewbox of the SVG containing the set humidity to perfectly + // fit the text + // That way it will auto-scale correctly + // This is not done to the SVG containing the current humidity, because + // it should not be centered on the text, but only on the value + if (this.shadowRoot && this.shadowRoot.querySelector("ha-card")) { + (this.shadowRoot.querySelector( + "ha-card" + ) as LitElement).updateComplete.then(() => { + const svgRoot = this.shadowRoot!.querySelector("#set-values"); + const box = svgRoot!.querySelector("g")!.getBBox(); + svgRoot!.setAttribute( + "viewBox", + `${box!.x} ${box!.y} ${box!.width} ${box!.height}` + ); + svgRoot!.setAttribute("width", `${box!.width}`); + svgRoot!.setAttribute("height", `${box!.height}`); + }); + } + } + + private _getSetHum( + stateObj: HassEntity + ): undefined | number | [number, number] { + if (stateObj.state === UNAVAILABLE) { + return undefined; + } + + return stateObj.attributes.humidity; + } + + private _dragEvent(e): void { + this._setHum = e.detail.value; + } + + private _setHumidity(e): void { + this.hass!.callService("humidifier", "set_humidity", { + entity_id: this._config!.entity, + humidity: e.detail.value, + }); + } + + private _handleMoreInfo() { + fireEvent(this, "hass-more-info", { + entityId: this._config!.entity, + }); + } + + static get styles(): CSSResult { + return css` + :host { + display: block; + } + + ha-card { + height: 100%; + position: relative; + overflow: hidden; + --name-font-size: 1.2rem; + --brightness-font-size: 1.2rem; + --rail-border-color: transparent; + } + + .more-info { + position: absolute; + cursor: pointer; + top: 0; + right: 0; + border-radius: 100%; + color: var(--secondary-text-color); + z-index: 25; + } + + .content { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + } + + #controls { + display: flex; + justify-content: center; + padding: 16px; + position: relative; + } + + #slider { + height: 100%; + width: 100%; + position: relative; + max-width: 250px; + min-width: 100px; + } + + round-slider { + --round-slider-path-color: var(--disabled-text-color); + --round-slider-bar-color: var(--mode-color); + padding-bottom: 10%; + } + + #slider-center { + position: absolute; + width: calc(100% - 40px); + height: calc(100% - 40px); + box-sizing: border-box; + border-radius: 100%; + left: 20px; + top: 20px; + text-align: center; + overflow-wrap: break-word; + pointer-events: none; + } + + #humidity { + position: absolute; + transform: translate(-50%, -50%); + width: 100%; + height: 50%; + top: 45%; + left: 50%; + } + + #set-values { + max-width: 80%; + transform: translate(0, -50%); + font-size: 20px; + } + + #set-mode { + fill: var(--secondary-text-color); + font-size: 16px; + } + + #info { + display: flex-vertical; + justify-content: center; + text-align: center; + padding: 16px; + margin-top: -60px; + font-size: var(--name-font-size); + } + + #modes > * { + color: var(--disabled-text-color); + cursor: pointer; + display: inline-block; + } + + #modes .selected-icon { + color: var(--mode-color); + } + + text { + fill: var(--primary-text-color); + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-humidifier-card": HuiHumidifierCard; + } +} diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 3ce3342309..c003fb32ab 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -133,6 +133,12 @@ export interface GlanceCardConfig extends LovelaceCardConfig { state_color?: boolean; } +export interface HumidifierCardConfig extends LovelaceCardConfig { + entity: string; + theme?: string; + name?: string; +} + export interface IframeCardConfig extends LovelaceCardConfig { aspect_ratio?: string; title?: string; diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index 8941ed45d2..9b3f1c830b 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -37,6 +37,7 @@ import { GroupEntity, HomeAssistant } from "../../../types"; import { AlarmPanelCardConfig, EntitiesCardConfig, + HumidifierCardConfig, LightCardConfig, PictureEntityCardConfig, ThermostatCardConfig, @@ -150,6 +151,12 @@ export const computeCards = ( refresh_interval: stateObj.attributes.refresh, }; cards.push(cardConfig); + } else if (domain === "humidifier") { + const cardConfig: HumidifierCardConfig = { + type: "humidifier", + entity: entityId, + }; + cards.push(cardConfig); } else if (domain === "light" && single) { const cardConfig: LightCardConfig = { type: "light", diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index cb5748eecf..e06db4c345 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -6,6 +6,7 @@ import "../cards/hui-entity-card"; import "../cards/hui-glance-card"; import "../cards/hui-history-graph-card"; import "../cards/hui-horizontal-stack-card"; +import "../cards/hui-humidifier-card"; import "../cards/hui-light-card"; import "../cards/hui-sensor-card"; import "../cards/hui-thermostat-card"; @@ -24,6 +25,7 @@ const ALWAYS_LOADED_TYPES = new Set([ "glance", "history-graph", "horizontal-stack", + "humidifier", "light", "sensor", "thermostat", diff --git a/src/panels/lovelace/create-element/create-row-element.ts b/src/panels/lovelace/create-element/create-row-element.ts index 80533bf0e5..433b50e0a5 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 LAZY_LOAD_TYPES = { "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"), + "humidifier-entity": () => import("../entity-rows/hui-humidifier-entity-row"), "input-datetime-entity": () => import("../entity-rows/hui-input-datetime-entity-row"), "input-number-entity": () => @@ -51,6 +52,7 @@ const DOMAIN_TO_ELEMENT_TYPE = { cover: "cover", fan: "toggle", group: "group", + humidifier: "humidifier", input_boolean: "toggle", input_number: "input-number", input_select: "input-select", diff --git a/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts new file mode 100644 index 0000000000..b293953407 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-humidifier-card-editor.ts @@ -0,0 +1,117 @@ +import "@polymer/paper-input/paper-input"; +import { + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/entity/ha-entity-picker"; +import { HomeAssistant } from "../../../../types"; +import { HumidifierCardConfig } from "../../cards/types"; +import { struct } from "../../common/structs/struct"; +import "../../components/hui-theme-select-editor"; +import { LovelaceCardEditor } from "../../types"; +import { EditorTarget, EntitiesEditorEvent } from "../types"; +import { configElementStyle } from "./config-elements-style"; + +const cardConfigStruct = struct({ + type: "string", + entity: "string", + name: "string?", + theme: "string?", +}); + +const includeDomains = ["humidifier"]; + +@customElement("hui-humidifier-card-editor") +export class HuiHumidifierCardEditor extends LitElement + implements LovelaceCardEditor { + @property() public hass?: HomeAssistant; + + @property() private _config?: HumidifierCardConfig; + + public setConfig(config: HumidifierCardConfig): void { + config = cardConfigStruct(config); + this._config = config; + } + + get _entity(): string { + return this._config!.entity || ""; + } + + get _name(): string { + return this._config!.name || ""; + } + + get _theme(): string { + return this._config!.theme || ""; + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + return html` + ${configElementStyle} +
+ + + +
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this._config[target.configValue!]; + } else { + this._config = { ...this._config, [target.configValue!]: target.value }; + } + } + fireEvent(this, "config-changed", { config: this._config }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-humidifier-card-editor": HuiHumidifierCardEditor; + } +} diff --git a/src/panels/lovelace/editor/lovelace-cards.ts b/src/panels/lovelace/editor/lovelace-cards.ts index 210f3f834d..9d777fa942 100644 --- a/src/panels/lovelace/editor/lovelace-cards.ts +++ b/src/panels/lovelace/editor/lovelace-cards.ts @@ -29,6 +29,10 @@ export const coreCards: Card[] = [ type: "history-graph", showElement: true, }, + { + type: "humidifier", + showElement: true, + }, { type: "light", showElement: true, diff --git a/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts b/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts new file mode 100644 index 0000000000..9a0e97522a --- /dev/null +++ b/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts @@ -0,0 +1,74 @@ +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + PropertyValues, + TemplateResult, +} from "lit-element"; +import "../../../components/ha-humidifier-state"; +import { HomeAssistant } from "../../../types"; +import { hasConfigOrEntityChanged } from "../common/has-changed"; +import "../components/hui-generic-entity-row"; +import { createEntityNotFoundWarning } from "../components/hui-warning"; +import { EntityConfig, LovelaceRow } from "./types"; + +@customElement("hui-humidifier-entity-row") +class HuiHumidifierEntityRow extends LitElement implements LovelaceRow { + @property() public hass?: HomeAssistant; + + @property() private _config?: EntityConfig; + + public setConfig(config: EntityConfig): void { + if (!config || !config.entity) { + throw new Error("Invalid Configuration: 'entity' required"); + } + + this._config = config; + } + + protected shouldUpdate(changedProps: PropertyValues): boolean { + return hasConfigOrEntityChanged(this, changedProps); + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + const stateObj = this.hass.states[this._config.entity]; + + if (!stateObj) { + return html` + + ${createEntityNotFoundWarning(this.hass, this._config.entity)} + + `; + } + + return html` + + + + `; + } + + static get styles(): CSSResult { + return css` + ha-humidifier-state { + text-align: right; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-humidifier-entity-row": HuiHumidifierEntityRow; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 66b9e0441e..ab158f34c7 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -44,6 +44,19 @@ "idle": "Idle", "fan": "Fan" } + }, + "humidifier": { + "mode": { + "normal": "Normal", + "eco": "Eco", + "away": "Away", + "boost": "Boost", + "comfort": "Comfort", + "home": "Home", + "sleep": "Sleep", + "auto": "Auto", + "baby": "Baby" + } } }, "state_badge": { @@ -146,6 +159,11 @@ "forward": "Forward", "reverse": "Reverse" }, + "humidifier": { + "mode": "Mode", + "target_humidity_entity": "{name} target humidity", + "on_entity": "{name} on" + }, "light": { "brightness": "Brightness", "color_temperature": "Color temperature", @@ -1856,6 +1874,10 @@ "name": "Horizontal Stack", "description": "The Horizontal Stack card allows you to stack together multiple cards, so they always sit next to each other in the space of one column." }, + "humidifier": { + "name": "Humidifier", + "description": "The Humidifier card gives control of your humidifier entity. Allowing you to change the humidity and mode of the entity." + }, "iframe": { "name": "Webpage", "description": "The Webpage card allows you to embed your favorite webpage right into Home Assistant." diff --git a/src/util/hass-attributes-util.js b/src/util/hass-attributes-util.js index 05fa3ebb63..04f8ab2701 100644 --- a/src/util/hass-attributes-util.js +++ b/src/util/hass-attributes-util.js @@ -89,7 +89,7 @@ hassAttributeUtil.LOGIC_STATE_ATTRIBUTES = hassAttributeUtil.LOGIC_STATE_ATTRIBU type: "array", options: hassAttributeUtil.DOMAIN_DEVICE_CLASS, description: "Device class", - domains: ["binary_sensor", "cover", "sensor", "switch"], + domains: ["binary_sensor", "cover", "humidifier", "sensor", "switch"], }, hidden: { type: "boolean", description: "Hide from UI" }, assumed_state: { @@ -100,6 +100,7 @@ hassAttributeUtil.LOGIC_STATE_ATTRIBUTES = hassAttributeUtil.LOGIC_STATE_ATTRIBU "cover", "climate", "fan", + "humidifier", "group", "water_heater", ], From 6242997849e3b5f897935acb75dcf56827b68fd7 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sun, 31 May 2020 23:10:32 +0300 Subject: [PATCH 02/24] linter fixes --- src/components/ha-humidifier-control.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-humidifier-control.js b/src/components/ha-humidifier-control.js index 6746e61843..59d8ef1133 100644 --- a/src/components/ha-humidifier-control.js +++ b/src/components/ha-humidifier-control.js @@ -95,7 +95,7 @@ class HaHumidifierControl extends EventsMixin(PolymerElement) { this.humidityStateInFlux(true); } if (newval <= this.max) { - // If no initial target_temp + // If no initial humidity // this forces control to start // from the min configured instead of 0 if (newval <= this.min) { From 44023c3db7d6e113f83ed6ca627386f1ebc33fa7 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sun, 31 May 2020 23:22:03 +0300 Subject: [PATCH 03/24] linter fix --- src/panels/lovelace/cards/hui-humidifier-card.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/panels/lovelace/cards/hui-humidifier-card.ts b/src/panels/lovelace/cards/hui-humidifier-card.ts index db3000adcc..a7dccb4b1f 100644 --- a/src/panels/lovelace/cards/hui-humidifier-card.ts +++ b/src/panels/lovelace/cards/hui-humidifier-card.ts @@ -12,8 +12,6 @@ import { svg, TemplateResult, } from "lit-element"; -import { classMap } from "lit-html/directives/class-map"; -import { UNIT_F } from "../../../common/const"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; @@ -22,7 +20,6 @@ import "../../../components/ha-card"; import { HumidifierEntity } from "../../../data/humidifier"; import { UNAVAILABLE } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; -import { actionHandler } from "../common/directives/action-handler-directive"; import { findEntities } from "../common/find-entites"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import { createEntityNotFoundWarning } from "../components/hui-warning"; @@ -60,7 +57,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { @property() private _config?: HumidifierCardConfig; - @property() private _setHum?: number | number[]; + @property() private _setHum?: number; public getCardSize(): number { return 5; From fe63c12cd961cf2565c1c5dccf0850aac344cc44 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sun, 31 May 2020 23:27:37 +0300 Subject: [PATCH 04/24] linter fix --- src/panels/lovelace/cards/hui-humidifier-card.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/panels/lovelace/cards/hui-humidifier-card.ts b/src/panels/lovelace/cards/hui-humidifier-card.ts index a7dccb4b1f..1e4bb5268f 100644 --- a/src/panels/lovelace/cards/hui-humidifier-card.ts +++ b/src/panels/lovelace/cards/hui-humidifier-card.ts @@ -248,9 +248,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { } } - private _getSetHum( - stateObj: HassEntity - ): undefined | number | [number, number] { + private _getSetHum(stateObj: HassEntity): undefined | number { if (stateObj.state === UNAVAILABLE) { return undefined; } From 404d0b8d057b65e4f3b537910dbcbf47779b6bf8 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sun, 31 May 2020 23:54:28 +0300 Subject: [PATCH 05/24] Add 'Target Humidity' string to the more-info dialog --- src/dialogs/more-info/controls/more-info-humidifier.ts | 2 +- src/translations/en.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dialogs/more-info/controls/more-info-humidifier.ts b/src/dialogs/more-info/controls/more-info-humidifier.ts index e9fe98991b..35a37908ab 100644 --- a/src/dialogs/more-info/controls/more-info-humidifier.ts +++ b/src/dialogs/more-info/controls/more-info-humidifier.ts @@ -73,7 +73,7 @@ class MoreInfoHumidifier extends LitElement { ${supportModes ? html` -
+
Date: Thu, 4 Jun 2020 00:53:51 +0300 Subject: [PATCH 06/24] LitElement --- src/components/entity/ha-entity-humidifier.ts | 61 ++++++++ src/components/entity/ha-entity-toggle.ts | 2 +- src/components/ha-humidifier-control.js | 142 ------------------ src/components/ha-humidifier-state.js | 83 ---------- .../controls/more-info-humidifier.ts | 1 - .../entity-rows/hui-humidifier-entity-row.ts | 7 +- 6 files changed, 66 insertions(+), 230 deletions(-) create mode 100644 src/components/entity/ha-entity-humidifier.ts delete mode 100644 src/components/ha-humidifier-control.js delete mode 100644 src/components/ha-humidifier-state.js diff --git a/src/components/entity/ha-entity-humidifier.ts b/src/components/entity/ha-entity-humidifier.ts new file mode 100644 index 0000000000..25588cb42a --- /dev/null +++ b/src/components/entity/ha-entity-humidifier.ts @@ -0,0 +1,61 @@ +import { css, CSSResult, html, TemplateResult } from "lit-element"; +import { HaEntityToggle } from "./ha-entity-toggle"; + +class HaEntityHumidifier extends HaEntityToggle { + protected render(): TemplateResult { + if (!this.stateObj) { + return super.render(); + } + + return html` +
+ ${this.stateObj.attributes.mode + ? html` + ${this.hass!.localize( + `state_attributes.humidifier.mode.${this.stateObj.attributes.mode}` + ) || this.stateObj.attributes.mode} + ` + : ""} +
+ ${this.stateObj.attributes.humidity + ? html`${this.stateObj.attributes.humidity} %` + : ""} +
+
+ + ${super.render()} + `; + } + + static get styles(): CSSResult { + return css` + :host { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + } + + .target { + color: var(--primary-text-color); + } + + .current { + color: var(--secondary-text-color); + } + + .state-label { + font-weight: bold; + text-transform: capitalize; + } + + .unit { + display: inline-block; + direction: ltr; + } + ${super.styles} + `; + } +} + +customElements.define("ha-entity-humidifier", HaEntityHumidifier); diff --git a/src/components/entity/ha-entity-toggle.ts b/src/components/entity/ha-entity-toggle.ts index 7b19129c7e..587f299641 100644 --- a/src/components/entity/ha-entity-toggle.ts +++ b/src/components/entity/ha-entity-toggle.ts @@ -22,7 +22,7 @@ const isOn = (stateObj?: HassEntity) => !STATES_OFF.includes(stateObj.state) && !UNAVAILABLE_STATES.includes(stateObj.state); -class HaEntityToggle extends LitElement { +export class HaEntityToggle extends LitElement { // hass is not a property so that we only re-render on stateObj changes public hass?: HomeAssistant; diff --git a/src/components/ha-humidifier-control.js b/src/components/ha-humidifier-control.js deleted file mode 100644 index 59d8ef1133..0000000000 --- a/src/components/ha-humidifier-control.js +++ /dev/null @@ -1,142 +0,0 @@ -import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "./ha-icon-button"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import { EventsMixin } from "../mixins/events-mixin"; - -/* - * @appliesMixin EventsMixin - */ -class HaHumidifierControl extends EventsMixin(PolymerElement) { - static get template() { - return html` - - - - -
[[value]] [[units]]
-
-
- -
-
- -
-
- `; - } - - static get properties() { - return { - value: { - type: Number, - observer: "valueChanged", - }, - units: { - type: String, - }, - min: { - type: Number, - }, - max: { - type: Number, - }, - step: { - type: Number, - value: 1, - }, - }; - } - - humidityStateInFlux(inFlux) { - this.$.humidity.classList.toggle("in-flux", inFlux); - } - - _round(val) { - // round value to precision derived from step - // insired by https://github.com/soundar24/roundSlider/blob/master/src/roundslider.js - const s = this.step.toString().split("."); - return s[1] ? parseFloat(val.toFixed(s[1].length)) : Math.round(val); - } - - incrementValue() { - const newval = this._round(this.value + this.step); - if (this.value < this.max) { - this.last_changed = Date.now(); - this.humidityStateInFlux(true); - } - if (newval <= this.max) { - // If no initial humidity - // this forces control to start - // from the min configured instead of 0 - if (newval <= this.min) { - this.value = this.min; - } else { - this.value = newval; - } - } else { - this.value = this.max; - } - } - - decrementValue() { - const newval = this._round(this.value - this.step); - if (this.value > this.min) { - this.last_changed = Date.now(); - this.humidityStateInFlux(true); - } - if (newval >= this.min) { - this.value = newval; - } else { - this.value = this.min; - } - } - - valueChanged() { - // when the last_changed timestamp is changed, - // trigger a potential event fire in - // the future, as long as last changed is far enough in the - // past. - if (this.last_changed) { - window.setTimeout(() => { - const now = Date.now(); - if (now - this.last_changed >= 2000) { - this.fire("change"); - this.humidityStateInFlux(false); - this.last_changed = null; - } - }, 2010); - } - } -} - -customElements.define("ha-humidifier-control", HaHumidifierControl); diff --git a/src/components/ha-humidifier-state.js b/src/components/ha-humidifier-state.js deleted file mode 100644 index 2731b6451e..0000000000 --- a/src/components/ha-humidifier-state.js +++ /dev/null @@ -1,83 +0,0 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import LocalizeMixin from "../mixins/localize-mixin"; - -/* - * @appliesMixin LocalizeMixin - */ -class HaHumidifierState extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - -
- -
[[computeTarget(stateObj.attributes.humidity)]]
-
- `; - } - - static get properties() { - return { - stateObj: Object, - }; - } - - computeTarget(humidity) { - if (humidity != null) { - return `${humidity} %`; - } - - return ""; - } - - _hasKnownState(state) { - return state !== "unknown"; - } - - _localizeState(localize, state) { - return localize(`state.default.${state}`) || state; - } - - _localizeMode(localize, mode) { - return localize(`state_attributes.humidifier.mode.${mode}`) || mode; - } - - _renderMode(attributes) { - return attributes.mode; - } -} -customElements.define("ha-humidifier-state", HaHumidifierState); diff --git a/src/dialogs/more-info/controls/more-info-humidifier.ts b/src/dialogs/more-info/controls/more-info-humidifier.ts index 35a37908ab..6a3bbc1aad 100644 --- a/src/dialogs/more-info/controls/more-info-humidifier.ts +++ b/src/dialogs/more-info/controls/more-info-humidifier.ts @@ -14,7 +14,6 @@ import { classMap } from "lit-html/directives/class-map"; import { fireEvent } from "../../../common/dom/fire_event"; import { supportsFeature } from "../../../common/entity/supports-feature"; import { computeRTLDirection } from "../../../common/util/compute_rtl"; -import "../../../components/ha-humidifier-control"; import "../../../components/ha-paper-dropdown-menu"; import "../../../components/ha-paper-slider"; import "../../../components/ha-switch"; diff --git a/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts b/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts index 9a0e97522a..159f2d02ca 100644 --- a/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts @@ -8,7 +8,8 @@ import { PropertyValues, TemplateResult, } from "lit-element"; -import "../../../components/ha-humidifier-state"; +import "../../../components/entity/ha-entity-humidifier"; +import { UNAVAILABLE_STATES } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; @@ -50,10 +51,10 @@ class HuiHumidifierEntityRow extends LitElement implements LovelaceRow { return html` - + > `; } From 05ad7ea011fd79961910bf5446caaa461c89c881 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Thu, 4 Jun 2020 00:58:53 +0300 Subject: [PATCH 07/24] lint --- src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts b/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts index 159f2d02ca..9e0ee5dd2c 100644 --- a/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts @@ -9,7 +9,6 @@ import { TemplateResult, } from "lit-element"; import "../../../components/entity/ha-entity-humidifier"; -import { UNAVAILABLE_STATES } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; From 20ca642e51a2154cca18725cee276e197a313ae6 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Thu, 4 Jun 2020 01:15:11 +0300 Subject: [PATCH 08/24] enable toggling from entities card --- src/common/const.ts | 1 + src/components/entity/ha-entity-humidifier.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/common/const.ts b/src/common/const.ts index c1398fce5f..b801ae37fc 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -80,6 +80,7 @@ export const DOMAINS_TOGGLE = new Set([ "switch", "group", "automation", + "humidifier", ]); /** Temperature units. */ diff --git a/src/components/entity/ha-entity-humidifier.ts b/src/components/entity/ha-entity-humidifier.ts index 25588cb42a..ac6603e499 100644 --- a/src/components/entity/ha-entity-humidifier.ts +++ b/src/components/entity/ha-entity-humidifier.ts @@ -53,6 +53,7 @@ class HaEntityHumidifier extends HaEntityToggle { display: inline-block; direction: ltr; } + ${super.styles} `; } From 82957ff6efadcad548819ccf19289fd843b10204 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Thu, 11 Jun 2020 01:46:07 +0300 Subject: [PATCH 09/24] Attributes --- src/data/history.ts | 1 + src/util/hass-attributes-util.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/data/history.ts b/src/data/history.ts index 0856e9eff3..73333cfd66 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -13,6 +13,7 @@ const LINE_ATTRIBUTES_TO_KEEP = [ "target_temp_high", "hvac_action", "humidity", + "mode", ]; export interface LineChartState { diff --git a/src/util/hass-attributes-util.js b/src/util/hass-attributes-util.js index 04f8ab2701..4eb17735a0 100644 --- a/src/util/hass-attributes-util.js +++ b/src/util/hass-attributes-util.js @@ -37,6 +37,7 @@ hassAttributeUtil.DOMAIN_DEVICE_CLASS = { "shutter", "window", ], + humidifier: ["dehumidifier", "humidifier"], sensor: [ "battery", "humidity", From a0ab4dffc993f27413c829e2da060f8afdfeb266 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Thu, 11 Jun 2020 21:28:07 +0300 Subject: [PATCH 10/24] display target humidity on glance card --- src/common/entity/compute_state_display.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 82eb783dfa..8b3d1bdfd5 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -53,6 +53,10 @@ export const computeStateDisplay = ( stateObj.attributes.minute ); return formatDateTime(date, language); + } else if (domain === "humidifier") { + if (stateObj.state === "on" && stateObj.attributes.humidity) { + return `${stateObj.attributes.humidity} %`; + } } return ( From e9ffdeff19e9378a55c59e75280401b0a39a026f Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Thu, 11 Jun 2020 21:33:40 +0300 Subject: [PATCH 11/24] linter fix --- src/common/entity/compute_state_display.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 8b3d1bdfd5..c3f1013c6e 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -53,7 +53,9 @@ export const computeStateDisplay = ( stateObj.attributes.minute ); return formatDateTime(date, language); - } else if (domain === "humidifier") { + } + + if (domain === "humidifier") { if (stateObj.state === "on" && stateObj.attributes.humidity) { return `${stateObj.attributes.humidity} %`; } From 4033131f2e0bd03b7405393128851230b5bc238a Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sat, 13 Jun 2020 03:33:10 +0300 Subject: [PATCH 12/24] Handle unknown states --- .../lovelace/cards/hui-humidifier-card.ts | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/panels/lovelace/cards/hui-humidifier-card.ts b/src/panels/lovelace/cards/hui-humidifier-card.ts index 1e4bb5268f..dd2bd4f7b0 100644 --- a/src/panels/lovelace/cards/hui-humidifier-card.ts +++ b/src/panels/lovelace/cards/hui-humidifier-card.ts @@ -18,7 +18,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeRTLDirection } from "../../../common/util/compute_rtl"; import "../../../components/ha-card"; import { HumidifierEntity } from "../../../data/humidifier"; -import { UNAVAILABLE } from "../../../data/entity"; +import { UNAVAILABLE_STATES } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; import { findEntities } from "../common/find-entites"; import { hasConfigOrEntityChanged } from "../common/has-changed"; @@ -96,20 +96,19 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { const rtlDirection = computeRTLDirection(this.hass); - const slider = - stateObj.state === UNAVAILABLE - ? html` ` - : html` - - `; + const slider = UNAVAILABLE_STATES.includes(stateObj.state) + ? html` ` + : html` + + `; const setValues = svg` @@ -122,17 +121,17 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { class="set-value" > ${ - stateObj.state === UNAVAILABLE - ? this.hass.localize("state.default.unavailable") - : this._setHum === undefined || this._setHum === null + UNAVAILABLE_STATES.includes(stateObj.state) || + this._setHum === undefined || + this._setHum === null ? "" : svg` ${this._setHum.toFixed()} + + % + ` } - - % - @@ -144,7 +143,8 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { > ${this.hass!.localize(`state.default.${stateObj.state}`)} ${ - stateObj.attributes.mode + stateObj.attributes.mode && + !UNAVAILABLE_STATES.includes(stateObj.state) ? html` - ${this.hass!.localize( @@ -249,7 +249,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { } private _getSetHum(stateObj: HassEntity): undefined | number { - if (stateObj.state === UNAVAILABLE) { + if (UNAVAILABLE_STATES.includes(stateObj.state)) { return undefined; } From 372ecc65577fe1b3bf3e6c7a3106d2ccbaf548e2 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Thu, 18 Jun 2020 16:37:56 +0300 Subject: [PATCH 13/24] make humidifier card lazy-loaded --- src/panels/lovelace/create-element/create-card-element.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index e06db4c345..187fc5346d 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -6,7 +6,6 @@ import "../cards/hui-entity-card"; import "../cards/hui-glance-card"; import "../cards/hui-history-graph-card"; import "../cards/hui-horizontal-stack-card"; -import "../cards/hui-humidifier-card"; import "../cards/hui-light-card"; import "../cards/hui-sensor-card"; import "../cards/hui-thermostat-card"; @@ -25,7 +24,6 @@ const ALWAYS_LOADED_TYPES = new Set([ "glance", "history-graph", "horizontal-stack", - "humidifier", "light", "sensor", "thermostat", @@ -39,6 +37,7 @@ const LAZY_LOAD_TYPES = { "empty-state": () => import("../cards/hui-empty-state-card"), starting: () => import("../cards/hui-starting-card"), "entity-filter": () => import("../cards/hui-entity-filter-card"), + humidifier: () => import("../cards/hui-humidifier-card"), "media-control": () => import("../cards/hui-media-control-card"), "picture-elements": () => import("../cards/hui-picture-elements-card"), "picture-entity": () => import("../cards/hui-picture-entity-card"), From 342f22e6a1f2ab85d0a45dd09681d7f17ac75544 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Thu, 18 Jun 2020 16:50:49 +0300 Subject: [PATCH 14/24] apply suggestions from code review --- src/components/entity/ha-entity-humidifier.ts | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/components/entity/ha-entity-humidifier.ts b/src/components/entity/ha-entity-humidifier.ts index ac6603e499..480188c54b 100644 --- a/src/components/entity/ha-entity-humidifier.ts +++ b/src/components/entity/ha-entity-humidifier.ts @@ -27,35 +27,36 @@ class HaEntityHumidifier extends HaEntityToggle { `; } - static get styles(): CSSResult { - return css` - :host { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - } + static get styles(): CSSResult[] { + return [ + super.styles, + css` + :host { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + } - .target { - color: var(--primary-text-color); - } + .target { + color: var(--primary-text-color); + } - .current { - color: var(--secondary-text-color); - } + .current { + color: var(--secondary-text-color); + } - .state-label { - font-weight: bold; - text-transform: capitalize; - } + .state-label { + font-weight: bold; + text-transform: capitalize; + } - .unit { - display: inline-block; - direction: ltr; - } - - ${super.styles} - `; + .unit { + display: inline-block; + direction: ltr; + } + `, + ]; } } From f0b0200932d7a08e64151205dead13dc18d7f245 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Thu, 18 Jun 2020 17:03:47 +0300 Subject: [PATCH 15/24] Revert "apply suggestions from code review" This reverts commit 342f22e6a1f2ab85d0a45dd09681d7f17ac75544. --- src/components/entity/ha-entity-humidifier.ts | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/components/entity/ha-entity-humidifier.ts b/src/components/entity/ha-entity-humidifier.ts index 480188c54b..ac6603e499 100644 --- a/src/components/entity/ha-entity-humidifier.ts +++ b/src/components/entity/ha-entity-humidifier.ts @@ -27,36 +27,35 @@ class HaEntityHumidifier extends HaEntityToggle { `; } - static get styles(): CSSResult[] { - return [ - super.styles, - css` - :host { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - } + static get styles(): CSSResult { + return css` + :host { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + } - .target { - color: var(--primary-text-color); - } + .target { + color: var(--primary-text-color); + } - .current { - color: var(--secondary-text-color); - } + .current { + color: var(--secondary-text-color); + } - .state-label { - font-weight: bold; - text-transform: capitalize; - } + .state-label { + font-weight: bold; + text-transform: capitalize; + } - .unit { - display: inline-block; - direction: ltr; - } - `, - ]; + .unit { + display: inline-block; + direction: ltr; + } + + ${super.styles} + `; } } From a763ad5bf1482a293327bba004749a2e2989f160 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Thu, 18 Jun 2020 16:50:49 +0300 Subject: [PATCH 16/24] reapply suggestions from code review --- src/components/entity/ha-entity-humidifier.ts | 51 ++++++++++--------- src/components/entity/ha-entity-toggle.ts | 39 +++++++------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/src/components/entity/ha-entity-humidifier.ts b/src/components/entity/ha-entity-humidifier.ts index ac6603e499..480188c54b 100644 --- a/src/components/entity/ha-entity-humidifier.ts +++ b/src/components/entity/ha-entity-humidifier.ts @@ -27,35 +27,36 @@ class HaEntityHumidifier extends HaEntityToggle { `; } - static get styles(): CSSResult { - return css` - :host { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - } + static get styles(): CSSResult[] { + return [ + super.styles, + css` + :host { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + } - .target { - color: var(--primary-text-color); - } + .target { + color: var(--primary-text-color); + } - .current { - color: var(--secondary-text-color); - } + .current { + color: var(--secondary-text-color); + } - .state-label { - font-weight: bold; - text-transform: capitalize; - } + .state-label { + font-weight: bold; + text-transform: capitalize; + } - .unit { - display: inline-block; - direction: ltr; - } - - ${super.styles} - `; + .unit { + display: inline-block; + direction: ltr; + } + `, + ]; } } diff --git a/src/components/entity/ha-entity-toggle.ts b/src/components/entity/ha-entity-toggle.ts index 587f299641..1756ccb929 100644 --- a/src/components/entity/ha-entity-toggle.ts +++ b/src/components/entity/ha-entity-toggle.ts @@ -138,23 +138,28 @@ export class HaEntityToggle extends LitElement { }, 2000); } - static get styles(): CSSResult { - return css` - :host { - white-space: nowrap; - min-width: 38px; - } - ha-icon-button { - color: var(--ha-icon-button-inactive-color, var(--primary-text-color)); - transition: color 0.5s; - } - ha-icon-button[state-active] { - color: var(--ha-icon-button-active-color, var(--primary-color)); - } - ha-switch { - padding: 13px 5px; - } - `; + static get styles(): CSSResult[] { + return [ + css` + :host { + white-space: nowrap; + min-width: 38px; + } + ha-icon-button { + color: var( + --ha-icon-button-inactive-color, + var(--primary-text-color) + ); + transition: color 0.5s; + } + ha-icon-button[state-active] { + color: var(--ha-icon-button-active-color, var(--primary-color)); + } + ha-switch { + padding: 13px 5px; + } + `, + ]; } } From a496563b5c7d643f80cbf6c164536da276b734d9 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Thu, 18 Jun 2020 18:18:37 +0300 Subject: [PATCH 17/24] fix HaEntityHumidifier.styles --- src/components/entity/ha-entity-humidifier.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/entity/ha-entity-humidifier.ts b/src/components/entity/ha-entity-humidifier.ts index 480188c54b..b77d4a56fe 100644 --- a/src/components/entity/ha-entity-humidifier.ts +++ b/src/components/entity/ha-entity-humidifier.ts @@ -28,8 +28,7 @@ class HaEntityHumidifier extends HaEntityToggle { } static get styles(): CSSResult[] { - return [ - super.styles, + return super.styles.concat([ css` :host { display: flex; @@ -56,7 +55,7 @@ class HaEntityHumidifier extends HaEntityToggle { direction: ltr; } `, - ]; + ]); } } From 14a51799a6bfa0fd1a006fd59b937e94129e0e21 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sat, 20 Jun 2020 21:23:01 +0300 Subject: [PATCH 18/24] Cater for null super.styles on page refresh --- src/components/entity/ha-entity-humidifier.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/entity/ha-entity-humidifier.ts b/src/components/entity/ha-entity-humidifier.ts index b77d4a56fe..2475f6273f 100644 --- a/src/components/entity/ha-entity-humidifier.ts +++ b/src/components/entity/ha-entity-humidifier.ts @@ -28,7 +28,8 @@ class HaEntityHumidifier extends HaEntityToggle { } static get styles(): CSSResult[] { - return super.styles.concat([ + let toggle_styles = super.styles; + let humidifier_styles = [ css` :host { display: flex; @@ -55,7 +56,10 @@ class HaEntityHumidifier extends HaEntityToggle { direction: ltr; } `, - ]); + ]; + return toggle_styles + ? toggle_styles.concat(humidifier_styles) + : humidifier_styles; } } From b242c6651adc8151e93f9f5bd8b592def816bc47 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sat, 20 Jun 2020 21:32:55 +0300 Subject: [PATCH 19/24] eslint --- src/components/entity/ha-entity-humidifier.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/entity/ha-entity-humidifier.ts b/src/components/entity/ha-entity-humidifier.ts index 2475f6273f..3e38b8b0b4 100644 --- a/src/components/entity/ha-entity-humidifier.ts +++ b/src/components/entity/ha-entity-humidifier.ts @@ -28,8 +28,8 @@ class HaEntityHumidifier extends HaEntityToggle { } static get styles(): CSSResult[] { - let toggle_styles = super.styles; - let humidifier_styles = [ + const toggle_styles = super.styles; + const humidifier_styles = [ css` :host { display: flex; From 2cc9d70915361d8474015976d5ecafeb780dae5d Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sat, 20 Jun 2020 22:25:21 +0300 Subject: [PATCH 20/24] Revert to 372ecc6 --- src/components/entity/ha-entity-humidifier.ts | 54 +++++++++---------- src/components/entity/ha-entity-toggle.ts | 39 ++++++-------- 2 files changed, 42 insertions(+), 51 deletions(-) diff --git a/src/components/entity/ha-entity-humidifier.ts b/src/components/entity/ha-entity-humidifier.ts index 3e38b8b0b4..ac6603e499 100644 --- a/src/components/entity/ha-entity-humidifier.ts +++ b/src/components/entity/ha-entity-humidifier.ts @@ -27,39 +27,35 @@ class HaEntityHumidifier extends HaEntityToggle { `; } - static get styles(): CSSResult[] { - const toggle_styles = super.styles; - const humidifier_styles = [ - css` - :host { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - } + static get styles(): CSSResult { + return css` + :host { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + } - .target { - color: var(--primary-text-color); - } + .target { + color: var(--primary-text-color); + } - .current { - color: var(--secondary-text-color); - } + .current { + color: var(--secondary-text-color); + } - .state-label { - font-weight: bold; - text-transform: capitalize; - } + .state-label { + font-weight: bold; + text-transform: capitalize; + } - .unit { - display: inline-block; - direction: ltr; - } - `, - ]; - return toggle_styles - ? toggle_styles.concat(humidifier_styles) - : humidifier_styles; + .unit { + display: inline-block; + direction: ltr; + } + + ${super.styles} + `; } } diff --git a/src/components/entity/ha-entity-toggle.ts b/src/components/entity/ha-entity-toggle.ts index 1756ccb929..587f299641 100644 --- a/src/components/entity/ha-entity-toggle.ts +++ b/src/components/entity/ha-entity-toggle.ts @@ -138,28 +138,23 @@ export class HaEntityToggle extends LitElement { }, 2000); } - static get styles(): CSSResult[] { - return [ - css` - :host { - white-space: nowrap; - min-width: 38px; - } - ha-icon-button { - color: var( - --ha-icon-button-inactive-color, - var(--primary-text-color) - ); - transition: color 0.5s; - } - ha-icon-button[state-active] { - color: var(--ha-icon-button-active-color, var(--primary-color)); - } - ha-switch { - padding: 13px 5px; - } - `, - ]; + static get styles(): CSSResult { + return css` + :host { + white-space: nowrap; + min-width: 38px; + } + ha-icon-button { + color: var(--ha-icon-button-inactive-color, var(--primary-text-color)); + transition: color 0.5s; + } + ha-icon-button[state-active] { + color: var(--ha-icon-button-active-color, var(--primary-color)); + } + ha-switch { + padding: 13px 5px; + } + `; } } From 9ba0de67f5ba2cc1e586e3843b88eb7df98eac1b Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Tue, 23 Jun 2020 02:41:53 +0300 Subject: [PATCH 21/24] Removed humidifier row and a space in front of percent --- src/common/entity/compute_state_display.ts | 2 +- src/components/entity/ha-entity-humidifier.ts | 62 ---------------- .../create-element/create-row-element.ts | 3 +- .../entity-rows/hui-humidifier-entity-row.ts | 74 ------------------- 4 files changed, 2 insertions(+), 139 deletions(-) delete mode 100644 src/components/entity/ha-entity-humidifier.ts delete mode 100644 src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index c3f1013c6e..10de4cb847 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -57,7 +57,7 @@ export const computeStateDisplay = ( if (domain === "humidifier") { if (stateObj.state === "on" && stateObj.attributes.humidity) { - return `${stateObj.attributes.humidity} %`; + return `${stateObj.attributes.humidity}%`; } } diff --git a/src/components/entity/ha-entity-humidifier.ts b/src/components/entity/ha-entity-humidifier.ts deleted file mode 100644 index ac6603e499..0000000000 --- a/src/components/entity/ha-entity-humidifier.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { css, CSSResult, html, TemplateResult } from "lit-element"; -import { HaEntityToggle } from "./ha-entity-toggle"; - -class HaEntityHumidifier extends HaEntityToggle { - protected render(): TemplateResult { - if (!this.stateObj) { - return super.render(); - } - - return html` -
- ${this.stateObj.attributes.mode - ? html` - ${this.hass!.localize( - `state_attributes.humidifier.mode.${this.stateObj.attributes.mode}` - ) || this.stateObj.attributes.mode} - ` - : ""} -
- ${this.stateObj.attributes.humidity - ? html`${this.stateObj.attributes.humidity} %` - : ""} -
-
- - ${super.render()} - `; - } - - static get styles(): CSSResult { - return css` - :host { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - } - - .target { - color: var(--primary-text-color); - } - - .current { - color: var(--secondary-text-color); - } - - .state-label { - font-weight: bold; - text-transform: capitalize; - } - - .unit { - display: inline-block; - direction: ltr; - } - - ${super.styles} - `; - } -} - -customElements.define("ha-entity-humidifier", HaEntityHumidifier); diff --git a/src/panels/lovelace/create-element/create-row-element.ts b/src/panels/lovelace/create-element/create-row-element.ts index 433b50e0a5..178192fa48 100644 --- a/src/panels/lovelace/create-element/create-row-element.ts +++ b/src/panels/lovelace/create-element/create-row-element.ts @@ -24,7 +24,6 @@ const LAZY_LOAD_TYPES = { "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"), - "humidifier-entity": () => import("../entity-rows/hui-humidifier-entity-row"), "input-datetime-entity": () => import("../entity-rows/hui-input-datetime-entity-row"), "input-number-entity": () => @@ -52,7 +51,7 @@ const DOMAIN_TO_ELEMENT_TYPE = { cover: "cover", fan: "toggle", group: "group", - humidifier: "humidifier", + humidifier: "toggle", input_boolean: "toggle", input_number: "input-number", input_select: "input-select", diff --git a/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts b/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts deleted file mode 100644 index 9e0ee5dd2c..0000000000 --- a/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { - css, - CSSResult, - customElement, - html, - LitElement, - property, - PropertyValues, - TemplateResult, -} from "lit-element"; -import "../../../components/entity/ha-entity-humidifier"; -import { HomeAssistant } from "../../../types"; -import { hasConfigOrEntityChanged } from "../common/has-changed"; -import "../components/hui-generic-entity-row"; -import { createEntityNotFoundWarning } from "../components/hui-warning"; -import { EntityConfig, LovelaceRow } from "./types"; - -@customElement("hui-humidifier-entity-row") -class HuiHumidifierEntityRow extends LitElement implements LovelaceRow { - @property() public hass?: HomeAssistant; - - @property() private _config?: EntityConfig; - - public setConfig(config: EntityConfig): void { - if (!config || !config.entity) { - throw new Error("Invalid Configuration: 'entity' required"); - } - - this._config = config; - } - - protected shouldUpdate(changedProps: PropertyValues): boolean { - return hasConfigOrEntityChanged(this, changedProps); - } - - protected render(): TemplateResult { - if (!this.hass || !this._config) { - return html``; - } - - const stateObj = this.hass.states[this._config.entity]; - - if (!stateObj) { - return html` - - ${createEntityNotFoundWarning(this.hass, this._config.entity)} - - `; - } - - return html` - - - - `; - } - - static get styles(): CSSResult { - return css` - ha-humidifier-state { - text-align: right; - } - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - "hui-humidifier-entity-row": HuiHumidifierEntityRow; - } -} From 6d000a3f9ab66df6b3696ff04ef870626c9def43 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 23 Jun 2020 00:32:21 +0000 Subject: [PATCH 22/24] [ci skip] Translation update --- translations/frontend/da.json | 19 +++++++++++++++++++ translations/frontend/nb.json | 33 ++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/translations/frontend/da.json b/translations/frontend/da.json index 2969ff8680..bcef048d21 100644 --- a/translations/frontend/da.json +++ b/translations/frontend/da.json @@ -506,6 +506,11 @@ "clear": "Ryd", "show_areas": "Vis områder" }, + "date-range-picker": { + "end_date": "Slutdato", + "select": "Vælg", + "start_date": "Startdato" + }, "device-picker": { "clear": "Ryd", "device": "Enhed", @@ -695,6 +700,7 @@ "zha_device_info": { "buttons": { "add": "Tilføj enheder", + "clusters": "Administrer klynger", "reconfigure": "Genkonfigurer enhed", "remove": "Fjern enhed", "zigbee_information": "Zigbee-oplysninger" @@ -1558,6 +1564,7 @@ } }, "mqtt": { + "button": "Konfigurer", "description_listen": "Lyt til et emne", "description_publish": "Udsend en pakke", "listening_to": "Lytter til", @@ -2008,11 +2015,23 @@ }, "history": { "period": "Periode", + "ranges": { + "last_week": "Sidste uge", + "this_week": "Denne uge", + "today": "I dag", + "yesterday": "I går" + }, "showing_entries": "Viser poster for" }, "logbook": { "entries_not_found": "Der blev ikke fundet nogen logbogsposter.", "period": "Periode", + "ranges": { + "last_week": "Sidste uge", + "this_week": "Denne uge", + "today": "I dag", + "yesterday": "I går" + }, "showing_entries": "Viser poster for" }, "lovelace": { diff --git a/translations/frontend/nb.json b/translations/frontend/nb.json index 9bc5132250..ce2dfe7d53 100644 --- a/translations/frontend/nb.json +++ b/translations/frontend/nb.json @@ -506,6 +506,11 @@ "clear": "Tøm", "show_areas": "Vis områder" }, + "date-range-picker": { + "end_date": "Sluttdato", + "select": "Velg", + "start_date": "Startdato" + }, "device-picker": { "clear": "Tøm", "device": "Enhet", @@ -695,6 +700,7 @@ "zha_device_info": { "buttons": { "add": "Legg til enheter via denne enheten", + "clusters": "Behandle Clusters", "reconfigure": "Rekonfigurer enhet", "remove": "Fjern enhet", "zigbee_information": "Zigbee-enhetssignatur" @@ -1558,6 +1564,7 @@ } }, "mqtt": { + "button": "Konfigurer", "description_listen": "Lytt til et emne", "description_publish": "Publiser en pakke", "listening_to": "Lytter til", @@ -1678,11 +1685,11 @@ "core": "Last inn lokasjon og spesialtilpassinger på nytt", "group": "Last inn grupper på nytt", "heading": "YAML -Konfigurasjon lastes på nytt", - "input_boolean": "Last input booleans på nytt", - "input_datetime": "Last input date på nytt", - "input_number": "Las input numbers på nytt", - "input_select": "Last input selects på nytt ", - "input_text": "Last input texts på nytt", + "input_boolean": "Last inn bolsk inndata på nytt", + "input_datetime": "Last inn dato inndata på nytt", + "input_number": "Last inn nummer inndata på nytt", + "input_select": "Last inn valg inndata på nytt ", + "input_text": "Last inn tekst inndata på nytt", "introduction": "Noen deler av Home Assistant kan laste inn uten å kreve omstart. Hvis du trykker last på nytt, vil du bytte den nåværende konfigurasjonen med den nye.", "person": "Last inn personer på nytt", "scene": "Last inn scener på nytt", @@ -1742,14 +1749,14 @@ "system": "" } }, - "users_privileges_note": "Brukere-gruppen er et pågående arbeid. Brukeren kan ikke administrere forekomsten via brukergrensesnittet. Vi overvåker fortsatt alle API-endepunkter for administrasjonsadministrasjon for å sikre at de begrenser tilgangen til administratorer på riktig måte." + "users_privileges_note": "Brukere-gruppen er et pågående arbeid. Brukeren kan ikke administrere forekomsten via brukergrensesnittet. Vi reviderer fortsatt alle API-endepunkter for å sikre at de begrenser tilgangen til administratorer på riktig måte." }, "zha": { "add_device_page": { "discovered_text": "Enheter vises her når de er oppdaget.", "discovery_text": "Oppdagede enheter vises her. Følg instruksjonene for enheten(e) og sett enheten(e) i paringsmodus.", "header": "Zigbee Home Automation - Legg til enheter", - "no_devices_found": "Ingen enheter er funnet, sørg for at de er i paringsmodus og holde dem våken mens du oppdager kjører.", + "no_devices_found": "Ingen enheter ble funnet, sørg for at de er i paringsmodus og holde dem våken mens oppdagelse pågår.", "pairing_mode": "Kontroller at enhetene er i paringsmodus. Sjekk instruksjonene til enheten om hvordan du gjør dette.", "search_again": "Søk på nytt", "spinner": "Søker etter ZHA Zigbee-enheter..." @@ -2035,11 +2042,23 @@ }, "history": { "period": "Periode", + "ranges": { + "last_week": "Forrige uke", + "this_week": "Denne uken", + "today": "I dag", + "yesterday": "I går" + }, "showing_entries": "Viser oppføringer for" }, "logbook": { "entries_not_found": "Finner ingen loggbokoppføringer.", "period": "Periode", + "ranges": { + "last_week": "Forrige uke", + "this_week": "Denne uken", + "today": "I dag", + "yesterday": "I går" + }, "showing_entries": "Viser oppføringer for" }, "lovelace": { From ee14d206c8eb6f6d436f20bb20477009fd865213 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 23 Jun 2020 08:51:41 -0700 Subject: [PATCH 23/24] Only use suspend logic in app (#6212) --- src/layouts/home-assistant.ts | 37 +++++++++++++++++++++++++++ src/state/hass-element.ts | 2 -- src/state/suspend-mixin.ts | 48 ----------------------------------- 3 files changed, 37 insertions(+), 50 deletions(-) delete mode 100644 src/state/suspend-mixin.ts diff --git a/src/layouts/home-assistant.ts b/src/layouts/home-assistant.ts index 61a1d365a8..69e6bd677e 100644 --- a/src/layouts/home-assistant.ts +++ b/src/layouts/home-assistant.ts @@ -22,6 +22,10 @@ export class HomeAssistantAppEl extends HassElement { private _haVersion?: string; + private _hiddenTimeout?: number; + + private _visiblePromiseResolve?: () => void; + protected render() { const hass = this.hass; @@ -71,6 +75,12 @@ export class HomeAssistantAppEl extends HassElement { super.hassConnected(); // @ts-ignore this._loadHassTranslations(this.hass!.language, "state"); + + document.addEventListener( + "visibilitychange", + () => this.__handleVisibilityChange(), + false + ); } protected hassReconnected() { @@ -137,6 +147,33 @@ export class HomeAssistantAppEl extends HassElement { ? route.path.substr(1) : route.path.substr(1, dividerPos - 1); } + + private __handleVisibilityChange() { + if (document.hidden) { + // If the document is hidden, we will prevent reconnects until we are visible again + this.hass!.connection.suspendReconnectUntil( + new Promise((resolve) => { + this._visiblePromiseResolve = resolve; + }) + ); + // We close the connection to Home Assistant after being hidden for 5 minutes + this._hiddenTimeout = window.setTimeout(() => { + this._hiddenTimeout = undefined; + this.hass!.connection.suspend(); + }, 300000); + } else { + // Clear timer to close the connection + if (this._hiddenTimeout) { + clearTimeout(this._hiddenTimeout); + this._hiddenTimeout = undefined; + } + // Unsuspend the reconnect + if (this._visiblePromiseResolve) { + this._visiblePromiseResolve(); + this._visiblePromiseResolve = undefined; + } + } + } } declare global { diff --git a/src/state/hass-element.ts b/src/state/hass-element.ts index 3b702e7bb1..a5cb0992ae 100644 --- a/src/state/hass-element.ts +++ b/src/state/hass-element.ts @@ -12,7 +12,6 @@ import SidebarMixin from "./sidebar-mixin"; import ThemesMixin from "./themes-mixin"; import TranslationsMixin from "./translations-mixin"; import { urlSyncMixin } from "./url-sync-mixin"; -import { suspendMixin } from "./suspend-mixin"; const ext = (baseClass: T, mixins): T => mixins.reduceRight((base, mixin) => mixin(base), baseClass); @@ -25,7 +24,6 @@ export class HassElement extends ext(HassBaseEl, [ SidebarMixin, DisconnectToastMixin, connectionMixin, - suspendMixin, NotificationMixin, dialogManagerMixin, urlSyncMixin, diff --git a/src/state/suspend-mixin.ts b/src/state/suspend-mixin.ts deleted file mode 100644 index 774a77a45f..0000000000 --- a/src/state/suspend-mixin.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Constructor } from "../types"; -import { HassBaseEl } from "./hass-base-mixin"; - -export const suspendMixin = >( - superClass: T -) => - class extends superClass { - private __hiddenTimeout?: number; - - private __visiblePromiseResolve?: () => void; - - protected hassConnected() { - super.hassConnected(); - - document.addEventListener( - "visibilitychange", - () => this.__handleVisibilityChange(), - false - ); - } - - private __handleVisibilityChange() { - if (document.hidden) { - // If the document is hidden, we will prevent reconnects until we are visible again - this.hass!.connection.suspendReconnectUntil( - new Promise((resolve) => { - this.__visiblePromiseResolve = resolve; - }) - ); - // We close the connection to Home Assistant after being hidden for 5 minutes - this.__hiddenTimeout = window.setTimeout(() => { - this.__hiddenTimeout = undefined; - this.hass!.connection.suspend(); - }, 300000); - } else { - // Clear timer to close the connection - if (this.__hiddenTimeout) { - clearTimeout(this.__hiddenTimeout); - this.__hiddenTimeout = undefined; - } - // Unsuspend the reconnect - if (this.__visiblePromiseResolve) { - this.__visiblePromiseResolve(); - this.__visiblePromiseResolve = undefined; - } - } - } - }; From 3ea750600371864e9640ce2aedebe59ad96fd9ae Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 23 Jun 2020 09:04:49 -0700 Subject: [PATCH 24/24] Bumped version to 20200623.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f369f06592..bdc1cc932a 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20200622.0", + version="20200623.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors",