From 7502a5c0e6f051d7408e28c788b994933a785f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Mon, 11 Nov 2019 22:09:58 +0100 Subject: [PATCH] Add mod-card --- README.md | 57 ++++++++++++++++++++++--------------------------- card-mod.js | 9 +++++++- src/main.js | 9 ++++---- src/mod-card.js | 39 +++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 37 deletions(-) create mode 100644 src/mod-card.js diff --git a/README.md b/README.md index b69718e..1529b7e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Specifically, it looks for `style:` in any cards configuration, and applies the The basis of almost all lovelace cards is a `ha-card` element, so that's probably where you'd want to start. -Note that some cards (`conditional`, `entity-filter`, `horizontal-stack` and `vertical-stack` as well as some custom cards, like `layout-card`, `auto-entities` and `state-switch` among others) do *not* have a `ha-card` element, and `card-mod` will thus **not** work for those. There is a workaround, though. See FAQ below. +Note that some cards (`conditional`, `entity-filter`, `horizontal-stack` and `vertical-stack` as well as some custom cards, like `layout-card`, `auto-entities` and `state-switch` among others) do *not* have a `ha-card` element, and `card-mod` will thus **not** work for those. There is a workaround, though. See [mod-card below](#mod-card). --- @@ -183,6 +183,28 @@ entity: alarm_control_panel.alarm ![advanced](https://user-images.githubusercontent.com/1299821/59151780-59732c80-8a39-11e9-9f19-34d3e0dbd8e9.png) +## Mod-card +There are some cards where card-mod just won't work. +Those cards often are not really *cards* at all, but change how other cards work. Examples include: `conditional`, `entity-filter`, `horizontal-stack` and `vertical-stack` as well as some custom cards, like `layout-card`, `auto-entities` and `state-switch` among others. + +For those cases, a special `mod-card` is provided as a workaround: + +```yaml +type: custom:mod-card +style: | + ha-card { + border: 1px solid green; + } +card: + type: vertical-stack + cards: + - type: light + entity: light.bed_light + - type: light + entity: light.kitchen_lights +``` +![mod-card](https://user-images.githubusercontent.com/1299821/68621707-b71c1100-04d0-11ea-8473-965dbd77b762.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: @@ -220,6 +242,8 @@ entities: - light.bed_light ``` + + # FAQ ### How do I convert my old card-modder configuration to card-mod? @@ -245,36 +269,5 @@ For rows in an entities card, you replace `ha-card` with `:host` as described ab Note that some cards can't be modded with card-mod. See below. -### Why won't this work for some cards? -The cards this doesn't work for often are not really *cards* at all, but change how other cards work. Examples include: `conditional`, `entity-filter`, `horizontal-stack` and `vertical-stack` as well as some custom cards, like `layout-card`, `auto-entities` and `state-switch` among others. - -Common for all those are that they have no `ha-card` element. A workaround for this is to put the card you want inside an entities card and mod that. For built-in cards, this can be done by setting the type of an entities row to `custom:hui--card`: - -```yaml -type: entities -style: | - ha-card { - --ha-card-background: red; - box-shadow: none; - background: none; - } - .card-content { - padding: 0 - } -entities: - - type: custom:hui-horizontal-stack-card - cards: - - type: entities - entities: - - light.bed_light - - light.kitchen_lights - - light.ceiling_lights - - type: glance - entities: - - light.bed_light - - light.kitchen_lights - - light.ceiling_lights -``` - --- Buy Me A Coffee diff --git a/card-mod.js b/card-mod.js index b2064bb..55ea926 100644 --- a/card-mod.js +++ b/card-mod.js @@ -1 +1,8 @@ -!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"]}();class a 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}}}function s(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",a);const r=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}`),r(e,t[a],n,i?i+1:0)):"$"===a?(o(`Stepping into ShadowRoot of ${e.tagName}`),r(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}`),r(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=()=>{r(this,n.style,{variables:{config:n},entity_ids:i},!!n.debug_cardmod)};o(),window.addEventListener("location-changed",()=>o())},s("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||!e.style)return n;if(!n||!n.values)return n;const i=n.values[0];if(!i||!i.updateComplete)return n;let o=e.entity_ids;const a=()=>{r(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":s("hass-more-info",{entityId:e.entity},this);break;case"navigate":history.pushState(null,"",e.tap_action.navigation_path),s("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}}},s("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.config||e.entityConf;if(!i||!i.style)return;let o=i.entity_ids;const a=()=>{r(t,i.style,{variables:{config:i},entity_ids:o},!!i.debug_cardmod)};a(),window.addEventListener("location-changed",a)})},s("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"]}();class s 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 s={user:i().user.name,browser:o,hash:location.hash.substr(1)||" ",...n.variables},a=n.template,r=n.entity_ids;return e.subscribeMessage(e=>t(e.result),{type:"render_template",template:a,variables:s,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}}}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",s);const r=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 s=document.createElement("card-mod");s.template={template:t,variables:n.variables,entity_ids:n.entity_ids},e.appendChild(s),o("Applied styles to:"),o(e)}else Object.keys(t).forEach(s=>"."===s?(o(`Stepping into root of ${e.tagName}`),r(e,t[s],n,i?i+1:0)):"$"===s?(o(`Stepping into ShadowRoot of ${e.tagName}`),r(e.shadowRoot,t[s],n,i?i+1:0)):(o(`Searching for: "${s}". ${e.querySelectorAll(s).length} matches.`),void e.querySelectorAll(`${s}`).forEach(e=>{o(`Stepping into ${e.tagName}`),r(e,t[s],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=()=>{r(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||!e.style)return n;if(!n||!n.values)return n;const i=n.values[0];if(!i||!i.updateComplete)return n;let o=e.entity_ids;const s=()=>{r(i.shadowRoot,e.style,{variables:{config:e},entity_ids:o},!!e.debug_cardmod)};return i.updateComplete.then(s),window.addEventListener("location-changed",s),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.config||e.entityConf;if(!i||!i.style)return;let o=i.entity_ids;const s=()=>{r(t,i.style,{variables:{config:i},entity_ids:o},!!i.debug_cardmod)};s(),window.addEventListener("location-changed",s)})},a("ll-rebuild",{})});const c=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),l=c.prototype.html,d=(c.prototype.css,"custom:");function u(e,t){const n=document.createElement("hui-error-card");return n.setConfig({type:"error",error:e,config:t}),n}function h(e,t){if(!t||"object"!=typeof t||!t.type)return u(`No ${e} type configured`,t);let n=t.type;if(n=n.startsWith(d)?n.substr(d.length):`hui-${n}-${e}`,customElements.get(n))return function(e,t){const n=document.createElement(e);try{n.setConfig(t)}catch(e){return u(e,t)}return n}(n,t);const i=u(`Custom element doesn't exist: ${n}.`,t);i.style.display="None";const o=setTimeout(()=>{i.style.display=""},2e3);return customElements.whenDefined(n).then(()=>{clearTimeout(o),a("ll-rebuild",{},i)}),i}class p extends c{static get properties(){return{hass:{},config:{},noHass:{type:Boolean}}}setConfig(e){var t;this._config=e,this.el?this.el.setConfig(e):this.el=this.create(e),this._hass&&(this.el.hass=this._hass),this.noHass&&(t=this,document.querySelector("home-assistant").provideHass(t))}set config(e){this.setConfig(e)}set hass(e){this._hass=e,this.el&&(this.el.hass=e)}createRenderRoot(){return this}render(){return l`${this.el}`}}if(!customElements.get("card-maker")){class e extends p{create(e){return function(e){return h("card",e)}(e)}getCardSize(){return this.firstElementChild&&this.firstElementChild.getCardSize?this.firstElementChild.getCardSize():1}}customElements.define("card-maker",e)}if(!customElements.get("element-maker")){class e extends p{create(e){return function(e){return h("element",e)}(e)}}customElements.define("element-maker",e)}if(!customElements.get("entity-row-maker")){class e extends p{create(e){return function(e){const t=new Set(["call-service","divider","section","weblink"]);if(!e)return u("Invalid configuration given.",e);if("string"==typeof e&&(e={entity:e}),"object"!=typeof e||!e.entity&&!e.type)return u("Invalid configuration given.",e);const n=e.type||"default";if(t.has(n)||n.startsWith(d))return h("row",e);const i=e.entity.split(".",1)[0];return Object.assign(e,{type:{alert:"toggle",automation:"toggle",climate:"climate",cover:"cover",fan:"toggle",group:"group",input_boolean:"toggle",input_number:"input-number",input_select:"input-select",input_text:"input-text",light:"toggle",lock:"lock",media_player:"media-player",remote:"toggle",scene:"scene",script:"script",sensor:"sensor",timer:"timer",switch:"toggle",vacuum:"toggle",water_heater:"climate",input_datetime:"input-datetime"}[i]||"text"}),h("entity-row",e)}(e)}}customElements.define("entity-row-maker",e)}const m="\nha-card {\n background: none;\n box-shadow: none;\n}";customElements.define("mod-card",class extends c{setConfig(e){this._config=e,void 0===!e.style?this._config.style=m:"string"==typeof e.style?this._config.style=m+e.style:e.style["."]?this._config.style["."]=m+e.style["."]:this._config.style["."]=m}render(){return l` + + + + `}})}]); \ No newline at end of file diff --git a/src/main.js b/src/main.js index 35723f1..49cc72d 100644 --- a/src/main.js +++ b/src/main.js @@ -1,4 +1,5 @@ -import "./card-mod.js"; -import "./ha-card.js"; -import "./hui-entities-card.js"; -import "./hui-glance-card.js"; +import "./card-mod"; +import "./ha-card"; +import "./hui-entities-card"; +import "./hui-glance-card"; +import "./mod-card" \ No newline at end of file diff --git a/src/mod-card.js b/src/mod-card.js new file mode 100644 index 0000000..0e9d047 --- /dev/null +++ b/src/mod-card.js @@ -0,0 +1,39 @@ +import { LitElement, html, css} from "card-tools/src/lit-element"; +import "card-tools/src/card-maker"; + +const NO_STYLE = ` +ha-card { + background: none; + box-shadow: none; +}`; + +class ModCard extends LitElement { + + setConfig(config) { + this._config = config; + if(!config.style === undefined) + { + this._config.style = NO_STYLE; + } else if (typeof(config.style) === "string") { + this._config.style = NO_STYLE + config.style; + } else if (config.style["."]) { + this._config.style["."] = NO_STYLE + config.style["."]; + } else { + this._config.style["."] = NO_STYLE; + } + + } + + render() { + return html` + + + + `; + } +} + +customElements.define("mod-card", ModCard); \ No newline at end of file