From ebbe7e805f3a11de77c7ed03670b1251f5548b0b Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Mon, 11 May 2020 17:14:02 -0400 Subject: [PATCH] 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 * Update Install Resize Observer to be a helper * Type import * Reviews * Updates to other cards Co-authored-by: Bram Kragten --- package.json | 2 +- src/panels/lovelace/cards/hui-gauge-card.ts | 231 ++++++++++-------- .../lovelace/cards/hui-media-control-card.ts | 17 +- .../cards/hui-weather-forecast-card.ts | 20 +- .../common/install-resize-observer.ts | 6 + .../hui-media-player-entity-row.ts | 19 +- yarn.lock | 8 +- 7 files changed, 161 insertions(+), 142 deletions(-) create mode 100644 src/panels/lovelace/common/install-resize-observer.ts diff --git a/package.json b/package.json index e1c47e9cf3..d5f286178b 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "@polymer/paper-toast": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1", "@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-date-picker": "^4.0.7", "@webcomponents/shadycss": "^1.9.0", diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index c2e24d6d00..9378f4da0f 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -10,17 +10,21 @@ import { TemplateResult, } from "lit-element"; import { styleMap } from "lit-html/directives/style-map"; +import "@thomasloven/round-slider"; + import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import "../../../components/ha-card"; -import { HomeAssistant } from "../../../types"; +import type { HomeAssistant } from "../../../types"; import { findEntities } from "../common/find-entites"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-warning"; -import { LovelaceCard, LovelaceCardEditor } from "../types"; -import { GaugeCardConfig } from "./types"; +import type { LovelaceCard, LovelaceCardEditor } from "../types"; +import type { GaugeCardConfig } from "./types"; +import { debounce } from "../../../common/util/debounce"; +import { installResizeObserver } from "../common/install-resize-observer"; export const severityMap = { red: "var(--label-badge-red)", @@ -63,11 +67,20 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { @property() public hass?: HomeAssistant; - @property() private _baseUnit = "50px"; - @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 { return 2; @@ -83,11 +96,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { this._config = { min: 0, max: 100, ...config }; } - public connectedCallback(): void { - super.connectedCallback(); - this._setBaseUnit(); - } - protected render(): TemplateResult { if (!this._config || !this.hass) { return html``; @@ -121,33 +129,32 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { `; } + const sliderBarColor = this._computeSeverity(state); + return html` -
-
-
-
-
+
-
+
${stateObj.state} ${this._config.unit || stateObj.attributes.unit_of_measurement || ""}
-
+
${this._config.name || computeStateName(stateObj)}
@@ -160,10 +167,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { } protected firstUpdated(): void { - this._updated = true; - this._setBaseUnit(); - // eslint-disable-next-line wc/no-self-class - this.classList.add("init"); + this._attachObserver(); } 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 { const sections = this._config!.severity; @@ -229,95 +223,122 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { 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 { fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); } + private async _attachObserver(): Promise { + 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 { return css` ha-card { cursor: pointer; - padding: 16px 16px 0 16px; height: 100%; + overflow: hidden; + padding: 16px 16px 0 16px; display: flex; + align-items: center; + justify-content: center; flex-direction: column; box-sizing: border-box; - justify-content: center; - align-items: center; } + ha-card:focus { outline: none; background: var(--divider-color); } - .container { - width: calc(var(--base-unit) * 4); - height: calc(var(--base-unit) * 2); - overflow: hidden; - position: relative; - } - .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; + + round-slider { + max-width: 200px; + --round-slider-path-width: 35px; + --round-slider-path-color: var(--disabled-text-color); + --round-slider-linecap: "butt"; } + .gauge-data { + line-height: 1; text-align: center; - color: var(--primary-text-color); - line-height: calc(var(--base-unit) * 0.3); - width: 100%; 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); - line-height: calc(var(--base-unit) * 0.55); + + .gauge-data .name { + padding-top: 6px; + font-size: 14px; } - .gauge-data #name { - padding-top: calc(var(--base-unit) * 0.15); - font-size: calc(var(--base-unit) * 0.3); + + /* ============= NARROW ============= */ + + :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; } `; } diff --git a/src/panels/lovelace/cards/hui-media-control-card.ts b/src/panels/lovelace/cards/hui-media-control-card.ts index f417ed131b..9e66390f5b 100644 --- a/src/panels/lovelace/cards/hui-media-control-card.ts +++ b/src/panels/lovelace/cards/hui-media-control-card.ts @@ -46,6 +46,7 @@ import "../components/hui-marquee"; import type { LovelaceCard, LovelaceCardEditor } from "../types"; import "../components/hui-warning"; import { MediaControlCardConfig } from "./types"; +import { installResizeObserver } from "../common/install-resize-observer"; function getContrastRatio( rgb1: [number, number, number], @@ -223,7 +224,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { public connectedCallback(): void { super.connectedCallback(); - this.updateComplete.then(() => this._measureCard()); + this.updateComplete.then(() => this._attachObserver()); if (!this.hass || !this._config) { return; @@ -252,6 +253,9 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { clearInterval(this._progressInterval); this._progressInterval = undefined; } + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + } } protected render(): TemplateResult { @@ -624,15 +628,8 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { this._cardHeight = card.offsetHeight; } - private _attachObserver(): void { - if (typeof ResizeObserver !== "function") { - import("resize-observer").then((modules) => { - modules.install(); - this._attachObserver(); - }); - return; - } - + private async _attachObserver(): Promise { + await installResizeObserver(); this._resizeObserver = new ResizeObserver( debounce(() => this._measureCard(), 250, false) ); diff --git a/src/panels/lovelace/cards/hui-weather-forecast-card.ts b/src/panels/lovelace/cards/hui-weather-forecast-card.ts index 76688e0c56..8fa9356417 100644 --- a/src/panels/lovelace/cards/hui-weather-forecast-card.ts +++ b/src/panels/lovelace/cards/hui-weather-forecast-card.ts @@ -31,6 +31,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-warning"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { WeatherForecastCardConfig } from "./types"; +import { installResizeObserver } from "../common/install-resize-observer"; const DAY_IN_MILLISECONDS = 86400000; @@ -72,7 +73,13 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { public connectedCallback(): void { super.connectedCallback(); - this.updateComplete.then(() => this._measureCard()); + this.updateComplete.then(() => this._attachObserver()); + } + + public disconnectedCallback(): void { + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + } } public getCardSize(): number { @@ -299,15 +306,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); } - private _attachObserver(): void { - if (typeof ResizeObserver !== "function") { - import("resize-observer").then((modules) => { - modules.install(); - this._attachObserver(); - }); - return; - } - + private async _attachObserver(): Promise { + await installResizeObserver(); this._resizeObserver = new ResizeObserver( debounce(() => this._measureCard(), 250, false) ); diff --git a/src/panels/lovelace/common/install-resize-observer.ts b/src/panels/lovelace/common/install-resize-observer.ts new file mode 100644 index 0000000000..f675858d99 --- /dev/null +++ b/src/panels/lovelace/common/install-resize-observer.ts @@ -0,0 +1,6 @@ +export const installResizeObserver = async () => { + if (typeof ResizeObserver !== "function") { + const modules = await import("resize-observer"); + modules.install(); + } +}; diff --git a/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts b/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts index ed2e230f90..3792aef585 100644 --- a/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts @@ -31,6 +31,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; import "../components/hui-warning"; import { EntityConfig, LovelaceRow } from "./types"; +import { installResizeObserver } from "../common/install-resize-observer"; @customElement("hui-media-player-entity-row") class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow { @@ -209,19 +210,13 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow { } private _attachObserver(): void { - if (typeof ResizeObserver !== "function") { - import("resize-observer").then((modules) => { - modules.install(); - this._attachObserver(); - }); - return; - } + installResizeObserver().then(() => { + this._resizeObserver = new ResizeObserver(() => + this._debouncedResizeListener() + ); - this._resizeObserver = new ResizeObserver(() => - this._debouncedResizeListener() - ); - - this._resizeObserver.observe(this); + this._resizeObserver.observe(this); + }); } private _computeControlIcon(stateObj: HassEntity): string { diff --git a/yarn.lock b/yarn.lock index 69e66b6ce9..c33003fb3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2613,10 +2613,10 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== -"@thomasloven/round-slider@0.3.7": - version "0.3.7" - resolved "https://registry.yarnpkg.com/@thomasloven/round-slider/-/round-slider-0.3.7.tgz#3f8f16f90296e1062d932f5ea8ebf244aa7e58f6" - integrity sha512-rIdEvyLt4YNahpAp1Ibk7qOn9mdgP3Qo2gORyojHqaBTV+t29N1zlTo/G0SbKTLDUtSGDslQWD3/nAdD3yBOYA== +"@thomasloven/round-slider@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@thomasloven/round-slider/-/round-slider-0.4.1.tgz#42ddd28abb25c378dce35c4c0ccdd72ea63b3b25" + integrity sha512-Z6jrXG5vowKQkOwdsyGDLi8ZT9lUfcYjFsaQe8djhDE8+x41GYp5lkJ4uCwT787A8WcODbtQfYtuxPOlZcizTw== dependencies: lit-element "^2.2.1"