mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Add new humidity control in humidifier more info (#17011)
* Add new humidity control in humidifier more info * Fix humidifier card * Some adjustments * Add current label * Some adjustments * Clean code * Remove unused code * Fix merge * Add current to main screen * Remove toggle * Add action * Update buttons * Add gallery * Some fixes * Add overflow * Update src/dialogs/more-info/components/humidifier/ha-more-info-humidifier-humidity.ts * Update src/dialogs/more-info/controls/more-info-humidifier.ts Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Use climate translation --------- Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
89e96e4681
commit
4986b013a2
3
gallery/src/pages/more-info/humidifier.markdown
Normal file
3
gallery/src/pages/more-info/humidifier.markdown
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Humidifier
|
||||||
|
---
|
57
gallery/src/pages/more-info/humidifier.ts
Normal file
57
gallery/src/pages/more-info/humidifier.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import "../../../../src/components/ha-card";
|
||||||
|
import "../../../../src/dialogs/more-info/more-info-content";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
|
import {
|
||||||
|
MockHomeAssistant,
|
||||||
|
provideHass,
|
||||||
|
} from "../../../../src/fake_data/provide_hass";
|
||||||
|
import "../../components/demo-more-infos";
|
||||||
|
|
||||||
|
const ENTITIES = [
|
||||||
|
getEntity("humidifier", "humidifier", "on", {
|
||||||
|
friendly_name: "Humidifier",
|
||||||
|
device_class: "humidifier",
|
||||||
|
current_humidity: 50,
|
||||||
|
humidity: 30,
|
||||||
|
}),
|
||||||
|
getEntity("humidifier", "dehumidifier", "on", {
|
||||||
|
friendly_name: "Dehumidifier",
|
||||||
|
device_class: "dehumidifier",
|
||||||
|
current_humidity: 50,
|
||||||
|
humidity: 30,
|
||||||
|
}),
|
||||||
|
getEntity("humidifier", "unavailable", "unavailable", {
|
||||||
|
friendly_name: "Unavailable humidifier",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-more-info-humidifier")
|
||||||
|
class DemoMoreInfoHumidifier extends LitElement {
|
||||||
|
@property() public hass!: MockHomeAssistant;
|
||||||
|
|
||||||
|
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<demo-more-infos
|
||||||
|
.hass=${this.hass}
|
||||||
|
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||||
|
></demo-more-infos>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProperties: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProperties);
|
||||||
|
const hass = provideHass(this._demoRoot);
|
||||||
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-more-info-humidifier": DemoMoreInfoHumidifier;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,25 @@
|
|||||||
|
import {
|
||||||
|
mdiAccountArrowRight,
|
||||||
|
mdiAirHumidifier,
|
||||||
|
mdiArrowDownBold,
|
||||||
|
mdiArrowUpBold,
|
||||||
|
mdiBabyCarriage,
|
||||||
|
mdiClockOutline,
|
||||||
|
mdiHome,
|
||||||
|
mdiLeaf,
|
||||||
|
mdiPower,
|
||||||
|
mdiPowerSleep,
|
||||||
|
mdiRefreshAuto,
|
||||||
|
mdiRocketLaunch,
|
||||||
|
mdiSofa,
|
||||||
|
mdiWaterPercent,
|
||||||
|
} from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
HassEntityAttributeBase,
|
HassEntityAttributeBase,
|
||||||
HassEntityBase,
|
HassEntityBase,
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
|
|
||||||
export type HumidifierState = "on" | "off";
|
export type HumidifierState = "off" | "on";
|
||||||
|
|
||||||
export type HumidifierAction = "off" | "idle" | "humidifying" | "drying";
|
export type HumidifierAction = "off" | "idle" | "humidifying" | "drying";
|
||||||
|
|
||||||
@ -19,7 +35,52 @@ export type HumidifierEntity = HassEntityBase & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HUMIDIFIER_SUPPORT_MODES = 1;
|
export const enum HumidifierEntityFeature {
|
||||||
|
MODES = 1,
|
||||||
|
}
|
||||||
|
|
||||||
export const HUMIDIFIER_DEVICE_CLASS_HUMIDIFIER = "humidifier";
|
export const enum HumidifierEntityDeviceClass {
|
||||||
export const HUMIDIFIER_DEVICE_CLASS_DEHUMIDIFIER = "dehumidifier";
|
HUMIDIFIER = "humidifier",
|
||||||
|
DEHUMIDIFIER = "dehumidifier",
|
||||||
|
}
|
||||||
|
|
||||||
|
type HumidifierBuiltInMode =
|
||||||
|
| "normal"
|
||||||
|
| "eco"
|
||||||
|
| "away"
|
||||||
|
| "boost"
|
||||||
|
| "comfort"
|
||||||
|
| "home"
|
||||||
|
| "sleep"
|
||||||
|
| "auto"
|
||||||
|
| "baby";
|
||||||
|
|
||||||
|
export const HUMIDIFIER_MODE_ICONS: Record<HumidifierBuiltInMode, string> = {
|
||||||
|
auto: mdiRefreshAuto,
|
||||||
|
away: mdiAccountArrowRight,
|
||||||
|
baby: mdiBabyCarriage,
|
||||||
|
boost: mdiRocketLaunch,
|
||||||
|
comfort: mdiSofa,
|
||||||
|
eco: mdiLeaf,
|
||||||
|
home: mdiHome,
|
||||||
|
normal: mdiWaterPercent,
|
||||||
|
sleep: mdiPowerSleep,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const computeHumidiferModeIcon = (mode?: string) =>
|
||||||
|
HUMIDIFIER_MODE_ICONS[mode as HumidifierBuiltInMode] ?? mdiAirHumidifier;
|
||||||
|
|
||||||
|
export const HUMIDIFIER_ACTION_ICONS: Record<HumidifierAction, string> = {
|
||||||
|
drying: mdiArrowDownBold,
|
||||||
|
humidifying: mdiArrowUpBold,
|
||||||
|
idle: mdiClockOutline,
|
||||||
|
off: mdiPower,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HUMIDIFIER_ACTION_MODE: Record<HumidifierAction, HumidifierState> =
|
||||||
|
{
|
||||||
|
drying: "on",
|
||||||
|
humidifying: "on",
|
||||||
|
idle: "off",
|
||||||
|
off: "off",
|
||||||
|
};
|
||||||
|
@ -0,0 +1,334 @@
|
|||||||
|
import { mdiMinus, mdiPlus } from "@mdi/js";
|
||||||
|
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
|
import { computeAttributeValueDisplay } from "../../../../common/entity/compute_attribute_display";
|
||||||
|
import { stateActive } from "../../../../common/entity/state_active";
|
||||||
|
import { stateColorCss } from "../../../../common/entity/state_color";
|
||||||
|
import { clamp } from "../../../../common/number/clamp";
|
||||||
|
import { formatNumber } from "../../../../common/number/format_number";
|
||||||
|
import { blankBeforePercent } from "../../../../common/translations/blank_before_percent";
|
||||||
|
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 {
|
||||||
|
HUMIDIFIER_ACTION_MODE,
|
||||||
|
HumidifierEntity,
|
||||||
|
HumidifierEntityDeviceClass,
|
||||||
|
} from "../../../../data/humidifier";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
|
||||||
|
@customElement("ha-more-info-humidifier-humidity")
|
||||||
|
export class HaMoreInfoHumidifierHumidity extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj!: HumidifierEntity;
|
||||||
|
|
||||||
|
@state() private _targetHumidity?: number;
|
||||||
|
|
||||||
|
protected willUpdate(changedProp: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProp);
|
||||||
|
if (changedProp.has("stateObj")) {
|
||||||
|
this._targetHumidity = this.stateObj.attributes.humidity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _step() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _min() {
|
||||||
|
return this.stateObj.attributes.min_humidity ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _max() {
|
||||||
|
return this.stateObj.attributes.max_humidity ?? 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent) {
|
||||||
|
const value = (ev.detail as any).value;
|
||||||
|
if (isNaN(value)) return;
|
||||||
|
this._targetHumidity = value;
|
||||||
|
this._callService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanging(ev: CustomEvent) {
|
||||||
|
const value = (ev.detail as any).value;
|
||||||
|
if (isNaN(value)) return;
|
||||||
|
this._targetHumidity = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _debouncedCallService = debounce(() => this._callService(), 1000);
|
||||||
|
|
||||||
|
private _callService() {
|
||||||
|
this.hass.callService("humidifier", "set_humidity", {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
humidity: this._targetHumidity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleButton(ev) {
|
||||||
|
const step = ev.currentTarget.step as number;
|
||||||
|
|
||||||
|
let humidity = this._targetHumidity ?? this._min;
|
||||||
|
humidity += step;
|
||||||
|
humidity = clamp(humidity, this._min, this._max);
|
||||||
|
|
||||||
|
this._targetHumidity = humidity;
|
||||||
|
this._debouncedCallService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderAction() {
|
||||||
|
const action = this.stateObj.attributes.action;
|
||||||
|
|
||||||
|
const actionLabel = computeAttributeValueDisplay(
|
||||||
|
this.hass.localize,
|
||||||
|
this.stateObj,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config,
|
||||||
|
this.hass.entities,
|
||||||
|
"action"
|
||||||
|
) as string;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<p class="action">
|
||||||
|
${action && ["drying", "humidifying"].includes(action)
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.humidifier.target_label",
|
||||||
|
{ action: actionLabel }
|
||||||
|
)
|
||||||
|
: action && action !== "off" && action !== "idle"
|
||||||
|
? actionLabel
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.humidifier.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 _renderTarget(humidity: number) {
|
||||||
|
const formatted = formatNumber(humidity, this.hass.locale, {
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="target">
|
||||||
|
<p class="value" aria-hidden="true">
|
||||||
|
${formatted}<span class="unit">%</span>
|
||||||
|
</p>
|
||||||
|
<p class="visually-hidden">
|
||||||
|
${formatted}${blankBeforePercent(this.hass.locale)}%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const mainColor = stateColorCss(this.stateObj);
|
||||||
|
const active = stateActive(this.stateObj);
|
||||||
|
|
||||||
|
const action = this.stateObj.attributes.action;
|
||||||
|
|
||||||
|
let actionColor: string | undefined;
|
||||||
|
if (action && action !== "idle" && action !== "off" && active) {
|
||||||
|
actionColor = stateColorCss(
|
||||||
|
this.stateObj,
|
||||||
|
HUMIDIFIER_ACTION_MODE[action]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetHumidity = this._targetHumidity;
|
||||||
|
const currentHumidity = this.stateObj.attributes.current_humidity;
|
||||||
|
|
||||||
|
if (targetHumidity != null) {
|
||||||
|
const inverted =
|
||||||
|
this.stateObj.attributes.device_class ===
|
||||||
|
HumidifierEntityDeviceClass.DEHUMIDIFIER;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="container"
|
||||||
|
style=${styleMap({
|
||||||
|
"--main-color": mainColor,
|
||||||
|
"--action-color": actionColor,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ha-control-circular-slider
|
||||||
|
.inverted=${inverted}
|
||||||
|
.value=${targetHumidity}
|
||||||
|
.min=${this._min}
|
||||||
|
.max=${this._max}
|
||||||
|
.step=${this._step}
|
||||||
|
.current=${currentHumidity}
|
||||||
|
.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._renderAction()}</div>
|
||||||
|
<div class="target-container">
|
||||||
|
${this._renderTarget(targetHumidity)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${this._renderButtons()}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="container"
|
||||||
|
style=${styleMap({
|
||||||
|
"--action-color": actionColor,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ha-control-circular-slider
|
||||||
|
.current=${currentHumidity}
|
||||||
|
.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 */
|
||||||
|
.target-container {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.target .value {
|
||||||
|
font-size: 56px;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: -0.25px;
|
||||||
|
}
|
||||||
|
.target .value .unit {
|
||||||
|
font-size: 0.4em;
|
||||||
|
line-height: 1;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
.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-humidifier-humidity": HaMoreInfoHumidifierHumidity;
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [
|
|||||||
"cover",
|
"cover",
|
||||||
"climate",
|
"climate",
|
||||||
"fan",
|
"fan",
|
||||||
|
"humidifier",
|
||||||
"input_boolean",
|
"input_boolean",
|
||||||
"light",
|
"light",
|
||||||
"lock",
|
"lock",
|
||||||
|
@ -1,37 +1,45 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { property } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
import {
|
import {
|
||||||
computeAttributeNameDisplay,
|
computeAttributeNameDisplay,
|
||||||
computeAttributeValueDisplay,
|
computeAttributeValueDisplay,
|
||||||
} from "../../../common/entity/compute_attribute_display";
|
} from "../../../common/entity/compute_attribute_display";
|
||||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
import { formatNumber } from "../../../common/number/format_number";
|
||||||
import "../../../components/ha-select";
|
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||||
import "../../../components/ha-slider";
|
|
||||||
import "../../../components/ha-switch";
|
|
||||||
import {
|
import {
|
||||||
HumidifierEntity,
|
HumidifierEntity,
|
||||||
HUMIDIFIER_SUPPORT_MODES,
|
HumidifierEntityFeature,
|
||||||
} from "../../../data/humidifier";
|
} from "../../../data/humidifier";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
|
||||||
|
import "../components/humidifier/ha-more-info-humidifier-humidity";
|
||||||
|
|
||||||
class MoreInfoHumidifier extends LitElement {
|
class MoreInfoHumidifier extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public stateObj?: HumidifierEntity;
|
@property() public stateObj?: HumidifierEntity;
|
||||||
|
|
||||||
|
@state() public _mode?: string;
|
||||||
|
|
||||||
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProps);
|
||||||
|
if (changedProps.has("stateObj")) {
|
||||||
|
this._mode = this.stateObj?.attributes.mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _resizeDebounce?: number;
|
private _resizeDebounce?: number;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@ -42,43 +50,52 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
const hass = this.hass;
|
const hass = this.hass;
|
||||||
const stateObj = this.stateObj;
|
const stateObj = this.stateObj;
|
||||||
|
|
||||||
const supportModes = supportsFeature(stateObj, HUMIDIFIER_SUPPORT_MODES);
|
const supportModes = supportsFeature(
|
||||||
|
stateObj,
|
||||||
|
HumidifierEntityFeature.MODES
|
||||||
|
);
|
||||||
|
|
||||||
const rtlDirection = computeRTLDirection(hass);
|
const currentHumidity = this.stateObj.attributes.current_humidity;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
|
${currentHumidity
|
||||||
|
? html`<div class="current">
|
||||||
|
${currentHumidity != null
|
||||||
|
? html`
|
||||||
|
<div>
|
||||||
|
<p class="label">
|
||||||
|
${computeAttributeNameDisplay(
|
||||||
|
this.hass.localize,
|
||||||
|
this.stateObj,
|
||||||
|
this.hass.entities,
|
||||||
|
"current_humidity"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p class="value">
|
||||||
|
${formatNumber(
|
||||||
|
currentHumidity,
|
||||||
|
this.hass.locale
|
||||||
|
)}${blankBeforePercent(this.hass.locale)}%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
<div class="controls">
|
||||||
|
<ha-more-info-humidifier-humidity
|
||||||
|
.hass=${this.hass}
|
||||||
|
.stateObj=${this.stateObj}
|
||||||
|
></ha-more-info-humidifier-humidity>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
"has-modes": supportModes,
|
"has-modes": supportModes,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div class="container-humidity">
|
|
||||||
<div>
|
|
||||||
${computeAttributeNameDisplay(
|
|
||||||
hass.localize,
|
|
||||||
stateObj,
|
|
||||||
hass.entities,
|
|
||||||
"humidity"
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="single-row">
|
|
||||||
<div class="target-humidity">${stateObj.attributes.humidity} %</div>
|
|
||||||
<ha-slider
|
|
||||||
step="1"
|
|
||||||
pin
|
|
||||||
ignore-bar-touch
|
|
||||||
dir=${rtlDirection}
|
|
||||||
.min=${stateObj.attributes.min_humidity}
|
|
||||||
.max=${stateObj.attributes.max_humidity}
|
|
||||||
.value=${stateObj.attributes.humidity}
|
|
||||||
@change=${this._targetHumiditySliderChanged}
|
|
||||||
>
|
|
||||||
</ha-slider>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ha-select
|
<ha-select
|
||||||
.label=${hass.localize("ui.card.humidifier.state")}
|
.label=${this.hass.localize("ui.card.humidifier.state")}
|
||||||
.value=${stateObj.state}
|
.value=${this.stateObj.state}
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
@selected=${this._handleStateChanged}
|
@selected=${this._handleStateChanged}
|
||||||
@ -86,21 +103,21 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
>
|
>
|
||||||
<mwc-list-item value="off">
|
<mwc-list-item value="off">
|
||||||
${computeStateDisplay(
|
${computeStateDisplay(
|
||||||
hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
this.stateObj,
|
||||||
hass.locale,
|
this.hass.locale,
|
||||||
this.hass.config,
|
this.hass.config,
|
||||||
hass.entities,
|
this.hass.entities,
|
||||||
"off"
|
"off"
|
||||||
)}
|
)}
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
<mwc-list-item value="on">
|
<mwc-list-item value="on">
|
||||||
${computeStateDisplay(
|
${computeStateDisplay(
|
||||||
hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
this.stateObj,
|
||||||
hass.locale,
|
this.hass.locale,
|
||||||
this.hass.config,
|
this.hass.config,
|
||||||
hass.entities,
|
this.hass.entities,
|
||||||
"on"
|
"on"
|
||||||
)}
|
)}
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
@ -114,6 +131,7 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
@selected=${this._handleModeChanged}
|
@selected=${this._handleModeChanged}
|
||||||
|
@action=${this._handleModeChanged}
|
||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
>
|
>
|
||||||
${stateObj.attributes.available_modes!.map(
|
${stateObj.attributes.available_modes!.map(
|
||||||
@ -121,9 +139,9 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
<mwc-list-item .value=${mode}>
|
<mwc-list-item .value=${mode}>
|
||||||
${computeAttributeValueDisplay(
|
${computeAttributeValueDisplay(
|
||||||
hass.localize,
|
hass.localize,
|
||||||
stateObj,
|
stateObj!,
|
||||||
hass.locale,
|
hass.locale,
|
||||||
this.hass.config,
|
hass.config,
|
||||||
hass.entities,
|
hass.entities,
|
||||||
"mode",
|
"mode",
|
||||||
mode
|
mode
|
||||||
@ -133,7 +151,7 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</ha-select>
|
</ha-select>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -153,16 +171,6 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _targetHumiditySliderChanged(ev) {
|
|
||||||
const newVal = ev.target.value;
|
|
||||||
this._callServiceHelper(
|
|
||||||
this.stateObj!.attributes.humidity,
|
|
||||||
newVal,
|
|
||||||
"set_humidity",
|
|
||||||
{ humidity: newVal }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleStateChanged(ev) {
|
private _handleStateChanged(ev) {
|
||||||
const newVal = ev.target.value || null;
|
const newVal = ev.target.value || null;
|
||||||
this._callServiceHelper(
|
this._callServiceHelper(
|
||||||
@ -173,16 +181,6 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleModeChanged(ev) {
|
|
||||||
const newVal = ev.target.value || null;
|
|
||||||
this._callServiceHelper(
|
|
||||||
this.stateObj!.attributes.mode,
|
|
||||||
newVal,
|
|
||||||
"set_mode",
|
|
||||||
{ mode: newVal }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _callServiceHelper(
|
private async _callServiceHelper(
|
||||||
oldVal: unknown,
|
oldVal: unknown,
|
||||||
newVal: unknown,
|
newVal: unknown,
|
||||||
@ -221,37 +219,71 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleModeChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
const index = ev.detail.index;
|
||||||
|
const newVal = this.stateObj!.attributes.available_modes![index];
|
||||||
|
const oldVal = this._mode;
|
||||||
|
|
||||||
|
if (!newVal || oldVal === newVal) return;
|
||||||
|
|
||||||
|
this._mode = newVal;
|
||||||
|
this.hass.callService("humidifier", "set_mode", {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
mode: newVal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return [
|
||||||
:host {
|
moreInfoControlStyle,
|
||||||
color: var(--primary-text-color);
|
css`
|
||||||
}
|
:host {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
ha-select {
|
.current {
|
||||||
width: 100%;
|
display: flex;
|
||||||
margin-top: 8px;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
ha-slider {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
margin-top: 8px;
|
||||||
|
}
|
||||||
.container-humidity .single-row {
|
`,
|
||||||
display: flex;
|
];
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-humidity {
|
|
||||||
width: 90px;
|
|
||||||
font-size: 200%;
|
|
||||||
margin: auto;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.single-row {
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,6 +225,15 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (domain === "humidifier" && stateActive(stateObj)) {
|
||||||
|
const humidity = (stateObj as HumidifierEntity).attributes.humidity;
|
||||||
|
if (humidity) {
|
||||||
|
return `${Math.round(humidity)}${blankBeforePercent(
|
||||||
|
this.hass!.locale
|
||||||
|
)}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const stateDisplay = computeStateDisplay(
|
const stateDisplay = computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
|
@ -1,32 +1,11 @@
|
|||||||
import {
|
|
||||||
mdiArrowDownBold,
|
|
||||||
mdiArrowUpBold,
|
|
||||||
mdiClockOutline,
|
|
||||||
mdiPower,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import { stateColorCss } from "../../../../../common/entity/state_color";
|
import { stateColorCss } from "../../../../../common/entity/state_color";
|
||||||
import {
|
import {
|
||||||
HumidifierAction,
|
HUMIDIFIER_ACTION_ICONS,
|
||||||
|
HUMIDIFIER_ACTION_MODE,
|
||||||
HumidifierEntity,
|
HumidifierEntity,
|
||||||
HumidifierState,
|
|
||||||
} from "../../../../../data/humidifier";
|
} from "../../../../../data/humidifier";
|
||||||
import { ComputeBadgeFunction } from "./tile-badge";
|
import { ComputeBadgeFunction } from "./tile-badge";
|
||||||
|
|
||||||
export const HUMIDIFIER_ACTION_ICONS: Record<HumidifierAction, string> = {
|
|
||||||
drying: mdiArrowDownBold,
|
|
||||||
humidifying: mdiArrowUpBold,
|
|
||||||
idle: mdiClockOutline,
|
|
||||||
off: mdiPower,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const HUMIDIFIER_ACTION_MODE: Record<HumidifierAction, HumidifierState> =
|
|
||||||
{
|
|
||||||
drying: "on",
|
|
||||||
humidifying: "on",
|
|
||||||
idle: "off",
|
|
||||||
off: "off",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const computeHumidifierBadge: ComputeBadgeFunction = (stateObj) => {
|
export const computeHumidifierBadge: ComputeBadgeFunction = (stateObj) => {
|
||||||
const hvacAction = (stateObj as HumidifierEntity).attributes.action;
|
const hvacAction = (stateObj as HumidifierEntity).attributes.action;
|
||||||
|
|
||||||
|
@ -1000,6 +1000,10 @@
|
|||||||
"climate": {
|
"climate": {
|
||||||
"target_label": "{action} to target",
|
"target_label": "{action} to target",
|
||||||
"target": "Target"
|
"target": "Target"
|
||||||
|
},
|
||||||
|
"humidifier": {
|
||||||
|
"target_label": "[%key:ui::dialogs::more_info_control::climate::target_label%]",
|
||||||
|
"target": "[%key:ui::dialogs::more_info_control::climate::target%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity_registry": {
|
"entity_registry": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user