diff --git a/compact-custom-header.js b/compact-custom-header.js index 493c680..b9972d8 100644 --- a/compact-custom-header.js +++ b/compact-custom-header.js @@ -1,423 +1,467 @@ console.info( - `%c COMPACT-CUSTOM-HEADER \n%c Version 1.4.1 `, + `%c COMPACT-CUSTOM-HEADER \n%c Version 1.4.2 `, "color: orange; font-weight: bold; background: black", "color: white; font-weight: bold; background: dimgray" ); -const LitElement = Object.getPrototypeOf( - customElements.get("ha-panel-lovelace") -); -const html = LitElement.prototype.html; -const hass = document.querySelector("home-assistant").hass; - -const fireEvent = (node, type, detail, options = {}) => { - detail = detail === null || detail === undefined ? {} : detail; - const event = new Event(type, { - bubbles: options.bubbles === undefined ? true : options.bubbles, - cancelable: Boolean(options.cancelable), - composed: options.composed === undefined ? true : options.composed - }); - event.detail = detail; - node.dispatchEvent(event); - return event; -}; - -const defaultConfig = { - header: true, - disable: false, - menu: "show", - voice: "show", - notifications: "show", - options: "show", - clock_format: 12, - clock_am_pm: true, - clock_date: false, - date_locale: hass.language, - chevrons: false, - redirect: true, - background: "", - hide_tabs: [], - show_tabs: [], - edit_mode_show_tabs: false, - default_tab: "", - default_tab_template: "", - kiosk_mode: false, - sidebar_swipe: true, - sidebar_closed: false, - disable_sidebar: false, - hide_help: false, - hide_config: false, - hide_unused: false, - tab_color: {}, - button_color: {}, - statusbar_color: "", - swipe: false, - swipe_amount: "15", - swipe_animate: "none", - swipe_skip: "", - swipe_wrap: true, - swipe_prevent_default: false, - swipe_skip_hidden: true, - warning: true, - compact_header: true, - view_css: "", - time_css: "", - date_css: "", - header_css: "", - tab_css: {}, - button_css: {} -}; - -let root = document.querySelector("home-assistant"); -root = root && root.shadowRoot; -root = root && root.querySelector("home-assistant-main"); -const main = root; -root = root && root.shadowRoot; -root = root && root.querySelector("app-drawer-layout partial-panel-resolver"); -const panelResolver = root; -root = (root && root.shadowRoot) || root; -root = root && root.querySelector("ha-panel-lovelace"); -root = root && root.shadowRoot; -root = root && root.querySelector("hui-root"); -const lovelace = root.lovelace; -root = root.shadowRoot; - -const frontendVersion = Number(window.frontendVersion); -const newSidebar = frontendVersion >= 20190710; -const header = root.querySelector("app-header"); -let cchConfig = buildConfig(lovelace.config.cch || {}); -if (typeof cchConfig.background == "boolean") cchConfig.background = ""; -const view = root.querySelector("ha-app-layout").querySelector("#view"); -const disabled = - window.location.href.includes("disable_cch") || cchConfig.disable; - -let sidebarClosed = false; -let editMode = header.className == "edit-mode"; -let firstRun = true; -let buttons = {}; -let prevColor = {}; - -run(); -breakingChangeNotification(); - -function run() { - const tabContainer = root.querySelector("paper-tabs"); - const tabs = tabContainer - ? Array.from(tabContainer.querySelectorAll("paper-tab")) - : []; - if (firstRun || buttons == undefined) { - buttons = getButtonElements(tabContainer); - } - if (!buttons.menu || !buttons.options || header.className == "edit-mode") { - return; - } - if (!disabled) { - insertEditMenu(tabs); - hideMenuItems(); - styleHeader(tabContainer, tabs, header); - styleButtons(tabs, tabContainer); - if (firstRun) { - sidebarMod(); - conditionalStyling(tabs, header); - } - hideTabs(tabContainer, tabs); - for (let button in buttons) { - if (cchConfig[button] == "clock") insertClock(button); - } - if (!editMode) tabContainerMargin(tabContainer); - if (cchConfig.swipe) swipeNavigation(tabs, tabContainer); - } - if (firstRun) { - observers(tabContainer, tabs, header); - defaultTab(tabs, tabContainer); - } - firstRun = false; - fireEvent(header, "iron-resize"); -} +class CompactCustomHeader { + constructor() { + this.LitElement = Object.getPrototypeOf( + customElements.get("ha-panel-lovelace") + ); + this.hass = document.querySelector("home-assistant").hass; + this.fireEvent = (node, type, detail, options = {}) => { + detail = detail === null || detail === undefined ? {} : detail; + const event = new Event(type, { + bubbles: options.bubbles === undefined ? true : options.bubbles, + cancelable: Boolean(options.cancelable), + composed: options.composed === undefined ? true : options.composed + }); + event.detail = detail; + node.dispatchEvent(event); + return event; + }; -function buildConfig(config) { - let exceptionConfig = {}; - let highestMatch = 0; - // Count number of matching conditions and choose config with most matches. - if (config.exceptions) { - config.exceptions.forEach(exception => { - const matches = countMatches(exception.conditions); - if (matches > highestMatch) { - highestMatch = matches; - exceptionConfig = exception.config; - } - }); + let ll = document.querySelector("home-assistant"); + ll = ll && ll.shadowRoot; + ll = ll && ll.querySelector("home-assistant-main"); + this.main = ll; + ll = ll && ll.shadowRoot; + ll = ll && ll.querySelector("app-drawer-layout partial-panel-resolver"); + this.panelResolver = ll; + ll = (ll && ll.shadowRoot) || ll; + ll = ll && ll.querySelector("ha-panel-lovelace"); + ll = ll && ll.shadowRoot; + ll = ll && ll.querySelector("hui-root"); + this.lovelace = ll.lovelace; + this.root = ll.shadowRoot; + + this.frontendVersion = Number(window.frontendVersion); + this.newSidebar = this.frontendVersion >= 20190710; + this.header = this.root.querySelector("app-header"); + this.editMode = this.header.className == "edit-mode"; + this.view = this.root.querySelector("ha-app-layout #view"); + + this.sidebarClosed = false; + this.firstRun = true; + this.buttons = {}; + this.prevColor = {}; + + this.defaultConfig = { + header: true, + disable: false, + menu: "show", + voice: "show", + notifications: "show", + options: "show", + clock_format: 12, + clock_am_pm: true, + clock_date: false, + date_locale: this.hass.language, + chevrons: false, + redirect: true, + background: "", + hide_tabs: "", + show_tabs: "", + edit_mode_show_tabs: false, + default_tab: "", + default_tab_template: "", + kiosk_mode: false, + sidebar_swipe: true, + sidebar_closed: false, + disable_sidebar: false, + hide_help: false, + hide_config: false, + hide_unused: false, + tab_color: {}, + button_color: {}, + statusbar_color: "", + swipe: false, + swipe_amount: "15", + swipe_animate: "none", + swipe_skip: "", + swipe_wrap: true, + swipe_prevent_default: false, + swipe_skip_hidden: true, + warning: true, + compact_header: true, + view_css: "", + time_css: "", + date_css: "", + header_css: "", + tab_css: {}, + button_css: {} + }; + + this.cchConfig = this.buildConfig( + this.lovelace.config.cch || {}, + this.hass.user.name + ); } - // If exception config uses hide_tabs and main config uses show_tabs, - // delete show_tabs and vice versa. - if ( - exceptionConfig.hide_tabs && - config.show_tabs && - exceptionConfig.hide_tabs.length && - config.show_tabs.length - ) { - delete config.show_tabs; - } else if ( - exceptionConfig.show_tabs && - config.hide_tabs && - exceptionConfig.show_tabs.length && - config.hide_tabs.length - ) { - delete config.hide_tabs; - } - - return { ...defaultConfig, ...config, ...exceptionConfig }; - - function countMatches(conditions) { - const userVars = { user: hass.user.name, user_agent: navigator.userAgent }; - let count = 0; - for (const cond in conditions) { - if (cond == "user" && conditions[cond].includes(",")) { - conditions[cond].split(/[ ,]+/).forEach(user => { - if (userVars[cond] == user) count++; - }); - } else { - if ( - userVars[cond] == conditions[cond] || - (cond == "query_string" && - window.location.search.includes(conditions[cond])) || - (cond == "user_agent" && userVars[cond].includes(conditions[cond])) || - (cond == "media_query" && window.matchMedia(conditions[cond]).matches) - ) { - count++; + + run() { + const tabContainer = this.root.querySelector("paper-tabs"); + const tabs = tabContainer + ? Array.from(tabContainer.querySelectorAll("paper-tab")) + : []; + let disabled = + window.location.href.includes("disable_cch") || this.cchConfig.disable; + + if (this.firstRun || this.buttons == undefined) { + this.buttons = this.getButtonElements(tabContainer); + } + if (!this.buttons.menu || !this.buttons.options || this.editMode) return; + if (!disabled) { + this.insertEditMenu(tabs); + this.hideMenuItems(); + this.styleHeader(tabContainer, tabs); + this.styleButtons(tabs, tabContainer); + if (this.firstRun) { + this.sidebarMod(); + this.conditionalStyling(tabs, this.header); + } + this.hideTabs(tabContainer, tabs); + for (let button in this.buttons) { + if (this.cchConfig[button] == "clock") this.insertClock(button); + } + if (!this.editMode) this.tabContainerMargin(tabContainer); + if (this.cchConfig.swipe) this.swipeNavigation(tabs, tabContainer); + if (this.firstRun) this.defaultTab(tabs, tabContainer); + } + if (this.firstRun) { + this.observers(tabContainer, tabs, disabled); + this.breakingChangeNotification(); + } + this.firstRun = false; + this.fireEvent(this.header, "iron-resize"); + } + + buildConfig(config, user_name) { + let exceptionConfig = {}; + let highestMatch = 0; + // Count number of matching conditions and choose config with most matches. + if (config.exceptions) { + config.exceptions.forEach(exception => { + const matches = countMatches(exception.conditions, user_name); + if (matches > highestMatch) { + highestMatch = matches; + exceptionConfig = exception.config; + } + }); + } + // If exception config uses hide_tabs and main config uses show_tabs, + // delete show_tabs and vice versa. + if ( + exceptionConfig.hide_tabs && + config.show_tabs && + exceptionConfig.hide_tabs.length && + config.show_tabs.length + ) { + delete config.show_tabs; + } else if ( + exceptionConfig.show_tabs && + config.hide_tabs && + exceptionConfig.show_tabs.length && + config.hide_tabs.length + ) { + delete config.hide_tabs; + } + + return { ...this.defaultConfig, ...config, ...exceptionConfig }; + + function countMatches(conditions, user_name) { + const userVars = { + user: user_name, + user_agent: navigator.userAgent + }; + let count = 0; + for (const cond in conditions) { + if (cond == "user" && conditions[cond].includes(",")) { + conditions[cond].split(/[ ,]+/).forEach(user => { + if (userVars[cond] == user) count++; + }); } else { - return 0; + if ( + userVars[cond] == conditions[cond] || + (cond == "query_string" && + window.location.search.includes(conditions[cond])) || + (cond == "user_agent" && + userVars[cond].includes(conditions[cond])) || + (cond == "media_query" && + window.matchMedia(conditions[cond]).matches) + ) { + count++; + } else { + return 0; + } } } + return count; } - return count; } -} -function observers(tabContainer, tabs, header) { - const callback = mutations => { - // Theme Changed. - if (mutations[0].target.nodeName == "HTML") { - mutations = [mutations[0]]; - styleHeader(tabContainer, tabs, header); - conditionalStyling(tabs, header); - return; - } - mutations.forEach(({ addedNodes, target }) => { - if (addedNodes.length && target.nodeName == "PARTIAL-PANEL-RESOLVER") { - // Navigated back to lovelace from elsewhere in HA. - buttons = getButtonElements(); - run(); - } else if (target.className == "edit-mode" && addedNodes.length) { - // Entered edit mode. - editMode = true; - if (!disabled) removeStyles(tabContainer, tabs, header); - buttons.options = root.querySelector("paper-menu-button"); - insertEditMenu(tabs); - fireEvent(header, "iron-resize"); - } else if (target.nodeName == "APP-HEADER" && addedNodes.length) { - // Exited edit mode. - for (let node of addedNodes) { - if (node.nodeName == "APP-TOOLBAR") { - editMode = false; - buttons = getButtonElements(); - root.querySelectorAll("[id^='cch']").forEach(style => { - style.remove(); - }); - setTimeout(() => { - run(); - if (!disabled) conditionalStyling(tabs, header); - }, 100); - } - } - } else if ( - // Viewing unused entities - frontendVersion < 20190911 && - addedNodes.length && - !addedNodes[0].nodeName == "HUI-UNUSED-ENTITIES" - ) { - let editor = root - .querySelector("ha-app-layout") - .querySelector("editor"); - if (editor) root.querySelector("ha-app-layout").removeChild(editor); - if (cchConfig.conditional_styles) { - buttons = getButtonElements(tabContainer); - conditionalStyling(tabs, header); - } - } else if (target.id == "view" && addedNodes.length) { - scrollTabIconIntoView(); + observers(tabContainer, tabs, disabled) { + // Watch for changes in Lovelace. + const callback = mutations => { + // Theme changed. + if (mutations[0].target.nodeName == "HTML") { + mutations = [mutations[0]]; + this.styleHeader(tabContainer, tabs); + this.conditionalStyling(tabs); + return; } - }); - }; - let observer = new MutationObserver(callback); - observer.observe(panelResolver, { childList: true }); - observer.observe(document.querySelector("html"), { attributes: true }); - observer.observe(view, { childList: true }); - observer.observe(root.querySelector("app-header"), { childList: true }); - - if (!disabled) { - window.hassConnection.then(({ conn }) => { - conn.socket.onmessage = () => { - if (cchConfig.conditional_styles && !editMode) { - conditionalStyling(tabs, header); + mutations.forEach(({ addedNodes, target }) => { + if (addedNodes.length && target.nodeName == "PARTIAL-PANEL-RESOLVER") { + // Navigated back to lovelace from elsewhere in HA. + this.buttons = this.getButtonElements(); + this.run(); + } else if (target.className == "edit-mode" && addedNodes.length) { + // Entered edit mode. + this.editMode = true; + if (!disabled) this.removeStyles(tabContainer, tabs, this.header); + this.buttons.options = this.root.querySelector("paper-menu-button"); + this.insertEditMenu(tabs); + this.fireEvent(this.header, "iron-resize"); + } else if (target.nodeName == "APP-HEADER" && addedNodes.length) { + // Exited edit mode. + let editor = this.root + .querySelector("ha-app-layout") + .querySelector("editor"); + if (editor) { + this.root.querySelector("ha-app-layout").removeChild(editor); + } + for (let node of addedNodes) { + if (node.nodeName == "APP-TOOLBAR") { + this.editMode = false; + this.buttons = this.getButtonElements(); + this.root.querySelectorAll("[id^='cch']").forEach(style => { + style.remove(); + }); + setTimeout(() => { + this.run(); + if (!disabled) this.conditionalStyling(tabs, this.header); + }, 100); + } + } + } else if ( + // Viewing unused entities + this.frontendVersion < 20190911 && + addedNodes.length && + !addedNodes[0].nodeName == "HUI-UNUSED-ENTITIES" + ) { + let editor = this.root + .querySelector("ha-app-layout") + .querySelector("editor"); + if (editor) { + this.root.querySelector("ha-app-layout").removeChild(editor); + } + if (this.cchConfig.conditional_styles) { + this.buttons = this.getButtonElements(tabContainer); + this.conditionalStyling(tabs, this.header); + } + } else if (target.id == "view" && addedNodes.length) { + // Navigating to new tab/view. + this.run(); + this.scrollTabIconIntoView(); } - }; + }); + }; + let observer = new MutationObserver(callback); + observer.observe(this.panelResolver, { childList: true }); + observer.observe(document.querySelector("html"), { attributes: true }); + observer.observe(this.view, { childList: true }); + observer.observe(this.root.querySelector("app-header"), { + childList: true }); - } -} -function getButtonElements() { - let buttons = {}; - buttons.options = root.querySelector("paper-menu-button"); - if (!editMode) { - buttons.menu = root.querySelector("ha-menu-button"); - buttons.voice = root.querySelector("ha-start-voice-button"); - if (!newSidebar) { - buttons.notifications = root.querySelector("hui-notifications-button"); + if (!disabled) { + // Watch for changes in entities. + window.hassConnection.then(({ conn }) => { + conn.socket.onmessage = () => { + if (this.cchConfig.conditional_styles && !this.editMode) { + conditionalStyling(tabs, this.header); + } + }; + }); } } - if (buttons.menu && buttons.menu.style.visibility == "hidden" && !disabled) { - buttons.menu.style.display = "none"; - } else if (buttons.menu) { - buttons.menu.style.display = ""; - } - return buttons; -} -function tabContainerMargin(tabContainer) { - let marginRight = 0; - let marginLeft = 15; - for (const button in buttons) { - let paperIconButton = - buttons[button].querySelector("paper-icon-button") || - buttons[button].shadowRoot.querySelector("paper-icon-button"); - let visible = paperIconButton - ? buttons[button].style.display !== "none" && - !paperIconButton.hasAttribute("hidden") - : buttons[button].style.display !== "none"; - if (cchConfig[button] == "show" && visible) { - if (button == "menu") marginLeft += 45; - else marginRight += 45; - } else if (cchConfig[button] == "clock" && visible) { - const clockWidth = - (cchConfig.clock_format == 12 && cchConfig.clock_am_pm) || - cchConfig.clock_date - ? 110 - : 80; - if (button == "menu") marginLeft += clockWidth + 15; - else marginRight += clockWidth; - } - } - if (tabContainer) { - tabContainer.style.marginRight = `${marginRight}px`; - tabContainer.style.marginLeft = `${marginLeft}px`; + getButtonElements(disabled) { + let buttons = {}; + buttons.options = this.root.querySelector("paper-menu-button"); + if (!this.editMode) { + buttons.menu = this.root.querySelector("ha-menu-button"); + buttons.voice = this.root.querySelector("ha-start-voice-button"); + if (!this.newSidebar) { + buttons.notifications = this.root.querySelector( + "hui-notifications-button" + ); + } + } + // Remove space taken up by "hidden" menu button anytime we get buttons. + if ( + buttons.menu && + buttons.menu.style.visibility == "hidden" && + !disabled + ) { + buttons.menu.style.display = "none"; + } else if (buttons.menu) { + buttons.menu.style.display = ""; + } + return buttons; + } + + tabContainerMargin(tabContainer) { + let marginRight = 0; + let marginLeft = 15; + for (const button in this.buttons) { + let paperIconButton = + this.buttons[button].querySelector("paper-icon-button") || + this.buttons[button].shadowRoot.querySelector("paper-icon-button"); + let visible = paperIconButton + ? this.buttons[button].style.display !== "none" && + !paperIconButton.hasAttribute("hidden") + : this.buttons[button].style.display !== "none"; + if (this.cchConfig[button] == "show" && visible) { + if (button == "menu") marginLeft += 45; + else marginRight += 45; + } else if (this.cchConfig[button] == "clock" && visible) { + const clockWidth = + (this.cchConfig.clock_format == 12 && this.cchConfig.clock_am_pm) || + this.cchConfig.clock_date + ? 110 + : 80; + if (button == "menu") marginLeft += clockWidth + 15; + else marginRight += clockWidth; + } + } + if (tabContainer) { + tabContainer.style.marginRight = `${marginRight}px`; + tabContainer.style.marginLeft = `${marginLeft}px`; + } } -} -function scrollTabIconIntoView() { - let paperTabs = root.querySelector("paper-tabs"); - let currentTab = paperTabs.querySelector(".iron-selected"); - if (!paperTabs || !currentTab) return; - let tab = currentTab.getBoundingClientRect(); - let container = paperTabs.shadowRoot - .querySelector("#tabsContainer") - .getBoundingClientRect(); - if (container.right < tab.right || container.left > tab.left) { - if ("scrollMarginInline" in document.documentElement.style) { - currentTab.scrollIntoView({ inline: "center" }); - } else if (Element.prototype.scrollIntoViewIfNeeded) { - currentTab.scrollIntoViewIfNeeded(true); - } else { - currentTab.scrollIntoView(); + scrollTabIconIntoView() { + let paperTabs = this.root.querySelector("paper-tabs"); + let currentTab = paperTabs.querySelector(".iron-selected"); + if (!paperTabs || !currentTab) return; + let tab = currentTab.getBoundingClientRect(); + let container = paperTabs.shadowRoot + .querySelector("#tabsContainer") + .getBoundingClientRect(); + // If tab's icon isn't in view scroll it in. + if (container.right < tab.right || container.left > tab.left) { + if ("scrollMarginInline" in document.documentElement.style) { + currentTab.scrollIntoView({ inline: "center" }); + } else if (Element.prototype.scrollIntoViewIfNeeded) { + currentTab.scrollIntoViewIfNeeded(true); + } else { + currentTab.scrollIntoView(); + } } } -} -function hideMenuItems() { - if (cchConfig.hide_help || cchConfig.hide_config || cchConfig.hide_unused) { - const itemCheck = (item, string) => { - let localized = hass.localize(`ui.panel.lovelace.menu.${string}`); - return ( - item.innerHTML.includes(localized) || - item.getAttribute("aria-label") == localized - ); - }; - buttons.options - .querySelector("paper-listbox") - .querySelectorAll("paper-item") - .forEach(item => { - if ( - (cchConfig.hide_help && itemCheck(item, "help")) || - (cchConfig.hide_unused && itemCheck(item, "unused_entities")) || - (cchConfig.hide_config && itemCheck(item, "configure_ui")) - ) { - item.parentNode.removeChild(item); - } - }); + hideMenuItems() { + // Hide items in options menu. + if ( + this.cchConfig.hide_help || + this.cchConfig.hide_config || + this.cchConfig.hide_unused + ) { + const localized = (item, string) => { + let localString = this.hass.localize( + `ui.panel.lovelace.menu.${string}` + ); + return ( + item.innerHTML.includes(localString) || + item.getAttribute("aria-label") == localString + ); + }; + this.buttons.options + .querySelector("paper-listbox") + .querySelectorAll("paper-item") + .forEach(item => { + if ( + (this.cchConfig.hide_help && localized(item, "help")) || + (this.cchConfig.hide_unused && + localized(item, "unused_entities")) || + (this.cchConfig.hide_config && localized(item, "configure_ui")) + ) { + item.parentNode.removeChild(item); + } + }); + } } -} -function insertEditMenu(tabs) { - if (buttons.options && editMode) { - // If any tabs are hidden, add "show all tabs" option. - if (cchConfig.hide_tabs && !cchConfig.edit_mode_show_tabs) { - let show_tabs = document.createElement("paper-item"); - show_tabs.setAttribute("id", "show_tabs"); - show_tabs.addEventListener("click", () => { - for (let i = 0; i < tabs.length; i++) { - tabs[i].style.removeProperty("display"); - } - }); - show_tabs.innerHTML = "Show all tabs"; - insertMenuItem(buttons.options.querySelector("paper-listbox"), show_tabs); - } + insertEditMenu(tabs, disabled) { + if (this.buttons.options && this.editMode) { + // If any tabs are hidden, add "show all tabs" option. + if (this.cchConfig.hide_tabs && !this.cchConfig.edit_mode_show_tabs) { + let show_tabs = document.createElement("paper-item"); + show_tabs.setAttribute("id", "show_tabs"); + show_tabs.addEventListener("click", () => { + for (let i = 0; i < tabs.length; i++) { + tabs[i].style.removeProperty("display"); + } + }); + show_tabs.innerHTML = "Show all tabs"; + this.insertMenuItem( + this.buttons.options.querySelector("paper-listbox"), + show_tabs + ); + } - // Add menu item to open CCH settings. - let cchSettings = document.createElement("paper-item"); - cchSettings.setAttribute("id", "cch_settings"); - cchSettings.addEventListener("click", () => { - showEditor(); - }); - cchSettings.innerHTML = "CCH Settings"; - insertMenuItem(buttons.options.querySelector("paper-listbox"), cchSettings); - if (!disabled) hideMenuItems(); + // Add menu item to open CCH settings. + let cchSettings = document.createElement("paper-item"); + cchSettings.setAttribute("id", "cch_settings"); + cchSettings.addEventListener("click", () => this.showEditor()); + cchSettings.innerHTML = "CCH Settings"; + this.insertMenuItem( + this.buttons.options.querySelector("paper-listbox"), + cchSettings + ); + if (!disabled) this.hideMenuItems(); + } } -} -function removeStyles(tabContainer, tabs, { style }) { - root.querySelector("app-header").style.backgroundColor = "#455a64"; - root.querySelectorAll("[id^='cch']").forEach(style => { - style.remove(); - }); - if (cchConfig.tab_css) { - for (let [key, value] of Object.entries(cchConfig.tab_css)) { - key = getViewIndex(key); - value = value.replace(/: /g, ":").replace(/; /g, ";"); - let css = tabs[key].style.cssText.replace(/: /g, ":").replace(/; /g, ";"); - tabs[key].style.cssText = css.replace(value, ""); - } - } - if (cchConfig.header_css) { - let value = cchConfig.header_css.replace(/: /g, ":").replace(/; /g, ";"); - let css = style.cssText.replace(/: /g, ":").replace(/; /g, ";"); - style.cssText = css.replace(value, ""); - } - if (tabContainer) { - tabContainer.style.marginLeft = ""; - tabContainer.style.marginRight = ""; - } - view.style = ""; - for (let i = 0; i < tabs.length; i++) { - tabs[i].style.color = ""; - } - if (cchConfig.edit_mode_show_tabs) { + removeStyles(tabContainer, tabs, { style }) { + this.root.querySelector("app-header").style.backgroundColor = "#455a64"; + this.root.querySelectorAll("[id^='cch']").forEach(style => { + style.remove(); + }); + if (this.cchConfig.tab_css) { + for (let [key, value] of Object.entries(this.cchConfig.tab_css)) { + key = getViewIndex(key); + value = value.replace(/: /g, ":").replace(/; /g, ";"); + let css = tabs[key].style.cssText + .replace(/: /g, ":") + .replace(/; /g, ";"); + tabs[key].style.cssText = css.replace(value, ""); + } + } + if (this.cchConfig.header_css) { + let value = this.cchConfig.header_css + .replace(/: /g, ":") + .replace(/; /g, ";"); + let css = style.cssText.replace(/: /g, ":").replace(/; /g, ";"); + style.cssText = css.replace(value, ""); + } + if (tabContainer) { + tabContainer.style.marginLeft = ""; + tabContainer.style.marginRight = ""; + } + this.view.style = ""; for (let i = 0; i < tabs.length; i++) { - tabs[i].style.removeProperty("display"); + tabs[i].style.color = ""; } - } - let viewStyle = document.createElement("style"); - viewStyle.setAttribute("id", "cch_view_styling"); - viewStyle.innerHTML = ` + if (this.cchConfig.edit_mode_show_tabs) { + for (let i = 0; i < tabs.length; i++) { + tabs[i].style.removeProperty("display"); + } + } + let viewStyle = document.createElement("style"); + viewStyle.setAttribute("id", "cch_view_styling"); + viewStyle.innerHTML = ` hui-view { min-height: 100vh; } @@ -425,139 +469,133 @@ function removeStyles(tabContainer, tabs, { style }) { min-height: calc(100vh - 52px); } `; - root.appendChild(viewStyle); -} + this.root.appendChild(viewStyle); + } -function styleHeader(tabContainer, tabs, header) { - document.body.style.backgroundColor = getComputedStyle( - document.body - ).getPropertyValue("--background-color"); - prevColor.background = - cchConfig.background || - getComputedStyle(document.body).getPropertyValue("--cch-background") || - getComputedStyle(document.body).getPropertyValue("--primary-color"); - let statusBarColor = cchConfig.statusbar_color || prevColor.background; - // Match mobile status bar color to header color. - let themeColor = document.querySelector('[name="theme-color"]'); - let themeColorApple = - document.querySelector('[name="apple-mobile-web-app-status-bar-style"]') || - document.createElement("meta"); - function colorStatusBar() { - statusBarColor = - cchConfig.statusbar_color || - cchConfig.background || + styleHeader(tabContainer, tabs) { + // Fix for old background config option. + if (typeof this.cchConfig.background == "boolean") { + this.cchConfig.background = ""; + } + this.prevColor.background = + this.cchConfig.background || getComputedStyle(document.body).getPropertyValue("--cch-background") || getComputedStyle(document.body).getPropertyValue("--primary-color"); - themeColor = document.querySelector("meta[name=theme-color]"); - themeColor.setAttribute("content", statusBarColor); - themeColor.setAttribute("default-content", statusBarColor); - if ( - !document.querySelector('[name="apple-mobile-web-app-status-bar-style"]') - ) { - themeColorApple.name = "apple-mobile-web-app-status-bar-style"; - themeColorApple.content = statusBarColor; - document.getElementsByTagName("head")[0].appendChild(themeColorApple); - } else { - themeColorApple.setAttribute("content", statusBarColor); + let statusBarColor = + this.cchConfig.statusbar_color || this.prevColor.background; + // Match mobile status bar color to header color. + let themeColor = document.querySelector('[name="theme-color"]'); + let themeColorApple = + document.querySelector( + '[name="apple-mobile-web-app-status-bar-style"]' + ) || document.createElement("meta"); + colorStatusBar(statusBarColor); + // If browser is idle or in background sometimes theme-color needs reset. + let observeStatus = new MutationObserver(() => { + if (themeColor.content != statusBarColor) colorStatusBar(statusBarColor); + }); + if (this.firstRun) { + observeStatus.observe(themeColor, { + attributes: true, + attributeFilter: ["content"] + }); } - } - colorStatusBar(); - // If app/browser is idle or in background sometimes theme-color needs reset. - let observeStatus = new MutationObserver(() => { - if (themeColor.content != statusBarColor) colorStatusBar(); - }); - if (firstRun) { - observeStatus.observe(themeColor, { attributeFilter: ["content"] }); - } - // Adjust view size & padding for new header size. - if (!cchConfig.header || cchConfig.kiosk_mode) { - header.style.display = "none"; - view.style.minHeight = "100vh"; - if ( - frontendVersion >= 20190911 && - !root.querySelector("#cch_view_styling") - ) { - let viewStyle = document.createElement("style"); - viewStyle.setAttribute("id", "cch_view_styling"); - viewStyle.innerHTML = ` + // Adjust view size & padding for new header size. + if (!this.cchConfig.header || this.cchConfig.kiosk_mode) { + this.header.style.display = "none"; + this.view.style.minHeight = "100vh"; + if ( + this.frontendVersion >= 20190911 && + !this.root.querySelector("#cch_view_styling") + ) { + let viewStyle = document.createElement("style"); + viewStyle.setAttribute("id", "cch_view_styling"); + viewStyle.innerHTML = ` hui-view { - ${cchConfig.view_css ? cchConfig.view_css : ""} + ${this.cchConfig.view_css ? this.cchConfig.view_css : ""} } hui-panel-view { - ${cchConfig.view_css ? cchConfig.view_css : ""} + ${this.cchConfig.view_css ? this.cchConfig.view_css : ""} } `; - root.appendChild(viewStyle); - } - } else { - view.style.minHeight = "100vh"; - view.style.marginTop = "-48.5px"; - view.style.paddingTop = "48.5px"; - view.style.boxSizing = "border-box"; - header.style.background = prevColor.background; - header.querySelector("app-toolbar").style.background = "transparent"; - if ( - frontendVersion >= 20190911 && - !root.querySelector("#cch_view_styling") - ) { - let viewStyle = document.createElement("style"); - viewStyle.setAttribute("id", "cch_view_styling"); - viewStyle.innerHTML = ` + this.root.appendChild(viewStyle); + } + } else { + this.view.style.minHeight = "100vh"; + this.view.style.marginTop = "-48.5px"; + this.view.style.paddingTop = "48.5px"; + this.view.style.boxSizing = "border-box"; + this.header.style.background = this.prevColor.background; + this.header.querySelector("app-toolbar").style.background = "transparent"; + if ( + this.frontendVersion >= 20190911 && + !this.root.querySelector("#cch_view_styling") + ) { + let viewStyle = document.createElement("style"); + viewStyle.setAttribute("id", "cch_view_styling"); + viewStyle.innerHTML = ` hui-view { margin-top: -48.5px; padding-top: 52px; min-height: 100vh; - ${cchConfig.view_css ? cchConfig.view_css : ""} + ${this.cchConfig.view_css ? this.cchConfig.view_css : ""} } hui-panel-view { margin-top: -48.5px; padding-top: 52px; min-height: calc(100vh - 52px); - ${cchConfig.view_css ? cchConfig.view_css : ""} + ${this.cchConfig.view_css ? this.cchConfig.view_css : ""} } `; - root.appendChild(viewStyle); - } - } - - // Match sidebar elements to header's size. - if (newSidebar && cchConfig.compact_header) { - let sidebar = main.shadowRoot.querySelector("ha-sidebar").shadowRoot; - sidebar.querySelector(".menu").style = "height:49px;"; - sidebar.querySelector("paper-listbox").style = "height:calc(100% - 180px);"; - } - - // Current tab icon color. - let conditionalTabs = cchConfig.conditional_styles - ? JSON.stringify(cchConfig.conditional_styles).includes("tab") - : false; - if ( - !root.querySelector("#cch_iron_selected") && - !editMode && - !conditionalTabs && - tabContainer - ) { - let style = document.createElement("style"); - style.setAttribute("id", "cch_iron_selected"); - style.innerHTML = ` + this.root.appendChild(viewStyle); + } + } + + // Match sidebar elements to header's size. + if (this.newSidebar && this.cchConfig.compact_header) { + let sidebar = this.main.shadowRoot.querySelector("ha-sidebar").shadowRoot; + sidebar.querySelector(".menu").style = "height:49px;"; + sidebar.querySelector("paper-listbox").style = + "height:calc(100% - 180px);"; + } + + // Current tab icon color. + let conditionalTabs = this.cchConfig.conditional_styles + ? JSON.stringify(this.cchConfig.conditional_styles).includes("tab") + : false; + if ( + !this.root.querySelector("#cch_iron_selected") && + !this.editMode && + !conditionalTabs && + tabContainer + ) { + let style = document.createElement("style"); + style.setAttribute("id", "cch_iron_selected"); + style.innerHTML = ` .iron-selected { ${ - cchConfig.active_tab_color - ? `color: ${`${cchConfig.active_tab_color} !important`}` + this.cchConfig.active_tab_color + ? `color: ${`${ + this.cchConfig.active_tab_color + } !important`}` : "var(--cch-active-tab-color)" } } `; - tabContainer.appendChild(style); - } + tabContainer.appendChild(style); + } - // Style current tab indicator. - let indicator = cchConfig.tab_indicator_color; - if (indicator && !root.querySelector("#cch_header_colors") && !editMode) { - let style = document.createElement("style"); - style.setAttribute("id", "cch_header_colors"); - style.innerHTML = ` + // Style current tab indicator. + let indicator = this.cchConfig.tab_indicator_color; + if ( + indicator && + !this.root.querySelector("#cch_header_colors") && + !this.editMode + ) { + let style = document.createElement("style"); + style.setAttribute("id", "cch_header_colors"); + style.innerHTML = ` paper-tabs { ${ indicator @@ -566,81 +604,96 @@ function styleHeader(tabContainer, tabs, header) { } } `; - root.appendChild(style); - } - - // Tab's icon color. - let all_tabs_color = cchConfig.all_tabs_color || "var(--cch-all-tabs-color)"; - if ( - (cchConfig.tab_color && Object.keys(cchConfig.tab_color).length) || - all_tabs_color - ) { - for (let i = 0; i < tabs.length; i++) { - tabs[i].style.color = cchConfig.tab_color[i] || all_tabs_color; + this.root.appendChild(style); } - } - // Add header custom css. - if (cchConfig.header_css) header.style.cssText += cchConfig.header_css; - - // Add view custom css. - if (cchConfig.view_css && frontendVersion < 20190911) { - view.style.cssText += cchConfig.view_css; - } + // Tab's icon color. + let all_tabs_color = + this.cchConfig.all_tabs_color || "var(--cch-all-tabs-color)"; + if ( + (this.cchConfig.tab_color && + Object.keys(this.cchConfig.tab_color).length) || + all_tabs_color + ) { + for (let i = 0; i < tabs.length; i++) { + tabs[i].style.color = this.cchConfig.tab_color[i] || all_tabs_color; + } + } - // Add tab custom css. - let tabCss = cchConfig.tab_css; - if (tabCss) { - for (let [key, value] of Object.entries(tabCss)) { - key = getViewIndex(key); - if (tabs[key]) tabs[key].style.cssText += value; + // Add custom css. + if (this.cchConfig.tab_css) { + for (let [key, value] of Object.entries(this.cchConfig.tab_css)) { + key = getViewIndex(key); + if (tabs[key]) tabs[key].style.cssText += value; + } + } + if (this.cchConfig.header_css) + this.header.style.cssText += this.cchConfig.header_css; + if (this.cchConfig.view_css && this.frontendVersion < 20190911) { + this.view.style.cssText += this.cchConfig.view_css; } - } - if (tabContainer) { - // Shift the header up to hide unused portion. - root.querySelector("app-toolbar").style.marginTop = cchConfig.compact_header - ? "-64px" - : ""; + if (tabContainer) { + // Shift the header up to hide unused portion. + this.root.querySelector("app-toolbar").style.marginTop = this.cchConfig + .compact_header + ? "-64px" + : ""; - tabs.forEach(({ style }) => { - style.marginTop = "-1px"; - }); + tabs.forEach(({ style }) => { + style.marginTop = "-1px"; + }); - // Show/hide tab navigation chevrons. - if (!cchConfig.chevrons) { - let chevron = tabContainer.shadowRoot.querySelectorAll( - '[icon^="paper-tabs:chevron"]' - ); - chevron[0].style.display = "none"; - chevron[1].style.display = "none"; - } else { - // Remove space taken up by "not-visible" chevron. - let style = document.createElement("style"); - style.setAttribute("id", "cch_chevron"); - style.innerHTML = ` + // Show/hide tab navigation chevrons. + if (!this.cchConfig.chevrons) { + let chevron = tabContainer.shadowRoot.querySelectorAll( + '[icon^="paper-tabs:chevron"]' + ); + chevron[0].style.display = "none"; + chevron[1].style.display = "none"; + } else { + // Remove space taken up by "not-visible" chevron. + let style = document.createElement("style"); + style.setAttribute("id", "cch_chevron"); + style.innerHTML = ` .not-visible { display:none; } `; - tabContainer.shadowRoot.appendChild(style); + tabContainer.shadowRoot.appendChild(style); + } + } + function colorStatusBar(statusBarColor) { + themeColor = document.querySelector("meta[name=theme-color]"); + themeColor.setAttribute("content", statusBarColor); + themeColor.setAttribute("default-content", statusBarColor); + if ( + !document.querySelector( + '[name="apple-mobile-web-app-status-bar-style"]' + ) + ) { + themeColorApple.name = "apple-mobile-web-app-status-bar-style"; + themeColorApple.content = statusBarColor; + document.getElementsByTagName("head")[0].appendChild(themeColorApple); + } else { + themeColorApple.setAttribute("content", statusBarColor); + } } } -} -function styleButtons({ length }, tabContainer) { - let topMargin = - length > 0 && cchConfig.compact_header ? "margin-top:111px;" : ""; - let topMarginMenu = - length > 0 && cchConfig.compact_header ? "margin-top:115px;" : ""; - // Reverse buttons object so menu is first in overflow menu. - buttons = reverseObject(buttons); - for (const button in buttons) { - if (!buttons[button]) continue; - if (button == "options" && cchConfig[button] == "overflow") { - cchConfig[button] = "show"; - } - let buttonStyle = ` + styleButtons({ length }, tabContainer) { + let topMargin = + length > 0 && this.cchConfig.compact_header ? "margin-top:111px;" : ""; + let topMarginMenu = + length > 0 && this.cchConfig.compact_header ? "margin-top:115px;" : ""; + // Reverse buttons object so "menu" is first in the overflow menu. + this.buttons = this.reverseObject(this.buttons); + for (const button in this.buttons) { + if (!this.buttons[button]) continue; + if (button == "options" && this.cchConfig[button] == "overflow") { + this.cchConfig[button] = "show"; + } + let buttonStyle = ` z-index:1; ${ button == "menu" @@ -650,46 +703,57 @@ function styleButtons({ length }, tabContainer) { ${button == "menu" ? "" : topMargin} ${button == "options" ? "margin-right:-5px;" : ""} `; - if (cchConfig[button] == "show" || cchConfig[button] == "clock") { - if (button == "menu") { - let paperIconButton = buttons[button].querySelector("paper-icon-button") - ? buttons[button].querySelector("paper-icon-button") - : buttons[button].shadowRoot.querySelector("paper-icon-button"); - if (!paperIconButton) continue; - paperIconButton.style.cssText = buttonStyle; - } else { - buttons[button].style.cssText = buttonStyle; - } - } else if (cchConfig[button] == "overflow") { - const menu_items = buttons.options.querySelector("paper-listbox"); - let paperIconButton = buttons[button].querySelector("paper-icon-button") - ? buttons[button].querySelector("paper-icon-button") - : buttons[button].shadowRoot.querySelector("paper-icon-button"); - if (paperIconButton && paperIconButton.hasAttribute("hidden")) { - continue; - } - const id = `menu_item_${button}`; - if (!menu_items.querySelector(`#${id}`)) { - const wrapper = document.createElement("paper-item"); - wrapper.setAttribute("id", id); - wrapper.innerText = getTranslation(button); - wrapper.appendChild(buttons[button]); - wrapper.addEventListener("click", () => { - paperIconButton.click(); - }); - paperIconButton.style.pointerEvents = "none"; - insertMenuItem(menu_items, wrapper); - if (button == "notifications" && !newSidebar) { - let style = document.createElement("style"); - style.innerHTML = ` + if ( + this.cchConfig[button] == "show" || + this.cchConfig[button] == "clock" + ) { + if (button == "menu") { + let paperIconButton = this.buttons[button].querySelector( + "paper-icon-button" + ) + ? this.buttons[button].querySelector("paper-icon-button") + : this.buttons[button].shadowRoot.querySelector( + "paper-icon-button" + ); + if (!paperIconButton) continue; + paperIconButton.style.cssText = buttonStyle; + } else { + this.buttons[button].style.cssText = buttonStyle; + } + } else if (this.cchConfig[button] == "overflow") { + const menu_items = this.buttons.options.querySelector("paper-listbox"); + let paperIconButton = this.buttons[button].querySelector( + "paper-icon-button" + ) + ? this.buttons[button].querySelector("paper-icon-button") + : this.buttons[button].shadowRoot.querySelector("paper-icon-button"); + if (paperIconButton && paperIconButton.hasAttribute("hidden")) { + continue; + } + const id = `menu_item_${button}`; + if (!menu_items.querySelector(`#${id}`)) { + const wrapper = document.createElement("paper-item"); + wrapper.setAttribute("id", id); + wrapper.innerText = getTranslation(button); + wrapper.appendChild(this.buttons[button]); + wrapper.addEventListener("click", () => { + paperIconButton.click(); + }); + paperIconButton.style.pointerEvents = "none"; + insertMenuItem(menu_items, wrapper); + if (button == "notifications" && !this.newSidebar) { + let style = document.createElement("style"); + style.innerHTML = ` .indicator { top: 5px; right: 0px; width: 10px; height: 10px; ${ - cchConfig.notify_indicator_color - ? `background-color:${cchConfig.notify_indicator_color}` + this.cchConfig.notify_indicator_color + ? `background-color:${ + this.cchConfig.notify_indicator_color + }` : "" } } @@ -697,69 +761,78 @@ function styleButtons({ length }, tabContainer) { display:none; } `; - paperIconButton.parentNode.appendChild(style); + paperIconButton.parentNode.appendChild(style); + } } + } else if (this.cchConfig[button] == "hide") { + this.buttons[button].style.display = "none"; + } + // Hide menu button if hiding the sidebar. + if ( + this.newSidebar && + (this.cchConfig.kiosk_mode || this.cchConfig.disable_sidebar) + ) { + this.buttons.menu.style.display = "none"; } - } else if (cchConfig[button] == "hide") { - buttons[button].style.display = "none"; } - // Hide menu button if hiding the sidebar. - if (newSidebar && (cchConfig.kiosk_mode || cchConfig.disable_sidebar)) { - buttons.menu.style.display = "none"; + + // Remove empty space taken up by hidden menu button. + if (this.buttons.menu && this.newSidebar && this.firstRun) { + new MutationObserver(() => { + if (this.buttons.menu.style.visibility == "hidden") { + this.buttons.menu.style.display = "none"; + } else { + this.buttons.menu.style.display = ""; + } + this.tabContainerMargin(tabContainer); + }).observe(this.buttons.menu, { + attributes: true, + attributeFilter: ["style"] + }); } - } - // Remove empty space taken up by hidden menu button. - if (buttons.menu && newSidebar && !disabled) { - new MutationObserver(() => { - if (buttons.menu.style.visibility == "hidden") { - buttons.menu.style.display = "none"; - } else { - buttons.menu.style.display = ""; + // Use color vars set in HA theme. + this.buttons.menu.style.color = "var(--cch-button-color-menu)"; + if (!this.newSidebar) { + this.buttons.notifications.style.color = + "var(--cch-button-color-notifications)"; + } + this.buttons.voice.style.color = "var(--cch-button-color-voice)"; + this.buttons.options.style.color = "var(--cch-button-color-options)"; + if (this.cchConfig.all_buttons_color) { + this.root.querySelector("app-toolbar").style.color = + this.cchConfig.all_buttons_color || "var(--cch-all-buttons-color)"; + } + + // Use colors set in CCH config. + for (const button in this.buttons) { + if (this.cchConfig.button_color[button]) { + this.buttons[button].style.color = this.cchConfig.button_color[button]; } - tabContainerMargin(tabContainer); - }).observe(buttons.menu, { attributeFilter: ["style"] }); - } - - // Use color vars set in HA theme. - buttons.menu.style.color = "var(--cch-button-color-menu)"; - if (!newSidebar) { - buttons.notifications.style.color = "var(--cch-button-color-notifications)"; - } - buttons.voice.style.color = "var(--cch-button-color-voice)"; - buttons.options.style.color = "var(--cch-button-color-options)"; - if (cchConfig.all_buttons_color) { - root.querySelector("app-toolbar").style.color = - cchConfig.all_buttons_color || "var(--cch-all-buttons-color)"; - } - - // Use colors set in CCH config. - for (const button in buttons) { - if (cchConfig.button_color[button]) { - buttons[button].style.color = cchConfig.button_color[button]; - } - } - - // Notification indicator's color for HA 0.96 and above. - if ( - newSidebar && - cchConfig.menu != "hide" && - !buttons.menu.shadowRoot.querySelector("#cch_dot") - ) { - let style = document.createElement("style"); - style.setAttribute("id", "cch_dot"); - let indicator = - cchConfig.notify_indicator_color || - getComputedStyle(header).getPropertyValue("--cch-tab-indicator-color") || - ""; - let border = getComputedStyle(header) - .getPropertyValue("background") - .includes("url") - ? "border-color: transparent !important" - : `border-color: ${getComputedStyle(header).getPropertyValue( - "background-color" - )} !important;`; - style.innerHTML = ` + } + + // Notification indicator's color for HA 0.96 and above. + if ( + this.newSidebar && + this.cchConfig.menu != "hide" && + !this.buttons.menu.shadowRoot.querySelector("#cch_dot") + ) { + let style = document.createElement("style"); + style.setAttribute("id", "cch_dot"); + let indicator = + this.cchConfig.notify_indicator_color || + getComputedStyle(this.header).getPropertyValue( + "--cch-tab-indicator-color" + ) || + ""; + let border = getComputedStyle(this.header) + .getPropertyValue("background") + .includes("url") + ? "border-color: transparent !important" + : `border-color: ${getComputedStyle(this.header).getPropertyValue( + "background-color" + )} !important;`; + style.innerHTML = ` .dot { ${topMargin} z-index: 2; @@ -767,186 +840,194 @@ function styleButtons({ length }, tabContainer) { ${border} } `; - buttons.menu.shadowRoot.appendChild(style); - } else if ( - // Notification indicator's color for HA 0.95 and below. - cchConfig.notify_indicator_color && - cchConfig.notifications == "show" && - !newSidebar - ) { - let style = document.createElement("style"); - style.innerHTML = ` + this.buttons.menu.shadowRoot.appendChild(style); + } else if ( + // Notification indicator's color for HA 0.95 and below. + this.cchConfig.notify_indicator_color && + this.cchConfig.notifications == "show" && + !this.newSidebar + ) { + let style = document.createElement("style"); + style.innerHTML = ` .indicator { - background-color:${cchConfig.notify_indicator_color || + background-color:${this.cchConfig.notify_indicator_color || "var(--cch-notify-indicator-color)"} !important; - color: ${cchConfig.notify_text_color || + color: ${this.cchConfig.notify_text_color || "var(--cch-notify-text-color), var(--primary-text-color)"}; } `; - buttons.notifications.shadowRoot.appendChild(style); - } + this.buttons.notifications.shadowRoot.appendChild(style); + } - // Add buttons's custom css. - let buttonCss = cchConfig.button_css; - if (buttonCss) { - for (const [key, value] of Object.entries(buttonCss)) { - if (!buttons[key]) { - continue; - } else { - buttons[key].style.cssText += value; + // Add buttons's custom css. + let buttonCss = this.cchConfig.button_css; + if (buttonCss) { + for (const [key, value] of Object.entries(buttonCss)) { + if (!this.buttons[key]) { + continue; + } else { + this.buttons[key].style.cssText += value; + } } } } -} -function getTranslation(button) { - switch (button) { - case "notifications": - return hass.localize("ui.notification_drawer.title"); - default: - return button.charAt(0).toUpperCase() + button.slice(1); + getTranslation(button) { + switch (button) { + case "notifications": + return this.hass.localize("ui.notification_drawer.title"); + default: + return button.charAt(0).toUpperCase() + button.slice(1); + } } -} -function defaultTab(tabs, tabContainer) { - let firstTab = tabs.indexOf(tabs.filter(tab => tab.style.display == "")[0]); - let default_tab = cchConfig.default_tab; - if (!default_tab || default_tab == []) return; - let template = cchConfig.default_tab_template; - if ((default_tab || template) && tabContainer) { - if (template) default_tab = templateEval(template, hass.states); - default_tab = getViewIndex(default_tab); - let activeTab = tabs.indexOf(tabContainer.querySelector(".iron-selected")); - if ( - activeTab != default_tab && - activeTab == firstTab && - (!cchConfig.redirect || - (cchConfig.redirect && tabs[default_tab].style.display != "none")) - ) { - tabs[default_tab].click(); + defaultTab(tabs, tabContainer) { + let firstTab = tabs.indexOf(tabs.filter(tab => tab.style.display == "")[0]); + let default_tab = this.cchConfig.default_tab; + if (typeof default_tab == "object" && !default_tab.length) return; + let template = this.cchConfig.default_tab_template; + if ((default_tab || template) && tabContainer) { + if (template) default_tab = this.templateEval(template, this.hass.states); + default_tab = this.getViewIndex(default_tab); + let activeTab = tabs.indexOf( + tabContainer.querySelector(".iron-selected") + ); + if ( + activeTab != default_tab && + activeTab == firstTab && + (!this.cchConfig.redirect || + (this.cchConfig.redirect && + tabs[default_tab].style.display != "none")) + ) { + tabs[default_tab].click(); + } } } -} -function sidebarMod() { - let menu = buttons.menu.querySelector("paper-icon-button"); - let sidebar = main.shadowRoot.querySelector("app-drawer"); - - // HA 0.95 and below - if (!newSidebar) { - if (!cchConfig.sidebar_swipe || cchConfig.kiosk_mode) { - sidebar.removeAttribute("swipe-open"); - } - if ((cchConfig.sidebar_closed || cchConfig.kiosk_mode) && !sidebarClosed) { - if (sidebar.hasAttribute("opened")) menu.click(); - sidebarClosed = true; - } - // HA 0.96 and above - } else if (cchConfig.disable_sidebar || cchConfig.kiosk_mode) { - sidebar.style.display = "none"; - sidebar.addEventListener( - "mouseenter", - event => { - event.stopPropagation(); - }, - true - ); - let style = document.createElement("style"); - style.type = "text/css"; - style.appendChild( - document.createTextNode( - ":host(:not([expanded])) {width: 0px !important;}" - ) - ); - main.shadowRoot.querySelector("ha-sidebar").shadowRoot.appendChild(style); + sidebarMod() { + let menu = this.buttons.menu.querySelector("paper-icon-button"); + let sidebar = this.main.shadowRoot.querySelector("app-drawer"); - style = document.createElement("style"); - style.type = "text/css"; - style.appendChild( - document.createTextNode(":host {--app-drawer-width: 0px !important;}") - ); - main.shadowRoot.appendChild(style); + // HA 0.95 and below + if (!this.newSidebar) { + if (!this.cchConfig.sidebar_swipe || this.cchConfig.kiosk_mode) { + sidebar.removeAttribute("swipe-open"); + } + if ( + (this.cchConfig.sidebar_closed || this.cchConfig.kiosk_mode) && + !this.sidebarClosed + ) { + if (sidebar.hasAttribute("opened")) menu.click(); + this.sidebarClosed = true; + } + // HA 0.96 and above + } else if (this.cchConfig.disable_sidebar || this.cchConfig.kiosk_mode) { + sidebar.style.display = "none"; + sidebar.addEventListener( + "mouseenter", + event => { + event.stopPropagation(); + }, + true + ); + let style = document.createElement("style"); + style.type = "text/css"; + style.appendChild( + document.createTextNode( + ":host(:not([expanded])) {width: 0px !important;}" + ) + ); + this.main.shadowRoot + .querySelector("ha-sidebar") + .shadowRoot.appendChild(style); + + style = document.createElement("style"); + style.type = "text/css"; + style.appendChild( + document.createTextNode(":host {--app-drawer-width: 0px !important;}") + ); + this.main.shadowRoot.appendChild(style); + } } -} -function hideTabs(tabContainer, tabs) { - let hidden_tabs = String(cchConfig.hide_tabs).length - ? String(cchConfig.hide_tabs) - .replace(/\s+/g, "") - .split(",") - : null; - let shown_tabs = String(cchConfig.show_tabs).length - ? String(cchConfig.show_tabs) - .replace(/\s+/g, "") - .split(",") - : null; - - // Set the tab config source. - if (!hidden_tabs && shown_tabs) { - let all_tabs = []; - shown_tabs = buildRanges(shown_tabs); - for (let i = 0; i < tabs.length; i++) all_tabs.push(i); - // Invert shown_tabs to hidden_tabs. - hidden_tabs = all_tabs.filter(el => !shown_tabs.includes(el)); - } else { - hidden_tabs = buildRanges(hidden_tabs); - } - - // Hide tabs. - for (const tab of hidden_tabs) { - if (!tabs[tab]) continue; - tabs[tab].style.display = "none"; - } - - if (cchConfig.redirect && tabContainer) { - const activeTab = tabContainer.querySelector("paper-tab.iron-selected"); - const activeTabIndex = tabs.indexOf(activeTab); - // Is the current tab hidden and is there at least one tab is visible. - if ( - hidden_tabs.includes(activeTabIndex) && - hidden_tabs.length != tabs.length - ) { - let i = 0; - // Find the first visible tab and navigate. - while (hidden_tabs.includes(i)) { - i++; + hideTabs(tabContainer, tabs) { + let hidden_tabs = String(this.cchConfig.hide_tabs).length + ? String(this.cchConfig.hide_tabs) + .replace(/\s+/g, "") + .split(",") + : null; + let shown_tabs = String(this.cchConfig.show_tabs).length + ? String(this.cchConfig.show_tabs) + .replace(/\s+/g, "") + .split(",") + : null; + + // Set the tab config source. + if (!hidden_tabs && shown_tabs) { + let all_tabs = []; + shown_tabs = buildRanges(shown_tabs); + for (let i = 0; i < tabs.length; i++) all_tabs.push(i); + // Invert shown_tabs to hidden_tabs. + hidden_tabs = all_tabs.filter(el => !shown_tabs.includes(el)); + } else { + hidden_tabs = this.buildRanges(hidden_tabs); + } + + // Hide tabs. + for (const tab of hidden_tabs) { + if (!tabs[tab]) continue; + tabs[tab].style.display = "none"; + } + + if (this.cchConfig.redirect && tabContainer) { + const activeTab = tabContainer.querySelector("paper-tab.iron-selected"); + const activeTabIndex = tabs.indexOf(activeTab); + // Is the current tab hidden and is there at least one tab is visible. + if ( + hidden_tabs.includes(activeTabIndex) && + hidden_tabs.length != tabs.length + ) { + let i = 0; + // Find the first visible tab and navigate. + while (hidden_tabs.includes(i)) { + i++; + } + tabs[i].click(); } - tabs[i].click(); } + return hidden_tabs; } - return hidden_tabs; -} -function insertMenuItem(menu_items, element) { - let first_item = menu_items.querySelector("paper-item"); - if (!menu_items.querySelector(`#${element.id}`)) { - first_item.parentNode.insertBefore(element, first_item); + insertMenuItem(menu_items, element) { + let first_item = menu_items.querySelector("paper-item"); + if (!menu_items.querySelector(`#${element.id}`)) { + first_item.parentNode.insertBefore(element, first_item); + } } -} -function insertClock(button) { - const clock_button = buttons[button].querySelector("paper-icon-button") - ? buttons[button] - : buttons[button].shadowRoot; - const clockIcon = clock_button.querySelector("paper-icon-button"); - const clockIronIcon = - clockIcon.querySelector("iron-icon") || - clockIcon.shadowRoot.querySelector("iron-icon"); - const clockWidth = - (cchConfig.clock_format == 12 && cchConfig.clock_am_pm) || - cchConfig.clock_date - ? 105 - : 80; - - if ( - !newSidebar && - cchConfig.notifications == "clock" && - cchConfig.clock_date && - !buttons.notifications.shadowRoot.querySelector("#cch_indicator") - ) { - let style = document.createElement("style"); - style.setAttribute("id", "cch_indicator"); - style.innerHTML = ` + insertClock(button) { + const clock_button = this.buttons[button].querySelector("paper-icon-button") + ? this.buttons[button] + : this.buttons[button].shadowRoot; + const clockIcon = clock_button.querySelector("paper-icon-button"); + const clockIronIcon = + clockIcon.querySelector("iron-icon") || + clockIcon.shadowRoot.querySelector("iron-icon"); + const clockWidth = + (this.cchConfig.clock_format == 12 && this.cchConfig.clock_am_pm) || + this.cchConfig.clock_date + ? 105 + : 80; + + if ( + !this.newSidebar && + this.cchConfig.notifications == "clock" && + this.cchConfig.clock_date && + !this.buttons.notifications.shadowRoot.querySelector("#cch_indicator") + ) { + let style = document.createElement("style"); + style.setAttribute("id", "cch_indicator"); + style.innerHTML = ` .indicator { top: unset; bottom: -3px; @@ -955,8 +1036,8 @@ function insertClock(button) { height: 3px; border-radius: 0; ${ - cchConfig.notify_indicator_color - ? `background-color:${cchConfig.notify_indicator_color}` + this.cchConfig.notify_indicator_color + ? `background-color:${this.cchConfig.notify_indicator_color}` : "" } } @@ -964,708 +1045,801 @@ function insertClock(button) { display:none; } `; - buttons.notifications.shadowRoot.appendChild(style); - } + this.buttons.notifications.shadowRoot.appendChild(style); + } - let clockElement = clockIronIcon.parentNode.getElementById("cch_clock"); - if (cchConfig.menu == "clock") { - buttons.menu.style.marginTop = cchConfig.compact_header ? "111px" : ""; - buttons.menu.style.zIndex = "1"; - } - if (!clockElement) { - clockIcon.style.cssText = ` + let clockElement = clockIronIcon.parentNode.getElementById("cch_clock"); + if (this.cchConfig.menu == "clock") { + this.buttons.menu.style.marginTop = this.cchConfig.compact_header + ? "111px" + : ""; + this.buttons.menu.style.zIndex = "1"; + } + if (!clockElement) { + clockIcon.style.cssText = ` margin-right:-5px; width:${clockWidth}px; text-align: center; `; - clockElement = document.createElement("p"); - clockElement.setAttribute("id", "cch_clock"); - let clockAlign = "center"; - let padding = ""; - let fontSize = ""; - if (cchConfig.clock_date && cchConfig.menu == "clock") { - clockAlign = "left"; - padding = "margin-right:-20px"; - fontSize = "font-size:12pt"; - } else if (cchConfig.clock_date) { - clockAlign = "right"; - padding = "margin-left:-20px"; - fontSize = "font-size:12pt"; - } - clockElement.style.cssText = ` - margin-top: ${cchConfig.clock_date ? "-4px" : "2px"}; + clockElement = document.createElement("p"); + clockElement.setAttribute("id", "cch_clock"); + let clockAlign = "center"; + let padding = ""; + let fontSize = ""; + if (this.cchConfig.clock_date && this.cchConfig.menu == "clock") { + clockAlign = "left"; + padding = "margin-right:-20px"; + fontSize = "font-size:12pt"; + } else if (this.cchConfig.clock_date) { + clockAlign = "right"; + padding = "margin-left:-20px"; + fontSize = "font-size:12pt"; + } + clockElement.style.cssText = ` + margin-top: ${this.cchConfig.clock_date ? "-4px" : "2px"}; text-align: ${clockAlign}; ${padding}; ${fontSize}; `; - clockIronIcon.parentNode.insertBefore(clockElement, clockIronIcon); - clockIronIcon.style.display = "none"; - let style = document.createElement("style"); - style.setAttribute("id", "cch_clock"); - style.innerHTML = ` + clockIronIcon.parentNode.insertBefore(clockElement, clockIronIcon); + clockIronIcon.style.display = "none"; + let style = document.createElement("style"); + style.setAttribute("id", "cch_clock"); + style.innerHTML = ` time { - ${cchConfig.time_css} + ${this.cchConfig.time_css} } date { - ${cchConfig.date_css} + ${this.cchConfig.date_css} } `; - clockIronIcon.parentNode.insertBefore(style, clockIronIcon); - } - - const clockFormat = { - hour12: cchConfig.clock_format != 24, - hour: "2-digit", - minute: "2-digit" - }; - updateClock(clockElement, clockFormat); -} - -function updateClock(clock, clockFormat) { - let date = new Date(); - let locale = cchConfig.date_locale || hass.language; - let time = date.toLocaleTimeString([], clockFormat); - let options = { - weekday: "short", - month: "2-digit", - day: "2-digit" - }; - date = cchConfig.clock_date - ? `
${date.toLocaleDateString(locale, options)}` - : ""; - if (!cchConfig.clock_am_pm && cchConfig.clock_format == 12) { - clock.innerHTML = `${date}`; - } else { - clock.innerHTML = `${date}`; - } - window.setTimeout(() => updateClock(clock, clockFormat), 60000); -} - -// Abandon all hope, ye who enter here. -function conditionalStyling(tabs, header) { - let _hass = document.querySelector("home-assistant").hass; - const conditional_styles = cchConfig.conditional_styles; - let tabContainer = tabs[0] ? tabs[0].parentNode : ""; - let styling = []; + clockIronIcon.parentNode.insertBefore(style, clockIronIcon); + } - if (Array.isArray(conditional_styles)) { - for (let i = 0; i < conditional_styles.length; i++) { - styling.push(Object.assign({}, conditional_styles[i])); + const clockFormat = { + hour12: this.cchConfig.clock_format != 24, + hour: "2-digit", + minute: "2-digit" + }; + this.updateClock(clockElement, clockFormat); + } + + updateClock(clock, clockFormat) { + let date = new Date(); + let seconds = date.getSeconds(); + let locale = this.cchConfig.date_locale || this.hass.language; + let time = date.toLocaleTimeString([], clockFormat); + let options = { + weekday: "short", + month: "2-digit", + day: "2-digit" + }; + date = this.cchConfig.clock_date + ? `
${date.toLocaleDateString(locale, options)}` + : ""; + if (!this.cchConfig.clock_am_pm && this.cchConfig.clock_format == 12) { + clock.innerHTML = `${date}`; + } else { + clock.innerHTML = `${date}`; } - } else { - styling.push(Object.assign({}, conditional_styles)); + window.setTimeout(() => { + this.updateClock(clock, clockFormat); + }, (60 - seconds) * 1000); } - function exists(configItem) { - return configItem !== undefined && configItem !== null; - } + // Abandon all hope, ye who enter here. + conditionalStyling(tabs) { + let _hass = document.querySelector("home-assistant").hass; + const conditional_styles = this.cchConfig.conditional_styles; + let tabContainer = tabs[0] ? tabs[0].parentNode : ""; + let styling = []; - function notificationCount() { - if (newSidebar) { - let badge = main.shadowRoot - .querySelector("ha-sidebar") - .shadowRoot.querySelector("span.notification-badge"); - if (!badge) return 0; - else return parseInt(badge.innerHTML); - } - let i = 0; - let drawer = root - .querySelector("hui-notification-drawer") - .shadowRoot.querySelector(".notifications"); - for (let notification of drawer.querySelectorAll(".notification")) { - if (notification.style.display !== "none") i++; + if (Array.isArray(conditional_styles)) { + for (let i = 0; i < conditional_styles.length; i++) { + styling.push(Object.assign({}, conditional_styles[i])); + } + } else { + styling.push(Object.assign({}, conditional_styles)); } - return i; - } - for (let i = 0; i < styling.length; i++) { - let template = styling[i].template; - let condition = styling[i].condition; + function exists(configItem) { + return configItem !== undefined && configItem !== null; + } - if (template) { - if (!template.length) template = [template]; - template.forEach(template => { - templates(template, tabs, _hass, header); - }); - } else if (condition) { - let entity = styling[i].entity; - if (_hass.states[entity] == undefined && entity !== "notifications") { - console.log(`CCH conditional styling: ${entity} does not exist.`); - continue; + function notificationCount() { + if (this.newSidebar) { + let badge = this.main.shadowRoot + .querySelector("ha-sidebar") + .shadowRoot.querySelector("span.notification-badge"); + if (!badge) return 0; + else return parseInt(badge.innerHTML); } - let entState = - entity == "notifications" - ? notificationCount() - : _hass.states[entity].state; - let condState = condition.state; - let above = condition.above; - let below = condition.below; - - let toStyle = - (exists(condState) && entState == condState) || - (exists(above) && - exists(below) && - entState > above && - entState < below) || - (exists(above) && entState > above) || - (exists(below) && entState < below); - - let tabIndex = styling[i].tab ? Object.keys(styling[i].tab)[0] : null; - let tabCondition = styling[i].tab ? styling[i].tab[tabIndex] : null; - let tabElem = tabs[getViewIndex(tabIndex)]; - let tabkey = `tab_${getViewIndex(tabIndex)}`; - let button = styling[i].button ? Object.keys(styling[i].button)[0] : null; - let background = styling[i].background; - - // Conditionally style tabs. - if (toStyle && exists(tabIndex) && tabElem) { - if (tabCondition.hide) tabElem.style.display = "none"; - if (tabCondition.color) { - if (prevColor[tabkey] == undefined) { - Object.assign(prevColor, { - [tabkey]: window - .getComputedStyle(tabElem, null) - .getPropertyValue("color") - }); - } - tabElem.style.color = tabCondition.color; - } - if (tabCondition.on_icon) { - tabElem - .querySelector("ha-icon") - .setAttribute("icon", tabCondition.on_icon); - } - } else if (!toStyle && exists(tabIndex) && tabElem) { - if (tabCondition.hide) { - tabElem.style.display = ""; - } - if (tabCondition.color && prevColor[tabkey]) { - tabElem.style.color = prevColor[tabkey]; - } - if (tabCondition.off_icon) { - tabElem - .querySelector("ha-icon") - .setAttribute("icon", tabCondition.off_icon); - } + let i = 0; + let drawer = this.root + .querySelector("hui-notification-drawer") + .shadowRoot.querySelector(".notifications"); + for (let notification of drawer.querySelectorAll(".notification")) { + if (notification.style.display !== "none") i++; } + return i; + } - if (toStyle && button) { - if (!buttons[button]) continue; - let buttonCondition = styling[i].button[button]; - let buttonElem = buttons[button].querySelector("paper-icon-button") - ? buttons[button].querySelector("paper-icon-button") - : buttons[button].shadowRoot.querySelector("paper-icon-button"); - if (buttonCondition.hide) { - buttonElem.style.display = "none"; - } - if (buttonCondition.color) { - if (prevColor.button == undefined) prevColor.button = {}; - if (prevColor.button[button] == undefined) { - prevColor.button[button] = window - .getComputedStyle(buttonElem, null) - .getPropertyValue("color"); - } - buttonElem.style.color = buttonCondition.color; - } - if (buttonCondition.on_icon) { - let icon = - buttonElem.querySelector("iron-icon") || - buttonElem.shadowRoot.querySelector("iron-icon"); - icon.setAttribute("icon", buttonCondition.on_icon); - } - } else if (!toStyle && button) { - let buttonCondition = styling[i].button[button]; - let buttonElem = buttons[button].querySelector("paper-icon-button") - ? buttons[button].querySelector("paper-icon-button") - : buttons[button].shadowRoot.querySelector("paper-icon-button"); - if (buttonCondition.hide) { - buttonElem.style.display = ""; - } - if ( - buttonCondition.color && - prevColor.button && - prevColor.button[button] - ) { - buttonElem.style.color = prevColor.button[button]; + for (let i = 0; i < styling.length; i++) { + let template = styling[i].template; + let condition = styling[i].condition; + + if (template) { + if (!template.length) template = [template]; + template.forEach(template => { + templates(template, tabs, _hass, this.header); + }); + } else if (condition) { + let entity = styling[i].entity; + if (_hass.states[entity] == undefined && entity !== "notifications") { + console.log(`CCH conditional styling: ${entity} does not exist.`); + continue; } - if (buttonCondition.off_icon) { - let icon = - buttonElem.querySelector("iron-icon") || - buttonElem.shadowRoot.querySelector("iron-icon"); - icon.setAttribute("icon", buttonCondition.off_icon); + let entState = + entity == "notifications" + ? notificationCount() + : _hass.states[entity].state; + let condState = condition.state; + let above = condition.above; + let below = condition.below; + + let toStyle = + (exists(condState) && entState == condState) || + (exists(above) && + exists(below) && + entState > above && + entState < below) || + (exists(above) && entState > above) || + (exists(below) && entState < below); + + let tabIndex = styling[i].tab ? Object.keys(styling[i].tab)[0] : null; + let tabCondition = styling[i].tab ? styling[i].tab[tabIndex] : null; + let tabElem = tabs[getViewIndex(tabIndex)]; + let tabkey = `tab_${getViewIndex(tabIndex)}`; + let button = styling[i].button + ? Object.keys(styling[i].button)[0] + : null; + let background = styling[i].background; + + // Conditionally style tabs. + if (toStyle && exists(tabIndex) && tabElem) { + if (tabCondition.hide) tabElem.style.display = "none"; + if (tabCondition.color) { + if (this.prevColor[tabkey] == undefined) { + Object.assign(this.prevColor, { + [tabkey]: window + .getComputedStyle(tabElem, null) + .getPropertyValue("color") + }); + } + tabElem.style.color = tabCondition.color; + } + if (tabCondition.on_icon) { + tabElem + .querySelector("ha-icon") + .setAttribute("icon", tabCondition.on_icon); + } + } else if (!toStyle && exists(tabIndex) && tabElem) { + if (tabCondition.hide) { + tabElem.style.display = ""; + } + if (tabCondition.color && this.prevColor[tabkey]) { + tabElem.style.color = this.prevColor[tabkey]; + } + if (tabCondition.off_icon) { + tabElem + .querySelector("ha-icon") + .setAttribute("icon", tabCondition.off_icon); + } } - } - // Conditionally style background. - if (toStyle && background) { - if (prevColor.background == undefined) { - prevColor.background = window - .getComputedStyle(header, null) - .getPropertyValue("background"); + if (toStyle && button) { + if (!this.buttons[button]) continue; + let buttonCondition = styling[i].button[button]; + let buttonElem = this.buttons[button].querySelector( + "paper-icon-button" + ) + ? this.buttons[button].querySelector("paper-icon-button") + : this.buttons[button].shadowRoot.querySelector( + "paper-icon-button" + ); + if (buttonCondition.hide) { + buttonElem.style.display = "none"; + } + if (buttonCondition.color) { + if (this.prevColor.button == undefined) this.prevColor.button = {}; + if (this.prevColor.button[button] == undefined) { + this.prevColor.button[button] = window + .getComputedStyle(buttonElem, null) + .getPropertyValue("color"); + } + buttonElem.style.color = buttonCondition.color; + } + if (buttonCondition.on_icon) { + let icon = + buttonElem.querySelector("iron-icon") || + buttonElem.shadowRoot.querySelector("iron-icon"); + icon.setAttribute("icon", buttonCondition.on_icon); + } + } else if (!toStyle && button) { + let buttonCondition = styling[i].button[button]; + let buttonElem = this.buttons[button].querySelector( + "paper-icon-button" + ) + ? this.buttons[button].querySelector("paper-icon-button") + : this.buttons[button].shadowRoot.querySelector( + "paper-icon-button" + ); + if (buttonCondition.hide) { + buttonElem.style.display = ""; + } + if ( + buttonCondition.color && + this.prevColor.button && + this.prevColor.button[button] + ) { + buttonElem.style.color = this.prevColor.button[button]; + } + if (buttonCondition.off_icon) { + let icon = + buttonElem.querySelector("iron-icon") || + buttonElem.shadowRoot.querySelector("iron-icon"); + icon.setAttribute("icon", buttonCondition.off_icon); + } } - header.style.background = styling[i].background; - } else if (!toStyle && background) { - header.style.background = prevColor.background; - } - } - } - tabContainerMargin(tabContainer); -} -function templates(template, tabs, _hass, { style }) { - let states = _hass.states; - for (const condition in template) { - if (condition == "tab") { - for (const tab in template[condition]) { - let tempCond = template[condition][tab]; - if (!tempCond.length) tempCond = [tempCond]; - tempCond.forEach(templateObj => { - let tabIndex = getViewIndex(Object.keys(template[condition])); - let styleTarget = Object.keys(templateObj); - let tabTemplate = templateObj[styleTarget]; - let tabElement = tabs[tabIndex]; - if (styleTarget == "icon") { - tabElement - .querySelector("ha-icon") - .setAttribute("icon", templateEval(tabTemplate, states)); - } else if (styleTarget == "color") { - tabElement.style.color = templateEval(tabTemplate, states); - } else if (styleTarget == "display") { - templateEval(tabTemplate, states) == "show" - ? (tabElement.style.display = "") - : (tabElement.style.display = "none"); + // Conditionally style background. + if (toStyle && background) { + if (this.prevColor.background == undefined) { + this.prevColor.background = window + .getComputedStyle(this.header, null) + .getPropertyValue("background"); } - }); + this.header.style.background = styling[i].background; + } else if (!toStyle && background) { + this.header.style.background = this.prevColor.background; + } } - } else if (condition == "button") { - for (const button in template[condition]) { - let tempCond = template[condition][button]; - if (!tempCond.length) tempCond = [tempCond]; - tempCond.forEach(templateObj => { - let buttonName = Object.keys(template[condition]); - if (newSidebar && buttonName == "notifications") return; - let styleTarget = Object.keys(templateObj); - let buttonElem = buttons[buttonName]; - let tempCond = templateObj[styleTarget]; - let iconTarget = buttonElem.querySelector("paper-icon-button") - ? buttonElem.querySelector("paper-icon-button") - : buttonElem.shadowRoot.querySelector("paper-icon-button"); - if (styleTarget == "icon") { - iconTarget.setAttribute("icon", templateEval(tempCond, states)); - } else if (styleTarget == "color") { - let tar = - iconTarget.querySelector("iron-icon") || - iconTarget.shadowRoot.querySelector("iron-icon"); - tar.style.color = templateEval(tempCond, states); - } else if (styleTarget == "display") { - templateEval(tempCond, states) == "show" - ? (buttonElem.style.display = "") - : (buttonElem.style.display = "none"); - } - }); + } + this.tabContainerMargin(tabContainer); + } + + templates(template, tabs, _hass, { style }) { + let states = _hass.states; + for (const condition in template) { + if (condition == "tab") { + for (const tab in template[condition]) { + let tempCond = template[condition][tab]; + if (!tempCond.length) tempCond = [tempCond]; + tempCond.forEach(templateObj => { + let tabIndex = getViewIndex(Object.keys(template[condition])); + let styleTarget = Object.keys(templateObj); + let tabTemplate = templateObj[styleTarget]; + let tabElement = tabs[tabIndex]; + if (styleTarget == "icon") { + tabElement + .querySelector("ha-icon") + .setAttribute("icon", templateEval(tabTemplate, states)); + } else if (styleTarget == "color") { + tabElement.style.color = templateEval(tabTemplate, states); + } else if (styleTarget == "display") { + templateEval(tabTemplate, states) == "show" + ? (tabElement.style.display = "") + : (tabElement.style.display = "none"); + } + }); + } + } else if (condition == "button") { + for (const button in template[condition]) { + let tempCond = template[condition][button]; + if (!tempCond.length) tempCond = [tempCond]; + tempCond.forEach(templateObj => { + let buttonName = Object.keys(template[condition]); + if (this.newSidebar && buttonName == "notifications") return; + let styleTarget = Object.keys(templateObj); + let buttonElem = this.buttons[buttonName]; + let tempCond = templateObj[styleTarget]; + let iconTarget = buttonElem.querySelector("paper-icon-button") + ? buttonElem.querySelector("paper-icon-button") + : buttonElem.shadowRoot.querySelector("paper-icon-button"); + if (styleTarget == "icon") { + iconTarget.setAttribute("icon", templateEval(tempCond, states)); + } else if (styleTarget == "color") { + let tar = + iconTarget.querySelector("iron-icon") || + iconTarget.shadowRoot.querySelector("iron-icon"); + tar.style.color = templateEval(tempCond, states); + } else if (styleTarget == "display") { + templateEval(tempCond, states) == "show" + ? (buttonElem.style.display = "") + : (buttonElem.style.display = "none"); + } + }); + } + } else if (condition == "background") { + style.background = templateEval(template[condition], states); } - } else if (condition == "background") { - style.background = templateEval(template[condition], states); } } -} -// Get range (e.g., "5 to 9") and build (5,6,7,8,9). -function buildRanges(array) { - let ranges = []; - if (!array) return []; - const sortNumber = (a, b) => a - b; - const range = (start, end) => - new Array(end - start + 1).fill(undefined).map((_, i) => i + start); - for (let i in array) { - if (typeof array[i] == "string" && array[i].includes("to")) { - let split = array[i].split("to"); - if (parseInt(split[1]) > parseInt(split[0])) { - ranges.push(range(parseInt(split[0]), parseInt(split[1]))); - } else { - ranges.push(range(parseInt(split[1]), parseInt(split[0]))); - } - } else if (isNaN(array[i])) { - let views = lovelace.config.views; - for (let view in views) { - if ( - views[view]["title"] == array[i] || - views[view]["path"] == array[i] - ) { - ranges.push(parseInt(view)); + // Get range (e.g., "5 to 9") and build (5,6,7,8,9). + buildRanges(array) { + let ranges = []; + if (!array) return []; + const sortNumber = (a, b) => a - b; + const range = (start, end) => + new Array(end - start + 1).fill(undefined).map((_, i) => i + start); + for (let i in array) { + if (typeof array[i] == "string" && array[i].includes("to")) { + let split = array[i].split("to"); + if (parseInt(split[1]) > parseInt(split[0])) { + ranges.push(range(parseInt(split[0]), parseInt(split[1]))); + } else { + ranges.push(range(parseInt(split[1]), parseInt(split[0]))); + } + } else if (isNaN(array[i])) { + let views = this.lovelace.config.views; + for (let view in views) { + if ( + views[view]["title"] == array[i] || + views[view]["path"] == array[i] + ) { + ranges.push(parseInt(view)); + } } + } else { + ranges.push(parseInt(array[i])); } - } else { - ranges.push(parseInt(array[i])); } + return ranges.flat().sort(sortNumber); } - return ranges.flat().sort(sortNumber); -} -function showEditor() { - window.scrollTo(0, 0); - if (!root.querySelector("ha-app-layout").querySelector("editor")) { - const container = document.createElement("editor"); - const nest = document.createElement("div"); - nest.style.cssText = ` + showEditor() { + window.scrollTo(0, 0); + if (!this.root.querySelector("ha-app-layout editor")) { + const container = document.createElement("editor"); + const nest = document.createElement("div"); + nest.style.cssText = ` padding: 20px; max-width: 600px; margin: 15px auto; background: var(--paper-card-background-color); border: 6px solid var(--paper-card-background-color); `; - container.style.cssText = ` + container.style.cssText = ` width: 100%; min-height: 100%; box-sizing: border-box; - position: absolute; - background: var(--background-color, grey); - z-index: 2; - padding: 5px; - `; - root.querySelector("ha-app-layout").insertBefore(container, view); - container.appendChild(nest); - nest.appendChild(document.createElement("compact-custom-header-editor")); - } -} - -function getViewIndex(viewString) { - let views = lovelace.config.views; - if (isNaN(viewString)) { - for (let view in views) { - if ( - views[view]["title"] == viewString || - views[view]["path"] == viewString - ) { - return view; - } - } - } else { - return parseInt(viewString); - } -} - -function reverseObject(object) { - let newObject = {}; - let keys = []; - for (let key in object) keys.push(key); - for (let i = keys.length - 1; i >= 0; i--) { - let value = object[keys[i]]; - newObject[keys[i]] = value; - } - return newObject; -} - -function templateEval(template, states) { - let entity = states; - try { - if (template.includes("return")) { - return eval(`(function() {${template}}())`); - } else { - return eval(template); - } - } catch (e) { - console.log( - `%cCCH Template Failed:%c\n${template}\n%c${e}`, - "text-decoration: underline;", - "", - "color: red;" - ); + position: absolute; + background: var(--background-color, grey); + z-index: 2; + padding: 5px; + `; + this.root + .querySelector("ha-app-layout") + .insertBefore(container, this.view); + container.appendChild(nest); + nest.appendChild(document.createElement("compact-custom-header-editor")); + } } -} - -function swipeNavigation(tabs, tabContainer) { - // To make it easier to update lovelace-swipe-navigation - // keep this as close to the standalone lovelace addon as possible. - let swipe_amount = cchConfig.swipe_amount || 15; - let swipe_groups = cchConfig.swipe_groups; - let animate = cchConfig.swipe_animate || "none"; - let skip_tabs = cchConfig.swipe_skip - ? buildRanges(cchConfig.swipe_skip.split(",")) - : []; - let wrap = cchConfig.swipe_wrap != undefined ? cchConfig.swipe_wrap : true; - let prevent_default = - cchConfig.swipe_prevent_default != undefined - ? cchConfig.swipe_prevent_default - : false; - swipe_amount /= 10 ** 2; - const appLayout = root.querySelector("ha-app-layout"); - let inGroup = true; - let xDown; - let yDown; - let xDiff; - let yDiff; - let activeTab; - let firstTab; - let lastTab; - let left; - let fTabs; - - appLayout.addEventListener("touchstart", handleTouchStart, { passive: true }); - appLayout.addEventListener("touchmove", handleTouchMove, { passive: false }); - appLayout.addEventListener("touchend", handleTouchEnd, { passive: true }); - - function handleTouchStart(event) { - filterTabs(); - if (swipe_groups && !inGroup) return; - let ignored = [ - "APP-HEADER", - "HA-SLIDER", - "SWIPE-CARD", - "HUI-MAP-CARD", - "ROUND-SLIDER", - "HUI-THERMOSTAT-CARD" - ]; - let path = (event.composedPath && event.composedPath()) || event.path; - if (path) { - for (let element of path) { - if (element.nodeName == "HUI-VIEW") break; - else if (ignored.includes(element.nodeName)) return; + getViewIndex(viewString) { + let views = this.lovelace.config.views; + if (isNaN(viewString)) { + for (let view in views) { + if ( + views[view]["title"] == viewString || + views[view]["path"] == viewString + ) { + return view; + } } + } else { + return parseInt(viewString); } - xDown = event.touches[0].clientX; - yDown = event.touches[0].clientY; } - function handleTouchMove(event) { - if (xDown && yDown) { - xDiff = xDown - event.touches[0].clientX; - yDiff = yDown - event.touches[0].clientY; - if (Math.abs(xDiff) > Math.abs(yDiff) && prevent_default) { - event.preventDefault(); - } + reverseObject(object) { + let newObject = {}; + let keys = []; + for (let key in object) keys.push(key); + for (let i = keys.length - 1; i >= 0; i--) { + let value = object[keys[i]]; + newObject[keys[i]] = value; } + return newObject; } - function handleTouchEnd() { - if (activeTab < 0 || Math.abs(xDiff) < Math.abs(yDiff)) { - xDown = yDown = xDiff = yDiff = null; - return; - } - if (xDiff > Math.abs(screen.width * swipe_amount)) { - left = false; - if (!wrap && fTabs[activeTab] == lastTab) return; - else if (fTabs[activeTab] == lastTab && wrap) click(firstTab); - else click(fTabs[activeTab + 1]); - } else if (xDiff < -Math.abs(screen.width * swipe_amount)) { - left = true; - if (!wrap && fTabs[activeTab] == firstTab) return; - else if (fTabs[activeTab] == firstTab && wrap) click(lastTab); - else click(fTabs[activeTab - 1]); - } - xDown = yDown = xDiff = yDiff = null; - } - - function filterTabs() { - let currentTab = tabs.indexOf(tabContainer.querySelector(".iron-selected")); - if (swipe_groups) { - let groups = swipe_groups.replace(/, /g, ",").split(","); - for (let group in groups) { - let firstLast = groups[group].replace(/ /g, "").split("to"); - if (wrap && currentTab >= firstLast[0] && currentTab <= firstLast[1]) { - inGroup = true; - firstTab = tabs[parseInt(firstLast[0])]; - lastTab = tabs[parseInt(firstLast[1])]; - fTabs = tabs.filter( - element => - tabs.indexOf(element) >= firstLast[0] && - tabs.indexOf(element) <= firstLast[1] - ); - break; - } else { - inGroup = false; - } + templateEval(template, states) { + let entity = states; + try { + if (template.includes("return")) { + return eval(`(function() {${template}}())`); + } else { + return eval(template); } - } - if (cchConfig.swipe_skip_hidden) { - fTabs = tabs.filter( - element => - !skip_tabs.includes(tabs.indexOf(element)) && - getComputedStyle(element, null).display != "none" - ); - } else { - fTabs = tabs.filter( - element => !skip_tabs.includes(tabs.indexOf(element)) + } catch (e) { + console.log( + `%cCCH Template Failed:%c\n${template}\n%c${e}`, + "text-decoration: underline;", + "", + "color: red;" ); } - if (!swipe_groups) { - firstTab = fTabs[0]; - lastTab = fTabs[fTabs.length - 1]; - } - activeTab = fTabs.indexOf(tabContainer.querySelector(".iron-selected")); } - if (!root.querySelector("#cch_swipe_animation")) { - let swipeAnimations = document.createElement("style"); - swipeAnimations.setAttribute("id", "cch_swipe_animation"); - swipeAnimations.innerHTML = ` + swipeNavigation(tabs, tabContainer) { + // To make it easier to update lovelace-swipe-navigation + // keep this as close to the standalone lovelace addon as possible. + let swipe_amount = this.cchConfig.swipe_amount || 15; + let swipe_groups = this.cchConfig.swipe_groups; + let animate = this.cchConfig.swipe_animate || "none"; + let skip_tabs = this.cchConfig.swipe_skip + ? this.buildRanges(this.cchConfig.swipe_skip.split(",")) + : []; + let wrap = + this.cchConfig.swipe_wrap != undefined ? this.cchConfig.swipe_wrap : true; + let prevent_default = + this.cchConfig.swipe_prevent_default != undefined + ? this.cchConfig.swipe_prevent_default + : false; + + swipe_amount /= 10 ** 2; + const appLayout = this.root.querySelector("ha-app-layout"); + let inGroup = true; + let xDown; + let yDown; + let xDiff; + let yDiff; + let activeTab; + let firstTab; + let lastTab; + let left; + let fTabs; + + appLayout.addEventListener("touchstart", handleTouchStart.bind(this), { + passive: true + }); + appLayout.addEventListener("touchmove", handleTouchMove, { + passive: false + }); + appLayout.addEventListener("touchend", handleTouchEnd, { passive: true }); + + click = click.bind(this); + clearClassNames = clearClassNames.bind(this); + animation = animation.bind(this); + + if (!this.root.querySelector("#cch_swipe_animation")) { + let swipeAnimations = document.createElement("style"); + swipeAnimations.setAttribute("id", "cch_swipe_animation"); + swipeAnimations.innerHTML = ` + @keyframes swipeOutRight, swipeOutLeft { + 0% { transform: translateX(0px); opacity: 1; } + } @keyframes swipeOutRight { - 0% { transform: translateX(0px); } 100% { transform: translateX(${screen.width / 1.5}px); opacity: 0; } } @keyframes swipeOutLeft { - 0% { transform: translateX(0px); } 100% { transform: translateX(-${screen.width / 1.5}px); opacity: 0; } } + @keyframes swipeInRight, swipeInLeft { + 100% { transform: translateX(0px); opacity: 1; } + } @keyframes swipeInRight { 0% { transform: translateX(${screen.width / 1.5}px); opacity: 0; } - 100% { transform: translateX(0px); opacity: 1; } } @keyframes swipeInLeft { 0% { transform: translateX(-${screen.width / 1.5}px); opacity: 0; } - 100% { transform: translateX(0px); opacity: 1; } } - .swipeOutRight, .swipeOutLeft, .swipeInRight, .swipeInLeft { - animation-fill-mode: forwards; + @keyframes fadeOut { + 0% { opacity: 1; } + 100% { opacity: 0; } + } + @keyframes fadeIn { + 0% { opacity: 0; } + 100% { opacity: 1; } + } + @keyframes flipOut { + 0% { transform: rotatey(0deg); opacity: 1; } + 100% { transform: rotatey(90deg); opacity: 0; } + } + @keyframes flipIn{ + 0% { transform: rotatey(90deg); opacity: 0; } + 100% { transform: rotatey(0deg); opacity: 1; } } .swipeOutRight { animation: swipeOutRight .20s 1; } .swipeOutLeft { animation: swipeOutLeft .20s 1; } .swipeInRight { animation: swipeInRight .20s 1; } .swipeInLeft { animation: swipeInLeft .20s 1; } + .fadeIn { animation: fadeIn .20s 1; } + .fadeOut { animation: fadeOut .20s 1; } + .flipIn { animation: flipIn .20s 1; } + .flipOut { animation: flipOut .20s 1; } + .swipeOutRight, + .swipeOutLeft, + .swipeInRight, + .swipeInLeft, + .fadeIn, + .fadeOut, + .flipIn, + .flipOut { + animation-fill-mode: forwards; + } `; - view.parentNode.appendChild(swipeAnimations); - } + this.view.parentNode.appendChild(swipeAnimations); + } - function animation(secs, transform, opacity, timeout) { - setTimeout(() => { - view.style.transition = `transform ${secs}s, opacity ${secs}s`; - view.style.transform = transform ? transform : ""; - view.style.opacity = opacity; - }, timeout); - } + function handleTouchStart(event) { + filterTabs(this.cchConfig); + if (swipe_groups && !inGroup) return; + let ignored = [ + "APP-HEADER", + "HA-SLIDER", + "SWIPE-CARD", + "HUI-MAP-CARD", + "ROUND-SLIDER", + "HUI-THERMOSTAT-CARD" + ]; + let path = (event.composedPath && event.composedPath()) || event.path; + if (path) { + for (let element of path) { + if (element.nodeName == "HUI-VIEW") break; + else if (ignored.includes(element.nodeName)) return; + } + } + xDown = event.touches[0].clientX; + yDown = event.touches[0].clientY; + } - function clear(huiView) { - huiView.className = ""; - huiView.style.overflowX = ""; - view.style.overflowX = ""; - } + function handleTouchMove(event) { + if (xDown && yDown) { + xDiff = xDown - event.touches[0].clientX; + yDiff = yDown - event.touches[0].clientY; + if (Math.abs(xDiff) > Math.abs(yDiff) && prevent_default) { + event.preventDefault(); + } + } + } - function navigate(tab, timeout) { - setTimeout(() => { - tab.dispatchEvent( - new MouseEvent("click", { bubbles: false, cancelable: true }) + function handleTouchEnd() { + if (activeTab < 0 || Math.abs(xDiff) < Math.abs(yDiff)) { + xDown = yDown = xDiff = yDiff = null; + return; + } + if (xDiff > Math.abs(screen.width * swipe_amount)) { + left = false; + if (!wrap && fTabs[activeTab] == lastTab) return; + else if (fTabs[activeTab] == lastTab && wrap) click(firstTab); + else click(fTabs[activeTab + 1]); + } else if (xDiff < -Math.abs(screen.width * swipe_amount)) { + left = true; + if (!wrap && fTabs[activeTab] == firstTab) return; + else if (fTabs[activeTab] == firstTab && wrap) click(lastTab); + else click(fTabs[activeTab - 1]); + } + xDown = yDown = xDiff = yDiff = null; + } + + function filterTabs(config) { + let currentTab = tabs.indexOf( + tabContainer.querySelector(".iron-selected") ); - }, timeout); - } + if (swipe_groups) { + let groups = swipe_groups.replace(/, /g, ",").split(","); + for (let group in groups) { + let firstLast = groups[group].replace(/ /g, "").split("to"); + if ( + wrap && + currentTab >= firstLast[0] && + currentTab <= firstLast[1] + ) { + inGroup = true; + firstTab = tabs[parseInt(firstLast[0])]; + lastTab = tabs[parseInt(firstLast[1])]; + fTabs = tabs.filter( + element => + tabs.indexOf(element) >= firstLast[0] && + tabs.indexOf(element) <= firstLast[1] + ); + break; + } else { + inGroup = false; + } + } + } + if (config.swipe_skip_hidden) { + fTabs = tabs.filter( + element => + !skip_tabs.includes(tabs.indexOf(element)) && + getComputedStyle(element, null).display != "none" + ); + } else { + fTabs = tabs.filter( + element => !skip_tabs.includes(tabs.indexOf(element)) + ); + } + if (!swipe_groups) { + firstTab = fTabs[0]; + lastTab = fTabs[fTabs.length - 1]; + } + activeTab = fTabs.indexOf(tabContainer.querySelector(".iron-selected")); + } - function click(tab) { - if (!tab || (tab.style.display == "none" && cchConfig.swipe_skip_hidden)) { - return; + function animation(secs, transform, opacity, timeout) { + setTimeout(() => { + this.view.style.transition = `transform ${secs}s, opacity ${secs}s`; + this.view.style.transform = transform ? transform : ""; + this.view.style.opacity = opacity; + }, timeout); } - if ( - !wrap && - ((activeTab == firstTab && left) || (activeTab == lastTab && !left)) - ) { - return; - } else if (animate == "swipe") { - const getHuiView = () => { - return ( - view.querySelector("hui-view") || view.querySelector("hui-panel-view") + + function clearClassNames(huiView) { + [ + "swipeOutRight", + "swipeOutLeft", + "swipeInRight", + "swipeInLeft", + "fadeIn", + "fadeOut", + "flipIn", + "flipOut" + ].forEach(name => { + if (huiView.classList.contains(name)) { + huiView.classList.remove(name); + } + if (this.view.classList.contains(name)) { + this.view.classList.remove(name); + } + }); + huiView.style.overflowX = ""; + this.view.style.overflowX = ""; + } + + function navigate(tab, timeout) { + setTimeout(() => { + tab.dispatchEvent( + new MouseEvent("click", { bubbles: false, cancelable: true }) ); - }; - if (window.cch_animation_running) return; - window.cch_animation_running = true; - let huiView = getHuiView(); - huiView.style.overflowX = "hidden"; - view.style.overflowX = "hidden"; - // Swipe view off screen and fade out. - huiView.className = ""; - view.style.transition = `opacity 0.20s`; - view.style.opacity = 1; - huiView.classList.add(left ? "swipeOutRight" : "swipeOutLeft"); - view.style.opacity = 0; - setTimeout(() => clear(huiView), 210); - const observer = new MutationObserver(mutations => { - mutations.forEach(({ addedNodes }) => { - addedNodes.forEach(({ nodeName }) => { - if (nodeName) { - // Swipe view on screen and fade in. - huiView = getHuiView(); - huiView.style.overflowX = "hidden"; - view.style.overflowX = "hidden"; - view.style.opacity = 1; - huiView.classList.add(left ? "swipeInLeft" : "swipeInRight"); - setTimeout(() => { - clear(huiView); - observer.disconnect(); - window.cch_animation_running = false; - }, 210); - return; - } + }, timeout); + } + + function click(tab) { + if ( + !tab || + this.animation_running || + (tab.style.display == "none" && this.cchConfig.swipe_skip_hidden) + ) { + return; + } + if (animate) + if ( + !wrap && + ((activeTab == firstTab && left) || (activeTab == lastTab && !left)) + ) { + return; + } else if (animate == "swipe") { + const getHuiView = () => { + return ( + this.view.querySelector("hui-view") || + this.view.querySelector("hui-panel-view") + ); + }; + this.animation_running = true; + let huiView = getHuiView(); + clearClassNames(huiView); + huiView.style.overflowX = "hidden"; + this.view.style.overflowX = "hidden"; + // Swipe view off screen and fade out. + huiView.classList.add(left ? "swipeOutRight" : "swipeOutLeft"); + this.view.classList.add("fadeOut"); + setTimeout(() => { + this.view.style.opacity = "0"; + clearClassNames(huiView); + }, 210); + // Watch for destination view to load. + const observer = new MutationObserver(mutations => { + mutations.forEach(({ addedNodes }) => { + addedNodes.forEach(({ nodeName }) => { + if (nodeName) { + // Swipe view on screen and fade in. + huiView = getHuiView(); + huiView.style.overflowX = "hidden"; + this.view.style.overflowX = "hidden"; + this.view.classList.add("fadeIn"); + huiView.classList.add(left ? "swipeInLeft" : "swipeInRight"); + setTimeout(() => { + this.view.style.opacity = "1"; + clearClassNames(huiView); + }, 210); + observer.disconnect(); + return; + } + }); + }); }); - }); - }); - observer.observe(view, { childList: true }); - // Navigate to next view and trigger the observer. - navigate(tab, 220); - } else if (animate == "fade") { - animation(0.16, "", 0, 0); - const observer = new MutationObserver(mutations => { - mutations.forEach(({ addedNodes }) => { - addedNodes.forEach(({ nodeName }) => { - if (nodeName == "HUI-VIEW") { - animation(0.16, "", 1, 0); - observer.disconnect(); - } + observer.observe(this.view, { childList: true }); + // Navigate to next view and trigger the observer. + navigate(tab, 220); + } else if (animate == "fade") { + animation(0.16, "", 0, 0); + const observer = new MutationObserver(mutations => { + mutations.forEach(({ addedNodes }) => { + addedNodes.forEach(({ nodeName }) => { + if (nodeName == "HUI-VIEW" || nodeName == "HUI-PANEL-VIEW") { + animation(0.16, "", 1, 0); + observer.disconnect(); + } + }); + }); }); - }); - }); - observer.observe(view, { childList: true }); - navigate(tab, 170); - } else if (animate == "flip") { - animation(0.25, "rotatey(90deg)", 0.25, 0); - const observer = new MutationObserver(mutations => { - mutations.forEach(({ addedNodes }) => { - addedNodes.forEach(({ nodeName }) => { - if (nodeName == "HUI-VIEW") { - animation(0.25, "rotatey(0deg)", 1, 50); - observer.disconnect(); - } + observer.observe(this.view, { childList: true }); + navigate(tab, 170); + } else if (animate == "flip") { + animation(0.25, "rotatey(90deg)", 0.25, 0); + const observer = new MutationObserver(mutations => { + mutations.forEach(({ addedNodes }) => { + addedNodes.forEach(({ nodeName }) => { + if (nodeName == "HUI-VIEW" || nodeName == "HUI-PANEL-VIEW") { + animation(0.25, "rotatey(0deg)", 1, 50); + observer.disconnect(); + } + }); + }); }); - }); - }); - observer.observe(view, { childList: true }); - navigate(tab, 270); - } else { - navigate(tab, 0); + observer.observe(this.view, { childList: true }); + navigate(tab, 270); + } else { + navigate(tab, 0); + } + this.animation_running = false; } } -} -function breakingChangeNotification() { - if ( - lovelace.config.cch == undefined && - JSON.stringify(lovelace.config.views).includes( - "custom:compact-custom-header" - ) - ) { - hass.callService("persistent_notification", "create", { - title: "CCH Breaking Change", - notification_id: "CCH_Breaking_Change", - message: - "Compact-Custom-Header's configuration method has changed. You are " + - "receiving this notification because you have updated CCH, but are " + - "using the old config method. Please, visit the [upgrade guide]" + - "(https://maykar.github.io/compact-custom-header/1_1_0_upgrade/) " + - "for more info." - }); + breakingChangeNotification() { + if ( + this.lovelace.config.cch == undefined && + JSON.stringify(this.lovelace.config.views).includes( + "custom:compact-custom-header" + ) + ) { + this.hass.callService("persistent_notification", "create", { + title: "CCH Breaking Change", + notification_id: "CCH_Breaking_Change", + message: + "Compact-Custom-Header's configuration method has changed. You are " + + "receiving this notification because you have updated CCH, but are " + + "using the old config method. Please, visit the [upgrade guide]" + + "(https://maykar.github.io/compact-custom-header/1_1_0_upgrade/) " + + "for more info." + }); + } } } -// EDITOR ////////////////////////////////////////////////////////////////////// +const cch = new CompactCustomHeader(); +cch.run(); -const buttonOptions = ["show", "hide", "clock", "overflow"]; -const overflowOptions = ["show", "hide", "clock"]; -const swipeAnimation = ["none", "swipe", "fade", "flip"]; -let _lovelace; - -class CompactCustomHeaderEditor extends LitElement { +class CompactCustomHeaderEditor extends cch.LitElement { static get properties() { return { _config: {} }; } firstUpdated() { + this.html = cch.LitElement.prototype.html; + if ( + !customElements.get("paper-toggle-button") && + customElements.get("ha-switch") + ) { + customElements.define( + "paper-toggle-button", + class extends customElements.get("ha-switch") {} + ); + } + let ll = document.querySelector("home-assistant"); ll = ll && ll.shadowRoot; ll = ll && ll.querySelector("home-assistant-main"); @@ -1674,29 +1848,33 @@ class CompactCustomHeaderEditor extends LitElement { ll = (ll && ll.shadowRoot) || ll; ll = ll && ll.querySelector("ha-panel-lovelace"); ll = ll && ll.shadowRoot; - _lovelace = ll && ll.querySelector("hui-root").lovelace; + this._lovelace = ll && ll.querySelector("hui-root").lovelace; - this._config = _lovelace.config.cch ? deepcopy(_lovelace.config.cch) : {}; + this.deepcopy = this.deepcopy.bind(this); + this._config = this._lovelace.config.cch + ? this.deepcopy(this._lovelace.config.cch) + : {}; } render() { - if (!this._config || !_lovelace) return html``; - return html` + if (!this._config || !this._lovelace) return this.html``; + return this.html`
X
${this.renderStyle()}

Exceptions


- ${this._config.exceptions - ? this._config.exceptions.map( - (exception, index) => html` + ${ + this._config.exceptions + ? this._config.exceptions.map( + (exception, index) => this.html` ` - ) - : ""} + ) + : "" + }
- ${this._mwc_button - ? html` + ${ + this._mwc_button + ? this.html` Add Exception ` - : html` + : this.html` Add Exception - `} + ` + }

Current User

-

${hass.user.name}

+

${cch.hass.user.name}

Current User Agent


${navigator.userAgent} @@ -1732,16 +1913,20 @@ class CompactCustomHeaderEditor extends LitElement { margin-bottom:-20px;" class="underline" > - ${!this.exception - ? html` + ${ + !this.exception + ? this.html` ${this._save_button} ` - : ""} - ${!this.exception - ? html` + : "" + } + ${ + !this.exception + ? this.html` ${this._cancel_button} ` - : ""} + : "" + } `; } @@ -1757,11 +1942,15 @@ class CompactCustomHeaderEditor extends LitElement { _save() { for (const key in this._config) { - if (this._config[key] == defaultConfig[key]) delete this._config[key]; + if (this._config[key] == cch.defaultConfig[key]) delete this._config[key]; + // Remove old config option. + if (typeof this._config.background == "boolean") { + this._config.background = ""; + } } - let newConfig = { ..._lovelace.config, ...{ cch: this._config } }; + let newConfig = { ...this._lovelace.config, ...{ cch: this._config } }; try { - _lovelace.saveConfig(newConfig).then(() => { + this._lovelace.saveConfig(newConfig).then(() => { location.reload(true); }); } catch (e) { @@ -1771,10 +1960,10 @@ class CompactCustomHeaderEditor extends LitElement { get _save_button() { return this._mwc_button - ? html` + ? this.html` Save and Reload ` - : html` + : this.html` Save and Reload @@ -1782,10 +1971,10 @@ class CompactCustomHeaderEditor extends LitElement { } get _cancel_button() { return this._mwc_button - ? html` + ? this.html` Cancel ` - : html` + : this.html` Cancel `; } @@ -1800,13 +1989,13 @@ class CompactCustomHeaderEditor extends LitElement { } this._config = { ...this._config, exceptions: newExceptions }; - fireEvent(this, "config-changed", { config: this._config }); + cch.fireEvent(this, "config-changed", { config: this._config }); } _configChanged({ detail }) { if (!this._config) return; this._config = { ...this._config, ...detail.config }; - fireEvent(this, "config-changed", { config: this._config }); + cch.fireEvent(this, "config-changed", { config: this._config }); } _exceptionChanged(ev) { @@ -1816,7 +2005,7 @@ class CompactCustomHeaderEditor extends LitElement { newExceptions[target] = ev.detail.exception; this._config = { ...this._config, exceptions: newExceptions }; - fireEvent(this, "config-changed", { config: this._config }); + cch.fireEvent(this, "config-changed", { config: this._config }); } _exceptionDelete(ev) { @@ -1826,12 +2015,25 @@ class CompactCustomHeaderEditor extends LitElement { newExceptions.splice(target.index, 1); this._config = { ...this._config, exceptions: newExceptions }; - fireEvent(this, "config-changed", { config: this._config }); + cch.fireEvent(this, "config-changed", { config: this._config }); this.requestUpdate(); } + deepcopy(value) { + if (!(!!value && typeof value == "object")) return value; + if (Object.prototype.toString.call(value) == "[object Date]") { + return new Date(value.getTime()); + } + if (Array.isArray(value)) return value.map(this.deepcopy); + const result = {}; + Object.keys(value).forEach(key => { + result[key] = this.deepcopy(value[key]); + }); + return result; + } + renderStyle() { - return html` + return this.html` - ${!this.exception - ? html` + ${ + !this.exception + ? this.html`

- Compact Custom Header  ₁.₄.₁ + Compact Custom Header  ₁.₄.₂

- ${this.getConfig("warning") - ? html` + ${ + this.getConfig("warning") + ? this.html`
Modifying options marked with a @@ -1978,15 +2189,19 @@ class CchConfigEditor extends LitElement {

` - : ""} + : "" + } ` - : ""} + : "" + } ${this.renderStyle()}
Kiosk Mode - ${this.getConfig("warning") - ? html` + ${ + this.getConfig("warning") + ? this.html` ` - : ""} + : "" + } Display Header - ${this.getConfig("warning") - ? html` + ${ + this.getConfig("warning") + ? this.html` ` - : ""} + : "" + }