Initial implementation

This commit is contained in:
2020-05-05 01:41:35 +03:00
parent 4b0d4a4560
commit 2e158474c0
8 changed files with 243 additions and 6 deletions
+10
View File
@@ -0,0 +1,10 @@
import { TimeUnit } from './TimeUnit';
export class Hour extends TimeUnit {
private static readonly DEFAULT_STEP = 1;
private static readonly MAX = 24;
constructor(value: number, step = Hour.DEFAULT_STEP) {
super(value, step, Hour.MAX);
}
}
+10
View File
@@ -0,0 +1,10 @@
import { TimeUnit } from './TimeUnit';
export class Minute extends TimeUnit {
private static readonly DEFAULT_STEP = 5;
private static readonly MAX = 50;
constructor(value: number, step = Minute.DEFAULT_STEP) {
super(value, step, Minute.MAX);
}
}
+44
View File
@@ -0,0 +1,44 @@
import { Direction } from '../types';
export abstract class TimeUnit {
constructor(private _value: number, private _step: number, private _limit: number) {}
get value(): number {
return this._value;
}
setStringValue(stringValue: string): void {
if (this.isValidString(stringValue)) {
this.setValue(parseInt(stringValue));
}
}
stepUpdate(direction: Direction): void {
direction === Direction.UP ? this.increment() : this.decrement();
}
increment(): void {
this.setValue(this.value + this._step);
}
decrement(): void {
this.setValue(this.value - this._step);
}
toString(): string {
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 {
if (newValue >= this._limit || newValue < 0) {
newValue = (newValue + this._limit) % this._limit;
}
this._value = newValue;
}
}
+3
View File
@@ -0,0 +1,3 @@
export { TimeUnit } from './TimeUnit';
export { Hour } from './Hour';
export { Minute } from './Minute';
+9
View File
@@ -0,0 +1,9 @@
import { html, TemplateResult } from 'lit-element';
import { TimePickerCardConfig } from './types';
export class Partial {
static error(error: string, origConfig: TimePickerCardConfig): TemplateResult {
const config = { error, origConfig };
return html`<hui-error-card ._config="${config}"></hui-error-card>`;
}
}
+59 -3
View File
@@ -1,8 +1,64 @@
import { css } from 'lit-element';
export const styles = css`
.card-warning {
background-color: var(--google-red-500, #ef5350);
color: #fff;
.time-picker-ha-card {
padding: 16px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.time-form {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 8px;
}
.time-picker-icon-up,
.time-picker-icon-down {
cursor: pointer;
}
.time-picker-icon-up {
padding: 8px;
}
.time-picker-icon-down {
margin-top: 8px;
padding: 8px;
}
.time-input {
width: 30px;
padding: 8px;
background: var(--card-background-color, transparent);
border: 0;
color: var(--text-color, #fff);
border-bottom: 1px solid var(--text-color, #fff);
text-align: center;
font-size: 1em;
}
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type='number'] {
-moz-appearance: textfield;
}
input[type='number']:focus {
outline: none;
}
input[type='number']:invalid {
box-shadow: none;
outline: none;
border: 0;
border-bottom: 2px solid red;
}
`;
+101 -3
View File
@@ -1,8 +1,11 @@
import { HomeAssistant } from 'custom-card-helpers';
import { HassEntity } from 'home-assistant-js-websocket';
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from 'lit-element';
import { CARD_VERSION } from './const';
import { Hour, Minute, TimeUnit } from './models';
import { Partial } from './partials';
import { styles } from './styles';
import { TimePickerCardConfig } from './types';
import { Direction, TimePickerCardConfig } from './types';
console.info(
`%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `,
@@ -14,13 +17,69 @@ console.info(
export class TimePickerCard extends LitElement {
@property() private hass?: HomeAssistant;
@property() private config?: TimePickerCardConfig;
@property() private hour?: Hour;
@property() private minute?: Minute;
private get entity(): HassEntity | undefined {
if (!this.config) {
return;
}
return this.hass?.states[this.config!.entity];
}
render(): TemplateResult | null {
if (!this.config || !this.hass) {
return null;
}
return html`<ha-card>TimePickerCard</ha-card>`;
if (!this.entity?.entity_id.startsWith('input_datetime')) {
return Partial.error('You must set an input_datetime entity', this.config);
}
if (!this.entity.attributes.has_time) {
return Partial.error(
'You must set an input_datetime entity that sets has_time: true',
this.config
);
}
this.hour = new Hour(this.entity?.attributes.hour ?? 0, this.config.hour_step);
this.minute = new Minute(this.entity?.attributes.minute ?? 0, this.config.minute_step);
return html`
<ha-card class="time-picker-ha-card">
<div class="time-form">
${this.renderStepChanger(Direction.UP, this.hour)}
<input
class="time-input"
type="number"
placeholder="HH"
min="0"
max="24"
.value=${this.hour.toString()}
@change=${this.onHourChange}
/>
${this.renderStepChanger(Direction.DOWN, this.hour)}
</div>
<div class="time-separator">
:
</div>
<div class="time-form">
${this.renderStepChanger(Direction.UP, this.minute)}
<input
class="time-input"
type="number"
placeholder="MM"
min="0"
max="60"
.value="${this.minute.toString()}"
@change=${this.onMinuteChange}
/>
${this.renderStepChanger(Direction.DOWN, this.minute)}
</div>
</ha-card>
`;
}
setConfig(config): void {
@@ -29,7 +88,7 @@ export class TimePickerCard extends LitElement {
}
if (!config.entity) {
throw new Error('You need to set an entity');
throw new Error('You must set an entity');
}
this.config = config;
@@ -39,6 +98,45 @@ export class TimePickerCard extends LitElement {
return 3;
}
onHourChange({ target: { value } }: { target: HTMLInputElement }): void {
this.hour!.setStringValue(value);
this.callService();
}
onMinuteChange({ target: { value } }: { target: HTMLInputElement }): void {
this.minute!.setStringValue(value);
this.callService();
}
private renderStepChanger(direction: Direction, unit: TimeUnit): TemplateResult {
const onIconClick = (): void => {
unit.stepUpdate(direction);
this.callService();
};
const className = `time-picker-icon-${direction}`;
return html`
<div class=${className} @click=${onIconClick}>
<ha-icon .icon="mdi:arrow-${direction}"></ha-icon>
<mwc-ripple id="ripple"></mwc-ripple>
</div>
`;
}
private callService(): Promise<void> {
if (!this.hass) {
throw new Error('Unable to update datetime');
}
const time = `${this.hour!.value}:${this.minute!.value}:00`;
return this.hass.callService('input_datetime', 'set_datetime', {
entity_id: this.entity?.entity_id,
time,
});
}
static get styles(): CSSResult {
return styles;
}
+7
View File
@@ -1,3 +1,10 @@
export interface TimePickerCardConfig {
entity: string;
hour_step?: number;
minute_step?: number;
}
export enum Direction {
UP = 'up',
DOWN = 'down',
}