Switch to css grid and code refactor (#132)

* Switch to css grid and code refactor

* Ability to specify the width of the card

* Update documentation

* Fix invalid CSS value

* requested changes and bugfix
This commit is contained in:
Jérôme W 2019-04-29 10:50:13 +02:00 committed by GitHub
parent 37a8b89256
commit 21c60bb033
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 513 additions and 396 deletions

View File

@ -20,7 +20,7 @@ Lovelace Button card for your entities.
- state display (optional)
- custom color (optional), or based on light rgb value
- custom state definition with customizable color, icon and style (optional)
- custom size (optional)
- [custom size of the icon, width and height](#Play-with-width-height-and-icon-size) (optional)
- custom icon (optional)
- custom css style (optional)
- multiple [layout](#Layout) support
@ -408,6 +408,65 @@ You can make the whole button blink:
icon: mdi:shield-check
```
### Play with width, height and icon size
Through the `styles` you can specify the `width` and `height` of the card, and also the icon size through the main `size` option. Playing with icon size will growth the card unless a `height` is specified.
If you specify a width for the card, it has to be in `px`. All the cards without a `width` defined will use the remaining space on the line.
![height-width](examples/width_height.png)
```yaml
- type: horizontal-stack
cards:
- type: "custom:button-card"
entity: light.test_light
color: auto
name: s:default h:200px
style:
- height: 200px
- type: "custom:button-card"
entity: light.test_light
color_type: card
color: auto
name: s:100% h:200px
size: 100%
style:
- height: 200px
- type: "custom:button-card"
entity: light.test_light
color_type: card
color: auto
size: 10%
name: s:10% h:200px
style:
- height: 200px
- type: horizontal-stack
cards:
- type: "custom:button-card"
entity: light.test_light
color: auto
name: 60px
style:
- height: 60px
- width: 60px
- type: "custom:button-card"
entity: light.test_light
color_type: card
color: auto
name: 80px
style:
- height: 80px
- width: 30px
- type: "custom:button-card"
entity: light.test_light
color_type: card
color: auto
name: 300px
style:
- height: 300px
```
## Credits
- [ciotlosm](https://github.com/ciotlosm) for the readme template and the awesome examples

399
dist/button-card.js vendored
View File

@ -3656,27 +3656,10 @@ const styles = css`
letter-spacing: normal;
width: 100%;
}
div.divTable{
display: table;
overflow: auto;
table-layout: fixed;
width: 100%;
}
div.divTableBody {
display: table-row-group;
}
div.divTableRow {
display: table-row;
}
.divTableCell {
display: table-cell;
vertical-align: middle;
}
div {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
min-width: 100%;
}
@keyframes blink{
0%{opacity:0;}
@ -3711,13 +3694,128 @@ const styles = css`
transform: rotate(360deg);
}
}
.rotating {
[rotating] {
-webkit-animation: rotating 2s linear infinite;
-moz-animation: rotating 2s linear infinite;
-ms-animation: rotating 2s linear infinite;
-o-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite;
}
.container {
display: grid;
max-height: 100%;
text-align: center;
height: 100%;
align-items: center;
}
.img-cell {
grid-area: i;
min-height: 0;
min-width: 0;
}
.icon {
height: 100%;
max-width: 100%;
object-fit: scale;
overflow: hidden;
}
.name {
grid-area: n;
max-width: 100%;
align-self: center;
justify-self: center;
/* margin: auto; */
}
.state {
grid-area: s;
max-width: 100%;
align-self: center;
justify-self: center;
/* margin: auto; */
}
.container.vertical {
grid-template-areas: "i" "n" "s";
grid-template-columns: 1fr;
grid-template-rows: 1fr min-content min-content;
}
.container.vertical.no-icon {
grid-template-areas: "n" "s";
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
.container.vertical.no-icon .state {
align-self: start;
}
.container.vertical.no-icon .name {
align-self: end;
}
.container.vertical.no-icon.no-name {
grid-template-areas: "s";
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.container.vertical.no-icon.no-name .state {
align-self: center;
}
.container.vertical.no-icon.no-state {
grid-template-areas: "n";
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.container.vertical.no-icon.no-state .name {
align-self: center;
}
.container.icon_name_state {
grid-template-areas: "i n";
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr;
}
.container.icon_name {
grid-template-areas: "i n" "s s";
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content;
}
.container.icon_state {
grid-template-areas: "i s" "n n";
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content;
}
.container.name_state {
grid-template-areas: "i" "n";
grid-template-columns: 1fr;
grid-template-rows: 1fr min-content;
}
.container.icon_name_state2nd {
grid-template-areas: "i n" "i s";
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr 1fr;
}
.container.icon_name_state2nd .name {
align-self: end;
}
.container.icon_name_state2nd .state {
align-self: start;
}
.container.icon_state_name2nd {
grid-template-areas: "i s" "i n";
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr 1fr;
}
.container.icon_state_name2nd .state {
align-self: end;
}
.container.icon_state_name2nd .name {
align-self: start;
}
`;
let ButtonCard = class ButtonCard extends LitElement {
@ -3728,23 +3826,12 @@ let ButtonCard = class ButtonCard extends LitElement {
if (!this.config || !this.hass) {
return html``;
}
const state = this.config.entity ? this.hass.states[this.config.entity] : undefined;
const configState = this.testConfigState(state);
switch (this.config.color_type) {
case 'blank-card':
return this.blankCardColoredHtml(state);
case 'label-card':
case 'card':
return this.cardColoredHtml(state, configState);
case 'icon':
default:
return this.iconColoredHtml(state, configState);
}
return this._cardHtml();
}
shouldUpdate(changedProps) {
return hasConfigOrEntityChanged(this, changedProps);
}
testConfigState(state) {
_getMatchingConfigState(state) {
if (!state || !this.config.state) {
return undefined;
}
@ -3786,7 +3873,7 @@ let ButtonCard = class ButtonCard extends LitElement {
}
return retval;
}
getDefaultColorForState(state) {
_getDefaultColorForState(state) {
switch (state.state) {
case 'on':
return this.config.color_on;
@ -3796,7 +3883,7 @@ let ButtonCard = class ButtonCard extends LitElement {
return this.config.default_color;
}
}
buildCssColorAttribute(state, configState) {
_buildCssColorAttribute(state, configState) {
let colorValue = '';
let color;
if (configState && configState.color) {
@ -3814,9 +3901,9 @@ let ButtonCard = class ButtonCard extends LitElement {
color = applyBrightnessToColor(color, (state.attributes.brightness + 245) / 5);
}
} else if (state.attributes.brightness) {
color = applyBrightnessToColor(this.getDefaultColorForState(state), (state.attributes.brightness + 245) / 5);
color = applyBrightnessToColor(this._getDefaultColorForState(state), (state.attributes.brightness + 245) / 5);
} else {
color = this.getDefaultColorForState(state);
color = this._getDefaultColorForState(state);
}
} else {
color = this.config.default_color;
@ -3824,13 +3911,13 @@ let ButtonCard = class ButtonCard extends LitElement {
} else if (colorValue) {
color = colorValue;
} else if (state) {
color = this.getDefaultColorForState(state);
color = this._getDefaultColorForState(state);
} else {
color = this.config.default_color;
}
return color;
}
buildIcon(state, configState) {
_buildIcon(state, configState) {
if (!this.config.show_icon) {
return undefined;
}
@ -3844,7 +3931,7 @@ let ButtonCard = class ButtonCard extends LitElement {
}
return icon;
}
buildEntityPicture(state, configState) {
_buildEntityPicture(state, configState) {
if (!this.config.show_entity_picture || !state && !configState && !this.config.entity_picture) {
return undefined;
}
@ -3858,7 +3945,7 @@ let ButtonCard = class ButtonCard extends LitElement {
}
return entityPicture;
}
buildStyle(state, configState) {
_buildStyle(state, configState) {
let cardStyle = {};
let styleArray;
if (state) {
@ -3875,7 +3962,7 @@ let ButtonCard = class ButtonCard extends LitElement {
}
return cardStyle;
}
buildEntityPictureStyle(state, configState) {
_buildEntityPictureStyle(state, configState) {
let entityPictureStyle = {};
let styleArray;
if (state) {
@ -3892,7 +3979,7 @@ let ButtonCard = class ButtonCard extends LitElement {
}
return entityPictureStyle;
}
buildName(state, configState) {
_buildName(state, configState) {
if (this.config.show_name === false) {
return undefined;
}
@ -3906,10 +3993,10 @@ let ButtonCard = class ButtonCard extends LitElement {
}
return name;
}
buildStateString(state) {
_buildStateString(state) {
let stateString;
if (this.config.show_state && state && state.state) {
const units = this.buildUnits(state);
const units = this._buildUnits(state);
if (units) {
stateString = `${state.state} ${units}`;
} else {
@ -3918,7 +4005,7 @@ let ButtonCard = class ButtonCard extends LitElement {
}
return stateString;
}
buildUnits(state) {
_buildUnits(state) {
let units;
if (state) {
if (this.config.show_units) {
@ -3931,7 +4018,7 @@ let ButtonCard = class ButtonCard extends LitElement {
}
return units;
}
isClickable(state) {
_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') {
if (state) {
@ -3955,134 +4042,11 @@ let ButtonCard = class ButtonCard extends LitElement {
}
return clickable;
}
rotate(configState) {
return configState && configState.spin ? 'rotating' : '';
_rotate(configState) {
return configState && configState.spin ? true : false;
}
buttonContent(state, configState, color) {
const icon = this.buildIcon(state, configState);
const name = this.buildName(state, configState);
const stateString = this.buildStateString(state);
const nameStateString = buildNameStateConcat(name, stateString);
const entityPicture = this.buildEntityPicture(state, configState);
const entityPictureStyle = this.buildEntityPictureStyle(state, configState);
const divTableCellStyles = { width: this.config.size, height: 'auto' };
const haIconInlineStyle = {
color,
width: this.config.size,
height: 'auto'
};
const haIconTableStyle = Object.assign({}, haIconInlineStyle, { width: 'auto', 'max-width': this.config.size });
const entityPictureInlineStyle = Object.assign({}, haIconInlineStyle, entityPictureStyle);
const entityPictureTableStyle = Object.assign({}, haIconTableStyle, entityPictureStyle);
switch (this.config.layout) {
case 'icon_name_state':
return html`
<div class='divTable'>
<div class='divTableBody'>
<div class='divTableRow'>
<div class='divTableCell' style=${styleMap(divTableCellStyles)}>
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconTableStyle)}
icon="${icon}" class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureTableStyle)}
class="${this.rotate(configState)}" />` : ''}
</div>
${nameStateString ? html`<div class="divTableCell">${nameStateString}</div>` : ''}
</div>
</div>
</div>
`;
case 'icon_name':
return html`
<div class="divTable">
<div class="divTableBody">
<div class="divTableRow">
<div class="divTableCell" style=${styleMap(divTableCellStyles)}>
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconTableStyle)}
icon="${icon}" class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureTableStyle)}
class="${this.rotate(configState)}" />` : ''}
</div>
${name ? html`<div class="divTableCell">${name}</div>` : ''}
</div>
</div>
</div>
${stateString !== undefined ? html`<div>${stateString}</div>` : ''}
`;
case 'icon_state':
return html`
<div class="divTable">
<div class="divTableBody">
<div class="divTableRow">
<div class="divTableCell" style=${styleMap(divTableCellStyles)}>
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconTableStyle)}
icon="${icon}" class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureTableStyle)}
class="${this.rotate(configState)}" />` : ''}
</div>
${stateString !== undefined ? html`<div class="divTableCell">${stateString}</div>` : ''}
</div>
</div>
</div>
${name ? html`<div>${name}</div>` : ''}
`;
case 'icon_state_name2nd':
return html`
<div class="divTable">
<div class="divTableBody">
<div class="divTableRow">
<div class="divTableCell" style=${styleMap(divTableCellStyles)}>
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconTableStyle)}
icon="${icon}" class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureTableStyle)}
class="${this.rotate(configState)}" />` : ''}
</div>
${stateString !== undefined && name ? html`<div class="divTableCell">${stateString}<br />${name}</div>` : ''}
${!stateString && name ? html`<div class="divTableCell">${name}</div>` : ''}
${stateString && !name ? html`<div class="divTableCell">${stateString}</div>` : ''}
</div>
</div>
</div>
`;
case 'icon_name_state2nd':
return html`
<div class="divTable">
<div class="divTableBody">
<div class="divTableRow">
<div class="divTableCell" style=${styleMap(divTableCellStyles)}>
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconTableStyle)}
icon="${icon}" class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureTableStyle)}
class="${this.rotate(configState)}" />` : ''}
</div>
${stateString !== undefined && name ? html`<div class="divTableCell">${name}<br />${stateString}</div>` : ''}
${!stateString && name ? html`<div class="divTableCell">${name}</div>` : ''}
${stateString && !name ? html`<div class="divTableCell">${stateString}</div>` : ''}
</div>
</div>
</div>
`;
case 'name_state':
return html`
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconInlineStyle)}
icon=${icon} class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureInlineStyle)}
class="${this.rotate(configState)}" />` : ''}
${nameStateString ? html`<div>${nameStateString}</div>` : ''}
`;
case 'vertical':
default:
return html`
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconInlineStyle)}
icon=${icon} class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureInlineStyle)}
class="${this.rotate(configState)}" />` : ''}
${name ? html`<div>${name}</div>` : ''}
${stateString ? html`<div>${stateString}</div>` : ''}
`;
}
}
blankCardColoredHtml(state) {
const color = this.buildCssColorAttribute(state, undefined);
_blankCardColoredHtml(state) {
const color = this._buildCssColorAttribute(state, undefined);
const fontColor = getFontColorBasedOnBackgroundColor(color);
return html`
<ha-card class="disabled">
@ -4090,32 +4054,95 @@ let ButtonCard = class ButtonCard extends LitElement {
</ha-card>
`;
}
cardColoredHtml(state, configState) {
const color = this.buildCssColorAttribute(state, configState);
const fontColor = getFontColorBasedOnBackgroundColor(color);
const style = Object.assign({ color: fontColor, 'background-color': color }, this.buildStyle(state, configState));
_cardHtml() {
const state = this.config.entity ? this.hass.states[this.config.entity] : undefined;
const configState = this._getMatchingConfigState(state);
const color = this._buildCssColorAttribute(state, configState);
let buttonColor = color;
let cardStyle = {};
const configCardStyle = this._buildStyle(state, configState);
switch (this.config.color_type) {
case 'blank-card':
return this._blankCardColoredHtml(state);
case 'card':
case 'label-card':
{
const fontColor = getFontColorBasedOnBackgroundColor(color);
cardStyle.color = fontColor;
cardStyle['background-color'] = color;
cardStyle = Object.assign({}, cardStyle, configCardStyle);
buttonColor = 'inherit';
break;
}
default:
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(style)} @ha-click="${this._handleTap}" @ha-hold="${this._handleHold}" .longpress="${longPress()}" .config="${this.config}">
${this.buttonContent(state, configState, 'inherit')}
<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)}
<mwc-ripple></mwc-ripple>
</ha-card>
`;
}
iconColoredHtml(state, configState) {
const color = this.buildCssColorAttribute(state, configState);
const style = this.buildStyle(state, configState);
_buttonContent(state, configState, color) {
const name = this._buildName(state, configState);
const stateString = this._buildStateString(state);
const nameStateString = buildNameStateConcat(name, stateString);
switch (this.config.layout) {
case 'icon_name_state':
case 'name_state':
return this._gridHtml(state, configState, this.config.layout, color, nameStateString, undefined);
default:
return this._gridHtml(state, configState, this.config.layout, color, name, stateString);
}
}
_gridHtml(state, configState, containerClass, color, name, stateString) {
const iconTemplate = this._getIconHtml(state, configState, color);
const itemClass = ['container', containerClass];
if (!iconTemplate) itemClass.push('no-icon');
if (!name) itemClass.push('no-name');
if (!stateString) itemClass.push('no-state');
return html`
<ha-card class="button-card-main ${this.isClickable(state) ? '' : 'disabled'}" style=${styleMap(style)} @ha-click="${this._handleTap}" @ha-hold="${this._handleHold}" .longpress="${longPress()}" .config="${this.config}">
${this.buttonContent(state, configState, color)}
<mwc-ripple></mwc-ripple>
</ha-card>
<div class=${itemClass.join(' ')}>
${iconTemplate ? iconTemplate : ''}
${name ? html`<div class="name">${name}</div>` : ''}
${stateString ? html`<div class="state">${stateString}</div>` : ''}
</div>
`;
}
_getIconHtml(state, configState, color) {
const icon = this._buildIcon(state, configState);
const entityPicture = this._buildEntityPicture(state, configState);
const entityPictureStyleFromConfig = this._buildEntityPictureStyle(state, configState);
const haIconStyle = {
color,
width: this.config.size,
'min-width': this.config.size
};
const entityPictureStyle = Object.assign({}, haIconStyle, entityPictureStyleFromConfig);
if (icon || entityPicture) {
return html`
<div class="img-cell">
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconStyle)}
.icon="${icon}" class="icon" ?rotating=${this._rotate(configState)}></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureStyle)}
class="icon" ?rotating=${this._rotate(configState)} />` : ''}
</div>
`;
} else {
return undefined;
}
}
setConfig(config) {
if (!config) {
throw new Error('Invalid configuration');
}
this.config = Object.assign({ tap_action: { action: 'toggle' }, hold_action: { action: 'none' }, 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_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)';

BIN
examples/width_height.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -44,25 +44,14 @@ class ButtonCard extends LitElement {
if (!this.config || !this.hass) {
return html``;
}
const state = this.config.entity ? this.hass.states[this.config.entity] : undefined;
const configState = this.testConfigState(state);
switch (this.config.color_type) {
case 'blank-card':
return this.blankCardColoredHtml(state);
case 'label-card':
case 'card':
return this.cardColoredHtml(state, configState);
case 'icon':
default:
return this.iconColoredHtml(state, configState);
}
return this._cardHtml();
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
return hasConfigOrEntityChanged(this, changedProps);
}
private testConfigState(state: HassEntity | undefined): StateConfig | undefined {
private _getMatchingConfigState(state: HassEntity | undefined): StateConfig | undefined {
if (!state || !this.config!.state) {
return undefined;
}
@ -104,7 +93,7 @@ class ButtonCard extends LitElement {
return retval;
}
private getDefaultColorForState(state: HassEntity): string {
private _getDefaultColorForState(state: HassEntity): string {
switch (state.state) {
case 'on':
return this.config!.color_on;
@ -115,7 +104,7 @@ class ButtonCard extends LitElement {
}
}
private buildCssColorAttribute(
private _buildCssColorAttribute(
state: HassEntity | undefined, configState: StateConfig | undefined,
): string {
let colorValue: string = '';
@ -136,10 +125,10 @@ class ButtonCard extends LitElement {
}
} else if (state.attributes.brightness) {
color = applyBrightnessToColor(
this.getDefaultColorForState(state), (state.attributes.brightness + 245) / 5,
this._getDefaultColorForState(state), (state.attributes.brightness + 245) / 5,
);
} else {
color = this.getDefaultColorForState(state);
color = this._getDefaultColorForState(state);
}
} else {
color = this.config!.default_color;
@ -147,14 +136,14 @@ class ButtonCard extends LitElement {
} else if (colorValue) {
color = colorValue;
} else if (state) {
color = this.getDefaultColorForState(state);
color = this._getDefaultColorForState(state);
} else {
color = this.config!.default_color;
}
return color;
}
private buildIcon(
private _buildIcon(
state: HassEntity | undefined, configState: StateConfig | undefined,
): string | undefined {
if (!this.config!.show_icon) {
@ -173,7 +162,7 @@ class ButtonCard extends LitElement {
return icon;
}
private buildEntityPicture(
private _buildEntityPicture(
state: HassEntity | undefined, configState: StateConfig | undefined,
): string | undefined {
if (!this.config!.show_entity_picture
@ -192,7 +181,7 @@ class ButtonCard extends LitElement {
return entityPicture;
}
private buildStyle(
private _buildStyle(
state: HassEntity | undefined, configState: StateConfig | undefined,
): StyleInfo {
let cardStyle: StyleInfo = {};
@ -212,7 +201,7 @@ class ButtonCard extends LitElement {
return cardStyle;
}
private buildEntityPictureStyle(
private _buildEntityPictureStyle(
state: HassEntity | undefined, configState: StateConfig | undefined,
): StyleInfo {
let entityPictureStyle: StyleInfo = {};
@ -232,7 +221,7 @@ class ButtonCard extends LitElement {
return entityPictureStyle;
}
private buildName(
private _buildName(
state: HassEntity | undefined, configState: StateConfig | undefined,
): string | undefined {
if (this.config!.show_name === false) {
@ -250,10 +239,10 @@ class ButtonCard extends LitElement {
return name;
}
private buildStateString(state: HassEntity | undefined): string | undefined {
private _buildStateString(state: HassEntity | undefined): string | undefined {
let stateString: string | undefined;
if (this.config!.show_state && state && state.state) {
const units = this.buildUnits(state);
const units = this._buildUnits(state);
if (units) {
stateString = `${state.state} ${units}`;
} else {
@ -263,7 +252,7 @@ class ButtonCard extends LitElement {
return stateString;
}
private buildUnits(state: HassEntity | undefined): string | undefined {
private _buildUnits(state: HassEntity | undefined): string | undefined {
let units: string | undefined;
if (state) {
if (this.config!.show_units) {
@ -277,7 +266,7 @@ class ButtonCard extends LitElement {
return units;
}
private isClickable(state: HassEntity | undefined): boolean {
private _isClickable(state: HassEntity | undefined): boolean {
let clickable = true;
if (this.config!.tap_action!.action === 'toggle' && this.config!.hold_action!.action === 'none'
|| this.config!.hold_action!.action === 'toggle' && this.config!.tap_action!.action === 'none') {
@ -304,151 +293,12 @@ class ButtonCard extends LitElement {
return clickable;
}
private rotate(configState: StateConfig | undefined): string {
return configState && configState.spin ? 'rotating' : '';
private _rotate(configState: StateConfig | undefined): Boolean {
return configState && configState.spin ? true : false;
}
private buttonContent(
state: HassEntity | undefined, configState: StateConfig | undefined, color: string,
): TemplateResult {
const icon = this.buildIcon(state, configState);
const name = this.buildName(state, configState);
const stateString = this.buildStateString(state);
const nameStateString = buildNameStateConcat(name, stateString);
const entityPicture = this.buildEntityPicture(state, configState);
const entityPictureStyle = this.buildEntityPictureStyle(state, configState);
const divTableCellStyles = { width: this.config!.size, height: 'auto' };
const haIconInlineStyle = {
color,
width: this.config!.size,
height: 'auto',
};
const haIconTableStyle = {
...haIconInlineStyle,
width: 'auto',
'max-width': this.config!.size,
};
const entityPictureInlineStyle = {
...haIconInlineStyle,
...entityPictureStyle,
};
const entityPictureTableStyle = {
...haIconTableStyle,
...entityPictureStyle,
};
switch (this.config!.layout) {
case 'icon_name_state':
return html`
<div class='divTable'>
<div class='divTableBody'>
<div class='divTableRow'>
<div class='divTableCell' style=${styleMap(divTableCellStyles)}>
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconTableStyle)}
icon="${icon}" class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureTableStyle)}
class="${this.rotate(configState)}" />` : ''}
</div>
${nameStateString ? html`<div class="divTableCell">${nameStateString}</div>` : ''}
</div>
</div>
</div>
`;
case 'icon_name':
return html`
<div class="divTable">
<div class="divTableBody">
<div class="divTableRow">
<div class="divTableCell" style=${styleMap(divTableCellStyles)}>
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconTableStyle)}
icon="${icon}" class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureTableStyle)}
class="${this.rotate(configState)}" />` : ''}
</div>
${name ? html`<div class="divTableCell">${name}</div>` : ''}
</div>
</div>
</div>
${stateString !== undefined ? html`<div>${stateString}</div>` : ''}
`;
case 'icon_state':
return html`
<div class="divTable">
<div class="divTableBody">
<div class="divTableRow">
<div class="divTableCell" style=${styleMap(divTableCellStyles)}>
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconTableStyle)}
icon="${icon}" class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureTableStyle)}
class="${this.rotate(configState)}" />` : ''}
</div>
${stateString !== undefined ? html`<div class="divTableCell">${stateString}</div>` : ''}
</div>
</div>
</div>
${name ? html`<div>${name}</div>` : ''}
`;
case 'icon_state_name2nd':
return html`
<div class="divTable">
<div class="divTableBody">
<div class="divTableRow">
<div class="divTableCell" style=${styleMap(divTableCellStyles)}>
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconTableStyle)}
icon="${icon}" class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureTableStyle)}
class="${this.rotate(configState)}" />` : ''}
</div>
${stateString !== undefined && name ? html`<div class="divTableCell">${stateString}<br />${name}</div>` : ''}
${!stateString && name ? html`<div class="divTableCell">${name}</div>` : ''}
${stateString && !name ? html`<div class="divTableCell">${stateString}</div>` : ''}
</div>
</div>
</div>
`;
case 'icon_name_state2nd':
return html`
<div class="divTable">
<div class="divTableBody">
<div class="divTableRow">
<div class="divTableCell" style=${styleMap(divTableCellStyles)}>
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconTableStyle)}
icon="${icon}" class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureTableStyle)}
class="${this.rotate(configState)}" />` : ''}
</div>
${stateString !== undefined && name ? html`<div class="divTableCell">${name}<br />${stateString}</div>` : ''}
${!stateString && name ? html`<div class="divTableCell">${name}</div>` : ''}
${stateString && !name ? html`<div class="divTableCell">${stateString}</div>` : ''}
</div>
</div>
</div>
`;
case 'name_state':
return html`
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconInlineStyle)}
icon=${icon} class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureInlineStyle)}
class="${this.rotate(configState)}" />` : ''}
${nameStateString ? html`<div>${nameStateString}</div>` : ''}
`;
case 'vertical':
default:
return html`
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconInlineStyle)}
icon=${icon} class="${this.rotate(configState)}"></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureInlineStyle)}
class="${this.rotate(configState)}" />` : ''}
${name ? html`<div>${name}</div>` : ''}
${stateString ? html`<div>${stateString}</div>` : ''}
`;
}
}
private blankCardColoredHtml(state: HassEntity | undefined): TemplateResult {
const color = this.buildCssColorAttribute(state, undefined);
private _blankCardColoredHtml(state: HassEntity | undefined): TemplateResult {
const color = this._buildCssColorAttribute(state, undefined);
const fontColor = getFontColorBasedOnBackgroundColor(color);
return html`
<ha-card class="disabled">
@ -457,35 +307,117 @@ class ButtonCard extends LitElement {
`;
}
private cardColoredHtml(
state: HassEntity | undefined, configState: StateConfig | undefined,
): TemplateResult {
const color = this.buildCssColorAttribute(state, configState);
const fontColor = getFontColorBasedOnBackgroundColor(color);
const style = {
color: fontColor,
'background-color': color,
...this.buildStyle(state, configState),
};
private _cardHtml(): TemplateResult {
const state = this.config!.entity ? this.hass!.states[this.config!.entity] : undefined;
const configState = this._getMatchingConfigState(state);
const color = this._buildCssColorAttribute(state, configState);
let buttonColor = color;
let cardStyle: StyleInfo = {};
const configCardStyle = this._buildStyle(state, configState);
switch (this.config!.color_type) {
case 'blank-card':
return this._blankCardColoredHtml(state);
case 'card':
case 'label-card': {
const fontColor = getFontColorBasedOnBackgroundColor(color);
cardStyle.color = fontColor;
cardStyle['background-color'] = color;
cardStyle = { ...cardStyle, ...configCardStyle };
buttonColor = 'inherit';
break;
}
default:
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(style)} @ha-click="${this._handleTap}" @ha-hold="${this._handleHold}" .longpress="${longPress()}" .config="${this.config}">
${this.buttonContent(state, configState, 'inherit')}
<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)}
<mwc-ripple></mwc-ripple>
</ha-card>
`;
}
private iconColoredHtml(
state: HassEntity | undefined, configState: StateConfig | undefined,
private _buttonContent(
state: HassEntity | undefined,
configState: StateConfig | undefined,
color: string,
): TemplateResult {
const color = this.buildCssColorAttribute(state, configState);
const style = this.buildStyle(state, configState);
const name = this._buildName(state, configState);
const stateString = this._buildStateString(state);
const nameStateString = buildNameStateConcat(name, stateString);
switch (this.config!.layout) {
case 'icon_name_state':
case 'name_state':
return this._gridHtml(state, configState, this.config!.layout, color,
nameStateString, undefined);
default:
return this._gridHtml(state, configState, this.config!.layout, color,
name, stateString);
}
}
private _gridHtml(
state: HassEntity | undefined,
configState: StateConfig | undefined,
containerClass: string,
color: string,
name: string | undefined,
stateString: string | undefined,
): TemplateResult {
const iconTemplate = this._getIconHtml(state, configState, color);
const itemClass: string[] = ['container', containerClass];
if (!iconTemplate) itemClass.push('no-icon');
if (!name) itemClass.push('no-name');
if (!stateString) itemClass.push('no-state');
return html`
<ha-card class="button-card-main ${this.isClickable(state) ? '' : 'disabled'}" style=${styleMap(style)} @ha-click="${this._handleTap}" @ha-hold="${this._handleHold}" .longpress="${longPress()}" .config="${this.config}">
${this.buttonContent(state, configState, color)}
<mwc-ripple></mwc-ripple>
</ha-card>
<div class=${itemClass.join(' ')}>
${iconTemplate ? iconTemplate : ''}
${name ? html`<div class="name">${name}</div>` : ''}
${stateString ? html`<div class="state">${stateString}</div>` : ''}
</div>
`;
}
private _getIconHtml(
state: HassEntity | undefined,
configState: StateConfig | undefined,
color: string,
): TemplateResult | undefined {
const icon = this._buildIcon(state, configState);
const entityPicture = this._buildEntityPicture(state, configState);
const entityPictureStyleFromConfig = this._buildEntityPictureStyle(state, configState);
const haIconStyle = {
color,
width: this.config!.size,
'min-width': this.config!.size,
};
const entityPictureStyle = {
...haIconStyle,
...entityPictureStyleFromConfig,
};
if (icon || entityPicture) {
return html`
<div class="img-cell">
${icon && !entityPicture ? html`<ha-icon style=${styleMap(haIconStyle)}
.icon="${icon}" class="icon" ?rotating=${this._rotate(configState)}></ha-icon>` : ''}
${entityPicture ? html`<img src="${entityPicture}" style=${styleMap(entityPictureStyle)}
class="icon" ?rotating=${this._rotate(configState)} />` : ''}
</div>
`;
} else {
return undefined;
}
}
public setConfig(config: ButtonCardConfig): void {
@ -496,6 +428,7 @@ class ButtonCard extends LitElement {
this.config = {
tap_action: { action: 'toggle' },
hold_action: { action: 'none' },
layout: 'vertical',
size: '40%',
color_type: 'icon',
show_name: true,

View File

@ -24,27 +24,10 @@ export const styles = css`
letter-spacing: normal;
width: 100%;
}
div.divTable{
display: table;
overflow: auto;
table-layout: fixed;
width: 100%;
}
div.divTableBody {
display: table-row-group;
}
div.divTableRow {
display: table-row;
}
.divTableCell {
display: table-cell;
vertical-align: middle;
}
div {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
min-width: 100%;
}
@keyframes blink{
0%{opacity:0;}
@ -79,13 +62,128 @@ export const styles = css`
transform: rotate(360deg);
}
}
.rotating {
[rotating] {
-webkit-animation: rotating 2s linear infinite;
-moz-animation: rotating 2s linear infinite;
-ms-animation: rotating 2s linear infinite;
-o-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite;
}
.container {
display: grid;
max-height: 100%;
text-align: center;
height: 100%;
align-items: center;
}
.img-cell {
grid-area: i;
min-height: 0;
min-width: 0;
}
.icon {
height: 100%;
max-width: 100%;
object-fit: scale;
overflow: hidden;
}
.name {
grid-area: n;
max-width: 100%;
align-self: center;
justify-self: center;
/* margin: auto; */
}
.state {
grid-area: s;
max-width: 100%;
align-self: center;
justify-self: center;
/* margin: auto; */
}
.container.vertical {
grid-template-areas: "i" "n" "s";
grid-template-columns: 1fr;
grid-template-rows: 1fr min-content min-content;
}
.container.vertical.no-icon {
grid-template-areas: "n" "s";
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
.container.vertical.no-icon .state {
align-self: start;
}
.container.vertical.no-icon .name {
align-self: end;
}
.container.vertical.no-icon.no-name {
grid-template-areas: "s";
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.container.vertical.no-icon.no-name .state {
align-self: center;
}
.container.vertical.no-icon.no-state {
grid-template-areas: "n";
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.container.vertical.no-icon.no-state .name {
align-self: center;
}
.container.icon_name_state {
grid-template-areas: "i n";
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr;
}
.container.icon_name {
grid-template-areas: "i n" "s s";
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content;
}
.container.icon_state {
grid-template-areas: "i s" "n n";
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content;
}
.container.name_state {
grid-template-areas: "i" "n";
grid-template-columns: 1fr;
grid-template-rows: 1fr min-content;
}
.container.icon_name_state2nd {
grid-template-areas: "i n" "i s";
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr 1fr;
}
.container.icon_name_state2nd .name {
align-self: end;
}
.container.icon_name_state2nd .state {
align-self: start;
}
.container.icon_state_name2nd {
grid-template-areas: "i s" "i n";
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr 1fr;
}
.container.icon_state_name2nd .state {
align-self: end;
}
.container.icon_state_name2nd .name {
align-self: start;
}
`;
export default styles;

View File

@ -28,7 +28,7 @@ export interface ButtonCardConfig {
style?: CssStyleConfig[];
state?: StateConfig[];
confirmation?: string;
layout?: 'vertical' | 'icon_name_state' | 'name_state' | 'icon_name' | 'icon_state' | 'icon_name_state2nd' | 'icon_state_name2nd';
layout: 'vertical' | 'icon_name_state' | 'name_state' | 'icon_name' | 'icon_state' | 'icon_name_state2nd' | 'icon_state_name2nd';
entity_picture_style?: CssStyleConfig[];
default_color: string;