Add hour formats. Fix and refactor style variables

This commit is contained in:
2020-05-06 16:20:14 +03:00
parent fa8c43fbc0
commit 677d596408
8 changed files with 167 additions and 32 deletions
+64
View File
@@ -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` <div class="time-period-selector">
<div class=${classMap(this.amClass)} @click=${this.onTimePeriodChange}>
AM<mwc-ripple></mwc-ripple>
</div>
<div class=${classMap(this.pmClass)} @click=${this.onTimePeriodChange}>
PM<mwc-ripple></mwc-ripple>
</div>
</div>`;
}
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);
}
`;
}
}
+8 -5
View File
@@ -12,6 +12,8 @@ import { Direction } from '../types';
@customElement('time-unit') @customElement('time-unit')
export class TimeUnitComponent extends LitElement { export class TimeUnitComponent extends LitElement {
static readonly EVENT_UPDATE = 'update';
@property() private unit!: TimeUnit; @property() private unit!: TimeUnit;
render(): TemplateResult { render(): TemplateResult {
@@ -23,7 +25,7 @@ export class TimeUnitComponent extends LitElement {
type="number" type="number"
placeholder="MM" placeholder="MM"
min="0" min="0"
max="60" max=${this.unit.maxValue}
.value="${this.unit.toString()}" .value="${this.unit.toString()}"
@change=${this.onInputChange} @change=${this.onInputChange}
/> />
@@ -43,7 +45,7 @@ export class TimeUnitComponent extends LitElement {
} }
private emitUpdate(): void { private emitUpdate(): void {
const event = new CustomEvent('update'); const event = new CustomEvent(TimeUnitComponent.EVENT_UPDATE);
this.dispatchEvent(event); this.dispatchEvent(event);
} }
@@ -70,15 +72,16 @@ export class TimeUnitComponent extends LitElement {
padding: 8px; padding: 8px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
color: var(--tpc-icon-color);
} }
.time-input { .time-input {
width: 30px; width: 30px;
padding: 8px 8px 6px; padding: 8px 8px 6px;
background: var(--time-picker-card-background-color); background: var(--tpc-elements-background-color);
border: 0; border: 0;
border-bottom: 2px solid var(--time-picker-card-background-color); border-bottom: 2px solid var(--tpc-elements-background-color);
color: var(--text-color, #fff); color: var(--tpc-text-color, #fff);
text-align: center; text-align: center;
font-size: 1em; font-size: 1em;
-moz-appearance: textfield; -moz-appearance: textfield;
-4
View File
@@ -2,7 +2,3 @@ import * as pkg from '../package.json';
export const CARD_VERSION = pkg.version; export const CARD_VERSION = pkg.version;
export const CARD_SIZE = 3; export const CARD_SIZE = 3;
export const STYLE_VARIABLES = {
'--time-picker-card-background-color': 'rgb(37, 47, 68)',
};
+23 -1
View File
@@ -1,10 +1,32 @@
import { TimeUnit } from './time-unit'; import { TimeUnit } from './time-unit';
import { HourMode } from '../types';
export class Hour extends TimeUnit { export class Hour extends TimeUnit {
private static readonly DEFAULT_STEP = 1; private static readonly DEFAULT_STEP = 1;
private static readonly MAX = 24; 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); 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;
}
} }
+8 -1
View File
@@ -2,9 +2,16 @@ import { TimeUnit } from './time-unit';
export class Minute extends TimeUnit { export class Minute extends TimeUnit {
private static readonly DEFAULT_STEP = 5; 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) { constructor(value: number, step = Minute.DEFAULT_STEP) {
super(value, step, Minute.MAX); super(value, step, Minute.MAX);
} }
protected isValidString(valueStr: string): boolean {
const value = parseInt(valueStr);
return !isNaN(value) && value >= 0 && value <= this._limit;
}
} }
+18 -7
View File
@@ -1,7 +1,23 @@
import { Direction } from '../types'; import { Direction } from '../types';
export abstract class TimeUnit { 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 { get value(): number {
return this._value; return this._value;
@@ -29,12 +45,7 @@ export abstract class TimeUnit {
return this.value < 10 ? `0${this.value}` : this.value.toString(); return this.value < 10 ? `0${this.value}` : this.value.toString();
} }
private isValidString(valueStr: string): boolean { protected setValue(newValue: number): void {
const value = parseInt(valueStr);
return !isNaN(value) && value >= 0 && value <= this._limit;
}
private setValue(newValue: number): void {
if (newValue >= this._limit || newValue < 0) { if (newValue >= this._limit || newValue < 0) {
newValue = (newValue + this._limit) % this._limit; newValue = (newValue + this._limit) % this._limit;
} }
+38 -14
View File
@@ -9,12 +9,13 @@ import {
property, property,
TemplateResult, TemplateResult,
} from 'lit-element'; } from 'lit-element';
import './components/time-period.component';
import './components/time-unit.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 { Hour } from './models/hour';
import { Minute } from './models/minute'; import { Minute } from './models/minute';
import { Partial } from './partials'; import { Partial } from './partials';
import { TimePickerCardConfig } from './types'; import { Period, TimePickerCardConfig } from './types';
console.info( console.info(
`%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `, `%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `,
@@ -28,14 +29,7 @@ export class TimePickerCard extends LitElement {
@property() private config!: TimePickerCardConfig; @property() private config!: TimePickerCardConfig;
@property() private hour!: Hour; @property() private hour!: Hour;
@property() private minute!: Minute; @property() private minute!: Minute;
@property() private period!: Period;
connectedCallback(): void {
super.connectedCallback();
Object.entries(STYLE_VARIABLES).forEach(([variable, value]) =>
this.style.setProperty(variable, value)
);
}
private get entity(): HassEntity | undefined { private get entity(): HassEntity | undefined {
return this.hass.states[this.config.entity]; 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; return this.config.name || this.entity?.attributes.friendly_name;
} }
private get shouldShowPeriod(): boolean {
return this.config.hour_mode === 12;
}
render(): TemplateResult | null { render(): TemplateResult | null {
if (!this.entity) { if (!this.entity) {
return Partial.error('Entity not found', this.config); return Partial.error('Entity not found', this.config);
@@ -66,16 +64,24 @@ export class TimePickerCard extends LitElement {
} }
const { hour, minute } = this.entity!.attributes; 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.minute = new Minute(minute, this.config.minute_step);
this.period = this.hour.value >= 12 ? Period.PM : Period.AM;
return html` return html`
<ha-card class="time-picker-ha-card"> <ha-card>
${this.shouldShowName ? Partial.header(this.name!) : ''} ${this.shouldShowName ? Partial.header(this.name!) : ''}
<div class="time-picker-content"> <div class="time-picker-content">
<time-unit .unit=${this.hour} @update=${this.callHassService}></time-unit> <time-unit .unit=${this.hour} @update=${this.callHassService}></time-unit>
<div class="time-separator">:</div> <div class="time-separator">:</div>
<time-unit .unit=${this.minute} @update=${this.callHassService}></time-unit> <time-unit .unit=${this.minute} @update=${this.callHassService}></time-unit>
${this.shouldShowPeriod
? html`<time-period
.period=${this.period}
@toggle=${this.onPeriodToggle}
></time-period>`
: ''}
</div> </div>
</ha-card> </ha-card>
`; `;
@@ -90,6 +96,10 @@ export class TimePickerCard extends LitElement {
throw new Error('You must set an entity'); 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; this.config = config;
} }
@@ -97,6 +107,11 @@ export class TimePickerCard extends LitElement {
return CARD_SIZE; return CARD_SIZE;
} }
private onPeriodToggle(): void {
this.hour.togglePeriod();
this.callHassService();
}
private callHassService(): Promise<void> { private callHassService(): Promise<void> {
if (!this.hass) { if (!this.hass) {
throw new Error('Unable to update datetime'); throw new Error('Unable to update datetime');
@@ -112,12 +127,21 @@ export class TimePickerCard extends LitElement {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` 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 { .time-picker-header {
padding: 16px; 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; font-size: 1em;
text-align: center; text-align: center;
} }
+8
View File
@@ -3,6 +3,7 @@ import { LovelaceCardConfig } from 'custom-card-helpers';
export interface TimePickerCardConfig extends LovelaceCardConfig { export interface TimePickerCardConfig extends LovelaceCardConfig {
entity: string; entity: string;
name?: string; name?: string;
hour_mode?: HourMode;
hour_step?: number; hour_step?: number;
minute_step?: number; minute_step?: number;
hide?: TimePickerHideConfig; hide?: TimePickerHideConfig;
@@ -16,3 +17,10 @@ export enum Direction {
UP = 'up', UP = 'up',
DOWN = 'down', DOWN = 'down',
} }
export enum Period {
AM = 'am',
PM = 'pm',
}
export type HourMode = 12 | 24 | undefined;