Label and templates support (#134)
* Label and templates support * Fix blank card with fixed width * Fix some layouts
This commit is contained in:
parent
21c60bb033
commit
56a8fac0fa
97
README.md
97
README.md
|
@ -21,6 +21,7 @@ Lovelace Button card for your entities.
|
|||
- custom color (optional), or based on light rgb value
|
||||
- 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)
|
||||
- Support for [templates](#templates) in some fields
|
||||
- custom icon (optional)
|
||||
- custom css style (optional)
|
||||
- multiple [layout](#Layout) support
|
||||
|
@ -52,10 +53,13 @@ Lovelace Button card for your entities.
|
|||
| `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. |
|
||||
| `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 | `states['light.mylight'].attributes.brightness` | 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 |
|
||||
| `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_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 |
|
||||
| `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 |
|
||||
|
@ -89,9 +93,13 @@ Lovelace Button card for your entities.
|
|||
| `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_style` | object list | optional | `- border-radius: 50%`, `- filter: grayscale(100%)`, ... | Style applied to the entity picture for this state |
|
||||
| `label` | string | optional | Any string that you want | Display a label below the card. See [Layouts](#layout) for more information. |
|
||||
| `label_template` | string | optional | `states['light.mylight'].attributes.brightness` | See [templates](#templates). Any javascript code which returns a string. Overrides `label` |
|
||||
|
||||
### Available operators
|
||||
|
||||
The order of your elements in the `state` object matters. The first one which is `true` will match.
|
||||
|
||||
| Operator | `value` example | Description |
|
||||
| :-------: | --------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| `<` | `5` | Current state is inferior to `value` |
|
||||
|
@ -101,6 +109,7 @@ Lovelace Button card for your entities.
|
|||
| `>` | `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` | `return states['input_select.light_mode'].state === 'night_mode'` | See [here](#state-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 |
|
||||
|
||||
### Layout
|
||||
|
@ -111,15 +120,29 @@ It is fully compatible with every `show_*` option. Make sure you set `show_state
|
|||
|
||||
Multiple values are possible, see the image below for examples:
|
||||
* `vertical` (default value if nothing is provided): Everything is centered vertically on top of each other
|
||||
* `icon_name_state`: Everything is aligned horizontally, name and state are concatenated
|
||||
* `name_state`: Icon sits on top of name and state concatenated on one line
|
||||
* `icon_name`: Icon and name are horizontally aligned, state is centered below
|
||||
* `icon_state`: Icon and state are horizontally aligned, name is centered below
|
||||
* `icon_name_state2nd`: Icon, name and state are horizontally aligned, name is above state
|
||||
* `icon_state_name2nd`: Icon, name and state are horizontally aligned, state is above name
|
||||
* `icon_name_state`: Everything is aligned horizontally, name and state are concatenated, label is centered below
|
||||
* `name_state`: Icon sits on top of name and state concatenated on one line, label below
|
||||
* `icon_name`: Icon and name are horizontally aligned, state and label are centered below
|
||||
* `icon_state`: Icon and state are horizontally aligned, name and label are centered below
|
||||
* `icon_label`: Icon and label are horizontally aligned, name and state are centered below
|
||||
* `icon_name_state2nd`: Icon, name and state are horizontally aligned, name is above state, label below name and state
|
||||
* `icon_state_name2nd`: Icon, name and state are horizontally aligned, state is above name, label below name and state
|
||||
|
||||
![layout_image](examples/layout.png)
|
||||
|
||||
### Templates
|
||||
|
||||
`label_template` supports templating.
|
||||
It will be interpreted as javascript code and the code should return a value.
|
||||
|
||||
Inside the javascript code, you'll have access to those variables:
|
||||
* `entity`: The current entity object, if the entity is defined in the card
|
||||
* `states`: An object with all the states of all the entities (equivalent to `hass.states`)
|
||||
* `user`: The user object (equivalent to `hass.user`)
|
||||
* `hass`: The complete `hass` object
|
||||
|
||||
See [here](#playing-with-label-templates) for some examples.
|
||||
|
||||
## Installation
|
||||
|
||||
### Manual Installation
|
||||
|
@ -466,6 +489,68 @@ If you specify a width for the card, it has to be in `px`. All the cards without
|
|||
style:
|
||||
- height: 300px
|
||||
```
|
||||
### Templates Support
|
||||
|
||||
#### Playing with label templates
|
||||
|
||||
![label_template](examples/labels.png)
|
||||
|
||||
```yaml
|
||||
- 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') + '%';
|
||||
show_label: true
|
||||
size: 15%
|
||||
style:
|
||||
- height: 100px
|
||||
- type: "custom:button-card"
|
||||
color_type: icon
|
||||
entity: light.test_light
|
||||
layout: icon_label
|
||||
label_template: >
|
||||
return 'Other State: ' + states['switch.skylight'].state;
|
||||
show_label: true
|
||||
show_name: false
|
||||
style:
|
||||
- height: 100px
|
||||
```
|
||||
|
||||
#### State Templates
|
||||
|
||||
The javascript code inside `value` needs to return `true` of `false`.
|
||||
|
||||
Example with `template`:
|
||||
```yaml
|
||||
- type: "custom:button-card"
|
||||
color_type: icon
|
||||
entity: switch.skylight
|
||||
show_state: true
|
||||
show_label: true
|
||||
state:
|
||||
- operator: template
|
||||
value: >
|
||||
return states['light.test_light'].attributes
|
||||
&& (states['light.test_light'].attributes.brightness <= 100)
|
||||
icon: mdi:alert
|
||||
- operator: default
|
||||
icon: mdi:lightbulb
|
||||
- type: "custom:button-card"
|
||||
color_type: icon
|
||||
entity: light.test_light
|
||||
show_label: true
|
||||
state:
|
||||
- operator: template
|
||||
value: >
|
||||
return states['input_select.light_mode'].state === 'night_mode'
|
||||
icon: mdi:weather-night
|
||||
label: Night Mode
|
||||
- operator: default
|
||||
icon: mdi:white-balance-sunny
|
||||
label: Day Mode
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
|
@ -3336,8 +3336,8 @@ function applyBrightnessToColor(color, brightness) {
|
|||
return color;
|
||||
}
|
||||
// Check if config or Entity changed
|
||||
function hasConfigOrEntityChanged(element, changedProps) {
|
||||
if (changedProps.has('config')) {
|
||||
function hasConfigOrEntityChanged(element, changedProps, forceUpdate) {
|
||||
if (changedProps.has('config') || forceUpdate) {
|
||||
return true;
|
||||
}
|
||||
if (element.config.entity) {
|
||||
|
@ -3711,8 +3711,9 @@ const styles = css`
|
|||
}
|
||||
.img-cell {
|
||||
grid-area: i;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
@ -3736,64 +3737,164 @@ const styles = css`
|
|||
/* margin: auto; */
|
||||
}
|
||||
|
||||
.container.vertical {
|
||||
grid-template-areas: "i" "n" "s";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr min-content min-content;
|
||||
.label {
|
||||
grid-area: l;
|
||||
max-width: 100%;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
.container.vertical.no-icon {
|
||||
grid-template-areas: "n" "s";
|
||||
|
||||
.container.vertical {
|
||||
grid-template-areas: "i" "n" "s" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
grid-template-rows: 1fr min-content min-content min-content;
|
||||
}
|
||||
/* Vertical No Icon */
|
||||
.container.vertical.no-icon {
|
||||
grid-template-areas: "n" "s" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr min-content 1fr;
|
||||
}
|
||||
.container.vertical.no-icon .state {
|
||||
align-self: start;
|
||||
align-self: center;
|
||||
}
|
||||
.container.vertical.no-icon .name {
|
||||
align-self: end;
|
||||
}
|
||||
.container.vertical.no-icon .label {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* Vertical No Icon No Name */
|
||||
.container.vertical.no-icon.no-name {
|
||||
grid-template-areas: "s" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-name .state {
|
||||
align-self: end;
|
||||
}
|
||||
.container.vertical.no-icon.no-name .label {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* Vertical No Icon No State */
|
||||
.container.vertical.no-icon.no-state {
|
||||
grid-template-areas: "n" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-state .name {
|
||||
align-self: end;
|
||||
}
|
||||
.container.vertical.no-icon.no-state .label {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* Vertical No Icon No Label */
|
||||
.container.vertical.no-icon.no-label {
|
||||
grid-template-areas: "n" "s";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-label .name {
|
||||
align-self: end;
|
||||
}
|
||||
.container.vertical.no-icon.no-label .state {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* Vertical No Icon No Label No Name */
|
||||
.container.vertical.no-icon.no-label.no-name {
|
||||
grid-template-areas: "s";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-name .state {
|
||||
.container.vertical.no-icon.no-label.no-name .state {
|
||||
align-self: center;
|
||||
}
|
||||
.container.vertical.no-icon.no-state {
|
||||
/* Vertical No Icon No Label No State */
|
||||
.container.vertical.no-icon.no-label.no-state {
|
||||
grid-template-areas: "n";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-state .name {
|
||||
.container.vertical.no-icon.no-label.no-state .name {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* Vertical No Icon No Name No State */
|
||||
.container.vertical.no-icon.no-name.no-state {
|
||||
grid-template-areas: "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-name.no-state .label {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.container.icon_name_state {
|
||||
grid-template-areas: "i n";
|
||||
grid-template-areas: "i n" "l l";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-rows: 1fr min-content;
|
||||
}
|
||||
|
||||
.container.icon_name {
|
||||
grid-template-areas: "i n" "s s";
|
||||
grid-template-areas: "i n" "s s" "l l";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr min-content;
|
||||
grid-template-rows: 1fr min-content min-content;
|
||||
}
|
||||
|
||||
.container.icon_state {
|
||||
grid-template-areas: "i s" "n n";
|
||||
grid-template-areas: "i s" "n n" "l l";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr min-content;
|
||||
grid-template-rows: 1fr min-content min-content;
|
||||
}
|
||||
|
||||
.container.name_state {
|
||||
grid-template-areas: "i" "n";
|
||||
grid-template-areas: "i" "n" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr min-content;
|
||||
grid-template-rows: 1fr min-content min-content;
|
||||
}
|
||||
.container.name_state.no-icon {
|
||||
grid-template-areas: "n" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
.container.name_state.no-icon .name {
|
||||
align-self: end
|
||||
}
|
||||
.container.name_state.no-icon .label {
|
||||
align-self: start
|
||||
}
|
||||
|
||||
.container.name_state.no-icon.no-label {
|
||||
grid-template-areas: "n";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.container.name_state.no-icon.no-label .name {
|
||||
align-self: center
|
||||
}
|
||||
|
||||
/* icon_name_state2nd default */
|
||||
.container.icon_name_state2nd {
|
||||
grid-template-areas: "i n" "i s" "i l";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr min-content 1fr;
|
||||
}
|
||||
.container.icon_name_state2nd .name {
|
||||
align-self: end;
|
||||
}
|
||||
.container.icon_name_state2nd .state {
|
||||
align-self: center;
|
||||
}
|
||||
.container.icon_name_state2nd .label {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* icon_name_state2nd No Label */
|
||||
.container.icon_name_state2nd.no-label {
|
||||
grid-template-areas: "i n" "i s";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
|
@ -3805,7 +3906,24 @@ const styles = css`
|
|||
align-self: start;
|
||||
}
|
||||
|
||||
/* icon_state_name2nd Default */
|
||||
.container.icon_state_name2nd {
|
||||
grid-template-areas: "i s" "i n" "i l";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr min-content 1fr;
|
||||
}
|
||||
.container.icon_state_name2nd .state {
|
||||
align-self: end;
|
||||
}
|
||||
.container.icon_state_name2nd .name {
|
||||
align-self: center;
|
||||
}
|
||||
.container.icon_state_name2nd .state {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* icon_state_name2nd No Label */
|
||||
.container.icon_state_name2nd.no-label {
|
||||
grid-template-areas: "i s" "i n";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
|
@ -3816,6 +3934,12 @@ const styles = css`
|
|||
.container.icon_state_name2nd .name {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.container.icon_label {
|
||||
grid-template-areas: "i l" "n n" "s s";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr min-content min-content;
|
||||
}
|
||||
`;
|
||||
|
||||
let ButtonCard = class ButtonCard extends LitElement {
|
||||
|
@ -3829,7 +3953,12 @@ let ButtonCard = class ButtonCard extends LitElement {
|
|||
return this._cardHtml();
|
||||
}
|
||||
shouldUpdate(changedProps) {
|
||||
return hasConfigOrEntityChanged(this, changedProps);
|
||||
const state = this.config.entity ? this.hass.states[this.config.entity] : undefined;
|
||||
const configState = this._getMatchingConfigState(state);
|
||||
const forceUpdate = this.config.show_label && (configState && configState.label_template || this.config.label_template) || this.config.state && this.config.state.find(elt => {
|
||||
return elt.operator === 'template';
|
||||
}) ? true : false;
|
||||
return hasConfigOrEntityChanged(this, changedProps, forceUpdate);
|
||||
}
|
||||
_getMatchingConfigState(state) {
|
||||
if (!state || !this.config.state) {
|
||||
|
@ -3858,6 +3987,10 @@ let ButtonCard = class ButtonCard extends LitElement {
|
|||
const matches = state.state.match(elt.value) ? true : false;
|
||||
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);
|
||||
}
|
||||
case 'default':
|
||||
def = elt;
|
||||
return false;
|
||||
|
@ -4018,6 +4151,28 @@ let ButtonCard = class ButtonCard extends LitElement {
|
|||
}
|
||||
return units;
|
||||
}
|
||||
_buildLabel(state, configState) {
|
||||
if (!this.config.show_label) {
|
||||
return undefined;
|
||||
}
|
||||
let label;
|
||||
let matchingLabelTemplate;
|
||||
if (configState && configState.label_template) {
|
||||
matchingLabelTemplate = configState.label_template;
|
||||
} else {
|
||||
matchingLabelTemplate = this.config.label_template;
|
||||
}
|
||||
if (!matchingLabelTemplate) {
|
||||
if (configState && configState.label) {
|
||||
label = configState.label;
|
||||
} else {
|
||||
label = this.config.label;
|
||||
}
|
||||
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);
|
||||
}
|
||||
_isClickable(state) {
|
||||
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') {
|
||||
|
@ -4045,11 +4200,11 @@ let ButtonCard = class ButtonCard extends LitElement {
|
|||
_rotate(configState) {
|
||||
return configState && configState.spin ? true : false;
|
||||
}
|
||||
_blankCardColoredHtml(state) {
|
||||
_blankCardColoredHtml(state, cardStyle) {
|
||||
const color = this._buildCssColorAttribute(state, undefined);
|
||||
const fontColor = getFontColorBasedOnBackgroundColor(color);
|
||||
return html`
|
||||
<ha-card class="disabled">
|
||||
<ha-card class="disabled" style=${styleMap(cardStyle)}>
|
||||
<div style="color: ${fontColor}; background-color: ${color};"></div>
|
||||
</ha-card>
|
||||
`;
|
||||
|
@ -4061,9 +4216,13 @@ let ButtonCard = class ButtonCard extends LitElement {
|
|||
let buttonColor = color;
|
||||
let cardStyle = {};
|
||||
const configCardStyle = this._buildStyle(state, configState);
|
||||
if (configCardStyle.width) {
|
||||
this.style.setProperty('flex', '0 0 auto');
|
||||
this.style.setProperty('max-width', 'fit-content');
|
||||
}
|
||||
switch (this.config.color_type) {
|
||||
case 'blank-card':
|
||||
return this._blankCardColoredHtml(state);
|
||||
return this._blankCardColoredHtml(state, configCardStyle);
|
||||
case 'card':
|
||||
case 'label-card':
|
||||
{
|
||||
|
@ -4078,10 +4237,6 @@ let ButtonCard = class ButtonCard extends LitElement {
|
|||
cardStyle = configCardStyle;
|
||||
break;
|
||||
}
|
||||
if (configCardStyle.width) {
|
||||
this.style.setProperty('flex', '0 0 auto');
|
||||
this.style.setProperty('max-width', 'fit-content');
|
||||
}
|
||||
return html`
|
||||
<ha-card class="button-card-main ${this._isClickable(state) ? '' : 'disabled'}" style=${styleMap(cardStyle)} @ha-click="${this._handleTap}" @ha-hold="${this._handleHold}" .longpress="${longPress()}" .config="${this.config}">
|
||||
${this._buttonContent(state, configState, buttonColor)}
|
||||
|
@ -4104,14 +4259,17 @@ let ButtonCard = class ButtonCard extends LitElement {
|
|||
_gridHtml(state, configState, containerClass, color, name, stateString) {
|
||||
const iconTemplate = this._getIconHtml(state, configState, color);
|
||||
const itemClass = ['container', containerClass];
|
||||
const label = this._buildLabel(state, configState);
|
||||
if (!iconTemplate) itemClass.push('no-icon');
|
||||
if (!name) itemClass.push('no-name');
|
||||
if (!stateString) itemClass.push('no-state');
|
||||
if (!label) itemClass.push('no-label');
|
||||
return html`
|
||||
<div class=${itemClass.join(' ')}>
|
||||
${iconTemplate ? iconTemplate : ''}
|
||||
${name ? html`<div class="name">${name}</div>` : ''}
|
||||
${stateString ? html`<div class="state">${stateString}</div>` : ''}
|
||||
${label ? html`<div class="label">${label}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -4142,7 +4300,7 @@ let ButtonCard = class ButtonCard extends LitElement {
|
|||
if (!config) {
|
||||
throw new Error('Invalid configuration');
|
||||
}
|
||||
this.config = Object.assign({ tap_action: { action: 'toggle' }, hold_action: { action: 'none' }, layout: 'vertical', size: '40%', color_type: 'icon', show_name: true, show_state: false, show_icon: true, show_units: true, show_entity_picture: false }, config);
|
||||
this.config = Object.assign({ tap_action: { action: 'toggle' }, hold_action: { action: 'none' }, layout: 'vertical', size: '40%', color_type: 'icon', show_name: true, show_state: false, show_icon: true, show_units: true, show_label: false, show_entity_picture: false }, config);
|
||||
this.config.default_color = 'var(--primary-text-color)';
|
||||
if (this.config.color_type !== 'icon') {
|
||||
this.config.color_off = 'var(--paper-card-background-color)';
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
|
@ -48,7 +48,17 @@ class ButtonCard extends LitElement {
|
|||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return hasConfigOrEntityChanged(this, changedProps);
|
||||
const state = this.config!.entity ? this.hass!.states[this.config!.entity] : undefined;
|
||||
const configState = this._getMatchingConfigState(state);
|
||||
const forceUpdate = (this.config!.show_label
|
||||
&& (configState
|
||||
&& configState.label_template
|
||||
|| this.config!.label_template)
|
||||
)
|
||||
|| this.config!.state
|
||||
&& this.config!.state.find((elt) => { return elt.operator === 'template'; })
|
||||
? true : false;
|
||||
return hasConfigOrEntityChanged(this, changedProps, forceUpdate);
|
||||
}
|
||||
|
||||
private _getMatchingConfigState(state: HassEntity | undefined): StateConfig | undefined {
|
||||
|
@ -77,6 +87,11 @@ class ButtonCard extends LitElement {
|
|||
const matches = state.state.match(elt.value) ? true : false;
|
||||
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);
|
||||
}
|
||||
case 'default':
|
||||
def = elt;
|
||||
return false;
|
||||
|
@ -266,6 +281,36 @@ class ButtonCard extends LitElement {
|
|||
return units;
|
||||
}
|
||||
|
||||
private _buildLabel(
|
||||
state: HassEntity | undefined,
|
||||
configState: StateConfig | undefined,
|
||||
): string | undefined {
|
||||
if (!this.config!.show_label) {
|
||||
return undefined;
|
||||
}
|
||||
let label: string | undefined;
|
||||
let matchingLabelTemplate: string | undefined;
|
||||
|
||||
if (configState && configState.label_template) {
|
||||
matchingLabelTemplate = configState.label_template;
|
||||
} else {
|
||||
matchingLabelTemplate = this.config!.label_template;
|
||||
}
|
||||
if (!matchingLabelTemplate) {
|
||||
if (configState && configState.label) {
|
||||
label = configState.label;
|
||||
} else {
|
||||
label = this.config!.label;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
private _isClickable(state: HassEntity | undefined): boolean {
|
||||
let clickable = true;
|
||||
if (this.config!.tap_action!.action === 'toggle' && this.config!.hold_action!.action === 'none'
|
||||
|
@ -297,11 +342,14 @@ class ButtonCard extends LitElement {
|
|||
return configState && configState.spin ? true : false;
|
||||
}
|
||||
|
||||
private _blankCardColoredHtml(state: HassEntity | undefined): TemplateResult {
|
||||
private _blankCardColoredHtml(
|
||||
state: HassEntity | undefined,
|
||||
cardStyle: StyleInfo,
|
||||
): TemplateResult {
|
||||
const color = this._buildCssColorAttribute(state, undefined);
|
||||
const fontColor = getFontColorBasedOnBackgroundColor(color);
|
||||
return html`
|
||||
<ha-card class="disabled">
|
||||
<ha-card class="disabled" style=${styleMap(cardStyle)}>
|
||||
<div style="color: ${fontColor}; background-color: ${color};"></div>
|
||||
</ha-card>
|
||||
`;
|
||||
|
@ -315,9 +363,13 @@ class ButtonCard extends LitElement {
|
|||
let cardStyle: StyleInfo = {};
|
||||
const configCardStyle = this._buildStyle(state, configState);
|
||||
|
||||
if (configCardStyle.width) {
|
||||
this.style.setProperty('flex', '0 0 auto');
|
||||
this.style.setProperty('max-width', 'fit-content');
|
||||
}
|
||||
switch (this.config!.color_type) {
|
||||
case 'blank-card':
|
||||
return this._blankCardColoredHtml(state);
|
||||
return this._blankCardColoredHtml(state, configCardStyle);
|
||||
case 'card':
|
||||
case 'label-card': {
|
||||
const fontColor = getFontColorBasedOnBackgroundColor(color);
|
||||
|
@ -331,10 +383,6 @@ class ButtonCard extends LitElement {
|
|||
cardStyle = configCardStyle;
|
||||
break;
|
||||
}
|
||||
if (configCardStyle.width) {
|
||||
this.style.setProperty('flex', '0 0 auto');
|
||||
this.style.setProperty('max-width', 'fit-content');
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card class="button-card-main ${this._isClickable(state) ? '' : 'disabled'}" style=${styleMap(cardStyle)} @ha-click="${this._handleTap}" @ha-hold="${this._handleHold}" .longpress="${longPress()}" .config="${this.config}">
|
||||
|
@ -374,15 +422,18 @@ class ButtonCard extends LitElement {
|
|||
): TemplateResult {
|
||||
const iconTemplate = this._getIconHtml(state, configState, color);
|
||||
const itemClass: string[] = ['container', containerClass];
|
||||
const label = this._buildLabel(state, configState);
|
||||
if (!iconTemplate) itemClass.push('no-icon');
|
||||
if (!name) itemClass.push('no-name');
|
||||
if (!stateString) itemClass.push('no-state');
|
||||
if (!label) itemClass.push('no-label');
|
||||
|
||||
return html`
|
||||
<div class=${itemClass.join(' ')}>
|
||||
${iconTemplate ? iconTemplate : ''}
|
||||
${name ? html`<div class="name">${name}</div>` : ''}
|
||||
${stateString ? html`<div class="state">${stateString}</div>` : ''}
|
||||
${label ? html`<div class="label">${label}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -435,6 +486,7 @@ class ButtonCard extends LitElement {
|
|||
show_state: false,
|
||||
show_icon: true,
|
||||
show_units: true,
|
||||
show_label: false,
|
||||
show_entity_picture: false,
|
||||
...config,
|
||||
};
|
||||
|
|
|
@ -61,8 +61,9 @@ export function applyBrightnessToColor(
|
|||
export function hasConfigOrEntityChanged(
|
||||
element: any,
|
||||
changedProps: PropertyValues,
|
||||
forceUpdate: Boolean,
|
||||
): boolean {
|
||||
if (changedProps.has('config')) {
|
||||
if (changedProps.has('config') || forceUpdate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
166
src/styles.ts
166
src/styles.ts
|
@ -79,8 +79,9 @@ export const styles = css`
|
|||
}
|
||||
.img-cell {
|
||||
grid-area: i;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
@ -104,64 +105,164 @@ export const styles = css`
|
|||
/* margin: auto; */
|
||||
}
|
||||
|
||||
.container.vertical {
|
||||
grid-template-areas: "i" "n" "s";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr min-content min-content;
|
||||
.label {
|
||||
grid-area: l;
|
||||
max-width: 100%;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
.container.vertical.no-icon {
|
||||
grid-template-areas: "n" "s";
|
||||
|
||||
.container.vertical {
|
||||
grid-template-areas: "i" "n" "s" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
grid-template-rows: 1fr min-content min-content min-content;
|
||||
}
|
||||
/* Vertical No Icon */
|
||||
.container.vertical.no-icon {
|
||||
grid-template-areas: "n" "s" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr min-content 1fr;
|
||||
}
|
||||
.container.vertical.no-icon .state {
|
||||
align-self: start;
|
||||
align-self: center;
|
||||
}
|
||||
.container.vertical.no-icon .name {
|
||||
align-self: end;
|
||||
}
|
||||
.container.vertical.no-icon .label {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* Vertical No Icon No Name */
|
||||
.container.vertical.no-icon.no-name {
|
||||
grid-template-areas: "s" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-name .state {
|
||||
align-self: end;
|
||||
}
|
||||
.container.vertical.no-icon.no-name .label {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* Vertical No Icon No State */
|
||||
.container.vertical.no-icon.no-state {
|
||||
grid-template-areas: "n" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-state .name {
|
||||
align-self: end;
|
||||
}
|
||||
.container.vertical.no-icon.no-state .label {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* Vertical No Icon No Label */
|
||||
.container.vertical.no-icon.no-label {
|
||||
grid-template-areas: "n" "s";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-label .name {
|
||||
align-self: end;
|
||||
}
|
||||
.container.vertical.no-icon.no-label .state {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* Vertical No Icon No Label No Name */
|
||||
.container.vertical.no-icon.no-label.no-name {
|
||||
grid-template-areas: "s";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-name .state {
|
||||
.container.vertical.no-icon.no-label.no-name .state {
|
||||
align-self: center;
|
||||
}
|
||||
.container.vertical.no-icon.no-state {
|
||||
/* Vertical No Icon No Label No State */
|
||||
.container.vertical.no-icon.no-label.no-state {
|
||||
grid-template-areas: "n";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-state .name {
|
||||
.container.vertical.no-icon.no-label.no-state .name {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* Vertical No Icon No Name No State */
|
||||
.container.vertical.no-icon.no-name.no-state {
|
||||
grid-template-areas: "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.container.vertical.no-icon.no-name.no-state .label {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.container.icon_name_state {
|
||||
grid-template-areas: "i n";
|
||||
grid-template-areas: "i n" "l l";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-rows: 1fr min-content;
|
||||
}
|
||||
|
||||
.container.icon_name {
|
||||
grid-template-areas: "i n" "s s";
|
||||
grid-template-areas: "i n" "s s" "l l";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr min-content;
|
||||
grid-template-rows: 1fr min-content min-content;
|
||||
}
|
||||
|
||||
.container.icon_state {
|
||||
grid-template-areas: "i s" "n n";
|
||||
grid-template-areas: "i s" "n n" "l l";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr min-content;
|
||||
grid-template-rows: 1fr min-content min-content;
|
||||
}
|
||||
|
||||
.container.name_state {
|
||||
grid-template-areas: "i" "n";
|
||||
grid-template-areas: "i" "n" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr min-content;
|
||||
grid-template-rows: 1fr min-content min-content;
|
||||
}
|
||||
.container.name_state.no-icon {
|
||||
grid-template-areas: "n" "l";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
.container.name_state.no-icon .name {
|
||||
align-self: end
|
||||
}
|
||||
.container.name_state.no-icon .label {
|
||||
align-self: start
|
||||
}
|
||||
|
||||
.container.name_state.no-icon.no-label {
|
||||
grid-template-areas: "n";
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.container.name_state.no-icon.no-label .name {
|
||||
align-self: center
|
||||
}
|
||||
|
||||
/* icon_name_state2nd default */
|
||||
.container.icon_name_state2nd {
|
||||
grid-template-areas: "i n" "i s" "i l";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr min-content 1fr;
|
||||
}
|
||||
.container.icon_name_state2nd .name {
|
||||
align-self: end;
|
||||
}
|
||||
.container.icon_name_state2nd .state {
|
||||
align-self: center;
|
||||
}
|
||||
.container.icon_name_state2nd .label {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* icon_name_state2nd No Label */
|
||||
.container.icon_name_state2nd.no-label {
|
||||
grid-template-areas: "i n" "i s";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
|
@ -173,7 +274,24 @@ export const styles = css`
|
|||
align-self: start;
|
||||
}
|
||||
|
||||
/* icon_state_name2nd Default */
|
||||
.container.icon_state_name2nd {
|
||||
grid-template-areas: "i s" "i n" "i l";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr min-content 1fr;
|
||||
}
|
||||
.container.icon_state_name2nd .state {
|
||||
align-self: end;
|
||||
}
|
||||
.container.icon_state_name2nd .name {
|
||||
align-self: center;
|
||||
}
|
||||
.container.icon_state_name2nd .state {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* icon_state_name2nd No Label */
|
||||
.container.icon_state_name2nd.no-label {
|
||||
grid-template-areas: "i s" "i n";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
|
@ -184,6 +302,12 @@ export const styles = css`
|
|||
.container.icon_state_name2nd .name {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.container.icon_label {
|
||||
grid-template-areas: "i l" "n n" "s s";
|
||||
grid-template-columns: 40% 1fr;
|
||||
grid-template-rows: 1fr min-content min-content;
|
||||
}
|
||||
`;
|
||||
|
||||
export default styles;
|
||||
|
|
18
src/types.ts
18
src/types.ts
|
@ -23,12 +23,15 @@ export interface ButtonCardConfig {
|
|||
show_icon?: boolean;
|
||||
show_units?: boolean;
|
||||
show_entity_picture?: boolean;
|
||||
show_label?: boolean;
|
||||
label?: string;
|
||||
label_template?: string;
|
||||
entity_picture?: string;
|
||||
units?: string;
|
||||
style?: CssStyleConfig[];
|
||||
state?: StateConfig[];
|
||||
confirmation?: string;
|
||||
layout: 'vertical' | 'icon_name_state' | 'name_state' | 'icon_name' | 'icon_state' | 'icon_name_state2nd' | 'icon_state_name2nd';
|
||||
layout: Layout;
|
||||
entity_picture_style?: CssStyleConfig[];
|
||||
|
||||
default_color: string;
|
||||
|
@ -36,8 +39,17 @@ export interface ButtonCardConfig {
|
|||
color_off: string;
|
||||
}
|
||||
|
||||
export type Layout = 'vertical'
|
||||
| 'icon_name_state'
|
||||
| 'name_state'
|
||||
| 'icon_name'
|
||||
| 'icon_state'
|
||||
| 'icon_name_state2nd'
|
||||
| 'icon_state_name2nd'
|
||||
| 'icon_label';
|
||||
|
||||
export interface StateConfig {
|
||||
operator?: '<' | '<=' | '==' | '>=' | '>' | '!=' | 'regex' | 'default';
|
||||
operator?: '<' | '<=' | '==' | '>=' | '>' | '!=' | 'regex' | 'template' | 'default';
|
||||
value?: any;
|
||||
name?: string;
|
||||
icon?: string;
|
||||
|
@ -46,6 +58,8 @@ export interface StateConfig {
|
|||
entity_picture_style?: CssStyleConfig[];
|
||||
entity_picture?: string;
|
||||
spin?: boolean;
|
||||
label?: string;
|
||||
label_template?: string;
|
||||
}
|
||||
|
||||
export interface CssStyleConfig {
|
||||
|
|
Loading…
Reference in New Issue