diff --git a/README.md b/README.md index 7fc095e..0be9d39 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,9 @@ Bar Card is a customizable animated card for the Home Assistant Lovelace front-end. ### Features +- Customizable bar, text and animation. - Automatic animation mode. Starts animating based on value increase or decrease. - Charge based animation mode. Will show increase based on custom entity state. -- Customizable animation speed and delay. - ## Options @@ -18,6 +17,8 @@ Bar Card is a customizable animated card for the Home Assistant Lovelace front-e | ---- | ---- | ------- | ----------- | type | string | **Required** | `custom:bar-card` | entity | string | **Required** | Entity State +| entities | array | none | A list of entities. Cards will share config. +| columns | number | none | Defines the number of columns when using entities list. | attribute | string | none | Defines the attribute to be displayed. | unit_of_measurement | string | none | Defines the unit of measurement to be displayed. | title | string | none | Title displayed next to the bar. @@ -28,6 +29,7 @@ Bar Card is a customizable animated card for the Home Assistant Lovelace front-e | saturation | string | 50% | Scales saturation of the bar. | height | string | 40px | Scales the height of the bar. | width | string | 70% | Scales the width of the bar. +| padding | string | 4px | Sets padding amount around the card. | min | number | 0 | The minimum entity value to be displayed. Accepts entity id as value. | max | number | 100 | The maximum entity value to be displayed. Accepts entity id as value. | target | number | none | Sets a target marker, must be a value between min and max value. Accepts entity id as value. @@ -41,7 +43,9 @@ Bar Card is a customizable animated card for the Home Assistant Lovelace front-e | indicator_style | object| none | A list of CSS styles applied to the indicator. | charge_entity | string | none | Charge enitity, **required** when using charge animation mode. States can be `on`/`off`, `true`/`false`, `charging`/`discharging` -## Default +## Examples + +### Default ![](images/default_increase.gif) @@ -50,7 +54,7 @@ Bar Card is a customizable animated card for the Home Assistant Lovelace front-e title: Default entity: sensor.default ``` -## Severity +### Severity ![](images/severity.gif) @@ -66,7 +70,7 @@ Bar Card is a customizable animated card for the Home Assistant Lovelace front-e - value: 100 hue: '120' ``` -## Hue +### Hue ![](images/hue.gif) @@ -76,7 +80,7 @@ Bar Card is a customizable animated card for the Home Assistant Lovelace front-e entity: sensor.default hue: 300 ``` -## Speed & Delay +### Speed & Delay ![](images/speed_delay.gif) @@ -87,6 +91,79 @@ Bar Card is a customizable animated card for the Home Assistant Lovelace front-e speed: 5000 delay: 10000 ``` + +### Entities + [Auto Entities](https://github.com/thomasloven/lovelace-auto-entities) + +![](images/entities_example.png) + +```yaml +- type: custom:auto-entities + card: + type: custom:bar-card + severity: + - value: 50 + hue: '0' + - value: 75 + hue: '40' + - value: 100 + hue: '120' + title_position: inside + columns: 3 + padding: 2px + title_style: + text-align: left + font-size: 16px + bar_style: + align-items: flex-start + font-size: 8px + filter: + include: + - entity_id: "*_battery" + +- type: custom:auto-entities + card: + type: custom:bar-card + attribute: battery + unit_of_measurement: "%" + severity: + - value: 50 + hue: '0' + - value: 75 + hue: '40' + - value: 100 + hue: '120' + title_position: inside + padding: 2px + columns: 2 + filter: + include: + - attributes: + battery: "<=100" + exclude: + - entity_id: "*_humidity" + - entity_id: "*_pressure" + - entity_id: "*_battery" + +- type: custom:bar-card + attribute: temperature + unit_of_measurement: "°C" + title_position: inside + columns: 5 + height: 200px + min: 15 + max: 32 + target: 22.5 + width: 100% + padding: 2px + direction: up + entities: + - binary_sensor.motion_hallway + - binary_sensor.motion_living_room + - binary_sensor.motion_kitchen + - binary_sensor.motion_bathroom + - binary_sensor.motion_toilet +``` + ## Credits Inspired by [Big Number Card](https://github.com/ciotlosm/custom-lovelace/tree/master/bignumber-card) by [ciotlosm](https://github.com/ciotlosm). diff --git a/bar-card.js b/bar-card.js index 6154061..8eb36ee 100644 --- a/bar-card.js +++ b/bar-card.js @@ -4,9 +4,6 @@ class BarCard extends HTMLElement { this.attachShadow({ mode: 'open' }) } setConfig (config) { - // Throw error if no entity defined - if (!config.entity) throw new Error('Please define an entity') - // Default Card variables if (!config.height) config.height = '40px' if (!config.direction) config.direction = 'right' @@ -19,6 +16,30 @@ class BarCard extends HTMLElement { if (!config.delay) config.delay = 5000 if (!config.min) config.min = 0 if (!config.max) config.max = 100 + if (!config.padding) config.padding = '4px' + + // Check entity types + let updateArray = false + let updateEntity = false + if (config.entities) { + let newArray = [] + config.entities.forEach(section => { + let type = typeof(section); + if (type == 'string'){ + let constructObject = {"entity":section}; + newArray.push(constructObject) + updateArray = true + } else { + updateEntity = true + } + + }) + if(updateArray == true){ + config.entities = newArray; + } + } else if (config.entity) { + config.entities = [{"entity":config.entity}] + } // Check if title position is inside if (!config.width) { @@ -27,9 +48,159 @@ class BarCard extends HTMLElement { } else { config.width = '100%' } + if (config.title_position == "top" || config.title_position == "bottom"){ + config.width = '100%' + } + } + + // Define card container + let cardContainer = document.createElement('card-container') + let cardContainerStyle = document.createElement('style') + cardContainerStyle.textContent = ` + card-container { + display: flex; + justify-content: space-around; + flex-wrap: wrap; + } + + ` + // For each entity in entities list + for (var i = 0; i <= config.entities.length-1; i++){ + let entityName = config.entities[i].entity.split('.') + cardContainer.appendChild(this._cardElements(config, entityName[0]+'_'+entityName[1]+i, config.entities[i].entity)) + } + + this.shadowRoot.appendChild(cardContainer) + this.shadowRoot.appendChild(cardContainerStyle) + + // For each entity in entities list. Initial update. + if (updateEntity == true) { + for(var i=0; i <= config.entities.length-1; i++){ + let entityName = config.entities[i].entity.split('.') + this._updateEntity(config.entities[i].entity, entityName[0]+'_'+entityName[1]+i) + } + } + + this._config = config + } + + // On hass update + set hass (hass) { + this._hass = hass + const config = this._config + for(var i=0; i <= config.entities.length-1; i++){ + let entityName = config.entities[i].entity.split('.') + this._updateEntity(config.entities[i].entity, entityName[0]+'_'+entityName[1]+i) + } + } + + _cardElements(config, id, entity) { + // Create card elements + const card = document.createElement('ha-card') + const container = document.createElement('div') + container.id = 'container_'+id + const background = document.createElement('div') + background.id = 'background_'+id + const backgroundBar = document.createElement('div') + backgroundBar.id = 'backgroundBar_'+id + const bar = document.createElement('div') + bar.id = 'bar_'+id + const value = document.createElement('div') + value.id = 'value_'+id + + // Check if animation is enabled + if (config.animation !== "off") { + var chargeBar = document.createElement('div') + chargeBar.id = 'chargeBar_'+id } + // Check if target is defined + if (config.target) { + var targetBar = document.createElement('div') + targetBar.id = 'targetBar_'+id + var targetBarColor = document.createElement('div') + targetBarColor.id = 'targetBarColor_'+id + var targetMarker = document.createElement('div') + targetMarker.id = 'targetMarker_'+id + var targetMarkerColor = document.createElement('div') + targetMarkerColor.id = 'targetMarkerColor_'+id + } + + // Check if indicator is enabled + if (config.indicator !== "off"){ + var indicatorContainer = document.createElement('div') + indicatorContainer.id = 'indicatorContainer_'+id + var indicatorBar = document.createElement('div') + indicatorBar.id = 'indicatorBar_'+id + var indicator = document.createElement('div') + indicator.id = 'indicator_'+id + var indicatorColor = document.createElement('div') + indicatorColor.id = 'indicatorColor_'+id + } + // Check if title is not inside + if (config.title !== "inside"){ + var title = document.createElement('div') + title.id = 'title_'+id + var titleBar = document.createElement('div') + titleBar.id = 'titleBar_'+id + } + + // Start building card + background.appendChild(backgroundBar) + background.appendChild(bar) + + // Check if animation is not disabled + if (config.animation !== "off") { + background.appendChild(chargeBar) + } + + // Check if target is configured + if (config.target) { + targetBar.appendChild(targetMarker) + targetBarColor.appendChild(targetMarkerColor) + background.appendChild(targetBar) + background.appendChild(targetBarColor) + } + + // Check if indicator is not disabled + if (config.indicator != 'off') { + indicatorContainer.appendChild(indicator) + indicatorContainer.appendChild(indicatorColor) + indicatorBar.appendChild(indicatorContainer) + background.appendChild(indicatorBar) + } + + // Select title position + switch (config.title_position) { + case 'left': + case 'right': + case 'top': + case 'bottom': + if (config.title_position != 'inside') { + titleBar.appendChild(title) + container.appendChild(titleBar) + } + container.appendChild(background) + break + case 'inside': + background.appendChild(title) + container.appendChild(background) + } + + background.appendChild(value) + card.appendChild(container) + card.appendChild(this._styleElements(config, id)) + + card.addEventListener('click', event => { + this._showAttributes('hass-more-info', { entityId: entity }) + }) + + return card + } + + _styleElements(config, id) { + const style = document.createElement('style'); // Create CSS style variables if (config.bar_style) var barStyle = this._customStyle(config.bar_style) if (config.title_style) var titleStyle = this._customStyle(config.title_style) @@ -66,14 +237,17 @@ class BarCard extends HTMLElement { // Set marker direction based on card direction let markerDirection let barFrom + let insideWhitespace switch (config.direction) { case 'left': barFrom = 'left' markerDirection = 'right' + insideWhitespace = 'nowrap' break case 'right': barFrom = 'right' markerDirection = 'left' + insideWhitespace = 'nowrap' break case 'up': barFrom = 'top' @@ -85,117 +259,98 @@ class BarCard extends HTMLElement { break } - // Create card elements - const card = document.createElement('ha-card') - const container = document.createElement('div') - container.id = 'container' - const background = document.createElement('div') - background.id = 'background' - const backgroundBar = document.createElement('div') - backgroundBar.id = 'backgroundBar' - const bar = document.createElement('div') - bar.id = 'bar' - const value = document.createElement('div') - value.id = 'value' - const style = document.createElement('style') - - // Check if animation is enabled - if (config.animation !== "off") { - var chargeBar = document.createElement('div') - chargeBar.id = 'chargeBar' - } - - // Check if target is defined - if (config.target) { - var targetBar = document.createElement('div') - targetBar.id = 'targetBar' - var targetBarColor = document.createElement('div') - targetBarColor.id = 'targetBarColor' - var targetMarker = document.createElement('div') - targetMarker.id = 'targetMarker' - var targetMarkerColor = document.createElement('div') - targetMarkerColor.id = 'targetMarkerColor' - } - - // Check if indicator is enabled - if (config.indicator !== "off"){ - var indicatorContainer = document.createElement('div') - indicatorContainer.id = 'indicatorContainer' - var indicatorBar = document.createElement('div') - indicatorBar.id = 'indicatorBar' - var indicator = document.createElement('div') - indicator.id = 'indicator' - var indicatorColor = document.createElement('div') - indicatorColor.id = 'indicatorColor' - } - - // Check if title is not inside - if (config.title !== "inside"){ - var title = document.createElement('div') - title.id = 'title' - var titleBar = document.createElement('div') - titleBar.id = 'titleBar' - title.textContent = config.title - } - // Set marker style based on bar direction let markerStyle if (barFrom == 'left' || barFrom == 'right') { markerStyle = ` - #targetMarker, #targetMarkerColor { + #targetMarker_${id}, #targetMarkerColor_${id} { position: absolute; background: #FFF0; ` + markerDirection + `: var(--targetMarker-percent); height: ${config.height}; border-left: 2px dashed var(--targetMarker-color); } - #targetMarkerColor { + #targetMarkerColor_${id} { border-left: 2px dashed var(--bar-fill-color); } ` } else { markerStyle = ` - #targetMarker, #targetMarkerColor { + #targetMarker_${id}, #targetMarkerColor_${id} { position: absolute; background: #FFF0; ` + markerDirection + `: var(--targetMarker-percent); width: 100%; border-top: 2px dashed var(--targetMarker-color); } - #targetMarkerColor { + #targetMarkerColor_${id} { border-top: 2px dashed var(--bar-fill-color); } ` } + // Set title style based on title position + let positionTitleStyle + if (config.title_position == 'inside') { + positionTitleStyle = ` + width: calc(100% - 8px); + text-align: center; + z-index: 1; + font-weight: bold; + color: #FFF; + text-shadow: 1px 1px #000; + padding-left: 4px; + padding-right: 4px; + text-overflow: ellipsis; + overflow: hidden; + white-space: ${insideWhitespace}; + ` + } else { + positionTitleStyle = ` + color: var(--primary-text-color); + padding-left: 10px; + padding-right: 10px; + text-overflow: ellipsis; + overflow: hidden; + ` + } + // Set CSS styles + + let haCardWidth + if (config.columns) { + haCardWidth = Math.trunc(100 / Number(config.columns)) + } else { + haCardWidth = 100; + } style.textContent = ` ha-card { background-color: var(--paper-card-background-color); - padding: 4px; + padding: ${config.padding}; + width: calc(${haCardWidth}% - (${config.padding} * 2)); ` + cardStyle + ` } - #container { + #container_${id} { display: flex; align-items: center; justify-content: center; ` + flexDirection + ` } - #background { + #background_${id} { position: relative; display: flex; + flex-direction: column; align-items: center; justify-content: center; width: ${config.width}; height: ${config.height}; + ` + barStyle + ` } - #title { - color: var(--primary-text-color); - padding-left: 10px; - padding-right: 10px; + #title_${id} { + ` + positionTitleStyle + ` ` + titleStyle + ` } - #titleBar { + #titleBar_${id} { position: relative; display: flex; align-items: center; @@ -204,45 +359,46 @@ class BarCard extends HTMLElement { ` + titleWidth + ` ` + titleStyle + ` } - #bar, #backgroundBar, #targetBar, #targetBarColor, #valueBar, #chargeBar, #chargeBarColor, #valueBar, #indicatorBar { + #bar_${id}, #backgroundBar_${id}, #targetBar_${id}, #targetBarColor_${id}, #valueBar_${id}, #chargeBar_${id}, #chargeBarColor_${id}, #valueBar_${id}, #indicatorBar_${id} { position: absolute; height: 100%; width: 100%; border-radius: ${config.rounding}; - ` + barStyle + ` } - #backgroundBar { + #backgroundBar_${id} { background: var(--bar-background-color); } - #bar { + #bar_${id} { background: linear-gradient(to ${barFrom}, var(--bar-fill-color) var(--bar-percent), #0000 var(--bar-percent), #0000 var(--bar-percent)); } - #chargeBar { + #chargeBar_${id} { background: linear-gradient(to ${barFrom}, #FFF0 var(--bar-percent), var(--bar-charge-color) var(--bar-percent), var(--bar-charge-color) var(--bar-charge-percent), #FFF0 var(--bar-charge-percent)); } - #targetBar { + #targetBar_${id} { background: linear-gradient(to ${barFrom}, #FFF0 var(--targetBar-left-percent), var(--targetBar-color) var(--targetBar-left-percent), var(--targetBar-color) var(--targetBar-right-percent), #FFF0 var(--targetBar-right-percent)); mix-blend-mode: difference; } - #targetBarColor { + #targetBarColor_${id} { background: linear-gradient(to ${barFrom}, #FFF0 var(--targetBar-left-percent), var(--bar-fill-color) var(--targetBar-left-percent), var(--bar-fill-color) var(--targetBar-right-percent), #FFF0 var(--targetBar-right-percent)); mix-blend-mode: color; } - #value { - white-space: pre-line; + #value_${id} { z-index: 1; + //width: 100%; + padding-left: 4px; + padding-right: 4px; text-align: center; font-weight: bold; color: #FFF; text-shadow: 1px 1px #000; ` + barStyle + ` } - #indicatorBar { + #indicatorBar_${id} { display: flex; align-items: var(--flex-align); justify-content: var(--flex-justify); } - #indicator, #indicatorColor { + #indicator_${id}, #indicatorColor_${id} { width: 25px; text-align: center; position: static; @@ -252,73 +408,21 @@ class BarCard extends HTMLElement { text-shadow: 0px 0px; ` + indicatorStyle + ` } - #indicatorColor { + #indicatorColor_${id} { margin-left: -25px; color: var(--bar-fill-color); mix-blend-mode: color; opacity: 1; ` + indicatorStyle + ` } - #indicatorContainer { + #indicatorContainer_${id} { display: flex; margin-top: 5px; margin-bottom: 5px; } ` + markerStyle + ` ` - - // Start building card - background.appendChild(backgroundBar) - background.appendChild(bar) - - // Check if animation is not disabled - if (config.animation !== "off") { - background.appendChild(chargeBar) - } - - // Check if target is configured - if (config.target) { - targetBar.appendChild(targetMarker) - targetBarColor.appendChild(targetMarkerColor) - background.appendChild(targetBar) - background.appendChild(targetBarColor) - } - - // Check if indicator is not disabled - if (config.indicator != 'off') { - indicatorContainer.appendChild(indicator) - indicatorContainer.appendChild(indicatorColor) - indicatorBar.appendChild(indicatorContainer) - background.appendChild(indicatorBar) - } - - // Select title position - switch (config.title_position) { - case 'left': - case 'right': - case 'top': - case 'bottom': - if (config.title_position != 'inside') { - titleBar.appendChild(title) - container.appendChild(titleBar) - } - container.appendChild(background) - break - case 'inside': - container.appendChild(background) - } - - background.appendChild(value) - card.appendChild(container) - card.appendChild(style) - - // Add on click event to card - card.addEventListener('click', event => { - this._showAttributes('hass-more-info', { entityId: config.entity }) - }) - - this.shadowRoot.appendChild(card) - this._config = config + return style } // Create style string from CSS options @@ -378,20 +482,20 @@ class BarCard extends HTMLElement { } // Update bar percentages - _updateBar (entityState, hass) { + _updateBar (entityState, hass, id) { const config = this._config const root = this.shadowRoot if (this._valueEntityCheck(config.min, hass) !== undefined && this._valueEntityCheck(config.max, hass) !== undefined) { - root.getElementById('bar').style.setProperty('--bar-percent', `${this._translatePercent(entityState, this._valueEntityCheck(config.min, hass), this._valueEntityCheck(config.max, hass))}%`) - root.getElementById('bar').style.setProperty('--bar-charge-percent', `${this._translatePercent(entityState, this._valueEntityCheck(config.min, hass), this._valueEntityCheck(config.max, hass))}%`) + root.getElementById('bar_'+id).style.setProperty('--bar-percent', `${this._translatePercent(entityState, this._valueEntityCheck(config.min, hass), this._valueEntityCheck(config.max, hass))}%`) + root.getElementById('bar_'+id).style.setProperty('--bar-charge-percent', `${this._translatePercent(entityState, this._valueEntityCheck(config.min, hass), this._valueEntityCheck(config.max, hass))}%`) } } // Create animation - _updateAnimation (entityState, configDirection, configDuration, hue, configStop) { + _updateAnimation (entityState, configDirection, configDuration, hue, configStop, id) { const config = this._config const root = this.shadowRoot - const element = root.getElementById('chargeBar') + const element = root.getElementById('chargeBar_'+id) let currentPercent = this._translatePercent(entityState, this._valueEntityCheck(config.min, this._hass), this._valueEntityCheck(config.max, this._hass)) let totalFrames = ((currentPercent) * 10) + (config.delay / (config.speed / 250)) @@ -434,66 +538,66 @@ class BarCard extends HTMLElement { } // Sets position and direction of the indicator - _updateIndicator (position, direction) { + _updateIndicator (position, direction, id) { const config = this._config const root = this.shadowRoot switch (position) { case 'right': - root.getElementById('indicatorBar').style.setProperty('--flex-align', 'center') - root.getElementById('indicatorBar').style.setProperty('--flex-justify', 'flex-end') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-align', 'center') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-justify', 'flex-end') break case 'left': - root.getElementById('indicatorBar').style.setProperty('--flex-align', 'center') - root.getElementById('indicatorBar').style.setProperty('--flex-justify', 'flex-start') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-align', 'center') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-justify', 'flex-start') break case 'top': - root.getElementById('indicatorBar').style.setProperty('--flex-align', 'flex-start') - root.getElementById('indicatorBar').style.setProperty('--flex-justify', 'center') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-align', 'flex-start') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-justify', 'center') break case 'bottom': - root.getElementById('indicatorBar').style.setProperty('--flex-align', 'flex-end') - root.getElementById('indicatorBar').style.setProperty('--flex-justify', 'center') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-align', 'flex-end') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-justify', 'center') break case 'auto': switch (direction) { case 'up': - root.getElementById('indicatorBar').style.setProperty('--flex-align', 'center') - root.getElementById('indicatorBar').style.setProperty('--flex-justify', 'flex-end') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-align', 'center') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-justify', 'flex-end') break case 'down': - root.getElementById('indicatorBar').style.setProperty('--flex-align', 'center') - root.getElementById('indicatorBar').style.setProperty('--flex-justify', 'flex-start') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-align', 'center') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-justify', 'flex-start') break } break case 'auto-vertical': switch (direction) { case 'up': - root.getElementById('indicatorBar').style.setProperty('--flex-align', 'flex-start') - root.getElementById('indicatorBar').style.setProperty('--flex-justify', 'center') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-align', 'flex-start') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-justify', 'center') break case 'down': - root.getElementById('indicatorBar').style.setProperty('--flex-align', 'flex-end') - root.getElementById('indicatorBar').style.setProperty('--flex-justify', 'center') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-align', 'flex-end') + root.getElementById('indicatorBar_'+id).style.setProperty('--flex-justify', 'center') break } } if (config.indicator != 'off') { switch (direction) { case 'up': - root.getElementById('indicator').textContent = '▲' - root.getElementById('indicatorColor').textContent = '▲' + root.getElementById('indicator_'+id).textContent = '▲' + root.getElementById('indicatorColor_'+id).textContent = '▲' break case 'down': - root.getElementById('indicator').textContent = '▼' - root.getElementById('indicatorColor').textContent = '▼' + root.getElementById('indicator_'+id).textContent = '▼' + root.getElementById('indicatorColor_'+id).textContent = '▼' break } } } // Scale the target bar size - _updateTargetBar (entityState, target, color, markerColor) { + _updateTargetBar (entityState, target, color, markerColor, id) { const config = this._config const root = this.shadowRoot @@ -509,22 +613,28 @@ class BarCard extends HTMLElement { initialPercent = currentPercent diffPercent = targetPercent } - root.getElementById('targetBar').style.setProperty('--targetBar-left-percent', initialPercent + '%') - root.getElementById('targetBarColor').style.setProperty('--targetBar-left-percent', initialPercent + '%') - root.getElementById('targetBar').style.setProperty('--targetBar-right-percent', diffPercent + '%') - root.getElementById('targetBarColor').style.setProperty('--targetBar-right-percent', diffPercent + '%') - root.getElementById('targetBar').style.setProperty('--targetBar-color', color) - - root.getElementById('targetMarker').style.setProperty('--targetMarker-percent', targetPercent + '%') - root.getElementById('targetMarkerColor').style.setProperty('--targetMarker-percent', targetPercent + '%') - root.getElementById('targetMarker').style.setProperty('--targetMarker-color', markerColor) + root.getElementById('targetBar_'+id).style.setProperty('--targetBar-left-percent', initialPercent + '%') + root.getElementById('targetBarColor_'+id).style.setProperty('--targetBar-left-percent', initialPercent + '%') + root.getElementById('targetBar_'+id).style.setProperty('--targetBar-right-percent', diffPercent + '%') + root.getElementById('targetBarColor_'+id).style.setProperty('--targetBar-right-percent', diffPercent + '%') + root.getElementById('targetBar_'+id).style.setProperty('--targetBar-color', color) + + root.getElementById('targetMarker_'+id).style.setProperty('--targetMarker-percent', targetPercent + '%') + root.getElementById('targetMarkerColor_'+id).style.setProperty('--targetMarker-percent', targetPercent + '%') + root.getElementById('targetMarker_'+id).style.setProperty('--targetMarker-color', markerColor) } - // On hass update - set hass (hass) { - this._hass = hass + _updateEntity (entity, id) { const config = this._config const root = this.shadowRoot + const hass = this._hass + if (!config.entity) { + config.title = hass.states[entity].attributes.friendly_name + } + + root.getElementById('title_'+id).textContent = config.title + + if (!this._entityState) this._entityState = [] // Define variables that have possible entities let configTarget @@ -534,21 +644,23 @@ class BarCard extends HTMLElement { // Check for unknown state let entityState - if (hass.states[config.entity] == undefined || hass.states[config.entity].state == 'unknown') { + if (hass.states[entity] == undefined || hass.states[entity].state == 'unknown') { entityState = 'N/A' } else { if (config.attribute) { - entityState = hass.states[config.entity].attributes[config.attribute] + entityState = hass.states[entity].attributes[config.attribute] } else { - entityState = hass.states[config.entity].state + entityState = hass.states[entity].state } + if (!isNaN(entityState)) { entityState = Math.min(entityState, configMax) entityState = Math.max(entityState, configMin) + } } let measurement - if (hass.states[config.entity] == undefined || hass.states[config.entity].state == 'unknown') measurement = '' + if (hass.states[entity] == undefined || hass.states[entity].state == 'unknown') measurement = '' else if (config.unit_of_measurement) measurement = config.unit_of_measurement - else measurement = hass.states[config.entity].attributes.unit_of_measurement || '' + else measurement = hass.states[entity].attributes.unit_of_measurement || '' // Set color hue let hue @@ -567,55 +679,57 @@ class BarCard extends HTMLElement { const targetMarkerColor = 'hsla(' + hue + ',' + config.saturation + ',50%,0.5)' const backgroundColor = 'hsla(' + hue + ',' + config.saturation + ',15%,0.5)' + // Define target, min and max if not defined + if (!this._entityTarget) this._entityTarget = {} + if (!this._currentMin) this._currentMin = {} + if (!this._currentMax) this._currentMax = {} + // On entity update - if (entityState !== this._entityState) { - this._updateBar(entityState, hass) + if (entityState !== this._entityState[id]) { + this._updateBar(entityState, hass, id) if (config.target) { - this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor) - this._entityTarget = configTarget + this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor, id) + this._entityTarget[id] = configTarget } - root.getElementById('bar').style.setProperty('--bar-fill-color', barColor) - root.getElementById('backgroundBar').style.setProperty('--bar-background-color', backgroundColor) - if (config.indicator != 'off') root.getElementById('indicatorColor').style.setProperty('--bar-fill-color', barColor) + root.getElementById('bar_'+id).style.setProperty('--bar-fill-color', barColor) + root.getElementById('backgroundBar_'+id).style.setProperty('--bar-background-color', backgroundColor) + if (config.indicator != 'off') root.getElementById('indicatorColor_'+id).style.setProperty('--bar-fill-color', barColor) if (config.target) { - root.getElementById('targetBarColor').style.setProperty('--bar-fill-color', barColor) - root.getElementById('targetMarkerColor').style.setProperty('--bar-fill-color', barColor) - } - if (config.title_position == 'inside') { - root.getElementById('value').textContent = `${config.title} \r\n${entityState} ${measurement}` - } else { - root.getElementById('value').textContent = `${entityState} ${measurement}` + root.getElementById('targetBarColor_'+id).style.setProperty('--bar-fill-color', barColor) + root.getElementById('targetMarkerColor_'+id).style.setProperty('--bar-fill-color', barColor) } + root.getElementById('value_'+id).textContent = `${entityState} ${measurement}` } + if (!this._currentAnimation) this._currentAnimation = {} // Select 'auto' animation if (config.animation == 'auto') { - if (entityState > this._entityState) { - this._updateIndicator(config.indicator, 'up') - this._currentAnimation = this._updateAnimation(entityState, 'normal', config.delay, hue, config.saturation, false) + if (entityState > this._entityState[id]) { + this._updateIndicator(config.indicator, 'up', id) + this._currentAnimation[id] = this._updateAnimation(entityState, 'normal', config.delay, hue, false, id) } - if (entityState < this._entityState) { - this._updateIndicator(config.indicator, 'down') - this._currentAnimation = this._updateAnimation(entityState, 'reverse', config.delay, hue, config.saturation, false) + if (entityState < this._entityState[id]) { + this._updateIndicator(config.indicator, 'down', id) + this._currentAnimation[id] = this._updateAnimation(entityState, 'reverse', config.delay, hue, false, id) } if (entityState == configMax || entityState == configMin) { if (config.indicator != 'off') { - root.getElementById('indicator').textContent = '' - root.getElementById('indicatorColor').textContent = '' + root.getElementById('indicator_'+id).textContent = '' + root.getElementById('indicatorColor_'+id).textContent = '' } if (entityState == configMax) { - root.getElementById('bar').style.setProperty('--bar-percent', '100%') - root.getElementById('bar').style.setProperty('--bar-fill-color', barColor) - root.getElementById('bar').style.setProperty('--bar-charge-percent', '100%') - if (this._currentAnimation) { - this._currentAnimation.pause() + root.getElementById('bar_'+id).style.setProperty('--bar-percent', '100%') + root.getElementById('bar_'+id).style.setProperty('--bar-fill-color', barColor) + root.getElementById('bar_'+id).style.setProperty('--bar-charge-percent', '100%') + if (this._currentAnimation[id]) { + this._currentAnimation[id].pause() } } if (entityState == configMin) { - root.getElementById('bar').style.setProperty('--bar-percent', '0%') - root.getElementById('bar').style.setProperty('--bar-charge-percent', '0%') - if (this._currentAnimation) { - this._currentAnimation.pause() + root.getElementById('bar_'+id).style.setProperty('--bar-percent', '0%') + root.getElementById('bar_'+id).style.setProperty('--bar-charge-percent', '0%') + if (this._currentAnimation[id]) { + this._currentAnimation[id].pause() } } } @@ -635,20 +749,20 @@ class BarCard extends HTMLElement { case "charging": case "on": case "true": - this._updateIndicator(config.indicator, 'up') - if (!this._currentAnimation || chargeEntityState != this._currentChargeState || entityState > this._entityState) { + this._updateIndicator(config.indicator, 'up', id) + if (!this._currentAnimation || chargeEntityState != this._currentChargeState || entityState > this._entityState[id]) { this._currentChargeState = chargeEntityState - this._currentAnimation = this._updateAnimation(entityState, 'normal', config.delay, hue, config.saturation, false) + this._currentAnimation = this._updateAnimation(entityState, 'normal', config.delay, hue, false, id) } break case "discharging": case "off": case "false": if (chargeEntityState == 'discharging' || chargeEntityState == 'off' || chargeEntityState == 'false') { - this._updateIndicator(config.indicator, 'down') - if (!this._currentAnimation || chargeEntityState != this._currentChargeState || entityState < this._entityState) { + this._updateIndicator(config.indicator, 'down', id) + if (!this._currentAnimation || chargeEntityState != this._currentChargeState || entityState < this._entityState[id]) { this._currentChargeState = chargeEntityState - this._currentAnimation = this._updateAnimation(entityState, 'reverse', config.delay, hue, config.saturation, false) + this._currentAnimation = this._updateAnimation(entityState, 'reverse', config.delay, hue, false, id) } } break @@ -657,43 +771,44 @@ class BarCard extends HTMLElement { // Select 'off' animation if (config.animation == "off") { - if (entityState > this._entityState) { - this._updateIndicator(config.indicator, 'up') + if (entityState > this._entityState[id]) { + this._updateIndicator(config.indicator, 'up', id) } - if (entityState < this._entityState) { - this._updateIndicator(config.indicator, 'down') + if (entityState < this._entityState[id]) { + this._updateIndicator(config.indicator, 'down', id) } } // On target update if (config.target) { - if (configTarget != this._entityTarget) { - this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor) - this._entityTarget = configTarget + if (configTarget != this._entityTarget[id]) { + this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor, id) + this._entityTarget[id] = configTarget } } // On min update if (configMin !== this._currentMin) { - this._updateBar(entityState, hass) - this._currentMin = configMin + this._updateBar(entityState, hass, id) + this._currentMin[id] = configMin if (config.target) { - this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor) - this._entityTarget = configTarget + this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor, id) + this._currentMin[id] = configMin } } // On max update if (configMax !== this._currentMax) { - this._updateBar(entityState, hass) - this._currentMax = configMax + this._updateBar(entityState, hass, id) + this._currentMax[id] = configMax if (config.target) { - this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor) - this._entityTarget = configTarget + this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor, id) + this._currentMax[id] = configMax } } - this._entityState = entityState + this._entityState[id] = entityState } + getCardSize () { return 1 } diff --git a/images/entities_example.png b/images/entities_example.png new file mode 100644 index 0000000..e7b929f Binary files /dev/null and b/images/entities_example.png differ diff --git a/tracker.json b/tracker.json index be1cba6..2430ab5 100644 --- a/tracker.json +++ b/tracker.json @@ -1,8 +1,8 @@ { "bar-card": { - "updated_at": "2019-03-16", - "version": "0.1.1", - "remote_location": "https://github.com/Gluwc/bar-card/releases/download/0.1.1/bar-card.js", + "updated_at": "2019-03-17", + "version": "0.1.2", + "remote_location": "https://github.com/Gluwc/bar-card/releases/download/0.1.2/bar-card.js", "visit_repo": "https://github.com/Gluwc/bar-card", "changelog": "https://github.com/Gluwc/bar-card/releases/latest" }