diff --git a/README.md b/README.md index 9861c4e..40fb7cd 100644 --- a/README.md +++ b/README.md @@ -21,19 +21,19 @@ 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 | title | string | none | Title displayed next to the bar. -| title_position | string | left | Position of the title. `left`,`right`, `inside` +| title_position | string | left | Position of the title. `left`,`right`,`top`,`bottom`,`inside` | animation | string | auto | Sets the mode of animation `auto`, `charge`, `off`. -| indicator | string | auto | Sets position off the indicator `auto`,`left`,`right`,`off` +| indicator | string | auto | Sets position off the indicator `auto`,`auto-vertical`,`left`,`right`,`top`,`bottom`,`off` | hue | number | 220 | Changed the color hue of the bar `0`-`360`. | 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. -| min | number | 0 | The minimum entity value to be displayed. -| max | number | 100 | The maximum entity value to be displayed. -| target | number | none | Sets a target marker. Must be a value between min and max value. +| 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. | speed | number | 2500 | The speed of the bar animation in milliseconds. | delay | number| 7500 | The amout of time between the bar animation loop in milliseconds. -| from | string | left | Direction of the bar. `left`,`right` +| direction | string | left | Direction of the bar. `left`,`right`,`up`,`down` | severity | object | none | A list of severity values. | card_style | object | none | A list of CSS styles applied to the card background. | bar_style | object | none | A list of CSS styles applied to the bar. @@ -88,7 +88,7 @@ Bar Card is a customizable animated card for the Home Assistant Lovelace front-e delay: 10000 ``` ## Credits -Based on [Big Number Card](https://github.com/ciotlosm/custom-lovelace/tree/master/bignumber-card) by [ciotlosm](https://github.com/ciotlosm). +Inspired by [Big Number Card](https://github.com/ciotlosm/custom-lovelace/tree/master/bignumber-card) by [ciotlosm](https://github.com/ciotlosm). ## Links [Home Assistant Community Topic](https://community.home-assistant.io/t/lovelace-bar-card/87503) diff --git a/bar-card.js b/bar-card.js index ecb0cbf..ef2cdc2 100644 --- a/bar-card.js +++ b/bar-card.js @@ -1,511 +1,657 @@ class BarCard extends HTMLElement { - constructor() { - super(); - this.attachShadow({ mode: 'open' }); + constructor () { + super() + this.attachShadow({ mode: 'open' }) } - setConfig(config) { - if (!config.entity) { - throw new Error('Please define an entity'); - } - - // Remove shadowroot if lastChild. - const root = this.shadowRoot; - if (root.lastChild) root.removeChild(root.lastChild); + 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.from) config.from = "left"; - if (!config.rounding) config.rounding = "3px"; - if (!config.width) config.width = "70%"; - if (!config.min) config.min = 0; - if (!config.max) config.max = 100; - if (!config.title_position) config.title_position = "left"; - if (!config.indicator) config.indicator = "auto"; - //config.target = 3; + if (!config.height) config.height = '40px' + if (!config.direction) config.direction = 'right' + if (!config.rounding) config.rounding = '3px' + if (!config.width) config.width = '70%' + if (!config.title_position) config.title_position = 'left' + if (!config.indicator) config.indicator = 'auto' + if (!config.saturation) config.saturation = '50%' + if (!config.animation) config.animation = 'auto' + if (!config.speed) config.speed = 1000 + if (!config.delay) config.delay = 5000 + if (!config.min) config.min = 0 + if (!config.max) config.max = 100 // 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); - if(config.indicator_style) var indicatorStyle = this._customStyle(config.indicator_style); - if(config.card_style) var cardStyle = this._customStyle(config.card_style); - + if (config.bar_style) var barStyle = this._customStyle(config.bar_style) + if (config.title_style) var titleStyle = this._customStyle(config.title_style) + if (config.indicator_style) var indicatorStyle = this._customStyle(config.indicator_style) + if (config.card_style) var cardStyle = this._customStyle(config.card_style) + // Sets position of the title - let titlePosition; - let barPosition; - switch(config.title_position){ - case "left": - titlePosition = "left: 0px;"; - barPosition = "right: 0px;"; - break; - case "right": - titlePosition = "right: 0px;"; - barPosition = "left: 0px;"; - break; - case "inside": - config.width = "100%"; + let titleAlign + let titleWidth + let flexDirection + switch (config.title_position) { + case 'left': + titleWidth = 'width: calc(100% - ' + config.width + ');' + titleAlign = 'justify-content: flex-start;' + flexDirection = 'flex-direction: row;' + break + case 'right': + titleWidth = 'width: calc(100% - ' + config.width + ');' + titleAlign = 'justify-content: flex-start;' + flexDirection = 'flex-direction: row-reverse;' + break + case 'top': + titleWidth = 'width: 100%;' + titleAlign = 'justify-content: center;' + flexDirection = 'flex-direction: column;' + break + case 'bottom': + titleWidth = 'width: 100%;' + titleAlign = 'justify-content: center;' + flexDirection = 'flex-direction: column-reverse;' + break + } + + // Set marker direction based on card direction + let markerDirection + let barFrom + switch (config.direction) { + case 'left': + barFrom = 'left' + markerDirection = 'right' + break + case 'right': + barFrom = 'right' + markerDirection = 'left' + break + case 'up': + barFrom = 'top' + markerDirection = 'bottom' + break + case 'down': + barFrom = 'bottom' + markerDirection = 'top' + break } - // Config adjustments. - if (config.from == "left") config.from = "right"; - else config.from = "right"; - - // Create card elements. - const card = document.createElement('ha-card'); - const background = document.createElement('div'); - background.id = "background"; - const bar = document.createElement('div'); - bar.id = "bar"; - const value = document.createElement('div'); - value.id = "value"; - const indicator = document.createElement('div'); - indicator.id = "indicator"; - const indicatorColor = document.createElement('div'); - indicatorColor.id = "indicatorColor"; - const targetBar = document.createElement('div'); - targetBar.id = "targetBar"; - const targetBarColor = document.createElement('div'); - targetBarColor.id = "targetBarColor"; - const title = document.createElement('div'); - title.id = "title"; - const titleBar = document.createElement('div'); - titleBar.id = "titleBar"; - title.textContent = config.title; - const style = document.createElement('style'); + // 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 chargeBar = document.createElement('div') + chargeBar.id = 'chargeBar' + const targetBar = document.createElement('div') + targetBar.id = 'targetBar' + const targetBarColor = document.createElement('div') + targetBarColor.id = 'targetBarColor' + const targetMarker = document.createElement('div') + targetMarker.id = 'targetMarker' + const targetMarkerColor = document.createElement('div') + targetMarkerColor.id = 'targetMarkerColor' + const title = document.createElement('div') + title.id = 'title' + const titleBar = document.createElement('div') + titleBar.id = 'titleBar' + title.textContent = config.title + const value = document.createElement('div') + value.id = 'value' + const indicatorContainer = document.createElement('div') + indicatorContainer.id = 'indicatorContainer' + const indicatorBar = document.createElement('div') + indicatorBar.id = 'indicatorBar' + const indicator = document.createElement('div') + indicator.id = 'indicator' + const indicatorColor = document.createElement('div') + indicatorColor.id = 'indicatorColor' + const style = document.createElement('style') + + // Set marker style based on bar direction + let markerStyle + if (barFrom == 'left' || barFrom == 'right') { + markerStyle = ` + #targetMarker, #targetMarkerColor { + position: absolute; + background: #FFF0; + ` + markerDirection + `: var(--targetMarker-percent); + height: ${config.height}; + border-left: 2px dashed var(--targetMarker-color); + } + #targetMarkerColor { + border-left: 2px dashed var(--bar-fill-color); + } + ` + } else { + markerStyle = ` + #targetMarker, #targetMarkerColor { + position: absolute; + background: #FFF0; + ` + markerDirection + `: var(--targetMarker-percent); + width: 100%; + border-top: 2px dashed var(--targetMarker-color); + } + #targetMarkerColor { + border-top: 2px dashed var(--bar-fill-color); + } + ` + } + + // Set CSS styles style.textContent = ` ha-card { background-color: var(--paper-card-background-color); padding: 4px; - `+cardStyle+` + ` + cardStyle + ` + } + #container { + display: flex; + align-items: center; + justify-content: center; + ` + flexDirection + ` } #background { position: relative; + display: flex; + align-items: center; + justify-content: center; + width: ${config.width}; height: ${config.height}; } - #indicator, #indicatorColor { - height: ${config.height}; - line-height: ${config.height}; - color: #7F7F7F; - opacity: 0.5; - mix-blend-mode: difference; - padding-right: 5px; - padding-left: 5px; - position: absolute; - text-shadow: 0px 0px; - `+indicatorStyle+` + #title { + color: var(--primary-text-color); + padding-left: 10px; + padding-right: 10px; + ` + titleStyle + ` } - #indicatorColor { - color: var(--bar-fill-color); - mix-blend-mode: color; - opacity: 1; - `+indicatorStyle+` + #titleBar { + position: relative; + display: flex; + align-items: center; + height: 40px; + ` + titleAlign + ` + ` + titleWidth + ` + ` + titleStyle + ` } - #bar { + #bar, #backgroundBar, #targetBar, #targetBarColor, #valueBar, #chargeBar, #chargeBarColor, #valueBar, #indicatorBar { position: absolute; - height: ${config.height}; - color: #FFF; - text-align: center; - font-weight: bold; - text-shadow: 1px 1px #000; - border-radius: 3px; - width: ${config.width}; - `+barPosition+` - --bar-direction: ${config.from}; - --bar-percent: 50%; - --bar-charge-percent: 0%; - --bar-charge-color: #000; - --bar-fill-color: var(--label-badge-blue); - background: linear-gradient(to ${config.from}, var(--bar-fill-color) var(--bar-percent), var(--bar-charge-color) var(--bar-percent), var(--bar-charge-color) var(--bar-charge-percent), var(--bar-background-color) var(--bar-percent), var(--bar-background-color) var(--bar-percent)); + height: 100%; + width: 100%; border-radius: ${config.rounding}; - `+barStyle+` + ` + barStyle + ` } - #targetBar, #targetBarColor { - position: absolute; - --targetBar-left-percent: 25%; - --targetBar-right-percent: 50%; - opacity: 0.33; + #backgroundBar { + background: var(--bar-background-color); + } + #bar { + background: linear-gradient(to ${barFrom}, var(--bar-fill-color) var(--bar-percent), #0000 var(--bar-percent), #0000 var(--bar-percent)); + } + #chargeBar { + 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 { + 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; - background: #7F7F7F; - left: var(--targetBar-left-percent); - width: var(--targetBar-right-percent); - height: ${config.height}; } #targetBarColor { - background: var(--bar-fill-color); + 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; - opacity: 1; } + ` + markerStyle + ` #value { - position: relative; - white-space: pre; - display: table-cell; - height: ${config.height}; - width: 1000px; - vertical-align: middle; + white-space: pre-line; + z-index: 1; + text-align: center; font-weight: bold; color: #FFF; text-shadow: 1px 1px #000; - `+barStyle+` - z-index: 1; + ` + barStyle + ` } - #title { - display: table-cell; - height: ${config.height}; - width: calc(100% - ${config.width}); - font-size: 14px; - vertical-align: middle; - color: var(--primary-text-color); - padding-left: 10px; - padding-right: 10px; - text-align: left; - `+titleStyle+` + #indicatorBar { + display: flex; + align-items: var(--flex-align); + justify-content: var(--flex-justify); } - #titleBar { - position: absolute; - `+titlePosition+` - height: ${config.height}; - width: calc(100% - ${config.width}); - `+titleStyle+` - } - `; - - // Build card. - if(config.indicator != "off"){ - bar.appendChild(indicator); - bar.appendChild(indicatorColor); - } - if(config.target){ - bar.appendChild(targetBar); - bar.appendChild(targetBarColor); + #indicator, #indicatorColor { + width: 25px; + text-align: center; + position: static; + mix-blend-mode: difference; + color: #7F7F7F; + opacity: 0.75; + text-shadow: 0px 0px; + ` + indicatorStyle + ` + } + #indicatorColor { + margin-left: -25px; + color: var(--bar-fill-color); + mix-blend-mode: color; + opacity: 1; + ` + indicatorStyle + ` + } + #indicatorContainer { + display: flex; + margin-top: 5px; + margin-bottom: 5px; + } + ` + + // Start building card + background.appendChild(backgroundBar) + background.appendChild(bar) + background.appendChild(chargeBar) + + // Check if target is configured + if (config.target) { + targetBar.appendChild(targetMarker) + targetBarColor.appendChild(targetMarkerColor) + background.appendChild(targetBar) + background.appendChild(targetBarColor) } - bar.appendChild(value); - if(config.title_position != "inside"){ - titleBar.appendChild(title); - background.appendChild(titleBar); + + // Check if indicator is not disabled + if (config.indicator != 'off') { + indicatorContainer.appendChild(indicator) + indicatorContainer.appendChild(indicatorColor) + indicatorBar.appendChild(indicatorContainer) } - background.appendChild(bar); - card.appendChild(background); - card.appendChild(style); + + background.appendChild(indicatorBar) + background.appendChild(value) + + // 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) + } + + card.appendChild(container) + card.appendChild(style) + + // Add on click event to card card.addEventListener('click', event => { - this._fire('hass-more-info', { entityId: config.entity }); - }); - root.appendChild(card); - this._config = config; - } + this._showAttributes('hass-more-info', { entityId: config.entity }) + }) - _customStyle(style){ - let styleString = ""; + this.shadowRoot.appendChild(card) + this._config = config + } + // Create style string from CSS options + _customStyle (style) { + let styleString = '' Object.keys(style).forEach(section => { - styleString = styleString + section + ":" + style[section] + "; "; - }); - return styleString; + styleString = styleString + section + ':' + style[section] + '; ' + }) + return styleString } - // Translates entity percentage to bar percentage. - _translatePercent(value, min, max) { - return 100 * (value - min) / (max - min); + // Translates entity percentage to bar percentage + _translatePercent (value, min, max) { + return 100 * (value - min) / (max - min) } - // Map range function. - _scale(num, in_min, in_max, out_min, out_max) { - return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + // Map range function + _scale (num, in_min, in_max, out_min, out_max) { + return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min } - // Create animation. - _animation(entityState, configDirection, configDuration, hue, saturation, configStop) { - const config = this._config; - const root = this.shadowRoot; - const element = root.getElementById("bar"); - if (!config.speed) config.speed = 1000; - - let currentPercent = this._translatePercent(entityState, config.min, config.max); - let totalFrames = ((currentPercent)*10)+(config.delay/(config.speed/250)); - let scaledPercent = (currentPercent)*10; - - if(configStop == true){ - configDuration = 0; - } + // Returns hue value based on severity array + _computeSeverity (stateValue, sections) { + let numberValue = Number(stateValue) + let hue + sections.forEach(section => { + if (numberValue <= section.value && !hue) { + hue = section.hue + } + }) + return hue + } - let options = { - iterations: Infinity, - iterationStart: 0, - delay: 0, - endDelay: 0, - direction: 'normal', - duration: configDuration, - fill: 'both', + // Check if value is NaN, otherwise assume it's an entity + _valueEntityCheck (value, hass) { + if (isNaN(value)) { + return hass.states[value].state + } else { + return value } - - let keyframes = []; - let i = scaledPercent; - if (configDirection == 'normal'){ - for(; i <= totalFrames; ){ - let brightness = this._scale(i/10, currentPercent, currentPercent+25, 50, 15); - if(brightness <= 15) brightness = 15; - let keyframe = {"--bar-charge-percent": i/10+"%", "--bar-percent": currentPercent+"%", "--bar-charge-color": "hsla("+hue+","+saturation+","+brightness+"%,0.5)"}; - keyframes.push(keyframe); - i++; - } - } - if(configDirection == 'reverse'){ - for(; i <= totalFrames; ){ - const reversePercent = currentPercent - ((i-scaledPercent)/10); - let brightness = this._scale(i/10, currentPercent, currentPercent+25, 30, 50); - if(brightness >= 50) brightness = 50; - let keyframe = {"--bar-charge-percent": currentPercent+"%","--bar-percent": reversePercent+"%", "--bar-charge-color": "hsla("+hue+","+saturation+","+brightness+"%,1)"}; - keyframes.push(keyframe); - i++; - } - } - return element.animate(keyframes, options); } - // Attributes action - _fire(type, detail, options) { - const node = this.shadowRoot; - options = options || {}; - detail = (detail === null || detail === undefined) ? {} : detail; + // Press action + _showAttributes (type, detail, options) { + const node = this.shadowRoot + options = options || {} + detail = (detail === null || detail === undefined) ? {} : detail const event = new Event(type, { bubbles: options.bubbles === undefined ? true : options.bubbles, cancelable: Boolean(options.cancelable), composed: options.composed === undefined ? true : options.composed - }); - event.detail = detail; - node.dispatchEvent(event); - return event; + }) + event.detail = detail + node.dispatchEvent(event) + return event } - // Returns hue value based on severity array - _computeSeverity(stateValue, sections) { - let numberValue = Number(stateValue); - let hue; - sections.forEach(section => { - if (numberValue <= section.value && !hue) { - hue = section.hue; + // Update bar percentages + _updateBar (entityState, hass) { + 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))}%`) + } + } + + // Create animation + _updateAnimation (entityState, configDirection, configDuration, hue, saturation, configStop) { + const config = this._config + const root = this.shadowRoot + const element = root.getElementById('chargeBar') + + 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)) + let scaledPercent = (currentPercent) * 10 + + if (configStop == true) { + configDuration = 0 + } + + let options = { + iterations: Infinity, + iterationStart: 0, + delay: 0, + endDelay: 0, + direction: 'normal', + duration: configDuration, + fill: 'both' + } + + let keyframes = [] + let i = scaledPercent + if (configDirection == 'normal') { + for (; i <= totalFrames;) { + let opacity = this._scale(i / 10, currentPercent, currentPercent + 25, 1, 0) + let keyframe = {'--bar-charge-percent': i / 10 + '%', '--bar-percent': currentPercent + '%', '--bar-charge-color': 'hsla(' + hue + ', 75%, 25%, ' + opacity + ')'} + keyframes.push(keyframe) + i++ + } + } + if (configDirection == 'reverse') { + for (; i <= totalFrames;) { + const reversePercent = currentPercent - ((i - scaledPercent) / 10) + let opacity = this._scale(i / 10, currentPercent, currentPercent + 25, 1, 0) + let keyframe = {'--bar-charge-percent': currentPercent + '%', '--bar-percent': reversePercent + '%', '--bar-charge-color': 'hsla(' + hue + ', 75%, 25%, ' + opacity + ')'} + keyframes.push(keyframe) + i++ } - }); - return hue; + } + return element.animate(keyframes, options) } // Sets position and direction of the indicator - _indicatorStyle(position, direction){ - const config = this._config; - const root = this.shadowRoot; - switch(position){ - case "right": - root.getElementById("indicator").style.setProperty('right', 0); - root.getElementById("indicator").style.removeProperty('left'); - root.getElementById("indicatorColor").style.setProperty('right', 0); - root.getElementById("indicatorColor").style.removeProperty('left'); - break; - case "left": - root.getElementById("indicator").style.setProperty('left', 0); - root.getElementById("indicator").style.removeProperty('right'); - root.getElementById("indicatorColor").style.setProperty('left', 0); - root.getElementById("indicatorColor").style.removeProperty('right'); - break; - case "auto": - switch(direction){ - case "up": - root.getElementById("indicator").style.setProperty('right', 0); - root.getElementById("indicator").style.removeProperty('left'); - root.getElementById("indicatorColor").style.setProperty('right', 0); - root.getElementById("indicatorColor").style.removeProperty('left'); - break; - case "down": - root.getElementById("indicator").style.setProperty('left', 0); - root.getElementById("indicator").style.removeProperty('right'); - root.getElementById("indicatorColor").style.setProperty('left', 0); - root.getElementById("indicatorColor").style.removeProperty('right'); - break; + _updateIndicator (position, direction) { + 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') + break + case 'left': + root.getElementById('indicatorBar').style.setProperty('--flex-align', 'center') + root.getElementById('indicatorBar').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') + break + case 'bottom': + root.getElementById('indicatorBar').style.setProperty('--flex-align', 'flex-end') + root.getElementById('indicatorBar').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') + break + case 'down': + root.getElementById('indicatorBar').style.setProperty('--flex-align', 'center') + root.getElementById('indicatorBar').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') + break + case 'down': + root.getElementById('indicatorBar').style.setProperty('--flex-align', 'flex-end') + root.getElementById('indicatorBar').style.setProperty('--flex-justify', 'center') + break } } - if(config.indicator != "off"){ - switch(direction){ - case "up": - root.getElementById("indicator").textContent = '▲'; - root.getElementById("indicatorColor").textContent = '▲'; - break; - case "down": - root.getElementById("indicator").textContent = '▼'; - root.getElementById("indicatorColor").textContent = '▼'; - break; + if (config.indicator != 'off') { + switch (direction) { + case 'up': + root.getElementById('indicator').textContent = '▲' + root.getElementById('indicatorColor').textContent = '▲' + break + case 'down': + root.getElementById('indicator').textContent = '▼' + root.getElementById('indicatorColor').textContent = '▼' + break } } } // Scale the target bar size - _targetBarScale(entityState){ - const config = this._config; - const root = this.shadowRoot; - let currentPercent = this._translatePercent(entityState, config.min, config.max); - let targetPercent = this._translatePercent(config.target, config.min, config.max) - - let initialPercent; - let diffPercent; - if(currentPercent > targetPercent){ - initialPercent = targetPercent; - diffPercent = currentPercent-targetPercent; - root.getElementById("targetBar").style.removeProperty('border-right'); - root.getElementById("targetBarColor").style.removeProperty('border-right'); - root.getElementById("targetBar").style.removeProperty('margin-right'); - root.getElementById("targetBarColor").style.removeProperty('margin-right'); - root.getElementById("targetBar").style.setProperty('border-left', '2px dashed #FFF'); - root.getElementById("targetBarColor").style.setProperty('border-left', '2px dashed var(--bar-fill-color)'); - root.getElementById("targetBar").style.setProperty('margin-left', '-2px'); - root.getElementById("targetBarColor").style.setProperty('margin-left', '-2px'); - } - else{ - initialPercent = currentPercent; - diffPercent = targetPercent-currentPercent; - root.getElementById("targetBar").style.removeProperty('border-left'); - root.getElementById("targetBarColor").style.removeProperty('border-left'); - root.getElementById("targetBar").style.removeProperty('margin-left'); - root.getElementById("targetBarColor").style.removeProperty('margin-left'); - root.getElementById("targetBar").style.setProperty('border-right', '2px dashed #FFF'); - root.getElementById("targetBarColor").style.setProperty('border-right', '2px dashed var(--bar-fill-color)'); - root.getElementById("targetBar").style.setProperty('margin-right', '-2px'); - root.getElementById("targetBarColor").style.setProperty('margin-right', '-2px'); + _updateTargetBar (entityState, target, color, markerColor) { + const config = this._config + const root = this.shadowRoot + + let currentPercent = this._translatePercent(entityState, this._valueEntityCheck(config.min, this._hass), this._valueEntityCheck(config.max, this._hass)) + let targetPercent = this._translatePercent(target, this._valueEntityCheck(config.min, this._hass), this._valueEntityCheck(config.max, this._hass)) + + let initialPercent + let diffPercent + if (currentPercent > targetPercent) { + initialPercent = targetPercent + diffPercent = currentPercent + } else { + initialPercent = currentPercent + diffPercent = targetPercent } - root.getElementById("targetBar").style.setProperty('--targetBar-left-percent', initialPercent+'%'); - root.getElementById("targetBar").style.setProperty('--targetBar-right-percent', diffPercent+'%'); - root.getElementById("targetBarColor").style.setProperty('--targetBar-left-percent', initialPercent+'%'); - root.getElementById("targetBarColor").style.setProperty('--targetBar-right-percent', diffPercent+'%'); + 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) } - + // Create card - set hass(hass) { - const config = this._config; - if(!config.saturation) config.saturation = "50%"; - if(!config.delay) config.delay = 5000; - if(!config.animation)config.animation = 'auto'; - if(!config.indicator)config.indicator = true; + set hass (hass) { + this._hass = hass + const config = this._config + const root = this.shadowRoot - const root = this.shadowRoot; + // Define variables that have possible entities + let configTarget + if (config.target) configTarget = this._valueEntityCheck(config.target, hass) + const configMin = this._valueEntityCheck(config.min, hass) + const configMax = this._valueEntityCheck(config.max, hass) // Check for unknown state - let entityState; - if(hass.states[config.entity] == undefined || hass.states[config.entity].state == "unknown"){ - entityState = "N/A"; - } - else { - entityState = hass.states[config.entity].state; - entityState = Math.min(entityState, config.max); - entityState = Math.max(entityState, config.min); + let entityState + if (hass.states[config.entity] == undefined || hass.states[config.entity].state == 'unknown') { + entityState = 'N/A' + } else { + entityState = hass.states[config.entity].state + 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 = ""; - else measurement = hass.states[config.entity].attributes.unit_of_measurement || ""; - + let measurement + if (hass.states[config.entity] == undefined || hass.states[config.entity].state == 'unknown') measurement = '' + else measurement = hass.states[config.entity].attributes.unit_of_measurement || '' + // Set color hue - let hue; - if(!config.severity) { - hue = 220; - if(config.hue){ - hue = config.hue; + let hue + if (!config.severity) { + hue = 220 + if (config.hue) { + hue = config.hue } - } - else{ - hue = this._computeSeverity(entityState, config.severity); + } else { + hue = this._computeSeverity(entityState, config.severity) } // Set style variables - const color = 'hsl('+hue+','+config.saturation+',50%)'; - const backgroundColor = 'hsla('+hue+','+config.saturation+',15%,0.5)'; - const chargeColor = 'hsla('+hue+','+config.saturation+',30%,0.5)'; + const barColor = 'hsl(' + hue + ',' + config.saturation + ',50%)' + const targetColor = 'hsla(' + hue + ',' + config.saturation + ',50%,0.25)' + const targetMarkerColor = 'hsla(' + hue + ',' + config.saturation + ',50%,0.5)' + const backgroundColor = 'hsla(' + hue + ',' + config.saturation + ',15%,0.5)' + + // On target update + if (config.target) { + if (configTarget != this._entityTarget) { + this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor) + this._entityTarget = configTarget + } + } + + // On min update + if (configMin !== this._currentMin) { + this._updateBar(entityState, hass) + this._currentMin = configMin + if (config.target) { + this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor) + this._entityTarget = configTarget + } + } - // Set targetBar - if(config.target){ - this._targetBarScale(entityState); + // On max update + if (configMax !== this._currentMax) { + this._updateBar(entityState, hass) + this._currentMax = configMax + if (config.target) { + this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor) + this._entityTarget = configTarget + } } // Select 'auto' animation - if(config.animation == 'auto'){ + if (config.animation == 'auto') { if (entityState > this._entityState) { - this._indicatorStyle(config.indicator,"up"); - if(!this._currentAnimation || entityState > this._entityState){ - this._currentAnimation = this._animation(entityState, 'normal', config.delay, hue, config.saturation, false); + this._updateIndicator(config.indicator, 'up') + if (!this._currentAnimation || entityState > this._entityState) { + this._currentAnimation = this._updateAnimation(entityState, 'normal', config.delay, hue, config.saturation, false) } } if (entityState < this._entityState) { - this._indicatorStyle(config.indicator,"down"); - if(!this._currentAnimation || entityState < this._entityState){ - this._currentAnimation = this._animation(entityState, 'reverse', config.delay, hue, config.saturation, false); + this._updateIndicator(config.indicator, 'down') + if (!this._currentAnimation || entityState < this._entityState) { + this._currentAnimation = this._updateAnimation(entityState, 'reverse', config.delay, hue, config.saturation, false) } } - if (entityState == config.max || entityState == config.min) { - if(config.indicator != "off"){ - root.getElementById("indicator").style.removeProperty('right'); - root.getElementById("indicator").style.removeProperty('left'); - root.getElementById("indicator").textContent = ''; + if (entityState == configMax || entityState == configMin) { + if (config.indicator != 'off') { + root.getElementById('indicator').style.removeProperty('right') + root.getElementById('indicator').style.removeProperty('left') + root.getElementById('indicator').textContent = '' } - if(entityState == config.max){ - root.getElementById("bar").style.setProperty('--bar-percent', '100%'); - root.getElementById("bar").style.setProperty('--bar-fill-color', color); - root.getElementById("bar").style.setProperty('--bar-charge-percent', '100%'); - if(!this._currentAnimation){ - this._currentAnimation = this._animation(entityState, 'normal', config.delay, hue, config.saturation, true); + 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 = this._updateAnimation(entityState, 'normal', config.delay, hue, config.saturation, true) } } - if(entityState == config.min){ - root.getElementById("bar").style.setProperty('--bar-percent', '0%'); - root.getElementById("bar").style.setProperty('--bar-charge-percent', '0%'); - if(!this._currentAnimation){ - this._currentAnimation = this._animation(entityState, 'normal', config.delay, hue, config.saturation, true); + 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 = this._updateAnimation(entityState, 'normal', config.delay, hue, config.saturation, true) } } } } - - // Select 'charge' animation. - if(config.animation == 'charge'){ - let chargeEntityState; - if(!config.charge_entity){ - entityState = "define 'charge_entity'"; - measurement = ""; - root.getElementById("value").style.setProperty('color', '#FF0000'); - }else{ - chargeEntityState = hass.states[config.charge_entity].state; - } - if (chargeEntityState == "charging" || chargeEntityState =="on" || chargeEntityState == "true") { - this._indicatorStyle(config.indicator,"up"); - - if(!this._currentAnimation || chargeEntityState != this._currentChargeState || entityState > this._entityState){ - this._currentChargeState = chargeEntityState; - this._currentAnimation = this._animation(entityState, 'normal', config.delay, hue, config.saturation, false); + + // Select 'charge' animation + if (config.animation == 'charge') { + let chargeEntityState + if (!config.charge_entity) { + entityState = "define 'charge_entity'" + measurement = '' + root.getElementById('value').style.setProperty('color', '#FF0000') + } else { + chargeEntityState = hass.states[config.charge_entity].state + } + if (chargeEntityState == 'charging' || chargeEntityState == 'on' || chargeEntityState == 'true') { + this._updateIndicator(config.indicator, 'up') + + if (!this._currentAnimation || chargeEntityState != this._currentChargeState || entityState > this._entityState) { + this._currentChargeState = chargeEntityState + this._currentAnimation = this._updateAnimation(entityState, 'normal', config.delay, hue, config.saturation, false) } } - if (chargeEntityState == "discharging" || chargeEntityState =="off" || chargeEntityState == "false") { - this._indicatorStyle(config.indicator,"down"); + if (chargeEntityState == 'discharging' || chargeEntityState == 'off' || chargeEntityState == 'false') { + this._updateIndicator(config.indicator, 'down') - if(!this._currentAnimation || chargeEntityState != this._currentChargeState || entityState < this._entityState){ - this._currentChargeState = chargeEntityState; - this._currentAnimation = this._animation(entityState, 'reverse', config.delay, hue, config.saturation, false); + if (!this._currentAnimation || chargeEntityState != this._currentChargeState || entityState < this._entityState) { + this._currentChargeState = chargeEntityState + this._currentAnimation = this._updateAnimation(entityState, 'reverse', config.delay, hue, config.saturation, false) } } } + // On entity update if (entityState !== this._entityState) { - if (config.min !== undefined && config.max !== undefined) { - root.getElementById("bar").style.setProperty('--bar-percent', `${this._translatePercent(entityState, config.min, config.max)}%`); - root.getElementById("bar").style.setProperty('--bar-charge-percent', `${this._translatePercent(entityState, config.min, config.max)}%`); + this._updateBar(entityState, hass) + if (config.target) { + this._updateTargetBar(entityState, configTarget, targetColor, targetMarkerColor) + this._entityTarget = configTarget } - root.getElementById("bar").style.setProperty('--bar-fill-color', color); - root.getElementById("bar").style.setProperty('--bar-background-color', backgroundColor); - root.getElementById("bar").style.setProperty('--bar-charge-color', chargeColor); - if(config.title_position == "inside"){ - root.getElementById("value").textContent = `${config.title} \r\n${entityState} ${measurement}`; + root.getElementById('bar').style.setProperty('--bar-fill-color', barColor) + if (config.indicator != 'off') root.getElementById('indicatorColor').style.setProperty('--bar-fill-color', barColor) + root.getElementById('backgroundBar').style.setProperty('--bar-background-color', backgroundColor) + if (config.target) { + root.getElementById('targetBarColor').style.setProperty('--bar-fill-color', barColor) + root.getElementById('targetMarkerColor').style.setProperty('--bar-fill-color', barColor) } - else{ - root.getElementById("value").textContent = `${entityState} ${measurement}`; + if (config.title_position == 'inside') { + root.getElementById('value').textContent = `${config.title} \r\n${entityState} ${measurement}` + } else { + root.getElementById('value').textContent = `${entityState} ${measurement}` } } - root.lastChild.hass = hass; - this._entityState = entityState; + this._entityState = entityState } - getCardSize() { - return 1; + getCardSize () { + return 1 } } -customElements.define('bar-card', BarCard); \ No newline at end of file +customElements.define('bar-card', BarCard) diff --git a/tracker.json b/tracker.json index a532d54..d7343ee 100644 --- a/tracker.json +++ b/tracker.json @@ -1,8 +1,8 @@ { "bar-card": { - "updated_at": "2019-03-09", - "version": "0.0.7", - "remote_location": "https://github.com/Gluwc/bar-card/releases/download/0.0.7/bar-card.js", + "updated_at": "2019-03-12", + "version": "0.1.0", + "remote_location": "https://github.com/Gluwc/bar-card/releases/download/0.1.0/bar-card.js", "visit_repo": "https://github.com/Gluwc/bar-card", "changelog": "https://github.com/Gluwc/bar-card/releases/latest" }