2.0.0 (#202)
* _template deprecation and custom fields support (#199) * Update custom-card-helpers * _template deprecated and custom_fields support * Update dependencies * Add npm audit fix to circleci * Styles support templating * *_action support templates * Some linting * Fixes #200 * Fixing template eval if input is a number * State values support templating * Documentation update * Doc formating issue * Bump major version
This commit is contained in:
parent
9ef93a2a35
commit
a9f27a64a2
|
@ -17,9 +17,7 @@ jobs:
|
|||
- restore_cache:
|
||||
keys:
|
||||
- deps-{{ .Environment.CACHE_VERSION }}-{{ checksum "package-lock.json" }}
|
||||
- deps-{{ .Environment.CACHE_VERSION }}-
|
||||
- run: rm -rf ~/repo/node_modules/custom-card-helpers/.git
|
||||
- run: npm install
|
||||
- run: npm install && npm audit fix
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
|
|
331
README.md
331
README.md
|
@ -27,6 +27,7 @@ Lovelace Button card for your entities.
|
|||
- [Easy styling options](#easy-styling-options)
|
||||
- [Light entity color variable](#light-entity-color-variable)
|
||||
- [ADVANCED styling options](#advanced-styling-options)
|
||||
- [Custom Fields](#custom-fields)
|
||||
- [Configuration Templates](#configuration-templates)
|
||||
- [General](#general)
|
||||
- [Merging state by id](#merging-state-by-id)
|
||||
|
@ -78,85 +79,83 @@ Lovelace Button card for your entities.
|
|||
|
||||
### Main Options
|
||||
|
||||
| Name | Type | Default | Supported options | Description |
|
||||
| Name | Type | Default | Supported options | Description |
|
||||
| -------------- | ----------- | ------------ | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `type` | string | **Required** | `custom:button-card` | Type of the card |
|
||||
| `type` | string | **Required** | `custom:button-card` | Type of the card |
|
||||
| `template` | string | optional | any valid template from `button_card_templates` | See [configuration template](#Configuration-Templates) |
|
||||
| `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). |
|
||||
| `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`. Supports templates, see [templates](#templates) |
|
||||
| `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` \| `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 |
|
||||
| `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. |
|
||||
| `name` | string | optional | `Air conditioner` | Define an optional text to show below the icon |
|
||||
| `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 |
|
||||
| `name` | string | optional | `Air conditioner` | Define an optional text to show below the icon. Supports templates, see [templates](#templates) |
|
||||
| `label` | string | optional | Any string that you want | Display a label below the card. See [Layouts](#layout) for more information. Supports templates, see [templates](#templates) |
|
||||
| `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 |
|
||||
| `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. |
|
||||
| `show_label` | boolean | `false` | `true` \| `false` | Display or hide the `label`/`label_template` |
|
||||
| `show_label` | boolean | `false` | `true` \| `false` | Display or hide the `label` |
|
||||
| `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` |
|
||||
| `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. Supports templates, see [templates](#templates) |
|
||||
| `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 |
|
||||
| `confirmation` | string | optional | Free-form text | Show a confirmation popup on tap with defined text |
|
||||
| `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 |
|
||||
| `confirmation` | string | optional | Free-form text | Show a confirmation popup on tap with defined text |
|
||||
| `lock` | boolean | `false` | `true` \| `false` | See [lock](#lock). This will display a normal button with a lock symbol in the corner. Clicking the button will make the lock go away and enable the button to be manoeuvred for five seconds |
|
||||
| `unlock_users` | string list | optional | A list of users | List of users allowed to unlock the button when `lock: true`. If not defined, everyone is allowed to unlock the button |
|
||||
| `layout` | string | optional | See [Layout](#Layout) | The layout of the button can be modified using this option |
|
||||
| `custom_fields` | object | optional | See [Custom Fields](#Custom-Fields) |
|
||||
|
||||
### Action
|
||||
|
||||
| Name | Type | Default | Supported options | Description |
|
||||
All the fields support templates, see [templates](#templates).
|
||||
|
||||
| Name | Type | Default | Supported options | Description |
|
||||
| ----------------- | ------ | -------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| `action` | string | `toggle` | `more-info`, `toggle`, `call-service`, `none`, `navigate`, `url` | Action to perform |
|
||||
| `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` |
|
||||
| `service_data` | object | none | Any service data | Service data to include (e.g. `entity_id: media_player.bedroom`) when `action` defined as `call-service`. If your `service_data` requires an `entity_id`, you can use the keywork `entity`, this will actually call the service on the entity defined in the main configuration of this card. Useful for [configuration templates](#configuration-templates) |
|
||||
| `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` |
|
||||
| `service_data` | object | none | Any service data | Service data to include (e.g. `entity_id: media_player.bedroom`) when `action` defined as `call-service`. If your `service_data` requires an `entity_id`, you can use the keywork `entity`, this will actually call the service on the entity defined in the main configuration of this card. Useful for [configuration templates](#configuration-templates) |
|
||||
| `haptic` | string | none | `success`, `warning`, `failure`, `light`, `medium`, `heavy`, `selection` | Haptic feedback for the [Beta IOS App](http://home-assistant.io/ios/beta) |
|
||||
| `repeat` | number | none | eg: `500` | For a hold_action, you can optionally configure the action to repeat while the button is being held down (for example, to repeatedly increase the volume of a media player). Define the number of milliseconds between repeat actions here. |
|
||||
|
||||
### State
|
||||
|
||||
| Name | Type | Default | Supported options | Description |
|
||||
| Name | Type | Default | Supported options | Description |
|
||||
| ---------- | ------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| `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` |
|
||||
| `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. Supports templates, see [templates](#templates) |
|
||||
| `icon` | string | optional | `mdi:battery` | The icon to display for this state. Defaults to the entity icon. Hide with `show_icon: false`. Supports templates, see [templates](#templates) |
|
||||
| `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. Supports templates, see [templates](#templates) |
|
||||
| `label` | string | optional | Any string that you want | Display a label below the card. See [Layouts](#layout) for more information. Supports templates, see [templates](#templates) |
|
||||
|
||||
### Available operators
|
||||
|
||||
The order of your elements in the `state` object matters. The first one which is `true` will match.
|
||||
The `value` field for all operators except `regex` support templating, see [templates](#templates)
|
||||
|
||||
| Operator | `value` example | Description |
|
||||
| Operator | `value` example | Description |
|
||||
| :-------: | --------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| `<` | `5` | Current state is inferior to `value` |
|
||||
| `<=` | `4` | Current state is inferior or equal to `value` |
|
||||
| `==` | `42` or `'on'` | **This is the default if no operator is specified.** Current state is equal (`==` javascript) to `value` |
|
||||
| `>=` | `32` | Current state is superior or equal to `value` |
|
||||
| `>` | `12` | Current state is superior to `value` |
|
||||
| `!=` | `'normal'` | Current state is not equal (`!=` javascript) to `value` |
|
||||
| `regex` | `'^norm.*$'` | `value` regex applied to current state does match |
|
||||
| `<` | `5` | Current state is inferior to `value` |
|
||||
| `<=` | `4` | Current state is inferior or equal to `value` |
|
||||
| `==` | `42` or `'on'` | **This is the default if no operator is specified.** Current state is equal (`==` javascript) to `value` |
|
||||
| `>=` | `32` | Current state is superior or equal to `value` |
|
||||
| `>` | `12` | Current state is superior to `value` |
|
||||
| `!=` | `'normal'` | Current state is not equal (`!=` javascript) to `value` |
|
||||
| `regex` | `'^norm.*$'` | `value` regex applied to current state does match |
|
||||
| `template` | | See [templates](#templates) for examples. `value` needs to be a javascript expression which returns a boolean. If the boolean is true, it will match this state |
|
||||
| `default` | N/A | If nothing matches, this is used |
|
||||
| `default` | N/A | If nothing matches, this is used |
|
||||
|
||||
### Layout
|
||||
|
||||
|
@ -178,22 +177,31 @@ Multiple values are possible, see the image below for examples:
|
|||
|
||||
### Templates
|
||||
|
||||
`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
|
||||
label_template: >
|
||||
return 'Connection: '
|
||||
+ (states['switch.connection'].state === 'on'
|
||||
? '<span style="color: #00FF00;">enabled</span>'
|
||||
: '<span style="color: #FF0000;">disabled</span>')
|
||||
+ ' / '
|
||||
+ (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`)
|
||||
The template rendering uses a special format. All the fields where template is supported also support plain text. To activate the templating feature for such a field, you'll need to enclose the javascript function inside 3 square brakets:
|
||||
`[[[ javascript function here ]]]`
|
||||
|
||||
Don't forget to quote if it's on one line:
|
||||
```yaml
|
||||
name: '[[[ if (entity.state > 42) return "Above 42"; else return "Below 42" ]]]'
|
||||
name: >
|
||||
[[[
|
||||
if (entity.state > 42)
|
||||
return "Above 42;
|
||||
else
|
||||
return "Below 42;
|
||||
]]]
|
||||
```
|
||||
|
||||
Those are the configuration fields which support templating:
|
||||
* `name` (Supports also HTML rendering): This needs to return a string
|
||||
* `label` (Supports also HTML rendering): This needs to return a string
|
||||
* `entity_picture`: This needs to return a path to a file or a url as a string.
|
||||
* `icon`: This needs to return a string in the format `mdi:icon`
|
||||
* All the styles in the style object: This needs to return a string
|
||||
* All the value of the state object, appart when the operator is `regex`
|
||||
* `operator: template`: The function for `value` needs to return a boolean
|
||||
* Else: The function for `value` needs to return a string or a number
|
||||
* All the `custom_fields` (Support also HTML rendering)
|
||||
|
||||
Inside the javascript code, you'll have access to those variables:
|
||||
* `entity`: The current entity object, if the entity is defined in the card
|
||||
|
@ -201,23 +209,12 @@ Inside the javascript code, you'll have access to those variables:
|
|||
* `user`: The user object (equivalent to `hass.user`)
|
||||
* `hass`: The complete `hass` object
|
||||
|
||||
The value shouldn't be enclosed in quotes:
|
||||
```yaml
|
||||
label_template: >
|
||||
return states['light.mylight'].attributes.brightness
|
||||
```
|
||||
or
|
||||
```yaml
|
||||
state:
|
||||
- operator: template
|
||||
value: >
|
||||
return states['input_select.light_mode'].state === 'night_mode'
|
||||
```
|
||||
|
||||
See [here](#templates-support) for some examples.
|
||||
See [here](#templates-support) for some examples or [here](#custom-fields) for some crazy advanced stuff using templates!
|
||||
|
||||
### Styles
|
||||
|
||||
All the styles entries, support Templating, see [here](#custom-fields) for some examples.
|
||||
|
||||
#### Easy styling options
|
||||
|
||||
For each element in the card, styles can be defined in 2 places:
|
||||
|
@ -372,6 +369,149 @@ Some examples:
|
|||
- filter: grayscale(100%)
|
||||
```
|
||||
|
||||
### Custom Fields
|
||||
|
||||
Custom fields support, using the `custom_fields` object, enables you to create your own fields on top of the pre-defined ones (name, state, label and icon).
|
||||
This is an advanced feature which leverages (if you require it) the CSS Grid.
|
||||
|
||||
Each custom field supports its own styling config, the name needs to match between both objects needs to match:
|
||||
```yaml
|
||||
- type: custom:button-card
|
||||
[...]
|
||||
custom_element:
|
||||
test_element: My test element
|
||||
styles:
|
||||
custom_fields:
|
||||
test_element:
|
||||
- color: red
|
||||
- font-size: 13px
|
||||
```
|
||||
|
||||
Examples are better than a long text, so here you go:
|
||||
* Placing an element wherever you want (that means bypassing the grid). Set the grid to `position: relative` and set the element to `position: absolute`
|
||||
|
||||
![custom_fields_1](examples/custom_fields_1.gif)
|
||||
|
||||
```yaml
|
||||
- type: custom:button-card
|
||||
icon: mdi:lightbulb
|
||||
aspect_ratio: 1/1
|
||||
name: Nb lights on
|
||||
styles:
|
||||
grid:
|
||||
- position: relative
|
||||
custom_fields:
|
||||
notification:
|
||||
- background-color: >
|
||||
[[[
|
||||
if (states['input_number.test'].state == 0)
|
||||
return "green";
|
||||
return "red";
|
||||
]]]
|
||||
- border-radius: 50%
|
||||
- position: absolute
|
||||
- left: 60%
|
||||
- top: 10%
|
||||
- height: 20px
|
||||
- width: 20px
|
||||
- font-size: 8px
|
||||
- line-height: 20px
|
||||
custom_fields:
|
||||
notification: >
|
||||
[[[ return Math.floor(states['input_number.test'].state / 10) ]]]
|
||||
```
|
||||
* Or you can use the grid. Each element will have it's name positioned as the `grid-area`:
|
||||
|
||||
![custom_fields_2](examples/custom_fields_2.png)
|
||||
|
||||
```yaml
|
||||
- type: custom:button-card
|
||||
entity: 'sensor.raspi_temp'
|
||||
icon: 'mdi:raspberry-pi'
|
||||
aspect_ratio: 1/1
|
||||
name: HassOS
|
||||
styles:
|
||||
card:
|
||||
- background-color: '#000044'
|
||||
- border-radius: 10%
|
||||
- padding: 10%
|
||||
- color: ivory
|
||||
- font-size: 10px
|
||||
- text-shadow: 0px 0px 5px black
|
||||
- text-transform: capitalize
|
||||
grid:
|
||||
- grid-template-areas: '"i temp" "n n" "cpu cpu" "ram ram" "sd sd"'
|
||||
- grid-template-columns: 1fr 1fr
|
||||
- grid-template-rows: 1fr min-content min-content min-content min-content
|
||||
name:
|
||||
- font-weight: bold
|
||||
- font-size: 13px
|
||||
- color: white
|
||||
- align-self: middle
|
||||
- justify-self: start
|
||||
- padding-bottom: 4px
|
||||
img_cell:
|
||||
- justify-content: start
|
||||
- align-items: start
|
||||
- margin: none
|
||||
icon:
|
||||
- color: >
|
||||
[[[
|
||||
if (entity.state < 60) return 'lime';
|
||||
if (entity.state >= 60 && entity.state < 80) return 'orange';
|
||||
else return 'red';
|
||||
]]]
|
||||
- width: 70%
|
||||
- margin-top: -10%
|
||||
custom_fields:
|
||||
temp:
|
||||
- align-self: start
|
||||
- justify-self: end
|
||||
cpu:
|
||||
- padding-bottom: 2px
|
||||
- align-self: middle
|
||||
- justify-self: start
|
||||
- --text-color-sensor: '[[[ if (states["sensor.raspi_cpu"].state > 80) return "red"; ]]]'
|
||||
ram:
|
||||
- padding-bottom: 2px
|
||||
- align-self: middle
|
||||
- justify-self: start
|
||||
- --text-color-sensor: '[[[ if (states["sensor.raspi_ram"].state > 80) return "red"; ]]]'
|
||||
sd:
|
||||
- align-self: middle
|
||||
- justify-self: start
|
||||
- --text-color-sensor: '[[[ if (states["sensor.raspi_sd"].state > 80) return "red"; ]]]'
|
||||
custom_fields:
|
||||
temp: >
|
||||
[[[
|
||||
return `<ha-icon
|
||||
icon="mdi:thermometer"
|
||||
style="width: 12px; height: 12px; color: yellow;">
|
||||
</ha-icon><span>${entity.state}°C</span>`
|
||||
]]]
|
||||
cpu: >
|
||||
[[[
|
||||
return `<ha-icon
|
||||
icon="mdi:server"
|
||||
style="width: 12px; height: 12px; color: deepskyblue;">
|
||||
</ha-icon><span>CPU: <span style="color: var(--text-color-sensor);">${states['sensor.raspi_cpu'].state}%</span></span>`
|
||||
]]]
|
||||
ram: >
|
||||
[[[
|
||||
return `<ha-icon
|
||||
icon="mdi:memory"
|
||||
style="width: 12px; height: 12px; color: deepskyblue;">
|
||||
</ha-icon><span>RAM: <span style="color: var(--text-color-sensor);">${states['sensor.raspi_ram'].state}%</span></span>`
|
||||
]]]
|
||||
sd: >
|
||||
[[[
|
||||
return `<ha-icon
|
||||
icon="mdi:harddisk"
|
||||
style="width: 12px; height: 12px; color: deepskyblue;">
|
||||
</ha-icon><span>SD: <span style="color: var(--text-color-sensor);">${states['sensor.raspi_sd'].state}%</span></span>`
|
||||
]]]
|
||||
```
|
||||
|
||||
### Configuration Templates
|
||||
|
||||
#### General
|
||||
|
@ -843,9 +983,11 @@ If you specify a width for the card, it has to be in `px`. All the cards without
|
|||
- type: "custom:button-card"
|
||||
color_type: icon
|
||||
entity: light.test_light
|
||||
label_template: >
|
||||
var bri = states['light.test_light'].attributes.brightness;
|
||||
return 'Brightness: ' + (bri ? bri : '0') + '%';
|
||||
label: >
|
||||
[[[
|
||||
var bri = states['light.test_light'].attributes.brightness;
|
||||
return 'Brightness: ' + (bri ? bri : '0') + '%';
|
||||
]]]
|
||||
show_label: true
|
||||
size: 15%
|
||||
styles:
|
||||
|
@ -855,8 +997,10 @@ If you specify a width for the card, it has to be in `px`. All the cards without
|
|||
color_type: icon
|
||||
entity: light.test_light
|
||||
layout: icon_label
|
||||
label_template: >
|
||||
return 'Other State: ' + states['switch.skylight'].state;
|
||||
label: >
|
||||
[[[
|
||||
return 'Other State: ' + states['switch.skylight'].state;
|
||||
]]]
|
||||
show_label: true
|
||||
show_name: false
|
||||
styles:
|
||||
|
@ -878,8 +1022,10 @@ Example with `template`:
|
|||
state:
|
||||
- operator: template
|
||||
value: >
|
||||
return states['light.test_light'].attributes
|
||||
&& (states['light.test_light'].attributes.brightness <= 100)
|
||||
[[[
|
||||
return states['light.test_light'].attributes
|
||||
&& (states['light.test_light'].attributes.brightness <= 100)
|
||||
]]]
|
||||
icon: mdi:alert
|
||||
- operator: default
|
||||
icon: mdi:lightbulb
|
||||
|
@ -890,7 +1036,7 @@ Example with `template`:
|
|||
state:
|
||||
- operator: template
|
||||
value: >
|
||||
return states['input_select.light_mode'].state === 'night_mode'
|
||||
[[[ return states['input_select.light_mode'].state === 'night_mode' ]]]
|
||||
icon: mdi:weather-night
|
||||
label: Night Mode
|
||||
- operator: default
|
||||
|
@ -906,9 +1052,11 @@ Example with `template`:
|
|||
- type: "custom:button-card"
|
||||
color_type: icon
|
||||
entity: light.test_light
|
||||
label_template: >
|
||||
var bri = states['light.test_light'].attributes.brightness;
|
||||
return 'Brightness: ' + (bri ? bri : '0') + '%';
|
||||
label: >
|
||||
[[[
|
||||
var bri = states['light.test_light'].attributes.brightness;
|
||||
return 'Brightness: ' + (bri ? bri : '0') + '%';
|
||||
]]]
|
||||
show_label: true
|
||||
show_state: true
|
||||
size: 10%
|
||||
|
@ -945,8 +1093,8 @@ Example with `template`:
|
|||
color_type: icon
|
||||
entity: light.test_light
|
||||
layout: icon_label
|
||||
label_template: >
|
||||
return 'Other State: ' + states['switch.skylight'].state;
|
||||
label: >
|
||||
[[[ return 'Other State: ' + states['switch.skylight'].state; ]]]
|
||||
show_label: true
|
||||
show_name: false
|
||||
size: 100%
|
||||
|
@ -1032,7 +1180,8 @@ Example with `template`:
|
|||
|
||||
## Credits
|
||||
|
||||
- [ciotlosm](https://github.com/ciotlosm) for the readme template and the awesome examples
|
||||
- [ciotlosm](https://github.com/ciotlosm) for the readme template and some awesome examples
|
||||
- [iantrich](https://github.com/iantrich), [LbDab](https://github.com/lbdab) and [jimz011](https://github.com/jimz011) for the inspiration and the awesome templates and cards you've created.
|
||||
|
||||
[commits-shield]: https://img.shields.io/github/commit-activity/y/custom-cards/button-card.svg?style=for-the-badge
|
||||
[commits]: https://github.com/custom-cards/button-card/commits/master
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "button-card",
|
||||
"version": "1.11.1",
|
||||
"version": "2.0.0",
|
||||
"description": "Button card for lovelace",
|
||||
"main": "dist/button-card.js",
|
||||
"pre-commit": [
|
||||
|
@ -32,33 +32,33 @@
|
|||
},
|
||||
"homepage": "https://github.com/custom-cards/button-card#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.4.4",
|
||||
"@babel/core": "^7.5.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
||||
"@babel/plugin-proposal-decorators": "^7.4.4",
|
||||
"@typescript-eslint/eslint-plugin": "^1.9.0",
|
||||
"@typescript-eslint/parser": "^1.9.0",
|
||||
"@typescript-eslint/eslint-plugin": "^1.12.0",
|
||||
"@typescript-eslint/parser": "^1.12.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",
|
||||
"eslint-config-airbnb-base": "^13.2.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"npm": "^6.10.1",
|
||||
"pre-commit": "^1.2.2",
|
||||
"prettier": "^1.17.1",
|
||||
"rollup": "^1.12.2",
|
||||
"rollup-plugin-babel": "^4.3.2",
|
||||
"prettier": "^1.18.2",
|
||||
"rollup": "^1.17.0",
|
||||
"rollup-plugin-babel": "^4.3.3",
|
||||
"rollup-plugin-json": "^4.0.0",
|
||||
"rollup-plugin-node-resolve": "^4.2.4",
|
||||
"rollup-plugin-terser": "^4.0.4",
|
||||
"rollup-plugin-typescript2": "^0.20.1",
|
||||
"ts-lit-plugin": "^1.0.6",
|
||||
"typescript": "^3.4.4",
|
||||
"typescript": "^3.5.3",
|
||||
"typescript-styled-plugin": "^0.14.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^2.4.0",
|
||||
"custom-card-helpers": "github:custom-cards/custom-card-helpers#fix-service-call",
|
||||
"@ctrl/tinycolor": "^2.5.3",
|
||||
"custom-card-helpers": "^1.2.2",
|
||||
"home-assistant-js-websocket": "^3.4.0",
|
||||
"lit-element": "^2.1.0",
|
||||
"lit-html": "^1.0.0"
|
||||
"lit-element": "^2.2.0",
|
||||
"lit-html": "^1.1.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,10 @@ 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 { classMap, ClassInfo } from 'lit-html/directives/class-map';
|
||||
import {
|
||||
HassEntity,
|
||||
} from 'home-assistant-js-websocket';
|
||||
import {
|
||||
ButtonCardConfig,
|
||||
StateConfig,
|
||||
} from './types';
|
||||
import {
|
||||
domainIcon,
|
||||
HomeAssistant,
|
||||
|
@ -25,10 +21,14 @@ import {
|
|||
getLovelace,
|
||||
timerTimeRemaining,
|
||||
secondsToDuration,
|
||||
durationToSeconds
|
||||
durationToSeconds,
|
||||
// Still not working...
|
||||
// longPress,
|
||||
} from 'custom-card-helpers';
|
||||
import {
|
||||
ButtonCardConfig,
|
||||
StateConfig,
|
||||
} from './types';
|
||||
import { longPress } from './long-press';
|
||||
import {
|
||||
computeDomain,
|
||||
|
@ -52,6 +52,8 @@ class ButtonCard extends LitElement {
|
|||
|
||||
@property() private _timeRemaining?: number;
|
||||
|
||||
@property() private _hasTemplate?: boolean;
|
||||
|
||||
private _interval?: number;
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
|
@ -62,9 +64,9 @@ class ButtonCard extends LitElement {
|
|||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (
|
||||
this.config &&
|
||||
this.config.entity &&
|
||||
computeDomain(this.config.entity) === 'timer'
|
||||
this.config
|
||||
&& this.config.entity
|
||||
&& computeDomain(this.config.entity) === 'timer'
|
||||
) {
|
||||
const stateObj = this.hass!.states[this.config.entity];
|
||||
this._startInterval(stateObj);
|
||||
|
@ -85,16 +87,7 @@ class ButtonCard extends LitElement {
|
|||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
const state = this.config!.entity ? this.hass!.states[this.config!.entity] : undefined;
|
||||
const configState = this._getMatchingConfigState(state);
|
||||
const forceUpdate = (
|
||||
configState && (
|
||||
this.config!.show_label && configState.label_template
|
||||
|| this.config!.show_entity_picture && configState.entity_picture_template
|
||||
|| this.config!.show_name && configState.name_template
|
||||
)
|
||||
|| this.config!.show_label && this.config!.label_template
|
||||
|| this.config!.show_name && this.config!.name_template
|
||||
|| this.config!.show_entity_picture && this.config!.entity_picture_template
|
||||
)
|
||||
const forceUpdate = this._hasTemplate
|
||||
|| this.config!.state
|
||||
&& this.config!.state.find(elt => elt.operator === 'template')
|
||||
|| changedProps.has('_timeRemaining')
|
||||
|
@ -106,10 +99,10 @@ class ButtonCard extends LitElement {
|
|||
super.updated(changedProps);
|
||||
|
||||
if (
|
||||
this.config &&
|
||||
this.config.entity &&
|
||||
computeDomain(this.config.entity) === 'timer' &&
|
||||
changedProps.has('hass')
|
||||
this.config
|
||||
&& this.config.entity
|
||||
&& computeDomain(this.config.entity) === 'timer'
|
||||
&& changedProps.has('hass')
|
||||
) {
|
||||
const stateObj = this.hass!.states[this.config.entity];
|
||||
const oldHass = changedProps.get('hass') as this['hass'];
|
||||
|
@ -139,7 +132,7 @@ class ButtonCard extends LitElement {
|
|||
if (stateObj.state === 'active') {
|
||||
this._interval = window.setInterval(
|
||||
() => this._calculateRemaining(stateObj),
|
||||
1000
|
||||
1000,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +147,7 @@ class ButtonCard extends LitElement {
|
|||
}
|
||||
|
||||
return secondsToDuration(
|
||||
this._timeRemaining || durationToSeconds(stateObj.attributes['duration'])
|
||||
this._timeRemaining || durationToSeconds(stateObj.attributes.duration),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -174,24 +167,25 @@ class ButtonCard extends LitElement {
|
|||
switch (elt.operator) {
|
||||
case '==':
|
||||
/* eslint eqeqeq: 0 */
|
||||
return (state && state.state == elt.value);
|
||||
return (state && state.state == this._getTemplateOrString(state, elt.value));
|
||||
case '<=':
|
||||
return (state && state.state <= elt.value);
|
||||
return (state && state.state <= this._getTemplateOrString(state, elt.value));
|
||||
case '<':
|
||||
return (state && state.state < elt.value);
|
||||
return (state && state.state < this._getTemplateOrString(state, elt.value));
|
||||
case '>=':
|
||||
return (state && state.state >= elt.value);
|
||||
return (state && state.state >= this._getTemplateOrString(state, elt.value));
|
||||
case '>':
|
||||
return (state && state.state > elt.value);
|
||||
return (state && state.state > this._getTemplateOrString(state, elt.value));
|
||||
case '!=':
|
||||
return (state && state.state != elt.value);
|
||||
return (state && state.state != this._getTemplateOrString(state, elt.value));
|
||||
case 'regex': {
|
||||
/* eslint no-unneeded-ternary: 0 */
|
||||
const matches = state && state.state.match(elt.value) ? true : false;
|
||||
const matches = state
|
||||
&& state.state.match(this._getTemplateOrString(state, elt.value)) ? true : false;
|
||||
return matches;
|
||||
}
|
||||
case 'template': {
|
||||
return this._evalTemplate(state, elt.value);
|
||||
return this._getTemplateOrString(state, elt.value);
|
||||
}
|
||||
case 'default':
|
||||
def = elt;
|
||||
|
@ -200,7 +194,7 @@ class ButtonCard extends LitElement {
|
|||
return false;
|
||||
}
|
||||
} else {
|
||||
return state && (elt.value == state.state);
|
||||
return state && (this._getTemplateOrString(state, elt.value) == state.state);
|
||||
}
|
||||
});
|
||||
if (!retval && def) {
|
||||
|
@ -210,11 +204,29 @@ class ButtonCard extends LitElement {
|
|||
}
|
||||
|
||||
private _evalTemplate(state: HassEntity | undefined, func: any): any {
|
||||
/* eslint no-new-func: 0 */
|
||||
return new Function('states', 'entity', 'user', 'hass',
|
||||
`'use strict'; ${func}`)
|
||||
.call(this, this.hass!.states, state, this.hass!.user, this.hass);
|
||||
}
|
||||
|
||||
private _getTemplateOrString(
|
||||
state: HassEntity | undefined,
|
||||
value: any | undefined,
|
||||
): any | undefined {
|
||||
if (!value) return undefined;
|
||||
if (typeof value === 'number') return value;
|
||||
const trimmed = value.trim();
|
||||
if (
|
||||
trimmed.substring(0, 3) === '[[['
|
||||
&& trimmed.slice(-3) === ']]]'
|
||||
) {
|
||||
return this._evalTemplate(state, trimmed.slice(3, -3));
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private _getDefaultColorForState(state: HassEntity): string {
|
||||
switch (state.state) {
|
||||
case 'on':
|
||||
|
@ -300,7 +312,7 @@ class ButtonCard extends LitElement {
|
|||
? state.attributes.icon
|
||||
: domainIcon(computeDomain(state.entity_id), state.state);
|
||||
}
|
||||
return icon;
|
||||
return this._getTemplateOrString(state, icon);
|
||||
}
|
||||
|
||||
private _buildEntityPicture(
|
||||
|
@ -311,33 +323,24 @@ class ButtonCard extends LitElement {
|
|||
return undefined;
|
||||
}
|
||||
let entityPicture: string | undefined;
|
||||
let matchingEntityPictureTemplate: string | undefined;
|
||||
|
||||
if (configState && configState.entity_picture_template) {
|
||||
matchingEntityPictureTemplate = configState.entity_picture_template;
|
||||
} else {
|
||||
matchingEntityPictureTemplate = this.config!.entity_picture_template;
|
||||
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;
|
||||
}
|
||||
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);
|
||||
return this._getTemplateOrString(state, entityPicture);
|
||||
}
|
||||
|
||||
private _buildStyleGeneric(
|
||||
state: HassEntity | undefined,
|
||||
configState: StateConfig | undefined,
|
||||
styleType: string,
|
||||
): StyleInfo {
|
||||
let style: StyleInfo = {};
|
||||
let style: any = {};
|
||||
if (this.config!.styles && this.config!.styles[styleType]) {
|
||||
style = Object.assign(style, ...this.config!.styles[styleType]);
|
||||
}
|
||||
|
@ -349,6 +352,41 @@ class ButtonCard extends LitElement {
|
|||
...configStateStyle,
|
||||
};
|
||||
}
|
||||
Object.keys(style).forEach((key) => {
|
||||
style[key] = this._getTemplateOrString(state, style[key]);
|
||||
});
|
||||
return style;
|
||||
}
|
||||
|
||||
private _buildCustomStyleGeneric(
|
||||
state: HassEntity | undefined,
|
||||
configState: StateConfig | undefined,
|
||||
styleType: string,
|
||||
): StyleInfo {
|
||||
let style: any = {};
|
||||
if (this.config!.styles
|
||||
&& this.config!.styles.custom_fields
|
||||
&& this.config!.styles.custom_fields[styleType]
|
||||
) {
|
||||
style = Object.assign(style, ...this.config!.styles.custom_fields[styleType]);
|
||||
}
|
||||
if (configState && configState.styles
|
||||
&& configState.styles.custom_fields
|
||||
&& configState.styles.custom_fields[styleType]
|
||||
) {
|
||||
let configStateStyle: StyleInfo = {};
|
||||
configStateStyle = Object.assign(
|
||||
configStateStyle,
|
||||
...configState.styles.custom_fields[styleType],
|
||||
);
|
||||
style = {
|
||||
...style,
|
||||
...configStateStyle,
|
||||
};
|
||||
}
|
||||
Object.keys(style).forEach((key) => {
|
||||
style[key] = this._getTemplateOrString(state, style[key]);
|
||||
});
|
||||
return style;
|
||||
}
|
||||
|
||||
|
@ -359,26 +397,17 @@ class ButtonCard extends LitElement {
|
|||
return undefined;
|
||||
}
|
||||
let name: string | undefined;
|
||||
let matchingNameTemplate: string | undefined;
|
||||
|
||||
if (configState && configState.name_template) {
|
||||
matchingNameTemplate = configState.name_template;
|
||||
} else {
|
||||
matchingNameTemplate = this.config!.name_template;
|
||||
}
|
||||
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;
|
||||
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 this._evalTemplate(state, matchingNameTemplate);
|
||||
return this._getTemplateOrString(state, name);
|
||||
}
|
||||
|
||||
private _buildStateString(state: HassEntity | undefined): string | undefined {
|
||||
|
@ -394,7 +423,7 @@ class ButtonCard extends LitElement {
|
|||
} else {
|
||||
stateString = this._computeTimeDisplay(state);
|
||||
if (state.state === 'paused') {
|
||||
stateString += ` (${localizedState})`
|
||||
stateString += ` (${localizedState})`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -422,8 +451,8 @@ class ButtonCard extends LitElement {
|
|||
state: HassEntity | undefined,
|
||||
style: StyleInfo,
|
||||
): TemplateResult | undefined {
|
||||
return this.config!.show_last_changed && state ?
|
||||
html`
|
||||
return this.config!.show_last_changed && state
|
||||
? html`
|
||||
<ha-relative-time
|
||||
id="label"
|
||||
class="ellipsis"
|
||||
|
@ -441,23 +470,45 @@ class ButtonCard extends LitElement {
|
|||
return undefined;
|
||||
}
|
||||
let label: string | undefined;
|
||||
let matchingLabelTemplate: string | undefined;
|
||||
|
||||
if (configState && configState.label_template) {
|
||||
matchingLabelTemplate = configState.label_template;
|
||||
if (configState && configState.label) {
|
||||
label = configState.label;
|
||||
} else {
|
||||
matchingLabelTemplate = this.config!.label_template;
|
||||
}
|
||||
if (!matchingLabelTemplate) {
|
||||
if (configState && configState.label) {
|
||||
label = configState.label;
|
||||
} else {
|
||||
label = this.config!.label;
|
||||
}
|
||||
return label;
|
||||
label = this.config!.label;
|
||||
}
|
||||
|
||||
return this._evalTemplate(state, matchingLabelTemplate);
|
||||
return this._getTemplateOrString(state, label);
|
||||
}
|
||||
|
||||
private _buildCustomFields(
|
||||
state: HassEntity | undefined,
|
||||
configState: StateConfig | undefined,
|
||||
): TemplateResult {
|
||||
let result = html``;
|
||||
const fields: any = {};
|
||||
if (this.config!.custom_fields) {
|
||||
Object.keys(this.config!.custom_fields).forEach((key) => {
|
||||
const value = this.config!.custom_fields![key];
|
||||
fields[key] = this._getTemplateOrString(state, value);
|
||||
});
|
||||
}
|
||||
if (configState && configState.custom_fields) {
|
||||
Object.keys(configState.custom_fields).forEach((key) => {
|
||||
const value = configState!.custom_fields![key];
|
||||
fields[key] = this._getTemplateOrString(state, value);
|
||||
});
|
||||
}
|
||||
Object.keys(fields).forEach((key) => {
|
||||
if (fields[key] != undefined) {
|
||||
const customStyle: StyleInfo = {
|
||||
...this._buildCustomStyleGeneric(state, configState, key),
|
||||
'grid-area': key,
|
||||
};
|
||||
result = html`${result}
|
||||
<div id=${key} class="ellipsis" style=${styleMap(customStyle)}>${unsafeHTML(fields[key])}</div>`;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private _isClickable(state: HassEntity | undefined): boolean {
|
||||
|
@ -525,15 +576,15 @@ class ButtonCard extends LitElement {
|
|||
const configState = this._getMatchingConfigState(state);
|
||||
const color = this._buildCssColorAttribute(state, configState);
|
||||
let buttonColor = color;
|
||||
let cardStyle: StyleInfo = {};
|
||||
let lockStyle: StyleInfo = {};
|
||||
let aspectRatio: StyleInfo = {};
|
||||
const lockStyleFromConfig = this._buildStyleGeneric(configState, 'lock');
|
||||
const configCardStyle = this._buildStyleGeneric(configState, 'card');
|
||||
let cardStyle: any = {};
|
||||
let lockStyle: any = {};
|
||||
const aspectRatio: any = {};
|
||||
const lockStyleFromConfig = this._buildStyleGeneric(state, configState, 'lock');
|
||||
const configCardStyle = this._buildStyleGeneric(state, 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');
|
||||
|
@ -629,11 +680,11 @@ class ButtonCard extends LitElement {
|
|||
const iconTemplate = this._getIconHtml(state, configState, color);
|
||||
const itemClass: string[] = [containerClass];
|
||||
const label = this._buildLabel(state, configState);
|
||||
const nameStyleFromConfig = this._buildStyleGeneric(configState, 'name');
|
||||
const stateStyleFromConfig = this._buildStyleGeneric(configState, 'state');
|
||||
const labelStyleFromConfig = this._buildStyleGeneric(configState, 'label');
|
||||
const nameStyleFromConfig = this._buildStyleGeneric(state, configState, 'name');
|
||||
const stateStyleFromConfig = this._buildStyleGeneric(state, configState, 'state');
|
||||
const labelStyleFromConfig = this._buildStyleGeneric(state, configState, 'label');
|
||||
const lastChangedTemplate = this._buildLastChanged(state, labelStyleFromConfig);
|
||||
const gridStyleFromConfig = this._buildStyleGeneric(configState, 'grid');
|
||||
const gridStyleFromConfig = this._buildStyleGeneric(state, configState, 'grid');
|
||||
if (!iconTemplate) itemClass.push('no-icon');
|
||||
if (!name) itemClass.push('no-name');
|
||||
if (!stateString) itemClass.push('no-state');
|
||||
|
@ -646,6 +697,7 @@ class ButtonCard extends LitElement {
|
|||
${stateString ? html`<div id="state" class="ellipsis" style=${styleMap(stateStyleFromConfig)}>${stateString}</div>` : ''}
|
||||
${label && !lastChangedTemplate ? html`<div id="label" class="ellipsis" style=${styleMap(labelStyleFromConfig)}>${unsafeHTML(label)}</div>` : ''}
|
||||
${lastChangedTemplate ? lastChangedTemplate : ''}
|
||||
${this._buildCustomFields(state, configState)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -657,10 +709,10 @@ class ButtonCard extends LitElement {
|
|||
): TemplateResult | undefined {
|
||||
const icon = this._buildIcon(state, configState);
|
||||
const entityPicture = this._buildEntityPicture(state, configState);
|
||||
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 entityPictureStyleFromConfig = this._buildStyleGeneric(state, configState, 'entity_picture');
|
||||
const haIconStyleFromConfig = this._buildStyleGeneric(state, configState, 'icon');
|
||||
const imgCellStyleFromConfig = this._buildStyleGeneric(state, configState, 'img_cell');
|
||||
const haCardStyleFromConfig = this._buildStyleGeneric(state, configState, 'card');
|
||||
|
||||
const haIconStyle: StyleInfo = {
|
||||
color,
|
||||
|
@ -698,7 +750,10 @@ class ButtonCard extends LitElement {
|
|||
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);
|
||||
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;
|
||||
|
@ -724,6 +779,10 @@ class ButtonCard extends LitElement {
|
|||
this.config!.color_off = 'var(--paper-item-icon-color)';
|
||||
}
|
||||
this.config!.color_on = 'var(--paper-item-icon-active-color)';
|
||||
|
||||
const jsonConfig = JSON.stringify(this.config);
|
||||
const rxp = new RegExp('\\[\\[\\[.*\\]\\]\\]', 'gm');
|
||||
this._hasTemplate = jsonConfig.match(rxp) ? true : false;
|
||||
}
|
||||
|
||||
// The height of your card. Home Assistant uses this to automatically
|
||||
|
@ -732,6 +791,25 @@ class ButtonCard extends LitElement {
|
|||
return 3;
|
||||
}
|
||||
|
||||
private _evalActions(config: ButtonCardConfig, action: string): ButtonCardConfig {
|
||||
const state = this.config!.entity ? this.hass!.states[this.config!.entity] : undefined;
|
||||
const configDuplicate = JSON.parse(JSON.stringify(config));
|
||||
Object.keys(configDuplicate![action]).forEach((key) => {
|
||||
if (key === 'service_data') {
|
||||
Object.keys(configDuplicate![action].service_data).forEach((sdKey) => {
|
||||
configDuplicate![action].service_data[sdKey] = this._getTemplateOrString(
|
||||
state, configDuplicate![action].service_data[sdKey],
|
||||
);
|
||||
});
|
||||
} else {
|
||||
configDuplicate![action][key] = this._getTemplateOrString(
|
||||
state, configDuplicate![action][key],
|
||||
);
|
||||
}
|
||||
});
|
||||
return configDuplicate;
|
||||
}
|
||||
|
||||
private _handleTap(ev): void {
|
||||
/* eslint no-alert: 0 */
|
||||
if (this.config!.confirmation
|
||||
|
@ -739,7 +817,7 @@ class ButtonCard extends LitElement {
|
|||
return;
|
||||
}
|
||||
const config = ev.target.config;
|
||||
handleClick(this, this.hass!, config, false, false);
|
||||
handleClick(this, this.hass!, this._evalActions(config, 'tap_action'), false, false);
|
||||
}
|
||||
|
||||
private _handleHold(ev): void {
|
||||
|
@ -749,7 +827,7 @@ class ButtonCard extends LitElement {
|
|||
return;
|
||||
}
|
||||
const config = ev.target.config;
|
||||
handleClick(this, this.hass!, config, true, false);
|
||||
handleClick(this, this.hass!, this._evalActions(config, 'hold_action'), true, false);
|
||||
}
|
||||
|
||||
private _handleDblTap(ev): void {
|
||||
|
@ -759,7 +837,7 @@ class ButtonCard extends LitElement {
|
|||
return;
|
||||
}
|
||||
const config = ev.target.config;
|
||||
handleClick(this, this.hass!, config, false, true);
|
||||
handleClick(this, this.hass!, this._evalActions(config, 'dbltap_action'), false, true);
|
||||
}
|
||||
|
||||
private _handleLock(ev): void {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { HassEntity } from 'home-assistant-js-websocket';
|
||||
import { computeDomain } from './helpers';
|
||||
import { LocalizeFunc } from 'custom-card-helpers';
|
||||
import { computeDomain } from './helpers';
|
||||
|
||||
export default (
|
||||
localize: LocalizeFunc,
|
||||
|
|
|
@ -139,13 +139,20 @@ export function mergeStatesById(
|
|||
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)));
|
||||
/* eslint eqeqeq: 0 no-confusing-arrow: 0 */
|
||||
resultStateConfigs = resultStateConfigs.concat(
|
||||
fromStates.filter(
|
||||
x => !intoStates
|
||||
? true
|
||||
: !intoStates.find(y => y.id && x.id ? y.id == x.id : false),
|
||||
),
|
||||
);
|
||||
}
|
||||
return resultStateConfigs;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ class LongPress extends HTMLElement implements LongPress {
|
|||
});
|
||||
|
||||
const clickStart = (ev: Event) => {
|
||||
ev.stopPropagation();
|
||||
if (this.cooldownStart) {
|
||||
return;
|
||||
}
|
||||
|
@ -134,6 +135,7 @@ class LongPress extends HTMLElement implements LongPress {
|
|||
};
|
||||
|
||||
const clickEnd = (ev: Event) => {
|
||||
ev.stopPropagation();
|
||||
if (
|
||||
this.cooldownEnd
|
||||
|| (['touchend', 'touchcancel'].includes(ev.type)
|
||||
|
|
18
src/types.ts
18
src/types.ts
|
@ -5,7 +5,6 @@ export interface ButtonCardConfig {
|
|||
type: string;
|
||||
entity?: string;
|
||||
name?: string;
|
||||
name_template?: string;
|
||||
icon?: string;
|
||||
color_type: 'icon' | 'card' | 'label-card' | 'blank-card'
|
||||
color?: 'auto' | 'auto-no-temperature' | string;
|
||||
|
@ -24,19 +23,17 @@ export interface ButtonCardConfig {
|
|||
show_last_changed?: boolean;
|
||||
show_label?: boolean;
|
||||
label?: string;
|
||||
label_template?: string;
|
||||
entity_picture?: string;
|
||||
entity_picture_template?: string;
|
||||
units?: string;
|
||||
state?: StateConfig[];
|
||||
styles?: StylesConfig;
|
||||
confirmation?: string;
|
||||
layout: Layout;
|
||||
entity_picture_style?: CssStyleConfig[];
|
||||
|
||||
default_color: string;
|
||||
color_on: string;
|
||||
color_off: string;
|
||||
custom_fields?: CustomFields;
|
||||
}
|
||||
|
||||
export type Layout = 'vertical'
|
||||
|
@ -53,16 +50,14 @@ export interface StateConfig {
|
|||
operator?: '<' | '<=' | '==' | '>=' | '>' | '!=' | 'regex' | 'template' | 'default';
|
||||
value?: any;
|
||||
name?: string;
|
||||
name_template?: string;
|
||||
icon?: string;
|
||||
color?: 'auto' | 'auto-no-temperature' | string;
|
||||
entity_picture_style?: CssStyleConfig[];
|
||||
entity_picture?: string;
|
||||
entity_picture_template?: string;
|
||||
styles?: StylesConfig;
|
||||
spin?: boolean;
|
||||
label?: string;
|
||||
label_template?: string;
|
||||
custom_fields?: CustomFields;
|
||||
}
|
||||
|
||||
export interface StylesConfig {
|
||||
|
@ -75,8 +70,17 @@ export interface StylesConfig {
|
|||
grid?: CssStyleConfig[];
|
||||
img_cell?: CssStyleConfig[];
|
||||
lock?: CssStyleConfig[];
|
||||
custom_fields?: CustomStyleConfig;
|
||||
}
|
||||
|
||||
export interface CustomStyleConfig {
|
||||
[key: string]: CssStyleConfig[];
|
||||
}
|
||||
|
||||
export interface CssStyleConfig {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CustomFields {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue