diff --git a/www/community/compact-custom-header/compact-custom-header.js b/www/community/compact-custom-header/compact-custom-header.js index 892e07c..3734973 100644 --- a/www/community/compact-custom-header/compact-custom-header.js +++ b/www/community/compact-custom-header/compact-custom-header.js @@ -1,998 +1,1477 @@ -const LitElement = Object.getPrototypeOf( - customElements.get("ha-panel-lovelace") +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" ); -const html = LitElement.prototype.html; -const hass = document.querySelector("home-assistant").hass; -const fireEvent = (node, type, detail, options) => { - options = 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; -}; +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; + }; -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: [], - default_tab: [], - 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: {}, - swipe: false, - swipe_amount: "15", - swipe_animate: "none", - swipe_skip: "", - swipe_wrap: true, - swipe_prevent_default: false, - warning: true, - compact_header: true -}; + 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; -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"); -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; + 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"); -const newSidebar = !root.querySelector("hui-notification-drawer"); + this.sidebarClosed = false; + this.firstRun = true; + this.buttons = {}; + this.prevColor = {}; -let notifications = notificationCount(); -const header = root.querySelector("app-header"); -let cchConfig = buildConfig(lovelace.config.cch || {}); -const view = root.querySelector("ha-app-layout").querySelector('[id="view"]'); + 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: {} + }; -let defaultTabRedirect = false; -let sidebarClosed = false; -let editMode = header.className == "edit-mode"; -let firstRun = true; -let condState = []; -let prevColor = {}; -let prevState = []; -let buttons = {}; - -run(); -breakingChangeNotification(); - -function run() { - const disable = cchConfig.disable; - const urlDisable = window.location.href.includes("disable_cch"); - 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; + this.cchConfig = this.buildConfig( + this.lovelace.config.cch || {}, + this.hass.user.name + ); } - if (!disable && !urlDisable) { - insertEditMenu(tabs); - hideMenuItems(); - styleHeader(tabContainer, tabs, header); - styleButtons(tabs); - defaultTab(tabs, tabContainer); - hideTabs(tabContainer, tabs); - for (let button in buttons) { - if (cchConfig[button] == "clock") insertClock(button); + 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 (firstRun) { - sidebarMod(); - conditionalStyling(tabs, header); - } - if (!editMode) tabContainerMargin(tabContainer); - if (cchConfig.swipe) swipeNavigation(tabs, tabContainer); - } - - if (!disable && firstRun) observers(tabContainer, tabs, urlDisable, header); - fireEvent(header, "iron-resize"); - firstRun = false; -} - -function buildConfig(config) { - let exceptionConfig = {}; - let highestMatch = 0; - if (config.exceptions) { - config.exceptions.forEach(exception => { - const matches = countMatches(exception.conditions); - if (matches > highestMatch) { - highestMatch = matches; - exceptionConfig = exception.config; - } - }); - } - - 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(",")) { - let userList = conditions[cond].split(/[ ,]+/); - for (let user in userList) { - if (userVars[cond] == userList[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; - } + 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); } - return count; - } -} - -function observers(tabContainer, tabs, urlDisable, header) { - const callback = function(mutations) { - mutations.forEach(mutation => { - if (mutation.target.className == "edit-mode") { - editMode = true; - if (!cchConfig.disable) removeStyles(tabContainer, tabs, header); - buttons.options = root.querySelector("paper-menu-button"); - insertEditMenu(tabs); - } else if (mutation.target.nodeName == "APP-HEADER") { - for (let node of mutation.addedNodes) { - if (node.nodeName == "APP-TOOLBAR") { - editMode = false; - buttons = getButtonElements(tabContainer); - run(); - return; - } - } - } else if (mutation.addedNodes.length) { - if (mutation.addedNodes[0].nodeName == "HUI-UNUSED-ENTITIES") return; - let editor = root - .querySelector("ha-app-layout") - .querySelector("editor"); - if (editor) root.querySelector("ha-app-layout").removeChild(editor); - if (!editMode && !urlDisable && cchConfig.conditional_styles) { - conditionalStyling(tabs, header); - } - } - }); - }; - new MutationObserver(callback).observe(view, { childList: true }); - new MutationObserver(callback).observe(root.querySelector("app-header"), { - childList: true - }); - - if (!urlDisable) { - window.hassConnection.then(({ conn }) => { - conn.socket.onmessage = () => { - notifications = notificationCount(); - if (cchConfig.conditional_styles && !editMode) { - conditionalStyling(tabs, header); - } - }; - }); - } -} - -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); + if (this.firstRun) { + this.observers(tabContainer, tabs, disabled); + this.breakingChangeNotification(); } + this.firstRun = false; + this.fireEvent(this.header, "iron-resize"); } - 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++; - } - return i; -} -function getButtonElements(tabContainer) { - 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 (buttons.menu && newSidebar) { - new MutationObserver(() => { - if (buttons.menu.style.visibility == "hidden") { - buttons.menu.style.display = "none"; - } else { - buttons.menu.style.display = ""; - } - tabContainerMargin(tabContainer); - }).observe(buttons.menu, { attributeFilter: ["style"] }); - } - return buttons; -} - -function tabContainerMargin(tabContainer) { - let marginRight = 0; - let marginLeft = 15; - for (const button in buttons) { - let visible = 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"; - } -} - -function hideMenuItems() { - if (cchConfig.hide_help || cchConfig.hide_config || cchConfig.hide_unused) { - let menuItems = buttons.options - .querySelector("paper-listbox") - .querySelectorAll("paper-item"); - for (let item of menuItems) { - if ( - (item.innerHTML.includes("Help") || - item.getAttribute("aria-label") == "Help") && - cchConfig.hide_help - ) { - item.parentNode.removeChild(item); - } else if ( - (item.innerHTML.includes("Unused entities") || - item.getAttribute("aria-label") == "Unused entities") && - cchConfig.hide_unused - ) { - item.parentNode.removeChild(item); - } else if ( - (item.innerHTML.includes("Configure UI") || - item.getAttribute("aria-label") == "Configure UI") && - cchConfig.hide_config - ) { - item.parentNode.removeChild(item); - } - } - } -} - -function insertEditMenu(tabs) { - if (buttons.options && editMode) { - if (cchConfig.hide_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"); + 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; } }); - show_tabs.innerHTML = "Show all tabs"; - insertMenuItem(buttons.options.querySelector("paper-listbox"), show_tabs); + } + // 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; } - 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); - } -} + return { ...this.defaultConfig, ...config, ...exceptionConfig }; -function removeStyles(tabContainer, tabs, header) { - let header_colors = root.querySelector('[id="cch_header_colors"]'); - if (tabContainer) { - tabContainer.style.marginLeft = ""; - tabContainer.style.marginRight = ""; - } - header.style.background = null; - view.style.minHeight = ""; - view.style.marginTop = ""; - view.style.paddingTop = ""; - view.style.boxSizing = ""; - if (root.querySelector('[id="cch_iron_selected"]')) { - root.querySelector('[id="cch_iron_selected"]').outerHTML = ""; - } - if (header_colors) header_colors.parentNode.removeChild(header_colors); - for (let i = 0; i < tabs.length; i++) { - tabs[i].style.color = ""; - } -} - -function styleHeader(tabContainer, tabs, header) { - if (!cchConfig.header || cchConfig.kiosk_mode) { - header.style.display = "none"; - view.style.minHeight = "100vh"; - } else { - view.style.minHeight = "100vh"; - view.style.marginTop = "-48.5px"; - view.style.paddingTop = "48.5px"; - view.style.boxSizing = "border-box"; - header.style.background = - cchConfig.background || - getComputedStyle(document.body).getPropertyValue("--cch-background") || - "var(--primary-color)"; - header.querySelector("app-toolbar").style.background = "transparent"; + 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; + } } - 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);"; - } - let indicator = cchConfig.tab_indicator_color; - if ( - indicator && - !root.querySelector('[id="cch_header_colors"]') && - !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" + 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); } } - `; - root.appendChild(style); + } 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); + } + }; + }); + } } - let conditionalTabs = cchConfig.conditional_styles - ? JSON.stringify(cchConfig.conditional_styles).includes("tab") - : false; - if ( - !root.querySelector('[id="cch_iron_selected"]') && - !editMode && - !conditionalTabs && - tabContainer - ) { - let style = document.createElement("style"); - style.setAttribute("id", "cch_iron_selected"); - style.innerHTML = ` - .iron-selected { + 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 { ${ - cchConfig.active_tab_color - ? `color: ${cchConfig.active_tab_color + " !important"}` - : "var(--cch-active-tab-color)" + indicator + ? `--paper-tabs-selection-bar-color: ${indicator} !important` + : "var(--cch-tab-indicator-color) !important" } } `; - tabContainer.appendChild(style); - } - 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); + } + + // 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); + } } } - if (tabContainer) { - // Shift the header up to hide unused portion. - root.querySelector("app-toolbar").style.marginTop = cchConfig.compact_header - ? "-64px" - : ""; + 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"; + } + } - if (!cchConfig.chevrons) { - // Hide 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. + // 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_chevron"); + 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 = ` - .not-visible { + .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; } `; - tabContainer.shadowRoot.appendChild(style); + this.buttons.notifications.shadowRoot.appendChild(style); } - } -} -function styleButtons(tabs) { - let topMargin = - tabs.length > 0 && cchConfig.compact_header ? "margin-top:111px;" : ""; - buttons = reverseObject(buttons); - if ( - newSidebar && - cchConfig.menu != "hide" && - !buttons.menu.shadowRoot.querySelector('[id="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 = ` - .dot { - ${topMargin} - z-index: 2; - ${indicator ? `background: ${indicator} !important` : ""} - ${border} - } - `; - buttons.menu.shadowRoot.appendChild(style); - } - for (const button in buttons) { - if (!buttons[button]) continue; - if (button == "options" && cchConfig[button] == "overflow") { - cchConfig[button] = "show"; + 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 (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 = ` - z-index:1; - ${topMargin} - ${button == "options" ? "margin-right:-5px; padding:0;" : ""} - `; - } else { - buttons[button].style.cssText = ` - z-index:1; - ${topMargin} - ${button == "options" ? "margin-right:-5px; padding:0;" : ""} - `; - } - } 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.hasAttribute("hidden")) { - continue; - } - const id = `menu_item_${button}`; - if (!menu_items.querySelector(`[id="${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 = ` - .indicator { - top: 5px; - right: 0px; - width: 10px; - height: 10px; - ${ - cchConfig.notify_indicator_color - ? `background-color:${cchConfig.notify_indicator_color}` - : "" - } - } - .indicator > div{ - display:none; - } + if (!clockElement) { + clockIcon.style.cssText = ` + margin-right:-5px; + width:${clockWidth}px; + text-align: center; `; - paperIconButton.parentNode.appendChild(style); + 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; } } - } else if (cchConfig[button] == "hide") { - buttons[button].style.display = "none"; } - if (newSidebar && (cchConfig.kiosk_mode || cchConfig.disable_sidebar)) { - buttons.menu.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 = 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); + } } } - // 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]; - } - } - - if (cchConfig.notify_indicator_color && cchConfig.notifications == "show") { - let style = document.createElement("style"); - style.innerHTML = ` - .indicator { - background-color:${cchConfig.notify_indicator_color || - "var(--cch-notify-indicator-color)"} !important; - color: ${cchConfig.notify_text_color || - "var(--cch-notify-text-color), var(--primary-text-color)"}; + // 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)); } - `; - if (!newSidebar) buttons.notifications.shadowRoot.appendChild(style); + } + } else { + ranges.push(parseInt(array[i])); + } + } + return ranges.flat().sort(sortNumber); } -} -function getTranslation(button) { - switch (button) { - case "notifications": - return hass.localize("ui.notification_drawer.title"); - default: - return button.charAt(0).toUpperCase() + button.slice(1); + 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")); + } } -} -function defaultTab(tabs, tabContainer) { - if (cchConfig.default_tab && !defaultTabRedirect && tabContainer) { - let default_tab = cchConfig.default_tab; - let activeTab = tabs.indexOf(tabContainer.querySelector(".iron-selected")); - if (isNaN(default_tab)) { - let views = lovelace.config.views; + getViewIndex(viewString) { + let views = this.lovelace.config.views; + if (isNaN(viewString)) { for (let view in views) { if ( - views[view]["title"] == default_tab || - views[view]["path"] == default_tab + views[view]["title"] == viewString || + views[view]["path"] == viewString ) { - default_tab = view; + return view; } } - } - if ( - activeTab != default_tab && - activeTab == 0 && - !cchConfig.hide_tabs.includes(default_tab) - ) { - tabs[default_tab].click(); - } - defaultTabRedirect = true; - } -} - -function sidebarMod() { - let menu = buttons.menu.querySelector("paper-icon-button"); - let sidebar = main.shadowRoot.querySelector("app-drawer"); - - 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; - } - } else if ( - newSidebar && - (cchConfig.disable_sidebar || cchConfig.kiosk_mode) - ) { - sidebar.style.display = "none"; - sidebar.addEventListener( - "mouseenter", - function(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); - - style = document.createElement("style"); - style.type = "text/css"; - style.appendChild( - document.createTextNode(":host {--app-drawer-width: 0px !important;}") - ); - 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++; - } - tabs[i].click(); + } else { + return parseInt(viewString); } } - return hidden_tabs; -} -function insertMenuItem(menu_items, element) { - let first_item = menu_items.querySelector("paper-item"); - if (!menu_items.querySelector(`[id="${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.shadowRoot.querySelector("iron-icon"); - const clockWidth = - (cchConfig.clock_format == 12 && cchConfig.clock_am_pm) || - cchConfig.clock_date - ? 110 - : 80; - - if ( - !newSidebar && - cchConfig.notifications == "clock" && - cchConfig.clock_date && - !buttons.notifications.shadowRoot.querySelector('[id="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; - ${ - cchConfig.notify_indicator_color - ? `background-color:${cchConfig.notify_indicator_color}` - : "" - } - } - .indicator > div{ - display:none; - } - `; - 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 = ` - 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"; + 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; } - clockElement.style.cssText = ` - margin-top: ${cchConfig.clock_date ? "-4px" : "2px"}; - text-align: ${clockAlign}; - ${padding}; - ${fontSize}; - `; - clockIronIcon.parentNode.insertBefore(clockElement, clockIronIcon); - clockIronIcon.style.display = "none"; + return newObject; } - 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 = time.slice(0, -3) + date; - } else { - clock.innerHTML = time + 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 elem, color, bg, hide, onIcon, offIcon, iconElem; - - const styleElements = () => { - if (bg && elem == "background") header.style.background = bg; - else if (color) elem.style.color = color; - if (onIcon && iconElem) iconElem.setAttribute("icon", onIcon); - if (hide && elem !== "background" && !editMode) { - elem.style.display = "none"; - } - }; - - const getElements = (key, elemArray, i, obj, styling) => { - elem = elemArray[key]; - color = styling[i][obj][key].color; - onIcon = styling[i][obj][key].on_icon; - offIcon = styling[i][obj][key].off_icon; - hide = styling[i][obj][key].hide; - if (!prevColor[key]) { - prevColor[key] = window - .getComputedStyle(elem, null) - .getPropertyValue("color"); - } - }; - - 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)); - } - - for (let i = 0; i < styling.length; i++) { - let template = styling[i].template; - if (template) { - if (!template.length) template = [template]; - template.forEach(template => { - templates(template, tabs, _hass, header); - }); - } else if (conditional_styles) { - let entity = styling[i].entity; - if (_hass.states[entity] == undefined && entity !== "notifications") { - console.log(`CCH conditional styling: ${entity} does not exist.`); - continue; - } - if (entity == "notifications") condState[i] = notifications; - else condState[i] = _hass.states[entity].state; - - if (condState[i] !== prevState[i] || !condState.length) { - prevState[i] = condState[i]; - let above = styling[i].condition.above; - let below = styling[i].condition.below; - - for (const obj in styling[i]) { - let key; - if (styling[i][obj]) { - key = Object.keys(styling[i][obj])[0]; - } - if (obj == "background") { - elem = "background"; - color = styling[i][obj].color; - bg = styling[i][obj]; - iconElem = false; - if (!prevColor[obj]) { - prevColor[obj] = window - .getComputedStyle(header, null) - .getPropertyValue("background"); - } - } else if (obj == "button") { - if (newSidebar && key == "notifications") continue; - getElements(key, buttons, i, obj, styling); - if (key == "menu") { - iconElem = elem - .querySelector("paper-icon-button") - .shadowRoot.querySelector("iron-icon"); - } else { - iconElem = elem.shadowRoot - .querySelector("paper-icon-button") - .shadowRoot.querySelector("iron-icon"); - } - } else if (obj == "tab") { - if (isNaN(key)) { - let views = lovelace.config.views; - for (let view in views) { - if (views[view]["title"] == key || views[view]["path"] == key) { - styling[i][obj][view] = styling[i][obj][key]; - delete styling[i][obj][key]; - key = view; - } - } - } - getElements(key, tabs, i, obj, styling); - iconElem = elem.querySelector("ha-icon"); - } - - if (condState[i] == styling[i].condition.state) { - styleElements(); - } else if ( - above !== undefined && - below !== undefined && - condState[i] > above && - condState[i] < below - ) { - styleElements(); - } else if ( - above !== undefined && - below == undefined && - condState[i] > above - ) { - styleElements(); - } else if ( - above == undefined && - below !== undefined && - condState[i] < below - ) { - styleElements(); - } else { - if (elem !== "background" && hide && elem.style.display == "none") { - elem.style.display = ""; - } - if (bg && elem == "background") { - header.style.background = prevColor[obj]; - } else if ( - obj !== "background" && - obj !== "entity" && - obj !== "condition" - ) { - elem.style.color = prevColor[key]; - } - if (onIcon && offIcon) { - iconElem.setAttribute("icon", offIcon); - } - } - } - } - } - } - tabContainerMargin(tabContainer); -} - -function templates(template, tabs, _hass, header) { - // Variables for templates. - let states = _hass.states; - let entity = states; - - const templateEval = template => { + templateEval(template, states) { + let entity = states; try { if (template.includes("return")) { return eval(`(function() {${template}}())`); @@ -1000,326 +1479,384 @@ function templates(template, tabs, _hass, header) { return eval(template); } } catch (e) { - console.log("CCH styling template failed."); - console.log(e); - } - }; - - 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 = Object.keys(template[condition]); - let views = lovelace.config.views; - if (isNaN(tabIndex)) { - for (let view in views) { - if ( - views[view]["title"] == tabIndex || - views[view]["path"] == tabIndex - ) { - tabIndex = view; - } - } - } else { - tabIndex = parseInt(tabIndex); - } - let styleTarget = Object.keys(templateObj); - let tabTemplate = templateObj[styleTarget]; - let tabElement = tabs[tabIndex]; - if (styleTarget == "icon") { - tabElement - .querySelector("ha-icon") - .setAttribute("icon", templateEval(tabTemplate, entity)); - } else if (styleTarget == "color") { - tabElement.style.color = templateEval(tabTemplate, entity); - } else if (styleTarget == "display") { - templateEval(tabTemplate, entity) == "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 (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, entity)); - } else if (styleTarget == "color") { - iconTarget.shadowRoot.querySelector( - "iron-icon" - ).style.color = templateEval(tempCond, entity); - } else if (styleTarget == "display") { - templateEval(tempCond, entity) == "show" - ? (buttonElem.style.display = "") - : (buttonElem.style.display = "none"); - } - }); - } - } else if (condition == "background") { - header.style.background = templateEval(template[condition], entity); + console.log( + `%cCCH Template Failed:%c\n${template}\n%c${e}`, + "text-decoration: underline;", + "", + "color: red;" + ); } } -} -// 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)); + 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; } } - } else { - ranges.push(parseInt(array[i])); + xDown = event.touches[0].clientX; + yDown = event.touches[0].clientY; } - } - 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 = ` - 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; - `; - root.querySelector("ha-app-layout").insertBefore(container, view); - container.appendChild(nest); - nest.appendChild(document.createElement("compact-custom-header-editor")); - } -} - -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 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 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 /= Math.pow(10, 2); - const appLayout = root.querySelector("ha-app-layout"); - let xDown, yDown, xDiff, yDiff, activeTab, firstTab, lastTab, left; - - appLayout.addEventListener("touchstart", handleTouchStart, { passive: true }); - appLayout.addEventListener("touchmove", handleTouchMove, { passive: false }); - appLayout.addEventListener("touchend", handleTouchEnd, { passive: true }); - - function handleTouchStart(event) { - let ignored = ["APP-HEADER", "HA-SLIDER", "SWIPE-CARD", "HUI-MAP-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.indexOf(element.nodeName) > -1) return; + 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(); + } } } - xDown = event.touches[0].clientX; - yDown = event.touches[0].clientY; - if (!lastTab) filterTabs(); - activeTab = tabs.indexOf(tabContainer.querySelector(".iron-selected")); - } - 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]); } - } - } - - 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; - activeTab == tabs.length - 1 ? click(firstTab) : click(activeTab + 1); - } else if (xDiff < -Math.abs(screen.width * swipe_amount)) { - left = true; - activeTab == 0 ? click(lastTab) : click(activeTab - 1); - } - xDown = yDown = xDiff = yDiff = null; - } - function filterTabs() { - tabs = tabs.filter(element => { - return ( - !skip_tabs.includes(tabs.indexOf(element)) && - getComputedStyle(element, null).display != "none" + function filterTabs(config) { + let currentTab = tabs.indexOf( + tabContainer.querySelector(".iron-selected") ); - }); - firstTab = wrap ? 0 : null; - lastTab = wrap ? tabs.length - 1 : null; + 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; + } } - function click(index) { + breakingChangeNotification() { if ( - (activeTab == 0 && !wrap && left) || - (activeTab == tabs.length - 1 && !wrap && !left) + this.lovelace.config.cch == undefined && + JSON.stringify(this.lovelace.config.views).includes( + "custom:compact-custom-header" + ) ) { - return; - } - if (animate == "swipe") { - let _in = left ? `${screen.width / 1.5}px` : `-${screen.width / 1.5}px`; - let _out = left ? `-${screen.width / 1.5}px` : `${screen.width / 1.5}px`; - view.style.transitionDuration = "200ms"; - view.style.opacity = 0; - view.style.transform = `translateX(${_in})`; - view.style.transition = "transform 0.20s, opacity 0.20s"; - setTimeout(function() { - tabs[index].dispatchEvent( - new MouseEvent("click", { bubbles: false, cancelable: true }) - ); - view.style.transitionDuration = "0ms"; - view.style.transform = `translateX(${_out})`; - view.style.transition = "transform 0s"; - }, 210); - setTimeout(function() { - view.style.transitionDuration = "200ms"; - view.style.opacity = 1; - view.style.transform = `translateX(0px)`; - view.style.transition = "transform 0.20s, opacity 0.20s"; - }, 215); - } else if (animate == "fade") { - view.style.transitionDuration = "200ms"; - view.style.transition = "opacity 0.20s"; - view.style.opacity = 0; - setTimeout(function() { - tabs[index].dispatchEvent( - new MouseEvent("click", { bubbles: false, cancelable: true }) - ); - view.style.transitionDuration = "0ms"; - view.style.opacity = 0; - view.style.transition = "opacity 0s"; - }, 210); - setTimeout(function() { - view.style.transitionDuration = "200ms"; - view.style.transition = "opacity 0.20s"; - view.style.opacity = 1; - }, 250); - } else if (animate == "flip") { - view.style.transitionDuration = "200ms"; - view.style.transform = "rotatey(90deg)"; - view.style.transition = "transform 0.20s, opacity 0.20s"; - view.style.opacity = 0.25; - setTimeout(function() { - tabs[index].dispatchEvent( - new MouseEvent("click", { bubbles: false, cancelable: true }) - ); - }, 210); - setTimeout(function() { - view.style.transitionDuration = "200ms"; - view.style.transform = "rotatey(0deg)"; - view.style.transition = "transform 0.20s, opacity 0.20s"; - view.style.opacity = 1; - }, 250); - } else { - tabs[index].dispatchEvent( - new MouseEvent("click", { bubbles: false, cancelable: true }) - ); + 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." + }); } } } -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." - }); - } -} +const cch = new CompactCustomHeader(); +cch.run(); -// EDITOR ////////////////////////////////////////////////////////////////// - -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: {} - }; + 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"); @@ -1328,29 +1865,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) => { - return 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}

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

`; } @@ -1410,41 +1958,49 @@ class CompactCustomHeaderEditor extends LitElement { } _save() { - for (var key in this._config) { - if (this._config[key] == defaultConfig[key]) { - delete this._config[key]; + 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 = { - ..._lovelace.config, - ...{ cch: this._config } - }; - try { - _lovelace.saveConfig(newConfig).then(() => { - location.reload(true); - }); - } catch (e) { - alert("Save failed: " + e); + 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 - ? html` - Save and Reload + ? this.html` + ${text} ` - : html` + : this.html` Save and Reload${text} `; } get _cancel_button() { return this._mwc_button - ? html` + ? this.html` Cancel ` - : html` + : this.html` Cancel `; } @@ -1453,78 +2009,156 @@ class CompactCustomHeaderEditor extends LitElement { let newExceptions; if (this._config.exceptions) { newExceptions = this._config.exceptions.slice(0); - newExceptions.push({ - conditions: {}, - config: {} - }); + newExceptions.push({ conditions: {}, config: {} }); } else { - newExceptions = [ - { - conditions: {}, - config: {} - } - ]; + newExceptions = [{ conditions: {}, config: {} }]; } - this._config = { - ...this._config, - exceptions: newExceptions - }; + this._config = { ...this._config, exceptions: newExceptions }; - fireEvent(this, "config-changed", { - config: this._config - }); + cch.fireEvent(this, "config-changed", { config: this._config }); } - _configChanged(ev) { - if (!this._config) { - return; - } - this._config = { - ...this._config, - ...ev.detail.config - }; - 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; - } + 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 - }; + this._config = { ...this._config, exceptions: newExceptions }; - fireEvent(this, "config-changed", { - config: this._config - }); + cch.fireEvent(this, "config-changed", { config: this._config }); } _exceptionDelete(ev) { - if (!this._config) { - return; - } + 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 - }; + 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; + } + + 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 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 @@ -1671,15 +2314,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` ` - : ""} + : "" + }
`; } _toggleCard() { this._closed = !this._closed; - fireEvent(this, "iron-resize"); + cch.fireEvent(this, "iron-resize"); } _tabVisibility() { - let show = this.shadowRoot.querySelector('[id="show"]'); - let hide = this.shadowRoot.querySelector('[id="hide"]'); - if (this.shadowRoot.querySelector('[id="tabs"]').value == "Hide Tabs") { + 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 { @@ -2186,31 +2886,25 @@ class CchConfigEditor extends LitElement { } _valueChanged(ev) { - if (!this.config) { - return; - } - const target = ev.target; - if (this[`_${target.configValue}`] === target.value) { - return; - } - if (target.configValue) { - if (target.value === "") { - delete this.config[target.configValue]; + if (!this.config) return; + if (ev.target.configValue) { + if (ev.target.value === "") { + delete this.config[ev.target.configValue]; } else { this.config = { ...this.config, - [target.configValue]: - target.checked !== undefined ? target.checked : target.value + [ev.target.configValue]: + ev.target.checked !== undefined + ? ev.target.checked + : ev.target.value }; } } - fireEvent(this, "cch-config-changed", { - config: this.config - }); + cch.fireEvent(this, "cch-config-changed", { config: this.config }); } renderStyle() { - return html` + return this.html` - - -
- ${title} -
-
- `; - popup.appendChild(message); - this.moreInfo(Object.keys(this.hass.states)[0]); - let moreInfo = document.querySelector("home-assistant")._moreInfoEl; - moreInfo._page = "none"; - moreInfo.shadowRoot.appendChild(popup); - moreInfo.large = large; - document.querySelector("home-assistant").provideHass(message); - - setTimeout(() => { - let interval = setInterval(() => { - if (moreInfo.getAttribute('aria-hidden')) { - popup.parentNode.removeChild(popup); - clearInterval(interval); - } - }, 100) - }, 1000); - return moreInfo; - } - static closePopUp() { - let moreInfo = document.querySelector("home-assistant")._moreInfoEl; - if (moreInfo) moreInfo.close() - } - - static logger(message, script=null) { - if(!('debug' in this.args(script))) return; - - if(typeof message !== "string") - message = JSON.stringify(message); - console.log(`%cDEBUG:%c ${message}`, - "color: blue; font-weight: bold", ""); - } - -}); - -// Global definition of cardTools -var cardTools = customElements.get('card-tools'); - -console.info(`%cCARD-TOOLS IS INSTALLED -%cDeviceID: ${customElements.get('card-tools').deviceID}`, -"color: green; font-weight: bold", -""); +!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=3)}([function(e,t,r){"use strict";function n(){return document.querySelector("home-assistant").hass}function o(e){return document.querySelector("home-assistant").provideHass(e)}function s(){var e=document.querySelector("home-assistant");if(e=(e=(e=(e=(e=(e=(e=(e=e&&e.shadowRoot)&&e.querySelector("home-assistant-main"))&&e.shadowRoot)&&e.querySelector("app-drawer-layout partial-panel-resolver"))&&e.shadowRoot||e)&&e.querySelector("ha-panel-lovelace"))&&e.shadowRoot)&&e.querySelector("hui-root")){var t=e.lovelace;return t.current_view=e.___curView,t}return null}function a(){var e=document.querySelector("home-assistant");return e=(e=(e=(e=(e=(e=(e=(e=(e=(e=(e=e&&e.shadowRoot)&&e.querySelector("home-assistant-main"))&&e.shadowRoot)&&e.querySelector("app-drawer-layout partial-panel-resolver"))&&e.shadowRoot||e)&&e.querySelector("ha-panel-lovelace"))&&e.shadowRoot)&&e.querySelector("hui-root"))&&e.shadowRoot)&&e.querySelector("ha-app-layout #view"))&&e.firstElementChild}r.d(t,"a",(function(){return n})),r.d(t,"d",(function(){return o})),r.d(t,"b",(function(){return s})),r.d(t,"c",(function(){return a}))},function(e,t,r){"use strict";r.d(t,"a",(function(){return n}));let n=function(){if(window.fully&&"function"==typeof fully.getDeviceId)return fully.getDeviceId();if(!localStorage["lovelace-player-device-id"]){const e=()=>Math.floor(1e5*(1+Math.random())).toString(16).substring(1);localStorage["lovelace-player-device-id"]=`${e()}${e()}-${e()}${e()}`}return localStorage["lovelace-player-device-id"]}()},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.d(__webpack_exports__,"a",(function(){return hasOldTemplate})),__webpack_require__.d(__webpack_exports__,"b",(function(){return parseOldTemplate}));var _hass_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(0),_deviceID_js__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(1);function hasOldTemplate(e){return/\[\[\s+.*\s+\]\]/.test(e)}function parseTemplateString(str,specialData={}){if("string"!=typeof str)return text;const FUNCTION=/^[a-zA-Z0-9_]+\(.*\)$/,EXPR=/([^=<>!]+)\s*(==|!=|<|>|<=|>=)\s*([^=<>!]+)/,SPECIAL=/^\{.+\}$/,STRING=/^"[^"]*"|'[^']*'$/;"string"==typeof specialData&&(specialData={}),specialData=Object.assign({user:Object(_hass_js__WEBPACK_IMPORTED_MODULE_0__.a)().user.name,browser:_deviceID_js__WEBPACK_IMPORTED_MODULE_1__.a,hash:location.hash.substr(1)||" "},specialData);const _parse_function=e=>{let t=[e.substr(0,e.indexOf("(")).trim()];for(e=e.substr(e.indexOf("(")+1);e;){let r=0,n=0,o=!1;for(;e[r];){let t=e[r++];if(t===o&&r>1&&"\\"!==e[r-2]?o=!1:"\"'".includes(t)&&(o=t),!o){if("("===t)n+=1;else if(")"===t){n-=1;continue}if(!(n>0)&&",)".includes(t))break}}t.push(e.substr(0,r-1).trim()),e=e.substr(r)}return t},_parse_special=e=>(e=e.substr(1,e.length-2),specialData[e]||`{${e}}`),_parse_entity=e=>{let t;if((e=e.split("."))[0].match(SPECIAL))t=_parse_special(e.shift()),t=Object(_hass_js__WEBPACK_IMPORTED_MODULE_0__.a)().states[t]||t;else if(t=Object(_hass_js__WEBPACK_IMPORTED_MODULE_0__.a)().states[`${e.shift()}.${e.shift()}`],!e.length)return t.state;return e.forEach(e=>t=t[e]),t},_eval_expr=str=>{if(str=EXPR.exec(str),null===str)return!1;const lhs=parseTemplateString(str[1]),rhs=parseTemplateString(str[3]);var expr="";return expr=parseFloat(lhs)!=lhs?`"${lhs}" ${str[2]} "${rhs}"`:`${parseFloat(lhs)} ${str[2]} ${parseFloat(rhs)}`,eval(expr)},_eval_function=e=>{if("if"===e[0])return _eval_expr(e[1])?parseTemplateString(e[2]):parseTemplateString(e[3])};try{return str=str.trim(),str.match(STRING)?str.substr(1,str.length-2):str.match(SPECIAL)?_parse_special(str):str.match(FUNCTION)?_eval_function(_parse_function(str)):str.includes(".")?_parse_entity(str):str}catch(e){return`[[ Template matching failed: ${str} ]]`}}function parseOldTemplate(e,t={}){if("string"!=typeof e)return e;return e=e.replace(/\[\[\s(.*?)\s\]\]/g,(e,r,n,o)=>parseTemplateString(r,t))}},function(e,t,r){"use strict";r.r(t);const n=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),o=n.prototype.html,s=n.prototype.css;function a(e,t,r=null){if((e=new Event(e,{bubbles:!0,cancelable:!1,composed:!0})).detail=t||{},r)r.dispatchEvent(e);else{var n=document.querySelector("home-assistant");(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=n&&n.shadowRoot)&&n.querySelector("home-assistant-main"))&&n.shadowRoot)&&n.querySelector("app-drawer-layout partial-panel-resolver"))&&n.shadowRoot||n)&&n.querySelector("ha-panel-lovelace"))&&n.shadowRoot)&&n.querySelector("hui-root"))&&n.shadowRoot)&&n.querySelector("ha-app-layout #view"))&&n.firstElementChild)&&n.dispatchEvent(e)}}const i="custom:";function c(e,t){const r=document.createElement("hui-error-card");return r.setConfig({type:"error",error:e,origConfig:t}),r}function l(e,t){if(!t||"object"!=typeof t||!t.type)return c(`No ${e} type configured`,t);let r=t.type;if(r=r.startsWith(i)?r.substr(i.length):`hui-${r}-${e}`,customElements.get(r))return function(e,t){const r=document.createElement(e);try{r.setConfig(t)}catch(e){return c(e,t)}return r}(r,t);const n=c(`Custom element doesn't exist: ${r}.`,t);n.style.display="None";const o=setTimeout(()=>{n.style.display=""},2e3);return customElements.whenDefined(r).then(()=>{clearTimeout(o),a("ll-rebuild",{},n)}),n}function u(e){return l("card",e)}function p(e){return l("element",e)}function d(e){const t=new Set(["call-service","divider","section","weblink"]);if(!e)return c("Invalid configuration given.",e);if("string"==typeof e&&(e={entity:e}),"object"!=typeof e||!e.entity&&!e.type)return c("Invalid configuration given.",e);const r=e.type||"default";if(t.has(r)||r.startsWith(i))return l("row",e);const n=e.entity.split(".",1)[0];return Object.assign(e,{type:{alert:"toggle",automation:"toggle",climate:"climate",cover:"cover",fan:"toggle",group:"group",input_boolean:"toggle",input_number:"input-number",input_select:"input-select",input_text:"input-text",light:"toggle",lock:"lock",media_player:"media-player",remote:"toggle",scene:"scene",script:"script",sensor:"sensor",timer:"timer",switch:"toggle",vacuum:"toggle",water_heater:"climate",input_datetime:"input-datetime"}[n]||"text"}),l("entity-row",e)}var f=r(0);const m=2;class h extends n{static get version(){return m}static get properties(){return{noHass:{type:Boolean}}}setConfig(e){this._config=e,this.el?this.el.setConfig(e):(this.el=this.create(e),this._hass&&(this.el.hass=this._hass),this.noHass&&Object(f.d)(this))}set config(e){this.setConfig(e)}set hass(e){this._hass=e,this.el&&(this.el.hass=e)}createRenderRoot(){return this}render(){return o`${this.el}`}}const _=function(e,t){const r=Object.getOwnPropertyDescriptors(t.prototype);for(const[t,n]of Object.entries(r))"constructor"!==t&&Object.defineProperty(e.prototype,t,n);const n=Object.getOwnPropertyDescriptors(t);for(const[t,r]of Object.entries(n))"prototype"!==t&&Object.defineProperty(e,t,r);const o=Object.getPrototypeOf(t),s=Object.getOwnPropertyDescriptors(o.prototype);for(const[t,r]of Object.entries(s))"constructor"!==t&&Object.defineProperty(Object.getPrototypeOf(e).prototype,t,r);const a=Object.getOwnPropertyDescriptors(o);for(const[t,r]of Object.entries(a))"prototype"!==t&&Object.defineProperty(Object.getPrototypeOf(e),t,r)},g=customElements.get("card-maker");if(!g||!g.version||g.version{document.body.querySelector("long-press").bind(e)}),customElements.whenDefined("action-handler").then(()=>{document.body.querySelector("action-handler").bind(e,t)}),e}function O(e,t=!1){a("hass-more-info",{entityId:e},document.querySelector("home-assistant"));const r=document.querySelector("home-assistant")._moreInfoEl;return r.large=t,r}function E(){const e=document.querySelector("home-assistant")&&document.querySelector("home-assistant")._moreInfoEl;e&&e.close()}function S(e,t,r=!1,n=null,o=!1){a("hass-more-info",{entityId:null});const s=document.querySelector("home-assistant")._moreInfoEl;s.close(),s.open();const i=document.createElement("div");i.innerHTML=`\n \n ${o?"":`\n \n \n
\n ${e}\n
\n
\n `}\n
\n \n \n
\n `;const c=i.querySelector(".scrollable");c.querySelector("card-maker").config=t,s.sizingTarget=c,s.large=r,s._page="none",s.shadowRoot.appendChild(i);let l={};if(n)for(var u in s.resetFit(),n)l[u]=s.style[u],s.style.setProperty(u,n[u]);return s._dialogOpenChanged=function(e){if(!e&&(this.stateObj&&this.fire("hass-more-info",{entityId:null}),this.shadowRoot==i.parentNode&&(this._page=null,this.shadowRoot.removeChild(i),n)))for(var t in s.resetFit(),l)l[t]?s.style.setProperty(t,l[t]):s.style.removeProperty(t)},s}function j(e,t,r){e||(e=Object(f.a)().connection);let n={user:Object(f.a)().user.name,browser:v.a,hash:location.hash.substr(1)||" ",...r.variables},o=r.template,s=r.entity_ids;return e.subscribeMessage(e=>t(e.result),{type:"render_template",template:o,variables:n,entity_ids:s})}var D=r(2);const T=Object(f.a)().callWS({type:"config/area_registry/list"}),P=Object(f.a)().callWS({type:"config/device_registry/list"}),C=Object(f.a)().callWS({type:"config/entity_registry/list"});async function q(){return window.cardToolsData=window.cardToolsData||{areas:await T,devices:await P,entities:await C},window.cardToolsData}function R(e){const t=window.cardToolsData;for(const r of t.areas)if(r.name.toLowerCase()===e.toLowerCase())return r;return null}function I(e){const t=window.cardToolsData;let r=[];if(!e)return r;for(const n of t.devices)n.area_id===e.area_id&&r.push(n);return r}function x(e){const t=window.cardToolsData;for(const r of t.devices)if(r.name.toLowerCase()===e.toLowerCase())return r;return null}function $(e){const t=window.cardToolsData;let r=[];if(!e)return r;for(const n of t.entities)n.device_id===e.id&&r.push(n.entity_id);return r}q();class k{static checkVersion(e){}static args(){}static logger(){}static get localize(){return Object(f.a)().localize}static get deviceID(){return v.a}static get fireEvent(){return a}static get hass(){return Object(f.a)()}static get lovelace(){return Object(f.b)()}static get lovelace_view(){return f.c}static get provideHass(){return f.d}static get LitElement(){return n}static get LitHtml(){return o}static get LitCSS(){return s}static get longpress(){return w}static get createCard(){return u}static get createElement(){return p}static get createEntityRow(){return d}static get moreInfo(){return O}static get popUp(){return S}static get closePopUp(){return E}static get hasTemplate(){return D.a}static parseTemplate(e,t,r={}){return"string"==typeof e?Object(D.b)(e,t):async function(e,t,r={}){for(var n in e||(e=e()),r={},r=Object.assign({user:e.user.name,browser:v.a,hash:location.hash.substr(1)||" "},r)){var o=new RegExp(`\\{${n}\\}`,"g");t=t.replace(o,r[n])}return e.callApi("POST","template",{template:t})}(e,t,r)}static get subscribeRenderTemplate(){return j}static get getData(){return q}static get areaByName(){return R}static get areaDevices(){return I}static get deviceByName(){return x}static get deviceEntities(){return $}}customElements.get("card-tools")||(customElements.define("card-tools",k),window.cardTools=customElements.get("card-tools"),console.info(`%cCARD-TOOLS 2 IS INSTALLED\n %cDeviceID: ${customElements.get("card-tools").deviceID}`,"color: green; font-weight: bold",""))}]); \ No newline at end of file diff --git a/www/community/lovelace-card-tools/card-tools.js.gz b/www/community/lovelace-card-tools/card-tools.js.gz index 7cbadc1..315ed2d 100644 Binary files a/www/community/lovelace-card-tools/card-tools.js.gz and b/www/community/lovelace-card-tools/card-tools.js.gz differ diff --git a/www/community/lovelace-card-tools/webpack.config.js b/www/community/lovelace-card-tools/webpack.config.js new file mode 100644 index 0000000..ee97d11 --- /dev/null +++ b/www/community/lovelace-card-tools/webpack.config.js @@ -0,0 +1,10 @@ +const path = require('path'); + +module.exports = { + entry: './src/main.js', + mode: 'production', + output: { + filename: 'card-tools.js', + path: path.resolve(__dirname) + } +}; diff --git a/www/community/lovelace-card-tools/webpack.config.js.gz b/www/community/lovelace-card-tools/webpack.config.js.gz new file mode 100644 index 0000000..5e35119 Binary files /dev/null and b/www/community/lovelace-card-tools/webpack.config.js.gz differ diff --git a/www/community/lovelace-fold-entity-row/fold-entity-row.js b/www/community/lovelace-fold-entity-row/fold-entity-row.js index 447a16a..040a64d 100644 --- a/www/community/lovelace-fold-entity-row/fold-entity-row.js +++ b/www/community/lovelace-fold-entity-row/fold-entity-row.js @@ -1,8 +1,8 @@ -!function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);const i=Object.getPrototypeOf(customElements.get("home-assistant-main")),o=i.prototype.html,s=i.prototype.css;function r(e,t,n=null){if((e=new Event(e,{bubbles:!0,cancelable:!1,composed:!0})).detail=t||{},n)n.dispatchEvent(e);else{var i=document.querySelector("home-assistant");(i=(i=(i=(i=(i=(i=(i=(i=(i=(i=(i=i&&i.shadowRoot)&&i.querySelector("home-assistant-main"))&&i.shadowRoot)&&i.querySelector("app-drawer-layout partial-panel-resolver"))&&i.shadowRoot||i)&&i.querySelector("ha-panel-lovelace"))&&i.shadowRoot)&&i.querySelector("hui-root"))&&i.shadowRoot)&&i.querySelector("ha-app-layout #view"))&&i.firstElementChild)&&i.dispatchEvent(e)}}const a="custom:",c=["input_number","input_select","input_text","input_datetime","scene","weblink"];function l(e,t){const n=document.createElement("hui-error-card");return n.setConfig({type:"error",error:e,config:t}),n}function u(e,t){if(!t||"object"!=typeof t||!t.type)return l(`No ${e} type configured`,t);let n=t.type;if(n=n.startsWith(a)?n.substr(a.length):`hui-${n}-${e}`,customElements.get(n))return function(e,t){const n=document.createElement(e);try{n.setConfig(t)}catch(e){return l(e,t)}return n}(n,t);const i=l(`Custom element doesn't exist: ${n}.`,t);i.style.display="None";const o=setTimeout(()=>{i.style.display=""},2e3);return customElements.whenDefined(n).then(()=>{clearTimeout(o),r("ll-rebuild",{},i)}),i}class h extends i{static get properties(){return{hass:{},config:{}}}setConfig(e){var t;this._config=e,this.el?this.el.setConfig(e):this.el=this.create(e),this._hass&&(this.el.hass=this._hass),this.noHass&&(t=this,document.querySelector("home-assistant").provideHass(t))}set config(e){this.setConfig(e)}set hass(e){this._hass=e,this.el&&(this.el.hass=e)}createRenderRoot(){return this}render(){return o`${this.el}`}}if(!customElements.get("card-maker")){class e extends h{create(e){return function(e){return u("card",e)}(e)}}customElements.define("card-maker",e)}if(!customElements.get("element-maker")){class e extends h{create(e){return function(e){return u("element",e)}(e)}}customElements.define("element-maker",e)}if(!customElements.get("entity-row-maker")){class e extends h{create(e){return function(e){const t=new Set(["call-service","divider","section","weblink"]);if(!e)return l("Invalid configuration given.",e);if("string"==typeof e&&(e={entity:e}),"object"!=typeof e||!e.entity&&!e.type)return l("Invalid configuration given.",e);const n=e.type||"default";if(t.has(n)||n.startsWith(a))return u("row",e);const i=e.entity.split(".",1)[0];return Object.assign(e,{type:{alert:"toggle",automation:"toggle",climate:"climate",cover:"cover",fan:"toggle",group:"group",input_boolean:"toggle",input_number:"input-number",input_select:"input-select",input_text:"input-text",light:"toggle",lock:"lock",media_player:"media-player",remote:"toggle",scene:"scene",script:"script",sensor:"sensor",timer:"timer",switch:"toggle",vacuum:"toggle",water_heater:"climate",input_datetime:"input-datetime"}[i]||"text"}),u("entity-row",e)}(e)}}customElements.define("entity-row-maker",e)}customElements.define("fold-entity-row",class extends i{static get properties(){return{hass:{},open:Boolean,items:{}}}setConfig(e){this._config=Object.assign({},{open:!1,padding:20,group_config:{}},e),this.open=this.open||this._config.open,this.items=this._config.items,this._config.entities&&(this.items=this._config.entities),"string"==typeof this._config.head&&this._config.head.startsWith("group.")&&(this.items=document.querySelector("home-assistant").hass.states[this._config.head].attributes.entity_id)}clickRow(e){const t=e.target.parentElement._config,n=t.entity||("string"==typeof t?t:null);e.stopPropagation(),this.hasMoreInfo(t)?function(e,t=!1){r("hass-more-info",{entityId:e},document.querySelector("home-assistant"));const n=document.querySelector("home-assistant")._moreInfoEl;n.large=t}(n):e.target.parentElement.hasAttribute("head")&&this.toggle(e)}toggle(e){e&&e.stopPropagation(),this.open=!this.open}hasMoreInfo(e){const t=e.entity||("string"==typeof e?e:null);return!(!t||c.includes(t.split(".",1)[0]))}firstUpdated(){const e=this.shadowRoot.querySelector("#head > entity-row-maker");e.updateComplete.then(()=>{const t=e.querySelector("hui-section-row");t&&t.updateComplete.then(()=>{t.shadowRoot.querySelector(".divider").style.marginRight="-56px"})})}render(){this._entities&&this._entities.forEach(e=>e.hass=this.hass);const e=e=>("string"==typeof e&&(e={entity:e}),Object.assign({},this._config.group_config,e));return o` +!function(t){var e={};function i(n){if(e[n])return e[n].exports;var s=e[n]={i:n,l:!1,exports:{}};return t[n].call(s.exports,s,s.exports,i),s.l=!0,s.exports}i.m=t,i.c=e,i.d=function(t,e,n){i.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var s in t)i.d(n,s,function(e){return t[e]}.bind(null,s));return n},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="",i(i.s=0)}([function(t,e,i){"use strict";i.r(e);const n=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),s=n.prototype.html,o=n.prototype.css;function r(){return document.querySelector("home-assistant").hass}const a="custom:",c=["input_number","input_select","input_text","scene","weblink"];function l(t,e){const i=document.createElement("hui-error-card");return i.setConfig({type:"error",error:t,config:e}),i}function h(t,e){if(!e||"object"!=typeof e||!e.type)return l(`No ${t} type configured`,e);let i=e.type;if(i=i.startsWith(a)?i.substr(a.length):`hui-${i}-${t}`,customElements.get(i))return function(t,e){const i=document.createElement(t);try{i.setConfig(e)}catch(t){return l(t,e)}return i}(i,e);const n=l(`Custom element doesn't exist: ${i}.`,e);n.style.display="None";const s=setTimeout(()=>{n.style.display=""},2e3);return customElements.whenDefined(i).then(()=>{clearTimeout(s),function(t,e,i=null){if((t=new Event(t,{bubbles:!0,cancelable:!1,composed:!0})).detail=e||{},i)i.dispatchEvent(t);else{var n=document.querySelector("home-assistant");(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=(n=n&&n.shadowRoot)&&n.querySelector("home-assistant-main"))&&n.shadowRoot)&&n.querySelector("app-drawer-layout partial-panel-resolver"))&&n.shadowRoot||n)&&n.querySelector("ha-panel-lovelace"))&&n.shadowRoot)&&n.querySelector("hui-root"))&&n.shadowRoot)&&n.querySelector("ha-app-layout #view"))&&n.firstElementChild)&&n.dispatchEvent(t)}}("ll-rebuild",{},n)}),n}class u extends n{static get properties(){return{hass:{},config:{},noHass:{type:Boolean}}}setConfig(t){var e;this._config=t,this.el?this.el.setConfig(t):this.el=this.create(t),this._hass&&(this.el.hass=this._hass),this.noHass&&(e=this,document.querySelector("home-assistant").provideHass(e))}set config(t){this.setConfig(t)}set hass(t){this._hass=t,this.el&&(this.el.hass=t)}createRenderRoot(){return this}render(){return s`${this.el}`}}if(!customElements.get("card-maker")){class t extends u{create(t){return function(t){return h("card",t)}(t)}getCardSize(){return this.firstElementChild&&this.firstElementChild.getCardSize?this.firstElementChild.getCardSize():1}}customElements.define("card-maker",t)}if(!customElements.get("element-maker")){class t extends u{create(t){return function(t){return h("element",t)}(t)}}customElements.define("element-maker",t)}if(!customElements.get("entity-row-maker")){class t extends u{create(t){return function(t){const e=new Set(["call-service","divider","section","weblink"]);if(!t)return l("Invalid configuration given.",t);if("string"==typeof t&&(t={entity:t}),"object"!=typeof t||!t.entity&&!t.type)return l("Invalid configuration given.",t);const i=t.type||"default";if(e.has(i)||i.startsWith(a))return h("row",t);const n=t.entity.split(".",1)[0];return Object.assign(t,{type:{alert:"toggle",automation:"toggle",climate:"climate",cover:"cover",fan:"toggle",group:"group",input_boolean:"toggle",input_number:"input-number",input_select:"input-select",input_text:"input-text",light:"toggle",lock:"lock",media_player:"media-player",remote:"toggle",scene:"scene",script:"script",sensor:"sensor",timer:"timer",switch:"toggle",vacuum:"toggle",water_heater:"climate",input_datetime:"input-datetime"}[n]||"text"}),h("entity-row",t)}(t)}}customElements.define("entity-row-maker",t)}customElements.define("fold-entity-row",class extends n{static get properties(){return{_hass:{},open:Boolean,items:{}}}setConfig(t){this._config=Object.assign({},{open:!1,padding:20,group_config:{}},t),this.open=this.open||this._config.open,this.head=this._config.head,this._config.entity&&(this.head=this._config.entity),"string"==typeof this.head&&(this.head={entity:this.head}),this.items=this._config.items,this._config.entities&&(this.items=this._config.entities),this.head.entity&&this.head.entity.startsWith("group.")&&!this.items&&(this.items=r().states[this.head.entity].attributes.entity_id)}clickRow(t){t.stopPropagation();const e=t.target.parentElement._config;this.hasMoreInfo(e)||e.tap_action?customElements.get("hui-entities-card").prototype._handleClick.bind(this)(e):t.target.parentElement.hasAttribute("head")&&this.toggle(t)}toggle(t){t&&t.stopPropagation(),this.open=!this.open}hasMoreInfo(t){const e=t.entity||("string"==typeof t?t:null);return!(!e||c.includes(e.split(".",1)[0]))}firstUpdated(){const t=this.shadowRoot.querySelector("#head > entity-row-maker");t.updateComplete.then(()=>{const e=t.querySelector("hui-section-row");e&&e.updateComplete.then(()=>{e.shadowRoot.querySelector(".divider").style.marginRight="-56px"})})}set hass(t){this._hass=t}render(){this._entities&&this._entities.forEach(t=>t.hass=this._hass);const t=t=>("string"==typeof t&&(t={entity:t}),Object.assign({},this._config.group_config,t));return s` - `}static get styles(){return s` + `}static get styles(){return o` #head { + --toggle-icon-width: 40px; display: flex; cursor: pointer; align-items: center; } #head entity-row-maker { flex-grow: 1; + max-width: calc(100% - var(--toggle-icon-width)); } #head ha-icon { - width: 40px; + width: var(--toggle-icon-width); cursor: pointer } diff --git a/www/community/lovelace-fold-entity-row/fold-entity-row.js.gz b/www/community/lovelace-fold-entity-row/fold-entity-row.js.gz index ff0b80c..3caac21 100644 Binary files a/www/community/lovelace-fold-entity-row/fold-entity-row.js.gz and b/www/community/lovelace-fold-entity-row/fold-entity-row.js.gz differ diff --git a/www/community/lovelace-fold-entity-row/webpack.config.js b/www/community/lovelace-fold-entity-row/webpack.config.js new file mode 100644 index 0000000..9572dd9 --- /dev/null +++ b/www/community/lovelace-fold-entity-row/webpack.config.js @@ -0,0 +1,10 @@ +const path = require('path'); + +module.exports = { + entry: './src/main.js', + mode: 'production', + output: { + filename: 'fold-entity-row.js', + path: path.resolve(__dirname) + } +}; diff --git a/www/community/lovelace-fold-entity-row/webpack.config.js.gz b/www/community/lovelace-fold-entity-row/webpack.config.js.gz new file mode 100644 index 0000000..5e2aa73 Binary files /dev/null and b/www/community/lovelace-fold-entity-row/webpack.config.js.gz differ diff --git a/www/community/lovelace-layout-card/layout-card.js b/www/community/lovelace-layout-card/layout-card.js index dbfe21c..ea64eb0 100644 --- a/www/community/lovelace-layout-card/layout-card.js +++ b/www/community/lovelace-layout-card/layout-card.js @@ -1,62 +1,31 @@ -customElements.whenDefined('card-tools').then(() => { -let cardTools = customElements.get('card-tools'); -class LayoutCard extends cardTools.LitElement { - - async setConfig(config) { - this.config = config; - this.layout = config.layout || 'auto'; - this.minCols = config.column_num || 1; - this.maxCols = config.max_columns || 100; - this.colWidth = config.column_width || 400; - this.maxWidth = config.max_width || 500; - this.minHeight = config.min_height || 5; - this.rtl = config.rtl || false; - this.cardSize = 1; - - this.make_cards(); - - window.addEventListener('resize', () => this.build()); - window.addEventListener('hass-open-menu', () => setTimeout(() => this.build(), 100)); - window.addEventListener('hass-close-menu', () => setTimeout(() => this.build(), 100)); - window.addEventListener('location-changed', () => { - if(location.hash === "") setTimeout(() => - this.build(), 100) - }); - if(config.rebuild) - window.setTimeout(() => this.build(), config.rebuild); - } - - render() { - return cardTools.LitHtml` -
- `; - } - - firstUpdated() { - if(this.parentElement && this.parentElement.id !== "view") - { - this.style.padding = "0"; - } - if(this.rtl) - this.shadowRoot.querySelector("#columns").style.flexDirection = 'row-reverse'; - this.build(); - this._cardModder = { - target: this, - styles: this.shadowRoot.querySelector("style") - }; - } - - static get styles() { - return cardTools.LitCSS` +!function(t){var e={};function n(s){if(e[s])return e[s].exports;var o=e[s]={i:s,l:!1,exports:{}};return t[s].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,s){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:s})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var s=Object.create(null);if(n.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(s,o,function(e){return t[e]}.bind(null,o));return s},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,n){"use strict";n.r(e);const s=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),o=s.prototype.html,i=s.prototype.css;const r="custom:";function c(t,e){const n=document.createElement("hui-error-card");return n.setConfig({type:"error",error:t,origConfig:e}),n}function a(t,e){if(!e||"object"!=typeof e||!e.type)return c(`No ${t} type configured`,e);let n=e.type;if(n=n.startsWith(r)?n.substr(r.length):`hui-${n}-${t}`,customElements.get(n))return function(t,e){const n=document.createElement(t);try{n.setConfig(e)}catch(t){return c(t,e)}return n}(n,e);const s=c(`Custom element doesn't exist: ${n}.`,e);s.style.display="None";const o=setTimeout(()=>{s.style.display=""},2e3);return customElements.whenDefined(n).then(()=>{clearTimeout(o),function(t,e,n=null){if((t=new Event(t,{bubbles:!0,cancelable:!1,composed:!0})).detail=e||{},n)n.dispatchEvent(t);else{var s=document.querySelector("home-assistant");(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=(s=s&&s.shadowRoot)&&s.querySelector("home-assistant-main"))&&s.shadowRoot)&&s.querySelector("app-drawer-layout partial-panel-resolver"))&&s.shadowRoot||s)&&s.querySelector("ha-panel-lovelace"))&&s.shadowRoot)&&s.querySelector("hui-root"))&&s.shadowRoot)&&s.querySelector("ha-app-layout #view"))&&s.firstElementChild)&&s.dispatchEvent(t)}}("ll-rebuild",{},s)}),s}function l(){return document.querySelector("home-assistant").hass}const u=2;class d extends s{static get version(){return u}static get properties(){return{noHass:{type:Boolean}}}setConfig(t){var e;this._config=t,this.el?this.el.setConfig(t):(this.el=this.create(t),this._hass&&(this.el.hass=this._hass),this.noHass&&(e=this,document.querySelector("home-assistant").provideHass(e)))}set config(t){this.setConfig(t)}set hass(t){this._hass=t,this.el&&(this.el.hass=t)}createRenderRoot(){return this}render(){return o`${this.el}`}}const h=function(t,e){const n=Object.getOwnPropertyDescriptors(e.prototype);for(const[e,s]of Object.entries(n))"constructor"!==e&&Object.defineProperty(t.prototype,e,s);const s=Object.getOwnPropertyDescriptors(e);for(const[e,n]of Object.entries(s))"prototype"!==e&&Object.defineProperty(t,e,n);const o=Object.getPrototypeOf(e),i=Object.getOwnPropertyDescriptors(o.prototype);for(const[e,n]of Object.entries(i))"constructor"!==e&&Object.defineProperty(Object.getPrototypeOf(t).prototype,e,n);const r=Object.getOwnPropertyDescriptors(o);for(const[e,n]of Object.entries(r))"prototype"!==e&&Object.defineProperty(Object.getPrototypeOf(t),e,n)},m=customElements.get("card-maker");if(!m||!m.version||m.version{if(!t)return;const s=e[function(){let t=0;for(let s=0;sthis.place_cards()),window.addEventListener("hass-open-menu",()=>setTimeout(()=>this.place_cards(),100)),window.addEventListener("hass-close-menu",()=>setTimeout(()=>this.place_cards(),100)),window.addEventListener("location-changed",()=>{""===location.hash&&setTimeout(()=>this.place_cards(),100)})}async updated(t){!this.cards.length&&(this._config.entities&&this._config.entities.length||this._config.cards&&this._config.cards.length)&&(this.cards=await this.build_cards(),this.place_cards()),t.has("hass")&&this.hass&&this.cards&&this.cards.forEach(t=>{t&&(t.hass=this.hass)})}async build_card(t){if("break"===t)return null;const e=document.createElement("card-maker");return e.config={...t,...this._config.card_options},e.hass=l(),this.shadowRoot.querySelector("#staging").appendChild(e),new Promise((t,n)=>e.updateComplete.then(()=>t(e)))}async build_cards(){const t=this.shadowRoot.querySelector("#staging");for(;t.lastChild;)t.removeChild(t.lastChild);return Promise.all((this._config.entities||this._config.cards).map(t=>this.build_card(t)))}place_cards(){const t=this.shadowRoot.querySelector("#columns").clientWidth;this.columns=function(t,e,n){const s=t=>"string"==typeof t&&t.endsWith("%")?Math.floor(e*parseInt(t)/100):parseInt(t);let o=0;if("object"==typeof n.column_width){let t=e;for(;t>0;){let e=n.column_width[o];void 0===e&&(e=n.column_width.slice(-1)[0]),t-=s(e),o+=1}o=Math.max(o-1,1)}else o=Math.floor(e/s(n.column_width));o=Math.max(o,n.min_columns),o=Math.min(o,n.max_columns);let i=[];for(let t=0;t{if(s+=1,!t)return;const n=e[(s-1)%e.length];n.appendChild(t),n.length+=t.getCardSize?t.getCardSize():1})}(t,i);break;case"vertical":!function(t,e,n){let s=0;t.forEach(t=>{if(!t)return void(s+=1);const n=e[s%e.length];n.appendChild(t),n.length+=t.getCardSize?t.getCardSize():1})}(t,i);break;case"auto":default:g(t,i,n)}return i=i.filter(t=>t.childElementCount>0)}(this.cards,t,this._config),this._config.rtl&&this.columns.reverse(),this.format_columns(),this.requestUpdate()}format_columns(){const t=(t,e,n,s="px")=>{if(void 0===this._config[e])return"";let o=`${t}: `;const i=this._config[e];return"object"==typeof i?i.length>n?o+=`${i[n]}`:o+=`${i.slice(-1)}`:o+=`${i}`,o.endsWith("px")||o.endsWith("%")||(o+=s),o+";"};for(const[e,n]of this.columns.entries()){const s=[t("max-width","max_width",e),t("min-width","min_width",e),t("width","column_width",e),t("flex-grow","flex_grow",e,"")];n.style.cssText="".concat(...s)}}getCardSize(){if(this.columns)return Math.max.apply(Math,this.columns.map(t=>t.length))}_isPanel(){if(this.isPanel)return!0;let t=this.parentElement,e=10;for(;e--;){if("hui-panel-view"===t.localName)return!0;if("div"===t.localName)return!1;t=t.parentElement}return!1}render(){return o` +
+ ${this.columns.map(t=>o` + ${t} + `)} +
+
+ `}static get styles(){return i` :host { - padding: 8px 4px 0; + padding: 0 4px; display: block; + margin-bottom: 0!important; } #columns { display: flex; flex-direction: row; justify-content: center; + margin-top: -8px; + } + #columns.panel { + margin-top: 0; } .column { @@ -65,130 +34,19 @@ class LayoutCard extends cardTools.LitElement { overflow-x: hidden; } - .column > * { + card-maker>* { display: block; margin: 4px 4px 8px; } - - .column > *:first-child { - margin-top: 0; + card-maker:first-child>* { + margin-top: 8px; } - `; - } - - make_cards() { - this._cards = this.config.cards.map((c) => { - if (typeof c === 'string') return c; - const card = cardTools.createCard(c); - if(this._hass) card.hass = this._hass; - this.appendChild(card); // Place card in DOM to get size - return card; - }); - } - - update_columns() { - const width = (this.shadowRoot && this.shadowRoot.querySelector("#columns").clientWidth) || (this.parentElement && this.parentElement.clientWidth); - this.colNum = Math.floor(width / this.colWidth) || 1; - this.colNum = Math.max(this.colNum, this.minCols); - this.colNum = Math.min(this.colNum, this.maxCols); - } - - build() { - if (this.offsetParent === null) return; - const root = this.shadowRoot.querySelector("#columns"); - while(root.lastChild) { - root.removeChild(root.lastChild); - } - - this.update_columns(); - - if(!this._cards) this.make_cards(); - - let cols = []; - let colSize = []; - for(let i = 0; i < this.colNum; i++) { - cols.push([]); - colSize.push(0); - } - - const shortestCol = () => { - let i = 0; - for(let j = 0; j < this.colNum; j++) { - if(colSize[j] < this.min_height) - return j; - if(colSize[j] < colSize[i]) - i = j; + card-maker:last-child>* { + margin-bottom: 4px; } - return i; - } - let i = 0; - this._cards.forEach((c) => { - const isBreak = (typeof(c) === 'string'); - const sz = c.getCardSize ? c.getCardSize() : 1; - - switch(this.layout) { - case 'horizontal': - if(i >= this.colNum) i = 0; - i += 1; - if(isBreak) break; - cols[i-1].push(c); - colSize[i-1] += sz; - break; - case 'vertical': - if(isBreak){ - i += 1; - if(i >= this.colNum) - i = 0; - break; - } - cols[i].push(c); - colSize[i] += sz; - break; - case 'auto': - default: - if(isBreak) break; - cols[shortestCol()].push(c); - colSize[shortestCol()] += sz; - break; + #staging { + visibility: hidden; + height: 0; } - }); - - cols = cols.filter((c) => c.length > 0); - cols.forEach((c, i) => { - const div = document.createElement('div'); - div.classList.add('column'); - c.forEach((e) => div.appendChild(e)); - root.appendChild(div); - if(cols.length > 1 && typeof(this.maxWidth) === 'object') { - div.style.setProperty('max-width', this.maxWidth[i]); - } else { - div.style.setProperty('max-width', this.maxWidth+'px'); - } - }); - - this.cardSize = Math.max.apply(null, colSize); - } - - set hass(hass) { - this._hass = hass; - if(this._cards) - this._cards - .filter((c) => typeof(c) !== 'string') - .forEach((c) => c.hass = hass); - } - - getCardSize() { - return this.cardSize; - } - -} - -customElements.define('layout-card', LayoutCard); -}); -window.setTimeout(() => { - if(customElements.get('card-tools')) return; - customElements.define('layout-card', class extends HTMLElement{ - setConfig() { throw new Error("Can't find card-tools. See https://github.com/thomasloven/lovelace-card-tools");} - }); -}, 2000); + `}get _cardModder(){return{target:this}}})}]); \ No newline at end of file diff --git a/www/community/lovelace-layout-card/layout-card.js.gz b/www/community/lovelace-layout-card/layout-card.js.gz index 95f2073..b528d4b 100644 Binary files a/www/community/lovelace-layout-card/layout-card.js.gz and b/www/community/lovelace-layout-card/layout-card.js.gz differ diff --git a/www/community/lovelace-layout-card/webpack.config.js b/www/community/lovelace-layout-card/webpack.config.js new file mode 100644 index 0000000..e24daea --- /dev/null +++ b/www/community/lovelace-layout-card/webpack.config.js @@ -0,0 +1,10 @@ +const path = require('path'); + +module.exports = { + entry: './src/main.js', + mode: 'production', + output: { + filename: 'layout-card.js', + path: path.resolve(__dirname) + } +}; \ No newline at end of file diff --git a/www/community/lovelace-layout-card/webpack.config.js.gz b/www/community/lovelace-layout-card/webpack.config.js.gz new file mode 100644 index 0000000..d279897 Binary files /dev/null and b/www/community/lovelace-layout-card/webpack.config.js.gz differ diff --git a/www/community/lovelace-multiple-entity-row/multiple-entity-row.js b/www/community/lovelace-multiple-entity-row/multiple-entity-row.js index 435f838..cf5c375 100644 --- a/www/community/lovelace-multiple-entity-row/multiple-entity-row.js +++ b/www/community/lovelace-multiple-entity-row/multiple-entity-row.js @@ -17,6 +17,7 @@ class MultipleEntityRow extends Polymer.Element { } .info { flex: 1 0 60px; + cursor: pointer; } .info, .info > * { white-space: nowrap; @@ -36,10 +37,12 @@ class MultipleEntityRow extends Polymer.Element { } state-badge { flex: 0 0 40px; + cursor: pointer; } .entity { margin-right: 16px; text-align: center; + cursor: pointer; } .entity span { font-size: 10px; @@ -50,58 +53,93 @@ class MultipleEntityRow extends Polymer.Element { } .state { min-width: 45px; - text-align: end; } .toggle { margin-left: 8px; } - -
+ +
- [[entityName(_config)]] + [[entityName(main)]]
-