├── .github └── workflows │ └── hacs.yml ├── CardSelect.png ├── CardViews.png ├── CodeEditor.png ├── GraphicalEditor.png ├── LICENSE ├── README.md ├── dist └── PlantPictureCard.js └── hacs.json /.github/workflows/hacs.yml: -------------------------------------------------------------------------------- 1 | name: HACS Validate 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | jobs: 10 | validate: 11 | runs-on: "ubuntu-latest" 12 | steps: 13 | - uses: "actions/checkout@v2" 14 | - name: HACS validation 15 | uses: "hacs/integration/action@master" 16 | with: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | CATEGORY: "plugin" 19 | -------------------------------------------------------------------------------- /CardSelect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badguy99/PlantPictureCard/eb06132a1cf6b450e835872c55f35b07d9f459f3/CardSelect.png -------------------------------------------------------------------------------- /CardViews.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badguy99/PlantPictureCard/eb06132a1cf6b450e835872c55f35b07d9f459f3/CardViews.png -------------------------------------------------------------------------------- /CodeEditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badguy99/PlantPictureCard/eb06132a1cf6b450e835872c55f35b07d9f459f3/CodeEditor.png -------------------------------------------------------------------------------- /GraphicalEditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badguy99/PlantPictureCard/eb06132a1cf6b450e835872c55f35b07d9f459f3/GraphicalEditor.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 badguy99 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PlantPictureCard 2 | 3 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) 4 | 5 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/bf35eae0509e4f6080dc0973df1f8b42)](https://www.codacy.com/manual/badguy99/PlantPictureCard?utm_source=github.com&utm_medium=referral&utm_content=badguy99/PlantPictureCard&utm_campaign=Badge_Grade) 6 | 7 | This adds a new custom card, which is like a picture glance card, but takes a plant entity rather than other entities. It provides all the plant data across the bottom bar in one go. 8 | The data should be exactly the same as that displayed by the plant card. And an image should be configured, which is displayed above the plant data. 9 | 10 | ## Card Configuration 11 | The card can be added through usual web interface. 12 | ![Plant Picture Card shows up in the card selection window](https://github.com/badguy99/PlantPictureCard/blob/master/CardSelect.png) 13 | 14 | Entity and Image are required. Title is optional. 15 | The configuration can be done via the visual editor or code editor. 16 | 17 | ![Setting up the Plant Picture Card with visual editor](https://github.com/badguy99/PlantPictureCard/blob/master/GraphicalEditor.png) 18 | ![Setting up the Plant Picture Card with code editor](https://github.com/badguy99/PlantPictureCard/blob/master/CodeEditor.png) 19 | 20 | ## Card Use in Lovelace 21 | The card should look ok full size or within a 2x2 horizontal and vertical stack 22 | 23 | ![Full size and 2x2 size Plant Picture Cards](https://github.com/badguy99/PlantPictureCard/blob/master/CardViews.png) 24 | -------------------------------------------------------------------------------- /dist/PlantPictureCard.js: -------------------------------------------------------------------------------- 1 | const LitElement = Object.getPrototypeOf( 2 | customElements.get("ha-panel-lovelace") 3 | ); 4 | const html = LitElement.prototype.html; 5 | const css = LitElement.prototype.css; 6 | 7 | class PlantPictureCard extends HTMLElement { 8 | 9 | static getConfigElement() { 10 | return document.createElement("plant-picture-card-editor"); 11 | } 12 | 13 | static getStubConfig() { 14 | return {entity: "", 15 | image: "" }; 16 | } 17 | 18 | constructor() { 19 | super(); 20 | this.attachShadow({ mode: "open" }); 21 | } 22 | 23 | _click(entity) { 24 | this._fire("hass-more-info", { entityId: entity }); 25 | } 26 | 27 | _fire(type, detail) { 28 | 29 | const event = new Event(type, { 30 | bubbles: true, 31 | cancelable: false, 32 | composed: true 33 | }); 34 | event.detail = detail || {}; 35 | this.shadowRoot.dispatchEvent(event); 36 | return event; 37 | } 38 | 39 | set hass(hass) { 40 | const config = this.config; 41 | 42 | var _title = config.title; 43 | 44 | if(!config.title){ 45 | _title = hass.states[config.entity].attributes.friendly_name; 46 | } 47 | 48 | this.shadowRoot.getElementById("box").innerHTML = ` 49 |
${_title}
50 |
51 |
52 | `; 53 | 54 | var _entities = [ 55 | hass.states[config.entity].attributes.sensors.moisture, 56 | hass.states[config.entity].attributes.sensors.temperature, 57 | hass.states[config.entity].attributes.sensors.brightness, 58 | hass.states[config.entity].attributes.sensors.conductivity, 59 | hass.states[config.entity].attributes.sensors.battery 60 | ]; 61 | 62 | var _sensors = [ 63 | "moisture", 64 | "temperature", 65 | "brightness", 66 | "conductivity", 67 | "battery" 68 | ]; 69 | 70 | const _icons = [ 71 | "mdi:water", 72 | "mdi:thermometer", 73 | "mdi:white-balance-sunny", 74 | "mdi:emoticon-poop", 75 | "mdi:battery" 76 | ]; 77 | 78 | var i; 79 | for (i = 0; i < _entities.length; i++) { 80 | if (typeof _entities[parseInt(i)] == "undefined") { continue; } 81 | var _sensor = _entities[parseInt(i)]; 82 | var _name = hass.states[String(_sensor)].attributes.friendly_name; 83 | var _state = hass.states[String(_sensor)].state; 84 | var _uom = hass.states[String(_sensor)].attributes.unit_of_measurement; 85 | var _icon = hass.states[String(_sensor)].attributes.icon; 86 | var _class = "state-on"; 87 | if ( hass.states[config.entity].attributes.problem.indexOf(_sensors[parseInt(i)]) !== -1){ 88 | _class += " state-problem"; 89 | } 90 | 91 | this.shadowRoot.getElementById("sensors").innerHTML += ` 92 |
93 |
94 |
${_state}
95 |
${_uom}
96 |
97 | `; 98 | } 99 | 100 | var j; 101 | for (j = 0; j < _entities.length; j++) { 102 | if (typeof _entities[parseInt(j)] == "undefined") { continue; } 103 | this.shadowRoot.getElementById("sensor"+[parseInt(j)]).onclick = this._click.bind(this, _entities[parseInt(j)]); 104 | } 105 | } 106 | 107 | setConfig(config) { 108 | if (!config.entity) { 109 | throw new Error("You need to define an entity"); 110 | } 111 | if (!config.image) { 112 | throw new Error("You need to define an image"); 113 | } 114 | 115 | const root = this.shadowRoot; 116 | if (root.lastChild) { 117 | root.removeChild(root.lastChild); 118 | } 119 | 120 | this.config = config; 121 | 122 | const card = document.createElement("ha-card"); 123 | const content = document.createElement("div"); 124 | const style = document.createElement("style"); 125 | 126 | style.textContent = ` 127 | 128 | ha-card { 129 | position: relative; 130 | padding: 0; 131 | background-size: 100%; 132 | } 133 | 134 | img { 135 | display: block; 136 | height: auto; 137 | transition: filter .2s linear; 138 | width: 100%; 139 | } 140 | 141 | .box { 142 | position: absolute; 143 | left: 0; 144 | right: 0; 145 | bottom: 0; 146 | height: 16%; 147 | min-height: 64px; 148 | background-color: rgba(0, 0, 0, 0.5); 149 | padding: 4px 8px; 150 | color: white; 151 | display: flex; 152 | flex-wrap: wrap; 153 | justify-content: space-evenly; 154 | align-items: center; 155 | text-align: center; 156 | } 157 | 158 | div.title { 159 | flex: 0 1 20%; 160 | } 161 | 162 | div#sensors { 163 | display: flex; 164 | justify-content: space-between; 165 | flex: 1; 166 | } 167 | 168 | .sensor { 169 | flex-grow: 1; 170 | } 171 | 172 | ha-icon { 173 | cursor: pointer; 174 | color: var(--primary-color); 175 | } 176 | 177 | .state-problem { 178 | color: var(--accent-color); 179 | } 180 | 181 | .uom { 182 | color: var(--primary-text-color); 183 | } 184 | 185 | `; 186 | 187 | content.id = "container"; 188 | content.innerHTML = ` 189 |
190 | 191 |
192 |
193 | `; 194 | card.appendChild(content); 195 | card.appendChild(style); 196 | root.appendChild(card); 197 | } 198 | 199 | 200 | // The height of your card. Home Assistant uses this to automatically 201 | // distribute all cards over the available columns. 202 | getCardSize() { 203 | return 3; 204 | } 205 | } 206 | 207 | customElements.define("plant-picture-card", PlantPictureCard); 208 | 209 | 210 | function deepClone(value) { 211 | if (!(!!value && typeof value === "object")) { 212 | return value; 213 | } 214 | if (Object.prototype.toString.call(value) === "[object Date]") { 215 | return new Date(value.getTime()); 216 | } 217 | if (Array.isArray(value)) { 218 | return value.map(deepClone); 219 | } 220 | var result = {}; 221 | Object.keys(value).forEach( 222 | function(key) { result[String(key)] = deepClone(value[String(key)]); }); 223 | return result; 224 | } 225 | 226 | export class PlantPictureCardEditor extends LitElement { 227 | 228 | setConfig(config) { 229 | this.config = deepClone(config); 230 | } 231 | 232 | render() { 233 | if (!this.hass) { 234 | return html``; 235 | } 236 | 237 | const entities = Object.keys(this.hass.states).filter((eid) => eid.substr(0, eid.indexOf(".")) === "plant"); 238 | 239 | return html` 240 |
241 |
242 | 248 | 253 | 254 | ${entities.map((entity) => { 255 | return html` 256 | ${entity} 257 | `; 258 | })} 259 | 260 | 261 | 267 |
268 |
269 | `; 270 | } 271 | 272 | _valueChanged(ev) { 273 | if (!this.config || !this.hass) { 274 | return; 275 | } 276 | const target = ev.target; 277 | if (this.config[`${target.configValue}`] === (target.value||target.__checked)) { 278 | return; 279 | } 280 | if (target.configValue) { 281 | if (target.value === "") { 282 | delete this.config[target.configValue]; 283 | } else { 284 | this.config = { 285 | ...this.config, 286 | [target.configValue]: target.value||target.__checked 287 | }; 288 | } 289 | } 290 | this.configChanged(this.config); 291 | } 292 | 293 | configChanged(newConfig) { 294 | const event = new Event("config-changed", { 295 | bubbles: true, 296 | composed: true 297 | }); 298 | event.detail = {config: newConfig}; 299 | this.dispatchEvent(event); 300 | } 301 | } 302 | 303 | customElements.define("plant-picture-card-editor", PlantPictureCardEditor); 304 | window.customCards = window.customCards || []; 305 | window.customCards.push({ 306 | type: "plant-picture-card", 307 | name: "Plant Picture Card", 308 | preview: true, // Optional - defaults to false 309 | description: "Like a picture glance card but for plant data" // Optional 310 | }); 311 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Plant Picture Card", 3 | "render_readme": true, 4 | "filename": "PlantPictureCard.js" 5 | } 6 | --------------------------------------------------------------------------------