* Add an option to ignore light temperature

* Deprecate style in favor of styles.card

* pre-commit hooks

* name_template and entity_picture_template

* Use custom-card-helpers

* Deep merging state by id

* Using latest custom-card-helpers

* Aspect ratio support and auto icon resizing

* Updating documentation
This commit is contained in:
Jérôme W 2019-05-24 12:54:42 +02:00 committed by GitHub
parent 6f12577e15
commit 5ec35fd72c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 4329 additions and 3270 deletions

3
.vscode/launch.json vendored
View File

@ -10,8 +10,7 @@
"name": "Chrome Localhost",
"url": "http://localhost:8123",
"webRoot": "${workspaceFolder}/dist",
"sourceMaps": true,
"preLaunchTask": "watch"
"sourceMaps": true
}
]
}

134
README.md
View File

@ -28,14 +28,16 @@ Lovelace Button card for your entities.
- [Light entity color variable](#light-entity-color-variable)
- [ADVANCED styling options](#advanced-styling-options)
- [Configuration Templates](#configuration-templates)
- [General](#general)
- [Merging state by id](#merging-state-by-id)
- [Installation](#installation)
- [Manual Installation](#manual-installation)
- [Installation and tracking with `custom_updater`](#installation-and-tracking-with-custom_updater)
- [Installation and tracking with `custom_updater`](#installation-and-tracking-with-customupdater)
- [Examples](#examples)
- [Configuration with states](#configuration-with-states)
- [Default behavior](#default-behavior)
- [With Operator on state](#with-operator-on-state)
- [`tap_action` Navigate](#tap_action-navigate)
- [`tap_action` Navigate](#tapaction-navigate)
- [blink](#blink)
- [Play with width, height and icon size](#play-with-width-height-and-icon-size)
- [Templates Support](#templates-support)
@ -43,6 +45,7 @@ Lovelace Button card for your entities.
- [State Templates](#state-templates)
- [Styling](#styling)
- [Lock](#lock)
- [Aspect Ratio](#aspect-ratio)
- [Credits](#credits)
@ -54,6 +57,7 @@ Lovelace Button card for your entities.
- custom color (optional), or based on light rgb value/temperature
- custom state definition with customizable color, icon and style (optional)
- [custom size of the icon, width and height](#Play-with-width-height-and-icon-size) (optional)
- [aspect ratio support](#aspect-ratio) (optional)
- Support for [templates](#templates) in some fields
- custom icon (optional)
- custom css style (optional)
@ -81,8 +85,9 @@ Lovelace Button card for your entities.
| `entity` | string | optional | `switch.ac` | entity_id |
| `icon` | string | optional | `mdi:air-conditioner` | Icon to display. Will be overriden by the icon defined in a state (if present). Defaults to the entity icon. Hide with `show_icon: false` |
| `color_type` | string | `icon` | `icon` \| `card` \| `blank-card` \| `label-card` | Color either the background of the card or the icon inside the card. Setting this to `card` enable automatic `font` and `icon` color. This allows the text/icon to be readable even if the background color is bright/dark. Additional color-type options `blank-card` and `label-card` can be used for organisation (see examples). |
| `color` | string | optional | `auto` \| `rgb(28, 128, 199)` | Color of the icon/card. `auto` sets the color based on the color of a light. By default, if the entity state is `off`, the color will be `var(--paper-item-icon-active-color)`, for `on` it will be `var(--paper-item-icon-color)` and for any other state it will be `var(--primary-text-color)`. You can redefine each colors using `state` |
| `size` | string | `40%` | `20px` | Size of the icon. Can be percentage or pixel |
| `color` | string | optional | `auto` \| `auto-no-temperature` \| `rgb(28, 128, 199)` | Color of the icon/card. `auto` sets the color based on the color of a light including the temperature of the light. Setting this to `auto-no-temperature` will behave like home-assistant's default ignoring the temperature of the light. By default, if the entity state is `off`, the color will be `var(--paper-item-icon-active-color)`, for `on` it will be `var(--paper-item-icon-color)` and for any other state it will be `var(--primary-text-color)`. You can redefine each colors using `state` |
| `size` | string | `40%` | `20px` | Size of the icon. Can be percentage or pixel |
| `aspect_ratio` | string | optional | `1/1`, `2/1`, `1/1.5`, ... | See [here](#aspect-ratio) for an example. Aspect ratio of the card. `1/1` being a square. This will auto adapt to your screen size |
| `tap_action` | object | optional | See [Action](#Action) | Define the type of action on click, if undefined, toggle will be used. |
| `hold_action` | object | optional | See [Action](#Action) | Define the type of action on hold, if undefined, nothing happens. |
| `dbltap_action` | object | optional | See [Action](#Action) | Define the type of action on double click, if undefined, nothing happens. |
@ -90,6 +95,7 @@ Lovelace Button card for your entities.
| `label` | string | optional | Any string that you want | Display a label below the card. See [Layouts](#layout) for more information. |
| `label_template` | string | optional | | See [templates](#templates). Any javascript code which returns a string. Overrides `label` |
| `show_name` | boolean | `true` | `true` \| `false` | Wether to show the name or not. Will pick entity_id's name by default, unless redefined in the `name` property or in any state `name` property |
| `name_template` | string | optional | | See [templates](#templates). Any javascript code which returns a string. Overrides `name` |
| `show_state` | boolean | `false` | `true` \| `false` | Show the state on the card. defaults to false if not set |
| `show_icon` | boolean | `true` | `true` \| `false` | Wether to show the icon or not. Unless redefined in `icon`, uses the default entity icon from hass |
| `show_units` | boolean | `true` | `true` \| `false` | Display or hide the units of a sensor, if any. |
@ -97,6 +103,7 @@ Lovelace Button card for your entities.
| `show_last_changed` | boolean | `false` | `true` \| `false` | Replace the label altogether and display the the `last_changed` attribute in a nice way (eg: `12 minutes ago`) |
| `show_entity_picture` | boolean | `false` | `true` \| `false` | Replace the icon by the entity picture (if any) or the custom picture (if any). Falls back to using the icon if both are undefined |
| `entity_picture` | string | optional | Can be any of `/local/*` file or a URL | Will override the icon/the default entity_picture with your own image. Best is to use a square image. You can also define one per state |
| `entity_picture_template` | string | optional | | See [templates](#templates). Any javascript code which returns a path to a file or a url as a string. Overrides `entity_picture` |
| `units` | string | optional | `Kb/s`, `lux`, ... | Override or define the units to display after the state of the entity. If omitted, it's using the entity's units |
| `styles` | object list | optional | | See [styles](#styles) |
| `state` | object list | optional | See [State](#State) | State to use for the color, icon and style of the button. Multiple states can be defined |
@ -109,6 +116,7 @@ Lovelace Button card for your entities.
| Name | Type | Default | Supported options | Description |
| ----------------- | ------ | -------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `action` | string | `toggle` | `more-info`, `toggle`, `call-service`, `none`, `navigate`, `url` | Action to perform |
| `entity` | string | none | Any entity id | **Only valid for `action: more-info`** to override the entity on which you want to call `more-info` |
| `navigation_path` | string | none | Eg: `/lovelace/0/` | Path to navigate to (e.g. `/lovelace/0/`) when action defined as navigate |
| `url` | string | none | Eg: `https://www.google.fr` | URL to open on click when action is `url`. The URL will open in a new tab |
| `service` | string | none | Any service | Service to call (e.g. `media_player.media_play_pause`) when `action` defined as `call-service` |
@ -123,11 +131,13 @@ Lovelace Button card for your entities.
| `operator` | string | `==` | See [Available Operators](#Available-operators) | The operator used to compare the current state against the `value` |
| `value` | string/number | **required** (unless operator is `default`) | If your entity is a sensor with numbers, use a number directly, else use a string | The value which will be compared against the current state of the entity |
| `name` | string | optional | Any string, `'Alert'`, `'My little switch is on'`, ... | if `show_name` is `true`, the name to display for this state. If undefined, uses the general config `name`, and if undefined uses the entity name |
| `name_template` | string | optional | | See [templates](#templates). Any javascript code which returns a string. Overrides `name` |
| `icon` | string | optional | `mdi:battery` | The icon to display for this state. Defaults to the entity icon. Hide with `show_icon: false` |
| `color` | string | `var(--primary-text-color)` | Any color, eg: `rgb(28, 128, 199)` or `blue` | The color of the icon (if `color_type: icon`) or the background (if `color_type: card`) |
| `styles` | string | optional | | See [styles](#styles) |
| `spin` | boolean | `false` | `true` \| `false` | Should the icon spin for this state? |
| `entity_picture` | string | optional | Can be any of `/local/*` file or a URL | Will override the icon/the default entity_picture with your own image for this state. Best is to use a square image |
| `entity_picture_template` | string | optional | | See [templates](#templates). Any javascript code which returns a path to a file or a url as a string. Overrides `entity_picture` |
| `label` | string | optional | Any string that you want | Display a label below the card. See [Layouts](#layout) for more information. |
| `label_template` | string | optional | | See [templates](#templates). Any javascript code which returns a string. Overrides `label` |
@ -167,7 +177,7 @@ Multiple values are possible, see the image below for examples:
### Templates
`label_template` supports templating as well as `value` for `state` when `operator: template`
`label_template`, `name_template`, `entity_picture_template` supports templating as well as `value` for `state` when `operator: template`
* `label_template`: It will be interpreted as javascript code and the code should return a string.
`label_template` supports inline HTML, so you can do stuff like:
```yaml
@ -180,6 +190,8 @@ Multiple values are possible, see the image below for examples:
+ (states['binary_sensor.status'].state === 'on' ? 'connected' : 'disconnected')
```
![label-template-example](examples/label_template.png)
* `name_template`: It will be interpreted as javascript code and the code should return a string.
* `entity_picture_template`: It will be interpreted as javascript code and the code should return a path to a file or a url as a string.
* `value` for `state` when `operator: template`: It will be interpreted as javascript code and the code should return a boolean (`true` or `false`)
Inside the javascript code, you'll have access to those variables:
@ -356,8 +368,12 @@ Some examples:
```
### Configuration Templates
#### General
* Define your config template in the main lovelace configuration and then use it in your button-card. This will avoid a lot of repetitions! It's basically YAML anchors, but without using YAML anchors and is very useful if you split your config in multiple files 😄
* You can overload any parameter with a new one, **appart from the states**. The state arrays in templates will be concatenated together with your config, meaning you can **add** new states but not change/delete states. Your main config states will be appended to the ones in the template.
* You can overload any parameter with a new one
* You can merge states together **by `id`** when using templates. The states you want to merge have to have the same `id`. This `id` parameter is new and can be anything (string, number, ...). States without `id` will be appended to the state array. Styles embedded in a state are merged together as usual. See [here](#merging-state-by-id) for an example.
* You can also inherit another template from within a template.
In `ui-lovelace.yaml` (or in another file using `!import`)
@ -396,6 +412,67 @@ And then where you use button-card, you can apply this template, and/or overload
name: My Test Header
```
#### Merging state by id
Example to merge state by `id`:
```yaml
button_card_templates:
sensor:
styles:
card:
- font-size: 16px
- width: 75px
tap_action:
action: more-info
state:
- color: orange
value: 75
id: my_id
sensor_humidity:
template: sensor
icon: 'mdi:weather-rainy'
state:
- color: 'rgb(255,0,0)'
operator: '>'
value: 50
- color: 'rgb(0,0,255)'
operator: '<'
value: 25
sensor_test:
template: sensor_humidity
state:
- color: pink
id: my_id
operator: '>'
value: 75
styles:
name:
- color: '#ff0000'
############### Used like this ##############
- type: custom:button-card
template: sensor_test
entity: input_number.test
show_entity_picture: true
```
Will result in this state object for your button (styles, operator and color are overridden for the `id: my_id` as you can see):
```yaml
state:
- color: pink
operator: '>'
value: 75
styles:
name:
- color: '#ff0000'
- color: 'rgb(255,0,0)'
operator: '>'
value: 50
- color: 'rgb(0,0,255)'
operator: '<'
value: 25
```
## Installation
### Manual Installation
@ -903,6 +980,51 @@ Example with `template`:
entity: switch.test
```
### Aspect Ratio
![aspect-ratio-image](examples/aspect_ratio.png)
```yaml
- type: vertical-stack
cards:
- type: horizontal-stack
cards:
- type: custom:button-card
name: 1/1
icon: mdi:lightbulb
aspect_ratio: 1/1
- type: custom:button-card
name: 2/1
icon: mdi:lightbulb
aspect_ratio: 2/1
- type: custom:button-card
name: 3/1
icon: mdi:lightbulb
aspect_ratio: 3/1
- type: custom:button-card
name: 4/1
icon: mdi:lightbulb
aspect_ratio: 4/1
- type: horizontal-stack
cards:
- type: custom:button-card
name: 1/1.2
icon: mdi:lightbulb
aspect_ratio: 1/1.2
- type: custom:button-card
name: 1/1.3
icon: mdi:lightbulb
aspect_ratio: 1/1.3
- type: custom:button-card
name: 1/1.4
icon: mdi:lightbulb
aspect_ratio: 1/1.4
- type: custom:button-card
name: 1/1.5
icon: mdi:lightbulb
aspect_ratio: 1/1.5
```
## Credits
- [ciotlosm](https://github.com/ciotlosm) for the readme template and the awesome examples

1232
dist/button-card.js vendored

File diff suppressed because it is too large Load Diff

BIN
examples/aspect_ratio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

8
hooks/pre-commit.sh Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
echo "Pre-Commit hooks running..."
npm run build
git add dist/button-card.js

5259
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,13 +4,12 @@
"description": "Button card for lovelace",
"main": "dist/button-card.js",
"pre-commit": [
"build",
"commit_dist"
"pre-commit"
],
"scripts": {
"pre-commit": "./hooks/pre-commit.sh",
"build": "npm run rollup && npm run babel",
"rollup": "rollup -c",
"commit_dist": "git add dist/button-card.js",
"babel": "babel dist/button-card.js --out-file dist/button-card.js",
"lint": "eslint src/button-card.ts",
"watch": "rollup -c rollup.debug.config.js --watch"
@ -33,30 +32,31 @@
},
"homepage": "https://github.com/custom-cards/button-card#readme",
"devDependencies": {
"@babel/core": "^7.4.3",
"@babel/plugin-proposal-class-properties": "^7.4.0",
"@babel/plugin-proposal-decorators": "^7.4.0",
"@typescript-eslint/eslint-plugin": "^1.7.0",
"@typescript-eslint/parser": "^1.7.0",
"@babel/core": "^7.4.4",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-proposal-decorators": "^7.4.4",
"@typescript-eslint/eslint-plugin": "^1.9.0",
"@typescript-eslint/parser": "^1.9.0",
"babel-cli": "^6.26.0",
"eslint": "^5.16.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-plugin-import": "^2.17.2",
"npm": "^6.9.0",
"pre-commit": "^1.2.2",
"prettier": "^1.17.0",
"rollup": "^1.10.1",
"prettier": "^1.17.1",
"rollup": "^1.12.2",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^4.2.3",
"rollup-plugin-node-resolve": "^4.2.4",
"rollup-plugin-terser": "^4.0.4",
"rollup-plugin-typescript2": "^0.20.1",
"ts-lit-plugin": "^1.0.5",
"ts-lit-plugin": "^1.0.6",
"typescript": "^3.4.4",
"typescript-styled-plugin": "^0.14.0"
},
"dependencies": {
"@ctrl/tinycolor": "^2.4.0",
"custom-card-helpers": "^1.2.1",
"home-assistant-js-websocket": "^3.4.0",
"lit-element": "^2.1.0",
"lit-html": "^1.0.0"

View File

@ -10,31 +10,36 @@ import {
import { styleMap, StyleInfo } from 'lit-html/directives/style-map';
import { unsafeHTML } from 'lit-html/directives/unsafe-html';
import { ifDefined } from 'lit-html/directives/if-defined';
import { classMap, ClassInfo } from 'lit-html/directives/class-map.js';
import {
HassEntity,
} from 'home-assistant-js-websocket';
import domainIcon from './domain_icons';
import {
ButtonCardConfig,
HomeAssistant,
StateConfig,
CssStyleConfig,
} from './types';
import {
domainIcon,
HomeAssistant,
handleClick,
getLovelace,
// Still not working...
// longPress,
} from 'custom-card-helpers';
import { longPress } from './long-press';
import {
computeDomain,
computeEntity,
getFontColorBasedOnBackgroundColor,
buildNameStateConcat,
applyBrightnessToColor,
hasConfigOrEntityChanged,
myHasConfigOrEntityChanged,
getLightColorBasedOnTemperature,
getLovelace,
mergeDeep,
mergeStatesById,
} from './helpers';
import { handleClick } from './handle-click';
import { longPress } from './long-press';
import { styles } from './styles';
import computeStateDisplay from './compute_state_display';
import myComputeStateDisplay from './compute_state_display';
@customElement('button-card')
class ButtonCard extends LitElement {
@ -64,7 +69,7 @@ class ButtonCard extends LitElement {
|| this.config!.state
&& this.config!.state.find(elt => elt.operator === 'template')
? true : false;
return hasConfigOrEntityChanged(this, changedProps, forceUpdate);
return myHasConfigOrEntityChanged(this, changedProps, forceUpdate);
}
private _getMatchingConfigState(state: HassEntity | undefined): StateConfig | undefined {
@ -100,9 +105,7 @@ class ButtonCard extends LitElement {
return matches;
}
case 'template': {
return new Function('states', 'entity', 'user', 'hass',
`'use strict'; ${elt.value}`)
.call(this, this.hass!.states, state, this.hass!.user, this.hass);
return this._evalTemplate(state, elt.value);
}
case 'default':
def = elt;
@ -120,6 +123,12 @@ class ButtonCard extends LitElement {
return retval;
}
private _evalTemplate(state: HassEntity | undefined, func: any): any {
return new Function('states', 'entity', 'user', 'hass',
`'use strict'; ${func}`)
.call(this, this.hass!.states, state, this.hass!.user, this.hass);
}
private _getDefaultColorForState(state: HassEntity): string {
switch (state.state) {
case 'on':
@ -131,7 +140,10 @@ class ButtonCard extends LitElement {
}
}
private _getColorForLightEntity(state: HassEntity | undefined): string {
private _getColorForLightEntity(
state: HassEntity | undefined,
useTemperature: boolean,
): string {
let color: string = this.config!.default_color;
if (state) {
if (state.attributes.rgb_color) {
@ -139,7 +151,8 @@ class ButtonCard extends LitElement {
if (state.attributes.brightness) {
color = applyBrightnessToColor(color, (state.attributes.brightness + 245) / 5);
}
} else if (state.attributes.color_temp
} else if (useTemperature
&& state.attributes.color_temp
&& state.attributes.min_mireds
&& state.attributes.max_mireds) {
color = getLightColorBasedOnTemperature(
@ -173,8 +186,8 @@ class ButtonCard extends LitElement {
} else if (this.config!.color) {
colorValue = this.config!.color;
}
if (colorValue == 'auto') {
color = this._getColorForLightEntity(state);
if (colorValue == 'auto' || colorValue == 'auto-no-temperature') {
color = this._getColorForLightEntity(state, colorValue !== 'auto-no-temperature');
} else if (colorValue) {
color = colorValue;
} else if (state) {
@ -212,15 +225,26 @@ class ButtonCard extends LitElement {
return undefined;
}
let entityPicture: string | undefined;
if (configState && configState.entity_picture) {
entityPicture = configState.entity_picture;
} else if (this.config!.entity_picture) {
entityPicture = this.config!.entity_picture;
let matchingEntityPictureTemplate: string | undefined;
if (configState && configState.entity_picture_template) {
matchingEntityPictureTemplate = configState.entity_picture_template;
} else {
entityPicture = state && state.attributes && state.attributes.entity_picture
? state.attributes.entity_picture : undefined;
matchingEntityPictureTemplate = this.config!.entity_picture_template;
}
return entityPicture;
if (!matchingEntityPictureTemplate) {
if (configState && configState.entity_picture) {
entityPicture = configState.entity_picture;
} else if (this.config!.entity_picture) {
entityPicture = this.config!.entity_picture;
} else if (state) {
entityPicture = state.attributes && state.attributes.entity_picture
? state.attributes.entity_picture : undefined;
}
return entityPicture;
}
return this._evalTemplate(state, matchingEntityPictureTemplate);
}
private _buildStyleGeneric(
@ -228,10 +252,10 @@ class ButtonCard extends LitElement {
styleType: string,
): StyleInfo {
let style: StyleInfo = {};
if (this.config!.styles[styleType]) {
if (this.config!.styles && this.config!.styles[styleType]) {
style = Object.assign(style, ...this.config!.styles[styleType]);
}
if (configState && configState.styles[styleType]) {
if (configState && configState.styles && configState.styles[styleType]) {
let configStateStyle: StyleInfo = {};
configStateStyle = Object.assign(configStateStyle, ...configState.styles[styleType]);
style = {
@ -249,21 +273,32 @@ class ButtonCard extends LitElement {
return undefined;
}
let name: string | undefined;
if (configState && configState.name) {
name = configState.name;
} else if (this.config!.name) {
name = this.config!.name;
} else if (state) {
name = state.attributes && state.attributes.friendly_name
? state.attributes.friendly_name : computeEntity(state.entity_id);
let matchingNameTemplate: string | undefined;
if (configState && configState.name_template) {
matchingNameTemplate = configState.name_template;
} else {
matchingNameTemplate = this.config!.name_template;
}
return name;
if (!matchingNameTemplate) {
if (configState && configState.name) {
name = configState.name;
} else if (this.config!.name) {
name = this.config!.name;
} else if (state) {
name = state.attributes && state.attributes.friendly_name
? state.attributes.friendly_name : computeEntity(state.entity_id);
}
return name;
}
return this._evalTemplate(state, matchingNameTemplate);
}
private _buildStateString(state: HassEntity | undefined): string | undefined {
let stateString: string | undefined;
if (this.config!.show_state && state && state.state) {
const localizedState = computeStateDisplay(this.hass!.localize, state);
const localizedState = myComputeStateDisplay(this.hass!.localize, state);
const units = this._buildUnits(state);
if (units) {
stateString = `${state.state} ${units}`;
@ -292,7 +327,15 @@ class ButtonCard extends LitElement {
state: HassEntity | undefined,
style: StyleInfo,
): TemplateResult | undefined {
return this.config!.show_last_changed && state ? html`<ha-relative-time id="label" class="ellipsis" .hass="${this.hass}" .datetime="${state.last_changed}" style=${styleMap(style)}></ha-relative-time>` : undefined;
return this.config!.show_last_changed && state ?
html`
<ha-relative-time
id="label"
class="ellipsis"
.hass="${this.hass}"
.datetime="${state.last_changed}"
style=${styleMap(style)}
></ha-relative-time>` : undefined;
}
private _buildLabel(
@ -319,16 +362,24 @@ class ButtonCard extends LitElement {
return label;
}
/* eslint no-new-func: 0 */
return new Function('states', 'entity', 'user', 'hass',
`'use strict'; ${matchingLabelTemplate}`)
.call(this, this.hass!.states, state, this.hass!.user, this.hass);
return this._evalTemplate(state, matchingLabelTemplate);
}
private _isClickable(state: HassEntity | undefined): boolean {
let clickable = true;
if (this.config!.tap_action!.action === 'toggle' && this.config!.hold_action!.action === 'none'
|| this.config!.hold_action!.action === 'toggle' && this.config!.tap_action!.action === 'none') {
if (
this.config!.tap_action!.action === 'toggle'
&& this.config!.hold_action!.action === 'none'
&& this.config!.dbltap_action!.action === 'none'
|| this.config!.hold_action!.action === 'toggle'
&& this.config!.tap_action!.action === 'none'
&& this.config!.dbltap_action!.action === 'none'
|| this.config!.dbltap_action!.action === 'toggle'
&& this.config!.tap_action!.action === 'none'
&& this.config!.hold_action!.action === 'none'
) {
if (state) {
switch (computeDomain(state.entity_id)) {
case 'sensor':
@ -343,8 +394,11 @@ class ButtonCard extends LitElement {
} else {
clickable = false;
}
} else if (this.config!.tap_action!.action != 'none'
|| this.config!.hold_action!.action != 'none') {
} else if (
this.config!.tap_action!.action != 'none'
|| this.config!.hold_action!.action != 'none'
|| this.config!.dbltap_action!.action != 'none'
) {
clickable = true;
} else {
clickable = false;
@ -380,7 +434,10 @@ class ButtonCard extends LitElement {
let lockStyle: StyleInfo = {};
const lockStyleFromConfig = this._buildStyleGeneric(configState, 'lock');
const configCardStyle = this._buildStyleGeneric(configState, 'card');
const classList: ClassInfo = {
'button-card-main': true,
disabled: !this._isClickable(state),
}
if (configCardStyle.width) {
this.style.setProperty('flex', '0 0 auto');
this.style.setProperty('max-width', 'fit-content');
@ -402,11 +459,25 @@ class ButtonCard extends LitElement {
cardStyle = configCardStyle;
break;
}
this.style.setProperty('--button-card-light-color', this._getColorForLightEntity(state));
if (this.config!.aspect_ratio) {
cardStyle['--aspect-ratio'] = this.config!.aspect_ratio;
cardStyle.padding = '0px';
}
this.style.setProperty('--button-card-light-color', this._getColorForLightEntity(state, true));
lockStyle = { ...lockStyle, ...lockStyleFromConfig };
return html`
<ha-card class="button-card-main ${this._isClickable(state) ? '' : 'disabled'}" style=${styleMap(cardStyle)} @ha-click="${this._handleTap}" @ha-hold="${this._handleHold}" @ha-dblclick=${this._handleDblTap} .hasDblClick=${this.config!.dbltap_action!.action !== 'none'} .repeat=${ifDefined(this.config!.hold_action!.repeat)} .longpress="${longPress()}" .config="${this.config}">
<ha-card
class=${classMap(classList)}
style=${styleMap(cardStyle)}
@ha-click="${this._handleTap}"
@ha-hold="${this._handleHold}"
@ha-dblclick=${this._handleDblTap}
.hasDblClick=${this.config!.dbltap_action!.action !== 'none'}
.repeat=${ifDefined(this.config!.hold_action!.repeat)}
.longpress=${longPress()}
.config="${this.config}"
>
${this._getLock(lockStyle)}
${this._buttonContent(state, configState, buttonColor)}
${this.config!.lock ? '' : html`<mwc-ripple id="ripple"></mwc-ripple>`}
@ -418,7 +489,7 @@ class ButtonCard extends LitElement {
if (this.config!.lock) {
return html`
<div id="overlay" style=${styleMap(lockStyle)} @click=${this._handleLock} @touchstart=${this._handleLock}>
<ha-icon id="lock" icon="mdi:lock-outline"></iron-icon>
<ha-icon id="lock" icon="mdi:lock-outline"></ha-icon>
</div>
`;
}
@ -487,13 +558,15 @@ class ButtonCard extends LitElement {
const entityPictureStyleFromConfig = this._buildStyleGeneric(configState, 'entity_picture');
const haIconStyleFromConfig = this._buildStyleGeneric(configState, 'icon');
const imgCellStyleFromConfig = this._buildStyleGeneric(configState, 'img_cell');
const haCardStyleFromConfig = this._buildStyleGeneric(configState, 'card');
const haIconStyle = {
const haIconStyle: StyleInfo = {
color,
width: this.config!.size,
position: !this.config!.aspect_ratio && !haCardStyleFromConfig.height ? 'relative' : 'absolute',
...haIconStyleFromConfig,
};
const entityPictureStyle = {
const entityPictureStyle: StyleInfo = {
...haIconStyle,
...entityPictureStyleFromConfig,
};
@ -520,10 +593,13 @@ class ButtonCard extends LitElement {
const ll = getLovelace();
let template: ButtonCardConfig = { ...config };
let tplName: string | undefined = template.template;
let mergedStateConfig: StateConfig[] | undefined = config.state;
while (tplName && ll.config.button_card_templates && ll.config.button_card_templates[tplName]) {
template = mergeDeep(ll.config.button_card_templates[tplName], template);
mergedStateConfig = mergeStatesById((ll.config.button_card_templates[tplName] as ButtonCardConfig).state, mergedStateConfig);
tplName = (ll.config.button_card_templates[tplName] as ButtonCardConfig).template;
}
template.state = mergedStateConfig;
this.config = {
tap_action: { action: 'toggle' },
hold_action: { action: 'none' },
@ -546,31 +622,6 @@ class ButtonCard extends LitElement {
this.config!.color_off = 'var(--paper-item-icon-color)';
}
this.config!.color_on = 'var(--paper-item-icon-active-color)';
/* Temporary until we deprecate style and entity_picture_style config option */
if (!this.config.styles) {
this.config.styles = {};
}
if (this.config.style && !this.config.styles.card) {
this.config.styles.card = this.config.style;
}
if (this.config.entity_picture_style && !this.config.styles.entity_picture) {
this.config.styles.entity_picture = this.config.entity_picture_style;
}
if (this.config.state) {
/* eslint no-param-reassign: ["error", { "props": false }] */
this.config.state.forEach((s) => {
if (!s.styles) {
s.styles = {};
}
if (s.entity_picture_style && !s.styles.entity_picture) {
s.styles.entity_picture = s.entity_picture_style;
}
if (s.style && !s.styles.card) {
s.styles.card = s.style;
}
});
}
}
// The height of your card. Home Assistant uses this to automatically

View File

@ -1,11 +1,11 @@
import { HassEntity } from 'home-assistant-js-websocket';
import { computeDomain } from './helpers';
import { LocalizeFunc } from './types';
import { LocalizeFunc } from 'custom-card-helpers';
export default (
localize: LocalizeFunc,
stateObj: HassEntity,
): string => {
): string | undefined => {
let display: string | undefined;
const domain = computeDomain(stateObj.entity_id);

View File

@ -1,89 +0,0 @@
/** Constants to be used in the frontend. */
// Constants should be alphabetically sorted by name.
// Arrays with values should be alphabetically sorted if order doesn't matter.
// Each constant should have a description what it is supposed to be used for.
/** Icon to use when no icon specified for domain. */
export const DEFAULT_DOMAIN_ICON = "hass:bookmark";
/** Panel to show when no panel is picked. */
export const DEFAULT_PANEL = "lovelace";
/** Domains that have a state card. */
export const DOMAINS_WITH_CARD = [
"climate",
"cover",
"configurator",
"input_select",
"input_number",
"input_text",
"lock",
"media_player",
"scene",
"script",
"timer",
"vacuum",
"water_heater",
"weblink",
];
/** Domains with separate more info dialog. */
export const DOMAINS_WITH_MORE_INFO = [
"alarm_control_panel",
"automation",
"camera",
"climate",
"configurator",
"cover",
"fan",
"group",
"history_graph",
"input_datetime",
"light",
"lock",
"media_player",
"script",
"sun",
"updater",
"vacuum",
"water_heater",
"weather",
];
/** Domains that show no more info dialog. */
export const DOMAINS_HIDE_MORE_INFO = [
"input_number",
"input_select",
"input_text",
"scene",
"weblink",
];
/** Domains that should have the history hidden in the more info dialog. */
export const DOMAINS_MORE_INFO_NO_HISTORY = [
"camera",
"configurator",
"history_graph",
"scene",
];
/** States that we consider "off". */
export const STATES_OFF = ["closed", "locked", "off"];
/** Domains where we allow toggle in Lovelace. */
export const DOMAINS_TOGGLE = new Set([
"fan",
"input_boolean",
"light",
"switch",
"group",
"automation",
]);
/** Temperature units. */
export const UNIT_C = "°C";
export const UNIT_F = "°F";
/** Entity ID of the default view. */
export const DEFAULT_VIEW_ENTITY_ID = "group.default_view";

View File

@ -1,103 +0,0 @@
/**
* Return the icon to be used for a domain.
*
* Optionally pass in a state to influence the domain icon.
*/
import { DEFAULT_DOMAIN_ICON } from "./const";
const fixedIcons = {
alert: "hass:alert",
automation: "hass:playlist-play",
calendar: "hass:calendar",
camera: "hass:video",
climate: "hass:thermostat",
configurator: "hass:settings",
conversation: "hass:text-to-speech",
device_tracker: "hass:account",
fan: "hass:fan",
group: "hass:google-circles-communities",
history_graph: "hass:chart-line",
homeassistant: "hass:home-assistant",
homekit: "hass:home-automation",
image_processing: "hass:image-filter-frames",
input_boolean: "hass:drawing",
input_datetime: "hass:calendar-clock",
input_number: "hass:ray-vertex",
input_select: "hass:format-list-bulleted",
input_text: "hass:textbox",
light: "hass:lightbulb",
mailbox: "hass:mailbox",
notify: "hass:comment-alert",
person: "hass:account",
plant: "hass:flower",
proximity: "hass:apple-safari",
remote: "hass:remote",
scene: "hass:google-pages",
script: "hass:file-document",
sensor: "hass:eye",
simple_alarm: "hass:bell",
sun: "hass:white-balance-sunny",
switch: "hass:flash",
timer: "hass:timer",
updater: "hass:cloud-upload",
vacuum: "hass:robot-vacuum",
water_heater: "hass:thermometer",
weblink: "hass:open-in-new",
};
export default function domainIcon(domain: string, state?: string): string {
if (domain in fixedIcons) {
return fixedIcons[domain];
}
switch (domain) {
case "alarm_control_panel":
switch (state) {
case "armed_home":
return "hass:bell-plus";
case "armed_night":
return "hass:bell-sleep";
case "disarmed":
return "hass:bell-outline";
case "triggered":
return "hass:bell-ring";
default:
return "hass:bell";
}
case "binary_sensor":
return state && state === "off"
? "hass:radiobox-blank"
: "hass:checkbox-marked-circle";
case "cover":
return state === "closed" ? "hass:window-closed" : "hass:window-open";
case "lock":
return state && state === "unlocked" ? "hass:lock-open" : "hass:lock";
case "media_player":
return state && state !== "off" && state !== "idle"
? "hass:cast-connected"
: "hass:cast";
case "zwave":
switch (state) {
case "dead":
return "hass:emoticon-dead";
case "sleeping":
return "hass:sleep";
case "initializing":
return "hass:timer-sand";
default:
return "hass:z-wave";
}
default:
// tslint:disable-next-line
console.warn(
"Unable to find icon for domain " + domain + " (" + state + ")"
);
return DEFAULT_DOMAIN_ICON;
}
}

View File

@ -1,78 +0,0 @@
// Polymer legacy event helpers used courtesy of the Polymer project.
//
// Copyright (c) 2017 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
declare global {
// tslint:disable-next-line
interface HASSDomEvents { }
}
export type ValidHassDomEvent = keyof HASSDomEvents;
export interface HASSDomEvent<T> extends Event {
detail: T;
}
/**
* Dispatches a custom event with an optional detail value.
*
* @param {string} type Name of event type.
* @param {*=} detail Detail value containing event-specific
* payload.
* @param {{ bubbles: (boolean|undefined),
* cancelable: (boolean|undefined),
* composed: (boolean|undefined) }=}
* options Object specifying options. These may include:
* `bubbles` (boolean, defaults to `true`),
* `cancelable` (boolean, defaults to false), and
* `node` on which to fire the event (HTMLElement, defaults to `this`).
* @return {Event} The new event that was fired.
*/
export const fireEvent = <HassEvent extends ValidHassDomEvent>(
node: HTMLElement | Window,
type: HassEvent,
detail?: HASSDomEvents[HassEvent],
options?: {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
}
): Event => {
options = options || {};
// @ts-ignore
detail = detail === null || detail === undefined ? {} : detail;
const event = new Event(type, {
bubbles: options.bubbles === undefined ? true : options.bubbles,
cancelable: Boolean(options.cancelable),
composed: options.composed === undefined ? true : options.composed,
});
(event as any).detail = detail;
node.dispatchEvent(event);
return event;
};

View File

@ -1,74 +0,0 @@
import { HomeAssistant, ActionConfig } from "./types";
import { fireEvent } from "./fire_event";
import { navigate } from "./navigate";
import { toggleEntity } from "./toggle-entity";
import { forwardHaptic } from "./haptic";
export const handleClick = (
node: HTMLElement,
hass: HomeAssistant,
config: {
entity?: string;
camera_image?: string;
hold_action?: ActionConfig;
tap_action?: ActionConfig;
dbltap_action?: ActionConfig;
},
hold: boolean,
dblClick: boolean,
): void => {
let actionConfig: ActionConfig | undefined;
if (dblClick && config.dbltap_action) {
actionConfig = config.dbltap_action;
} else if (hold && config.hold_action) {
actionConfig = config.hold_action;
} else if (!hold && config.tap_action) {
actionConfig = config.tap_action;
}
if (!actionConfig) {
actionConfig = {
action: "toggle",
};
}
switch (actionConfig.action) {
case "more-info":
if (config.entity || config.camera_image) {
fireEvent(node, "hass-more-info", {
entityId: config.entity ? config.entity : config.camera_image!,
});
if (actionConfig.haptic) forwardHaptic(node, actionConfig.haptic);
}
break;
case "navigate":
if (actionConfig.navigation_path) {
navigate(node, actionConfig.navigation_path);
if (actionConfig.haptic) forwardHaptic(node, actionConfig.haptic);
}
break;
case 'url':
actionConfig.url && window.open(actionConfig.url);
if (actionConfig.haptic) forwardHaptic(node, actionConfig.haptic);
break;
case "toggle":
if (config.entity) {
toggleEntity(hass, config.entity!);
if (actionConfig.haptic) forwardHaptic(node, actionConfig.haptic);
}
break;
case "call-service": {
if (!actionConfig.service) {
return;
}
const [domain, service] = actionConfig.service.split(".", 2);
const localActionConfig = { ...actionConfig };
if (actionConfig.service_data && actionConfig.service_data.entity_id === 'entity') {
localActionConfig.service_data!.entity_id = config.entity;
}
hass.callService(domain, service, localActionConfig.service_data);
if (actionConfig.haptic) forwardHaptic(node, actionConfig.haptic);
}
}
};

View File

@ -1,29 +0,0 @@
/**
* Utility function that enables haptic feedback
*/
import { fireEvent } from "./fire_event";
// Allowed types are from iOS HIG.
// https://developer.apple.com/design/human-interface-guidelines/ios/user-interaction/feedback/#haptics
// Implementors on platforms other than iOS should attempt to match the patterns (shown in HIG) as closely as possible.
export type HapticType =
| "success"
| "warning"
| "failure"
| "light"
| "medium"
| "heavy"
| "selection";
declare global {
// for fire event
interface HASSDomEvents {
haptic: HapticType;
}
}
export const forwardHaptic = (el: HTMLElement, hapticType: HapticType) => {
fireEvent(el, "haptic", hapticType);
};

View File

@ -1,6 +1,7 @@
import { PropertyValues } from 'lit-element';
import tinycolor, { TinyColor } from '@ctrl/tinycolor';
import { HomeAssistant } from './types';
import { HomeAssistant } from 'custom-card-helpers';
import { StateConfig } from './types';
export function computeDomain(entityId: string): string {
return entityId.substr(0, entityId.indexOf('.'));
@ -74,7 +75,7 @@ export function applyBrightnessToColor(
}
// Check if config or Entity changed
export function hasConfigOrEntityChanged(
export function myHasConfigOrEntityChanged(
element: any,
changedProps: PropertyValues,
forceUpdate: Boolean,
@ -97,24 +98,6 @@ export function hasConfigOrEntityChanged(
}
}
export function getLovelace() {
let root: any = document.querySelector('home-assistant');
root = root && root.shadowRoot;
root = root && root.querySelector('home-assistant-main');
root = root && root.shadowRoot;
root = root && root.querySelector('app-drawer-layout partial-panel-resolver');
root = root && root.shadowRoot || root;
root = root && root.querySelector('ha-panel-lovelace');
root = root && root.shadowRoot;
root = root && root.querySelector('hui-root');
if (root) {
const ll = root.lovelace;
ll.current_view = root.___curView;
return ll;
}
return null;
}
/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation and filtering.
@ -143,3 +126,26 @@ export function mergeDeep(...objects: any): any {
return prev;
}, {});
}
export function mergeStatesById(
intoStates: StateConfig[] | undefined,
fromStates: StateConfig[] | undefined,
): StateConfig[] {
let resultStateConfigs: StateConfig[] = [];
if (intoStates) {
intoStates.forEach((intoState) => {
let localState = intoState;
if (fromStates) {
fromStates.forEach((fromState) => {
if (fromState.id && intoState.id && fromState.id == intoState.id)
localState = mergeDeep(localState, fromState);
})
}
resultStateConfigs.push(localState);
});
}
if (fromStates) {
resultStateConfigs = resultStateConfigs.concat(fromStates.filter(x => !intoStates ? true : !intoStates.find(y => y.id && x.id ? y.id == x.id : false)));
}
return resultStateConfigs;
}

View File

@ -1,25 +0,0 @@
import { fireEvent } from "./fire_event";
declare global {
// for fire event
interface HASSDomEvents {
"location-changed": {
replace: boolean;
};
}
}
export const navigate = (
_node: any,
path: string,
replace: boolean = false
) => {
if (replace) {
history.replaceState(null, "", path);
} else {
history.pushState(null, "", path);
}
fireEvent(window, "location-changed", {
replace
});
};

View File

@ -107,25 +107,37 @@ export const styles = css`
#container {
display: grid;
max-height: 100%;
text-align: center;
width: 100%;
height: 100%;
text-align: center;
align-items: center;
}
#img-cell {
/* display: flex; */
display: flex;
grid-area: i;
height: 100%;
width: 100%;
max-width: 100%;
max-height: 100%;
align-self: center;
justify-self: center;
overflow: hidden;
justify-content: center;
align-items: center;
position: relative;
}
ha-icon#icon, img#icon {
ha-icon#icon {
height: 100%;
max-width: 100%;
object-fit: contain;
overflow: hidden;
vertical-align: middle;
width: 100%;
max-height: 100%;
position: absolute;
}
img#icon {
display: block;
height: auto;
width: 100%;
position: absolute;
}
#name {
grid-area: n;
@ -149,9 +161,6 @@ export const styles = css`
justify-self: center;
}
#container {
width: 100%;
}
#container.vertical {
grid-template-areas: "i" "n" "s" "l";
grid-template-columns: 1fr;
@ -348,6 +357,30 @@ export const styles = css`
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content min-content;
}
[style*="--aspect-ratio"] > :first-child {
width: 100%;
}
[style*="--aspect-ratio"] > img {
height: auto;
}
@supports (--custom:property) {
[style*="--aspect-ratio"] {
position: relative;
padding: 0px;
}
[style*="--aspect-ratio"]::before {
content: "";
display: block;
padding-bottom: calc(100% / (var(--aspect-ratio)));
}
[style*="--aspect-ratio"] > :first-child {
position: absolute;
top: 0;
left: 0;
height: 100%;
}
}
`;
export default styles;

View File

@ -1,10 +0,0 @@
import { STATES_OFF } from "./const";
import { turnOnOffEntity } from "./turn-on-off-entity";
import { HomeAssistant } from "./types";
export const toggleEntity = (
hass: HomeAssistant,
entityId: string
): Promise<void> => {
const turnOn = STATES_OFF.includes(hass.states[entityId].state);
return turnOnOffEntity(hass, entityId, turnOn);
};

View File

@ -1,25 +0,0 @@
import * as helpers from "./helpers";
import { HomeAssistant } from "./types";
export const turnOnOffEntity = (
hass: HomeAssistant,
entityId: string,
turnOn = true
): Promise<void> => {
const stateDomain = helpers.computeDomain(entityId);
const serviceDomain = stateDomain === "group" ? "homeassistant" : stateDomain;
let service: string;
switch (stateDomain) {
case "lock":
service = turnOn ? "unlock" : "lock";
break;
case "cover":
service = turnOn ? "open_cover" : "close_cover";
break;
default:
service = turnOn ? "turn_on" : "turn_off";
}
return hass.callService(serviceDomain, service, { entity_id: entityId });
};

View File

@ -1,22 +1,16 @@
import {
HassEntities,
HassConfig,
Auth,
Connection,
MessageBase,
HassServices,
} from 'home-assistant-js-websocket';
import { HapticType } from './haptic';
import { ActionConfig } from 'custom-card-helpers';
export interface ButtonCardConfig {
template?: string;
type: string;
entity?: string;
name?: string;
name_template?: string;
icon?: string;
color_type: 'icon' | 'card' | 'label-card' | 'blank-card'
color?: string;
color?: 'auto' | 'auto-no-temperature' | string;
size: string;
aspect_ratio?: string?
lock: boolean;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
@ -31,10 +25,10 @@ export interface ButtonCardConfig {
label?: string;
label_template?: string;
entity_picture?: string;
entity_picture_template?: string;
units?: string;
style?: CssStyleConfig[];
state?: StateConfig[];
styles: StylesConfig;
styles?: StylesConfig;
confirmation?: string;
layout: Layout;
entity_picture_style?: CssStyleConfig[];
@ -54,15 +48,17 @@ export type Layout = 'vertical'
| 'icon_label';
export interface StateConfig {
id?: string;
operator?: '<' | '<=' | '==' | '>=' | '>' | '!=' | 'regex' | 'template' | 'default';
value?: any;
name?: string;
name_template?: string;
icon?: string;
color?: string;
style?: CssStyleConfig[];
color?: 'auto' | 'auto-no-temperature' | string;
entity_picture_style?: CssStyleConfig[];
entity_picture?: string;
styles: StylesConfig;
entity_picture_template?: string;
styles?: StylesConfig;
spin?: boolean;
label?: string;
label_template?: string;
@ -83,179 +79,3 @@ export interface StylesConfig {
export interface CssStyleConfig {
[key: string]: any;
}
export interface ToggleActionConfig {
action: 'toggle';
repeat?: number | undefined;
haptic?: HapticType;
}
export interface CallServiceActionConfig {
action: 'call-service';
haptic?: HapticType;
repeat?: number | undefined;
service: string;
service_data?: {
entity_id?: string | [string];
[key: string]: any;
};
}
export interface NavigateActionConfig {
action: 'navigate';
haptic?: HapticType;
repeat?: number | undefined;
navigation_path: string;
}
export interface MoreInfoActionConfig {
action: 'more-info';
repeat?: number | undefined;
haptic?: HapticType;
}
export interface NoActionConfig {
action: 'none';
repeat?: number | undefined;
}
export interface UrlActionConfig {
action: 'url';
haptic?: HapticType;
repeat?: number | undefined;
url: string;
}
export type ActionConfig =
| ToggleActionConfig
| CallServiceActionConfig
| NavigateActionConfig
| UrlActionConfig
| MoreInfoActionConfig
| NoActionConfig;
declare global {
// for fire event
interface HASSDomEvents {
'value-changed': {
value: unknown;
};
'config-changed': {
config: ButtonCardConfig;
};
'hass-more-info': {
entityId: string | null;
};
'll-rebuild': {};
undefined;
}
}
export type ValidHassDomEvent = keyof HASSDomEvents;
export type LocalizeFunc = (key: string, ...args: any[]) => string;
export interface Credential {
auth_provider_type: string;
auth_provider_id: string;
}
export interface MFAModule {
id: string;
name: string;
enabled: boolean;
}
export interface CurrentUser {
id: string;
is_owner: boolean;
name: string;
credentials: Credential[];
mfa_modules: MFAModule[];
}
export interface Theme {
// Incomplete
'primary-color': string;
'text-primary-color': string;
'accent-color': string;
}
export interface Themes {
default_theme: string;
themes: { [key: string]: Theme };
}
export interface Panel {
component_name: string;
config: { [key: string]: any } | null;
icon: string | null;
title: string | null;
url_path: string;
}
export interface Panels {
[name: string]: Panel;
}
export interface Resources {
[language: string]: { [key: string]: string };
}
export interface Translation {
nativeName: string;
isRTL: boolean;
fingerprints: { [fragment: string]: string };
}
export interface HomeAssistant {
auth: Auth;
connection: Connection;
connected: boolean;
states: HassEntities;
services: HassServices;
config: HassConfig;
themes: Themes;
selectedTheme?: string | null;
panels: Panels;
panelUrl: string;
// i18n
// current effective language, in that order:
// - backend saved user selected lanugage
// - language in local appstorage
// - browser language
// - english (en)
language: string;
// local stored language, keep that name for backward compability
selectedLanguage: string;
resources: Resources;
localize: LocalizeFunc;
translationMetadata: {
fragments: string[];
translations: {
[lang: string]: Translation;
};
};
dockedSidebar: boolean;
moreInfoEntityId: string;
user: CurrentUser;
callService: (
domain: string,
service: string,
serviceData?: { [key: string]: any }
) => Promise<void>;
callApi: <T>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
path: string,
parameters?: { [key: string]: any }
) => Promise<T>;
fetchWithAuth: (
path: string,
init?: { [key: string]: any }
) => Promise<Response>;
sendWS: (msg: MessageBase) => Promise<void>;
callWS: <T>(msg: MessageBase) => Promise<T>;
}