├── README.md └── card-modder.js /README.md: -------------------------------------------------------------------------------- 1 | # card-modder 2 | 3 | # This is deprecated - Use [card-mod](https://github.com/thomasloven/lovelace-card-mod) instead as far as possible 4 | 5 | ## Note: card-mod does not support modding entity rows - other than that it's better than card-modder in every way 6 | 7 | Allows you to style a home-assistant lovelace card 8 | 9 | This card requires [card-tools](https://github.com/thomasloven/lovelace-card-tools) to be installed. 10 | 11 | For installation instructions [see this guide](https://github.com/thomasloven/hass-config/wiki/Lovelace-Plugins). 12 | 13 | ## Options 14 | 15 | | Name | Type | Default | Description 16 | | ---- | ---- | ------- | ----------- 17 | | type | string | **Required** | `custom:card-modder` 18 | | card | object | **Required** | The card you wish to style 19 | | style | list | none | List of css styles to apply to card 20 | | extra\_styles | string | none | Extra style data to add to card environment 21 | | report\_size | number | none | Size to report 22 | 23 | # Styling 24 | 25 | card-modder can be used to apply CSS styling to any lovelace card. 26 | 27 | Styles are automatically applied recursively to all cards within stacks. 28 | 29 | Any CSS style can be used, and will be applied to the base element of the card 30 | (``). Most cards use css variables for styling, and to find out which 31 | ones, I recommend either the official ["partial list of variables 32 | used"](https://github.com/home-assistant/home-assistant-polymer/blob/master/src/resources/ha-style.ts) 33 | or that you open the card in your browsers object inspector and check out the 34 | styling options manually. 35 | 36 | ```yaml 37 | - type: custom:card-modder 38 | style: 39 | --paper-card-background-color: rgba(0, 100, 0, 0.2) 40 | --paper-item-icon-color: white 41 | border-radius: 5px 42 | color: rgb(0, 0, 100) 43 | card: 44 | type: glance 45 | title: Styled card 46 | entities: 47 | - light.bed_light 48 | - light.ceiling_lights 49 | - light.kitchen_lights 50 | - type: glance 51 | title: Unstyled card 52 | entities: 53 | - light.bed_light 54 | - light.ceiling_lights 55 | - light.kitchen_lights 56 | ``` 57 | 58 | **Note about rounded corners:** Some cards which have a background image needs the style `overflow: hidden` added for rounded corners to work. 59 | ![card-modder-styling](https://user-images.githubusercontent.com/1299821/47842006-b92a9a80-ddbb-11e8-915a-9d54f7e62a5e.png) 60 | 61 | ## Templates 62 | 63 | A simple form of templates can be used in the styles. 64 | Templates are on the form `[[ domain.entity.state ]]` or `[[ domain.entity.attributes.attribute_name ]]`. 65 | 66 | ```yaml 67 | - type: custom:card-modder 68 | style: 69 | background-image: "url(http://www.place[[ input_select.background.state ]].com/600/250)" 70 | card: 71 | type: entities 72 | entities: 73 | - input_select.background 74 | - type: custom:card-modder 75 | card: 76 | type: custom:hui-toggle-entity-row 77 | entity: light.bed_light 78 | style: {color: blue} 79 | - light.bed_light 80 | - light.bed_light 81 | ``` 82 | ![skarminspelning 2018-12-13 kl 00 05 45 mov](https://user-images.githubusercontent.com/1299821/49904941-3261e680-fe6c-11e8-8d7d-25b6fbbfc9bf.gif) 83 | 84 | Note that this doesn't work reliably with stacks at this point. 85 | 86 | 87 | # Extra styles 88 | 89 | Card-modder adds styles to the `ha-card` element of a lovelace card (if found). The `extra_styles` parameter can be used to inject code directly into a `style` tag being a sibling of the `ha-card`. 90 | 91 | This allows e.g. for blinking backgrounds: 92 | 93 | ```yaml 94 | type: custom:card-modder 95 | style: 96 | animation: [[ sensor.button_animation.state ]] 2s linear infinite 97 | extra_styles: > 98 | @keyframes blink { 99 | 50% { 100 | background: red; 101 | } 102 | } 103 | card: 104 | type: entity-button 105 | entity: light.bed_light 106 | ``` 107 | ![extra_styles](https://user-images.githubusercontent.com/1299821/52751643-81ed9b80-2ff0-11e9-889b-65fcbbae8678.gif) 108 | 109 | Here `sensor.button_animation` is a [template sensor](https://www.home-assistant.io/components/sensor.template/) which is setup such that when the button should be blinking it has the value `blink`, and no value otherwise. E.g: 110 | 111 | ```yaml 112 | sensor: 113 | - platform: template 114 | sensors: 115 | button_animation: 116 | value_template: "{% if is_state('light.bed_light', 'off') %}blink{% endif %}" 117 | ``` 118 | 119 | ## 120 | ![hd8glbnumi](https://user-images.githubusercontent.com/1299821/52778030-5943c100-3045-11e9-90f8-47624a76ac94.gif) 121 | 122 | Example by [Isabella Gross Alström](https://github.com/isabellaalstrom/HomeAssistantConfiguration). 123 | 124 | 125 | # Forcing size 126 | 127 | By default lovelace orders cards on the screen in columns. To predict where a card will pop up there are a few rules: 128 | 129 | - Each card has a vertical size. The size is specified in units of roughly 50 pixels. 130 | - Cards are placed in the order they are specified in ui-lovelace.yaml 131 | - Cards will be placed in the first available column (from the left) which is 132 | less than **5** units tall. 133 | - If no column has less than 5 units, the card will be placed in the shortest 134 | column. 135 | 136 | The size of each card varies a bit, generally: 137 | - One row in an entities card is 1 unit tall. 138 | - A card title is one unit tall. 139 | - One row of glance icons is two units tall. 140 | - A `picture-`card is 3 units tall. 141 | 142 | You can find the size of each card from the source code 143 | [here](https://github.com/home-assistant/home-assistant-polymer/tree/master/src/panels/lovelace/cards). 144 | Look for the `getCardSize()` function in the card you want. 145 | 146 | 147 | ### The point 148 | 149 | Any way... card-modder can be used to set what height a card should report: 150 | 151 | ``` 152 | - type: custom:card-modder 153 | report_size: 7 154 | card: 155 | type: glance 156 | title: 'report_size: 7' 157 | entities: 158 | - light.bed_light 159 | - light.ceiling_lights 160 | - light.kitchen_lights 161 | - type: glance 162 | title: Card 2 163 | entities: 164 | ... 165 | - type: glance 166 | title: Card 3 167 | entities: 168 | ... 169 | - type: glance 170 | title: Card 4 171 | entities: 172 | ... 173 | - type: glance 174 | title: Card 5 175 | entities: 176 | ... 177 | ``` 178 | ![card-modder-sizing](https://user-images.githubusercontent.com/1299821/47842030-ce9fc480-ddbb-11e8-9062-02cfc0e8f144.png) 179 | 180 | --- 181 | Buy Me A Coffee 182 | -------------------------------------------------------------------------------- /card-modder.js: -------------------------------------------------------------------------------- 1 | customElements.whenDefined('card-tools').then(() => { 2 | class CardModder extends cardTools.LitElement { 3 | 4 | constructor() { 5 | super(); 6 | this.EL_STYLES = ["left", "top", "right", "bottom", "position"]; 7 | } 8 | 9 | static get properties() { 10 | return { 11 | card: {}, 12 | }; 13 | } 14 | 15 | async setConfig(config) { 16 | cardTools.checkVersion(0.3); 17 | 18 | if(!config || !config.card) { 19 | throw new Error("Card config incorrect"); 20 | } 21 | if(Array.isArray(config.card)) { 22 | throw new Error("It says 'card', not 'cardS'. Remove the dash."); 23 | } 24 | 25 | this.templated = []; 26 | this.attempts = 5; 27 | 28 | if (config.entities) 29 | config.card.entities = config.entities; 30 | 31 | this.card = cardTools.createCard(config.card); 32 | this._cards = [this.card]; 33 | if(this._hass) 34 | this.card.hass = this._hass; 35 | 36 | if(this._config) 37 | this._cardMod(this.card); 38 | 39 | this._config = config; 40 | 41 | this.recurse = config.recurse !== undefined ? config.recurse : true; 42 | 43 | window.addEventListener("location-changed", () => this.hass = this._hass); 44 | } 45 | 46 | createRenderRoot() { 47 | return this; 48 | } 49 | render() { 50 | return cardTools.LitHtml` 51 |
${this.card}
52 | `; 53 | } 54 | 55 | async firstUpdated() { 56 | this._cardMod(this.card); 57 | } 58 | 59 | async _cardMod(root) { 60 | if(!this._config.style && !this._config.extra_styles) return; 61 | 62 | if (this.recurse && root._cards && root._cards.length) { 63 | root._cards.forEach(c => this._cardMod(c)); 64 | return; 65 | } 66 | 67 | let target = null; 68 | let styles = null; 69 | while(!target) { 70 | await root.updateComplete; 71 | if(root._cardModder) { 72 | target = root._cardModder.target; 73 | styles = root._cardModder.styles; 74 | continue; 75 | } 76 | if(root.querySelector("style")) 77 | styles = root.querySelector("style"); 78 | if(root.querySelector("ha-card")) { 79 | target = root.querySelector("ha-card"); 80 | continue; 81 | } 82 | if(root.querySelector("vertical-stack-in-card")) { 83 | target = root.querySelector("vertical-stack-in-card"); 84 | continue; 85 | } 86 | if(root.card) { 87 | root = root.card; 88 | continue; 89 | } 90 | if(root.shadowRoot) { 91 | root = root.shadowRoot; 92 | continue; 93 | } 94 | if(root.querySelector("#root")) { 95 | root = root.querySelector("#root"); 96 | continue; 97 | } 98 | if(root.firstElementChild) { 99 | root = root.firstElementChild; 100 | continue; 101 | } 102 | break; 103 | } 104 | if(this.classList.contains("element")) { 105 | if(!target) 106 | target = this.card; 107 | root = this; 108 | } 109 | if(!target && this.attempts) // Try again 110 | setTimeout(() => this._cardMod(root), 100); 111 | this.attempts--; 112 | target = target || this.card; 113 | 114 | if(this._config.extra_styles) { 115 | if(!styles) { 116 | styles = document.createElement('style'); 117 | root.appendChild(styles); 118 | } 119 | if(!styles.innerHTML.includes(this._config.extra_styles)) 120 | styles.innerHTML += this._config.extra_styles; 121 | } 122 | 123 | if(this._config.style) { 124 | for(var k in this._config.style) { 125 | if(cardTools.hasTemplate(this._config.style[k])) 126 | this.templated.push(k); 127 | if(this.card.style.setProperty) 128 | this.card.style.setProperty(k, ''); 129 | if(target.style.setProperty) { 130 | target.style.setProperty(k, cardTools.parseTemplate(this._config.style[k])); 131 | } 132 | if(this.classList.contains("element") && this.EL_STYLES.indexOf(k) > -1) { 133 | this.style.setProperty(k, cardTools.parseTemplate(this._config.style[k])); 134 | } 135 | } 136 | } 137 | this.target = target; 138 | } 139 | 140 | set hass(hass) { 141 | this._hass = hass; 142 | if(this.card) this.card.hass = hass; 143 | if(this.templated) 144 | this.templated.forEach((k) => { 145 | this.target.style.setProperty(k, cardTools.parseTemplate(this._config.style[k], '')); 146 | if(this.classList.contains("element") && this.EL_STYLES.indexOf(k) > -1) { 147 | this.style.setProperty(k, cardTools.parseTemplate(this._config.style[k])); 148 | } 149 | }); 150 | } 151 | 152 | getCardSize() { 153 | if(this._config && this._config.report_size) 154 | return this._config.report_size; 155 | if(this.card) 156 | return typeof this.card.getCardSize === "function" ? this.card.getCardSize() : 1; 157 | return 1; 158 | } 159 | } 160 | 161 | customElements.define('card-modder', CardModder); 162 | }); 163 | 164 | window.setTimeout(() => { 165 | if(customElements.get('card-tools')) return; 166 | customElements.define('card-modder', class extends HTMLElement{ 167 | setConfig() { throw new Error("Can't find card-tools. See https://github.com/thomasloven/lovelace-card-tools");} 168 | }); 169 | }, 2000); 170 | --------------------------------------------------------------------------------