mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-27 23:07:20 +00:00
Add humidifier entity integration
This commit is contained in:
parent
362236d7df
commit
ec5d7508c9
@ -37,6 +37,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
|||||||
"fan",
|
"fan",
|
||||||
"group",
|
"group",
|
||||||
"history_graph",
|
"history_graph",
|
||||||
|
"humidifier",
|
||||||
"input_datetime",
|
"input_datetime",
|
||||||
"light",
|
"light",
|
||||||
"lock",
|
"lock",
|
||||||
|
@ -22,6 +22,7 @@ const fixedIcons = {
|
|||||||
history_graph: "hass:chart-line",
|
history_graph: "hass:chart-line",
|
||||||
homeassistant: "hass:home-assistant",
|
homeassistant: "hass:home-assistant",
|
||||||
homekit: "hass:home-automation",
|
homekit: "hass:home-automation",
|
||||||
|
humidifier: "hass:air-humidifier",
|
||||||
image_processing: "hass:image-filter-frames",
|
image_processing: "hass:image-filter-frames",
|
||||||
input_boolean: "hass:toggle-switch-outline",
|
input_boolean: "hass:toggle-switch-outline",
|
||||||
input_datetime: "hass:calendar-clock",
|
input_datetime: "hass:calendar-clock",
|
||||||
|
@ -8,6 +8,7 @@ export const iconColorCSS = css`
|
|||||||
ha-icon[data-domain="camera"][data-state="streaming"],
|
ha-icon[data-domain="camera"][data-state="streaming"],
|
||||||
ha-icon[data-domain="cover"][data-state="open"],
|
ha-icon[data-domain="cover"][data-state="open"],
|
||||||
ha-icon[data-domain="fan"][data-state="on"],
|
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="light"][data-state="on"],
|
||||||
ha-icon[data-domain="input_boolean"][data-state="on"],
|
ha-icon[data-domain="input_boolean"][data-state="on"],
|
||||||
ha-icon[data-domain="lock"][data-state="unlocked"],
|
ha-icon[data-domain="lock"][data-state="unlocked"],
|
||||||
|
142
src/components/ha-humidifier-control.js
Normal file
142
src/components/ha-humidifier-control.js
Normal file
@ -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`
|
||||||
|
<style include="iron-flex iron-flex-alignment"></style>
|
||||||
|
<style>
|
||||||
|
/* local DOM styles go here */
|
||||||
|
:host {
|
||||||
|
@apply --layout-flex;
|
||||||
|
@apply --layout-horizontal;
|
||||||
|
@apply --layout-justified;
|
||||||
|
}
|
||||||
|
.in-flux#humidity {
|
||||||
|
color: var(--google-red-500);
|
||||||
|
}
|
||||||
|
#humidity {
|
||||||
|
@apply --layout-self-center;
|
||||||
|
font-size: 200%;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
.control-buttons {
|
||||||
|
font-size: 200%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
--mdc-icon-size: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- local DOM goes here -->
|
||||||
|
<div id="humidity">[[value]] [[units]]</div>
|
||||||
|
<div class="control-buttons">
|
||||||
|
<div>
|
||||||
|
<ha-icon-button
|
||||||
|
icon="hass:chevron-up"
|
||||||
|
on-click="incrementValue"
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ha-icon-button
|
||||||
|
icon="hass:chevron-down"
|
||||||
|
on-click="decrementValue"
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
83
src/components/ha-humidifier-state.js
Normal file
83
src/components/ha-humidifier-state.js
Normal file
@ -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`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="target">
|
||||||
|
<template is="dom-if" if="[[_hasKnownState(stateObj.state)]]">
|
||||||
|
<span class="state-label">
|
||||||
|
[[_localizeState(localize, stateObj.state)]]
|
||||||
|
<template is="dom-if" if="[[_renderMode(stateObj.attributes)]]">
|
||||||
|
- [[_localizeMode(localize, stateObj.attributes.mode)]]
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<div class="unit">[[computeTarget(stateObj.attributes.humidity)]]</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
@ -262,6 +262,28 @@ class StateHistoryChartLine extends LocalizeMixin(PolymerElement) {
|
|||||||
pushData(new Date(state.last_changed), series);
|
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 {
|
} else {
|
||||||
// Only disable interpolation for sensors
|
// Only disable interpolation for sensors
|
||||||
const isStep = domain === "sensor";
|
const isStep = domain === "sensor";
|
||||||
|
@ -5,13 +5,14 @@ import { computeStateName } from "../common/entity/compute_state_name";
|
|||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { HomeAssistant } from "../types";
|
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 = [
|
const LINE_ATTRIBUTES_TO_KEEP = [
|
||||||
"temperature",
|
"temperature",
|
||||||
"current_temperature",
|
"current_temperature",
|
||||||
"target_temp_low",
|
"target_temp_low",
|
||||||
"target_temp_high",
|
"target_temp_high",
|
||||||
"hvac_action",
|
"hvac_action",
|
||||||
|
"humidity",
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface LineChartState {
|
export interface LineChartState {
|
||||||
@ -221,6 +222,8 @@ export const computeHistory = (
|
|||||||
unit = hass.config.unit_system.temperature;
|
unit = hass.config.unit_system.temperature;
|
||||||
} else if (computeStateDomain(stateInfo[0]) === "water_heater") {
|
} else if (computeStateDomain(stateInfo[0]) === "water_heater") {
|
||||||
unit = hass.config.unit_system.temperature;
|
unit = hass.config.unit_system.temperature;
|
||||||
|
} else if (computeStateDomain(stateInfo[0]) === "humidifier") {
|
||||||
|
unit = "%";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!unit) {
|
if (!unit) {
|
||||||
|
19
src/data/humidifier.ts
Normal file
19
src/data/humidifier.ts
Normal file
@ -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";
|
@ -14,6 +14,7 @@ import "./more-info-default";
|
|||||||
import "./more-info-fan";
|
import "./more-info-fan";
|
||||||
import "./more-info-group";
|
import "./more-info-group";
|
||||||
import "./more-info-history_graph";
|
import "./more-info-history_graph";
|
||||||
|
import "./more-info-humidifier";
|
||||||
import "./more-info-input_datetime";
|
import "./more-info-input_datetime";
|
||||||
import "./more-info-light";
|
import "./more-info-light";
|
||||||
import "./more-info-lock";
|
import "./more-info-lock";
|
||||||
|
219
src/dialogs/more-info/controls/more-info-humidifier.ts
Normal file
219
src/dialogs/more-info/controls/more-info-humidifier.ts
Normal file
@ -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`
|
||||||
|
<div
|
||||||
|
class=${classMap({
|
||||||
|
"has-modes": supportModes,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div class="container-humidity">
|
||||||
|
<div>${hass.localize("ui.card.humidifier.humidity")}</div>
|
||||||
|
<div class="single-row">
|
||||||
|
<div class="target-humidity">
|
||||||
|
${stateObj.attributes.humidity} %
|
||||||
|
</div>
|
||||||
|
<ha-paper-slider
|
||||||
|
class="humidity"
|
||||||
|
step="1"
|
||||||
|
pin
|
||||||
|
ignore-bar-touch
|
||||||
|
dir=${rtlDirection}
|
||||||
|
.min=${stateObj.attributes.min_humidity}
|
||||||
|
.max=${stateObj.attributes.max_humidity}
|
||||||
|
.secondaryProgress=${stateObj.attributes.max_humidity}
|
||||||
|
.value=${stateObj.attributes.humidity}
|
||||||
|
@change=${this._targetHumiditySliderChanged}
|
||||||
|
>
|
||||||
|
</ha-paper-slider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${supportModes
|
||||||
|
? html`
|
||||||
|
<div class="container-preset_modes">
|
||||||
|
<ha-paper-dropdown-menu
|
||||||
|
label-float
|
||||||
|
dynamic-align
|
||||||
|
.label=${hass.localize("ui.card.humidifier.mode")}
|
||||||
|
>
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
attr-for-selected="item-name"
|
||||||
|
.selected=${stateObj.attributes.mode}
|
||||||
|
@selected-changed=${this._handleModeChanged}
|
||||||
|
>
|
||||||
|
${stateObj.attributes.available_modes!.map(
|
||||||
|
(mode) => html`
|
||||||
|
<paper-item item-name=${mode}>
|
||||||
|
${hass.localize(
|
||||||
|
`state_attributes.humidifier.mode.${mode}`
|
||||||
|
) || mode}
|
||||||
|
</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</ha-paper-dropdown-menu>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
397
src/panels/lovelace/cards/hui-humidifier-card.ts
Normal file
397
src/panels/lovelace/cards/hui-humidifier-card.ts
Normal file
@ -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<LovelaceCardEditor> {
|
||||||
|
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`
|
||||||
|
<hui-warning>
|
||||||
|
${createEntityNotFoundWarning(this.hass, this._config.entity)}
|
||||||
|
</hui-warning>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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` <round-slider disabled="true"></round-slider> `
|
||||||
|
: html`
|
||||||
|
<round-slider
|
||||||
|
.value=${targetHumidity}
|
||||||
|
.min=${stateObj.attributes.min_humidity}
|
||||||
|
.max=${stateObj.attributes.max_humidity}
|
||||||
|
.rtl=${rtlDirection === "rtl"}
|
||||||
|
.step="1"
|
||||||
|
@value-changing=${this._dragEvent}
|
||||||
|
@value-changed=${this._setHumidity}
|
||||||
|
></round-slider>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const setValues = svg`
|
||||||
|
<svg viewBox="0 0 40 20">
|
||||||
|
<text
|
||||||
|
x="50%"
|
||||||
|
dx="1"
|
||||||
|
y="60%"
|
||||||
|
text-anchor="middle"
|
||||||
|
style="font-size: 13px;"
|
||||||
|
class="set-value"
|
||||||
|
>
|
||||||
|
${
|
||||||
|
stateObj.state === UNAVAILABLE
|
||||||
|
? this.hass.localize("state.default.unavailable")
|
||||||
|
: this._setHum === undefined || this._setHum === null
|
||||||
|
? ""
|
||||||
|
: svg`
|
||||||
|
${this._setHum.toFixed()}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
|
||||||
|
%
|
||||||
|
</tspan>
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
<svg id="set-values">
|
||||||
|
<g>
|
||||||
|
<text
|
||||||
|
dy="22"
|
||||||
|
text-anchor="middle"
|
||||||
|
id="set-mode"
|
||||||
|
>
|
||||||
|
${this.hass!.localize(`state.default.${stateObj.state}`)}
|
||||||
|
${
|
||||||
|
stateObj.attributes.mode
|
||||||
|
? html`
|
||||||
|
-
|
||||||
|
${this.hass!.localize(
|
||||||
|
`state_attributes.humidifier.mode.${stateObj.attributes.mode}`
|
||||||
|
) || stateObj.attributes.mode}
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
<ha-icon-button
|
||||||
|
icon="hass:dots-vertical"
|
||||||
|
class="more-info"
|
||||||
|
@click=${this._handleMoreInfo}
|
||||||
|
tabindex="0"
|
||||||
|
></ha-icon-button>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div id="controls">
|
||||||
|
<div id="slider">
|
||||||
|
${slider}
|
||||||
|
<div id="slider-center">
|
||||||
|
<div id="humidity">
|
||||||
|
${setValues}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="info">
|
||||||
|
${name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -133,6 +133,12 @@ export interface GlanceCardConfig extends LovelaceCardConfig {
|
|||||||
state_color?: boolean;
|
state_color?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HumidifierCardConfig extends LovelaceCardConfig {
|
||||||
|
entity: string;
|
||||||
|
theme?: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IframeCardConfig extends LovelaceCardConfig {
|
export interface IframeCardConfig extends LovelaceCardConfig {
|
||||||
aspect_ratio?: string;
|
aspect_ratio?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -37,6 +37,7 @@ import { GroupEntity, HomeAssistant } from "../../../types";
|
|||||||
import {
|
import {
|
||||||
AlarmPanelCardConfig,
|
AlarmPanelCardConfig,
|
||||||
EntitiesCardConfig,
|
EntitiesCardConfig,
|
||||||
|
HumidifierCardConfig,
|
||||||
LightCardConfig,
|
LightCardConfig,
|
||||||
PictureEntityCardConfig,
|
PictureEntityCardConfig,
|
||||||
ThermostatCardConfig,
|
ThermostatCardConfig,
|
||||||
@ -150,6 +151,12 @@ export const computeCards = (
|
|||||||
refresh_interval: stateObj.attributes.refresh,
|
refresh_interval: stateObj.attributes.refresh,
|
||||||
};
|
};
|
||||||
cards.push(cardConfig);
|
cards.push(cardConfig);
|
||||||
|
} else if (domain === "humidifier") {
|
||||||
|
const cardConfig: HumidifierCardConfig = {
|
||||||
|
type: "humidifier",
|
||||||
|
entity: entityId,
|
||||||
|
};
|
||||||
|
cards.push(cardConfig);
|
||||||
} else if (domain === "light" && single) {
|
} else if (domain === "light" && single) {
|
||||||
const cardConfig: LightCardConfig = {
|
const cardConfig: LightCardConfig = {
|
||||||
type: "light",
|
type: "light",
|
||||||
|
@ -6,6 +6,7 @@ import "../cards/hui-entity-card";
|
|||||||
import "../cards/hui-glance-card";
|
import "../cards/hui-glance-card";
|
||||||
import "../cards/hui-history-graph-card";
|
import "../cards/hui-history-graph-card";
|
||||||
import "../cards/hui-horizontal-stack-card";
|
import "../cards/hui-horizontal-stack-card";
|
||||||
|
import "../cards/hui-humidifier-card";
|
||||||
import "../cards/hui-light-card";
|
import "../cards/hui-light-card";
|
||||||
import "../cards/hui-sensor-card";
|
import "../cards/hui-sensor-card";
|
||||||
import "../cards/hui-thermostat-card";
|
import "../cards/hui-thermostat-card";
|
||||||
@ -24,6 +25,7 @@ const ALWAYS_LOADED_TYPES = new Set([
|
|||||||
"glance",
|
"glance",
|
||||||
"history-graph",
|
"history-graph",
|
||||||
"horizontal-stack",
|
"horizontal-stack",
|
||||||
|
"humidifier",
|
||||||
"light",
|
"light",
|
||||||
"sensor",
|
"sensor",
|
||||||
"thermostat",
|
"thermostat",
|
||||||
|
@ -24,6 +24,7 @@ const LAZY_LOAD_TYPES = {
|
|||||||
"climate-entity": () => import("../entity-rows/hui-climate-entity-row"),
|
"climate-entity": () => import("../entity-rows/hui-climate-entity-row"),
|
||||||
"cover-entity": () => import("../entity-rows/hui-cover-entity-row"),
|
"cover-entity": () => import("../entity-rows/hui-cover-entity-row"),
|
||||||
"group-entity": () => import("../entity-rows/hui-group-entity-row"),
|
"group-entity": () => import("../entity-rows/hui-group-entity-row"),
|
||||||
|
"humidifier-entity": () => import("../entity-rows/hui-humidifier-entity-row"),
|
||||||
"input-datetime-entity": () =>
|
"input-datetime-entity": () =>
|
||||||
import("../entity-rows/hui-input-datetime-entity-row"),
|
import("../entity-rows/hui-input-datetime-entity-row"),
|
||||||
"input-number-entity": () =>
|
"input-number-entity": () =>
|
||||||
@ -51,6 +52,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
|
|||||||
cover: "cover",
|
cover: "cover",
|
||||||
fan: "toggle",
|
fan: "toggle",
|
||||||
group: "group",
|
group: "group",
|
||||||
|
humidifier: "humidifier",
|
||||||
input_boolean: "toggle",
|
input_boolean: "toggle",
|
||||||
input_number: "input-number",
|
input_number: "input-number",
|
||||||
input_select: "input-select",
|
input_select: "input-select",
|
||||||
|
@ -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}
|
||||||
|
<div class="card-config">
|
||||||
|
<ha-entity-picker
|
||||||
|
.label="${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.generic.entity"
|
||||||
|
)} (${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.config.required"
|
||||||
|
)})"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value="${this._entity}"
|
||||||
|
.configValue=${"entity"}
|
||||||
|
.includeDomains=${includeDomains}
|
||||||
|
@change="${this._valueChanged}"
|
||||||
|
allow-custom-entity
|
||||||
|
></ha-entity-picker>
|
||||||
|
<paper-input
|
||||||
|
.label="${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.generic.name"
|
||||||
|
)} (${this.hass.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.config.optional"
|
||||||
|
)})"
|
||||||
|
.value="${this._name}"
|
||||||
|
.configValue="${"name"}"
|
||||||
|
@value-changed="${this._valueChanged}"
|
||||||
|
></paper-input>
|
||||||
|
<hui-theme-select-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value="${this._theme}"
|
||||||
|
.configValue="${"theme"}"
|
||||||
|
@value-changed="${this._valueChanged}"
|
||||||
|
></hui-theme-select-editor>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||||
|
if (!this._config || !this.hass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const target = ev.target! as EditorTarget;
|
||||||
|
|
||||||
|
if (this[`_${target.configValue}`] === target.value) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,10 @@ export const coreCards: Card[] = [
|
|||||||
type: "history-graph",
|
type: "history-graph",
|
||||||
showElement: true,
|
showElement: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "humidifier",
|
||||||
|
showElement: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "light",
|
type: "light",
|
||||||
showElement: true,
|
showElement: true,
|
||||||
|
74
src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts
Normal file
74
src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts
Normal file
@ -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`
|
||||||
|
<hui-warning>
|
||||||
|
${createEntityNotFoundWarning(this.hass, this._config.entity)}
|
||||||
|
</hui-warning>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||||
|
<ha-humidifier-state
|
||||||
|
.hass=${this.hass}
|
||||||
|
.stateObj=${stateObj}
|
||||||
|
></ha-humidifier-state>
|
||||||
|
</hui-generic-entity-row>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
ha-humidifier-state {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-humidifier-entity-row": HuiHumidifierEntityRow;
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,19 @@
|
|||||||
"idle": "Idle",
|
"idle": "Idle",
|
||||||
"fan": "Fan"
|
"fan": "Fan"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"humidifier": {
|
||||||
|
"mode": {
|
||||||
|
"normal": "Normal",
|
||||||
|
"eco": "Eco",
|
||||||
|
"away": "Away",
|
||||||
|
"boost": "Boost",
|
||||||
|
"comfort": "Comfort",
|
||||||
|
"home": "Home",
|
||||||
|
"sleep": "Sleep",
|
||||||
|
"auto": "Auto",
|
||||||
|
"baby": "Baby"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"state_badge": {
|
"state_badge": {
|
||||||
@ -146,6 +159,11 @@
|
|||||||
"forward": "Forward",
|
"forward": "Forward",
|
||||||
"reverse": "Reverse"
|
"reverse": "Reverse"
|
||||||
},
|
},
|
||||||
|
"humidifier": {
|
||||||
|
"mode": "Mode",
|
||||||
|
"target_humidity_entity": "{name} target humidity",
|
||||||
|
"on_entity": "{name} on"
|
||||||
|
},
|
||||||
"light": {
|
"light": {
|
||||||
"brightness": "Brightness",
|
"brightness": "Brightness",
|
||||||
"color_temperature": "Color temperature",
|
"color_temperature": "Color temperature",
|
||||||
@ -1856,6 +1874,10 @@
|
|||||||
"name": "Horizontal Stack",
|
"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."
|
"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": {
|
"iframe": {
|
||||||
"name": "Webpage",
|
"name": "Webpage",
|
||||||
"description": "The Webpage card allows you to embed your favorite webpage right into Home Assistant."
|
"description": "The Webpage card allows you to embed your favorite webpage right into Home Assistant."
|
||||||
|
@ -89,7 +89,7 @@ hassAttributeUtil.LOGIC_STATE_ATTRIBUTES = hassAttributeUtil.LOGIC_STATE_ATTRIBU
|
|||||||
type: "array",
|
type: "array",
|
||||||
options: hassAttributeUtil.DOMAIN_DEVICE_CLASS,
|
options: hassAttributeUtil.DOMAIN_DEVICE_CLASS,
|
||||||
description: "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" },
|
hidden: { type: "boolean", description: "Hide from UI" },
|
||||||
assumed_state: {
|
assumed_state: {
|
||||||
@ -100,6 +100,7 @@ hassAttributeUtil.LOGIC_STATE_ATTRIBUTES = hassAttributeUtil.LOGIC_STATE_ATTRIBU
|
|||||||
"cover",
|
"cover",
|
||||||
"climate",
|
"climate",
|
||||||
"fan",
|
"fan",
|
||||||
|
"humidifier",
|
||||||
"group",
|
"group",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
],
|
],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user