diff --git a/www/community/compact-custom-header/compact-custom-header.js b/www/community/compact-custom-header/compact-custom-header.js deleted file mode 100644 index 3734973..0000000 --- a/www/community/compact-custom-header/compact-custom-header.js +++ /dev/null @@ -1,3180 +0,0 @@ -console.info( - `%c COMPACT-CUSTOM-HEADER \n%c Version 1.4.9 `, - "color: orange; font-weight: bold; background: black", - "color: white; font-weight: bold; background: dimgray" -); - -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; - }; - - 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, - yaml_editor: 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 - ); - } - - 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.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 { - 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; - } - } - - 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; - } - 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(); - if (tabContainer) 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 - }); - - if (!disabled) { - // Watch for changes in entities. - window.hassConnection.then(({ conn }) => { - conn.socket.onmessage = () => { - if (this.cchConfig.conditional_styles && !this.editMode) { - this.conditionalStyling(tabs, this.header); - } - }; - }); - } - } - - 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") || - this.root.querySelector('[icon="hass:microphone"]'); - 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) { - if (!this.buttons[button]) continue; - 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`; - } - } - - 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(); - } - } - } - - 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); - } - }); - } - } - - insertEditMenu(tabs, disabled) { - if ( - this.buttons.options && - (this.editMode || - (this.lovelace.mode == "yaml" && this.cchConfig.yaml_editor)) - ) { - // 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", () => this.showEditor()); - cchSettings.innerHTML = "CCH Settings"; - this.insertMenuItem( - this.buttons.options.querySelector("paper-listbox"), - cchSettings - ); - if (!disabled) this.hideMenuItems(); - } - } - - 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 = this.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.color = ""; - } - 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; - } - hui-panel-view { - min-height: calc(100vh - 52px); - } - `; - this.root.appendChild(viewStyle); - } - - 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"); - 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"] - }); - } - - // 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 { - ${this.cchConfig.view_css ? this.cchConfig.view_css : ""} - } - hui-panel-view { - ${this.cchConfig.view_css ? this.cchConfig.view_css : ""} - } - `; - 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.conditionalStyling(tabs, this.header); - 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; - ${this.cchConfig.view_css ? this.cchConfig.view_css : ""} - } - hui-panel-view { - margin-top: -52px; - padding-top: 52px; - min-height: calc(100vh - 52px); - ${this.cchConfig.view_css ? this.cchConfig.view_css : ""} - } - `; - 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 { - ${ - this.cchConfig.active_tab_color - ? `color: ${`${ - this.cchConfig.active_tab_color - } !important`}` - : "var(--cch-active-tab-color)" - } - } - `; - tabContainer.appendChild(style); - } - - // 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 - ? `--paper-tabs-selection-bar-color: ${indicator} !important` - : "var(--cch-tab-indicator-color) !important" - } - } - `; - this.root.appendChild(style); - } - - // 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 custom css. - if (this.cchConfig.tab_css) { - for (let [key, value] of Object.entries(this.cchConfig.tab_css)) { - key = this.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. - this.root.querySelector("app-toolbar").style.marginTop = this.cchConfig - .compact_header - ? "-64px" - : ""; - - tabs.forEach(({ style }) => { - style.marginTop = "-1px"; - }); - - // 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); - } - } - 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); - } - } - } - - 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" - ? `padding: 8px 0; margin-bottom:5px; ${topMarginMenu}` - : "padding: 8px;" - } - ${ - button == "voice" && this.cchConfig["voice"] == "clock" - ? "width: 100px; padding:4px;" - : "" - } - ${button == "menu" ? "" : topMargin} - ${button == "options" ? "margin-right:-5px;" : ""} - `; - 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 = this.getTranslation(button); - wrapper.appendChild(this.buttons[button]); - wrapper.addEventListener("click", () => { - paperIconButton.click(); - }); - paperIconButton.style.pointerEvents = "none"; - this.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; - ${ - this.cchConfig.notify_indicator_color - ? `background-color:${ - this.cchConfig.notify_indicator_color - }` - : "" - } - } - .indicator > div{ - display:none; - } - `; - 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"; - } - } - - // 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"] - }); - } - - // 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)"; - } - if (this.buttons.voice) 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]; - } - } - - // 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; - ${indicator ? `background: ${indicator} !important` : ""} - ${border} - } - `; - 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:${this.cchConfig.notify_indicator_color || - "var(--cch-notify-indicator-color)"} !important; - color: ${this.cchConfig.notify_text_color || - "var(--cch-notify-text-color), var(--primary-text-color)"}; - } - `; - this.buttons.notifications.shadowRoot.appendChild(style); - } - - // 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; - } - } - } - } - - getTranslation(button) { - switch (button) { - case "notifications": - return this.hass.localize("ui.notification_drawer.title"); - default: - return button.charAt(0).toUpperCase() + button.slice(1); - } - } - - 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(); - } - } - } - - sidebarMod() { - let menu = this.buttons.menu.querySelector("paper-icon-button"); - let sidebar = this.main.shadowRoot.querySelector("app-drawer"); - - // 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); - } - } - - 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 = this.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(); - } - } - return hidden_tabs; - } - - 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); - } - } - - insertClock(button) { - if (!this.buttons[button]) return; - 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") || this.buttons[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; - right: 0px; - width: 90%; - height: 3px; - border-radius: 0; - ${ - this.cchConfig.notify_indicator_color - ? `background-color:${this.cchConfig.notify_indicator_color}` - : "" - } - } - .indicator > div{ - display:none; - } - `; - this.buttons.notifications.shadowRoot.appendChild(style); - } - - 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 (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 = ` - time { - ${this.cchConfig.time_css} - } - date { - ${this.cchConfig.date_css} - } - `; - clockIronIcon.parentNode.insertBefore(style, clockIronIcon); - } - - 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}`; - } - window.setTimeout(() => { - this.updateClock(clock, clockFormat); - }, (60 - seconds) * 1000); - } - - // 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 = []; - - 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)); - } - - function exists(configItem) { - return configItem !== undefined && configItem !== null; - } - - 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 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; - } - - 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 => { - this.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; - } - 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[this.getViewIndex(tabIndex)]; - let tabkey = `tab_${this.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); - } - } - - 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); - } - } - - // 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; - } - } - } - 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 = this.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", this.templateEval(tabTemplate, states)); - } else if (styleTarget == "color") { - tabElement.style.color = this.templateEval(tabTemplate, states); - } else if (styleTarget == "display") { - this.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", - this.templateEval(tempCond, states) - ); - } else if (styleTarget == "color") { - let tar = - iconTarget.querySelector("iron-icon") || - iconTarget.shadowRoot.querySelector("iron-icon"); - tar.style.color = this.templateEval(tempCond, states); - } else if (styleTarget == "display") { - this.templateEval(tempCond, states) == "show" - ? (buttonElem.style.display = "") - : (buttonElem.style.display = "none"); - } - }); - } - } else if (condition == "background") { - style.background = this.templateEval(template[condition], states); - } - } - } - - // 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])); - } - } - return ranges.flat().sort(sortNumber); - } - - 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 = ` - width: 100%; - min-height: 100%; - box-sizing: border-box; - 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")); - } - } - - 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); - } - } - - 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; - } - - 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;" - ); - } - } - - swipeNavigation(tabs, tabContainer) { - // To make it easier to update lovelace-swipe-navigation - // keep this as close to the standalone lovelace addon as possible. - if (!tabContainer) return; - 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 { - 100% { transform: translateX(${screen.width / 1.5}px); opacity: 0; } - } - @keyframes swipeOutLeft { - 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; } - } - @keyframes swipeInLeft { - 0% { transform: translateX(-${screen.width / 1.5}px); opacity: 0; } - } - @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; - } - `; - this.view.parentNode.appendChild(swipeAnimations); - } - - 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 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 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") - ); - 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 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); - } - - 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 }) - ); - }, 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(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(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(this.view, { childList: true }); - navigate(tab, 270); - } else { - navigate(tab, 0); - } - this.animation_running = false; - } - } - - 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." - }); - } - } -} - -const cch = new CompactCustomHeader(); -cch.run(); - -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"); - ll = ll && ll.shadowRoot; - ll = ll && ll.querySelector("app-drawer-layout partial-panel-resolver"); - ll = (ll && ll.shadowRoot) || ll; - ll = ll && ll.querySelector("ha-panel-lovelace"); - ll = ll && ll.shadowRoot; - this._lovelace = ll && ll.querySelector("hui-root").lovelace; - - this.deepcopy = this.deepcopy.bind(this); - this._config = this._lovelace.config.cch - ? this.deepcopy(this._lovelace.config.cch) - : {}; - } - - render() { - if (!this._config || !this._lovelace) return this.html``; - return this.html` -
- X -
- ${this.renderStyle()} - - -

Exceptions

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

Current User

-

${cch.hass.user.name}

-

Current User Agent

-
- ${navigator.userAgent} -
-

- ${ - !this.exception - ? this.html` - ${this._save_button} - ` - : "" - } - ${ - !this.exception - ? this.html` - ${this._cancel_button} - ` - : "" - } -

- `; - } - - get _mwc_button() { - return customElements.get("mwc-button") ? true : false; - } - - _close() { - let editor = this.parentNode.parentNode.parentNode.querySelector("editor"); - this.parentNode.parentNode.parentNode.removeChild(editor); - } - - _save() { - for (const key in this._config) { - 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 = { ...this._lovelace.config, ...{ cch: this._config } }; - if (cch.lovelace.mode == "storage") { - try { - this._lovelace.saveConfig(newConfig).then(() => { - window.location.href = window.location.href; - }); - } catch (e) { - alert(`Save failed: ${e}`); - } - } else { - window.prompt( - "Copy to clipboard: Ctrl+C, Enter\n" + - "This option is experimental, check the copied config and backup.", - this.obj2yaml({ cch: newConfig.cch }) - ); - } - } - - get _save_button() { - let text = cch.lovelace.mode == "storage" ? "Save and Reload" : "Copy YAML"; - return this._mwc_button - ? this.html` - ${text} - ` - : this.html` - ${text} - `; - } - get _cancel_button() { - return this._mwc_button - ? this.html` - Cancel - ` - : this.html` - Cancel - `; - } - - _addException() { - let newExceptions; - if (this._config.exceptions) { - newExceptions = this._config.exceptions.slice(0); - newExceptions.push({ conditions: {}, config: {} }); - } else { - newExceptions = [{ conditions: {}, config: {} }]; - } - this._config = { ...this._config, exceptions: newExceptions }; - - cch.fireEvent(this, "config-changed", { config: this._config }); - } - - _configChanged({ detail }) { - if (!this._config) return; - this._config = { ...this._config, ...detail.config }; - cch.fireEvent(this, "config-changed", { config: this._config }); - } - - _exceptionChanged(ev) { - if (!this._config) return; - const target = ev.target.index; - const newExceptions = this._config.exceptions.slice(0); - newExceptions[target] = ev.detail.exception; - this._config = { ...this._config, exceptions: newExceptions }; - - cch.fireEvent(this, "config-changed", { config: this._config }); - } - - _exceptionDelete(ev) { - if (!this._config) return; - const target = ev.target; - const newExceptions = this._config.exceptions.slice(0); - newExceptions.splice(target.index, 1); - this._config = { ...this._config, exceptions: newExceptions }; - - 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; - } - - obj2yaml(obj) { - if (typeof obj == "string") obj = JSON.parse(obj); - const ret = []; - convert(obj, ret); - return ret.join("\n"); - function getType(obj) { - if (obj instanceof Array) { - return "array"; - } else if (typeof obj == "string") { - return "string"; - } else if (typeof obj == "boolean") { - return "boolean"; - } else if (typeof obj == "number") { - return "number"; - } else if (typeof obj == "undefined" || obj === null) { - return "null"; - } else { - return "hash"; - } - } - function convert(obj, ret) { - const type = getType(obj); - switch (getType(obj)) { - case "array": - convertArray(obj, ret); - break; - case "hash": - convertHash(obj, ret); - break; - case "string": - convertString(obj, ret); - break; - case "null": - ret.push("null"); - break; - case "number": - ret.push(obj.toString()); - break; - case "boolean": - ret.push(obj ? "true" : "false"); - break; - } - } - function convertArray(obj, ret) { - if (obj.length === 0) ret.push("[]"); - for (let i = 0; i < obj.length; i++) { - const ele = obj[i]; - const recurse = []; - convert(ele, recurse); - for (let j = 0; j < recurse.length; j++) { - ret.push((j == 0 ? "- " : " ") + recurse[j]); - } - } - } - function convertHash(obj, ret) { - for (const k in obj) { - const recurse = []; - if (obj.hasOwnProperty(k)) { - const ele = obj[k]; - convert(ele, recurse); - const type = getType(ele); - if ( - type == "string" || - type == "null" || - type == "number" || - type == "boolean" - ) { - ret.push(`${k}: ${recurse[0]}`); - } else { - ret.push(`${k}: `); - for (let i = 0; i < recurse.length; i++) { - ret.push(` ${recurse[i]}`); - } - } - } - } - } - function convertString(obj, ret) { - if ((obj.includes("'") && obj.includes('"')) || obj.length > 45) { - if (obj.includes(";")) { - obj = obj.includes("; ") ? obj.split("; ") : obj.split(";"); - obj[0] = `>\n ${obj[0]}`; - if (obj[obj.length - 1].trim() == "") obj.pop(); - obj = obj.join(";\n "); - obj = obj.replace(/\n$/, ""); - ret.push(obj); - } else { - ret.push(`>\n ${obj}`); - } - } else if (obj.includes('"')) { - obj = obj.replace(/\n$/, ""); - ret.push(`'${obj}'`); - } else { - obj = obj.replace(/\n$/, ""); - ret.push(`"${obj}"`); - } - } - } - - renderStyle() { - return this.html` - - `; - } -} - -customElements.define( - "compact-custom-header-editor", - CompactCustomHeaderEditor -); - -class CchConfigEditor extends cch.LitElement { - static get properties() { - return { - defaultConfig: {}, - config: {}, - exception: {}, - _closed: {} - }; - } - - constructor() { - super(); - this.buttonOptions = ["show", "hide", "clock", "overflow"]; - this.overflowOptions = ["show", "hide", "clock"]; - this.swipeAnimation = ["none", "swipe", "fade", "flip"]; - } - - get _clock() { - return ( - this.getConfig("menu") == "clock" || - this.getConfig("voice") == "clock" || - this.getConfig("notifications") == "clock" || - this.getConfig("options") == "clock" - ); - } - - getConfig(item) { - return this.config[item] !== undefined - ? this.config[item] - : cch.defaultConfig[item]; - } - - render() { - this.exception = this.exception !== undefined && this.exception !== false; - return this.html` - - - - ${ - !this.exception - ? this.html` -

- Compact Custom Header  ₁.₄.₉ -

-

- - - - Docs    - - - - Github    - - - - Forums -

- ${ - this.getConfig("warning") - ? this.html` -
-
- Modifying options marked with a - or hiding the options button will remove your ability to - edit from the UI. You can disable CCH by adding - "?disable_cch" to the end of your URL to temporarily restore - the default header. -
-
- ` - : "" - } - ` - : "" - } - ${this.renderStyle()} -
- - Disable CCH - - - Compact Header - - - Kiosk Mode - ${ - this.getConfig("warning") - ? this.html` - - ` - : "" - } - - - Display Header - ${ - this.getConfig("warning") - ? this.html` - - ` - : "" - } - - - Display Tab Chevrons - - - Hidden Tab Redirect - - - Hide & Disable Sidebar - - - Close Sidebar - - ${ - !this.exception - ? this.html` - - Display CCH Warnings - - ` - : "" - } - - Swipe Open Sidebar - -
-

Menu Items

-
- - Hide "Configure UI" - ${ - this.getConfig("warning") - ? this.html` - - ` - : "" - } - - - Hide "Help" - - - Hide "Unused Entities" - -
-

Buttons

-
-
- - - - ${this.buttonOptions.map( - option => this.html` - ${option} - ` - )} - - -
-
- - - - ${this.buttonOptions.map( - option => this.html` - ${option} - ` - )} - - -
-
-
-
- - - - ${this.overflowOptions.map( - option => this.html` - ${option} - ` - )} - - -
-
- - - - ${this.buttonOptions.map( - option => this.html` - ${option} - ` - )} - - -
-
- ${ - this._clock - ? this.html` -

Clock Options

-
- - - 12 - 24 - - - - - -
- - AM / PM - - Date -
-
- ` - : "" - } -

Tabs

- - 0 ? "1" : "0"}" - > - Hide Tabs - Show Tabs - - -
-
0 ? "initial" : "none" - }" - > - - -
-
0 ? "none" : "initial" - }" - > - - -
- - -
-

