mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 13:57:21 +00:00
Gauge convert and fix issues (#1886)
* Gauge convert and fix issues * Fixing Unit_of_Measurement * Addressing Reviews * Updating typing
This commit is contained in:
parent
8c155d4d0e
commit
b6d0d777bf
@ -1,211 +0,0 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
|
|
||||||
|
|
||||||
import "../../../components/ha-card.js";
|
|
||||||
|
|
||||||
import EventsMixin from "../../../mixins/events-mixin.js";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
*/
|
|
||||||
class HuiGaugeCard extends EventsMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
ha-card {
|
|
||||||
--base-unit: 50px;
|
|
||||||
height: calc(var(--base-unit)*3);
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.container{
|
|
||||||
width: calc(var(--base-unit) * 4);
|
|
||||||
height: calc(var(--base-unit) * 2);
|
|
||||||
position: absolute;
|
|
||||||
top: calc(var(--base-unit)*1.5);
|
|
||||||
left: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
text-align: center;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
.gauge-a{
|
|
||||||
z-index: 1;
|
|
||||||
position: absolute;
|
|
||||||
background-color: var(--primary-background-color);
|
|
||||||
width: calc(var(--base-unit) * 4);
|
|
||||||
height: calc(var(--base-unit) * 2);
|
|
||||||
top: 0%;
|
|
||||||
border-radius:calc(var(--base-unit) * 2.5) calc(var(--base-unit) * 2.5) 0px 0px ;
|
|
||||||
}
|
|
||||||
.gauge-b{
|
|
||||||
z-index: 3;
|
|
||||||
position: absolute;
|
|
||||||
background-color: var(--paper-card-background-color);
|
|
||||||
width: calc(var(--base-unit) * 2.5);
|
|
||||||
height: calc(var(--base-unit) * 1.25);
|
|
||||||
top: calc(var(--base-unit) * 0.75);
|
|
||||||
margin-left: calc(var(--base-unit) * 0.75);
|
|
||||||
margin-right: auto;
|
|
||||||
border-radius: calc(var(--base-unit) * 2.5) calc(var(--base-unit) * 2.5) 0px 0px ;
|
|
||||||
}
|
|
||||||
.gauge-c{
|
|
||||||
z-index: 2;
|
|
||||||
position: absolute;
|
|
||||||
background-color: var(--label-badge-blue);
|
|
||||||
width: calc(var(--base-unit) * 4);
|
|
||||||
height: calc(var(--base-unit) * 2);
|
|
||||||
top: calc(var(--base-unit) * 2);
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
border-radius: 0px 0px calc(var(--base-unit) * 2) calc(var(--base-unit) * 2) ;
|
|
||||||
transform-origin: center top;
|
|
||||||
transition: all 1.3s ease-in-out;
|
|
||||||
}
|
|
||||||
.gauge-data{
|
|
||||||
z-index: 4;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
line-height: calc(var(--base-unit) * 0.3);
|
|
||||||
position: absolute;
|
|
||||||
width: calc(var(--base-unit) * 4);
|
|
||||||
height: calc(var(--base-unit) * 2.1);
|
|
||||||
top: calc(var(--base-unit) * 1.2);
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
transition: all 1s ease-out;
|
|
||||||
}
|
|
||||||
.gauge-data #percent{
|
|
||||||
font-size: calc(var(--base-unit) * 0.55);
|
|
||||||
}
|
|
||||||
.gauge-data #title{
|
|
||||||
padding-top: calc(var(--base-unit) * 0.15);
|
|
||||||
font-size: calc(var(--base-unit) * 0.30);
|
|
||||||
}
|
|
||||||
.not-found {
|
|
||||||
flex: 1;
|
|
||||||
background-color: yellow;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-card on-click='_handleClick'>
|
|
||||||
<div class='container'>
|
|
||||||
<div class='gauge-a'></div>
|
|
||||||
<div class='gauge-b'></div>
|
|
||||||
<div class='gauge-c' id='gauge'></div>
|
|
||||||
<div class='gauge-data'>
|
|
||||||
<div id='percent'>[[_computeStateDisplay(_stateObj)]]</div>
|
|
||||||
<div id='title'>[[_computeTitle(_stateObj)]]</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
_config: Object,
|
|
||||||
_stateObj: {
|
|
||||||
type: Object,
|
|
||||||
computed: "_computeStateObj(hass.states, _config.entity)",
|
|
||||||
observer: "_stateObjChanged",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getCardSize() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
setConfig(config) {
|
|
||||||
if (!config || !config.entity)
|
|
||||||
throw new Error("Invalid card configuration");
|
|
||||||
this._config = { min: 0, max: 100, ...config };
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeStateObj(states, entityId) {
|
|
||||||
return states && entityId in states ? states[entityId] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_stateObjChanged(stateObj) {
|
|
||||||
if (!stateObj || isNaN(stateObj.state)) return;
|
|
||||||
|
|
||||||
const config = this._config;
|
|
||||||
const turn = this._translateTurn(stateObj.state, config) / 10;
|
|
||||||
|
|
||||||
this.$.gauge.style.transform = `rotate(${turn}turn)`;
|
|
||||||
this.$.gauge.style.backgroundColor = this._computeSeverity(
|
|
||||||
stateObj.state,
|
|
||||||
config.severity
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeStateDisplay(stateObj) {
|
|
||||||
if (!stateObj || isNaN(stateObj.state)) return "";
|
|
||||||
const unitOfMeasurement =
|
|
||||||
this._config.unit_of_measurement ||
|
|
||||||
stateObj.attributes.unit_of_measurement ||
|
|
||||||
"";
|
|
||||||
return `${stateObj.state} ${unitOfMeasurement}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeTitle(stateObj) {
|
|
||||||
if (!stateObj) {
|
|
||||||
this.$.title.className = "not-found";
|
|
||||||
return "Entity not available: " + this._config.entity;
|
|
||||||
}
|
|
||||||
if (isNaN(stateObj.state)) {
|
|
||||||
this.$.title.className = "not-found";
|
|
||||||
return "Entity is non-numeric: " + this._config.entity;
|
|
||||||
}
|
|
||||||
this.$.title.className = "";
|
|
||||||
return this._config.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeSeverity(stateValue, sections) {
|
|
||||||
const numberValue = Number(stateValue);
|
|
||||||
const severityMap = {
|
|
||||||
red: "var(--label-badge-red)",
|
|
||||||
green: "var(--label-badge-green)",
|
|
||||||
yellow: "var(--label-badge-yellow)",
|
|
||||||
normal: "var(--label-badge-blue)",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!sections) return severityMap.normal;
|
|
||||||
|
|
||||||
const sectionsArray = Object.keys(sections);
|
|
||||||
const sortable = sectionsArray.map((severity) => [
|
|
||||||
severity,
|
|
||||||
sections[severity],
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (var i = 0; i < sortable.length; i++) {
|
|
||||||
if (severityMap[sortable[i][0]] == null || isNaN(sortable[i][1])) {
|
|
||||||
return severityMap.normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sortable.sort((a, b) => a[1] - b[1]);
|
|
||||||
|
|
||||||
if (numberValue >= sortable[0][1] && numberValue < sortable[1][1]) {
|
|
||||||
return severityMap[sortable[0][0]];
|
|
||||||
}
|
|
||||||
if (numberValue >= sortable[1][1] && numberValue < sortable[2][1]) {
|
|
||||||
return severityMap[sortable[1][0]];
|
|
||||||
}
|
|
||||||
if (numberValue >= sortable[2][1]) {
|
|
||||||
return severityMap[sortable[2][0]];
|
|
||||||
}
|
|
||||||
return severityMap.normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
_translateTurn(value, config) {
|
|
||||||
return (5 * (value - config.min)) / (config.max - config.min);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleClick() {
|
|
||||||
this.fire("hass-more-info", { entityId: this._config.entity });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("hui-gauge-card", HuiGaugeCard);
|
|
266
src/panels/lovelace/cards/hui-gauge-card.ts
Normal file
266
src/panels/lovelace/cards/hui-gauge-card.ts
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
import {
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyDeclarations,
|
||||||
|
PropertyValues,
|
||||||
|
} from "@polymer/lit-element";
|
||||||
|
import { LovelaceCard, LovelaceConfig } from "../types.js";
|
||||||
|
import { HomeAssistant } from "../../../types.js";
|
||||||
|
import isValidEntityId from "../../../common/entity/valid_entity_id.js";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event.js";
|
||||||
|
|
||||||
|
import "../../../components/ha-card.js";
|
||||||
|
|
||||||
|
interface Config extends LovelaceConfig {
|
||||||
|
entity: string;
|
||||||
|
title?: string;
|
||||||
|
unit_of_measurement?: string;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
severity?: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
const severityMap = {
|
||||||
|
red: "var(--label-badge-red)",
|
||||||
|
green: "var(--label-badge-green)",
|
||||||
|
yellow: "var(--label-badge-yellow)",
|
||||||
|
normal: "var(--label-badge-blue)",
|
||||||
|
};
|
||||||
|
|
||||||
|
class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||||
|
public hass?: HomeAssistant;
|
||||||
|
private _config?: Config;
|
||||||
|
|
||||||
|
static get properties(): PropertyDeclarations {
|
||||||
|
return {
|
||||||
|
hass: {},
|
||||||
|
_config: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCardSize() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setConfig(config) {
|
||||||
|
if (!config || !config.entity) {
|
||||||
|
throw new Error("Invalid card configuration");
|
||||||
|
}
|
||||||
|
if (!isValidEntityId(config.entity)) {
|
||||||
|
throw new Error("Invalid Entity");
|
||||||
|
}
|
||||||
|
this._config = { min: 0, max: 100, ...config };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._config || !this.hass) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
const stateObj = this.hass.states[this._config.entity];
|
||||||
|
let error;
|
||||||
|
if (!stateObj) {
|
||||||
|
error = "Entity not available: " + this._config.entity;
|
||||||
|
} else if (isNaN(Number(stateObj.state))) {
|
||||||
|
error = "Entity is non-numeric: " + this._config.entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${this.renderStyle()}
|
||||||
|
<ha-card @click="${this._handleClick}">
|
||||||
|
${
|
||||||
|
error
|
||||||
|
? html`<div class="not-found">${error}</div>`
|
||||||
|
: html`
|
||||||
|
<div class='container'>
|
||||||
|
<div class='gauge-a'></div>
|
||||||
|
<div class='gauge-b'></div>
|
||||||
|
<div class='gauge-c' id='gauge'></div>
|
||||||
|
<div class='gauge-data'>
|
||||||
|
<div id='percent'>${stateObj.state}
|
||||||
|
${this._config.unit_of_measurement ||
|
||||||
|
stateObj.attributes.unit_of_measurement ||
|
||||||
|
""}
|
||||||
|
</div>
|
||||||
|
<div id='title'>${this._config.title}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected shouldUpdate(changedProps: PropertyValues) {
|
||||||
|
if (changedProps.get("hass")) {
|
||||||
|
return (
|
||||||
|
(changedProps.get("hass") as any).states[this._config!.entity] !==
|
||||||
|
this.hass!.states[this._config!.entity]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (changedProps.get("_config")) {
|
||||||
|
return changedProps.get("_config") !== this._config;
|
||||||
|
}
|
||||||
|
return (changedProps as unknown) as boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated() {
|
||||||
|
if (
|
||||||
|
!this._config ||
|
||||||
|
!this.hass ||
|
||||||
|
!this.shadowRoot!.getElementById("gauge")
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const stateObj = this.hass.states[this._config.entity];
|
||||||
|
if (isNaN(Number(stateObj.state))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const turn = this._translateTurn(Number(stateObj.state), this._config);
|
||||||
|
|
||||||
|
this.shadowRoot!.getElementById(
|
||||||
|
"gauge"
|
||||||
|
)!.style.cssText = `transform: rotate(${turn}turn); background-color: ${this._computeSeverity(
|
||||||
|
stateObj.state,
|
||||||
|
this._config.severity!
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
(this.shadowRoot!.querySelector(
|
||||||
|
"ha-card"
|
||||||
|
)! as HTMLElement).style.setProperty(
|
||||||
|
"--base-unit",
|
||||||
|
this._computeBaseUnit()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderStyle() {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
ha-card {
|
||||||
|
--base-unit: 50px;
|
||||||
|
height: calc(var(--base-unit)*3);
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.container{
|
||||||
|
width: calc(var(--base-unit) * 4);
|
||||||
|
height: calc(var(--base-unit) * 2);
|
||||||
|
position: absolute;
|
||||||
|
top: calc(var(--base-unit)*1.5);
|
||||||
|
left: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
.gauge-a{
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
width: calc(var(--base-unit) * 4);
|
||||||
|
height: calc(var(--base-unit) * 2);
|
||||||
|
top: 0%;
|
||||||
|
border-radius:calc(var(--base-unit) * 2.5) calc(var(--base-unit) * 2.5) 0px 0px ;
|
||||||
|
}
|
||||||
|
.gauge-b{
|
||||||
|
z-index: 3;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--paper-card-background-color);
|
||||||
|
width: calc(var(--base-unit) * 2.5);
|
||||||
|
height: calc(var(--base-unit) * 1.25);
|
||||||
|
top: calc(var(--base-unit) * 0.75);
|
||||||
|
margin-left: calc(var(--base-unit) * 0.75);
|
||||||
|
margin-right: auto;
|
||||||
|
border-radius: calc(var(--base-unit) * 2.5) calc(var(--base-unit) * 2.5) 0px 0px ;
|
||||||
|
}
|
||||||
|
.gauge-c{
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--label-badge-blue);
|
||||||
|
width: calc(var(--base-unit) * 4);
|
||||||
|
height: calc(var(--base-unit) * 2);
|
||||||
|
top: calc(var(--base-unit) * 2);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
border-radius: 0px 0px calc(var(--base-unit) * 2) calc(var(--base-unit) * 2) ;
|
||||||
|
transform-origin: center top;
|
||||||
|
transition: all 1.3s ease-in-out;
|
||||||
|
}
|
||||||
|
.gauge-data{
|
||||||
|
z-index: 4;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
line-height: calc(var(--base-unit) * 0.3);
|
||||||
|
position: absolute;
|
||||||
|
width: calc(var(--base-unit) * 4);
|
||||||
|
height: calc(var(--base-unit) * 2.1);
|
||||||
|
top: calc(var(--base-unit) * 1.2);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
transition: all 1s ease-out;
|
||||||
|
}
|
||||||
|
.gauge-data #percent{
|
||||||
|
font-size: calc(var(--base-unit) * 0.55);
|
||||||
|
}
|
||||||
|
.gauge-data #title{
|
||||||
|
padding-top: calc(var(--base-unit) * 0.15);
|
||||||
|
font-size: calc(var(--base-unit) * 0.30);
|
||||||
|
}
|
||||||
|
.not-found {
|
||||||
|
flex: 1;
|
||||||
|
background-color: yellow;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeSeverity(stateValue: string, sections: object) {
|
||||||
|
const numberValue = Number(stateValue);
|
||||||
|
|
||||||
|
if (!sections) {
|
||||||
|
return severityMap.normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sectionsArray = Object.keys(sections);
|
||||||
|
const sortable = sectionsArray.map((severity) => [
|
||||||
|
severity,
|
||||||
|
sections[severity],
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (const severity of sortable) {
|
||||||
|
if (severityMap[severity[0]] == null || isNaN(severity[1])) {
|
||||||
|
return severityMap.normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortable.sort((a, b) => a[1] - b[1]);
|
||||||
|
|
||||||
|
if (numberValue >= sortable[0][1] && numberValue < sortable[1][1]) {
|
||||||
|
return severityMap[sortable[0][0]];
|
||||||
|
}
|
||||||
|
if (numberValue >= sortable[1][1] && numberValue < sortable[2][1]) {
|
||||||
|
return severityMap[sortable[1][0]];
|
||||||
|
}
|
||||||
|
if (numberValue >= sortable[2][1]) {
|
||||||
|
return severityMap[sortable[2][0]];
|
||||||
|
}
|
||||||
|
return severityMap.normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _translateTurn(value: number, config: Config) {
|
||||||
|
const maxTurnValue = Math.min(Math.max(value, config.min!), config.max!);
|
||||||
|
return (
|
||||||
|
(5 * (maxTurnValue - config.min!)) / (config.max! - config.min!) / 10
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeBaseUnit() {
|
||||||
|
return this.clientWidth < 200 ? this.clientWidth / 5 + "px" : "50px";
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleClick() {
|
||||||
|
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("hui-gauge-card", HuiGaugeCard);
|
@ -22,7 +22,7 @@ import "../cards/hui-sensor-card.js";
|
|||||||
import "../cards/hui-vertical-stack-card.ts";
|
import "../cards/hui-vertical-stack-card.ts";
|
||||||
import "../cards/hui-thermostat-card.ts";
|
import "../cards/hui-thermostat-card.ts";
|
||||||
import "../cards/hui-weather-forecast-card";
|
import "../cards/hui-weather-forecast-card";
|
||||||
import "../cards/hui-gauge-card.js";
|
import "../cards/hui-gauge-card";
|
||||||
|
|
||||||
import createErrorCardConfig from "./create-error-card-config.js";
|
import createErrorCardConfig from "./create-error-card-config.js";
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user