Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2791d8109c | ||
|
|
14cc1a2c10 | ||
|
|
76a52f0c9f | ||
|
|
ce22ceb808 | ||
|
|
45d3364959 | ||
|
|
6ac3d62823 |
@@ -15,11 +15,21 @@ Requires an [Input Datetime](https://www.home-assistant.io/integrations/input_da
|
||||
|
||||
## Screenshots
|
||||
|
||||
### Default config - card name shown, 24 hour mode
|
||||
|
||||

|
||||
|
||||
### Custom config - hidden card name, 12 hour mode
|
||||
|
||||

|
||||
|
||||

|
||||
### Custom config - hidden card name, 12 hour mode with a "single" hour mode picker
|
||||
|
||||

|
||||
|
||||
### Using a custom theme
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
@@ -54,8 +64,15 @@ hide:
|
||||
| hour_mode | `12` or `24` | **Optional** | Hour format. If `12`, card will show AM/PM picker | `24` |
|
||||
| hour_step | number | **Optional** | Hour change when clicking arrows | `1` |
|
||||
| minute_step | number | **Optional** | Minute change when clicking arrows | `5` |
|
||||
| layout | object | **Optional** | Card Layout configuration | `none` |
|
||||
| hide | object | **Optional** | Hide object | `none` |
|
||||
|
||||
## Layout Object
|
||||
|
||||
| Name | Type | Requirement | Description | Default |
|
||||
| --------- | -------------------- | ------------ | -------------------------------------------------------------------------------------------------- | -------- |
|
||||
| hour_mode | `single` or `double` | **Optional** | Whether to show both AM/PM or just the current mode. In `single` mode, tap the value to change it. | `double` |
|
||||
|
||||
## Hide Object
|
||||
|
||||
| Name | Type | Requirement | Description | Default |
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "time-picker-card",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "time-picker-card",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"author": "Georgi Gardev <georgi@gardev.com>",
|
||||
"description": "Lovelace time-picker-card",
|
||||
"keywords": [
|
||||
|
||||
@@ -8,13 +8,18 @@ import {
|
||||
TemplateResult,
|
||||
} from 'lit-element';
|
||||
import { ClassInfo, classMap } from 'lit-html/directives/class-map';
|
||||
import { Period } from '../types';
|
||||
import { Period, HourModeLayout } from '../types';
|
||||
|
||||
/**
|
||||
* Renders a Time Period selector with an input for a value and two arrows for step-chaning
|
||||
* the value up and down.
|
||||
*/
|
||||
@customElement('time-period')
|
||||
export class TimePeriodComponent extends LitElement {
|
||||
static readonly EVENT_TOGGLE = 'toggle';
|
||||
|
||||
@property() private period!: Period;
|
||||
@property() private mode!: HourModeLayout;
|
||||
|
||||
private get amClass(): ClassInfo {
|
||||
return { 'time-period': true, active: this.period === Period.AM };
|
||||
@@ -26,12 +31,7 @@ export class TimePeriodComponent extends LitElement {
|
||||
|
||||
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>
|
||||
${this.mode === 'single' ? this.renderSingle() : this.renderDouble()}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,21 @@ export class TimePeriodComponent extends LitElement {
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
private renderSingle(): TemplateResult {
|
||||
return html`<div class="time-period" @click=${this.onTimePeriodChange}>
|
||||
${this.period}<mwc-ripple></mwc-ripple>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private renderDouble(): TemplateResult {
|
||||
return html`<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>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.time-period-selector {
|
||||
|
||||
@@ -10,6 +10,9 @@ import {
|
||||
import { TimeUnit } from '../models/time-unit';
|
||||
import { Direction } from '../types';
|
||||
|
||||
/**
|
||||
* Renders an AM/PM picker.
|
||||
*/
|
||||
@customElement('time-unit')
|
||||
export class TimeUnitComponent extends LitElement {
|
||||
static readonly EVENT_UPDATE = 'update';
|
||||
|
||||
@@ -2,3 +2,10 @@ import * as pkg from '../package.json';
|
||||
|
||||
export const CARD_VERSION = pkg.version;
|
||||
export const CARD_SIZE = 3;
|
||||
|
||||
export const ENTITY_DOMAIN = 'input_datetime';
|
||||
|
||||
// Config defaults
|
||||
export const DEFAULT_HOUR_STEP = 1;
|
||||
export const DEFAULT_MINUTE_STEP = 5;
|
||||
export const DEFAULT_LAYOUT_HOUR_MODE = 'double';
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { TimeUnit } from './time-unit';
|
||||
import { DEFAULT_HOUR_STEP } from '../const';
|
||||
import { HourMode } from '../types';
|
||||
import { TimeUnit } from './time-unit';
|
||||
|
||||
/**
|
||||
* Represents the hour value of a datetime.
|
||||
*/
|
||||
export class Hour extends TimeUnit {
|
||||
private static readonly DEFAULT_STEP = 1;
|
||||
private static readonly MAX = 24;
|
||||
private static readonly VALUE_LIMIT = 24;
|
||||
|
||||
constructor(value: number, step = Hour.DEFAULT_STEP, private hourMode: HourMode) {
|
||||
super(value, step, Hour.MAX);
|
||||
constructor(value: number, step = DEFAULT_HOUR_STEP, private hourMode: HourMode) {
|
||||
super(value, step, Hour.VALUE_LIMIT);
|
||||
}
|
||||
|
||||
get maxValue(): number {
|
||||
return this.hourMode || Hour.MAX;
|
||||
return this.hourMode || Hour.VALUE_LIMIT;
|
||||
}
|
||||
|
||||
togglePeriod(): void {
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { DEFAULT_MINUTE_STEP } from '../const';
|
||||
import { TimeUnit } from './time-unit';
|
||||
|
||||
/**
|
||||
* * Represents the minute value of a datetime.
|
||||
*/
|
||||
export class Minute extends TimeUnit {
|
||||
private static readonly DEFAULT_STEP = 5;
|
||||
private static readonly MAX = 60;
|
||||
private static readonly VALUE_LIMIT = 60;
|
||||
|
||||
maxValue = Minute.MAX;
|
||||
maxValue = Minute.VALUE_LIMIT;
|
||||
|
||||
constructor(value: number, step = Minute.DEFAULT_STEP) {
|
||||
super(value, step, Minute.MAX);
|
||||
constructor(value: number, step = DEFAULT_MINUTE_STEP) {
|
||||
super(value, step, Minute.VALUE_LIMIT);
|
||||
}
|
||||
|
||||
protected isValidString(valueStr: string): boolean {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Direction } from '../types';
|
||||
|
||||
/**
|
||||
* Represents a single unit of a datetime oobject - e.g. hours or minutes.
|
||||
*/
|
||||
export abstract class TimeUnit {
|
||||
/**
|
||||
* Return true if the valueStr can be set as a value of this instance.
|
||||
* Whether {@param valueStr} can be set as a value of this instance.
|
||||
*/
|
||||
protected abstract isValidString(valueStr: string): boolean;
|
||||
|
||||
@@ -23,28 +26,36 @@ export abstract class TimeUnit {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value from {@param stringValue}, if it can be parsed and is valid.
|
||||
* @param stringValue
|
||||
*/
|
||||
setStringValue(stringValue: string): void {
|
||||
if (this.isValidString(stringValue)) {
|
||||
this.setValue(parseInt(stringValue));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the value in {@param direction} by the step size specified in the constructoor.
|
||||
* @param direction
|
||||
*/
|
||||
stepUpdate(direction: Direction): void {
|
||||
direction === Direction.UP ? this.increment() : this.decrement();
|
||||
}
|
||||
|
||||
increment(): void {
|
||||
toString(): string {
|
||||
return this.value < 10 ? `0${this.value}` : this.value.toString();
|
||||
}
|
||||
|
||||
private increment(): void {
|
||||
this.setValue(this.value + this._step);
|
||||
}
|
||||
|
||||
decrement(): void {
|
||||
private decrement(): void {
|
||||
this.setValue(this.value - this._step);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.value < 10 ? `0${this.value}` : this.value.toString();
|
||||
}
|
||||
|
||||
protected setValue(newValue: number): void {
|
||||
if (newValue >= this._limit || newValue < 0) {
|
||||
newValue = (newValue + this._limit) % this._limit;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HomeAssistant } from 'custom-card-helpers';
|
||||
import { computeDomain, HomeAssistant } from 'custom-card-helpers';
|
||||
import { HassEntity } from 'home-assistant-js-websocket';
|
||||
import {
|
||||
css,
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from 'lit-element';
|
||||
import './components/time-period.component';
|
||||
import './components/time-unit.component';
|
||||
import { CARD_SIZE, CARD_VERSION } from './const';
|
||||
import { CARD_SIZE, CARD_VERSION, ENTITY_DOMAIN, DEFAULT_LAYOUT_HOUR_MODE } from './const';
|
||||
import { Hour } from './models/hour';
|
||||
import { Minute } from './models/minute';
|
||||
import { Partial } from './partials';
|
||||
@@ -52,13 +52,13 @@ export class TimePickerCard extends LitElement {
|
||||
return Partial.error('Entity not found', this.config);
|
||||
}
|
||||
|
||||
if (!this.entity.entity_id.startsWith('input_datetime')) {
|
||||
return Partial.error('You must set an input_datetime entity', this.config);
|
||||
if (computeDomain(this.entity.entity_id) !== ENTITY_DOMAIN) {
|
||||
return Partial.error(`You must set an ${ENTITY_DOMAIN} entity`, this.config);
|
||||
}
|
||||
|
||||
if (!this.entity.attributes.has_time) {
|
||||
return Partial.error(
|
||||
'You must set an input_datetime entity that sets has_time: true',
|
||||
`You must set an ${ENTITY_DOMAIN} entity that sets has_time: true`,
|
||||
this.config
|
||||
);
|
||||
}
|
||||
@@ -79,6 +79,7 @@ export class TimePickerCard extends LitElement {
|
||||
${this.shouldShowPeriod
|
||||
? html`<time-period
|
||||
.period=${this.period}
|
||||
.mode=${this.config.layout?.hour_mode ?? DEFAULT_LAYOUT_HOUR_MODE}
|
||||
@toggle=${this.onPeriodToggle}
|
||||
></time-period>`
|
||||
: ''}
|
||||
@@ -119,7 +120,7 @@ export class TimePickerCard extends LitElement {
|
||||
|
||||
const time = `${this.hour.value}:${this.minute.value}:00`;
|
||||
|
||||
return this.hass.callService('input_datetime', 'set_datetime', {
|
||||
return this.hass.callService(ENTITY_DOMAIN, 'set_datetime', {
|
||||
entity_id: this.entity!.entity_id,
|
||||
time,
|
||||
});
|
||||
|
||||
@@ -6,9 +6,18 @@ export interface TimePickerCardConfig extends LovelaceCardConfig {
|
||||
hour_mode?: HourMode;
|
||||
hour_step?: number;
|
||||
minute_step?: number;
|
||||
layout?: TimePickerLayoutConfig;
|
||||
hide?: TimePickerHideConfig;
|
||||
}
|
||||
|
||||
export type HourMode = 12 | 24 | undefined;
|
||||
|
||||
export interface TimePickerLayoutConfig {
|
||||
hour_mode: HourModeLayout;
|
||||
}
|
||||
|
||||
export type HourModeLayout = 'single' | 'double';
|
||||
|
||||
export interface TimePickerHideConfig {
|
||||
name: boolean;
|
||||
}
|
||||
@@ -19,8 +28,6 @@ export enum Direction {
|
||||
}
|
||||
|
||||
export enum Period {
|
||||
AM = 'am',
|
||||
PM = 'pm',
|
||||
AM = 'AM',
|
||||
PM = 'PM',
|
||||
}
|
||||
|
||||
export type HourMode = 12 | 24 | undefined;
|
||||
|
||||