Swipe Navigation

-
- - Swipe Navigation - - ${ - this.config.swipe - ? this.html` - - Wrapping - - - Prevent Default - -
-
-
- - - ${this.swipeAnimation.map( - option => this.html` - ${option} - ` - )} - - -
- - -
- - - - ` - : "" - } - - `; - } - - _toggleCard() { - this._closed = !this._closed; - cch.fireEvent(this, "iron-resize"); - } - - _tabVisibility() { - let show = this.shadowRoot.querySelector("#show"); - let hide = this.shadowRoot.querySelector("#hide"); - if (this.shadowRoot.querySelector("#tabs").value == "Hide Tabs") { - show.style.display = "none"; - hide.style.display = "initial"; - } else { - hide.style.display = "none"; - show.style.display = "initial"; - } - } - - _valueChanged(ev) { - if (!this.config) return; - if (ev.target.configValue) { - if (ev.target.value === "") { - delete this.config[ev.target.configValue]; - } else { - this.config = { - ...this.config, - [ev.target.configValue]: - ev.target.checked !== undefined - ? ev.target.checked - : ev.target.value - }; - } - } - cch.fireEvent(this, "cch-config-changed", { config: this.config }); - } - - renderStyle() { - return this.html` - - `; - } -} - -customElements.define("cch-config-editor", CchConfigEditor); - -class CchExceptionEditor extends cch.LitElement { - static get properties() { - return { config: {}, exception: {}, _closed: {} }; - } - - constructor() { - super(); - this._closed = true; - } - - render() { - if (!this.exception) { - return this.html``; - } - return this.html` - ${this.renderStyle()} - - - - -
-
- ${Object.values(this.exception.conditions) - .join(", ") - .substring(0, 40) || "New Exception"} - - - - -
-

