mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Merge pull request #6214 from home-assistant/dev
This commit is contained in:
commit
5268afabdb
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20200622.0",
|
version="20200623.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -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",
|
||||||
@ -79,6 +80,7 @@ export const DOMAINS_TOGGLE = new Set([
|
|||||||
"switch",
|
"switch",
|
||||||
"group",
|
"group",
|
||||||
"automation",
|
"automation",
|
||||||
|
"humidifier",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** Temperature units. */
|
/** Temperature units. */
|
||||||
|
@ -55,6 +55,12 @@ export const computeStateDisplay = (
|
|||||||
return formatDateTime(date, language);
|
return formatDateTime(date, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (domain === "humidifier") {
|
||||||
|
if (stateObj.state === "on" && stateObj.attributes.humidity) {
|
||||||
|
return `${stateObj.attributes.humidity}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Return device class translation
|
// Return device class translation
|
||||||
(stateObj.attributes.device_class &&
|
(stateObj.attributes.device_class &&
|
||||||
|
@ -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"],
|
||||||
|
@ -22,7 +22,7 @@ const isOn = (stateObj?: HassEntity) =>
|
|||||||
!STATES_OFF.includes(stateObj.state) &&
|
!STATES_OFF.includes(stateObj.state) &&
|
||||||
!UNAVAILABLE_STATES.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
|
// hass is not a property so that we only re-render on stateObj changes
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@ -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,15 @@ 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",
|
||||||
|
"mode",
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface LineChartState {
|
export interface LineChartState {
|
||||||
@ -224,6 +226,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";
|
||||||
|
218
src/dialogs/more-info/controls/more-info-humidifier.ts
Normal file
218
src/dialogs/more-info/controls/more-info-humidifier.ts
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
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-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-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);
|
@ -22,6 +22,10 @@ export class HomeAssistantAppEl extends HassElement {
|
|||||||
|
|
||||||
private _haVersion?: string;
|
private _haVersion?: string;
|
||||||
|
|
||||||
|
private _hiddenTimeout?: number;
|
||||||
|
|
||||||
|
private _visiblePromiseResolve?: () => void;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const hass = this.hass;
|
const hass = this.hass;
|
||||||
|
|
||||||
@ -71,6 +75,12 @@ export class HomeAssistantAppEl extends HassElement {
|
|||||||
super.hassConnected();
|
super.hassConnected();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this._loadHassTranslations(this.hass!.language, "state");
|
this._loadHassTranslations(this.hass!.language, "state");
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
"visibilitychange",
|
||||||
|
() => this.__handleVisibilityChange(),
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected hassReconnected() {
|
protected hassReconnected() {
|
||||||
@ -137,6 +147,33 @@ export class HomeAssistantAppEl extends HassElement {
|
|||||||
? route.path.substr(1)
|
? route.path.substr(1)
|
||||||
: route.path.substr(1, dividerPos - 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 {
|
declare global {
|
||||||
|
392
src/panels/lovelace/cards/hui-humidifier-card.ts
Normal file
392
src/panels/lovelace/cards/hui-humidifier-card.ts
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
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 { 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_STATES } from "../../../data/entity";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 = UNAVAILABLE_STATES.includes(stateObj.state)
|
||||||
|
? 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"
|
||||||
|
>
|
||||||
|
${
|
||||||
|
UNAVAILABLE_STATES.includes(stateObj.state) ||
|
||||||
|
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 &&
|
||||||
|
!UNAVAILABLE_STATES.includes(stateObj.state)
|
||||||
|
? 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 {
|
||||||
|
if (UNAVAILABLE_STATES.includes(stateObj.state)) {
|
||||||
|
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",
|
||||||
|
@ -38,6 +38,7 @@ const LAZY_LOAD_TYPES = {
|
|||||||
"empty-state": () => import("../cards/hui-empty-state-card"),
|
"empty-state": () => import("../cards/hui-empty-state-card"),
|
||||||
starting: () => import("../cards/hui-starting-card"),
|
starting: () => import("../cards/hui-starting-card"),
|
||||||
"entity-filter": () => import("../cards/hui-entity-filter-card"),
|
"entity-filter": () => import("../cards/hui-entity-filter-card"),
|
||||||
|
humidifier: () => import("../cards/hui-humidifier-card"),
|
||||||
"media-control": () => import("../cards/hui-media-control-card"),
|
"media-control": () => import("../cards/hui-media-control-card"),
|
||||||
"picture-elements": () => import("../cards/hui-picture-elements-card"),
|
"picture-elements": () => import("../cards/hui-picture-elements-card"),
|
||||||
"picture-entity": () => import("../cards/hui-picture-entity-card"),
|
"picture-entity": () => import("../cards/hui-picture-entity-card"),
|
||||||
|
@ -51,6 +51,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
|
|||||||
cover: "cover",
|
cover: "cover",
|
||||||
fan: "toggle",
|
fan: "toggle",
|
||||||
group: "group",
|
group: "group",
|
||||||
|
humidifier: "toggle",
|
||||||
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,
|
||||||
|
@ -12,7 +12,6 @@ import SidebarMixin from "./sidebar-mixin";
|
|||||||
import ThemesMixin from "./themes-mixin";
|
import ThemesMixin from "./themes-mixin";
|
||||||
import TranslationsMixin from "./translations-mixin";
|
import TranslationsMixin from "./translations-mixin";
|
||||||
import { urlSyncMixin } from "./url-sync-mixin";
|
import { urlSyncMixin } from "./url-sync-mixin";
|
||||||
import { suspendMixin } from "./suspend-mixin";
|
|
||||||
|
|
||||||
const ext = <T extends Constructor>(baseClass: T, mixins): T =>
|
const ext = <T extends Constructor>(baseClass: T, mixins): T =>
|
||||||
mixins.reduceRight((base, mixin) => mixin(base), baseClass);
|
mixins.reduceRight((base, mixin) => mixin(base), baseClass);
|
||||||
@ -25,7 +24,6 @@ export class HassElement extends ext(HassBaseEl, [
|
|||||||
SidebarMixin,
|
SidebarMixin,
|
||||||
DisconnectToastMixin,
|
DisconnectToastMixin,
|
||||||
connectionMixin,
|
connectionMixin,
|
||||||
suspendMixin,
|
|
||||||
NotificationMixin,
|
NotificationMixin,
|
||||||
dialogManagerMixin,
|
dialogManagerMixin,
|
||||||
urlSyncMixin,
|
urlSyncMixin,
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import { Constructor } from "../types";
|
|
||||||
import { HassBaseEl } from "./hass-base-mixin";
|
|
||||||
|
|
||||||
export const suspendMixin = <T extends Constructor<HassBaseEl>>(
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -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,12 @@
|
|||||||
"forward": "Forward",
|
"forward": "Forward",
|
||||||
"reverse": "Reverse"
|
"reverse": "Reverse"
|
||||||
},
|
},
|
||||||
|
"humidifier": {
|
||||||
|
"humidity": "Target humidity",
|
||||||
|
"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",
|
||||||
@ -1935,6 +1954,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."
|
||||||
|
@ -37,6 +37,7 @@ hassAttributeUtil.DOMAIN_DEVICE_CLASS = {
|
|||||||
"shutter",
|
"shutter",
|
||||||
"window",
|
"window",
|
||||||
],
|
],
|
||||||
|
humidifier: ["dehumidifier", "humidifier"],
|
||||||
sensor: [
|
sensor: [
|
||||||
"battery",
|
"battery",
|
||||||
"humidity",
|
"humidity",
|
||||||
@ -89,7 +90,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 +101,7 @@ hassAttributeUtil.LOGIC_STATE_ATTRIBUTES = hassAttributeUtil.LOGIC_STATE_ATTRIBU
|
|||||||
"cover",
|
"cover",
|
||||||
"climate",
|
"climate",
|
||||||
"fan",
|
"fan",
|
||||||
|
"humidifier",
|
||||||
"group",
|
"group",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
],
|
],
|
||||||
|
@ -506,6 +506,11 @@
|
|||||||
"clear": "Ryd",
|
"clear": "Ryd",
|
||||||
"show_areas": "Vis områder"
|
"show_areas": "Vis områder"
|
||||||
},
|
},
|
||||||
|
"date-range-picker": {
|
||||||
|
"end_date": "Slutdato",
|
||||||
|
"select": "Vælg",
|
||||||
|
"start_date": "Startdato"
|
||||||
|
},
|
||||||
"device-picker": {
|
"device-picker": {
|
||||||
"clear": "Ryd",
|
"clear": "Ryd",
|
||||||
"device": "Enhed",
|
"device": "Enhed",
|
||||||
@ -695,6 +700,7 @@
|
|||||||
"zha_device_info": {
|
"zha_device_info": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"add": "Tilføj enheder",
|
"add": "Tilføj enheder",
|
||||||
|
"clusters": "Administrer klynger",
|
||||||
"reconfigure": "Genkonfigurer enhed",
|
"reconfigure": "Genkonfigurer enhed",
|
||||||
"remove": "Fjern enhed",
|
"remove": "Fjern enhed",
|
||||||
"zigbee_information": "Zigbee-oplysninger"
|
"zigbee_information": "Zigbee-oplysninger"
|
||||||
@ -1558,6 +1564,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mqtt": {
|
"mqtt": {
|
||||||
|
"button": "Konfigurer",
|
||||||
"description_listen": "Lyt til et emne",
|
"description_listen": "Lyt til et emne",
|
||||||
"description_publish": "Udsend en pakke",
|
"description_publish": "Udsend en pakke",
|
||||||
"listening_to": "Lytter til",
|
"listening_to": "Lytter til",
|
||||||
@ -2008,11 +2015,23 @@
|
|||||||
},
|
},
|
||||||
"history": {
|
"history": {
|
||||||
"period": "Periode",
|
"period": "Periode",
|
||||||
|
"ranges": {
|
||||||
|
"last_week": "Sidste uge",
|
||||||
|
"this_week": "Denne uge",
|
||||||
|
"today": "I dag",
|
||||||
|
"yesterday": "I går"
|
||||||
|
},
|
||||||
"showing_entries": "Viser poster for"
|
"showing_entries": "Viser poster for"
|
||||||
},
|
},
|
||||||
"logbook": {
|
"logbook": {
|
||||||
"entries_not_found": "Der blev ikke fundet nogen logbogsposter.",
|
"entries_not_found": "Der blev ikke fundet nogen logbogsposter.",
|
||||||
"period": "Periode",
|
"period": "Periode",
|
||||||
|
"ranges": {
|
||||||
|
"last_week": "Sidste uge",
|
||||||
|
"this_week": "Denne uge",
|
||||||
|
"today": "I dag",
|
||||||
|
"yesterday": "I går"
|
||||||
|
},
|
||||||
"showing_entries": "Viser poster for"
|
"showing_entries": "Viser poster for"
|
||||||
},
|
},
|
||||||
"lovelace": {
|
"lovelace": {
|
||||||
|
@ -506,6 +506,11 @@
|
|||||||
"clear": "Tøm",
|
"clear": "Tøm",
|
||||||
"show_areas": "Vis områder"
|
"show_areas": "Vis områder"
|
||||||
},
|
},
|
||||||
|
"date-range-picker": {
|
||||||
|
"end_date": "Sluttdato",
|
||||||
|
"select": "Velg",
|
||||||
|
"start_date": "Startdato"
|
||||||
|
},
|
||||||
"device-picker": {
|
"device-picker": {
|
||||||
"clear": "Tøm",
|
"clear": "Tøm",
|
||||||
"device": "Enhet",
|
"device": "Enhet",
|
||||||
@ -695,6 +700,7 @@
|
|||||||
"zha_device_info": {
|
"zha_device_info": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"add": "Legg til enheter via denne enheten",
|
"add": "Legg til enheter via denne enheten",
|
||||||
|
"clusters": "Behandle Clusters",
|
||||||
"reconfigure": "Rekonfigurer enhet",
|
"reconfigure": "Rekonfigurer enhet",
|
||||||
"remove": "Fjern enhet",
|
"remove": "Fjern enhet",
|
||||||
"zigbee_information": "Zigbee-enhetssignatur"
|
"zigbee_information": "Zigbee-enhetssignatur"
|
||||||
@ -1558,6 +1564,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mqtt": {
|
"mqtt": {
|
||||||
|
"button": "Konfigurer",
|
||||||
"description_listen": "Lytt til et emne",
|
"description_listen": "Lytt til et emne",
|
||||||
"description_publish": "Publiser en pakke",
|
"description_publish": "Publiser en pakke",
|
||||||
"listening_to": "Lytter til",
|
"listening_to": "Lytter til",
|
||||||
@ -1678,11 +1685,11 @@
|
|||||||
"core": "Last inn lokasjon og spesialtilpassinger på nytt",
|
"core": "Last inn lokasjon og spesialtilpassinger på nytt",
|
||||||
"group": "Last inn grupper på nytt",
|
"group": "Last inn grupper på nytt",
|
||||||
"heading": "YAML -Konfigurasjon lastes på nytt",
|
"heading": "YAML -Konfigurasjon lastes på nytt",
|
||||||
"input_boolean": "Last input booleans på nytt",
|
"input_boolean": "Last inn bolsk inndata på nytt",
|
||||||
"input_datetime": "Last input date på nytt",
|
"input_datetime": "Last inn dato inndata på nytt",
|
||||||
"input_number": "Las input numbers på nytt",
|
"input_number": "Last inn nummer inndata på nytt",
|
||||||
"input_select": "Last input selects på nytt ",
|
"input_select": "Last inn valg inndata på nytt ",
|
||||||
"input_text": "Last input texts 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.",
|
"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",
|
"person": "Last inn personer på nytt",
|
||||||
"scene": "Last inn scener på nytt",
|
"scene": "Last inn scener på nytt",
|
||||||
@ -1742,14 +1749,14 @@
|
|||||||
"system": ""
|
"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": {
|
"zha": {
|
||||||
"add_device_page": {
|
"add_device_page": {
|
||||||
"discovered_text": "Enheter vises her når de er oppdaget.",
|
"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.",
|
"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",
|
"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.",
|
"pairing_mode": "Kontroller at enhetene er i paringsmodus. Sjekk instruksjonene til enheten om hvordan du gjør dette.",
|
||||||
"search_again": "Søk på nytt",
|
"search_again": "Søk på nytt",
|
||||||
"spinner": "Søker etter ZHA Zigbee-enheter..."
|
"spinner": "Søker etter ZHA Zigbee-enheter..."
|
||||||
@ -2035,11 +2042,23 @@
|
|||||||
},
|
},
|
||||||
"history": {
|
"history": {
|
||||||
"period": "Periode",
|
"period": "Periode",
|
||||||
|
"ranges": {
|
||||||
|
"last_week": "Forrige uke",
|
||||||
|
"this_week": "Denne uken",
|
||||||
|
"today": "I dag",
|
||||||
|
"yesterday": "I går"
|
||||||
|
},
|
||||||
"showing_entries": "Viser oppføringer for"
|
"showing_entries": "Viser oppføringer for"
|
||||||
},
|
},
|
||||||
"logbook": {
|
"logbook": {
|
||||||
"entries_not_found": "Finner ingen loggbokoppføringer.",
|
"entries_not_found": "Finner ingen loggbokoppføringer.",
|
||||||
"period": "Periode",
|
"period": "Periode",
|
||||||
|
"ranges": {
|
||||||
|
"last_week": "Forrige uke",
|
||||||
|
"this_week": "Denne uken",
|
||||||
|
"today": "I dag",
|
||||||
|
"yesterday": "I går"
|
||||||
|
},
|
||||||
"showing_entries": "Viser oppføringer for"
|
"showing_entries": "Viser oppføringer for"
|
||||||
},
|
},
|
||||||
"lovelace": {
|
"lovelace": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user