Skip to content

Commit

Permalink
Add resolution option, give elements but paper row an action handler
Browse files Browse the repository at this point in the history
Fixes #75
Fixes #85
  • Loading branch information
rianadon committed Jun 20, 2023
1 parent 64e2b27 commit 24f7c44
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 25 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ Either `entity` or `entities` must be supplied. Use `entity` if you'd like to em
| bar_radius | string | **Optional** | Border radius of the progress bar | - |
| bar_direction | string | **Optional** | Override the direction of bar progress. Can be `ltr` or `rtl` | - |
| layout | string | **Optional** | Hide the name (`hide_name`) and (optionally icon—`full_row`) | `normal` |
| resolution | string | **Optional** | Set to `seconds`, `minutes`, or `automatic` to switch between `h:m:s` and `h:m` formats. | `seconds` |
| modifications | array | **Optional** | Adjustments to make depending on percentage ([example](<#customize-appearance-based-on-timer-percentage>)) | - |
| translations | dict | **Optional** | Mapping of substitutions for status text | |

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lovelace-timer-bar-card",
"version": "1.26.0",
"version": "1.27.0",
"description": "Progress bar display for Home Assistant timers",
"keywords": [
"home-assistant",
Expand Down
17 changes: 10 additions & 7 deletions src/ha-generic-entity-row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,24 @@ export function genericEntityRow(children: TemplateResult, hass?: HomeAssistant,
// Hide the pointer if tap action is none
const pointer = config.tap_action?.action !== "none" ? "pointer" : "";

return html`<div class="generic-entity-row"
@action=${createHandleAction(hass, config)}
.actionHandler=${createActionHandler (config)}
>
return html`<div class="generic-entity-row">
<state-badge
class="${pointer}"
.hass=${hass}
.stateObj=${stateObj}
.overrideIcon=${config.icon}
.overrideImage=${config.image}
.stateColor=${config.state_color}
tabindex="0"
tabindex="${pointer ? "0" : undefined}"
@action=${createHandleAction(hass, config)}
.actionHandler=${createActionHandler(config)}
></state-badge>
${name ? html`<div class="info ${pointer}" .title=${name}>${name}</div>` : ''}
${name
? html`<div class="info ${pointer}" .title=${name}
@action=${createHandleAction(hass, config)}
.actionHandler=${createActionHandler(config)}
>${name}</div>`
: ''}
${createPaperButtons(config.extend_paper_buttons_row, 'center')}
${children}
${createPaperButtons(config.extend_paper_buttons_row, 'right')}
Expand Down
50 changes: 50 additions & 0 deletions src/lib/seconds-to-duration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/** secondsToDuration is adapted from the custom-card-helpers library,
which in turn was adapted from Home Assistant.
https://github.com/home-assistant/frontend/blob/dev/src/common/datetime/seconds_to_duration.ts
I've added the configurable resolution.
*/

type Resolution = "seconds"|"minutes"|"automatic";

const leftPad = (num: number) => (num < 10 ? `0${num}` : num);

/** Returns "minutes" if seconds>=1hr, otherwise minutes. */
function automaticRes(seconds: number) {
if (seconds >= 3600) return "minutes"
return "seconds"
}

/** Returns the h:m:s components as numbers. */
function toHMS(d: number, resolution: Resolution) {
switch(resolution === "automatic" ? automaticRes(d) : resolution) {
case "seconds": {
const h = Math.floor(d / 3600);
const m = Math.floor((d % 3600) / 60);
const s = Math.floor((d % 3600) % 60);
return [h,m,s];
}
case "minutes": {
const h = Math.floor(d / 3600);
const m = Math.floor((d % 3600) / 60);
return [0,h,m];
}
}
}

/** Convert some number of seconds into a duration string. */
export default function secondsToDuration(d: number, resolution: Resolution) {
const [h, m, s] = toHMS(d, resolution)

if (h > 0) {
return `${h}:${leftPad(m)}:${leftPad(s)}`;
}
if (m > 0) {
return `${m}:${leftPad(s)}`;
}
if (s > 0) {
return "" + s;
}
return null;
}
49 changes: 33 additions & 16 deletions src/timer-bar-entity-row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { LitElement, html, CSSResultGroup, TemplateResult, css, PropertyValues }
import { state, property } from "lit/decorators.js";
import { StyleInfo, styleMap } from 'lit/directives/style-map.js';

import { HomeAssistant, hasConfigOrEntityChanged, secondsToDuration, computeStateDisplay } from 'custom-card-helpers';
import { HomeAssistant, hasConfigOrEntityChanged, computeStateDisplay } from 'custom-card-helpers';
import { findDuration, formatStartTime, timerTimeRemaining, timerTimePercent, findMode, stateMode, autoMode, tryDurationToSeconds, MIN_SYNC_ERROR, MAX_FIX_SYNC_ERROR } from './helpers';
import { TimerBarEntityConfig, HassEntity, Translations, TimerBarConfig, Mode } from './types';
import { genericEntityRow, genericEntityRowStyles } from './ha-generic-entity-row';
import { createActionHandler, createHandleAction } from './helpers-actions';
import secondsToDuration from './lib/seconds-to-duration';

export function fillConfig(config: TimerBarEntityConfig): TimerBarConfig {
return {
Expand All @@ -26,6 +27,7 @@ export function fillConfig(config: TimerBarEntityConfig): TimerBarConfig {
bar_background: '#eee',
bar_foreground: 'var(--mdc-theme-primary, #6200ee);',
layout: 'normal',
resolution: 'seconds',
...config,
translations: {
scheduled_for: 'Scheduled for',
Expand Down Expand Up @@ -115,33 +117,28 @@ export class TimerBarEntityRow extends LitElement {
case 'active':
return this._renderRow(activeConfig, html`
${this._renderBar(percent)}
<div class="text-content value ${pointer}" style=${this._textStyle()}>
${secondsToDuration(this._timeRemaining || 0)}
</div>
${this._renderTime(pointer)}
`);

case 'pause':
return this._renderRow(activeConfig, html`
<div class="status ${pointer}" style=${this._statusStyle()}>
${localize(this.hass!, state.state, state, this.config.translations)}
</div>
<div class="text-content value ${pointer}" style=${this._textStyle()}>
${secondsToDuration(this._timeRemaining || 0)}
</div>
${this._renderStatus(pointer, '')}
${this._renderTime(pointer)}
`);

case 'waiting':
return this._renderRow(this.modConfig, html`
<div class="status ${pointer}" style=${this._statusStyle(true)}>
${localize(this.hass!, "scheduled_for", undefined, this.config.translations)} ${formatStartTime(state)}
</div>
${this._renderStatus(pointer, formatStartTime(state))}
`);

default:
const textHidden = (this.modConfig.text_width && parseInt(this.modConfig.text_width) === 0);
const style = textHidden ? 'visibility: hidden' : '';
return this._renderRow(this.modConfig, html`
<div class="text-content value ${pointer}" style=${style}>${localize(this.hass!, state?.state, state, this.config.translations)}</div>
<div class="text-content value ${pointer}" style=${style}
@action=${createHandleAction(this.hass!, this.config)}
.actionHandler=${createActionHandler(this.config)}
>${localize(this.hass!, state?.state, state, this.config.translations)}</div>
`);
}
}
Expand All @@ -151,7 +148,6 @@ export class TimerBarEntityRow extends LitElement {

if (this.modConfig.full_row || this.modConfig.layout === 'full_row')
return html`${warning}<div class="flex" @action=${createHandleAction(this.hass!, config)} .actionHandler=${createActionHandler (config)}> ${contents}</div>${this._renderDebug()}`;

if (this.modConfig.layout === 'hide_name') config = {...config, name: ''};
return html`
${warning}
Expand All @@ -160,6 +156,25 @@ export class TimerBarEntityRow extends LitElement {
`;
}

private _renderTime(pointer: string) {
return html`<div class="text-content value ${pointer}" style=${this._textStyle()}
@action=${createHandleAction(this.hass!, this.config)}
.actionHandler=${createActionHandler(this.config)}>
${secondsToDuration(this._timeRemaining || 0, this.modConfig.resolution!)}
</div>`;
}

private _renderStatus(pointer: string, content: TemplateResult|string) {
const state = this.hass!.states[this.config.entity!];
return html`
<div class="status ${pointer}" style=${this._statusStyle(!!content)}
@action=${createHandleAction(this.hass!, this.config)}
.actionHandler=${createActionHandler(this.config)}>
${localize(this.hass!, state.state, state, this.config.translations)}
${content}
</div>`;
}

private get _bar_width() {
if (this.modConfig.full_row || this.modConfig.layout === 'full_row') return `calc(100% - ${this.modConfig.text_width})`;
if (this.modConfig.layout === 'hide_name') return 'auto';
Expand All @@ -174,7 +189,9 @@ export class TimerBarEntityRow extends LitElement {
const bgStyle = this._barStyle('100%', this.modConfig.bar_background!);
const fgStyle = this._barStyle(percent+"%", this.modConfig.bar_foreground!);
const pointer = this.config.tap_action?.action !== "none" ? "pointer" : "";
return html`<div class="bar-container ${pointer}" style=${containerStyle}>
return html`<div class="bar-container ${pointer}" style=${containerStyle}
@action=${createHandleAction(this.hass!, this.config)}
.actionHandler=${createActionHandler(this.config)}>
<div class="bar" style=${bgStyle}>
<div style=${fgStyle}>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,15 @@ export interface TimerBarEntityConfig extends styleConfig {
start_time?: AttributeConfig;
end_time?: AttributeConfig;
debug?: boolean;
sync_issues?: "show" | "ignore" | "fix",
sync_issues?: "show" | "ignore" | "fix";

modifications?: modsConfig[];
translations?: Translations;
hold_action?: any;
tap_action?: any;
double_tap_action?: any;
extend_paper_buttons_row?: any;
resolution?: "seconds" | "minutes" | "automatic";
}

export interface TimerBarConfig extends TimerBarEntityConfig {
Expand Down

0 comments on commit 24f7c44

Please sign in to comment.