mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 03:36:44 +00:00
Update water heater more info (#17544)
This commit is contained in:
parent
25b4d91d72
commit
aa85b87e11
@ -36,6 +36,7 @@ const STATE_COLORED_DOMAIN = new Set([
|
|||||||
"timer",
|
"timer",
|
||||||
"update",
|
"update",
|
||||||
"vacuum",
|
"vacuum",
|
||||||
|
"water_heater",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const stateColorCss = (stateObj: HassEntity, state?: string) => {
|
export const stateColorCss = (stateObj: HassEntity, state?: string) => {
|
||||||
|
70
src/data/water_heater.ts
Normal file
70
src/data/water_heater.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
mdiFinance,
|
||||||
|
mdiFireCircle,
|
||||||
|
mdiHeatWave,
|
||||||
|
mdiLeaf,
|
||||||
|
mdiLightningBolt,
|
||||||
|
mdiPower,
|
||||||
|
mdiRocketLaunch,
|
||||||
|
} from "@mdi/js";
|
||||||
|
import type {
|
||||||
|
HassEntityAttributeBase,
|
||||||
|
HassEntityBase,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
|
|
||||||
|
export const enum WaterHeaterEntityFeature {
|
||||||
|
TARGET_TEMPERATURE = 1,
|
||||||
|
OPERATION_MODE = 2,
|
||||||
|
AWAY_MODE = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OperationMode =
|
||||||
|
| "eco"
|
||||||
|
| "electric"
|
||||||
|
| "performance"
|
||||||
|
| "high_demand"
|
||||||
|
| "heat_pump"
|
||||||
|
| "gas"
|
||||||
|
| "off";
|
||||||
|
|
||||||
|
export type WaterHeaterEntity = HassEntityBase & {
|
||||||
|
attributes: HassEntityAttributeBase & {
|
||||||
|
target_temp_step?: number;
|
||||||
|
min_temp: number;
|
||||||
|
max_temp: number;
|
||||||
|
current_temperature?: number;
|
||||||
|
temperature?: number;
|
||||||
|
operation_mode: OperationMode;
|
||||||
|
operation_list: OperationMode[];
|
||||||
|
away_mode?: "on" | "off";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const hvacModeOrdering: { [key in OperationMode]: number } = {
|
||||||
|
eco: 1,
|
||||||
|
electric: 2,
|
||||||
|
performance: 3,
|
||||||
|
high_demand: 4,
|
||||||
|
heat_pump: 5,
|
||||||
|
gas: 6,
|
||||||
|
off: 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const compareWaterHeaterOperationMode = (
|
||||||
|
mode1: OperationMode,
|
||||||
|
mode2: OperationMode
|
||||||
|
) => hvacModeOrdering[mode1] - hvacModeOrdering[mode2];
|
||||||
|
|
||||||
|
export const WATER_HEATER_OPERATION_MODE_ICONS: Record<OperationMode, string> =
|
||||||
|
{
|
||||||
|
eco: mdiLeaf,
|
||||||
|
electric: mdiLightningBolt,
|
||||||
|
performance: mdiRocketLaunch,
|
||||||
|
high_demand: mdiFinance,
|
||||||
|
heat_pump: mdiHeatWave,
|
||||||
|
gas: mdiFireCircle,
|
||||||
|
off: mdiPower,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const computeOperationModeIcon = (mode: OperationMode) =>
|
||||||
|
WATER_HEATER_OPERATION_MODE_ICONS[mode];
|
@ -0,0 +1,327 @@
|
|||||||
|
import { mdiMinus, mdiPlus } from "@mdi/js";
|
||||||
|
import {
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
|
import { stateColorCss } from "../../../../common/entity/state_color";
|
||||||
|
import { supportsFeature } from "../../../../common/entity/supports-feature";
|
||||||
|
import { clamp } from "../../../../common/number/clamp";
|
||||||
|
import { formatNumber } from "../../../../common/number/format_number";
|
||||||
|
import { debounce } from "../../../../common/util/debounce";
|
||||||
|
import "../../../../components/ha-control-circular-slider";
|
||||||
|
import "../../../../components/ha-outlined-icon-button";
|
||||||
|
import "../../../../components/ha-svg-icon";
|
||||||
|
import { UNAVAILABLE } from "../../../../data/entity";
|
||||||
|
import {
|
||||||
|
WaterHeaterEntity,
|
||||||
|
WaterHeaterEntityFeature,
|
||||||
|
} from "../../../../data/water_heater";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
|
@customElement("ha-more-info-water_heater-temperature")
|
||||||
|
export class HaMoreInfoWaterHeaterTemperature extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj!: WaterHeaterEntity;
|
||||||
|
|
||||||
|
@state() private _targetTemperature?: number;
|
||||||
|
|
||||||
|
protected willUpdate(changedProp: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProp);
|
||||||
|
if (changedProp.has("stateObj")) {
|
||||||
|
this._targetTemperature = this.stateObj.attributes.temperature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _step() {
|
||||||
|
return (
|
||||||
|
this.stateObj.attributes.target_temp_step ||
|
||||||
|
(this.hass.config.unit_system.temperature.indexOf("F") === -1 ? 0.5 : 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _min() {
|
||||||
|
return this.stateObj.attributes.min_temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _max() {
|
||||||
|
return this.stateObj.attributes.max_temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent) {
|
||||||
|
const value = (ev.detail as any).value;
|
||||||
|
if (isNaN(value)) return;
|
||||||
|
this._targetTemperature = value;
|
||||||
|
this._callService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanging(ev: CustomEvent) {
|
||||||
|
const value = (ev.detail as any).value;
|
||||||
|
if (isNaN(value)) return;
|
||||||
|
this._targetTemperature = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _debouncedCallService = debounce(() => this._callService(), 1000);
|
||||||
|
|
||||||
|
private _callService() {
|
||||||
|
this.hass.callService("water_heater", "set_temperature", {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
temperature: this._targetTemperature,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleButton(ev) {
|
||||||
|
const step = ev.currentTarget.step as number;
|
||||||
|
|
||||||
|
let temp = this._targetTemperature ?? this._min;
|
||||||
|
temp += step;
|
||||||
|
temp = clamp(temp, this._min, this._max);
|
||||||
|
|
||||||
|
this._targetTemperature = temp;
|
||||||
|
this._debouncedCallService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderLabel() {
|
||||||
|
return html`
|
||||||
|
<p class="action">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.water_heater.target"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderButtons() {
|
||||||
|
return html`
|
||||||
|
<div class="buttons">
|
||||||
|
<ha-outlined-icon-button
|
||||||
|
.step=${-this._step}
|
||||||
|
@click=${this._handleButton}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiMinus}></ha-svg-icon>
|
||||||
|
</ha-outlined-icon-button>
|
||||||
|
<ha-outlined-icon-button
|
||||||
|
.step=${this._step}
|
||||||
|
@click=${this._handleButton}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
</ha-outlined-icon-button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderTargetTemperature(temperature: number) {
|
||||||
|
const digits = this._step.toString().split(".")?.[1]?.length ?? 0;
|
||||||
|
const formatted = formatNumber(temperature, this.hass.locale, {
|
||||||
|
maximumFractionDigits: digits,
|
||||||
|
minimumFractionDigits: digits,
|
||||||
|
});
|
||||||
|
const [temperatureInteger] = formatted.includes(".")
|
||||||
|
? formatted.split(".")
|
||||||
|
: formatted.split(",");
|
||||||
|
|
||||||
|
const temperatureDecimal = formatted.replace(temperatureInteger, "");
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<p class="temperature">
|
||||||
|
<span aria-hidden="true">
|
||||||
|
${temperatureInteger}
|
||||||
|
${digits !== 0
|
||||||
|
? html`<span class="decimal">${temperatureDecimal}</span>`
|
||||||
|
: nothing}
|
||||||
|
<span class="unit">
|
||||||
|
${this.hass.config.unit_system.temperature}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="visually-hidden">
|
||||||
|
${this.stateObj.attributes.temperature}
|
||||||
|
${this.hass.config.unit_system.temperature}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const supportsTargetTemperature = supportsFeature(
|
||||||
|
this.stateObj,
|
||||||
|
WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||||
|
);
|
||||||
|
|
||||||
|
const mainColor = stateColorCss(this.stateObj);
|
||||||
|
|
||||||
|
if (supportsTargetTemperature && this._targetTemperature != null) {
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="container"
|
||||||
|
style=${styleMap({
|
||||||
|
"--main-color": mainColor,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ha-control-circular-slider
|
||||||
|
.value=${this._targetTemperature}
|
||||||
|
.min=${this._min}
|
||||||
|
.max=${this._max}
|
||||||
|
.step=${this._step}
|
||||||
|
.current=${this.stateObj.attributes.current_temperature}
|
||||||
|
.disabled=${this.stateObj!.state === UNAVAILABLE}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
@value-changing=${this._valueChanging}
|
||||||
|
>
|
||||||
|
</ha-control-circular-slider>
|
||||||
|
<div class="info">
|
||||||
|
<div class="action-container">${this._renderLabel()}</div>
|
||||||
|
<div class="temperature-container">
|
||||||
|
${this._renderTargetTemperature(this._targetTemperature)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${this._renderButtons()}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="container">
|
||||||
|
<ha-control-circular-slider
|
||||||
|
.current=${this.stateObj.attributes.current_temperature}
|
||||||
|
.min=${this._min}
|
||||||
|
.max=${this._max}
|
||||||
|
.step=${this._step}
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
</ha-control-circular-slider>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
/* Layout */
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
}
|
||||||
|
.info * {
|
||||||
|
margin: 0;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
/* Elements */
|
||||||
|
.temperature-container {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.temperature {
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: 58px;
|
||||||
|
line-height: 64px;
|
||||||
|
letter-spacing: -0.25px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.temperature span {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
.temperature .unit {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
.temperature .decimal {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 40px;
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-right: -18px;
|
||||||
|
}
|
||||||
|
.action-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 200px;
|
||||||
|
height: 48px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.action {
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--action-color, inherit);
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 120px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.buttons ha-outlined-icon-button {
|
||||||
|
--md-outlined-icon-button-container-size: 48px;
|
||||||
|
--md-outlined-icon-button-icon-size: 24px;
|
||||||
|
}
|
||||||
|
/* Accessibility */
|
||||||
|
.visually-hidden {
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
width: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
/* Slider */
|
||||||
|
ha-control-circular-slider {
|
||||||
|
--control-circular-slider-color: var(
|
||||||
|
--main-color,
|
||||||
|
var(--disabled-color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ha-control-circular-slider::after {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -10%;
|
||||||
|
left: -10%;
|
||||||
|
right: -10%;
|
||||||
|
bottom: -10%;
|
||||||
|
background: radial-gradient(
|
||||||
|
50% 50% at 50% 50%,
|
||||||
|
var(--action-color, transparent) 0%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
opacity: 0.15;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-more-info-water_heater-temperature": HaMoreInfoWaterHeaterTemperature;
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,7 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [
|
|||||||
"lock",
|
"lock",
|
||||||
"siren",
|
"siren",
|
||||||
"switch",
|
"switch",
|
||||||
|
"water_heater",
|
||||||
];
|
];
|
||||||
/** Domains with separate more info dialog. */
|
/** Domains with separate more info dialog. */
|
||||||
export const DOMAINS_WITH_MORE_INFO = [
|
export const DOMAINS_WITH_MORE_INFO = [
|
||||||
|
@ -1,235 +0,0 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
|
||||||
import { timeOut } from "@polymer/polymer/lib/utils/async";
|
|
||||||
import { Debouncer } from "@polymer/polymer/lib/utils/debounce";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { featureClassNames } from "../../../common/entity/feature_class_names";
|
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
|
||||||
import "../../../components/ha-select";
|
|
||||||
import "../../../components/ha-switch";
|
|
||||||
import "../../../components/ha-water_heater-control";
|
|
||||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex"></style>
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-select {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-water_heater-control.range-control-left,
|
|
||||||
ha-water_heater-control.range-control-right {
|
|
||||||
float: left;
|
|
||||||
width: 46%;
|
|
||||||
}
|
|
||||||
ha-water_heater-control.range-control-left {
|
|
||||||
margin-right: 4%;
|
|
||||||
}
|
|
||||||
ha-water_heater-control.range-control-right {
|
|
||||||
margin-left: 4%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.single-row {
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class$="[[computeClassNames(stateObj)]]">
|
|
||||||
<div class="container-temperature">
|
|
||||||
<div class$="[[stateObj.attributes.operation_mode]]">
|
|
||||||
<div hidden$="[[!supportsTemperatureControls(stateObj)]]">
|
|
||||||
[[localize('ui.card.water_heater.target_temperature')]]
|
|
||||||
</div>
|
|
||||||
<template is="dom-if" if="[[supportsTemperature(stateObj)]]">
|
|
||||||
<ha-water_heater-control
|
|
||||||
value="[[stateObj.attributes.temperature]]"
|
|
||||||
units="[[hass.config.unit_system.temperature]]"
|
|
||||||
step="[[computeTemperatureStepSize(hass, stateObj)]]"
|
|
||||||
min="[[stateObj.attributes.min_temp]]"
|
|
||||||
max="[[stateObj.attributes.max_temp]]"
|
|
||||||
on-change="targetTemperatureChanged"
|
|
||||||
>
|
|
||||||
</ha-water_heater-control>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[supportsOperationMode(stateObj)]]">
|
|
||||||
<div class="container-operation_list">
|
|
||||||
<div class="controls">
|
|
||||||
<ha-select
|
|
||||||
label="[[localize('ui.card.water_heater.operation')]]"
|
|
||||||
value="[[stateObj.attributes.operation_mode]]"
|
|
||||||
on-selected="handleOperationmodeChanged"
|
|
||||||
fixedMenuPosition
|
|
||||||
naturalMenuWidth
|
|
||||||
on-closed="stopPropagation"
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
is="dom-repeat"
|
|
||||||
items="[[stateObj.attributes.operation_list]]"
|
|
||||||
>
|
|
||||||
<mwc-list-item value="[[item]]">
|
|
||||||
[[_localizeOperationMode(localize, item)]]
|
|
||||||
</mwc-list-item>
|
|
||||||
</template>
|
|
||||||
</ha-select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[supportsAwayMode(stateObj)]]">
|
|
||||||
<div class="container-away_mode">
|
|
||||||
<div class="center horizontal layout single-row">
|
|
||||||
<div class="flex">
|
|
||||||
[[localize('ui.card.water_heater.away_mode')]]
|
|
||||||
</div>
|
|
||||||
<ha-switch
|
|
||||||
checked="[[awayToggleChecked]]"
|
|
||||||
on-change="awayToggleChanged"
|
|
||||||
>
|
|
||||||
</ha-switch>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
stateObj: {
|
|
||||||
type: Object,
|
|
||||||
observer: "stateObjChanged",
|
|
||||||
},
|
|
||||||
|
|
||||||
awayToggleChecked: Boolean,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
stateObjChanged(newVal, oldVal) {
|
|
||||||
if (newVal) {
|
|
||||||
this.setProperties({
|
|
||||||
awayToggleChecked: newVal.attributes.away_mode === "on",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVal) {
|
|
||||||
this._debouncer = Debouncer.debounce(
|
|
||||||
this._debouncer,
|
|
||||||
timeOut.after(500),
|
|
||||||
() => {
|
|
||||||
this.fire("iron-resize");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
computeTemperatureStepSize(hass, stateObj) {
|
|
||||||
if (stateObj.attributes.target_temp_step) {
|
|
||||||
return stateObj.attributes.target_temp_step;
|
|
||||||
}
|
|
||||||
if (hass.config.unit_system.temperature.indexOf("F") !== -1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsTemperatureControls(stateObj) {
|
|
||||||
return this.supportsTemperature(stateObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsTemperature(stateObj) {
|
|
||||||
return (
|
|
||||||
supportsFeature(stateObj, 1) &&
|
|
||||||
typeof stateObj.attributes.temperature === "number"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsOperationMode(stateObj) {
|
|
||||||
return supportsFeature(stateObj, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsAwayMode(stateObj) {
|
|
||||||
return supportsFeature(stateObj, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeClassNames(stateObj) {
|
|
||||||
const _featureClassNames = {
|
|
||||||
1: "has-target_temperature",
|
|
||||||
2: "has-operation_mode",
|
|
||||||
4: "has-away_mode",
|
|
||||||
};
|
|
||||||
|
|
||||||
const classes = [featureClassNames(stateObj, _featureClassNames)];
|
|
||||||
|
|
||||||
classes.push("more-info-water_heater");
|
|
||||||
|
|
||||||
return classes.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
targetTemperatureChanged(ev) {
|
|
||||||
const temperature = ev.target.value;
|
|
||||||
if (temperature === this.stateObj.attributes.temperature) return;
|
|
||||||
this.callServiceHelper("set_temperature", { temperature: temperature });
|
|
||||||
}
|
|
||||||
|
|
||||||
awayToggleChanged(ev) {
|
|
||||||
const oldVal = this.stateObj.attributes.away_mode === "on";
|
|
||||||
const newVal = ev.target.checked;
|
|
||||||
if (oldVal === newVal) return;
|
|
||||||
this.callServiceHelper("set_away_mode", { away_mode: newVal });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOperationmodeChanged(ev) {
|
|
||||||
const oldVal = this.stateObj.attributes.operation_mode;
|
|
||||||
const newVal = ev.target.value;
|
|
||||||
if (!newVal || oldVal === newVal) return;
|
|
||||||
this.callServiceHelper("set_operation_mode", {
|
|
||||||
operation_mode: newVal,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
stopPropagation(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
callServiceHelper(service, data) {
|
|
||||||
// We call stateChanged after a successful call 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.
|
|
||||||
/* eslint-disable no-param-reassign */
|
|
||||||
data.entity_id = this.stateObj.entity_id;
|
|
||||||
/* eslint-enable no-param-reassign */
|
|
||||||
this.hass.callService("water_heater", service, data).then(() => {
|
|
||||||
this.stateObjChanged(this.stateObj);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_localizeOperationMode(localize, mode) {
|
|
||||||
return (
|
|
||||||
localize(`component.water_heater.entity_component._.state.${mode}`) ||
|
|
||||||
mode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("more-info-water_heater", MoreInfoWaterHeater);
|
|
262
src/dialogs/more-info/controls/more-info-water_heater.ts
Normal file
262
src/dialogs/more-info/controls/more-info-water_heater.ts
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
import { mdiAccount, mdiAccountArrowRight, mdiWaterBoiler } from "@mdi/js";
|
||||||
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
|
import { formatNumber } from "../../../common/number/format_number";
|
||||||
|
import "../../../components/ha-control-select-menu";
|
||||||
|
import "../../../components/ha-list-item";
|
||||||
|
import {
|
||||||
|
OperationMode,
|
||||||
|
WaterHeaterEntity,
|
||||||
|
WaterHeaterEntityFeature,
|
||||||
|
compareWaterHeaterOperationMode,
|
||||||
|
computeOperationModeIcon,
|
||||||
|
} from "../../../data/water_heater";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
|
||||||
|
import "../components/water_heater/ha-more-info-water_heater-temperature";
|
||||||
|
|
||||||
|
@customElement("more-info-water_heater")
|
||||||
|
class MoreInfoWaterHeater extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public stateObj?: WaterHeaterEntity;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.stateObj) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateObj = this.stateObj;
|
||||||
|
|
||||||
|
const supportOperationMode = supportsFeature(
|
||||||
|
stateObj,
|
||||||
|
WaterHeaterEntityFeature.OPERATION_MODE
|
||||||
|
);
|
||||||
|
|
||||||
|
const supportAwayMode = supportsFeature(
|
||||||
|
stateObj,
|
||||||
|
WaterHeaterEntityFeature.AWAY_MODE
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentTemperature = this.stateObj.attributes.current_temperature;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="current">
|
||||||
|
${currentTemperature != null
|
||||||
|
? html`
|
||||||
|
<div>
|
||||||
|
<p class="label">
|
||||||
|
${this.hass.formatEntityAttributeName(
|
||||||
|
this.stateObj,
|
||||||
|
"current_temperature"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p class="value">
|
||||||
|
${formatNumber(currentTemperature, this.hass.locale)}
|
||||||
|
${this.hass.config.unit_system.temperature}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<ha-more-info-water_heater-temperature
|
||||||
|
.hass=${this.hass}
|
||||||
|
.stateObj=${this.stateObj}
|
||||||
|
></ha-more-info-water_heater-temperature>
|
||||||
|
</div>
|
||||||
|
<div class="secondary-controls">
|
||||||
|
<div class="secondary-controls-scroll">
|
||||||
|
${supportOperationMode && stateObj.attributes.operation_list
|
||||||
|
? html`
|
||||||
|
<ha-control-select-menu
|
||||||
|
.label=${this.hass.formatEntityAttributeName(
|
||||||
|
stateObj,
|
||||||
|
"operation"
|
||||||
|
)}
|
||||||
|
.value=${stateObj.state}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
@selected=${this._handleOperationModeChanged}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon"
|
||||||
|
.path=${computeOperationModeIcon(
|
||||||
|
stateObj.state as OperationMode
|
||||||
|
) ?? mdiWaterBoiler}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${stateObj.attributes.operation_list
|
||||||
|
.concat()
|
||||||
|
.sort(compareWaterHeaterOperationMode)
|
||||||
|
.map(
|
||||||
|
(mode) => html`
|
||||||
|
<ha-list-item .value=${mode} graphic="icon">
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${computeOperationModeIcon(mode)}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.formatEntityState(stateObj, mode)}
|
||||||
|
</ha-list-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-control-select-menu>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
${supportAwayMode
|
||||||
|
? html`
|
||||||
|
<ha-control-select-menu
|
||||||
|
.label=${this.hass.formatEntityAttributeName(
|
||||||
|
stateObj,
|
||||||
|
"away_mode"
|
||||||
|
)}
|
||||||
|
.value=${stateObj.attributes.away_mode}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
@selected=${this._handleAwayModeChanged}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon"
|
||||||
|
.path=${stateObj.attributes.away_mode === "on"
|
||||||
|
? mdiAccountArrowRight
|
||||||
|
: mdiAccount}
|
||||||
|
></ha-svg-icon>
|
||||||
|
<ha-list-item .value=${"on"} graphic="icon">
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiAccountArrowRight}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.localize("state.default.on")}
|
||||||
|
</ha-list-item>
|
||||||
|
<ha-list-item .value=${"off"} graphic="icon">
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiAccount}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.localize("state.default.off")}
|
||||||
|
</ha-list-item>
|
||||||
|
</ha-control-select-menu>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleOperationModeChanged(ev) {
|
||||||
|
const newVal = ev.target.value;
|
||||||
|
this._callServiceHelper(
|
||||||
|
this.stateObj!.state,
|
||||||
|
newVal,
|
||||||
|
"set_operation_mode",
|
||||||
|
{
|
||||||
|
operation_mode: newVal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleAwayModeChanged(ev) {
|
||||||
|
const newVal = ev.target.value === "on";
|
||||||
|
const oldVal = this.stateObj!.attributes.away_mode === "on";
|
||||||
|
|
||||||
|
this._callServiceHelper(oldVal, newVal, "set_away_mode", {
|
||||||
|
away_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("water_heater", 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(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
moreInfoControlStyle,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current p {
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current .label {
|
||||||
|
opacity: 0.8;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current .value {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"more-info-water_heater": MoreInfoWaterHeater;
|
||||||
|
}
|
||||||
|
}
|
@ -176,6 +176,12 @@ documentContainer.innerHTML = `<custom-style>
|
|||||||
--state-sensor-battery-high-color: var(--green-color);
|
--state-sensor-battery-high-color: var(--green-color);
|
||||||
--state-sensor-battery-low-color: var(--red-color);
|
--state-sensor-battery-low-color: var(--red-color);
|
||||||
--state-sensor-battery-medium-color: var(--orange-color);
|
--state-sensor-battery-medium-color: var(--orange-color);
|
||||||
|
--state-water_heater-eco-color: var(--green-color);
|
||||||
|
--state-water_heater-electric-color: var(--orange-color);
|
||||||
|
--state-water_heater-gas-color: var(--orange-color);
|
||||||
|
--state-water_heater-heat_pump-color: var(--orange-color);
|
||||||
|
--state-water_heater-high_demand-color: var(--deep-orange-color);
|
||||||
|
--state-water_heater-performance-color: var(--deep-orange-color);
|
||||||
|
|
||||||
/* history colors */
|
/* history colors */
|
||||||
--history-unavailable-color: transparent;
|
--history-unavailable-color: transparent;
|
||||||
|
@ -1005,6 +1005,9 @@
|
|||||||
"humidifier": {
|
"humidifier": {
|
||||||
"target_label": "[%key:ui::dialogs::more_info_control::climate::target_label%]",
|
"target_label": "[%key:ui::dialogs::more_info_control::climate::target_label%]",
|
||||||
"target": "[%key:ui::dialogs::more_info_control::climate::target%]"
|
"target": "[%key:ui::dialogs::more_info_control::climate::target%]"
|
||||||
|
},
|
||||||
|
"water_heater": {
|
||||||
|
"target": "[%key:ui::dialogs::more_info_control::climate::target%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity_registry": {
|
"entity_registry": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user