Conditions

- - -

Config

- - -
-
- `; - } - - renderStyle() { - return this.html` - - `; - } - - _toggleCard() { - this._closed = !this._closed; - cch.fireEvent(this, "iron-resize"); - } - - _deleteException() { - cch.fireEvent(this, "cch-exception-delete"); - } - - _conditionsChanged({ detail }) { - if (!this.exception) return; - const newException = { ...this.exception, conditions: detail.conditions }; - cch.fireEvent(this, "cch-exception-changed", { exception: newException }); - } - - _configChanged(ev) { - ev.stopPropagation(); - if (!this.exception) return; - const newException = { ...this.exception, config: ev.detail.config }; - cch.fireEvent(this, "cch-exception-changed", { exception: newException }); - } -} - -customElements.define("cch-exception-editor", CchExceptionEditor); - -class CchConditionsEditor extends cch.LitElement { - static get properties() { - return { conditions: {} }; - } - get _user() { - return this.conditions.user || ""; - } - get _user_agent() { - return this.conditions.user_agent || ""; - } - get _media_query() { - return this.conditions.media_query || ""; - } - get _query_string() { - return this.conditions.query_string || ""; - } - - render() { - if (!this.conditions) return this.html``; - return this.html` - - - - - - - - - `; - } - - _valueChanged(ev) { - if (!this.conditions) return; - const target = ev.target; - if (this[`_${target.configValue}`] === target.value) return; - if (target.configValue) { - if (target.value === "") { - delete this.conditions[target.configValue]; - } else { - this.conditions = { - ...this.conditions, - [target.configValue]: target.value - }; - } - } - cch.fireEvent(this, "cch-conditions-changed", { - conditions: this.conditions - }); - } -} - -customElements.define("cch-conditions-editor", CchConditionsEditor); diff --git a/www/community/compact-custom-header/compact-custom-header.js.gz b/www/community/compact-custom-header/compact-custom-header.js.gz deleted file mode 100644 index 5c8293c..0000000 Binary files a/www/community/compact-custom-header/compact-custom-header.js.gz and /dev/null differ