From da8513febcd0f13ed08135a3bbd07f5197cfd40c Mon Sep 17 00:00:00 2001 From: dfcarvajal Date: Thu, 29 Aug 2019 10:20:24 +0200 Subject: [PATCH] Add cch module e include de lovelace para consumos --- lovelace/consumos.yaml | 202 +- ui-lovelace.yaml | 14 +- .../compact-custom-header.js | 2536 +++++++++++++++++ .../compact-custom-header.js.gz | Bin 0 -> 15213 bytes 4 files changed, 2650 insertions(+), 102 deletions(-) create mode 100644 www/community/compact-custom-header/compact-custom-header.js create mode 100644 www/community/compact-custom-header/compact-custom-header.js.gz diff --git a/lovelace/consumos.yaml b/lovelace/consumos.yaml index 0f4e719..3b5d952 100644 --- a/lovelace/consumos.yaml +++ b/lovelace/consumos.yaml @@ -1,102 +1,102 @@ badges: [] - cards: - - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_power - max: 3400 - min: 0 - name: Consumo Actual General - severity: - green: 200 - red: 3000 - yellow: 1000 - theme: default - type: gauge - unit: W - - entities: - - sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_power - hours_to_show: 48 - refresh_interval: 60 - title: Consumo 48 horas - type: history-graph - - entities: - - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_current - - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_power - - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_power_factor - - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_energy - - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_energy_2 - show_header_toggle: false - title: General Electricidad - type: entities - - entities: - - entity: sensor.consumo_varios_1 - name: UV1 - - entity: sensor.consumo_varios_2 - name: UV2 - - entity: sensor.consumo_humedos - name: Húmedos - - entity: sensor.consumo_horno - name: Horno - - entity: sensor.consumo_lavavajillas - name: Lavavajillas - - entity: sensor.consumo_lavadora - name: Lavadora - - entity: sensor.consumo_calefaccion - name: Calefacción - - entity: sensor.consumo_luces - name: Luces - - entity: sensor.consumo_persianas - name: Persianas - - entity: sensor.consumo_coche_electrico - name: Coche - title: Energía Consumida Actual - type: glance - - entities: - - entity: sensor.consumo_diario_varios_1 - name: UV1 - - entity: sensor.consumo_diario_varios_2 - name: UV2 - - entity: sensor.consumo_diario_humedos - name: Húmedos - - entity: sensor.consumo_diario_horno - name: Horno - - entity: sensor.consumo_diario_lavavajillas - name: Lavavajillas - - entity: sensor.consumo_diario_lavadora - name: Lavadora - - entity: sensor.consumo_diario_calefaccion - name: Calefacción - - entity: sensor.consumo_diario_luces - name: Luces - - entity: sensor.consumo_diario_persianas - name: Persianas - - entity: sensor.consumo_diario_coche_electrico - name: Coche - title: Energía Consumida Hoy - type: glance - - entities: - - entity: sensor.consumo_calefaccion - name: Calefacción - - entity: sensor.consumo_coche_electrico - name: Coche - - entity: sensor.consumo_luces - name: Luces - - entity: sensor.consumo_persianas - name: Persianas - - entity: sensor.consumo_horno - name: Horno - - entity: sensor.consumo_humedos - name: Humedos - - entity: sensor.consumo_lavavajillas - name: Lavavajillas - - entity: sensor.consumo_lavadora - name: Lavadora - - entity: sensor.consumo_varios_1 - name: UV1 - - entity: sensor.consumo_varios_2 - name: UV2 - hours_to_show: 12 - refresh_interval: 60 - title: Consumos - type: history-graph - icon: 'mdi:gauge' - path: consumos - title: Consumos +cards: + - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_power + max: 3400 + min: 0 + name: Consumo Actual General + severity: + green: 200 + red: 3000 + yellow: 1000 + theme: default + type: gauge + unit: W + - entities: + - sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_power + hours_to_show: 48 + refresh_interval: 60 + title: Consumo 48 horas + type: history-graph + - entities: + - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_current + - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_power + - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_power_factor + - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_energy + - entity: sensor.qubino_zmnhtdx_smart_meter_s4_s5_s6_energy_2 + show_header_toggle: false + title: General Electricidad + type: entities + - entities: + - entity: sensor.consumo_varios_1 + name: UV1 + - entity: sensor.consumo_varios_2 + name: UV2 + - entity: sensor.consumo_humedos + name: Húmedos + - entity: sensor.consumo_horno + name: Horno + - entity: sensor.consumo_lavavajillas + name: Lavavajillas + - entity: sensor.consumo_lavadora + name: Lavadora + - entity: sensor.consumo_calefaccion + name: Calefacción + - entity: sensor.consumo_luces + name: Luces + - entity: sensor.consumo_persianas + name: Persianas + - entity: sensor.consumo_coche_electrico + name: Coche + title: Energía Consumida Actual + type: glance + - entities: + - entity: sensor.consumo_diario_varios_1 + name: UV1 + - entity: sensor.consumo_diario_varios_2 + name: UV2 + - entity: sensor.consumo_diario_humedos + name: Húmedos + - entity: sensor.consumo_diario_horno + name: Horno + - entity: sensor.consumo_diario_lavavajillas + name: Lavavajillas + - entity: sensor.consumo_diario_lavadora + name: Lavadora + - entity: sensor.consumo_diario_calefaccion + name: Calefacción + - entity: sensor.consumo_diario_luces + name: Luces + - entity: sensor.consumo_diario_persianas + name: Persianas + - entity: sensor.consumo_diario_coche_electrico + name: Coche + title: Energía Consumida Hoy + type: glance + - entities: + - entity: sensor.consumo_calefaccion + name: Calefacción + - entity: sensor.consumo_coche_electrico + name: Coche + - entity: sensor.consumo_luces + name: Luces + - entity: sensor.consumo_persianas + name: Persianas + - entity: sensor.consumo_horno + name: Horno + - entity: sensor.consumo_humedos + name: Humedos + - entity: sensor.consumo_lavavajillas + name: Lavavajillas + - entity: sensor.consumo_lavadora + name: Lavadora + - entity: sensor.consumo_varios_1 + name: UV1 + - entity: sensor.consumo_varios_2 + name: UV2 + hours_to_show: 12 + refresh_interval: 60 + title: Consumos + type: history-graph +icon: 'mdi:gauge' +path: consumos +title: Consumos diff --git a/ui-lovelace.yaml b/ui-lovelace.yaml index f45a85d..014cf0e 100644 --- a/ui-lovelace.yaml +++ b/ui-lovelace.yaml @@ -1,6 +1,8 @@ resources: - type: module url: /community_plugin/mini-media-player/mini-media-player.js + - type: module + url: /community_plugin/compact-custom-header/compact-custom-header.js - type: js url: /community_plugin/lovelace-xiaomi-vacuum-card/xiaomi-vacuum-card.js - type: js @@ -23,6 +25,16 @@ resources: url: /community_plugin/lovelace-fold-entity-row/fold-entity-row.js - type: js url: /community_plugin/lovelace-auto-entities/auto-entities.js + +cch: + swipe: true + options: show + exceptions: + - conditions: + user_agent: Mobile + config: + options: hide + title: Hogar views: - badges: [] @@ -832,7 +844,7 @@ views: panel: false path: vehiculos title: Vehiculos - - !include \lovelace\consumos.yaml + - !include /config/lovelace/consumos.yaml - badges: - binary_sensor.trabajo - binary_sensor.colegio diff --git a/www/community/compact-custom-header/compact-custom-header.js b/www/community/compact-custom-header/compact-custom-header.js new file mode 100644 index 0000000..892e07c --- /dev/null +++ b/www/community/compact-custom-header/compact-custom-header.js @@ -0,0 +1,2536 @@ +const LitElement = Object.getPrototypeOf( + customElements.get("ha-panel-lovelace") +); +const html = LitElement.prototype.html; +const hass = document.querySelector("home-assistant").hass; + +const fireEvent = (node, type, detail, options) => { + 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; +}; + +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 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; + +const newSidebar = !root.querySelector("hui-notification-drawer"); + +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"]'); + +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; + } + + 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); + } + 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; + } + } + } + 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); + } + } + 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"); + } + }); + show_tabs.innerHTML = "Show all tabs"; + insertMenuItem(buttons.options.querySelector("paper-listbox"), show_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); + } +} + +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"; + } + + 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" + } + } + `; + root.appendChild(style); + } + + 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 { + ${ + cchConfig.active_tab_color + ? `color: ${cchConfig.active_tab_color + " !important"}` + : "var(--cch-active-tab-color)" + } + } + `; + 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; + } + } + + if (tabContainer) { + // Shift the header up to hide unused portion. + root.querySelector("app-toolbar").style.marginTop = cchConfig.compact_header + ? "-64px" + : ""; + + 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. + let style = document.createElement("style"); + style.setAttribute("id", "cch_chevron"); + style.innerHTML = ` + .not-visible { + display:none; + } + `; + tabContainer.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"; + } + 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; + } + `; + paperIconButton.parentNode.appendChild(style); + } + } + } else if (cchConfig[button] == "hide") { + buttons[button].style.display = "none"; + } + if (newSidebar && (cchConfig.kiosk_mode || cchConfig.disable_sidebar)) { + buttons.menu.style.display = "none"; + } + } + + // 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)"}; + } + `; + if (!newSidebar) buttons.notifications.shadowRoot.appendChild(style); + } +} + +function getTranslation(button) { + switch (button) { + case "notifications": + return hass.localize("ui.notification_drawer.title"); + default: + return button.charAt(0).toUpperCase() + button.slice(1); + } +} + +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; + for (let view in views) { + if ( + views[view]["title"] == default_tab || + views[view]["path"] == default_tab + ) { + default_tab = 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(); + } + } + 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"; + } + clockElement.style.cssText = ` + margin-top: ${cchConfig.clock_date ? "-4px" : "2px"}; + text-align: ${clockAlign}; + ${padding}; + ${fontSize}; + `; + clockIronIcon.parentNode.insertBefore(clockElement, clockIronIcon); + clockIronIcon.style.display = "none"; + } + + 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 => { + try { + if (template.includes("return")) { + return eval(`(function() {${template}}())`); + } else { + 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); + } + } +} + +// 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)); + } + } + } else { + ranges.push(parseInt(array[i])); + } + } + 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; + } + } + 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; + 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" + ); + }); + firstTab = wrap ? 0 : null; + lastTab = wrap ? tabs.length - 1 : null; + } + + function click(index) { + if ( + (activeTab == 0 && !wrap && left) || + (activeTab == tabs.length - 1 && !wrap && !left) + ) { + 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 }) + ); + } + } +} + +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." + }); + } +} + +// EDITOR ////////////////////////////////////////////////////////////////// + +const buttonOptions = ["show", "hide", "clock", "overflow"]; +const overflowOptions = ["show", "hide", "clock"]; +const swipeAnimation = ["none", "swipe", "fade", "flip"]; +let _lovelace; + +class CompactCustomHeaderEditor extends LitElement { + static get properties() { + return { + _config: {} + }; + } + + firstUpdated() { + let ll = document.querySelector("home-assistant"); + ll = ll && ll.shadowRoot; + ll = ll && ll.querySelector("home-assistant-main"); + ll = ll && ll.shadowRoot; + ll = ll && ll.querySelector("app-drawer-layout partial-panel-resolver"); + ll = (ll && ll.shadowRoot) || ll; + ll = ll && ll.querySelector("ha-panel-lovelace"); + ll = ll && ll.shadowRoot; + _lovelace = ll && ll.querySelector("hui-root").lovelace; + + this._config = _lovelace.config.cch ? deepcopy(_lovelace.config.cch) : {}; + } + + render() { + if (!this._config || !_lovelace) return html``; + return html` +
+ X +
+ ${this.renderStyle()} + + +

Exceptions

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

Current User

+

${hass.user.name}

+

Current User Agent

+
+ ${navigator.userAgent} +
+

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

+ `; + } + + get _mwc_button() { + return customElements.get("mwc-button") ? true : false; + } + + _close() { + let editor = this.parentNode.parentNode.parentNode.querySelector("editor"); + this.parentNode.parentNode.parentNode.removeChild(editor); + } + + _save() { + for (var key in this._config) { + if (this._config[key] == defaultConfig[key]) { + delete this._config[key]; + } + } + let newConfig = { + ..._lovelace.config, + ...{ cch: this._config } + }; + try { + _lovelace.saveConfig(newConfig).then(() => { + location.reload(true); + }); + } catch (e) { + alert("Save failed: " + e); + } + } + + get _save_button() { + return this._mwc_button + ? html` + Save and Reload + ` + : html` + Save and Reload + `; + } + get _cancel_button() { + return this._mwc_button + ? html` + Cancel + ` + : html` + Cancel + `; + } + + _addException() { + let newExceptions; + if (this._config.exceptions) { + newExceptions = this._config.exceptions.slice(0); + newExceptions.push({ + conditions: {}, + config: {} + }); + } else { + newExceptions = [ + { + conditions: {}, + config: {} + } + ]; + } + this._config = { + ...this._config, + exceptions: newExceptions + }; + + 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 + }); + } + + _exceptionChanged(ev) { + if (!this._config) { + return; + } + const target = ev.target.index; + const newExceptions = this._config.exceptions.slice(0); + newExceptions[target] = ev.detail.exception; + this._config = { + ...this._config, + exceptions: newExceptions + }; + + fireEvent(this, "config-changed", { + config: this._config + }); + } + + _exceptionDelete(ev) { + if (!this._config) { + return; + } + const target = ev.target; + const newExceptions = this._config.exceptions.slice(0); + newExceptions.splice(target.index, 1); + this._config = { + ...this._config, + exceptions: newExceptions + }; + + fireEvent(this, "config-changed", { + config: this._config + }); + this.requestUpdate(); + } + + renderStyle() { + return html` + + `; + } +} + +customElements.define( + "compact-custom-header-editor", + CompactCustomHeaderEditor +); + +class CchConfigEditor extends LitElement { + static get properties() { + return { + defaultConfig: {}, + config: {}, + exception: {}, + _closed: {} + }; + } + + get _clock() { + return ( + this.getConfig("menu") == "clock" || + this.getConfig("voice") == "clock" || + this.getConfig("notifications") == "clock" || + this.getConfig("options") == "clock" + ); + } + + getConfig(item) { + return this.config[item] !== undefined + ? this.config[item] + : this.defaultConfig[item]; + } + + render() { + this.exception = this.exception !== undefined && this.exception !== false; + return html` + + + + ${!this.exception + ? html` +

+ Compact Custom Header +

+

+ + + + Docs    + + + + Github    + + + + Forums +

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

Menu Items

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

Buttons

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

Clock Options

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

Tabs

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

Swipe Navigation

+
+ + Swipe Navigation + + ${this.config.swipe + ? html` + + Wrapping + + + Prevent Default + +
+
+
+ + + ${swipeAnimation.map(option => { + return html` + ${option} + `; + })} + + +
+ + +
+ + + + ` + : ""} + + `; + } + + _toggleCard() { + this._closed = !this._closed; + 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") { + show.style.display = "none"; + hide.style.display = "initial"; + } else { + hide.style.display = "none"; + show.style.display = "initial"; + } + } + + _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]; + } else { + this.config = { + ...this.config, + [target.configValue]: + target.checked !== undefined ? target.checked : target.value + }; + } + } + fireEvent(this, "cch-config-changed", { + config: this.config + }); + } + + renderStyle() { + return html` + + `; + } +} + +customElements.define("cch-config-editor", CchConfigEditor); + +class CchExceptionEditor extends LitElement { + static get properties() { + return { + config: {}, + exception: {}, + _closed: {} + }; + } + + constructor() { + super(); + this._closed = true; + } + + render() { + if (!this.exception) { + return html``; + } + return html` + ${this.renderStyle()} + + + + +
+
+ ${Object.values(this.exception.conditions) + .join(", ") + .substring(0, 40) || "New Exception"} + + + + +
+

Conditions

+ + +

Config

+ + +
+
+ `; + } + + renderStyle() { + return html` + + `; + } + + _toggleCard() { + this._closed = !this._closed; + fireEvent(this, "iron-resize"); + } + + _deleteException() { + fireEvent(this, "cch-exception-delete"); + } + + _conditionsChanged(ev) { + if (!this.exception) { + return; + } + const newException = { + ...this.exception, + conditions: ev.detail.conditions + }; + fireEvent(this, "cch-exception-changed", { + exception: newException + }); + } + + _configChanged(ev) { + ev.stopPropagation(); + if (!this.exception) { + return; + } + const newException = { ...this.exception, config: ev.detail.config }; + fireEvent(this, "cch-exception-changed", { + exception: newException + }); + } +} + +customElements.define("cch-exception-editor", CchExceptionEditor); + +class CchConditionsEditor extends LitElement { + static get properties() { + return { + conditions: {} + }; + } + + get _user() { + return this.conditions.user || ""; + } + + get _user_agent() { + return this.conditions.user_agent || ""; + } + + get _media_query() { + return this.conditions.media_query || ""; + } + + get _query_string() { + return this.conditions.query_string || ""; + } + + render() { + if (!this.conditions) { + return html``; + } + return html` + + + + + + + + + `; + } + + _valueChanged(ev) { + if (!this.conditions) { + return; + } + const target = ev.target; + if (this[`_${target.configValue}`] === target.value) { + return; + } + if (target.configValue) { + if (target.value === "") { + delete this.conditions[target.configValue]; + } else { + this.conditions = { + ...this.conditions, + [target.configValue]: target.value + }; + } + } + fireEvent(this, "cch-conditions-changed", { + conditions: this.conditions + }); + } +} + +customElements.define("cch-conditions-editor", CchConditionsEditor); + +function 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(deepcopy); + } + var result = {}; + Object.keys(value).forEach(function(key) { + result[key] = deepcopy(value[key]); + }); + return result; +} + +console.info( + `%c COMPACT-CUSTOM-HEADER \n%c Version 1.3.5 `, + "color: orange; font-weight: bold; background: black", + "color: white; font-weight: bold; background: dimgray" +); diff --git a/www/community/compact-custom-header/compact-custom-header.js.gz b/www/community/compact-custom-header/compact-custom-header.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..f12271fb398408e9c51511b2f4d89476c9ed282f GIT binary patch literal 15213 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 literal 0 HcmV?d00001