mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 05:47:20 +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-thermostat-card.ts";
|
||||
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";
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user