From 127153a489a76dc9e464ffa211704a8fca00df20 Mon Sep 17 00:00:00 2001 From: Marius <33354141+Mariusthvdb@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:18:00 +0100 Subject: [PATCH] refactor custom-ui-only (#132) * refactor custom-ui-only clear all more-info remnants add color to Entity card ! thank you @elchininet rename Hooks add some comments * console.groupCollapsed * add attributes icon_color and templates in description constants * add hook for core button-card * Update custom-ui-only.js --- custom-ui-only.js | 196 +++++++++++++++++++--------------------------- 1 file changed, 82 insertions(+), 114 deletions(-) diff --git a/custom-ui-only.js b/custom-ui-only.js index 537e04e..77a7443 100644 --- a/custom-ui-only.js +++ b/custom-ui-only.js @@ -1,24 +1,26 @@ // Define constants for the custom-ui component -const Name = "Custom-ui"; -const Version = "20230802"; -const Description = "add icon_color and templates"; +const Name = "Custom-ui only"; +const Version = "20240110"; +const Description = "add attributes icon_color and templates"; const Url = "https://github.com/Mariusthvdb/custom-ui"; // Log information about the custom-ui component -console.info( - `%c ${Name} \n%c Version ${Version} ${Description}`, +console.groupCollapsed( + `%c ${Name} ${Version} is installed \n%c ${Description} `, "color: gold; font-weight: bold; background: black", - "color: white; font-weight: bold; background: steelblue" -); + "color: white; font-weight: bold; background: steelblue"), +console.log("Readme:",Url), +console.groupEnd() // Define the custom-ui object and its methods window.customUI = { - // Helper function to find an element either in shadowRoot or regular DOM +// Helper function to find an element either in shadowRoot or regular DOM lightOrShadow: (elem, selector) => elem.shadowRoot ? elem.shadowRoot.querySelector(selector) : elem.querySelector(selector), - // Apply template attributes to an entity's attributes + +// Apply template attributes to an entity's attributes async maybeApplyTemplateAttributes(hass, states, entity) { const newAttributes = {}; const templateKeys = Object.keys(entity.attributes.templates); @@ -52,82 +54,9 @@ window.customUI = { }; return newEntity; }, - // Update the more-info dialog to hide certain attributes based on custom logic -// async updateMoreInfo(ev) { -// if (!ev.detail.expanded) return; -// const moreInfoInfo = document -// .querySelector("home-assistant") -// .shadowRoot.querySelector("ha-more-info-dialog") -// .shadowRoot.querySelector("ha-dialog") -// .getElementsByClassName("content")[0] -// .querySelector("ha-more-info-info"); -// -// try { -// let t; -// { -// let moreInfoNodeName; -// const contentChild = moreInfoInfo.shadowRoot.querySelector( -// "more-info-content" -// ).childNodes; -// for (const nodeItem of contentChild) { -// if (nodeItem.nodeName.toLowerCase().startsWith("more-info-")) { -// moreInfoNodeName = nodeItem.nodeName.toLowerCase(); -// break; -// } -// } -// if (moreInfoNodeName == "more-info-group") { -// let moreInfoNestedNodeName; -// const contentChildNested = -// moreInfoInfo.shadowRoot -// .querySelector("more-info-group") -// .shadowRoot.childNodes; -// for (const nodeItemNested of contentChildNested) { -// if ( -// nodeItemNested.nodeName.toLowerCase().startsWith("more-info-") -// ) { -// moreInfoNestedNodeName = nodeItemNested.nodeName.toLowerCase(); -// break; -// } -// } -// t = moreInfoInfo.shadowRoot -// .querySelector("more-info-group") -// .shadowRoot.querySelector(moreInfoNestedNodeName) -// .shadowRoot.querySelector("ha-attributes") -// .shadowRoot.querySelectorAll(".data-entry"); -// } else { -// t = moreInfoInfo.shadowRoot -// .querySelector(moreInfoNodeName) -// .shadowRoot.querySelector("ha-attributes") -// .shadowRoot.querySelectorAll(".data-entry"); -// } -// } -// if (t.length) { -// let e; -// for (const node of t) { -// const o = node.getElementsByClassName("key")[0]; -// if (o.innerText.toLowerCase().trim() == "hide attributes") { -// e = o.parentNode -// .getElementsByClassName("value")[0] -// .innerText.split(",") -// .map((item) => item.replace("_", " ").trim()); -// e.push("hide attributes"); -// } -// } -// for (const node of t) { -// const o = node.getElementsByClassName("key")[0]; -// if ( -// e.includes(o.innerText.toLowerCase().trim()) || -// e.includes("all") -// ) { -// o.parentNode.style.display = "none"; -// } -// } -// } -// } catch (err) {} -// }, - // Install a hook to update the states with template attributes - installStatesHook() { +// Install a hook to update the states with template attributes + installTemplateAttributesHook() { customElements.whenDefined("home-assistant").then(() => { const homeAssistant = customElements.get("home-assistant"); if (!homeAssistant?.prototype?._updateHass) return; @@ -146,9 +75,9 @@ window.customUI = { obj.states, entity ); - if (hass.states && entity !== hass.states[key]) { + if (hass.states && JSON.stringify(entity) !== JSON.stringify(hass.states[key])) { obj.states[key] = newEntity; - } else if (entity !== newEntity) { + } else if (JSON.stringify(entity) !== JSON.stringify(newEntity)) { obj.states[key] = newEntity; } }); @@ -157,8 +86,57 @@ window.customUI = { }; }); }, - // Install a hook to update the state badge with custom styling - installStateBadge() { + +// Install a hook to update the button card with custom styling + installButtonCardStylingHook() { + customElements.whenDefined("hui-button-card").then(() => { + const buttonCard = customElements.get("hui-button-card"); + if (!buttonCard) return; + if (buttonCard.prototype?.updated) { + const originalUpdated = buttonCard.prototype.updated; + buttonCard.prototype.updated = function customUpdated(changedProps) { + if (!changedProps.has('_stateObj')) { + return; + } + const { _stateObj } = this; + if (_stateObj.attributes?.icon_color) { + this.style?.setProperty('--icon-primary-color', _stateObj.attributes.icon_color); + } + originalUpdated.call(this, changedProps); + } + } + }); + }, + +// Install a hook to update the entity card with custom styling + installEntityCardStylingHook() { + customElements.whenDefined("hui-entity-card").then(() => { + const entityCard = customElements.get("hui-entity-card"); + if (!entityCard) return; + if (entityCard.prototype?.updated) { + const originalUpdated = entityCard.prototype.updated; + entityCard.prototype.updated = function customUpdated(changedProps) { + if ( + !changedProps.has('_config') || + !changedProps.has('hass') + ) { + return; + } + const { _config, hass } = this; + const entityId = _config?.entity; + const states = hass?.states; + const iconColor = states?.[entityId]?.attributes?.icon_color; + if (iconColor) { + this.style?.setProperty('--paper-item-icon-color', iconColor); + } + originalUpdated.call(this, changedProps); + } + } + }); + }, + +// Install a hook to update the state badge with custom styling + installStateBadgeStylingHook() { customElements.whenDefined("state-badge").then(() => { const stateBadge = customElements.get("state-badge"); if (!stateBadge) return; @@ -182,16 +160,20 @@ window.customUI = { } }); }, - // Install the hooks for updating states and state badges - installClassHooks() { - window.customUI.installStatesHook(); - window.customUI.installStateBadge(); + +// Install the hooks for updating states, entity cards, and state badges + installCustomHooks() { + window.customUI.installTemplateAttributesHook(); + window.customUI.installButtonCardStylingHook(); + window.customUI.installEntityCardStylingHook(); + window.customUI.installStateBadgeStylingHook(); }, + async init() { - // Check if initialization has already been done +// Check if initialization has already been done if (window.customUI.initDone) return; - // Wait for the hass.states to be populated +// Wait for the hass.states to be populated const main = window.customUI.lightOrShadow(document, "home-assistant"); await new Promise((resolve) => { const intervalId = setInterval(() => { @@ -202,14 +184,11 @@ window.customUI = { }, 100); }); - // Install the hooks and mark initialization as done - window.customUI.installClassHooks(); +// Install the hooks and mark initialization as done + window.customUI.installCustomHooks(); window.customUI.initDone = true; - // Add an event listener for expanded-changed events -// window.addEventListener("expanded-changed", window.customUI.updateMoreInfo); - - // Push custom-ui information to a global list +// Push custom-ui information to a global list window.CUSTOM_UI_LIST = window.CUSTOM_UI_LIST || []; window.CUSTOM_UI_LIST.push({ name: Name, @@ -217,7 +196,8 @@ window.customUI = { url: Url }); }, - // Evaluate a template expression + +// Evaluate a template expression computeTemplate( template, hass, @@ -245,18 +225,6 @@ window.customUI = { } } }; + // Initialize the custom-ui component window.customUI.init(); - - -// The code you provided seems to be responsible for initializing and setting up some hooks in your application. Here are a few suggestions for potential improvements: -// -// Consolidate Hook Installation: Currently, the installClassHooks function checks if the hooks have already been installed and then sets a flag to avoid repeated installation. Instead of checking the flag in both installClassHooks and init functions, you can move the flag check to the init function and directly call installClassHooks without the need for a separate flag. -// Use setTimeout with Promises: In the init function, you're using setTimeout to delay the initialization if main.hass.states is falsy. Instead of using setTimeout, you can use await with a Promise to wait for a certain condition to be met. For example, you could wrap the initialization logic in a Promise and use await to delay the execution until main.hass.states is truthy. -// Error Handling in computeTemplate: In the computeTemplate function, you catch SyntaxError and ReferenceError exceptions and log a warning. However, you rethrow other types of exceptions. It might be beneficial to handle all types of exceptions consistently, either by logging warnings or by throwing them. - -// Here are the applied recommendations: -// -// Consolidate Hook Installation: The flag check for installation has been moved to the init function, and the installClassHooks function is directly called without the need for a separate flag. -// Use setTimeout with Promises: The use of setTimeout has been replaced with await and a Promise to wait for the main.hass.states to be populated before initializing. -// Error Handling in computeTemplate: The error handling in computeTemplate has been updated to log a warning for all types of exceptions consistently. \ No newline at end of file