Skip to content

Commit

Permalink
Display live remaining time for timer on tile card (#21290)
Browse files Browse the repository at this point in the history
Display timer by default on tile card
  • Loading branch information
piitaya authored Jul 11, 2024
1 parent d958358 commit e59c04c
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 146 deletions.
2 changes: 1 addition & 1 deletion src/data/timer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const computeDisplayTimer = (
return hass.formatEntityState(stateObj);
}

let display = secondsToDuration(timeRemaining || 0);
let display = secondsToDuration(timeRemaining || 0) || "0";

if (stateObj.state === "paused") {
display = `${display} (${hass.formatEntityState(stateObj)})`;
Expand Down
17 changes: 13 additions & 4 deletions src/panels/lovelace/cards/hui-tile-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,19 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
}

if (domain === "update") {
return html`${computeUpdateStateDisplay(
stateObj as UpdateEntity,
this.hass!
)}`;
return html`
${computeUpdateStateDisplay(stateObj as UpdateEntity, this.hass!)}
`;
}

if (domain === "timer") {
import("../../../state-display/state-display-timer");
return html`
<state-display-timer
.hass=${this.hass}
.stateObj=${stateObj}
></state-display-timer>
`;
}

return this._renderStateContent(stateObj, "state");
Expand Down
87 changes: 6 additions & 81 deletions src/panels/lovelace/entity-rows/hui-timer-entity-row.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, nothing } from "lit";
import { LitElement, PropertyValues, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeDisplayTimer, timerTimeRemaining } from "../../../data/timer";
import "../../../state-display/state-display-timer";
import { HomeAssistant } from "../../../types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-generic-entity-row";
Expand All @@ -14,42 +13,11 @@ class HuiTimerEntityRow extends LitElement {

@state() private _config?: EntityConfig;

@state() private _timeRemaining?: number;

private _interval?: number;

public setConfig(config: EntityConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;

if (!this.hass) {
return;
}

const stateObj = this.hass!.states[this._config.entity];

if (stateObj) {
this._startInterval(stateObj);
} else {
this._clearInterval();
}
}

public disconnectedCallback(): void {
super.disconnectedCallback();
this._clearInterval();
}

public connectedCallback(): void {
super.connectedCallback();
if (this._config && this._config.entity) {
const stateObj = this.hass?.states[this._config!.entity];
if (stateObj) {
this._startInterval(stateObj);
}
}
}

protected render() {
Expand All @@ -70,61 +38,18 @@ class HuiTimerEntityRow extends LitElement {
return html`
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
<div class="text-content">
${computeDisplayTimer(this.hass, stateObj, this._timeRemaining)}
<state-display-timer
.hass=${this.hass}
.stateObj=${stateObj}
></state-display-timer>
</div>
</hui-generic-entity-row>
`;
}

protected shouldUpdate(changedProps: PropertyValues): boolean {
if (changedProps.has("_timeRemaining")) {
return true;
}

return hasConfigOrEntityChanged(this, changedProps);
}

protected updated(changedProps: PropertyValues) {
super.updated(changedProps);

if (!this._config || !changedProps.has("hass")) {
return;
}
const stateObj = this.hass!.states[this._config!.entity];
const oldHass = changedProps.get("hass") as this["hass"];
const oldStateObj = oldHass
? oldHass.states[this._config!.entity]
: undefined;

if (oldStateObj !== stateObj) {
this._startInterval(stateObj);
} else if (!stateObj) {
this._clearInterval();
}
}

private _clearInterval(): void {
if (this._interval) {
window.clearInterval(this._interval);
this._interval = undefined;
}
}

private _startInterval(stateObj: HassEntity): void {
this._clearInterval();
this._calculateRemaining(stateObj);

if (stateObj.state === "active") {
this._interval = window.setInterval(
() => this._calculateRemaining(stateObj),
1000
);
}
}

private _calculateRemaining(stateObj: HassEntity): void {
this._timeRemaining = timerTimeRemaining(stateObj);
}
}

declare global {
Expand Down
74 changes: 74 additions & 0 deletions src/state-display/state-display-timer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { PropertyValues, ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeDisplayTimer, timerTimeRemaining } from "../data/timer";
import type { HomeAssistant } from "../types";

@customElement("state-display-timer")
class StateDisplayTimer extends ReactiveElement {
@property({ attribute: false }) public hass!: HomeAssistant;

@property({ attribute: false }) public stateObj!: HassEntity;

@state() private timeRemaining?: number;

private _updateRemaining: any;

protected createRenderRoot() {
return this;
}

protected update(changedProps: PropertyValues) {
super.update(changedProps);
this.innerHTML =
computeDisplayTimer(this.hass, this.stateObj, this.timeRemaining) ?? "-";
}

connectedCallback() {
super.connectedCallback();
if (this.stateObj) {
this._startInterval(this.stateObj);
}
}

disconnectedCallback() {
super.disconnectedCallback();
this._clearInterval();
}

protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (changedProp.has("stateObj")) {
this._startInterval(this.stateObj);
}
}

private _clearInterval() {
if (this._updateRemaining) {
clearInterval(this._updateRemaining);
this._updateRemaining = null;
}
}

private _startInterval(stateObj: HassEntity) {
this._clearInterval();
this._calculateRemaining(stateObj);

if (stateObj.state === "active") {
this._updateRemaining = setInterval(
() => this._calculateRemaining(this.stateObj),
1000
);
}
}

private _calculateRemaining(stateObj: HassEntity) {
this.timeRemaining = timerTimeRemaining(stateObj);
}
}

declare global {
interface HTMLElementTagNameMap {
"state-display-timer": StateDisplayTimer;
}
}
67 changes: 7 additions & 60 deletions src/state-summary/state-card-timer.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import type { HassEntity } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../components/entity/state-info";
import { computeDisplayTimer, timerTimeRemaining } from "../data/timer";
import { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
import "../state-display/state-display-timer";
import { HomeAssistant } from "../types";

@customElement("state-card-timer")
class StateCardTimer extends LitElement {
Expand All @@ -21,10 +14,6 @@ class StateCardTimer extends LitElement {

@property({ type: Boolean }) public inDialog = false;

@property({ type: Number }) public timeRemaining?: number;

private _updateRemaining: any;

protected render(): TemplateResult {
return html`
<div class="horizontal justified layout">
Expand All @@ -34,63 +23,21 @@ class StateCardTimer extends LitElement {
.inDialog=${this.inDialog}
></state-info>
<div class="state">
${this._displayState(this.timeRemaining, this.stateObj)}
<state-display-timer
.hass=${this.hass}
.stateObj=${this.stateObj}
></state-display-timer>
</div>
</div>
`;
}

connectedCallback() {
super.connectedCallback();
this._startInterval(this.stateObj);
}

disconnectedCallback() {
super.disconnectedCallback();
this._clearInterval();
}

protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (changedProp.has("stateObj")) {
this._startInterval(this.stateObj);
}
}

private _clearInterval() {
if (this._updateRemaining) {
clearInterval(this._updateRemaining);
this._updateRemaining = null;
}
}

private _startInterval(stateObj) {
this._clearInterval();
this._calculateRemaining(stateObj);

if (stateObj.state === "active") {
this._updateRemaining = setInterval(
() => this._calculateRemaining(this.stateObj),
1000
);
}
}

private _calculateRemaining(stateObj) {
this.timeRemaining = timerTimeRemaining(stateObj);
}

private _displayState(timeRemaining, stateObj) {
return computeDisplayTimer(this.hass, stateObj, timeRemaining);
}

static get styles(): CSSResultGroup {
return [
haStyle,
css`
.state {
color: var(--primary-text-color);
margin-left: 16px;
margin-inline-start: 16px;
margin-inline-end: initial;
Expand Down

0 comments on commit e59c04c

Please sign in to comment.