2537 lines
76 KiB
JavaScript
2537 lines
76 KiB
JavaScript
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
|
|
? `</br>${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`
|
|
<div @click="${this._close}" class="title_control">
|
|
X
|
|
</div>
|
|
${this.renderStyle()}
|
|
<cch-config-editor
|
|
.defaultConfig="${defaultConfig}"
|
|
.config="${this._config}"
|
|
@cch-config-changed="${this._configChanged}"
|
|
>
|
|
</cch-config-editor>
|
|
<h4 class="underline">Exceptions</h4>
|
|
<br />
|
|
${this._config.exceptions
|
|
? this._config.exceptions.map((exception, index) => {
|
|
return html`
|
|
<cch-exception-editor
|
|
.config="${this._config}"
|
|
.exception="${exception}"
|
|
.index="${index}"
|
|
@cch-exception-changed="${this._exceptionChanged}"
|
|
@cch-exception-delete="${this._exceptionDelete}"
|
|
>
|
|
</cch-exception-editor>
|
|
`;
|
|
})
|
|
: ""}
|
|
<br />
|
|
${this._mwc_button
|
|
? html`
|
|
<mwc-button @click="${this._addException}"
|
|
>Add Exception
|
|
</mwc-button>
|
|
`
|
|
: html`
|
|
<paper-button @click="${this._addException}"
|
|
>Add Exception
|
|
</paper-button>
|
|
`}
|
|
|
|
<h4 class="underline">Current User</h4>
|
|
<p style="font-size:16pt">${hass.user.name}</p>
|
|
<h4 class="underline">Current User Agent</h4>
|
|
<br />
|
|
${navigator.userAgent}
|
|
<br />
|
|
<h4
|
|
style="background:var(--paper-card-background-color);
|
|
margin-bottom:-20px;"
|
|
class="underline"
|
|
>
|
|
${!this.exception
|
|
? html`
|
|
${this._save_button}
|
|
`
|
|
: ""}
|
|
${!this.exception
|
|
? html`
|
|
${this._cancel_button}
|
|
`
|
|
: ""}
|
|
</h4>
|
|
`;
|
|
}
|
|
|
|
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`
|
|
<mwc-button raised @click="${this._save}">Save and Reload</mwc-button>
|
|
`
|
|
: html`
|
|
<paper-button raised @click="${this._save}"
|
|
>Save and Reload</paper-button
|
|
>
|
|
`;
|
|
}
|
|
get _cancel_button() {
|
|
return this._mwc_button
|
|
? html`
|
|
<mwc-button raised @click="${this._close}">Cancel</mwc-button>
|
|
`
|
|
: html`
|
|
<paper-button raised @click="${this._close}">Cancel</paper-button>
|
|
`;
|
|
}
|
|
|
|
_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`
|
|
<style>
|
|
h3,
|
|
h4 {
|
|
font-size: 16pt;
|
|
margin-bottom: 5px;
|
|
width: 100%;
|
|
}
|
|
paper-button {
|
|
margin: 0;
|
|
background-color: var(--primary-color);
|
|
color: var(--text-primary-color, #fff);
|
|
}
|
|
.toggle-button {
|
|
margin: 4px;
|
|
background-color: transparent;
|
|
color: var(--primary-color);
|
|
}
|
|
.title_control {
|
|
color: var(--text-dark-color);
|
|
font-weight: bold;
|
|
font-size: 22px;
|
|
float: right;
|
|
cursor: pointer;
|
|
margin: -10px -5px -5px -5px;
|
|
}
|
|
.user_agent {
|
|
display: block;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
padding: 5px;
|
|
border: 0;
|
|
resize: none;
|
|
width: 100%;
|
|
}
|
|
.underline {
|
|
width: 100%;
|
|
background: var(--dark-color);
|
|
color: var(--text-dark-color);
|
|
padding: 5px;
|
|
width: calc(100% + 30px);
|
|
margin-left: calc(0% - 20px);
|
|
}
|
|
</style>
|
|
`;
|
|
}
|
|
}
|
|
|
|
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`
|
|
<custom-style>
|
|
<style is="custom-style">
|
|
a {
|
|
color: var(--text-dark-color);
|
|
text-decoration: none;
|
|
}
|
|
.card-header {
|
|
margin-top: -5px;
|
|
@apply --paper-font-headline;
|
|
}
|
|
.card-header paper-icon-button {
|
|
margin-top: -5px;
|
|
float: right;
|
|
}
|
|
</style>
|
|
</custom-style>
|
|
${!this.exception
|
|
? html`
|
|
<h1 style="margin-top:-20px;margin-bottom:0;" class="underline">
|
|
Compact Custom Header
|
|
</h1>
|
|
<h4
|
|
style="margin-top:-5px;padding-top:10px;font-size:12pt;"
|
|
class="underline"
|
|
>
|
|
<a
|
|
href="https://maykar.github.io/compact-custom-header/"
|
|
target="_blank"
|
|
>
|
|
<ha-icon icon="mdi:help-circle" style="margin-top:-5px;">
|
|
</ha-icon>
|
|
Docs </a
|
|
>
|
|
<a
|
|
href="https://github.com/maykar/compact-custom-header"
|
|
target="_blank"
|
|
>
|
|
<ha-icon icon="mdi:github-circle" style="margin-top:-5px;">
|
|
</ha-icon>
|
|
Github </a
|
|
>
|
|
<a
|
|
href="https://community.home-assistant.io/t/compact-custom-header"
|
|
target="_blank"
|
|
>
|
|
<ha-icon icon="hass:home-assistant" style="margin-top:-5px;">
|
|
</ha-icon>
|
|
Forums</a
|
|
>
|
|
</h4>
|
|
${this.getConfig("warning")
|
|
? html`
|
|
<br />
|
|
<div class="warning">
|
|
Modifying options marked with a
|
|
<iron-icon
|
|
icon="hass:alert"
|
|
style="width:20px;margin-top:-6px;"
|
|
></iron-icon
|
|
>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.
|
|
</div>
|
|
<br />
|
|
`
|
|
: ""}
|
|
`
|
|
: ""}
|
|
${this.renderStyle()}
|
|
<div class="side-by-side">
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.disable === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("disable") !== false}"
|
|
.configValue="${"disable"}"
|
|
@change="${this._valueChanged}"
|
|
title="Completely disable CCH. Useful for exceptions."
|
|
>
|
|
Disable CCH
|
|
</paper-toggle-button>
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.compact_header === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("compact_header") !== false}"
|
|
.configValue="${"compact_header"}"
|
|
@change="${this._valueChanged}"
|
|
title="Make header compact."
|
|
>
|
|
Compact Header
|
|
</paper-toggle-button>
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.kiosk_mode === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("kiosk_mode") !== false}"
|
|
.configValue="${"kiosk_mode"}"
|
|
@change="${this._valueChanged}"
|
|
title="Hide the header, close the sidebar, and disable sidebar swipe."
|
|
>
|
|
Kiosk Mode
|
|
${this.getConfig("warning")
|
|
? html`
|
|
<iron-icon icon="hass:alert" class="alert"></iron-icon>
|
|
`
|
|
: ""}
|
|
</paper-toggle-button>
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.header === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("header") !== false &&
|
|
this.getConfig("kiosk_mode") == false}"
|
|
.configValue="${"header"}"
|
|
@change="${this._valueChanged}"
|
|
title="Turn off to hide the header completely."
|
|
>
|
|
Display Header
|
|
${this.getConfig("warning")
|
|
? html`
|
|
<iron-icon icon="hass:alert" class="alert"></iron-icon>
|
|
`
|
|
: ""}
|
|
</paper-toggle-button>
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.chevrons === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("chevrons") !== false}"
|
|
.configValue="${"chevrons"}"
|
|
@change="${this._valueChanged}"
|
|
title="View scrolling controls in header."
|
|
>
|
|
Display Tab Chevrons
|
|
</paper-toggle-button>
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.redirect === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("redirect") !== false}"
|
|
.configValue="${"redirect"}"
|
|
@change="${this._valueChanged}"
|
|
title="Auto-redirect away from hidden tabs."
|
|
>
|
|
Hidden Tab Redirect
|
|
</paper-toggle-button>
|
|
<paper-toggle-button
|
|
style="${newSidebar ? "" : "display:none;"}"
|
|
class="${this.exception && this.config.disable_sidebar === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("disable_sidebar") !== false ||
|
|
this.getConfig("kiosk_mode") !== false}"
|
|
.configValue="${"disable_sidebar"}"
|
|
@change="${this._valueChanged}"
|
|
title="Hides and prevents sidebar from opening."
|
|
>
|
|
Hide & Disable Sidebar
|
|
</paper-toggle-button>
|
|
<paper-toggle-button
|
|
style="${newSidebar ? "display:none;" : ""}"
|
|
class="${this.exception && this.config.sidebar_closed === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("sidebar_closed") !== false ||
|
|
this.getConfig("kiosk_mode") !== false}"
|
|
.configValue="${"sidebar_closed"}"
|
|
@change="${this._valueChanged}"
|
|
title="Closes the sidebar on opening editor_lovelace."
|
|
>
|
|
Close Sidebar
|
|
</paper-toggle-button>
|
|
${!this.exception
|
|
? html`
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.warning === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("warning") !== false}"
|
|
.configValue="${"warning"}"
|
|
@change="${this._valueChanged}"
|
|
title="Toggle warnings in this editor."
|
|
>
|
|
Display CCH Warnings
|
|
</paper-toggle-button>
|
|
`
|
|
: ""}
|
|
<paper-toggle-button
|
|
style="${newSidebar ? "display:none;" : ""}"
|
|
class="${this.exception && this.config.sidebar_swipe === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("sidebar_swipe") !== false &&
|
|
this.getConfig("kiosk_mode") == false}"
|
|
.configValue="${"sidebar_swipe"}"
|
|
@change="${this._valueChanged}"
|
|
title="Swipe to open sidebar on mobile devices."
|
|
>
|
|
Swipe Open Sidebar
|
|
</paper-toggle-button>
|
|
</div>
|
|
<h4 class="underline">Menu Items</h4>
|
|
<div class="side-by-side">
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.hide_config === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("hide_config") !== false}"
|
|
.configValue="${"hide_config"}"
|
|
@change="${this._valueChanged}"
|
|
title='Hide "Configure UI" in options menu.'
|
|
>
|
|
Hide "Configure UI"
|
|
${this.getConfig("warning")
|
|
? html`
|
|
<iron-icon icon="hass:alert" class="alert"></iron-icon>
|
|
`
|
|
: ""}
|
|
</paper-toggle-button>
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.hide_help === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("hide_help") !== false}"
|
|
.configValue="${"hide_help"}"
|
|
@change="${this._valueChanged}"
|
|
title='Hide "Help" in options menu.'
|
|
>
|
|
Hide "Help"
|
|
</paper-toggle-button>
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.hide_unused === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("hide_unused") !== false}"
|
|
.configValue="${"hide_unused"}"
|
|
@change="${this._valueChanged}"
|
|
title='Hide "Help" in options menu.'
|
|
>
|
|
Hide "Unused Entities"
|
|
</paper-toggle-button>
|
|
</div>
|
|
<h4 class="underline">Buttons</h4>
|
|
<div class="buttons side-by-side">
|
|
<div
|
|
class="${this.exception && this.config.menu === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
>
|
|
<iron-icon icon="hass:menu"></iron-icon>
|
|
<paper-dropdown-menu
|
|
@value-changed="${this._valueChanged}"
|
|
label="Menu Button:"
|
|
.configValue="${"menu"}"
|
|
>
|
|
<paper-listbox
|
|
slot="dropdown-content"
|
|
.selected="${buttonOptions.indexOf(this.getConfig("menu"))}"
|
|
>
|
|
${buttonOptions.map(option => {
|
|
return html`
|
|
<paper-item>${option}</paper-item>
|
|
`;
|
|
})}
|
|
</paper-listbox>
|
|
</paper-dropdown-menu>
|
|
</div>
|
|
<div
|
|
class="${this.exception && this.config.voice === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
>
|
|
<iron-icon icon="hass:microphone"></iron-icon>
|
|
<paper-dropdown-menu
|
|
@value-changed="${this._valueChanged}"
|
|
label="Voice Button:"
|
|
.configValue="${"voice"}"
|
|
>
|
|
<paper-listbox
|
|
slot="dropdown-content"
|
|
.selected="${buttonOptions.indexOf(this.getConfig("voice"))}"
|
|
>
|
|
${buttonOptions.map(option => {
|
|
return html`
|
|
<paper-item>${option}</paper-item>
|
|
`;
|
|
})}
|
|
</paper-listbox>
|
|
</paper-dropdown-menu>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="buttons side-by-side"
|
|
style="${newSidebar ? "width:50%;" : ""}"
|
|
>
|
|
<div
|
|
class="${this.exception && this.config.options === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
>
|
|
<iron-icon icon="hass:dots-vertical"></iron-icon>
|
|
<paper-dropdown-menu
|
|
@value-changed="${this._valueChanged}"
|
|
label="Options Button:"
|
|
.configValue="${"options"}"
|
|
>
|
|
<paper-listbox
|
|
slot="dropdown-content"
|
|
.selected="${overflowOptions.indexOf(this.getConfig("options"))}"
|
|
>
|
|
${overflowOptions.map(option => {
|
|
return html`
|
|
<paper-item>${option}</paper-item>
|
|
`;
|
|
})}
|
|
</paper-listbox>
|
|
</paper-dropdown-menu>
|
|
</div>
|
|
<div
|
|
style="${newSidebar ? "display:none;" : ""}"
|
|
class="${this.exception && this.config.notifications === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
>
|
|
<iron-icon icon="hass:bell"></iron-icon>
|
|
<paper-dropdown-menu
|
|
@value-changed="${this._valueChanged}"
|
|
label="Notifications Button:"
|
|
.configValue="${"notifications"}"
|
|
>
|
|
<paper-listbox
|
|
slot="dropdown-content"
|
|
.selected="${buttonOptions.indexOf(
|
|
this.getConfig("notifications")
|
|
)}"
|
|
>
|
|
${buttonOptions.map(option => {
|
|
return html`
|
|
<paper-item>${option}</paper-item>
|
|
`;
|
|
})}
|
|
</paper-listbox>
|
|
</paper-dropdown-menu>
|
|
</div>
|
|
</div>
|
|
${this._clock
|
|
? html`
|
|
<h4 class="underline">Clock Options</h4>
|
|
<div class="side-by-side">
|
|
<paper-dropdown-menu
|
|
class="${this.exception &&
|
|
this.getConfig("clock_format") === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
label="Clock format"
|
|
@value-changed="${this._valueChanged}"
|
|
.configValue="${"clock_format"}"
|
|
>
|
|
<paper-listbox
|
|
slot="dropdown-content"
|
|
.selected="${this.getConfig("clock_format") === "24" ? 1 : 0}"
|
|
>
|
|
<paper-item>12</paper-item>
|
|
<paper-item>24</paper-item>
|
|
</paper-listbox>
|
|
</paper-dropdown-menu>
|
|
<paper-input
|
|
class="${this.exception && this.config.date_locale === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
label="Date Locale:"
|
|
.value="${this.getConfig("date_locale")}"
|
|
.configValue="${"date_locale"}"
|
|
@value-changed="${this._valueChanged}"
|
|
>
|
|
</paper-input>
|
|
|
|
<div class="side-by-side">
|
|
<paper-toggle-button
|
|
class="${this.exception &&
|
|
this.config.clock_am_pm === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("clock_am_pm") !== false}"
|
|
.configValue="${"clock_am_pm"}"
|
|
@change="${this._valueChanged}"
|
|
>
|
|
AM / PM</paper-toggle-button
|
|
>
|
|
<paper-toggle-button
|
|
class="${this.exception &&
|
|
this.config.clock_date === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("clock_date") !== false}"
|
|
.configValue="${"clock_date"}"
|
|
@change="${this._valueChanged}"
|
|
>
|
|
Date</paper-toggle-button
|
|
>
|
|
</div>
|
|
</div>
|
|
`
|
|
: ""}
|
|
<h4 class="underline">Tabs</h4>
|
|
<paper-dropdown-menu id="tabs" @value-changed="${this._tabVisibility}">
|
|
<paper-listbox
|
|
slot="dropdown-content"
|
|
.selected="${this.getConfig("show_tabs").length > 0 ? "1" : "0"}"
|
|
>
|
|
<paper-item>Hide Tabs</paper-item>
|
|
<paper-item>Show Tabs</paper-item>
|
|
</paper-listbox>
|
|
</paper-dropdown-menu>
|
|
<div class="side-by-side">
|
|
<div
|
|
id="show"
|
|
style="display:${this.getConfig("show_tabs").length > 0
|
|
? "initial"
|
|
: "none"}"
|
|
>
|
|
<paper-input
|
|
class="${this.exception && this.config.show_tabs === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
label="Comma-separated list of tab numbers to show:"
|
|
.value="${this.getConfig("show_tabs")}"
|
|
.configValue="${"show_tabs"}"
|
|
@value-changed="${this._valueChanged}"
|
|
>
|
|
</paper-input>
|
|
</div>
|
|
<div
|
|
id="hide"
|
|
style="display:${this.getConfig("show_tabs").length > 0
|
|
? "none"
|
|
: "initial"}"
|
|
>
|
|
<paper-input
|
|
class="${this.exception && this.config.hide_tabs === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
label="Comma-separated list of tab numbers to hide:"
|
|
.value="${this.getConfig("hide_tabs")}"
|
|
.configValue="${"hide_tabs"}"
|
|
@value-changed="${this._valueChanged}"
|
|
>
|
|
</paper-input>
|
|
</div>
|
|
<paper-input
|
|
class="${this.exception && this.config.default_tab === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
label="Default tab:"
|
|
.value="${this.getConfig("default_tab")}"
|
|
.configValue="${"default_tab"}"
|
|
@value-changed="${this._valueChanged}"
|
|
>
|
|
</paper-input>
|
|
</div>
|
|
<h4 class="underline">Swipe Navigation</h4>
|
|
<div class="side-by-side">
|
|
<paper-toggle-button
|
|
class="${this.exception && this.config.swipe === undefined
|
|
? "inherited"
|
|
: ""}"
|
|
?checked="${this.getConfig("swipe") !== false}"
|
|
.configValue="${"swipe"}"
|
|
@change="${this._valueChanged}"
|
|
title="Toggle Swipe Navigation"
|
|
>
|
|
Swipe Navigation
|
|
</paper-toggle-button>
|
|
${this.config.swipe
|
|
? html`
|
|
<paper-toggle-button
|
|
class="${
|
|
this.exception && this.config.swipe_wrap === undefined
|
|
? "inherited"
|
|
: ""
|
|
}"
|
|
?checked="${this.getConfig("swipe_wrap") !== false}"
|
|
.configValue="${"swipe_wrap"}"
|
|
@change="${this._valueChanged}"
|
|
title="Wrap from first to last tab and vice versa."
|
|
>
|
|
Wrapping
|
|
</paper-toggle-button>
|
|
<paper-toggle-button
|
|
class="${
|
|
this.exception && this.config.swipe_prevent_default === undefined
|
|
? "inherited"
|
|
: ""
|
|
}"
|
|
?checked="${this.getConfig("swipe_prevent_default") !== false}"
|
|
.configValue="${"swipe_prevent_default"}"
|
|
@change="${this._valueChanged}"
|
|
title="Prevent browsers default horizontal swipe action."
|
|
>
|
|
Prevent Default
|
|
</paper-toggle-button>
|
|
<div
|
|
class="${
|
|
this.exception && this.config.swipe_animate === undefined
|
|
? "inherited"
|
|
: ""
|
|
}"
|
|
>
|
|
</div>
|
|
<div class="side-by-side">
|
|
<paper-dropdown-menu
|
|
@value-changed="${this._valueChanged}"
|
|
label="Swipe Animation:"
|
|
.configValue="${"swipe_animate"}"
|
|
>
|
|
<paper-listbox
|
|
slot="dropdown-content"
|
|
.selected="${swipeAnimation.indexOf(
|
|
this.getConfig("swipe_animate")
|
|
)}"
|
|
>
|
|
${swipeAnimation.map(option => {
|
|
return html`
|
|
<paper-item>${option}</paper-item>
|
|
`;
|
|
})}
|
|
</paper-listbox>
|
|
</paper-dropdown-menu>
|
|
</div>
|
|
<paper-input
|
|
class="${
|
|
this.exception && this.config.swipe_amount === undefined
|
|
? "inherited"
|
|
: ""
|
|
}"
|
|
label="Percentage of screen width needed for swipe:"
|
|
.value="${this.getConfig("swipe_amount")}"
|
|
.configValue="${"swipe_amount"}"
|
|
@value-changed="${this._valueChanged}"
|
|
>
|
|
</paper-input>
|
|
</div>
|
|
<paper-input
|
|
class="${
|
|
this.exception && this.config.swipe_skip === undefined
|
|
? "inherited"
|
|
: ""
|
|
}"
|
|
label="Comma-separated list of tabs to skip over on swipe:"
|
|
.value="${this.getConfig("swipe_skip")}"
|
|
.configValue="${"swipe_skip"}"
|
|
@value-changed="${this._valueChanged}"
|
|
>
|
|
</paper-input>
|
|
</div>
|
|
`
|
|
: ""}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
_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`
|
|
<style>
|
|
h3,
|
|
h4 {
|
|
font-size: 16pt;
|
|
margin-bottom: 5px;
|
|
width: 100%;
|
|
}
|
|
paper-toggle-button {
|
|
padding-top: 16px;
|
|
}
|
|
iron-icon {
|
|
padding-right: 5px;
|
|
}
|
|
iron-input {
|
|
max-width: 115px;
|
|
}
|
|
.inherited {
|
|
opacity: 0.4;
|
|
}
|
|
.inherited:hover {
|
|
opacity: 1;
|
|
}
|
|
.side-by-side {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
}
|
|
.side-by-side > * {
|
|
flex: 1;
|
|
padding-right: 4px;
|
|
flex-basis: 33%;
|
|
}
|
|
.buttons > div {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
.buttons > div paper-dropdown-menu {
|
|
flex-grow: 1;
|
|
}
|
|
.buttons > div iron-icon {
|
|
padding-right: 15px;
|
|
padding-top: 20px;
|
|
margin-left: -3px;
|
|
}
|
|
.buttons > div:nth-of-type(2n) iron-icon {
|
|
padding-left: 20px;
|
|
}
|
|
.warning {
|
|
background-color: #455a64;
|
|
padding: 10px;
|
|
color: #ffcd4c;
|
|
border-radius: 5px;
|
|
}
|
|
.alert {
|
|
color: #ffcd4c;
|
|
width: 20px;
|
|
margin-top: -6px;
|
|
}
|
|
[closed] {
|
|
overflow: hidden;
|
|
height: 52px;
|
|
}
|
|
paper-card {
|
|
margin-top: 10px;
|
|
width: 100%;
|
|
transition: all 0.5s ease;
|
|
}
|
|
.underline {
|
|
width: 100%;
|
|
background: var(--dark-color);
|
|
color: var(--text-dark-color);
|
|
padding: 5px;
|
|
width: calc(100% + 30px);
|
|
margin-left: calc(0% - 20px);
|
|
}
|
|
</style>
|
|
`;
|
|
}
|
|
}
|
|
|
|
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()}
|
|
<custom-style>
|
|
<style is="custom-style">
|
|
.card-header {
|
|
margin-top: -5px;
|
|
@apply --paper-font-headline;
|
|
}
|
|
.card-header paper-icon-button {
|
|
margin-top: -5px;
|
|
float: right;
|
|
}
|
|
</style>
|
|
</custom-style>
|
|
<paper-card ?closed=${this._closed}>
|
|
<div class="card-content">
|
|
<div class="card-header">
|
|
${Object.values(this.exception.conditions)
|
|
.join(", ")
|
|
.substring(0, 40) || "New Exception"}
|
|
<paper-icon-button
|
|
icon="${this._closed ? "mdi:chevron-down" : "mdi:chevron-up"}"
|
|
@click="${this._toggleCard}"
|
|
>
|
|
</paper-icon-button>
|
|
<paper-icon-button
|
|
?hidden=${this._closed}
|
|
icon="mdi:delete"
|
|
@click="${this._deleteException}"
|
|
>
|
|
</paper-icon-button>
|
|
</div>
|
|
<h4 class="underline">Conditions</h4>
|
|
<cch-conditions-editor
|
|
.conditions="${this.exception.conditions}"
|
|
@cch-conditions-changed="${this._conditionsChanged}"
|
|
>
|
|
</cch-conditions-editor>
|
|
<h4 class="underline">Config</h4>
|
|
<cch-config-editor
|
|
exception
|
|
.defaultConfig="${{ ...defaultConfig, ...this.config }}"
|
|
.config="${this.exception.config}"
|
|
@cch-config-changed="${this._configChanged}"
|
|
>
|
|
</cch-config-editor>
|
|
</div>
|
|
</paper-card>
|
|
`;
|
|
}
|
|
|
|
renderStyle() {
|
|
return html`
|
|
<style>
|
|
h3,
|
|
h4 {
|
|
font-size: 16pt;
|
|
margin-bottom: 5px;
|
|
width: 100%;
|
|
}
|
|
[closed] {
|
|
overflow: hidden;
|
|
height: 52px;
|
|
}
|
|
paper-card {
|
|
margin-top: 10px;
|
|
width: 100%;
|
|
transition: all 0.5s ease;
|
|
}
|
|
.underline {
|
|
width: 100%;
|
|
background: var(--dark-color);
|
|
color: var(--text-dark-color);
|
|
padding: 5px;
|
|
width: calc(100% + 30px);
|
|
margin-left: calc(0% - 20px);
|
|
}
|
|
</style>
|
|
`;
|
|
}
|
|
|
|
_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`
|
|
<paper-input
|
|
label="User (Seperate multiple users with a comma.)"
|
|
.value="${this._user}"
|
|
.configValue="${"user"}"
|
|
@value-changed="${this._valueChanged}"
|
|
>
|
|
</paper-input>
|
|
<paper-input
|
|
label="User Agent"
|
|
.value="${this._user_agent}"
|
|
.configValue="${"user_agent"}"
|
|
@value-changed="${this._valueChanged}"
|
|
>
|
|
</paper-input>
|
|
<paper-input
|
|
label="Media Query"
|
|
.value="${this._media_query}"
|
|
.configValue="${"media_query"}"
|
|
@value-changed="${this._valueChanged}"
|
|
>
|
|
</paper-input>
|
|
<paper-input
|
|
label="Query String"
|
|
.value="${this._query_string}"
|
|
.configValue="${"query_string"}"
|
|
@value-changed="${this._valueChanged}"
|
|
>
|
|
</paper-input>
|
|
`;
|
|
}
|
|
|
|
_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"
|
|
);
|