diff --git a/README.md b/README.md index a822999..75b727b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## Overview -This is a Time Picker card for [Home Assistant](https://www.home-assistant.io/)'s [Lovelace UI](https://www.home-assistant.io/lovelace). +This is a Time Picker Card for [Home Assistant](https://www.home-assistant.io/)'s [Lovelace UI](https://www.home-assistant.io/lovelace). Requires an [Input Datetime](https://www.home-assistant.io/integrations/input_datetime/) that has time (`has_time: true`). @@ -29,7 +29,7 @@ Requires an [Input Datetime](https://www.home-assistant.io/integrations/input_da ### Using a custom theme -![Cudtom theme](examples/custom.png) +![Custom theme](examples/custom.png) ## Installation @@ -43,6 +43,15 @@ resources: ## Usage +### Visual Editor + +Time Picker Card supports Lovelace's Visual Editor. Click the + button to add a card and search for time picker. + +![Visual Editor](examples/visual_editor.png) + + +### YAML + ```yaml type: 'custom:time-picker-card' entity: input_datetime.alarm_time @@ -81,7 +90,7 @@ hide: ## Theme Variables -Time Picker card will automatically pick up colors from your lovelace theme, but if you want to customize some of them, +Time Picker Card will automatically pick up colors from your lovelace theme, but if you want to customize some of them, you can use the following variables in your theme's config file: | Name | Default | Description | diff --git a/examples/visual_editor.png b/examples/visual_editor.png new file mode 100644 index 0000000..8108b92 Binary files /dev/null and b/examples/visual_editor.png differ diff --git a/src/editor.ts b/src/editor.ts new file mode 100644 index 0000000..6ce378d --- /dev/null +++ b/src/editor.ts @@ -0,0 +1,185 @@ +import { computeDomain, HomeAssistant, LovelaceCardConfig } from 'custom-card-helpers'; +import { HassEntity } from 'home-assistant-js-websocket'; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from 'lit-element'; +import { DEFAULT_HOUR_STEP, DEFAULT_MINUTE_STEP, ENTITY_DOMAIN } from './const'; +import { HourMode, HourModeLayout, TimePickerCardConfig } from './types'; + +// TODO: Remove this once there's a new release of custom-card-helpers that exports it. +export interface LovelaceCardEditor extends HTMLElement { + hass?: HomeAssistant; + lovelace?: unknown; + setConfig(config: LovelaceCardConfig): void; +} + +@customElement('time-picker-card-editor') +export class TimePickerCardEditor extends LitElement implements LovelaceCardEditor { + private static readonly NUMBER_PROPERTIES = ['hour_step', 'minute_step', 'hour_mode']; + private static readonly CONFIG_CHANGED_EVENT = 'config-changed'; + + @property({ type: Object }) hass!: HomeAssistant; + @property() private config!: TimePickerCardConfig; + + private get entity(): HassEntity | undefined { + return this.hass.states[this.config.entity]; + } + + private get datetimeEntities(): Array { + // Find all input_datetime entities + const datetimeEntities = Object.keys(this.hass.states) + .filter((entityId) => computeDomain(entityId) === ENTITY_DOMAIN) + .map((entityId) => this.hass.states[entityId]); + + // Return only the datetime entities that have time. + return datetimeEntities + .filter((entity) => entity.attributes.has_time === true) + .map((entity) => entity.entity_id); + } + + render(): TemplateResult { + return html`
+ + + ${this.datetimeEntities.map((entity) => html`${entity}`)} + + +
+ + + Show name? + +
+
+ + +
+
+ + 12-Hour mode + + + "Single" hour mode layout (in 12-Hour mode only) + +
+
`; + } + + setConfig(config): void { + this.config = config; + } + + private onHourModeChange({ target: { checked } }): void { + const hourMode: HourMode = checked ? 12 : 24; + + const newConfig = { ...this.config, hour_mode: hourMode }; + this.dispatch(newConfig); + } + + private onHourModeLayoutChange({ target: { checked } }): void { + const hourModeLayout: HourModeLayout = checked ? 'single' : 'double'; + + const newConfig = { ...this.config, layout: { hour_mode: hourModeLayout } }; + this.dispatch(newConfig); + } + + private onHideNameChange({ target: { checked } }): void { + const newConfig = { ...this.config, hide: { name: !checked } }; + this.dispatch(newConfig); + } + + private onValueChange(e: CustomEvent): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const property = (e.target as any).configValue; + let value = e.detail.value; + + if (TimePickerCardEditor.NUMBER_PROPERTIES.indexOf(property) > -1) { + value = parseInt(value); + + if (isNaN(value)) { + return; + } + } + + if (this.config[property] === value) { + return; + } + + const newConfig = { ...this.config, [property]: value }; + + this.dispatch(newConfig); + } + + private dispatch(config: TimePickerCardConfig): void { + const event = new CustomEvent(TimePickerCardEditor.CONFIG_CHANGED_EVENT, { + bubbles: true, + composed: true, + detail: { config }, + }); + + this.dispatchEvent(event); + } + + static get styles(): CSSResult { + return css` + ha-switch { + padding: 16px 0; + } + .side-by-side { + display: flex; + } + .side-by-side > * { + flex: 1; + padding-right: 4px; + } + .suffix { + margin: 0 8px; + } + `; + } +} diff --git a/src/time-picker-card.ts b/src/time-picker-card.ts index bfea9ca..60e9ee3 100644 --- a/src/time-picker-card.ts +++ b/src/time-picker-card.ts @@ -1,4 +1,4 @@ -import { computeDomain, HomeAssistant } from 'custom-card-helpers'; +import { computeDomain, HomeAssistant, LovelaceCard } from 'custom-card-helpers'; import { HassEntity } from 'home-assistant-js-websocket'; import { css, @@ -11,7 +11,8 @@ import { } from 'lit-element'; import './components/time-period.component'; import './components/time-unit.component'; -import { CARD_SIZE, CARD_VERSION, ENTITY_DOMAIN, DEFAULT_LAYOUT_HOUR_MODE } from './const'; +import './editor'; +import { CARD_SIZE, CARD_VERSION, DEFAULT_LAYOUT_HOUR_MODE, ENTITY_DOMAIN } from './const'; import { Hour } from './models/hour'; import { Minute } from './models/minute'; import { Partial } from './partials'; @@ -23,9 +24,18 @@ console.info( 'color: white; font-weight: bold; background: dimgray' ); +/* eslint-disable @typescript-eslint/no-explicit-any */ +(window as any).customCards = (window as any).customCards || []; +(window as any).customCards.push({ + type: 'time-picker-card', + name: 'Time Picker Card', + description: 'A Time Picker card for setting the time value of Input Datetime entities.', +}); +/* eslint-enable @typescript-eslint/no-explicit-any */ + @customElement('time-picker-card') -export class TimePickerCard extends LitElement { - @property() private hass!: HomeAssistant; +export class TimePickerCard extends LitElement implements LovelaceCard { + @property({ type: Object }) hass!: HomeAssistant; @property() private config!: TimePickerCardConfig; @property() private hour!: Hour; @property() private minute!: Minute; @@ -157,4 +167,22 @@ export class TimePickerCard extends LitElement { } `; } + + static getStubConfig( + _: HomeAssistant, + entities: Array + ): Omit { + const datetimeEntity = entities.find((entityId) => computeDomain(entityId) === ENTITY_DOMAIN); + + return { + entity: datetimeEntity || 'input_datetime.example_entity', + hour_mode: 24, + hour_step: 1, + minute_step: 5, + }; + } + + static getConfigElement(): LovelaceCard { + return document.createElement('time-picker-card-editor') as LovelaceCard; + } }