Gauge Card: Convert to Round Slider (#5510)

* Use round slider for gauge

* Update guage to slider

* Add severity back

* Remove Base Unit

* fix merge

* resize observer

* Update src/panels/lovelace/cards/hui-gauge-card.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Update Install Resize Observer to be a helper

* Type import

* Reviews

* Updates to other cards

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Zack Arnett 2020-05-11 17:14:02 -04:00 committed by GitHub
parent c861ee025e
commit ebbe7e805f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 161 additions and 142 deletions

View File

@ -77,7 +77,7 @@
"@polymer/paper-toast": "^3.0.1", "@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0", "@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.3.7", "@thomasloven/round-slider": "0.4.1",
"@vaadin/vaadin-combo-box": "^5.0.10", "@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7", "@vaadin/vaadin-date-picker": "^4.0.7",
"@webcomponents/shadycss": "^1.9.0", "@webcomponents/shadycss": "^1.9.0",

View File

@ -10,17 +10,21 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { styleMap } from "lit-html/directives/style-map"; import { styleMap } from "lit-html/directives/style-map";
import "@thomasloven/round-slider";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import "../../../components/ha-card"; import "../../../components/ha-card";
import { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entites"; import { findEntities } from "../common/find-entites";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-warning"; import "../components/hui-warning";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import type { LovelaceCard, LovelaceCardEditor } from "../types";
import { GaugeCardConfig } from "./types"; import type { GaugeCardConfig } from "./types";
import { debounce } from "../../../common/util/debounce";
import { installResizeObserver } from "../common/install-resize-observer";
export const severityMap = { export const severityMap = {
red: "var(--label-badge-red)", red: "var(--label-badge-red)",
@ -63,11 +67,20 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@property() private _baseUnit = "50px";
@property() private _config?: GaugeCardConfig; @property() private _config?: GaugeCardConfig;
private _updated?: boolean; private _resizeObserver?: ResizeObserver;
public connectedCallback(): void {
super.connectedCallback();
this.updateComplete.then(() => this._attachObserver());
}
public disconnectedCallback(): void {
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
}
public getCardSize(): number { public getCardSize(): number {
return 2; return 2;
@ -83,11 +96,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
this._config = { min: 0, max: 100, ...config }; this._config = { min: 0, max: 100, ...config };
} }
public connectedCallback(): void {
super.connectedCallback();
this._setBaseUnit();
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
@ -121,33 +129,32 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
`; `;
} }
const sliderBarColor = this._computeSeverity(state);
return html` return html`
<ha-card <ha-card
@click="${this._handleClick}" @click=${this._handleClick}
tabindex="0" tabindex="0"
style=${styleMap({ style=${styleMap({
"--base-unit": this._baseUnit, "--round-slider-bar-color": sliderBarColor,
})} })}
> >
<div class="container"> <round-slider
<div class="gauge-a"></div> readonly
<div arcLength="180"
class="gauge-c" startAngle="180"
style=${styleMap({ .value=${state}
transform: `rotate(${this._translateTurn(state)}turn)`, .min=${this._config.min}
"background-color": this._computeSeverity(state), .max=${this._config.max}
})} ></round-slider>
></div>
<div class="gauge-b"></div>
</div>
<div class="gauge-data"> <div class="gauge-data">
<div id="percent"> <div class="percent">
${stateObj.state} ${stateObj.state}
${this._config.unit || ${this._config.unit ||
stateObj.attributes.unit_of_measurement || stateObj.attributes.unit_of_measurement ||
""} ""}
</div> </div>
<div id="name"> <div class="name">
${this._config.name || computeStateName(stateObj)} ${this._config.name || computeStateName(stateObj)}
</div> </div>
</div> </div>
@ -160,10 +167,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
} }
protected firstUpdated(): void { protected firstUpdated(): void {
this._updated = true; this._attachObserver();
this._setBaseUnit();
// eslint-disable-next-line wc/no-self-class
this.classList.add("init");
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
@ -187,16 +191,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
} }
} }
private _setBaseUnit(): void {
if (!this.isConnected || !this._updated) {
return;
}
const baseUnit = this._computeBaseUnit();
if (baseUnit !== "0px") {
this._baseUnit = baseUnit;
}
}
private _computeSeverity(numberValue: number): string { private _computeSeverity(numberValue: number): string {
const sections = this._config!.severity; const sections = this._config!.severity;
@ -229,95 +223,122 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
return severityMap.normal; return severityMap.normal;
} }
private _translateTurn(value: number): number {
const { min, max } = this._config!;
const maxTurnValue = Math.min(Math.max(value, min!), max!);
return (5 * (maxTurnValue - min!)) / (max! - min!) / 10;
}
private _computeBaseUnit(): string {
return this.clientWidth < 200 ? this.clientWidth / 5 + "px" : "50px";
}
private _handleClick(): void { private _handleClick(): void {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
} }
private async _attachObserver(): Promise<void> {
await installResizeObserver();
this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false)
);
const card = this.shadowRoot!.querySelector("ha-card");
// If we show an error or warning there is no ha-card
if (!card) {
return;
}
this._resizeObserver.observe(card);
}
private _measureCard() {
if (this.offsetWidth < 200) {
this.setAttribute("narrow", "");
} else {
this.removeAttribute("narrow");
}
if (this.offsetWidth < 150) {
this.setAttribute("veryNarrow", "");
} else {
this.removeAttribute("veryNarrow");
}
}
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
ha-card { ha-card {
cursor: pointer; cursor: pointer;
padding: 16px 16px 0 16px;
height: 100%; height: 100%;
overflow: hidden;
padding: 16px 16px 0 16px;
display: flex; display: flex;
align-items: center;
justify-content: center;
flex-direction: column; flex-direction: column;
box-sizing: border-box; box-sizing: border-box;
justify-content: center;
align-items: center;
} }
ha-card:focus { ha-card:focus {
outline: none; outline: none;
background: var(--divider-color); background: var(--divider-color);
} }
.container {
width: calc(var(--base-unit) * 4); round-slider {
height: calc(var(--base-unit) * 2); max-width: 200px;
overflow: hidden; --round-slider-path-width: 35px;
position: relative; --round-slider-path-color: var(--disabled-text-color);
} --round-slider-linecap: "butt";
.gauge-a {
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 {
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 {
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;
}
.init .gauge-c {
transition: all 1.3s ease-in-out;
} }
.gauge-data { .gauge-data {
line-height: 1;
text-align: center; text-align: center;
color: var(--primary-text-color);
line-height: calc(var(--base-unit) * 0.3);
width: 100%;
position: relative; position: relative;
top: calc(var(--base-unit) * -0.5); color: var(--primary-text-color);
margin-top: -28px;
margin-bottom: 14px;
} }
.init .gauge-data {
transition: all 1s ease-out; .gauge-data .percent {
font-size: 28px;
} }
.gauge-data #percent {
font-size: calc(var(--base-unit) * 0.55); .gauge-data .name {
line-height: calc(var(--base-unit) * 0.55); padding-top: 6px;
font-size: 14px;
} }
.gauge-data #name {
padding-top: calc(var(--base-unit) * 0.15); /* ============= NARROW ============= */
font-size: calc(var(--base-unit) * 0.3);
:host([narrow]) round-slider {
--round-slider-path-width: 22px;
}
:host([narrow]) .gauge-data {
margin-top: -24px;
margin-bottom: 12px;
}
:host([narrow]) .gauge-data .percent {
font-size: 24px;
}
:host([narrow]) .gauge-data .name {
font-size: 12px;
}
/* ============= VERY NARROW ============= */
:host([veryNarrow]) round-slider {
--round-slider-path-width: 15px;
}
:host([veryNarrow]) ha-card {
padding-bottom: 16px;
}
:host([veryNarrow]) .gauge-data {
margin-top: 0;
margin-bottom: 0;
}
:host([veryNarrow]) .gauge-data .percent {
font-size: 20px;
}
:host([veryNarrow]) .gauge-data .name {
font-size: 10px;
} }
`; `;
} }

