From 677d596408c317316d1b2f2cb56cc8e586ac7ff1 Mon Sep 17 00:00:00 2001 From: Georgi Gardev Date: Wed, 6 May 2020 16:20:14 +0300 Subject: [PATCH] Add hour formats. Fix and refactor style variables --- src/components/time-period.component.ts | 64 +++++++++++++++++++++++++ src/components/time-unit.component.ts | 13 +++-- src/const.ts | 4 -- src/models/hour.ts | 24 +++++++++- src/models/minute.ts | 9 +++- src/models/time-unit.ts | 25 +++++++--- src/time-picker-card.ts | 52 ++++++++++++++------ src/types.ts | 8 ++++ 8 files changed, 167 insertions(+), 32 deletions(-) create mode 100644 src/components/time-period.component.ts diff --git a/src/components/time-period.component.ts b/src/components/time-period.component.ts new file mode 100644 index 0000000..71195b0 --- /dev/null +++ b/src/components/time-period.component.ts @@ -0,0 +1,64 @@ +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from 'lit-element'; +import { ClassInfo, classMap } from 'lit-html/directives/class-map'; +import { Period } from '../types'; + +@customElement('time-period') +export class TimePeriodComponent extends LitElement { + static readonly EVENT_TOGGLE = 'toggle'; + + @property() private period!: Period; + + render(): TemplateResult { + return html`
+
+ AM +
+
+ PM +
+
`; + } + + onTimePeriodChange(): void { + const event = new CustomEvent(TimePeriodComponent.EVENT_TOGGLE); + this.dispatchEvent(event); + } + + private get amClass(): ClassInfo { + return { 'time-period': true, active: this.period === Period.AM }; + } + + private get pmClass(): ClassInfo { + return { 'time-period': true, active: this.period === Period.PM }; + } + + static get styles(): CSSResult { + return css` + .time-period-selector { + padding: 0 8px; + } + + .time-period { + width: 30px; + padding: 8px; + background: var(--tpc-elements-background-color); + color: var(--tpc-text-color, #fff); + text-align: center; + font-size: 1em; + cursor: pointer; + } + + .time-period.active { + background: var(--tpc-accent-color); + } + `; + } +} diff --git a/src/components/time-unit.component.ts b/src/components/time-unit.component.ts index bff630b..4688118 100644 --- a/src/components/time-unit.component.ts +++ b/src/components/time-unit.component.ts @@ -12,6 +12,8 @@ import { Direction } from '../types'; @customElement('time-unit') export class TimeUnitComponent extends LitElement { + static readonly EVENT_UPDATE = 'update'; + @property() private unit!: TimeUnit; render(): TemplateResult { @@ -23,7 +25,7 @@ export class TimeUnitComponent extends LitElement { type="number" placeholder="MM" min="0" - max="60" + max=${this.unit.maxValue} .value="${this.unit.toString()}" @change=${this.onInputChange} /> @@ -43,7 +45,7 @@ export class TimeUnitComponent extends LitElement { } private emitUpdate(): void { - const event = new CustomEvent('update'); + const event = new CustomEvent(TimeUnitComponent.EVENT_UPDATE); this.dispatchEvent(event); } @@ -70,15 +72,16 @@ export class TimeUnitComponent extends LitElement { padding: 8px; text-align: center; cursor: pointer; + color: var(--tpc-icon-color); } .time-input { width: 30px; padding: 8px 8px 6px; - background: var(--time-picker-card-background-color); + background: var(--tpc-elements-background-color); border: 0; - border-bottom: 2px solid var(--time-picker-card-background-color); - color: var(--text-color, #fff); + border-bottom: 2px solid var(--tpc-elements-background-color); + color: var(--tpc-text-color, #fff); text-align: center; font-size: 1em; -moz-appearance: textfield; diff --git a/src/const.ts b/src/const.ts index 31b84a4..28002c0 100644 --- a/src/const.ts +++ b/src/const.ts @@ -2,7 +2,3 @@ import * as pkg from '../package.json'; export const CARD_VERSION = pkg.version; export const CARD_SIZE = 3; - -export const STYLE_VARIABLES = { - '--time-picker-card-background-color': 'rgb(37, 47, 68)', -}; diff --git a/src/models/hour.ts b/src/models/hour.ts index 52406a2..7079816 100644 --- a/src/models/hour.ts +++ b/src/models/hour.ts @@ -1,10 +1,32 @@ import { TimeUnit } from './time-unit'; +import { HourMode } from '../types'; export class Hour extends TimeUnit { private static readonly DEFAULT_STEP = 1; private static readonly MAX = 24; - constructor(value: number, step = Hour.DEFAULT_STEP) { + constructor(value: number, step = Hour.DEFAULT_STEP, private hourMode: HourMode) { super(value, step, Hour.MAX); } + + get maxValue(): number { + return this.hourMode || Hour.MAX; + } + + togglePeriod(): void { + this.setValue(this.value + 12); + } + + toString(): string { + const value = this.hourMode === 12 ? (this.value + 12) % 12 : this.value; + + return value < 10 ? `0${value}` : value.toString(); + } + + protected isValidString(valueStr: string): boolean { + const value = parseInt(valueStr); + const limit = this.hourMode || this._limit; + + return !isNaN(value) && value >= 0 && value <= limit; + } } diff --git a/src/models/minute.ts b/src/models/minute.ts index 0e714e4..f9b6d8d 100644 --- a/src/models/minute.ts +++ b/src/models/minute.ts @@ -2,9 +2,16 @@ import { TimeUnit } from './time-unit'; export class Minute extends TimeUnit { private static readonly DEFAULT_STEP = 5; - private static readonly MAX = 50; + private static readonly MAX = 60; + + maxValue = Minute.MAX; constructor(value: number, step = Minute.DEFAULT_STEP) { super(value, step, Minute.MAX); } + + protected isValidString(valueStr: string): boolean { + const value = parseInt(valueStr); + return !isNaN(value) && value >= 0 && value <= this._limit; + } } diff --git a/src/models/time-unit.ts b/src/models/time-unit.ts index 78b87b9..db5e4af 100644 --- a/src/models/time-unit.ts +++ b/src/models/time-unit.ts @@ -1,7 +1,23 @@ import { Direction } from '../types'; export abstract class TimeUnit { - constructor(private _value: number, private _step: number, private _limit: number) {} + /** + * Return true if the valueStr can be set as a value of this instance. + */ + protected abstract isValidString(valueStr: string): boolean; + + /** + * The max allowed value for this instance. Used for UI validation. + */ + abstract maxValue: number; + + /** + * Create a new instance of a TimeUnit + * @param _value current value + * @param _step how much to increase / decrease the value when step-changing + * @param _limit value upper limit + */ + constructor(private _value: number, protected _step: number, protected _limit: number) {} get value(): number { return this._value; @@ -29,12 +45,7 @@ export abstract class TimeUnit { return this.value < 10 ? `0${this.value}` : this.value.toString(); } - private isValidString(valueStr: string): boolean { - const value = parseInt(valueStr); - return !isNaN(value) && value >= 0 && value <= this._limit; - } - - private setValue(newValue: number): void { + protected setValue(newValue: number): void { if (newValue >= this._limit || newValue < 0) { newValue = (newValue + this._limit) % this._limit; } diff --git a/src/time-picker-card.ts b/src/time-picker-card.ts index 36df334..d70e1cd 100644 --- a/src/time-picker-card.ts +++ b/src/time-picker-card.ts @@ -9,12 +9,13 @@ import { property, TemplateResult, } from 'lit-element'; +import './components/time-period.component'; import './components/time-unit.component'; -import { CARD_SIZE, CARD_VERSION, STYLE_VARIABLES } from './const'; +import { CARD_SIZE, CARD_VERSION } from './const'; import { Hour } from './models/hour'; import { Minute } from './models/minute'; import { Partial } from './partials'; -import { TimePickerCardConfig } from './types'; +import { Period, TimePickerCardConfig } from './types'; console.info( `%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `, @@ -28,14 +29,7 @@ export class TimePickerCard extends LitElement { @property() private config!: TimePickerCardConfig; @property() private hour!: Hour; @property() private minute!: Minute; - - connectedCallback(): void { - super.connectedCallback(); - - Object.entries(STYLE_VARIABLES).forEach(([variable, value]) => - this.style.setProperty(variable, value) - ); - } + @property() private period!: Period; private get entity(): HassEntity | undefined { return this.hass.states[this.config.entity]; @@ -49,6 +43,10 @@ export class TimePickerCard extends LitElement { return this.config.name || this.entity?.attributes.friendly_name; } + private get shouldShowPeriod(): boolean { + return this.config.hour_mode === 12; + } + render(): TemplateResult | null { if (!this.entity) { return Partial.error('Entity not found', this.config); @@ -66,16 +64,24 @@ export class TimePickerCard extends LitElement { } const { hour, minute } = this.entity!.attributes; - this.hour = new Hour(hour, this.config.hour_step); + this.hour = new Hour(hour, this.config.hour_step, this.config.hour_mode); this.minute = new Minute(minute, this.config.minute_step); + this.period = this.hour.value >= 12 ? Period.PM : Period.AM; return html` - + ${this.shouldShowName ? Partial.header(this.name!) : ''}
:
+ + ${this.shouldShowPeriod + ? html`` + : ''}
`; @@ -90,6 +96,10 @@ export class TimePickerCard extends LitElement { throw new Error('You must set an entity'); } + if (config.hour_mode && config.hour_mode !== 12 && config.hour_mode !== 24) { + throw new Error('Invalid hour_mode: select either 12 or 24'); + } + this.config = config; } @@ -97,6 +107,11 @@ export class TimePickerCard extends LitElement { return CARD_SIZE; } + private onPeriodToggle(): void { + this.hour.togglePeriod(); + this.callHassService(); + } + private callHassService(): Promise { if (!this.hass) { throw new Error('Unable to update datetime'); @@ -112,12 +127,21 @@ export class TimePickerCard extends LitElement { static get styles(): CSSResult { return css` - .time-picker-ha-card { + :host { + --tpc-elements-background-color: var( + --time-picker-elements-background-color, + var(--dark-primary-color) + ); + + --tpc-icon-color: var(--time-picker-icon-color, var(--primary-text-color)); + --tpc-text-color: var(--time-picker-text-color, #fff); + --tpc-accent-color: var(--time-picker-accent-color, var(--accent-color)); } .time-picker-header { padding: 16px; - background-color: var(--time-picker-card-background-color); + color: var(--tpc-text-color, #fff); + background-color: var(--tpc-elements-background-color); font-size: 1em; text-align: center; } diff --git a/src/types.ts b/src/types.ts index d52146d..0172ffa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import { LovelaceCardConfig } from 'custom-card-helpers'; export interface TimePickerCardConfig extends LovelaceCardConfig { entity: string; name?: string; + hour_mode?: HourMode; hour_step?: number; minute_step?: number; hide?: TimePickerHideConfig; @@ -16,3 +17,10 @@ export enum Direction { UP = 'up', DOWN = 'down', } + +export enum Period { + AM = 'am', + PM = 'pm', +} + +export type HourMode = 12 | 24 | undefined;