Compare commits

...
12 Commits
Author SHA1 Message Date
GeorgeSG 588cead35d 0.1.0 2020-05-09 20:39:14 +03:00
GeorgeSG 04462e096c Update README 2020-05-09 20:38:29 +03:00
GeorgeSG 03312aa5f7 Add new screenshot and reorganize README. Add code examples 2020-05-09 20:37:23 +03:00
GeorgeSG 4488b303d2 Add name position option. Refactor types 2020-05-09 20:24:37 +03:00
GeorgeSG 491e571aab Add layout.align option 2020-05-09 19:39:36 +03:00
GeorgeSG ae7dfb7668 Add test step in ci 2020-05-09 16:20:11 +03:00
GeorgeSG cc22b482b4 Add some tests 2020-05-09 16:17:53 +03:00
GeorgeSG 812e7f3016 Bump custom-card-helpers. Refactor types 2020-05-09 13:40:26 +03:00
GeorgeSG 20cba88ef2 Update README - add full URLs to images 2020-05-09 13:17:44 +03:00
GeorgeSG 5b6c9d0af0 Update README - add downloads shield 2020-05-09 12:58:27 +03:00
GeorgeSG 993fda7d34 Update README - HACS default 2020-05-09 12:50:11 +03:00
GeorgeSG df9de8bd00 Refactor editor datetimeEntities 2020-05-08 00:58:58 +03:00
17 changed files with 2656 additions and 139 deletions
+3 -2
View File
@@ -13,7 +13,8 @@
"rules": {
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/explicit-function-return-type": [1, { "allowExpressions": true }]
"@typescript-eslint/explicit-function-return-type": [1, { "allowExpressions": true }],
"@typescript-eslint/no-namespace": 0
},
"ignorePatterns": ["dist/", "node_modules/"]
"ignorePatterns": ["dist/", "node_modules/", "*.js"]
}
+2
View File
@@ -18,3 +18,5 @@ jobs:
npm install
npm run lint
npm run build
- name: Test
run: npm run test
+86 -43
View File
@@ -1,11 +1,12 @@
# Time Picker Card by [@georgesg](https://www.github.com/georgesg)
# Time Picker Card
[![hacs_badge][hacs-shield]][hacs]
[![GitHub Release][releases-shield]][releases]
![CI](https://github.com/GeorgeSG/lovelace-time-picker-card/workflows/CI/badge.svg)
[![Project Maintenance][maintenance-shield]][maintenance]
[![GitHub Activity][commits-shield]][commits]
[![License][license-shield]][license]
[![HACS][hacs-shield]][hacs-link]
[![Downloads][downloads-shield]][downloads-link]
[![GitHub Release][releases-shield]][releases-link]
[![CI][ci-shield]][ci-link]
[![Project Maintenance][maintenance-shield]][maintenance-link]
[![GitHub Activity][commits-shield]][commits-link]
[![License][license-shield]][license-link]
## Overview
@@ -13,24 +14,6 @@ This is a Time Picker Card for [Home Assistant](https://www.home-assistant.io/)'
Requires an [Input Datetime](https://www.home-assistant.io/integrations/input_datetime/) that has time (`has_time: true`).
## Screenshots
### Default config - card name shown, 24 hour mode
![Default theme with card name](examples/default_with_name.png)
### Custom config - hidden card name, 12 hour mode
![Default theme with no card name](examples/default_without_name.png)
### Custom config - hidden card name, 12 hour mode with a "single" hour mode picker
![Default theme with single hour mode](examples/single_hour_mode.png)
### Using a custom theme
![Custom theme](examples/custom.png)
## Installation
Install using [HACS](https://hacs.xyz) or follow this [guide](https://github.com/thomasloven/hass-config/wiki/Lovelace-Plugins)
@@ -47,8 +30,7 @@ resources:
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)
![Visual Editor](https://raw.githubusercontent.com/GeorgeSG/lovelace-time-picker-card/master/examples/visual_editor.png)
### YAML
@@ -63,6 +45,59 @@ hide:
name: false
```
## Examples
### Default config - card name shown, 24 hour mode
![Default theme with card name](https://raw.githubusercontent.com/GeorgeSG/lovelace-time-picker-card/master/examples/default_with_name.png)
```yaml
type: 'custom:time-picker-card'
entity: input_datetime.alarm_time
```
### Custom config - hidden card name, 12 hour mode
![Default theme with no card name](https://raw.githubusercontent.com/GeorgeSG/lovelace-time-picker-card/master/examples/default_without_name.png)
```yaml
type: 'custom:time-picker-card'
entity: input_datetime.alarm_time
hour_mode: 12
hide:
name: true
```
### Custom config - hidden card name, 12 hour mode with a "single" hour mode picker
![Default theme with single hour mode](https://raw.githubusercontent.com/GeorgeSG/lovelace-time-picker-card/master/examples/single_hour_mode.png)
```yaml
type: 'custom:time-picker-card'
entity: input_datetime.alarm_time
hour_mode: 12
layout:
hour_mode: single
hide:
name: true
```
### Custom config - card name inside card and controls aligned right
![Default theme with single hour mode](https://raw.githubusercontent.com/GeorgeSG/lovelace-time-picker-card/master/examples/name_inside.png)
```yaml
type: 'custom:time-picker-card'
entity: input_datetime.alarm_time
layout:
name: inside
align_controls: right
```
### With a custom lovelace theme
![Custom theme](https://raw.githubusercontent.com/GeorgeSG/lovelace-time-picker-card/master/examples/custom.png)
## Options
| Name | Type | Requirement | Description | Default |
@@ -78,9 +113,11 @@ hide:
## 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` |
| Name | Value | Requirement | Description | Default |
| -------------- | ------------------------- | ------------ | -------------------------------------------------------------------------------------------------- | -------- |
| hour_mode | `single`, `double` | **Optional** | Whether to show both AM/PM or just the current mode. In `single` mode, tap the value to change it. | `double` |
| align_controls | `left`, `center`, `right` | **Optional** | Horizontal alignment of the controls | `center` |
| name | `header`, `inside` | **Optional** | Whether to show the name as a header or inside the card | `header` |
## Hide Object
@@ -106,18 +143,24 @@ you can use the following variables in your theme's config file:
**Georgi Gardev**
- [gar.dev](https://gar.dev)
- [![GitHub][github-icon]](https://github.com/GeorgeSG/) [GeorgeSG](https://github.com/GeorgeSG/)
- [![Twitter][twitter-icon]](https://twitter.com/georgesg92) [@georgesg92](https://twitter.com/georgesg92)
- [![GitHub][github-icon]][github-link] [GeorgeSG][github-link]
- [![Twitter][twitter-icon]][twitter-link] [@georgesg92][twitter-link]
[commits-shield]: https://img.shields.io/github/commit-activity/y/GeorgeSG/lovelace-time-picker-card?style=flat-square
[commits]: https://github.com/GeorgeSG/lovelace-time-picker-card/commits/master
[license-shield]: https://img.shields.io/github/license/GeorgeSG/lovelace-time-picker-card?style=flat-square
[license]: https://github.com/GeorgeSG/lovelace-time-picker-card/blob/master/LICENSE
[maintenance-shield]: https://img.shields.io/maintenance/yes/2020.svg?style=flat-square
[maintenance]: https://github.com/GeorgeSG/lovelace-time-picker-card
[releases-shield]: https://img.shields.io/github/release/GeorgeSG/lovelace-time-picker-card.svg?style=flat-square
[releases]: https://github.com/GeorgeSG/lovelace-time-picker-card/releases
[hacs-shield]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=flat-square
[hacs]: https://github.com/custom-components/hacs
[twitter-icon]: http://i.imgur.com/wWzX9uB.png
[hacs-shield]: https://img.shields.io/badge/HACS-Default-brightgreen.svg
[hacs-link]: https://github.com/custom-components/hacs
[downloads-shield]: https://img.shields.io/github/downloads/GeorgeSG/lovelace-time-picker-card/latest/total?color=brightgreen&logo=github
[downloads-link]: https://github.com/GeorgeSG/lovelace-time-picker-card/releases
[releases-shield]: https://img.shields.io/github/release/GeorgeSG/lovelace-time-picker-card.svg
[releases-link]: https://github.com/GeorgeSG/lovelace-time-picker-card/releases
[ci-shield]: https://img.shields.io/github/workflow/status/GeorgeSG/lovelace-time-picker-card/CI?label=CI&logo=github&
[ci-link]: https://github.com/GeorgeSG/lovelace-time-picker-card/actions?query=workflow%3ACI
[maintenance-shield]: https://img.shields.io/maintenance/yes/2020.svg
[maintenance-link]: https://github.com/GeorgeSG/lovelace-time-picker-card
[commits-shield]: https://img.shields.io/github/commit-activity/y/GeorgeSG/lovelace-time-picker-card
[commits-link]: https://github.com/GeorgeSG/lovelace-time-picker-card/commits/master
[license-shield]: https://img.shields.io/github/license/GeorgeSG/lovelace-time-picker-card?color=brightgreen
[license-link]: https://github.com/GeorgeSG/lovelace-time-picker-card/blob/master/LICENSE
[github-icon]: http://i.imgur.com/9I6NRUm.png
[github-link]: https://github.com/GeorgeSG/
[twitter-icon]: http://i.imgur.com/wWzX9uB.png
[twitter-link]: https://twitter.com/georgesg92
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

+37
View File
@@ -0,0 +1,37 @@
module.exports = function (config) {
config.set({
basePath: 'test/',
frameworks: ['mocha', 'chai'],
files: ['*.test.ts'],
preprocessors: {
'*.test.ts': ['rollup'],
},
rollupPreprocessor: {
plugins: [
require('@rollup/plugin-node-resolve')(),
require('@rollup/plugin-json')(),
require('@rollup/plugin-typescript')(),
require('rollup-plugin-babel')(),
],
output: {
format: 'umd',
sourcemap: 'inline',
},
},
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
port: 9876,
colors: true,
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
autoWatch: false,
singleRun: true,
browsers: ['ChromeHeadless'],
concurrency: Infinity,
});
};
+2180 -27
View File
File diff suppressed because it is too large Load Diff
+12 -2
View File
@@ -1,6 +1,6 @@
{
"name": "time-picker-card",
"version": "0.0.5",
"version": "0.1.0",
"author": "Georgi Gardev <georgi@gardev.com>",
"description": "Lovelace time-picker-card",
"keywords": [
@@ -19,31 +19,41 @@
"build": "npm run lint && npm run rollup",
"lint": "eslint src/*.ts",
"rollup": "rollup -c",
"test": "./node_modules/karma/bin/karma start",
"release-patch": "npm version patch && git push origin master --tags",
"release-minor": "npm version minor && git push origin master --tags",
"release-major": "npm version major && git push origin master --tags"
},
"dependencies": {
"custom-card-helpers": "^1.5.0",
"custom-card-helpers": "^1.6.4",
"home-assistant-js-websocket": "^5.1.0",
"lit-element": "^2.3.1",
"lit-html": "^1.2.1"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@open-wc/testing": "^2.5.16",
"@rollup/plugin-json": "^4.0.3",
"@rollup/plugin-node-resolve": "^7.1.1",
"@rollup/plugin-typescript": "^4.0.0",
"@types/chai": "^4.2.11",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"karma": "^5.0.5",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.1",
"karma-rollup-preprocessor": "^7.0.5",
"mocha": "^7.1.2",
"prettier": "^2.0.5",
"rollup": "^1.32.1",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-serve": "^1.0.1",
"rollup-plugin-terser": "^5.3.0",
"sinon": "^9.0.2",
"typescript": "^3.8.3"
}
}
+2 -2
View File
@@ -8,7 +8,7 @@ import {
TemplateResult,
} from 'lit-element';
import { ClassInfo, classMap } from 'lit-html/directives/class-map';
import { Period, HourModeLayout } from '../types';
import { Layout, Period } from '../types';
/**
* Renders a Time Period selector with an input for a value and two arrows for step-chaning
@@ -19,7 +19,7 @@ export class TimePeriodComponent extends LitElement {
static readonly EVENT_TOGGLE = 'toggle';
@property() private period!: Period;
@property() private mode!: HourModeLayout;
@property() private mode!: Layout.HourMode;
private get amClass(): ClassInfo {
return { 'time-period': true, active: this.period === Period.AM };
+3
View File
@@ -1,4 +1,5 @@
import * as pkg from '../package.json';
import { Layout } from './types';
export const CARD_VERSION = pkg.version;
export const CARD_SIZE = 3;
@@ -9,3 +10,5 @@ export const ENTITY_DOMAIN = 'input_datetime';
export const DEFAULT_HOUR_STEP = 1;
export const DEFAULT_MINUTE_STEP = 5;
export const DEFAULT_LAYOUT_HOUR_MODE = 'double';
export const DEFAULT_LAYOUT_ALIGN_CONTROLS = Layout.AlignControls.CENTER;
export const DEFAULT_LAYOUT_NAME = Layout.Name.HEADER;
+72 -32
View File
@@ -1,4 +1,4 @@
import { computeDomain, HomeAssistant, LovelaceCardConfig } from 'custom-card-helpers';
import { computeDomain, HomeAssistant, LovelaceCardEditor } from 'custom-card-helpers';
import { HassEntity } from 'home-assistant-js-websocket';
import {
css,
@@ -9,15 +9,14 @@ import {
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;
}
import {
DEFAULT_HOUR_STEP,
DEFAULT_MINUTE_STEP,
ENTITY_DOMAIN,
DEFAULT_LAYOUT_ALIGN_CONTROLS,
DEFAULT_LAYOUT_NAME,
} from './const';
import { TimePickerCardConfig, Layout, HourMode } from './types';
@customElement('time-picker-card-editor')
export class TimePickerCardEditor extends LitElement implements LovelaceCardEditor {
@@ -32,15 +31,11 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
}
private get datetimeEntities(): Array<string> {
// 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);
return Object.keys(this.hass.states).filter(
(entityId) =>
computeDomain(entityId) === ENTITY_DOMAIN &&
this.hass.states[entityId].attributes.has_time === true
);
}
render(): TemplateResult {
@@ -58,14 +53,14 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
${this.datetimeEntities.map((entity) => html`<paper-item>${entity}</paper-item>`)}
</paper-listbox>
</paper-dropdown-menu>
<paper-input
label="Name (Optional)"
.configValue=${'name'}
.value=${this.config.name}
.placeholder=${this.entity?.attributes.friendly_name}
@value-changed=${this.onValueChange}
></paper-input>
<div class="side-by-side">
<paper-input
label="Name (Optional)"
.configValue=${'name'}
.value=${this.config.name}
.placeholder=${this.entity?.attributes.friendly_name}
@value-changed=${this.onValueChange}
></paper-input>
<ha-switch
style="margin-left: 10px"
.checked="${!Boolean(this.config.hide?.name)}"
@@ -74,6 +69,20 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
>
Show name?
</ha-switch>
<paper-dropdown-menu
style="width: 100%"
label="Name Position (Optional)"
@value-changed=${this.onLayoutNameChange}
>
<paper-listbox
slot="dropdown-content"
.selected=${Object.values(Layout.Name).indexOf(
this.config.layout?.name ?? DEFAULT_LAYOUT_NAME
)}
>
${Object.values(Layout.Name).map((name) => html`<paper-item>${name}</paper-item>`)}
</paper-listbox>
</paper-dropdown-menu>
</div>
<div class="side-by-side">
<paper-input
@@ -107,6 +116,22 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
"Single" hour mode layout (in 12-Hour mode only)
</ha-switch>
</div>
<div class="side-by-side">
<paper-dropdown-menu
style="width: 100%"
label="Align Controls (Optional)"
@value-changed=${this.onLayoutAlignChange}
>
<paper-listbox
slot="dropdown-content"
.selected=${Object.values(Layout.AlignControls).indexOf(
this.config.layout?.align_controls ?? DEFAULT_LAYOUT_ALIGN_CONTROLS
)}
>
${Object.values(Layout.AlignControls).map((a) => html`<paper-item>${a}</paper-item>`)}
</paper-listbox>
</paper-dropdown-menu>
</div>
</div>`;
}
@@ -121,15 +146,30 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
this.dispatch(newConfig);
}
private onHourModeLayoutChange({ target: { checked } }): void {
const hourModeLayout: HourModeLayout = checked ? 'single' : 'double';
const newConfig = { ...this.config, layout: { hour_mode: hourModeLayout } };
private onHideNameChange({ target: { checked } }): void {
const newConfig = { ...this.config, hide: { name: !checked } };
this.dispatch(newConfig);
}
private onHideNameChange({ target: { checked } }): void {
const newConfig = { ...this.config, hide: { name: !checked } };
private onLayoutAlignChange({ detail: { value } }: CustomEvent): void {
const layout = { ...this.config.layout, align_controls: value };
const newConfig = { ...this.config, layout };
this.dispatch(newConfig);
}
private onLayoutNameChange({ detail: { value } }: CustomEvent): void {
const layout = { ...this.config.layout, name: value };
const newConfig = { ...this.config, layout };
this.dispatch(newConfig);
}
private onHourModeLayoutChange({ target: { checked } }): void {
const hourModeLayout: Layout.HourMode = checked ? 'single' : 'double';
const newConfig = {
...this.config,
layout: { ...this.config.layout, hour_mode: hourModeLayout },
};
this.dispatch(newConfig);
}
+8 -2
View File
@@ -1,6 +1,7 @@
import { html, TemplateResult } from 'lit-element';
import { TimePickerCardConfig } from './types';
import { LovelaceCard } from 'custom-card-helpers';
import { HassEntity } from 'home-assistant-js-websocket';
export class Partial {
static error(error: string, origConfig: TimePickerCardConfig): TemplateResult {
@@ -14,11 +15,16 @@ export class Partial {
return html`${errorCard}`;
}
static header(title: string, icon?: string): TemplateResult | null {
static headerName(title: string): TemplateResult {
return html`
<div class="time-picker-header">
${icon ? html`<ha-icon class="icon" icon=${icon}></ha-icon>` : ''}${title}
${title}
</div>
`;
}
static nestedName(name: string, entity: HassEntity): TemplateResult {
return html`<state-badge .stateObj=${entity}></state-badge>
<div class="entity-name-inside">${name}</div>`;
}
}
+91 -25
View File
@@ -9,14 +9,22 @@ import {
property,
TemplateResult,
} from 'lit-element';
import './components/time-period.component';
import './components/time-unit.component';
import './editor';
import { CARD_SIZE, CARD_VERSION, DEFAULT_LAYOUT_HOUR_MODE, ENTITY_DOMAIN } from './const';
import { ClassInfo, classMap } from 'lit-html/directives/class-map';
import {
CARD_SIZE,
CARD_VERSION,
DEFAULT_LAYOUT_HOUR_MODE,
ENTITY_DOMAIN,
DEFAULT_LAYOUT_ALIGN_CONTROLS,
} from './const';
import { Hour } from './models/hour';
import { Minute } from './models/minute';
import { Partial } from './partials';
import { Period, TimePickerCardConfig } from './types';
import { Period, TimePickerCardConfig, Layout } from './types';
import './components/time-period.component';
import './components/time-unit.component';
import './editor';
console.info(
`%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `,
@@ -24,14 +32,12 @@ 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({
window.customCards = window.customCards || [];
window.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 implements LovelaceCard {
@@ -45,8 +51,20 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
return this.hass.states[this.config.entity];
}
private get shouldShowName(): boolean {
return Boolean(this.config.hide?.name) === false && Boolean(this.name);
private get hasNameInHeader(): boolean {
return (
Boolean(this.name) &&
Boolean(this.config.hide?.name) === false &&
this.config.layout?.name !== Layout.Name.INSIDE
);
}
private get hasNameInside(): boolean {
return (
Boolean(this.name) &&
Boolean(this.config.hide?.name) === false &&
this.config.layout?.name === Layout.Name.INSIDE
);
}
private get name(): string | undefined {
@@ -57,6 +75,24 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
return this.config.hour_mode === 12;
}
private get layoutAlign(): Layout.AlignControls {
return this.config.layout?.align_controls ?? DEFAULT_LAYOUT_ALIGN_CONTROLS;
}
private get rowClass(): ClassInfo {
return {
'time-picker-row': true,
'with-header-name': this.hasNameInHeader,
};
}
private get contentClass(): ClassInfo {
return {
'time-picker-content': true,
[`layout-${this.layoutAlign}`]: true,
};
}
render(): TemplateResult | null {
if (!this.entity) {
return Partial.error('Entity not found', this.config);
@@ -80,19 +116,23 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
return html`
<ha-card>
${this.shouldShowName ? Partial.header(this.name!) : ''}
<div class="time-picker-content">
<time-unit .unit=${this.hour} @update=${this.callHassService}></time-unit>
<div class="time-separator">:</div>
<time-unit .unit=${this.minute} @update=${this.callHassService}></time-unit>
${this.hasNameInHeader ? Partial.headerName(this.name!) : ''}
<div class=${classMap(this.rowClass)}>
${this.hasNameInside ? Partial.nestedName(this.name!, this.entity) : ''}
${this.shouldShowPeriod
? html`<time-period
.period=${this.period}
.mode=${this.config.layout?.hour_mode ?? DEFAULT_LAYOUT_HOUR_MODE}
@toggle=${this.onPeriodToggle}
></time-period>`
: ''}
<div class=${classMap(this.contentClass)}>
<time-unit .unit=${this.hour} @update=${this.callHassService}></time-unit>
<div class="time-separator">:</div>
<time-unit .unit=${this.minute} @update=${this.callHassService}></time-unit>
${this.shouldShowPeriod
? html`<time-period
.period=${this.period}
.mode=${this.config.layout?.hour_mode ?? DEFAULT_LAYOUT_HOUR_MODE}
@toggle=${this.onPeriodToggle}
></time-period>`
: ''}
</div>
</div>
</ha-card>
`;
@@ -158,13 +198,39 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
text-align: center;
}
.time-picker-content {
padding: 8px 16px 16px;
.time-picker-row {
display: flex;
flex-direction: row;
align-items: center;
padding: 16px;
}
.time-picker-row.with-header-name {
padding: 8px 16px 16px;
}
.time-picker-content {
display: flex;
flex-direction: row;
align-items: center;
flex: 1 0 auto;
}
.time-picker-content.layout-left {
justify-content: flex-start;
}
.time-picker-content.layout-center {
justify-content: center;
}
.time-picker-content.layout-right {
justify-content: flex-end;
}
.entity-name-inside {
margin-left: 16px;
}
`;
}
+18 -3
View File
@@ -13,13 +13,28 @@ export interface TimePickerCardConfig extends LovelaceCardConfig {
export type HourMode = 12 | 24 | undefined;
export interface TimePickerLayoutConfig {
hour_mode: HourModeLayout;
align_controls?: Layout.AlignControls;
name?: Layout.Name;
hour_mode?: Layout.HourMode;
}
export type HourModeLayout = 'single' | 'double';
export namespace Layout {
export type HourMode = 'single' | 'double';
export enum AlignControls {
LEFT = 'left',
CENTER = 'center',
RIGHT = 'right',
}
export enum Name {
HEADER = 'header',
INSIDE = 'inside',
}
}
export interface TimePickerHideConfig {
name: boolean;
name?: boolean;
}
export enum Direction {
+12
View File
@@ -0,0 +1,12 @@
export interface CustomCardEntry {
type: string;
name?: string;
description?: string;
preview?: boolean;
}
declare global {
interface Window {
customCards: Array<CustomCardEntry>;
}
}
+17
View File
@@ -0,0 +1,17 @@
import { LovelaceCard } from 'custom-card-helpers';
import { customElement, html, LitElement, TemplateResult } from 'lit-element';
@customElement('hui-error-card')
export class HuiErrorCard extends LitElement implements LovelaceCard {
setConfig(): void {
return;
}
render(): TemplateResult {
return html``;
}
getCardSize(): number {
return 1;
}
}
+111
View File
@@ -0,0 +1,111 @@
import { expect, fixture, html } from '@open-wc/testing';
import { HomeAssistant } from 'custom-card-helpers';
import { HassEntity } from 'home-assistant-js-websocket';
import '../src/time-picker-card';
import { TimePickerCard } from '../src/time-picker-card';
import { TimePickerCardConfig, HourMode } from '../src/types';
import './test-error-card';
async function render(config?: {
entityId?: string;
friendlyName?: string;
hasTime?: boolean;
hourMode?: HourMode;
hideName?: boolean;
}): Promise<TimePickerCard> {
const { entityId, friendlyName, hasTime, hourMode, hideName } = Object.assign(
{
entityId: 'input_datetime.wake_time',
friendlyName: 'Wake Time',
hasTime: true,
hideName: false,
},
config
);
const testEntity: HassEntity = {
entity_id: entityId,
attributes: {
friendly_name: friendlyName,
has_time: hasTime,
},
state: '00:10:00',
last_changed: '',
last_updated: '',
context: {
id: entityId,
user_id: null,
},
};
const hass: Partial<HomeAssistant> = {
states: {
[entityId]: testEntity,
},
};
const testConfig: TimePickerCardConfig = {
type: 'custom:time-picker-card',
entity: entityId,
hour_mode: hourMode,
hide: {
name: hideName,
},
};
const card = await fixture<TimePickerCard>(
html`<time-picker-card .hass=${hass} .config=${testConfig}></time-picker-card>`
);
card.setConfig(testConfig);
return card;
}
function errorCard(): string {
return html`<hui-error-card></hui-error-card>`.getHTML();
}
describe('test', () => {
it('renders', async () => {
const card = await render();
expect(card).shadowDom.to.match('time-picker-card');
});
it('errors if entity is not an input_datetime', async () => {
const card = await render({ entityId: 'sensor.test_sensor' });
expect(card).shadowDom.to.equal(errorCard());
});
it("errors if entity doesn't have time", async () => {
const card = await render({ hasTime: false });
expect(card).shadowDom.to.equal(errorCard());
});
it('renders card name', async () => {
const card = await render();
expect(card.shadowRoot?.innerHTML).to.match(/Wake Time/);
});
it('hides card name', async () => {
const card = await render({ hideName: true });
expect(card.shadowRoot?.innerHTML).not.to.match(/Wake Time/);
});
it('renders time units', async () => {
const card = await render();
expect(card.shadowRoot?.innerHTML.match(/<time-unit>/g)!.length).to.equal(2);
});
describe('time-period', () => {
it('is not rendered by default', async () => {
const card = await render();
expect(card.shadowRoot?.innerHTML.match(/<time-period>/g)).to.be.null;
});
it('renders in 12-hour mode', async () => {
const card = await render({ hourMode: 12 });
expect(card.shadowRoot?.innerHTML.match(/<time-period>/g)!.length).to.equal(1);
});
});
});
+2 -1
View File
@@ -12,6 +12,7 @@
"noImplicitAny": false,
"skipLibCheck": true,
"resolveJsonModule": true,
"experimentalDecorators": true
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true // for importing sinon in tests
}
}