View File

@ -46,6 +46,7 @@ import "../components/hui-marquee";
import type { LovelaceCard, LovelaceCardEditor } from "../types"; import type { LovelaceCard, LovelaceCardEditor } from "../types";
import "../components/hui-warning"; import "../components/hui-warning";
import { MediaControlCardConfig } from "./types"; import { MediaControlCardConfig } from "./types";
import { installResizeObserver } from "../common/install-resize-observer";
function getContrastRatio( function getContrastRatio(
rgb1: [number, number, number], rgb1: [number, number, number],
@ -223,7 +224,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.updateComplete.then(() => this._measureCard()); this.updateComplete.then(() => this._attachObserver());
if (!this.hass || !this._config) { if (!this.hass || !this._config) {
return; return;
@ -252,6 +253,9 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
clearInterval(this._progressInterval); clearInterval(this._progressInterval);
this._progressInterval = undefined; this._progressInterval = undefined;
} }
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
} }
protected render(): TemplateResult { protected render(): TemplateResult {
@ -624,15 +628,8 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
this._cardHeight = card.offsetHeight; this._cardHeight = card.offsetHeight;
} }
private _attachObserver(): void { private async _attachObserver(): Promise<void> {
if (typeof ResizeObserver !== "function") { await installResizeObserver();
import("resize-observer").then((modules) => {
modules.install();
this._attachObserver();
});
return;
}
this._resizeObserver = new ResizeObserver( this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false) debounce(() => this._measureCard(), 250, false)
); );

