diff --git a/custom-ui-gpt.js b/custom-ui-gpt.js index 30dd944..1996d6b 100644 --- a/custom-ui-gpt.js +++ b/custom-ui-gpt.js @@ -1,7 +1,7 @@ "use strict"; const Name = "Custom-ui"; -const Version = "20221228-GPT"; +const Version = "20230102-GPT"; const Description = "adapted for HA 2022.4 + "; const Url = "https://github.com/Mariusthvdb/custom-ui"; console.info( @@ -9,26 +9,37 @@ console.info( "color: gold; font-weight: bold; background: black", "color: white; font-weight: bold; background: steelblue" ); - window.customUI = window.customUI || { - -// optimized version of the lightOrShadow function using the ternary operator to perform -// the check for the shadow root: - lightOrShadow(elem, selector) { - const result = elem.shadowRoot ? elem.shadowRoot.querySelector(selector) : elem.querySelector(selector); + const result = elem.shadowRoot + ? elem.shadowRoot.querySelector(selector) + : elem.querySelector(selector); return result; }, - maybeApplyTemplateAttributes(hass, states, entity) { + var _entity$untemplated_a2; const newAttributes = {}; Object.keys(entity.attributes.templates).forEach((key) => { + var _entity$untemplated_a; if (key === "state") { - console.warn(`State templating is not supported anymore, please check you customization for ${entity.entity_id}`); + console.warn( + `State templating is not supported anymore, please check you customization for ${entity.entity_id}` + ); return; } const template = entity.attributes.templates[key]; - const value = window.customUI.computeTemplate(template, hass, states, entity, entity.attributes, (entity.untemplated_attributes?.[key]) || entity.attributes[key], entity.untemplated_state || entity.state); + const value = window.customUI.computeTemplate( + template, + hass, + states, + entity, + entity.attributes, + ((_entity$untemplated_a = entity.untemplated_attributes) === null || + _entity$untemplated_a === void 0 + ? void 0 + : _entity$untemplated_a[key]) || entity.attributes[key], + entity.untemplated_state || entity.state + ); if (value === null) return; newAttributes[key] = value; }); @@ -36,194 +47,170 @@ window.customUI = window.customUI || { ...entity, attributes: { ...entity.attributes, - ...newAttributes, + ...newAttributes }, - untemplated_attributes: entity.untemplated_attributes ?? entity.attributes, + untemplated_attributes: + (_entity$untemplated_a2 = entity.untemplated_attributes) !== null && + _entity$untemplated_a2 !== void 0 + ? _entity$untemplated_a2 + : entity.attributes }; }, - -// Here is an alternative implementation of the updateMoreInfo function using -// requestAnimationFrame to wait until the DOM elements are available: -// This implementation will continuously check for the availability of the DOM elements -// until they are found, at which point it will perform the necessary operations and then exit. -// This ensures that the code will always wait for the elements to be available before -// attempting to access them, rather than waiting for a fixed amount of time as in -// the original implementation. - -// The EL suffix is used to denote that the variables are DOM elements. It is a naming -// convention that is often used to indicate the type of the variable. In this case, it helps -// to clearly distinguish the variables that represent DOM elements from other variables in -// the code. This can make the code easier to read and understand, especially when working -// with a large number of variables. - -// uses the for...of loop to iterate over the t array, and also preserves the original -// behavior of scheduling another check if the elements are not yet available: - updateMoreInfo(ev) { if (!ev.detail.expanded) return; - + let homeAssistantEl; + let moreInfoDialogEl; + let dialogEl; + let contentEl; + let moreInfoInfoEl; + let moreInfoContentEl; + let moreInfoEl; + let moreInfoNodeName; + let moreInfoGroupEl; + let moreInfoNestedEl; + let moreInfoNestedNodeName; + let attributesEl; + let t; + let e; const checkElements = () => { - try { - const homeAssistantEl = document.querySelector("home-assistant"); - const moreInfoDialogEl = homeAssistantEl.shadowRoot.querySelector("ha-more-info-dialog"); - const dialogEl = moreInfoDialogEl.shadowRoot.querySelector("ha-dialog"); - const contentEl = dialogEl.getElementsByClassName("content")[0]; - const moreInfoInfoEl = contentEl.querySelector("ha-more-info-info"); - const moreInfoContentEl = moreInfoInfoEl.shadowRoot.querySelector("more-info-content"); - const moreInfoEl = moreInfoContentEl.firstElementChild; - const moreInfoNodeName = moreInfoEl.nodeName.toLowerCase(); - - let t; - if (moreInfoNodeName === "more-info-group") { - const moreInfoGroupEl = moreInfoEl; - const moreInfoNestedEl = moreInfoGroupEl.firstElementChild; - const moreInfoNestedNodeName = moreInfoNestedEl.nodeName.toLowerCase(); - t = moreInfoGroupEl.shadowRoot.querySelector(moreInfoNestedNodeName).shadowRoot.querySelector("ha-attributes").shadowRoot.querySelectorAll(".data-entry"); - } else { - t = moreInfoEl.shadowRoot.querySelector("ha-attributes").shadowRoot.querySelectorAll(".data-entry"); - } - - if (t.length) { - let e; - for (const entry of t) { - const keyEl = entry.getElementsByClassName("key")[0]; - if (keyEl.innerText.toLowerCase().trim() === "hide attributes") { - e = keyEl.parentNode.getElementsByClassName("value")[0].innerText.split(",").map((item) => item.replace("_", " ").trim()); - e.push("hide attributes"); - } - } - - for (const entry of t) { - const keyEl = entry.getElementsByClassName("key")[0]; - if (e.includes(keyEl.innerText.toLowerCase().trim()) || e.includes("all")) keyEl.parentNode.style.display = "none"; + homeAssistantEl = + homeAssistantEl || document.querySelector("home-assistant"); + moreInfoDialogEl = + moreInfoDialogEl || + homeAssistantEl.shadowRoot.querySelector("ha-more-info-dialog"); + dialogEl = + dialogEl || moreInfoDialogEl.shadowRoot.querySelector("ha-dialog"); + contentEl = contentEl || dialogEl.getElementsByClassName("content")[0]; + moreInfoInfoEl = + moreInfoInfoEl || contentEl.querySelector("ha-more-info-info"); + moreInfoContentEl = + moreInfoContentEl || + moreInfoInfoEl.shadowRoot.querySelector("more-info-content"); + moreInfoEl = moreInfoEl || moreInfoContentEl.firstElementChild; + moreInfoNodeName = moreInfoNodeName || moreInfoEl.nodeName.toLowerCase(); + if (moreInfoNodeName === "more-info-group") { + moreInfoGroupEl = moreInfoGroupEl || moreInfoEl; + moreInfoNestedEl = + moreInfoNestedEl || moreInfoGroupEl.firstElementChild; + moreInfoNestedNodeName = + moreInfoNestedNodeName || moreInfoNestedEl.nodeName.toLowerCase(); + attributesEl = + attributesEl || + moreInfoGroupEl.shadowRoot + .querySelector(moreInfoNestedNodeName) + .shadowRoot.querySelector("ha-attributes").shadowRoot; + } else { + attributesEl = + attributesEl || + moreInfoEl.shadowRoot.querySelector("ha-attributes").shadowRoot; + } + t = attributesEl ? attributesEl.querySelectorAll(".data-entry") : null; + if (t && t.length) { + e = []; + for (const entry of t) { + const keyEl = entry.getElementsByClassName("key")[0]; + if (keyEl.innerText.toLowerCase().trim() === "hide attributes") { + e = keyEl.parentNode + .getElementsByClassName("value")[0] + .innerText.split(",") + .map((item) => item.replace("_", " ").trim()); + e.push("hide attributes"); } } - } catch (err) { + for (const entry of t) { + const keyEl = entry.getElementsByClassName("key")[0]; + if ( + e.includes(keyEl.innerText.toLowerCase().trim()) || + e.includes("all") + ) + keyEl.parentNode.style.display = "none"; + } + } else { // elements are not available yet, schedule another check requestAnimationFrame(checkElements); } }; - requestAnimationFrame(checkElements); }, -// installStatesHook() { -// const homeAssistant = customElements.get('home-assistant'); -// if (!homeAssistant?.prototype?._updateHass) -// return; -// -// const originalUpdate = homeAssistant.prototype._updateHass; -// homeAssistant.prototype._updateHass = function update(obj) { -// const { hass } = this; -// -// if (obj.hasOwnProperty('states')) { -// for (const key in obj.states) { -// if (!obj.states.hasOwnProperty(key)) continue; -// -// const entity = obj.states[key]; -// -// if (!entity.attributes.templates) continue; -// -// const newEntity = window.customUI.maybeApplyTemplateAttributes(hass, obj.states, entity); -// if (hass.states && entity !== hass.states[key]) { -// obj.states[key] = newEntity; -// } else if (entity !== newEntity) { -// obj.states[key] = newEntity; -// } -// } -// } -// -// originalUpdate.call(this, obj); -// }; -// }, - -// The main difference between the two code blocks is that the first one waits for the -// home-assistant custom element to be defined before it modifies the _updateHass method, -// while the second block modifies the _updateHass method immediately. -// -// In the first code block, the whenDefined method is used to wait until the home-assistant -// custom element is defined, and then it modifies the _updateHass method. This is useful -// if the home-assistant element has not yet been defined when the code is executed, as it -// ensures that the modification is applied only when the element is available. -// -// In the second code block, the _updateHass method is modified immediately, without waiting -// for the home-assistant element to be defined. This assumes that the home-assistant element -// is already defined and available. -// -// Both code blocks perform the same function, which is to modify the _updateHass method -// of the home-assistant element in order to update the states of the entity with a new -// value. The difference is just in the way that the modification is applied, either -// immediately or after waiting for the element to be defined. - installStatesHook() { customElements.whenDefined("home-assistant").then(() => { - const homeAssistant = customElements.get('home-assistant'); - if (!homeAssistant?.prototype?._updateHass) return; - + var _homeAssistant$protot; + const homeAssistant = customElements.get("home-assistant"); + if ( + !( + homeAssistant !== null && + homeAssistant !== void 0 && + (_homeAssistant$protot = homeAssistant.prototype) !== null && + _homeAssistant$protot !== void 0 && + _homeAssistant$protot._updateHass + ) + ) + return; window.customUI.updateHass = homeAssistant.prototype._updateHass; homeAssistant.prototype._updateHass = function update(obj) { const { hass } = this; - if (obj.states) { Object.values(obj.states).forEach((entity) => { if (!entity.attributes.templates) return; - obj.states[entity.entity_id] = window.customUI.maybeApplyTemplateAttributes(hass, obj.states, entity); + obj.states[entity.entity_id] = + window.customUI.maybeApplyTemplateAttributes( + hass, + obj.states, + entity + ); }); } - window.customUI.updateHass.call(this, obj); }; }); }, - - installStateBadge() { customElements.whenDefined("state-badge").then(() => { - const stateBadge = customElements.get('state-badge'); + const stateBadge = customElements.get("state-badge"); if (!stateBadge) return; - if (stateBadge.prototype.updated) { const originalUpdated = stateBadge.prototype.updated; stateBadge.prototype.updated = function customUpdated(changedProps) { - if (!changedProps.has('stateObj')) return; - + if (!changedProps.has("stateObj")) return; const { stateObj } = this; - - if (stateObj.attributes.icon_color && !stateObj.attributes.entity_picture) { - this.style.backgroundImage = ''; + if ( + stateObj.attributes.icon_color && + !stateObj.attributes.entity_picture + ) { + this.style.backgroundImage = ""; this._showIcon = true; - this._iconStyle= { color: stateObj.attributes.icon_color }; + this._iconStyle = { + color: stateObj.attributes.icon_color + }; } - originalUpdated.call(this, changedProps); }; } }); }, - installClassHooks() { - if (window.customUI.classInitDone) - return; - + if (window.customUI.classInitDone) return; window.customUI.classInitDone = true; window.customUI.installStatesHook(); window.customUI.installStateBadge(); }, - init() { - if (window.customUI.initDone) - return; - + var _main$hass; + if (window.customUI.initDone) return; window.customUI.installClassHooks(); - const main = window.customUI.lightOrShadow(document, "home-assistant"); - if (!main.hass?.states) { + if ( + !( + (_main$hass = main.hass) !== null && + _main$hass !== void 0 && + _main$hass.states + ) + ) { window.setTimeout(window.customUI.init, 1000); return; } - window.customUI.initDone = true; window.addEventListener("expanded-changed", window.customUI.updateMoreInfo); - window.CUSTOM_UI_LIST = window.CUSTOM_UI_LIST || []; window.CUSTOM_UI_LIST.push({ name: `${Name}`, @@ -231,45 +218,42 @@ window.customUI = window.customUI || { url: `${Url}` }); }, - -// computeTemplate(template, hass, entities, entity, attributes, attribute, state) { -// const functionBody = (template.indexOf('return') >= 0) ? template : `return \`${template}\`;`; -// -// try { -// return new Function('hass', 'entities', 'entity', 'attributes', 'attribute', 'state', functionBody) -// (hass, entities, entity, attributes, attribute, state); -// } catch (e) { -// if ((e instanceof SyntaxError) || e instanceof ReferenceError) { -// console.warn(`${e.name}: ${e.message} in template ${functionBody}`); -// return null; -// } -// throw e; -// } -// } - -// This version of the function uses a do-while loop to continuously retry the template -// computation until it succeeds or an error other than a SyntaxError or ReferenceError -// is thrown. This ensures that the function will always return a result if it is possible -// to compute one, and will only throw an error in the case of an unexpected exception. -// -// The use of the do-while loop also ensures that the function is thread-safe, as only a -// single thread will be able to execute the loop at a time. This eliminates the risk of -// race conditions or other synchronization issues that could arise when multiple threads -// try to access the function concurrently. - - computeTemplate(template, hass, entities, entity, attributes, attribute, state) { - const functionBody = (template.indexOf('return') >= 0) ? template : `return \`${template}\`;`; - + computeTemplate( + template, + hass, + entities, + entity, + attributes, + attribute, + state + ) { + const functionBody = + template.indexOf("return") >= 0 ? template : `return \`${template}\`;`; try { - const computeTemplateFn = new Function('hass', 'entities', 'entity', 'attributes', 'attribute', 'state', functionBody); + const computeTemplateFn = new Function( + "hass", + "entities", + "entity", + "attributes", + "attribute", + "state", + functionBody + ); let result; let error; do { try { - result = computeTemplateFn(hass, entities, entity, attributes, attribute, state); + result = computeTemplateFn( + hass, + entities, + entity, + attributes, + attribute, + state + ); error = null; } catch (e) { - if ((e instanceof SyntaxError) || e instanceof ReferenceError) { + if (e instanceof SyntaxError || e instanceof ReferenceError) { console.warn(`${e.name}: ${e.message} in template ${functionBody}`); return null; } @@ -281,12 +265,5 @@ window.customUI = window.customUI || { throw e; } } - }; - window.customUI.init(); - -// calls the init() function in the customUI object. This function initializes the custom UI -// by setting up event listeners and performing any other necessary setup tasks. -// It is likely that this function is called when the script is first loaded, -// to ensure that the custom UI is ready to function as intended. \ No newline at end of file