First commit

This commit is contained in:
Zack Arnett 2021-11-10 17:30:06 -06:00
parent c26a59d805
commit b2a4fb7cee

View File

@ -4,6 +4,8 @@ import {
mdiDotsVertical,
mdiFan,
mdiFire,
mdiMinus,
mdiPlus,
mdiPower,
mdiSnowflake,
mdiWaterPercent,
@ -16,10 +18,9 @@ import {
html,
LitElement,
PropertyValues,
svg,
TemplateResult,
} from "lit";
import { customElement, property, state, query } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { UNIT_F } from "../../../common/const";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
@ -27,11 +28,9 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { formatNumber } from "../../../common/number/format_number";
import "../../../components/ha-card";
import type { HaCard } from "../../../components/ha-card";
import "../../../components/ha-icon-button";
import {
ClimateEntity,
CLIMATE_PRESET_NONE,
compareClimateHvacModes,
HvacMode,
} from "../../../data/climate";
@ -84,8 +83,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
@state() private _setTemp?: number | number[];
@query("ha-card") private _card?: HaCard;
public getCardSize(): number {
return 7;
}
@ -116,125 +113,97 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
const name =
this._config!.name ||
computeStateName(this.hass!.states[this._config!.entity]);
const targetTemp =
stateObj.attributes.temperature !== null &&
Number.isFinite(Number(stateObj.attributes.temperature))
? stateObj.attributes.temperature
: stateObj.attributes.min_temp;
// const targetTemp =
// stateObj.attributes.temperature !== null &&
// Number.isFinite(Number(stateObj.attributes.temperature))
// ? stateObj.attributes.temperature
// : stateObj.attributes.min_temp;
const slider =
stateObj.state === UNAVAILABLE
? html` <round-slider disabled="true"></round-slider> `
: html`
<round-slider
.value=${targetTemp}
.low=${stateObj.attributes.target_temp_low}
.high=${stateObj.attributes.target_temp_high}
.min=${stateObj.attributes.min_temp}
.max=${stateObj.attributes.max_temp}
.step=${this._stepSize}
@value-changing=${this._dragEvent}
@value-changed=${this._setTemperature}
></round-slider>
`;
// const currentTemperature = html`
// <span viewBox="0 0 40 20">
// <span
// x="50%"
// dx="1"
// y="60%"
// text-anchor="middle"
// style="font-size: 13px;"
// >
// ${stateObj.attributes.current_temperature !== null &&
// !isNaN(stateObj.attributes.current_temperature)
// ? html`${formatNumber(
// stateObj.attributes.current_temperature,
// this.hass.locale
// )}
// <span dx="-3" dy="-6.5" style="font-size: 4px;">
// ${this.hass.config.unit_system.temperature}
// </span>`
// : ""}
// </span>
// </span>
// `;
const currentTemperature = svg`
<svg viewBox="0 0 40 20">
<text
x="50%"
dx="1"
y="60%"
text-anchor="middle"
style="font-size: 13px;"
>
${
stateObj.attributes.current_temperature !== null &&
!isNaN(stateObj.attributes.current_temperature)
? svg`${formatNumber(
stateObj.attributes.current_temperature,
this.hass.locale
)}
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
${this.hass.config.unit_system.temperature}
</tspan>`
: ""
}
</text>
</svg>
`;
const setValues = svg`
<svg id="set-values">
<g>
<text text-anchor="middle" class="set-value">
${
stateObj.state === UNAVAILABLE
? this.hass.localize("state.default.unavailable")
: this._setTemp === undefined || this._setTemp === null
? ""
: Array.isArray(this._setTemp)
? this._stepSize === 1
? svg`
${formatNumber(this._setTemp[0], this.hass.locale, {
maximumFractionDigits: 0,
})} -
${formatNumber(this._setTemp[1], this.hass.locale, {
maximumFractionDigits: 0,
})}
`
: svg`
${formatNumber(this._setTemp[0], this.hass.locale, {
minimumFractionDigits: 1,
maximumFractionDigits: 1,
})} -
${formatNumber(this._setTemp[1], this.hass.locale, {
minimumFractionDigits: 1,
maximumFractionDigits: 1,
})}
`
: this._stepSize === 1
? svg`
${formatNumber(this._setTemp, this.hass.locale, {
maximumFractionDigits: 0,
})}
`
: svg`
${formatNumber(this._setTemp, this.hass.locale, {
minimumFractionDigits: 1,
maximumFractionDigits: 1,
})}
`
}
</text>
<text
dy="22"
text-anchor="middle"
id="set-mode"
>
${
stateObj.attributes.hvac_action
? this.hass!.localize(
`state_attributes.climate.hvac_action.${stateObj.attributes.hvac_action}`
)
: this.hass!.localize(
`component.climate.state._.${stateObj.state}`
)
}
${
stateObj.attributes.preset_mode &&
stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
? html`
-
${this.hass!.localize(
`state_attributes.climate.preset_mode.${stateObj.attributes.preset_mode}`
) || stateObj.attributes.preset_mode}
`
: ""
}
</text>
</g>
</svg>
`;
// const setValues = html`
// <html id="set-values">
// <span class="set-value">
// ${stateObj.state === UNAVAILABLE
// ? this.hass.localize("state.default.unavailable")
// : this._setTemp === undefined || this._setTemp === null
// ? ""
// : Array.isArray(this._setTemp)
// ? this._stepSize === 1
// ? html`
// ${formatNumber(this._setTemp[0], this.hass.locale, {
// maximumFractionDigits: 0,
// })}
// -
// ${formatNumber(this._setTemp[1], this.hass.locale, {
// maximumFractionDigits: 0,
// })}
// `
// : html`
// ${formatNumber(this._setTemp[0], this.hass.locale, {
// minimumFractionDigits: 1,
// maximumFractionDigits: 1,
// })}
// -
// ${formatNumber(this._setTemp[1], this.hass.locale, {
// minimumFractionDigits: 1,
// maximumFractionDigits: 1,
// })}
// `
// : this._stepSize === 1
// ? html`
// ${formatNumber(this._setTemp, this.hass.locale, {
// maximumFractionDigits: 0,
// })}
// `
// : html`
// ${formatNumber(this._setTemp, this.hass.locale, {
// minimumFractionDigits: 1,
// maximumFractionDigits: 1,
// })}
// `}
// </span>
// <span>
// ${stateObj.attributes.hvac_action
// ? this.hass!.localize(
// `state_attributes.climate.hvac_action.${stateObj.attributes.hvac_action}`
// )
// : this.hass!.localize(
// `component.climate.state._.${stateObj.state}`
// )}
// ${stateObj.attributes.preset_mode &&
// stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
// ? html`
// -
// ${this.hass!.localize(
// `state_attributes.climate.preset_mode.${stateObj.attributes.preset_mode}`
// ) || stateObj.attributes.preset_mode}
// `
// : ""}
// </span>
// </html>
// `;
return html`
<ha-card
@ -251,16 +220,87 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
@click=${this._handleMoreInfo}
tabindex="0"
></ha-icon-button>
<div class="current-controls">
<div class="current">
${stateObj.attributes.current_temperature !== null &&
!isNaN(stateObj.attributes.current_temperature)
? html`
<span class="current-temp">
${formatNumber(
stateObj.attributes.current_temperature,
this.hass.locale
)}
<span class="current-temp-unit">
${this.hass.config.unit_system.temperature}
</span>
</span>
`
: ""}
<span class="current-action">
${stateObj.attributes.hvac_action
? this.hass!.localize(
`state_attributes.climate.hvac_action.${stateObj.attributes.hvac_action}`
)
: this.hass!.localize(
`component.climate.state._.${stateObj.state}`
)}
to
${stateObj.state === UNAVAILABLE
? this.hass.localize("state.default.unavailable")
: this._setTemp === undefined || this._setTemp === null
? ""
: Array.isArray(this._setTemp)
? this._stepSize === 1
? html`
${formatNumber(this._setTemp[0], this.hass.locale, {
maximumFractionDigits: 0,
})}
-
${formatNumber(this._setTemp[1], this.hass.locale, {
maximumFractionDigits: 0,
})}
`
: html`
${formatNumber(this._setTemp[0], this.hass.locale, {
minimumFractionDigits: 1,
maximumFractionDigits: 1,
})}
-
${formatNumber(this._setTemp[1], this.hass.locale, {
minimumFractionDigits: 1,
maximumFractionDigits: 1,
})}
`
: this._stepSize === 1
? html`
${formatNumber(this._setTemp, this.hass.locale, {
maximumFractionDigits: 0,
})}
`
: html`
${formatNumber(this._setTemp, this.hass.locale, {
minimumFractionDigits: 1,
maximumFractionDigits: 1,
})}
`}</span
>
</div>
<div class="controls">
<ha-icon-button
@click=${this._handleAction}
tabindex="0"
.path=${mdiPlus}
>
</ha-icon-button>
<ha-icon-button
@click=${this._handleAction}
tabindex="0"
.path=${mdiMinus}
>
</ha-icon-button>
</div>
</div>
<div class="content">
<div id="controls">
<div id="slider">
${slider}
<div id="slider-center">
<div id="temperature">${currentTemperature} ${setValues}</div>
</div>
</div>
</div>
<div id="info" .title=${name}>
<div id="modes">
${(stateObj.attributes.hvac_modes || [])
@ -270,7 +310,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
</div>
${name}
</div>
</div>
</ha-card>
`;
}
@ -303,15 +342,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
) {
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._rescale_svg();
}
}
public willUpdate(changedProps: PropertyValues) {
@ -331,27 +361,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
}
}
private _rescale_svg() {
// Set the viewbox of the SVG containing the set temperature to perfectly
// fit the text
// That way it will auto-scale correctly
// This is not done to the SVG containing the current temperature, because
// it should not be centered on the text, but only on the value
const card = this._card;
if (card) {
card.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 get _stepSize(): number {
const stateObj = this.hass!.states[this._config!.entity] as ClimateEntity;
@ -381,18 +390,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
return stateObj.attributes.temperature;
}
private _dragEvent(e): void {
const stateObj = this.hass!.states[this._config!.entity] as ClimateEntity;
if (e.detail.low) {
this._setTemp = [e.detail.low, stateObj.attributes.target_temp_high];
} else if (e.detail.high) {
this._setTemp = [stateObj.attributes.target_temp_low, e.detail.high];
} else {
this._setTemp = e.detail.value;
}
}
private _setTemperature(e): void {
const stateObj = this.hass!.states[this._config!.entity] as ClimateEntity;
@ -508,47 +505,49 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
justify-content: center;
}
#controls {
.current-controls {
display: flex;
justify-content: center;
align-items: center;
}
.current {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
}
.current-temp {
font-size: 64px;
font-weight: 300;
display: flex;
align-items: flex-start;
line-height: 0.8;
padding-bottom: 8px;
}
.current-temp-unit {
font-size: 22px;
}
.current-action {
font-size: 16px;
}
.controls {
display: flex;
justify-content: space-between;
padding: 16px;
position: relative;
flex-direction: column;
min-height: 100px;
}
#slider {
height: 100%;
width: 100%;
position: relative;
max-width: 250px;
min-width: 100px;
}
round-slider {
--round-slider-path-color: var(--slider-track-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;
}
#temperature {
position: absolute;
transform: translate(-50%, -50%);
width: 100%;
height: 50%;
top: 45%;
left: 50%;
.controls ha-icon-button {
border: 1px solid var(--divider-color);
border-radius: 50%;
--mdc-icon-button-size: 36px;
}
#set-values {
@ -566,8 +565,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
display: flex-vertical;
justify-content: center;
text-align: center;
padding: 16px;
margin-top: -60px;
padding: 0 16px 16px 16px;
font-size: var(--name-font-size);
}