diff --git a/README.md b/README.md index bec4043..a8164da 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,43 @@ entity: alarm_control_panel.alarm ![advanced](https://user-images.githubusercontent.com/1299821/59151780-59732c80-8a39-11e9-9f19-34d3e0dbd8e9.png) +# Bonus function - entities card tap\_action +With card-mod installed, you will be able to use `tap_action` with rows in your entities cards: + +Example: +```yaml +type: entities +entities: + - entity: light.bed_light + name: default + - entity: light.bed_light + name: toggle + tap_action: + action: toggle + - entity: light.bed_light + name: navigate + tap_action: + action: navigate + navigation_path: /lovelace/1 + - entity: light.bed_light + name: url + tap_action: + action: url + url_path: https://google.com + - entity: light.bed_light + name: call-service + tap_action: + action: call-service + service: browser_mod.popup + service_data: + deviceID: this + title: Popup + card: + type: entities + entities: + - light.bed_light +``` + # FAQ ### How do I convert my old card-modder configuration to card-mod? diff --git a/card-mod.js b/card-mod.js index 86076ce..cb31cc7 100644 --- a/card-mod.js +++ b/card-mod.js @@ -1 +1 @@ -!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);const o=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view"));o.prototype.html,o.prototype.css;function i(e,t,n=null){if((e=new Event(e,{bubbles:!0,cancelable:!1,composed:!0})).detail=t||{},n)n.dispatchEvent(e);else{var o=document.querySelector("home-assistant");(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=o&&o.shadowRoot)&&o.querySelector("home-assistant-main"))&&o.shadowRoot)&&o.querySelector("app-drawer-layout partial-panel-resolver"))&&o.shadowRoot||o)&&o.querySelector("ha-panel-lovelace"))&&o.shadowRoot)&&o.querySelector("hui-root"))&&o.shadowRoot)&&o.querySelector("ha-app-layout #view"))&&o.firstElementChild)&&o.dispatchEvent(e)}}function a(){return document.querySelector("home-assistant").hass}let s=function(){if(window.fully&&"function"==typeof fully.getDeviceId)return fully.getDeviceId();if(!localStorage["lovelace-player-device-id"]){const e=()=>Math.floor(1e5*(1+Math.random())).toString(16).substring(1);localStorage["lovelace-player-device-id"]=`${e()}${e()}-${e()}${e()}`}return localStorage["lovelace-player-device-id"]}();customElements.define("card-mod",class extends HTMLElement{disconnectedCallback(){this._disconnect()}connectedCallback(){this._connect()}_has_template(e){return!!e.template.includes("{%")||!!e.template.includes("{{")}set template(e){this._data=e,this._has_template(e)&&!this._data.entity_ids&&this._data.template.includes("config.entity")&&this._data.variables.config&&this._data.variables.config.entity&&(this._data.entity_ids=[this._data.variables.config.entity])}update(){this._disconnect().then(()=>this._connect())}async _connect(){this._data&&(this._has_template(this._data)||(this.innerHTML=``),this._unsubRenderTemplate||(this._unsubRenderTemplate=function(e,t,n){e||(e=a().connection);let o={user:a().user.name,browser:s,hash:location.hash.substr(1)||" ",...n.variables},i=n.template,r=n.entity_ids;return e.subscribeMessage(e=>t(e.result),{type:"render_template",template:i,variables:o,entity_ids:r})}(null,e=>this.innerHTML=``,this._data),this._unsubRenderTemplate.catch(()=>{this.innerHTML=``,this._unsubRenderTemplate=void 0})))}async _disconnect(){if(this._unsubRenderTemplate)try{const e=await this._unsubRenderTemplate;this._unsubRenderTemplate=void 0,await e()}catch(e){if("not_found"!==e.code)throw e}}});const r=async function(e,t,n,o){const i=function(e){o&&("string"==typeof e?console.log(" ".repeat(2*(o-1))+e):console.log(e))};if(e&&t)if(e.updateComplete&&await e.updateComplete,"string"==typeof t){const o=e.querySelector("card-mod");if(o)return void o.update();const a=document.createElement("card-mod");a.template={template:t,variables:n.variables,entity_ids:n.entity_ids},e.appendChild(a),i("Applied styles to:"),i(e)}else Object.keys(t).forEach(a=>"."===a?(i(`Stepping into root of ${e.tagName}`),r(e,t[a],n,o?o+1:0)):"$"===a?(i(`Stepping into ShadowRoot of ${e.tagName}`),r(e.shadowRoot,t[a],n,o?o+1:0)):(i(`Searching for: "${a}". ${e.querySelectorAll(a).length} matches.`),void e.querySelectorAll(`${a}`).forEach(e=>{i(`Stepping into ${e.tagName}`),r(e,t[a],n,o?o+1:0)})))};customElements.whenDefined("ha-card").then(()=>{const e=customElements.get("ha-card"),t=function(e){return e.config?e.config:e._config?e._config:e.host?t(e.host):e.parentElement?t(e.parentElement):e.parentNode?t(e.parentNode):null};e.prototype.firstUpdated=function(){const e=this.shadowRoot.querySelector(".card-header");e&&this.insertBefore(e,this.children[0]);const n=t(this);if(!n||!n.style)return;let o=n.entity_ids;const i=()=>{r(this,n.style,{variables:{config:n},entity_ids:o},!!n.debug_cardmod)};i(),window.addEventListener("location-changed",()=>i())},i("ll-rebuild",{})}),customElements.whenDefined("hui-entities-card").then(()=>{const e=customElements.get("hui-entities-card"),t=e.prototype.renderEntity;e.prototype.renderEntity=function(e){const n=t.bind(this)(e);if(!e.style)return n;if(!n.values)return n;const o=n.values[0];if(!o||!o.updateComplete)return n;let i=e.entity_ids;const a=()=>{r(o.shadowRoot,e.style,{variables:{config:e},entity_ids:i},!!e.debug_cardmod)};return o.updateComplete.then(a),window.addEventListener("location-changed",a),n},i("ll-rebuild",{})}),customElements.whenDefined("hui-glance-card").then(()=>{customElements.get("hui-glance-card").prototype.firstUpdated=function(){this.shadowRoot.querySelectorAll("ha-card div.entity").forEach(e=>{const t=e.attachShadow({mode:"open"});[...e.children].forEach(e=>t.appendChild(e));const n=document.createElement("style");t.appendChild(n),n.innerHTML="\n :host {\n box-sizing: border-box;\n padding: 0 4px;\n display: flex;\n flex-direction: column;\n align-items: center;\n cursor: pointer;\n margin-bottom: 12px;\n width: var(--glance-column-width, 20%);\n }\n div {\n width: 100%;\n text-align: center;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .name {\n min-height: var(--paper-font-body1_-_line-height, 20px);\n }\n state-badge {\n margin: 8px 0;\n }\n ";const o=e.entityConf;if(!o.style)return;let i=o.entity_ids;const a=()=>{r(t,o.style,{variables:{config:o},entity_ids:i},!!o.debug_cardmod)};a(),window.addEventListener("location-changed",a)})},i("ll-rebuild",{})})}]); \ No newline at end of file +!function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";function i(){return document.querySelector("home-assistant").hass}n.r(t);let o=function(){if(window.fully&&"function"==typeof fully.getDeviceId)return fully.getDeviceId();if(!localStorage["lovelace-player-device-id"]){const e=()=>Math.floor(1e5*(1+Math.random())).toString(16).substring(1);localStorage["lovelace-player-device-id"]=`${e()}${e()}-${e()}${e()}`}return localStorage["lovelace-player-device-id"]}();function a(e,t,n=null){if((e=new Event(e,{bubbles:!0,cancelable:!1,composed:!0})).detail=t||{},n)n.dispatchEvent(e);else{var i=document.querySelector("home-assistant");(i=(i=(i=(i=(i=(i=(i=(i=(i=(i=(i=i&&i.shadowRoot)&&i.querySelector("home-assistant-main"))&&i.shadowRoot)&&i.querySelector("app-drawer-layout partial-panel-resolver"))&&i.shadowRoot||i)&&i.querySelector("ha-panel-lovelace"))&&i.shadowRoot)&&i.querySelector("hui-root"))&&i.shadowRoot)&&i.querySelector("ha-app-layout #view"))&&i.firstElementChild)&&i.dispatchEvent(e)}}customElements.define("card-mod",class extends HTMLElement{disconnectedCallback(){this._disconnect()}connectedCallback(){this._connect()}_has_template(e){return!!e.template.includes("{%")||!!e.template.includes("{{")}set template(e){this._data=e,this._has_template(e)&&!this._data.entity_ids&&this._data.template.includes("config.entity")&&this._data.variables.config&&this._data.variables.config.entity&&(this._data.entity_ids=[this._data.variables.config.entity])}update(){this._disconnect().then(()=>this._connect())}async _connect(){this._data&&(this._has_template(this._data)||(this.innerHTML=``),this._unsubRenderTemplate||(this._unsubRenderTemplate=function(e,t,n){e||(e=i().connection);let a={user:i().user.name,browser:o,hash:location.hash.substr(1)||" ",...n.variables},s=n.template,r=n.entity_ids;return e.subscribeMessage(e=>t(e.result),{type:"render_template",template:s,variables:a,entity_ids:r})}(null,e=>this.innerHTML=``,this._data),this._unsubRenderTemplate.catch(()=>{this.innerHTML=``,this._unsubRenderTemplate=void 0})))}async _disconnect(){if(this._unsubRenderTemplate)try{const e=await this._unsubRenderTemplate;this._unsubRenderTemplate=void 0,await e()}catch(e){if("not_found"!==e.code)throw e}}});const s=async function(e,t,n,i){const o=function(e){i&&("string"==typeof e?console.log(" ".repeat(2*(i-1))+e):console.log(e))};if(e&&t)if(e.updateComplete&&await e.updateComplete,"string"==typeof t){const i=e.querySelector("card-mod");if(i)return void i.update();const a=document.createElement("card-mod");a.template={template:t,variables:n.variables,entity_ids:n.entity_ids},e.appendChild(a),o("Applied styles to:"),o(e)}else Object.keys(t).forEach(a=>"."===a?(o(`Stepping into root of ${e.tagName}`),s(e,t[a],n,i?i+1:0)):"$"===a?(o(`Stepping into ShadowRoot of ${e.tagName}`),s(e.shadowRoot,t[a],n,i?i+1:0)):(o(`Searching for: "${a}". ${e.querySelectorAll(a).length} matches.`),void e.querySelectorAll(`${a}`).forEach(e=>{o(`Stepping into ${e.tagName}`),s(e,t[a],n,i?i+1:0)})))};customElements.whenDefined("ha-card").then(()=>{const e=customElements.get("ha-card"),t=function(e){return e.config?e.config:e._config?e._config:e.host?t(e.host):e.parentElement?t(e.parentElement):e.parentNode?t(e.parentNode):null};e.prototype.firstUpdated=function(){const e=this.shadowRoot.querySelector(".card-header");e&&this.insertBefore(e,this.children[0]);const n=t(this);if(!n||!n.style)return;let i=n.entity_ids;const o=()=>{s(this,n.style,{variables:{config:n},entity_ids:i},!!n.debug_cardmod)};o(),window.addEventListener("location-changed",()=>o())},a("ll-rebuild",{})}),customElements.whenDefined("hui-entities-card").then(()=>{const e=customElements.get("hui-entities-card"),t=e.prototype.renderEntity;e.prototype.renderEntity=function(e){const n=t.bind(this)(e);if(!e.style)return n;if(!n.values)return n;const i=n.values[0];if(!i||!i.updateComplete)return n;let o=e.entity_ids;const a=()=>{s(i.shadowRoot,e.style,{variables:{config:e},entity_ids:o},!!e.debug_cardmod)};return i.updateComplete.then(a),window.addEventListener("location-changed",a),n},e.prototype._handleClick=function(e){switch(e.tap_action&&e.tap_action.action?e.tap_action.action:"more-info"){case"more-info":a("hass-more-info",{entityId:e.entity},this);break;case"navigate":history.pushState(null,"",e.tap_action.navigation_path),a("location-changed",{},this);break;case"url":e.tap_action.url_path&&window.open(e.tap_action.url_path);break;case"toggle":if(e.entity){const t=e.entity.split(".",1)[0],n=["closed","locked","off"].includes(this._hass.states[e.entity].state),i=({lock:["unlock","lock"],cover:["open_cover","close_cover"]}[t]||["turn_on","turn_off"])[n?0:1];this._hass.callService(t,i,{entity_id:e.entity})}break;case"call-service":{if(!e.tap_action.service)break;const[t,n]=e.tap_action.service.split(".",2);this._hass.callService(t,n,e.tap_action.service_data);break}}},a("ll-rebuild",{})}),customElements.whenDefined("hui-glance-card").then(()=>{customElements.get("hui-glance-card").prototype.firstUpdated=function(){this.shadowRoot.querySelectorAll("ha-card div.entity").forEach(e=>{const t=e.attachShadow({mode:"open"});[...e.children].forEach(e=>t.appendChild(e));const n=document.createElement("style");t.appendChild(n),n.innerHTML="\n :host {\n box-sizing: border-box;\n padding: 0 4px;\n display: flex;\n flex-direction: column;\n align-items: center;\n cursor: pointer;\n margin-bottom: 12px;\n width: var(--glance-column-width, 20%);\n }\n div {\n width: 100%;\n text-align: center;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .name {\n min-height: var(--paper-font-body1_-_line-height, 20px);\n }\n state-badge {\n margin: 8px 0;\n }\n ";const i=e.entityConf;if(!i.style)return;let o=i.entity_ids;const a=()=>{s(t,i.style,{variables:{config:i},entity_ids:o},!!i.debug_cardmod)};a(),window.addEventListener("location-changed",a)})},a("ll-rebuild",{})})}]); \ No newline at end of file diff --git a/src/apply-style.js b/src/apply-style.js new file mode 100644 index 0000000..c323f49 --- /dev/null +++ b/src/apply-style.js @@ -0,0 +1,51 @@ +export const applyStyle = async function(root, style, params, debug) { + + const debugPrint = function(str){ + if(!debug) return; + if(typeof str === "string") + console.log(' '.repeat(2*(debug-1)) + str); + else + console.log(str); + } + + if(!root || !style) return; + + if(root.updateComplete) + await root.updateComplete; + + if(typeof style === "string") { + const oldStyleEl = root.querySelector("card-mod"); + if(oldStyleEl) { + oldStyleEl.update(); + return; + } + + // Add new style tag to the root element + const styleEl = document.createElement('card-mod'); + styleEl.template = { + template: style, + variables: params.variables, + entity_ids: params.entity_ids, + }; + root.appendChild(styleEl); + debugPrint("Applied styles to:"); + debugPrint(root); + } else { + Object.keys(style).forEach((k) => { + if(k === ".") { + debugPrint(`Stepping into root of ${root.tagName}`) + return applyStyle(root, style[k], params, debug?debug+1:0); + } else if(k === "$") { + debugPrint(`Stepping into ShadowRoot of ${root.tagName}`) + return applyStyle(root.shadowRoot, style[k], params, debug?debug+1:0); + } else { + debugPrint(`Searching for: "${k}". ${root.querySelectorAll(k).length} matches.`); + root.querySelectorAll(`${k}`).forEach((el) => { + debugPrint(`Stepping into ${el.tagName}`) + applyStyle(el, style[k], params, debug?debug+1:0); + }); + return; + } + }); + } +} diff --git a/src/card-mod.js b/src/card-mod.js new file mode 100644 index 0000000..4e762fd --- /dev/null +++ b/src/card-mod.js @@ -0,0 +1,66 @@ +import {subscribeRenderTemplate} from "/card-tools/templates.js"; + +class CardMod extends HTMLElement { + + disconnectedCallback() { + this._disconnect(); + } + connectedCallback() { + this._connect(); + } + + _has_template(data) { + if(data.template.includes("{%")) return true; + if(data.template.includes("{{")) return true; + return false; + } + + set template(data) { + this._data = data; + if(!this._has_template(data)) return; + + if(!this._data.entity_ids && this._data.template.includes("config.entity")) { + if(this._data.variables.config && this._data.variables.config.entity) { + this._data.entity_ids = [this._data.variables.config.entity]; + } + } + } + + update() { + this._disconnect().then(() => this._connect()); + } + + async _connect() { + if(!this._data) return; + + if(!this._has_template(this._data)) { + this.innerHTML = ``; + } + + if(this._unsubRenderTemplate) return; + this._unsubRenderTemplate = subscribeRenderTemplate(null, + (result) => this.innerHTML = ``, + this._data); + + this._unsubRenderTemplate.catch(() => { + this.innerHTML = ``; + this._unsubRenderTemplate = undefined; + }); + } + + async _disconnect() { + if(this._unsubRenderTemplate) { + try { + const unsub = await this._unsubRenderTemplate; + this._unsubRenderTemplate = undefined; + await unsub(); + } catch (e) { + if(e.code !== "not_found") + throw e; + } + } + } + +} + +customElements.define("card-mod", CardMod); diff --git a/src/ha-card.js b/src/ha-card.js new file mode 100644 index 0000000..6ef1417 --- /dev/null +++ b/src/ha-card.js @@ -0,0 +1,48 @@ +import {fireEvent} from "/card-tools/event.js"; +import {applyStyle} from "./apply-style.js"; + +customElements.whenDefined('ha-card').then(() => { + const HaCard = customElements.get('ha-card'); + + const findConfig = function(node) { + if(node.config) + return node.config; + if(node._config) + return node._config; + if(node.host) + return findConfig(node.host); + if(node.parentElement) + return findConfig(node.parentElement); + if(node.parentNode) + return findConfig(node.parentNode); + return null; + }; + + + HaCard.prototype.firstUpdated = function() { + // Move the header inside the slot instead of in the shadowDOM + // makes it easier to style it consistently + const header = this.shadowRoot.querySelector(".card-header"); + if(header) + { + this.insertBefore(header, this.children[0]); + } + + const config = findConfig(this); + if(!config || !config.style) return; + + let entity_ids = config.entity_ids; + + const apply = () => { + applyStyle(this, config.style, { + variables: {config}, + entity_ids + }, !!config.debug_cardmod); + } + + apply(); + window.addEventListener("location-changed", () => apply()); + } + + fireEvent('ll-rebuild', {}); +}); diff --git a/src/hui-entities-card.js b/src/hui-entities-card.js new file mode 100644 index 0000000..98ad408 --- /dev/null +++ b/src/hui-entities-card.js @@ -0,0 +1,67 @@ +import {fireEvent} from "/card-tools/event.js"; +import {applyStyle} from "./apply-style.js"; + +customElements.whenDefined('hui-entities-card').then(() => { + const EntitiesCard = customElements.get('hui-entities-card'); + + const oldRenderEntity = EntitiesCard.prototype.renderEntity; + EntitiesCard.prototype.renderEntity = function(config) { + + const retval = oldRenderEntity.bind(this)(config); + + if(!config.style) return retval; + if(!retval.values) return retval; + const row = retval.values[0]; + if(!row || !row.updateComplete) return retval; + + let entity_ids = config.entity_ids; + + const apply = () => { + applyStyle(row.shadowRoot, config.style, { + variables: {config}, + entity_ids + }, !!config.debug_cardmod); + } + + row.updateComplete.then(apply); + window.addEventListener("location-changed", apply); + return retval; + } + + EntitiesCard.prototype._handleClick = function(config) { + const action = config.tap_action && config.tap_action.action ? config.tap_action.action : "more-info"; + switch(action) { + case "more-info": + fireEvent("hass-more-info", { entityId: config.entity }, this); + break; + case "navigate": + history.pushState(null, "", config.tap_action.navigation_path); + fireEvent("location-changed", {}, this); + break; + case "url": + if(config.tap_action.url_path) { + window.open(config.tap_action.url_path); + } + break; + case "toggle": + if(config.entity) { + const domain = config.entity.split(".", 1)[0]; + const isOff = ["closed", "locked", "off"].includes(this._hass.states[config.entity].state) + const service = ({ + lock: ["unlock", "lock"], + cover: ["open_cover", "close_cover"]}[domain] + || ["turn_on", "turn_off"])[isOff?0:1]; + this._hass.callService(domain, service, { entity_id: config.entity }); + } + break; + case "call-service": { + if(!config.tap_action.service) break; + const [domain, service] = config.tap_action.service.split(".", 2); + this._hass.callService(domain, service, config.tap_action.service_data); + break; + } + } + }; + + fireEvent('ll-rebuild', {}); +}); diff --git a/src/hui-glance-card.js b/src/hui-glance-card.js new file mode 100644 index 0000000..65ecf43 --- /dev/null +++ b/src/hui-glance-card.js @@ -0,0 +1,57 @@ +import {fireEvent} from "/card-tools/event.js"; +import {applyStyle} from "./apply-style.js"; + +customElements.whenDefined('hui-glance-card').then(() => { + const GlanceCard = customElements.get('hui-glance-card'); + + GlanceCard.prototype.firstUpdated = function () { + const entities = this.shadowRoot.querySelectorAll("ha-card div.entity"); + entities.forEach((e) => { + const root = e.attachShadow({mode: "open"}); + [...e.children].forEach((el) => root.appendChild(el)); + const styletag = document.createElement("style"); + root.appendChild(styletag); + styletag.innerHTML = ` + :host { + box-sizing: border-box; + padding: 0 4px; + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + margin-bottom: 12px; + width: var(--glance-column-width, 20%); + } + div { + width: 100%; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .name { + min-height: var(--paper-font-body1_-_line-height, 20px); + } + state-badge { + margin: 8px 0; + } + `; + + const config = e.entityConf; + if(!config.style) return; + let entity_ids = config.entity_ids; + + const apply = () => { + applyStyle(root, config.style, { + variables: {config}, + entity_ids + }, !!config.debug_cardmod); + } + + apply(); + window.addEventListener("location-changed", apply); + }); + } + + fireEvent('ll-rebuild', {}); +}); diff --git a/src/main.js b/src/main.js index 2af62f2..35723f1 100644 --- a/src/main.js +++ b/src/main.js @@ -1,254 +1,4 @@ -import {html, css} from "/card-tools/lit-element.js"; -import {fireEvent} from "/card-tools/event.js"; -import {subscribeRenderTemplate} from "/card-tools/templates.js"; - -class CardMod extends HTMLElement { - - disconnectedCallback() { - this._disconnect(); - } - connectedCallback() { - this._connect(); - } - - _has_template(data) { - if(data.template.includes("{%")) return true; - if(data.template.includes("{{")) return true; - return false; - } - - set template(data) { - this._data = data; - if(!this._has_template(data)) return; - - if(!this._data.entity_ids && this._data.template.includes("config.entity")) { - if(this._data.variables.config && this._data.variables.config.entity) { - this._data.entity_ids = [this._data.variables.config.entity]; - } - } - } - - update() { - this._disconnect().then(() => this._connect()); - } - - async _connect() { - if(!this._data) return; - - if(!this._has_template(this._data)) { - this.innerHTML = ``; - } - - if(this._unsubRenderTemplate) return; - this._unsubRenderTemplate = subscribeRenderTemplate(null, - (result) => this.innerHTML = ``, - this._data); - - this._unsubRenderTemplate.catch(() => { - this.innerHTML = ``; - this._unsubRenderTemplate = undefined; - }); - } - - async _disconnect() { - if(this._unsubRenderTemplate) { - try { - const unsub = await this._unsubRenderTemplate; - this._unsubRenderTemplate = undefined; - await unsub(); - } catch (e) { - if(e.code !== "not_found") - throw e; - } - } - } - -} - -customElements.define("card-mod", CardMod); - - -const applyStyle = async function(root, style, params, debug) { - - const debugPrint = function(str){ - if(!debug) return; - if(typeof str === "string") - console.log(' '.repeat(2*(debug-1)) + str); - else - console.log(str); - } - - if(!root || !style) return; - - if(root.updateComplete) - await root.updateComplete; - - if(typeof style === "string") { - const oldStyleEl = root.querySelector("card-mod"); - if(oldStyleEl) { - oldStyleEl.update(); - return; - } - - // Add new style tag to the root element - const styleEl = document.createElement('card-mod'); - styleEl.template = { - template: style, - variables: params.variables, - entity_ids: params.entity_ids, - }; - root.appendChild(styleEl); - debugPrint("Applied styles to:"); - debugPrint(root); - } else { - Object.keys(style).forEach((k) => { - if(k === ".") { - debugPrint(`Stepping into root of ${root.tagName}`) - return applyStyle(root, style[k], params, debug?debug+1:0); - } else if(k === "$") { - debugPrint(`Stepping into ShadowRoot of ${root.tagName}`) - return applyStyle(root.shadowRoot, style[k], params, debug?debug+1:0); - } else { - debugPrint(`Searching for: "${k}". ${root.querySelectorAll(k).length} matches.`); - root.querySelectorAll(`${k}`).forEach((el) => { - debugPrint(`Stepping into ${el.tagName}`) - applyStyle(el, style[k], params, debug?debug+1:0); - }); - return; - } - }); - } -} - - -customElements.whenDefined('ha-card').then(() => { - const HaCard = customElements.get('ha-card'); - - const findConfig = function(node) { - if(node.config) - return node.config; - if(node._config) - return node._config; - if(node.host) - return findConfig(node.host); - if(node.parentElement) - return findConfig(node.parentElement); - if(node.parentNode) - return findConfig(node.parentNode); - return null; - }; - - - HaCard.prototype.firstUpdated = function() { - // Move the header inside the slot instead of in the shadowDOM - // makes it easier to style it consistently - const header = this.shadowRoot.querySelector(".card-header"); - if(header) - { - this.insertBefore(header, this.children[0]); - } - - const config = findConfig(this); - if(!config || !config.style) return; - - let entity_ids = config.entity_ids; - - const apply = () => { - applyStyle(this, config.style, { - variables: {config}, - entity_ids - }, !!config.debug_cardmod); - } - - apply(); - window.addEventListener("location-changed", () => apply()); - } - - fireEvent('ll-rebuild', {}); -}); - - -customElements.whenDefined('hui-entities-card').then(() => { - const EntitiesCard = customElements.get('hui-entities-card'); - - const oldRenderEntity = EntitiesCard.prototype.renderEntity; - EntitiesCard.prototype.renderEntity = function(config) { - - const retval = oldRenderEntity.bind(this)(config); - - if(!config.style) return retval; - if(!retval.values) return retval; - const row = retval.values[0]; - if(!row || !row.updateComplete) return retval; - - let entity_ids = config.entity_ids; - - const apply = () => { - applyStyle(row.shadowRoot, config.style, { - variables: {config}, - entity_ids - }, !!config.debug_cardmod); - } - - row.updateComplete.then(apply); - window.addEventListener("location-changed", apply); - return retval; - } - fireEvent('ll-rebuild', {}); -}); - - -customElements.whenDefined('hui-glance-card').then(() => { - const GlanceCard = customElements.get('hui-glance-card'); - - GlanceCard.prototype.firstUpdated = function () { - const entities = this.shadowRoot.querySelectorAll("ha-card div.entity"); - entities.forEach((e) => { - const root = e.attachShadow({mode: "open"}); - [...e.children].forEach((el) => root.appendChild(el)); - const styletag = document.createElement("style"); - root.appendChild(styletag); - styletag.innerHTML = ` - :host { - box-sizing: border-box; - padding: 0 4px; - display: flex; - flex-direction: column; - align-items: center; - cursor: pointer; - margin-bottom: 12px; - width: var(--glance-column-width, 20%); - } - div { - width: 100%; - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .name { - min-height: var(--paper-font-body1_-_line-height, 20px); - } - state-badge { - margin: 8px 0; - } - `; - - const config = e.entityConf; - if(!config.style) return; - let entity_ids = config.entity_ids; - - const apply = () => { - applyStyle(root, config.style, { - variables: {config}, - entity_ids - }, !!config.debug_cardmod); - } - - apply(); - window.addEventListener("location-changed", apply); - }); - } - - fireEvent('ll-rebuild', {}); -}); +import "./card-mod.js"; +import "./ha-card.js"; +import "./hui-entities-card.js"; +import "./hui-glance-card.js";