View File

@ -31,6 +31,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-warning"; import "../components/hui-warning";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { WeatherForecastCardConfig } from "./types"; import { WeatherForecastCardConfig } from "./types";
import { installResizeObserver } from "../common/install-resize-observer";
const DAY_IN_MILLISECONDS = 86400000; const DAY_IN_MILLISECONDS = 86400000;
@ -72,7 +73,13 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.updateComplete.then(() => this._measureCard()); this.updateComplete.then(() => this._attachObserver());
}
public disconnectedCallback(): void {
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
} }
public getCardSize(): number { public getCardSize(): number {
@ -299,15 +306,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
} }
private _attachObserver(): void { private async _attachObserver(): Promise<void> {
if (typeof ResizeObserver !== "function") { await installResizeObserver();
import("resize-observer").then((modules) => {
modules.install();
this._attachObserver();
});
return;
}
this._resizeObserver = new ResizeObserver( this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false) debounce(() => this._measureCard(), 250, false)
); );

View File

@ -0,0 +1,6 @@
export const installResizeObserver = async () => {
if (typeof ResizeObserver !== "function") {
const modules = await import("resize-observer");
modules.install();
}
};

View File

@ -31,6 +31,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row"; import "../components/hui-generic-entity-row";
import "../components/hui-warning"; import "../components/hui-warning";
import { EntityConfig, LovelaceRow } from "./types"; import { EntityConfig, LovelaceRow } from "./types";
import { installResizeObserver } from "../common/install-resize-observer";
@customElement("hui-media-player-entity-row") @customElement("hui-media-player-entity-row")
class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow { class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
@ -209,19 +210,13 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
} }
private _attachObserver(): void { private _attachObserver(): void {
if (typeof ResizeObserver !== "function") { installResizeObserver().then(() => {
import("resize-observer").then((modules) => { this._resizeObserver = new ResizeObserver(() =>
modules.install(); this._debouncedResizeListener()
this._attachObserver(); );
});
return;
}
this._resizeObserver = new ResizeObserver(() => this._resizeObserver.observe(this);
this._debouncedResizeListener() });
);
this._resizeObserver.observe(this);
} }
private _computeControlIcon(stateObj: HassEntity): string { private _computeControlIcon(stateObj: HassEntity): string {

View File

@ -2613,10 +2613,10 @@
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
"@thomasloven/round-slider@0.3.7": "@thomasloven/round-slider@0.4.1":
version "0.3.7" version "0.4.1"
resolved "https://registry.yarnpkg.com/@thomasloven/round-slider/-/round-slider-0.3.7.tgz#3f8f16f90296e1062d932f5ea8ebf244aa7e58f6" resolved "https://registry.yarnpkg.com/@thomasloven/round-slider/-/round-slider-0.4.1.tgz#42ddd28abb25c378dce35c4c0ccdd72ea63b3b25"
integrity sha512-rIdEvyLt4YNahpAp1Ibk7qOn9mdgP3Qo2gORyojHqaBTV+t29N1zlTo/G0SbKTLDUtSGDslQWD3/nAdD3yBOYA== integrity sha512-Z6jrXG5vowKQkOwdsyGDLi8ZT9lUfcYjFsaQe8djhDE8+x41GYp5lkJ4uCwT787A8WcODbtQfYtuxPOlZcizTw==
dependencies: dependencies:
lit-element "^2.2.1" lit-element "^2.2.1"