Compare commits
	
		
			No commits in common. "fffb0172873d4428b0b058986918007f3beafd5d" and "87039644388108f7bb30f055f69fd38d5e24be07" have entirely different histories.
		
	
	
		
			fffb017287
			...
			8703964438
		
	
		
@ -3,7 +3,6 @@ import logging
 | 
			
		||||
from homeassistant.components.websocket_api import event_message
 | 
			
		||||
from homeassistant.helpers import device_registry, entity_registry
 | 
			
		||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
 | 
			
		||||
from homeassistant.core import callback
 | 
			
		||||
 | 
			
		||||
from .const import DATA_BROWSERS, DOMAIN, DATA_ADDERS
 | 
			
		||||
from .sensor import BrowserSensor
 | 
			
		||||
@ -131,14 +130,11 @@ class BrowserModBrowser:
 | 
			
		||||
            er.async_remove(self.entities["camera"].entity_id)
 | 
			
		||||
            del self.entities["camera"]
 | 
			
		||||
 | 
			
		||||
        hass.create_task(
 | 
			
		||||
            self.send(
 | 
			
		||||
                None, browserEntities={k: v.entity_id for k, v in self.entities.items()}
 | 
			
		||||
            )
 | 
			
		||||
        self.send(
 | 
			
		||||
            None, browserEntities={k: v.entity_id for k, v in self.entities.items()}
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @callback
 | 
			
		||||
    async def send(self, command, **kwargs):
 | 
			
		||||
    def send(self, command, **kwargs):
 | 
			
		||||
        """Send a command to this browser."""
 | 
			
		||||
        if self.connection is None:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -9,8 +9,6 @@ from homeassistant.components.websocket_api import (
 | 
			
		||||
 | 
			
		||||
from homeassistant.components import websocket_api
 | 
			
		||||
 | 
			
		||||
from homeassistant.core import callback
 | 
			
		||||
 | 
			
		||||
from .const import (
 | 
			
		||||
    BROWSER_ID,
 | 
			
		||||
    DATA_STORE,
 | 
			
		||||
@ -42,7 +40,6 @@ async def async_setup_connection(hass):
 | 
			
		||||
        browserID = msg[BROWSER_ID]
 | 
			
		||||
        store = hass.data[DOMAIN][DATA_STORE]
 | 
			
		||||
 | 
			
		||||
        @callback
 | 
			
		||||
        def send_update(data):
 | 
			
		||||
            connection.send_message(event_message(msg["id"], {"result": data}))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@
 | 
			
		||||
  "dependencies": ["panel_custom", "websocket_api", "http", "frontend", "lovelace"],
 | 
			
		||||
  "codeowners": [],
 | 
			
		||||
  "requirements": [],
 | 
			
		||||
  "version": "2.0.0b5",
 | 
			
		||||
  "version": "2.0.0b4",
 | 
			
		||||
  "iot_class": "local_push",
 | 
			
		||||
  "config_flow": true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ async def async_setup_services(hass):
 | 
			
		||||
            if target not in browsers:
 | 
			
		||||
                continue
 | 
			
		||||
            browser = browsers[target]
 | 
			
		||||
            hass.create_task(browser.send(service, **data))
 | 
			
		||||
            browser.send(service, **data)
 | 
			
		||||
 | 
			
		||||
    def handle_service(call):
 | 
			
		||||
        service = call.service
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,6 @@ class SettingsStoreData:
 | 
			
		||||
    defaultPanel = attr.ib(type=str, default=None)
 | 
			
		||||
    sidebarPanelOrder = attr.ib(type=list, default=None)
 | 
			
		||||
    sidebarHiddenPanels = attr.ib(type=list, default=None)
 | 
			
		||||
    sidebarTitle = attr.ib(type=str, default=None)
 | 
			
		||||
    faviconTemplate = attr.ib(type=str, default=None)
 | 
			
		||||
    titleTemplate = attr.ib(type=str, default=None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,294 +0,0 @@
 | 
			
		||||
import { LitElement, html, css } from "lit";
 | 
			
		||||
import { property } from "lit/decorators.js";
 | 
			
		||||
import { selectTree } from "../helpers";
 | 
			
		||||
 | 
			
		||||
class BrowserModSettingsTable extends LitElement {
 | 
			
		||||
  @property() settingKey;
 | 
			
		||||
  @property() settingSelector = {
 | 
			
		||||
    template: {},
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  @property() hass;
 | 
			
		||||
  @property() default;
 | 
			
		||||
 | 
			
		||||
  @property() tableData = [];
 | 
			
		||||
 | 
			
		||||
  _users = undefined;
 | 
			
		||||
 | 
			
		||||
  firstUpdated() {
 | 
			
		||||
    window.browser_mod.addEventListener("browser-mod-config-update", () =>
 | 
			
		||||
      this.updateTable()
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updated(changedProperties) {
 | 
			
		||||
    if (changedProperties.has("settingKey")) this.updateTable();
 | 
			
		||||
    if (
 | 
			
		||||
      changedProperties.has("hass") &&
 | 
			
		||||
      changedProperties.get("hass") === undefined
 | 
			
		||||
    )
 | 
			
		||||
      this.updateTable();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async fetchUsers(): Promise<any[]> {
 | 
			
		||||
    if (this._users === undefined)
 | 
			
		||||
      this._users = await this.hass.callWS({ type: "config/auth/list" });
 | 
			
		||||
    return this._users;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearSetting(type, target) {
 | 
			
		||||
    const clearSettingCallback = async () => {
 | 
			
		||||
      if (this.settingKey === "sidebarPanelOrder") {
 | 
			
		||||
        const sideBar: any = await selectTree(
 | 
			
		||||
          document,
 | 
			
		||||
          "home-assistant $ home-assistant-main $ app-drawer-layout app-drawer ha-sidebar"
 | 
			
		||||
        );
 | 
			
		||||
        window.browser_mod.setSetting(type, target, {
 | 
			
		||||
          sidebarHiddenPanels: "[]",
 | 
			
		||||
          sidebarPanelOrder: "[]",
 | 
			
		||||
        });
 | 
			
		||||
        window.browser_mod.setSetting(type, target, {
 | 
			
		||||
          sidebarHiddenPanels: undefined,
 | 
			
		||||
          sidebarPanelOrder: undefined,
 | 
			
		||||
        });
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (this.default)
 | 
			
		||||
        window.browser_mod.setSetting(type, target, {
 | 
			
		||||
          [this.settingKey]: this.default,
 | 
			
		||||
        });
 | 
			
		||||
      window.browser_mod.setSetting(type, target, {
 | 
			
		||||
        [this.settingKey]: undefined,
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
    window.browser_mod?.showPopup(
 | 
			
		||||
      "Are you sure",
 | 
			
		||||
      "Do you wish to clear this setting?",
 | 
			
		||||
      {
 | 
			
		||||
        right_button: "Yes",
 | 
			
		||||
        right_button_action: clearSettingCallback,
 | 
			
		||||
        left_button: "No",
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  changeSetting(type, target) {
 | 
			
		||||
    const changeSettingCallback = async (newValue) => {
 | 
			
		||||
      if (this.settingKey === "sidebarPanelOrder") {
 | 
			
		||||
        const sideBar: any = await selectTree(
 | 
			
		||||
          document,
 | 
			
		||||
          "home-assistant $ home-assistant-main $ app-drawer-layout app-drawer ha-sidebar"
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        window.browser_mod.setSetting(type, target, {
 | 
			
		||||
          sidebarHiddenPanels: JSON.stringify(sideBar._hiddenPanels),
 | 
			
		||||
          sidebarPanelOrder: JSON.stringify(sideBar._panelOrder),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        console.log(sideBar._hiddenPanels, sideBar._panelOrder);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      let value = newValue.value;
 | 
			
		||||
      window.browser_mod.setSetting(type, target, { [this.settingKey]: value });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const settings = window.browser_mod?.getSetting?.(this.settingKey);
 | 
			
		||||
    const def =
 | 
			
		||||
      (type === "global" ? settings.global : settings[type][target]) ??
 | 
			
		||||
      this.default;
 | 
			
		||||
    window.browser_mod?.showPopup(
 | 
			
		||||
      "Change value",
 | 
			
		||||
      (this.settingSelector as any).plaintext ?? [
 | 
			
		||||
        {
 | 
			
		||||
          name: "value",
 | 
			
		||||
          label: (this.settingSelector as any).label ?? "",
 | 
			
		||||
          default: def,
 | 
			
		||||
          selector: this.settingSelector,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      {
 | 
			
		||||
        right_button: "OK",
 | 
			
		||||
        right_button_action: changeSettingCallback,
 | 
			
		||||
        left_button: "Cancel",
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addBrowserSetting() {
 | 
			
		||||
    const settings = window.browser_mod?.getSetting?.(this.settingKey);
 | 
			
		||||
    const allBrowsers = window.browser_mod._data.browsers;
 | 
			
		||||
    const browsers = [];
 | 
			
		||||
    for (const target of Object.keys(allBrowsers)) {
 | 
			
		||||
      if (settings.browser[target] == null) browsers.push(target);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (browsers.length === 0) {
 | 
			
		||||
      window.browser_mod.showPopup(
 | 
			
		||||
        "No browsers to configure",
 | 
			
		||||
        "All registered browsers have already been configured.",
 | 
			
		||||
        { right_button: "OK" }
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    window.browser_mod.showPopup(
 | 
			
		||||
      "Select browser to configure",
 | 
			
		||||
      [
 | 
			
		||||
        {
 | 
			
		||||
          name: "browser",
 | 
			
		||||
          label: "",
 | 
			
		||||
          selector: {
 | 
			
		||||
            select: { options: browsers },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      {
 | 
			
		||||
        right_button: "Next",
 | 
			
		||||
        right_button_action: (value) =>
 | 
			
		||||
          this.changeSetting("browser", value.browser),
 | 
			
		||||
        left_button: "Cancel",
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async addUserSetting() {
 | 
			
		||||
    const settings = window.browser_mod?.getSetting?.(this.settingKey);
 | 
			
		||||
    const allUsers = await this.fetchUsers();
 | 
			
		||||
    const users = [];
 | 
			
		||||
    for (const target of allUsers) {
 | 
			
		||||
      if (target.username && settings.user[target.id] == null)
 | 
			
		||||
        users.push({ label: target.name, value: target.id });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (users.length === 0) {
 | 
			
		||||
      window.browser_mod.showPopup(
 | 
			
		||||
        "No users to configure",
 | 
			
		||||
        "All users have already been configured.",
 | 
			
		||||
        { right_button: "OK" }
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    window.browser_mod.showPopup(
 | 
			
		||||
      "Select user to configure",
 | 
			
		||||
      [
 | 
			
		||||
        {
 | 
			
		||||
          name: "user",
 | 
			
		||||
          label: "",
 | 
			
		||||
          selector: {
 | 
			
		||||
            select: { options: users },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      {
 | 
			
		||||
        right_button: "Next",
 | 
			
		||||
        right_button_action: (value) => this.changeSetting("user", value.user),
 | 
			
		||||
        left_button: "Cancel",
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async updateTable() {
 | 
			
		||||
    if (this.hass === undefined) return;
 | 
			
		||||
    const users = await this.fetchUsers();
 | 
			
		||||
    const settings = window.browser_mod?.getSetting?.(this.settingKey);
 | 
			
		||||
    const data = [];
 | 
			
		||||
    for (const [k, v] of Object.entries(settings.user)) {
 | 
			
		||||
      const user = users.find((usr) => usr.id === k);
 | 
			
		||||
      data.push({
 | 
			
		||||
        name: `User: ${user.name}`,
 | 
			
		||||
        value: String(v),
 | 
			
		||||
        controls: html`
 | 
			
		||||
          <ha-icon-button @click=${() => this.changeSetting("user", k)}>
 | 
			
		||||
            <ha-icon .icon=${"mdi:pencil"} style="display:flex;"></ha-icon>
 | 
			
		||||
          </ha-icon-button>
 | 
			
		||||
          <ha-icon-button @click=${() => this.clearSetting("user", k)}>
 | 
			
		||||
            <ha-icon .icon=${"mdi:delete"} style="display:flex;"></ha-icon>
 | 
			
		||||
          </ha-icon-button>
 | 
			
		||||
        `,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data.push({
 | 
			
		||||
      name: "",
 | 
			
		||||
      value: html`
 | 
			
		||||
        <mwc-button @click=${() => this.addUserSetting()}>
 | 
			
		||||
          <ha-icon .icon=${"mdi:plus"}></ha-icon>
 | 
			
		||||
          Add user setting
 | 
			
		||||
        </mwc-button>
 | 
			
		||||
      `,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    for (const [k, v] of Object.entries(settings.browser)) {
 | 
			
		||||
      data.push({
 | 
			
		||||
        name: `Browser: ${k}`,
 | 
			
		||||
        value: String(v),
 | 
			
		||||
        controls: html`
 | 
			
		||||
          <ha-icon-button @click=${() => this.changeSetting("browser", k)}>
 | 
			
		||||
            <ha-icon .icon=${"mdi:pencil"} style="display:flex;"></ha-icon>
 | 
			
		||||
          </ha-icon-button>
 | 
			
		||||
          <ha-icon-button @click=${() => this.clearSetting("browser", k)}>
 | 
			
		||||
            <ha-icon .icon=${"mdi:delete"} style="display:flex;"></ha-icon>
 | 
			
		||||
          </ha-icon-button>
 | 
			
		||||
        `,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data.push({
 | 
			
		||||
      name: "",
 | 
			
		||||
      value: html`
 | 
			
		||||
        <mwc-button @click=${() => this.addBrowserSetting()}>
 | 
			
		||||
          <ha-icon .icon=${"mdi:plus"}></ha-icon>
 | 
			
		||||
          Add browser setting
 | 
			
		||||
        </mwc-button>
 | 
			
		||||
      `,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    data.push({
 | 
			
		||||
      name: "GLOBAL",
 | 
			
		||||
      value:
 | 
			
		||||
        settings.global != null
 | 
			
		||||
          ? String(settings.global)
 | 
			
		||||
          : html`<span style="color: var(--warning-color);">DEFAULT</span>`,
 | 
			
		||||
      controls: html`
 | 
			
		||||
        <ha-icon-button @click=${() => this.changeSetting("global", null)}>
 | 
			
		||||
          <ha-icon .icon=${"mdi:pencil"} style="display:flex;"></ha-icon>
 | 
			
		||||
        </ha-icon-button>
 | 
			
		||||
        <ha-icon-button @click=${() => this.clearSetting("global", null)}>
 | 
			
		||||
          <ha-icon .icon=${"mdi:delete"} style="display:flex;"></ha-icon>
 | 
			
		||||
        </ha-icon-button>
 | 
			
		||||
      `,
 | 
			
		||||
    });
 | 
			
		||||
    this.tableData = data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const global = window.browser_mod?.global_settings?.[this.settingKey];
 | 
			
		||||
    const columns = {
 | 
			
		||||
      name: {
 | 
			
		||||
        title: "Name",
 | 
			
		||||
        grows: true,
 | 
			
		||||
      },
 | 
			
		||||
      value: {
 | 
			
		||||
        title: "Value",
 | 
			
		||||
        grows: true,
 | 
			
		||||
      },
 | 
			
		||||
      controls: {},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return html`
 | 
			
		||||
      <ha-data-table .columns=${columns} .data=${this.tableData} auto-height>
 | 
			
		||||
      </ha-data-table>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get styles() {
 | 
			
		||||
    return css`
 | 
			
		||||
      :host {
 | 
			
		||||
        display: block;
 | 
			
		||||
      }
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define("browser-mod-settings-table", BrowserModSettingsTable);
 | 
			
		||||
@ -136,7 +136,7 @@ class BrowserModRegisteredBrowsersCard extends LitElement {
 | 
			
		||||
  private _renderInteractionAlert() {
 | 
			
		||||
    return html`
 | 
			
		||||
      <ha-alert title="Interaction requirement">
 | 
			
		||||
        For privacy reasons many browsers require the user to interact with a
 | 
			
		||||
        For security reasons many browsers require the user to interact with a
 | 
			
		||||
        webpage before allowing audio playback or video capture. This may affect
 | 
			
		||||
        the
 | 
			
		||||
        <code>media_player</code> and <code>camera</code> components of Browser
 | 
			
		||||
 | 
			
		||||
@ -1,197 +1,277 @@
 | 
			
		||||
import { LitElement, html, css } from "lit";
 | 
			
		||||
import { property, state } from "lit/decorators.js";
 | 
			
		||||
import { loadDeveloperToolsTemplate, selectTree } from "../helpers";
 | 
			
		||||
 | 
			
		||||
import "./browser-mod-settings-table";
 | 
			
		||||
import { loadDeveloperToolsTemplate } from "../helpers";
 | 
			
		||||
 | 
			
		||||
loadDeveloperToolsTemplate();
 | 
			
		||||
 | 
			
		||||
class BrowserModFrontendSettingsCard extends LitElement {
 | 
			
		||||
  @property() hass;
 | 
			
		||||
 | 
			
		||||
  @state() _dashboards = [];
 | 
			
		||||
 | 
			
		||||
  @state() _editSidebar = false;
 | 
			
		||||
  _savedSidebar = { panelOrder: [], hiddenPanels: [] };
 | 
			
		||||
  @state() _selectedTab = 0;
 | 
			
		||||
 | 
			
		||||
  firstUpdated() {
 | 
			
		||||
    window.browser_mod.addEventListener("browser-mod-config-update", () =>
 | 
			
		||||
      this.requestUpdate()
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updated(changedProperties) {
 | 
			
		||||
    if (
 | 
			
		||||
      changedProperties.has("hass") &&
 | 
			
		||||
      changedProperties.get("hass") === undefined
 | 
			
		||||
    ) {
 | 
			
		||||
      (async () =>
 | 
			
		||||
        (this._dashboards = await this.hass.callWS({
 | 
			
		||||
          type: "lovelace/dashboards/list",
 | 
			
		||||
        })))();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async toggleEditSidebar() {
 | 
			
		||||
    const sideBar: any = await selectTree(
 | 
			
		||||
      document,
 | 
			
		||||
      "home-assistant $ home-assistant-main $ app-drawer-layout app-drawer ha-sidebar"
 | 
			
		||||
    window.browser_mod.addEventListener("browser-mod-favicon-update", () =>
 | 
			
		||||
      this.requestUpdate()
 | 
			
		||||
    );
 | 
			
		||||
    sideBar.editMode = !sideBar.editMode;
 | 
			
		||||
    this._editSidebar = sideBar.editMode;
 | 
			
		||||
    if (this._editSidebar) {
 | 
			
		||||
      this._savedSidebar = {
 | 
			
		||||
        panelOrder: sideBar._panelOrder,
 | 
			
		||||
        hiddenPanels: sideBar._hiddenPanels,
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      sideBar._panelOrder = this._savedSidebar.panelOrder ?? [];
 | 
			
		||||
      sideBar._hiddenPanels = this._savedSidebar.hiddenPanels ?? [];
 | 
			
		||||
      this._savedSidebar = { panelOrder: [], hiddenPanels: [] };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _handleSwitchTab(ev: CustomEvent) {
 | 
			
		||||
    this._selectedTab = parseInt(ev.detail.index, 10);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const db = this._dashboards.map((d) => {
 | 
			
		||||
      return { value: d.url_path, label: d.title };
 | 
			
		||||
    });
 | 
			
		||||
    const dashboardSelector = {
 | 
			
		||||
      select: {
 | 
			
		||||
        options: [{ value: "lovelace", label: "lovelace (default)" }, ...db],
 | 
			
		||||
        custom_value: true,
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
    const level = ["user", "browser", "global"][this._selectedTab];
 | 
			
		||||
    return html`
 | 
			
		||||
      <ha-card header="Frontend Settings" outlined>
 | 
			
		||||
        <div class="card-content">
 | 
			
		||||
          <ha-alert alert-type="warning" title="Please note:">
 | 
			
		||||
            The settings in this section severely change the way the Home
 | 
			
		||||
        <ha-alert alert-type="warning">
 | 
			
		||||
          <p>
 | 
			
		||||
            Please note: The settings in this section severely change the way the Home
 | 
			
		||||
            Assistant frontend works and looks. It is very easy to forget that
 | 
			
		||||
            you made a setting here when you switch devices or user.
 | 
			
		||||
            <p>
 | 
			
		||||
              Do not report any issues to Home Assistant before clearing
 | 
			
		||||
              <b>EVERY</b> setting here and thouroghly clearing all your browser
 | 
			
		||||
              caches. Failure to do so means you risk wasting a lot of peoples
 | 
			
		||||
              time, and you will be severly and rightfully ridiculed.
 | 
			
		||||
            </p>
 | 
			
		||||
          </p>
 | 
			
		||||
          <p>
 | 
			
		||||
            Do not report any issues to Home Assistant before clearing
 | 
			
		||||
            <b>EVERY</b> setting here and thouroghly clearing all your browser
 | 
			
		||||
            caches. Failure to do so means you risk wasting a lot of peoples
 | 
			
		||||
            time, and you will be severly and rightfully ridiculed.
 | 
			
		||||
          </p>
 | 
			
		||||
          </ha-alert>
 | 
			
		||||
          <p>
 | 
			
		||||
            Settings below are applied by first match. I.e. if a matching User
 | 
			
		||||
            setting exists, it will be applied. Otherwise any matching Browser
 | 
			
		||||
            setting and otherwise the GLOBAL setting if that differs from
 | 
			
		||||
            DEFAULT.
 | 
			
		||||
          Global settings are applied for all users and browsers.</br>
 | 
			
		||||
          User settings are applied to the current user and overrides any Global settings.</br>
 | 
			
		||||
          Browser settings are applied for the current browser and overrides any User or Global settings.
 | 
			
		||||
          </p>
 | 
			
		||||
          <mwc-tab-bar
 | 
			
		||||
            .activeIndex=${this._selectedTab}
 | 
			
		||||
            @MDCTabBar:activated=${this._handleSwitchTab}
 | 
			
		||||
          >
 | 
			
		||||
            <mwc-tab .label=${"User (" + this.hass.user.name + ")"}></mwc-tab>
 | 
			
		||||
            <ha-icon .icon=${"mdi:chevron-double-right"}></ha-icon>
 | 
			
		||||
            <mwc-tab .label=${"Browser"}></mwc-tab>
 | 
			
		||||
            <ha-icon .icon=${"mdi:chevron-double-right"}></ha-icon>
 | 
			
		||||
            <mwc-tab .label=${"Global"}></mwc-tab>
 | 
			
		||||
          </mwc-tab-bar>
 | 
			
		||||
 | 
			
		||||
          <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
          <ha-settings-row>
 | 
			
		||||
            <span slot="heading">Title template</span>
 | 
			
		||||
            <span slot="description">
 | 
			
		||||
              Jinja template for the browser window/tab title
 | 
			
		||||
            </span>
 | 
			
		||||
          </ha-settings-row>
 | 
			
		||||
          <browser-mod-settings-table
 | 
			
		||||
            .hass=${this.hass}
 | 
			
		||||
            .settingKey=${"titleTemplate"}
 | 
			
		||||
          ></browser-mod-settings-table>
 | 
			
		||||
 | 
			
		||||
          <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
          <ha-settings-row>
 | 
			
		||||
            <span slot="heading">Favicon template</span>
 | 
			
		||||
            <span slot="description">
 | 
			
		||||
              Jinja template for the browser favicon
 | 
			
		||||
            </span>
 | 
			
		||||
          </ha-settings-row>
 | 
			
		||||
          <browser-mod-settings-table
 | 
			
		||||
            .hass=${this.hass}
 | 
			
		||||
            .settingKey=${"faviconTemplate"}
 | 
			
		||||
          ></browser-mod-settings-table>
 | 
			
		||||
 | 
			
		||||
          <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
          <ha-settings-row>
 | 
			
		||||
            <span slot="heading">Hide sidebar</span>
 | 
			
		||||
            <span slot="description">
 | 
			
		||||
              Completely remove the sidebar from all panels
 | 
			
		||||
            </span>
 | 
			
		||||
          </ha-settings-row>
 | 
			
		||||
          <browser-mod-settings-table
 | 
			
		||||
            .hass=${this.hass}
 | 
			
		||||
            .settingKey=${"hideSidebar"}
 | 
			
		||||
            .settingSelector=${{ boolean: {}, label: "Hide sidebar" }}
 | 
			
		||||
          ></browser-mod-settings-table>
 | 
			
		||||
 | 
			
		||||
          <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
          <ha-settings-row>
 | 
			
		||||
            <span slot="heading">Hide header</span>
 | 
			
		||||
            <span slot="description">
 | 
			
		||||
              Completely remove the header from all panels
 | 
			
		||||
            </span>
 | 
			
		||||
          </ha-settings-row>
 | 
			
		||||
          <browser-mod-settings-table
 | 
			
		||||
            .hass=${this.hass}
 | 
			
		||||
            .settingKey=${"hideHeader"}
 | 
			
		||||
            .settingSelector=${{ boolean: {}, label: "Hide header" }}
 | 
			
		||||
          ></browser-mod-settings-table>
 | 
			
		||||
 | 
			
		||||
          <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
          <ha-settings-row>
 | 
			
		||||
            <span slot="heading">Default dashboard</span>
 | 
			
		||||
            <span slot="description">
 | 
			
		||||
              The dashboard that is showed when navigating to
 | 
			
		||||
              ${location.origin}/
 | 
			
		||||
            </span>
 | 
			
		||||
          </ha-settings-row>
 | 
			
		||||
          <browser-mod-settings-table
 | 
			
		||||
            .hass=${this.hass}
 | 
			
		||||
            .settingKey=${"defaultPanel"}
 | 
			
		||||
            .settingSelector=${dashboardSelector}
 | 
			
		||||
            .default=${"lovelace"}
 | 
			
		||||
          ></browser-mod-settings-table>
 | 
			
		||||
 | 
			
		||||
          <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
          <ha-settings-row>
 | 
			
		||||
            <span slot="heading">Sidebar order</span>
 | 
			
		||||
            <span slot="description">
 | 
			
		||||
              Order and visibility of sidebar items. <br />Click EDIT and set
 | 
			
		||||
              the sidebar up as you want. Then save the settings and finally
 | 
			
		||||
              click RESTORE.
 | 
			
		||||
            </span>
 | 
			
		||||
            <mwc-button @click=${() => this.toggleEditSidebar()}>
 | 
			
		||||
              ${this._editSidebar ? "Restore" : "Edit"}
 | 
			
		||||
            </mwc-button>
 | 
			
		||||
          </ha-settings-row>
 | 
			
		||||
          <browser-mod-settings-table
 | 
			
		||||
            .hass=${this.hass}
 | 
			
		||||
            .settingKey=${"sidebarPanelOrder"}
 | 
			
		||||
            .settingSelector=${{
 | 
			
		||||
              plaintext: "Press OK to store the current sidebar order",
 | 
			
		||||
            }}
 | 
			
		||||
            .default=${"lovelace"}
 | 
			
		||||
          ></browser-mod-settings-table>
 | 
			
		||||
 | 
			
		||||
          <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
          <ha-settings-row>
 | 
			
		||||
            <span slot="heading">Sidebar title</span>
 | 
			
		||||
            <span slot="description">
 | 
			
		||||
              The title at the top of the sidebar
 | 
			
		||||
            </span>
 | 
			
		||||
          </ha-settings-row>
 | 
			
		||||
          <browser-mod-settings-table
 | 
			
		||||
            .hass=${this.hass}
 | 
			
		||||
            .settingKey=${"sidebarTitle"}
 | 
			
		||||
            .settingSelector=${{ text: {} }}
 | 
			
		||||
          ></browser-mod-settings-table>
 | 
			
		||||
          ${this._render_settings(level)}
 | 
			
		||||
        </div>
 | 
			
		||||
      </ha-card>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _render_settings(level) {
 | 
			
		||||
    const global = window.browser_mod.global_settings;
 | 
			
		||||
    const browser = window.browser_mod.browser_settings;
 | 
			
		||||
    const user = window.browser_mod.user_settings;
 | 
			
		||||
    const current = { global, browser, user }[level];
 | 
			
		||||
 | 
			
		||||
    const DESC_BOOLEAN = (val) =>
 | 
			
		||||
      ({ true: "Enabled", false: "Disabled", undefined: "Unset" }[String(val)]);
 | 
			
		||||
    const DESC_SET_UNSET = (val) => (val === undefined ? "Unset" : "Set");
 | 
			
		||||
    const OVERRIDDEN = (key) => {
 | 
			
		||||
      if (level !== "browser" && browser[key] !== undefined)
 | 
			
		||||
        return html`<br />Overridden by browser setting`;
 | 
			
		||||
      if (level === "global" && user[key] !== undefined)
 | 
			
		||||
        return html`<br />Overridden by user setting`;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return html`
 | 
			
		||||
      <div class="box">
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <span slot="heading">Favicon template</span>
 | 
			
		||||
          ${OVERRIDDEN("faviconTemplate")}
 | 
			
		||||
          <img src="${window.browser_mod._currentFavicon}" class="favicon" />
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
        <ha-code-editor
 | 
			
		||||
          .hass=${this.hass}
 | 
			
		||||
          .value=${current.faviconTemplate}
 | 
			
		||||
          @value-changed=${(ev) => {
 | 
			
		||||
            const tpl = ev.detail.value || undefined;
 | 
			
		||||
            window.browser_mod.set_setting("faviconTemplate", tpl, level);
 | 
			
		||||
          }}
 | 
			
		||||
        ></ha-code-editor>
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() =>
 | 
			
		||||
              window.browser_mod.set_setting(
 | 
			
		||||
                "faviconTemplate",
 | 
			
		||||
                undefined,
 | 
			
		||||
                level
 | 
			
		||||
              )}
 | 
			
		||||
          >
 | 
			
		||||
            Clear
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
 | 
			
		||||
        <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <span slot="heading">Title template</span>
 | 
			
		||||
          ${OVERRIDDEN("titleTemplate")}
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
        <ha-code-editor
 | 
			
		||||
          .hass=${this.hass}
 | 
			
		||||
          .value=${current.titleTemplate}
 | 
			
		||||
          @value-changed=${(ev) => {
 | 
			
		||||
            const tpl = ev.detail.value || undefined;
 | 
			
		||||
            window.browser_mod.set_setting("titleTemplate", tpl, level);
 | 
			
		||||
          }}
 | 
			
		||||
        ></ha-code-editor>
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() =>
 | 
			
		||||
              window.browser_mod.set_setting("titleTemplate", undefined, level)}
 | 
			
		||||
          >
 | 
			
		||||
            Clear
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
 | 
			
		||||
        <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <span slot="heading">Hide Sidebar</span>
 | 
			
		||||
          <span slot="description">Hide the sidebar and hamburger menu</span>
 | 
			
		||||
          Currently: ${DESC_BOOLEAN(current.hideSidebar)}
 | 
			
		||||
          ${OVERRIDDEN("hideSidebar")}
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() =>
 | 
			
		||||
              window.browser_mod.set_setting("hideSidebar", true, level)}
 | 
			
		||||
          >
 | 
			
		||||
            Enable
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() =>
 | 
			
		||||
              window.browser_mod.set_setting("hideSidebar", false, level)}
 | 
			
		||||
          >
 | 
			
		||||
            Disable
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() =>
 | 
			
		||||
              window.browser_mod.set_setting("hideSidebar", undefined, level)}
 | 
			
		||||
          >
 | 
			
		||||
            Clear
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
 | 
			
		||||
        <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <span slot="heading">Hide Header</span>
 | 
			
		||||
          <span slot="description">Hide the header on all pages</span>
 | 
			
		||||
          Currently: ${DESC_BOOLEAN(current.hideHeader)}
 | 
			
		||||
          ${OVERRIDDEN("hideHeader")}
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() =>
 | 
			
		||||
              window.browser_mod.set_setting("hideHeader", true, level)}
 | 
			
		||||
          >
 | 
			
		||||
            Enable
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() =>
 | 
			
		||||
              window.browser_mod.set_setting("hideHeader", false, level)}
 | 
			
		||||
          >
 | 
			
		||||
            Disable
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() =>
 | 
			
		||||
              window.browser_mod.set_setting("hideHeader", undefined, level)}
 | 
			
		||||
          >
 | 
			
		||||
            Clear
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
 | 
			
		||||
        <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <span slot="heading">Sidebar order</span>
 | 
			
		||||
          <span slot="description">
 | 
			
		||||
            Order and visibility of sidebar buttons
 | 
			
		||||
          </span>
 | 
			
		||||
          Currently: ${DESC_SET_UNSET(current.sidebarPanelOrder)}
 | 
			
		||||
          ${OVERRIDDEN("sidebarPanelOrder")}
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <span slot="description">
 | 
			
		||||
            Clearing this does NOT restore the original default order.
 | 
			
		||||
          </span>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() => {
 | 
			
		||||
              window.browser_mod.set_setting(
 | 
			
		||||
                "sidebarPanelOrder",
 | 
			
		||||
                localStorage.getItem("sidebarPanelOrder"),
 | 
			
		||||
                level
 | 
			
		||||
              );
 | 
			
		||||
              window.browser_mod.set_setting(
 | 
			
		||||
                "sidebarHiddenPanels",
 | 
			
		||||
                localStorage.getItem("sidebarHiddenPanels"),
 | 
			
		||||
                level
 | 
			
		||||
              );
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            Set
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() => {
 | 
			
		||||
              window.browser_mod.set_setting(
 | 
			
		||||
                "sidebarPanelOrder",
 | 
			
		||||
                undefined,
 | 
			
		||||
                level
 | 
			
		||||
              );
 | 
			
		||||
              window.browser_mod.set_setting(
 | 
			
		||||
                "sidebarHiddenPanels",
 | 
			
		||||
                undefined,
 | 
			
		||||
                level
 | 
			
		||||
              );
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            Clear
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
 | 
			
		||||
        <div class="separator"></div>
 | 
			
		||||
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <span slot="heading">Default dashboard</span>
 | 
			
		||||
          <span slot="description"
 | 
			
		||||
            >The dashboard that's displayed by default</span
 | 
			
		||||
          >
 | 
			
		||||
          Currently: ${DESC_SET_UNSET(current.defaultPanel)}
 | 
			
		||||
          ${OVERRIDDEN("defaultPanel")}
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
        <ha-settings-row>
 | 
			
		||||
          <span slot="description">
 | 
			
		||||
            Clearing this does NOT restore the original default dashboard.
 | 
			
		||||
          </span>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() => {
 | 
			
		||||
              window.browser_mod.set_setting(
 | 
			
		||||
                "defaultPanel",
 | 
			
		||||
                localStorage.getItem("defaultPanel"),
 | 
			
		||||
                level
 | 
			
		||||
              );
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            Set
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
          <mwc-button
 | 
			
		||||
            @click=${() => {
 | 
			
		||||
              window.browser_mod.set_setting("defaultPanel", undefined, level);
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            Clear
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
        </ha-settings-row>
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get styles() {
 | 
			
		||||
    return css`
 | 
			
		||||
      .box {
 | 
			
		||||
@ -200,7 +280,7 @@ class BrowserModFrontendSettingsCard extends LitElement {
 | 
			
		||||
      }
 | 
			
		||||
      .separator {
 | 
			
		||||
        border-bottom: 1px solid var(--divider-color);
 | 
			
		||||
        margin: 16px -16px 0px;
 | 
			
		||||
        margin: 0 -8px;
 | 
			
		||||
      }
 | 
			
		||||
      img.favicon {
 | 
			
		||||
        width: 64px;
 | 
			
		||||
 | 
			
		||||
@ -15,13 +15,12 @@ loadConfigDashboard().then(() => {
 | 
			
		||||
    @property() connection;
 | 
			
		||||
 | 
			
		||||
    firstUpdated() {
 | 
			
		||||
      window.addEventListener("browser-mod-config-update", () =>
 | 
			
		||||
      window.browser_mod.addEventListener("browser-mod-config-update", () =>
 | 
			
		||||
        this.requestUpdate()
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
      if (!window.browser_mod) return html``;
 | 
			
		||||
      return html`
 | 
			
		||||
        <ha-app-layout>
 | 
			
		||||
          <app-header slot="header" fixed>
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,9 @@
 | 
			
		||||
const TIMEOUT_ERROR = "SELECTTREE-TIMEOUT";
 | 
			
		||||
 | 
			
		||||
export async function await_element(el, hard = false) {
 | 
			
		||||
async function _await_el(el) {
 | 
			
		||||
  if (el.localName?.includes("-"))
 | 
			
		||||
    await customElements.whenDefined(el.localName);
 | 
			
		||||
  if (el.updateComplete) await el.updateComplete;
 | 
			
		||||
  if (hard) {
 | 
			
		||||
    if (el.pageRendered) await el.pageRendered;
 | 
			
		||||
    if (el._panelState) {
 | 
			
		||||
      let rounds = 0;
 | 
			
		||||
      while (el._panelState !== "loaded" && rounds++ < 5)
 | 
			
		||||
        await new Promise((r) => setTimeout(r, 100));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function _selectTree(root, path, all = false) {
 | 
			
		||||
@ -26,7 +18,7 @@ async function _selectTree(root, path, all = false) {
 | 
			
		||||
 | 
			
		||||
    if (!p.trim().length) continue;
 | 
			
		||||
 | 
			
		||||
    await_element(e);
 | 
			
		||||
    _await_el(e);
 | 
			
		||||
    el = p === "$" ? [e.shadowRoot] : e.querySelectorAll(p);
 | 
			
		||||
  }
 | 
			
		||||
  return all ? el : el[0];
 | 
			
		||||
@ -110,7 +102,6 @@ export const loadConfigDashboard = async () => {
 | 
			
		||||
  const configRouter: any = document.createElement("ha-panel-config");
 | 
			
		||||
  await configRouter?.routerOptions?.routes?.dashboard?.load?.(); // Load ha-config-dashboard
 | 
			
		||||
  await configRouter?.routerOptions?.routes?.cloud?.load?.(); // Load ha-settings-row
 | 
			
		||||
  await configRouter?.routerOptions?.routes?.entities?.load?.(); // Load ha-data-table
 | 
			
		||||
  await customElements.whenDefined("ha-config-dashboard");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -141,32 +132,3 @@ export function throttle(timeout) {
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function runOnce(restart = false) {
 | 
			
		||||
  return function (target, propertyKey, descriptor) {
 | 
			
		||||
    const fn = descriptor.value;
 | 
			
		||||
    let running = undefined;
 | 
			
		||||
    const newfn = function (...rest) {
 | 
			
		||||
      if (restart && running === false) running = true;
 | 
			
		||||
      if (running !== undefined) return;
 | 
			
		||||
      running = false;
 | 
			
		||||
 | 
			
		||||
      const retval = fn.bind(this)(...rest);
 | 
			
		||||
      if (running) {
 | 
			
		||||
        running = undefined;
 | 
			
		||||
        return newfn.bind(this)(...rest);
 | 
			
		||||
      } else {
 | 
			
		||||
        running = undefined;
 | 
			
		||||
        return retval;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
    descriptor.value = newfn;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function waitRepeat(fn, times, delay) {
 | 
			
		||||
  while (times--) {
 | 
			
		||||
    fn();
 | 
			
		||||
    await new Promise((r) => setTimeout(r, delay));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ export const ConnectionMixin = (SuperClass) => {
 | 
			
		||||
    public browserEntities = {};
 | 
			
		||||
 | 
			
		||||
    LOG(...args) {
 | 
			
		||||
      if (window.browser_mod_log === undefined) return;
 | 
			
		||||
      return;
 | 
			
		||||
      const dt = new Date();
 | 
			
		||||
      console.log(`${dt.toLocaleTimeString()}`, ...args);
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ export const ConnectionMixin = (SuperClass) => {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fireEvent(event, detail = undefined) {
 | 
			
		||||
      this.dispatchEvent(new CustomEvent(event, { detail, bubbles: true }));
 | 
			
		||||
      this.dispatchEvent(new CustomEvent(event, { detail }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private incoming_message(msg) {
 | 
			
		||||
@ -37,7 +37,6 @@ export const ConnectionMixin = (SuperClass) => {
 | 
			
		||||
        this.update_config(msg.result);
 | 
			
		||||
      }
 | 
			
		||||
      this._connectionResolve?.();
 | 
			
		||||
      this._connectionResolve = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private update_config(cfg) {
 | 
			
		||||
 | 
			
		||||
@ -1,37 +1,25 @@
 | 
			
		||||
import { await_element, waitRepeat, runOnce, selectTree } from "../helpers";
 | 
			
		||||
import { selectTree } from "../helpers";
 | 
			
		||||
 | 
			
		||||
export const AutoSettingsMixin = (SuperClass) => {
 | 
			
		||||
  class AutoSettingsMixinClass extends SuperClass {
 | 
			
		||||
  return class AutoSettingsMixinClass extends SuperClass {
 | 
			
		||||
    _faviconTemplateSubscription;
 | 
			
		||||
    _titleTemplateSubscription;
 | 
			
		||||
    __currentTitle = undefined;
 | 
			
		||||
 | 
			
		||||
    @runOnce()
 | 
			
		||||
    async runHideHeader() {
 | 
			
		||||
      while (!(await this._hideHeader()))
 | 
			
		||||
        await new Promise((r) => setTimeout(r, 500));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @runOnce(true)
 | 
			
		||||
    async runUpdateTitle() {
 | 
			
		||||
      await waitRepeat(() => this._updateTitle(), 3, 500);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
      super();
 | 
			
		||||
 | 
			
		||||
      const runUpdates = async () => {
 | 
			
		||||
        this.runUpdateTitle();
 | 
			
		||||
        this.runHideHeader();
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      this._auto_settings_setup();
 | 
			
		||||
      this.addEventListener("browser-mod-config-update", () => {
 | 
			
		||||
        this._auto_settings_setup();
 | 
			
		||||
        runUpdates();
 | 
			
		||||
      });
 | 
			
		||||
      this.addEventListener("browser-mod-config-update", () =>
 | 
			
		||||
        this._auto_settings_setup()
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      window.addEventListener("location-changed", runUpdates);
 | 
			
		||||
      window.addEventListener("location-changed", () => {
 | 
			
		||||
        this._updateTitle();
 | 
			
		||||
        setTimeout(() => this._updateTitle(), 500);
 | 
			
		||||
        setTimeout(() => this._updateTitle(), 1000);
 | 
			
		||||
        setTimeout(() => this._updateTitle(), 5000);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _auto_settings_setup() {
 | 
			
		||||
@ -52,7 +40,7 @@ export const AutoSettingsMixin = (SuperClass) => {
 | 
			
		||||
 | 
			
		||||
      // Default panel
 | 
			
		||||
      if (settings.defaultPanel) {
 | 
			
		||||
        localStorage.setItem("defaultPanel", `"${settings.defaultPanel}"`);
 | 
			
		||||
        localStorage.setItem("defaultPanel", settings.defaultPanel);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Hide sidebar
 | 
			
		||||
@ -67,18 +55,18 @@ export const AutoSettingsMixin = (SuperClass) => {
 | 
			
		||||
        ).then((el) => el?.remove?.());
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Sidebar title
 | 
			
		||||
      if (settings.sidebarTitle) {
 | 
			
		||||
        selectTree(
 | 
			
		||||
          document,
 | 
			
		||||
          "home-assistant $ home-assistant-main $ app-drawer-layout app-drawer ha-sidebar $ .title"
 | 
			
		||||
        ).then((el) => {
 | 
			
		||||
          if (el) (el as HTMLElement).innerHTML = settings.sidebarTitle;
 | 
			
		||||
      // Hide header
 | 
			
		||||
      if (settings.hideHeader === true) {
 | 
			
		||||
        customElements.whenDefined("app-header-layout").then(() => {
 | 
			
		||||
          const appHeader = customElements.get("app-header").prototype;
 | 
			
		||||
          const _attached = appHeader.attached;
 | 
			
		||||
          appHeader.attached = function () {
 | 
			
		||||
            _attached.bind(this)();
 | 
			
		||||
            this.style.setProperty("display", "none");
 | 
			
		||||
          };
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Hide header
 | 
			
		||||
 | 
			
		||||
      // Favicon template
 | 
			
		||||
      if (settings.faviconTemplate !== undefined) {
 | 
			
		||||
        (async () => {
 | 
			
		||||
@ -123,6 +111,7 @@ export const AutoSettingsMixin = (SuperClass) => {
 | 
			
		||||
    _updateFavicon({ result }) {
 | 
			
		||||
      const link: any = document.head.querySelector("link[rel~='icon']");
 | 
			
		||||
      link.href = result;
 | 
			
		||||
      window.browser_mod.fireEvent("browser-mod-favicon-update");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get _currentTitle() {
 | 
			
		||||
@ -132,77 +121,7 @@ export const AutoSettingsMixin = (SuperClass) => {
 | 
			
		||||
    _updateTitle(data = undefined) {
 | 
			
		||||
      if (data) this.__currentTitle = data.result;
 | 
			
		||||
      if (this.__currentTitle) document.title = this.__currentTitle;
 | 
			
		||||
      window.browser_mod.fireEvent("browser-mod-favicon-update");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _hideHeader() {
 | 
			
		||||
      if (this.settings.hideHeader !== true) return true;
 | 
			
		||||
      let el = await selectTree(
 | 
			
		||||
        document,
 | 
			
		||||
        "home-assistant $ home-assistant-main $ app-drawer-layout partial-panel-resolver"
 | 
			
		||||
      );
 | 
			
		||||
      if (!el) return false;
 | 
			
		||||
      let steps = 0;
 | 
			
		||||
      while (el && el.localName !== "ha-app-layout" && steps++ < 5) {
 | 
			
		||||
        await await_element(el, true);
 | 
			
		||||
        const next =
 | 
			
		||||
          el.querySelector("ha-app-layout") ??
 | 
			
		||||
          el.firstElementChild ??
 | 
			
		||||
          el.shadowRoot;
 | 
			
		||||
        el = next;
 | 
			
		||||
      }
 | 
			
		||||
      if (el?.localName !== "ha-app-layout") return false;
 | 
			
		||||
      if (el.header) {
 | 
			
		||||
        el.header.style.setProperty("display", "none");
 | 
			
		||||
        setTimeout(() => el._updateLayoutStates(), 0);
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getSetting(key) {
 | 
			
		||||
      const retval = { global: undefined, browser: {}, user: {} };
 | 
			
		||||
      retval.global = this._data.settings?.[key];
 | 
			
		||||
      for (const [k, v] of Object.entries(this._data.browsers ?? {})) {
 | 
			
		||||
        if ((v as any).settings?.[key] != null)
 | 
			
		||||
          retval.browser[k] = (v as any).settings[key];
 | 
			
		||||
      }
 | 
			
		||||
      for (const [k, v] of Object.entries(this._data.user_settings ?? {})) {
 | 
			
		||||
        if (v[key] != null) retval.user[k] = v[key];
 | 
			
		||||
      }
 | 
			
		||||
      return retval;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setSetting(type, target, settings) {
 | 
			
		||||
      if (type === "global") {
 | 
			
		||||
        for (const [key, value] of Object.entries(settings))
 | 
			
		||||
          this.connection.sendMessage({
 | 
			
		||||
            type: "browser_mod/settings",
 | 
			
		||||
            key,
 | 
			
		||||
            value,
 | 
			
		||||
          });
 | 
			
		||||
      } else if (type === "browser") {
 | 
			
		||||
        const browser = this._data.browsers[target];
 | 
			
		||||
        const newsettings = { ...browser.settings, ...settings };
 | 
			
		||||
        console.log(newsettings);
 | 
			
		||||
        this.connection.sendMessage({
 | 
			
		||||
          type: "browser_mod/register",
 | 
			
		||||
          browserID: target,
 | 
			
		||||
          data: {
 | 
			
		||||
            ...browser,
 | 
			
		||||
            settings: newsettings,
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
      } else if (type === "user") {
 | 
			
		||||
        const user = target;
 | 
			
		||||
        for (const [key, value] of Object.entries(settings))
 | 
			
		||||
          this.connection.sendMessage({
 | 
			
		||||
            type: "browser_mod/settings",
 | 
			
		||||
            user,
 | 
			
		||||
            key,
 | 
			
		||||
            value,
 | 
			
		||||
          });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return AutoSettingsMixinClass;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -223,6 +223,7 @@ class BrowserModPopup extends LitElement {
 | 
			
		||||
  static get styles() {
 | 
			
		||||
    return css`
 | 
			
		||||
      ha-dialog {
 | 
			
		||||
        --dialog-backdrop-filter: blur(5px);
 | 
			
		||||
        z-index: 10;
 | 
			
		||||
        --mdc-dialog-min-width: var(--popup-min-width, 400px);
 | 
			
		||||
        --mdc-dialog-max-width: var(--popup-max-width, 600px);
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,6 @@ interface FullyKiosk {
 | 
			
		||||
declare global {
 | 
			
		||||
  interface Window {
 | 
			
		||||
    browser_mod?: BrowserMod;
 | 
			
		||||
    browser_mod_log?: any;
 | 
			
		||||
    fully?: FullyKiosk;
 | 
			
		||||
    hassConnection?: Promise<any>;
 | 
			
		||||
    customCards?: [{}?];
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "browser_mod",
 | 
			
		||||
  "version": "2.0.0b4",
 | 
			
		||||
  "version": "2.0.0b2",
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "browser_mod",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "version": "2.0.0b5",
 | 
			
		||||
  "version": "2.0.0b4",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "build": "rollup -c",
 | 
			
		||||
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
- id: "1660669793583"
 | 
			
		||||
  alias: Toggle bed light
 | 
			
		||||
  description: ""
 | 
			
		||||
  trigger:
 | 
			
		||||
    - platform: time_pattern
 | 
			
		||||
      seconds: /3
 | 
			
		||||
  condition: []
 | 
			
		||||
  action:
 | 
			
		||||
    - type: toggle
 | 
			
		||||
      device_id: 98861bdf58b3c79183c03be06da14f27
 | 
			
		||||
      entity_id: light.bed_light
 | 
			
		||||
      domain: light
 | 
			
		||||
  mode: single
 | 
			
		||||
 | 
			
		||||
- alias: Popup when kitchen light togggled
 | 
			
		||||
  trigger:
 | 
			
		||||
    - platform: state
 | 
			
		||||
      entity_id: light.kitchen_lights
 | 
			
		||||
  action:
 | 
			
		||||
    - service: browser_mod.popup
 | 
			
		||||
      data:
 | 
			
		||||
        title: automation
 | 
			
		||||
        content:
 | 
			
		||||
          type: markdown
 | 
			
		||||
          content: "{%raw%}{{states('light.bed_light')}}{%endraw%}"
 | 
			
		||||
@ -1,7 +1,5 @@
 | 
			
		||||
default_config:
 | 
			
		||||
 | 
			
		||||
automation: !include test/automations.yaml
 | 
			
		||||
 | 
			
		||||
demo:
 | 
			
		||||
 | 
			
		||||
http:
 | 
			
		||||
@ -14,8 +12,6 @@ logger:
 | 
			
		||||
  logs:
 | 
			
		||||
    custom_components.browser_mod: info
 | 
			
		||||
 | 
			
		||||
# debugpy:
 | 
			
		||||
 | 
			
		||||
# browser_mod:
 | 
			
		||||
#   devices:
 | 
			
		||||
#     camdevice:
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,6 @@ views:
 | 
			
		||||
          action: more-info
 | 
			
		||||
 | 
			
		||||
  - !include views/popup.yaml
 | 
			
		||||
  - !include views/frontend-backend.yaml
 | 
			
		||||
 | 
			
		||||
  - title: Popup card
 | 
			
		||||
    popup_cards:
 | 
			
		||||
 | 
			
		||||
@ -1,30 +0,0 @@
 | 
			
		||||
title: frontend vs backend
 | 
			
		||||
 | 
			
		||||
cards:
 | 
			
		||||
  - type: entities
 | 
			
		||||
    entities:
 | 
			
		||||
      - light.bed_light
 | 
			
		||||
      - light.kitchen_lights
 | 
			
		||||
 | 
			
		||||
  - type: button
 | 
			
		||||
    name: fire-dom-event
 | 
			
		||||
    tap_action:
 | 
			
		||||
      action: fire-dom-event
 | 
			
		||||
      browser_mod:
 | 
			
		||||
        service: browser_mod.popup
 | 
			
		||||
        data:
 | 
			
		||||
          title: fire-dom-event
 | 
			
		||||
          content:
 | 
			
		||||
            type: markdown
 | 
			
		||||
            content: "{{states('light.bed_light')}}"
 | 
			
		||||
 | 
			
		||||
  - type: button
 | 
			
		||||
    name: call-service
 | 
			
		||||
    tap_action:
 | 
			
		||||
      action: call-service
 | 
			
		||||
      service: browser_mod.popup
 | 
			
		||||
      data:
 | 
			
		||||
        title: call-service
 | 
			
		||||
        content:
 | 
			
		||||
          type: markdown
 | 
			
		||||
          content: "{{states('light.bed_light')}}"
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user