"method"===e.kind&&e.descriptor&&!("value"in e.descriptor)?{...e,finisher(n){n.createProperty(e.key,i);}}:{kind:"field",key:Symbol(),placement:"own",descriptor:{},originalKey:e.key,initializer(){"function"==typeof e.initializer&&(this[e.key]=e.initializer.call(this));},finisher(n){n.createProperty(e.key,i);}};function e(e){return (n,t)=>void 0!==t?((i,e,n)=>{e.constructor.createProperty(n,i);})(e,n,t):i(e,n)}
+/**
+ * @license
+ * Copyright 2017 Google LLC
+ * SPDX-License-Identifier: BSD-3-Clause
+ */function t(t){return e({...t,state:!0})}
+
/**
* @license
* Copyright 2021 Google LLC
@@ -81,6 +87,142 @@ const loadDevTools = async () => {
await customElements.whenDefined("ha-config-dashboard");
};
+class BrowserModSettingsCard extends s {
+ constructor() {
+ super(...arguments);
+ this._selectedTab = 0;
+ }
+ firstUpdated() {
+ window.browser_mod.addEventListener("browser-mod-config-update", () => this.requestUpdate());
+ }
+ _handleSwitchTab(ev) {
+ this._selectedTab = parseInt(ev.detail.index, 10);
+ }
+ render() {
+ const level = ["browser", "user", "global"][this._selectedTab];
+ return $ `
+
+
+
+
+
+
+
+
+ ${this._render_settings(level)}
+
+
+ `;
+ }
+ _render_settings(level) {
+ const global = window.browser_mod.global_settings;
+ const user = window.browser_mod.user_settings;
+ const browser = window.browser_mod.browser_settings;
+ const current = { global, user, browser }[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 $ `
Overridden by browser setting`;
+ if (level === "global" && user[key] !== undefined)
+ return $ `
Overridden by user setting`;
+ };
+ return $ `
+
+ Kiosk mode
+ Hide sidebar and header
+ Currenty: ${DESC_BOOLEAN(current.kiosk)} ${OVERRIDDEN("kiosk")}
+
+
+ window.browser_mod.set_setting("kiosk", true, level)}
+ >
+ Enable
+
+ window.browser_mod.set_setting("kiosk", false, level)}
+ >
+ Disable
+
+ window.browser_mod.set_setting("kiosk", undefined, level)}
+ >
+ Clear
+
+
+
+
+ Sidebar order
+ Order and visibility of sidebar buttons
+ Currenty: ${DESC_SET_UNSET(current.sidebarPanelOrder)}
+ ${OVERRIDDEN("sidebarPanelOrder")}
+
+
+ {
+ window.browser_mod.set_setting("sidebarPanelOrder", localStorage.getItem("sidebarPanelOrder"), level);
+ window.browser_mod.set_setting("sidebarHiddenPanels", localStorage.getItem("sidebarHiddenPanels"), level);
+ }}
+ >
+ Set
+
+ {
+ window.browser_mod.set_setting("sidebarPanelOrder", undefined, level);
+ window.browser_mod.set_setting("sidebarHiddenPanels", undefined, level);
+ }}
+ >
+ Clear
+
+
+ `;
+ }
+ _render_user() {
+ return $ `
+ User
+
+ Kiosk mode
+ Hide sidebar and header
+ Currenty: Overridden
+
+
+ Set screensaver
+ Set screensaver card
+ Enable
+ Disable
+ Clear
+
+ `;
+ }
+ _render_browser() {
+ return $ `
+ Browser
+
+ Kiosk mode
+ Hide sidebar and header
+ Currenty: Overridden
+
+
+ Set screensaver
+ Set screensaver card
+ Enable
+ Disable
+ Clear
+
+ `;
+ }
+}
+__decorate([
+ e()
+], BrowserModSettingsCard.prototype, "hass", void 0);
+__decorate([
+ t()
+], BrowserModSettingsCard.prototype, "_selectedTab", void 0);
+customElements.define("browser-mod-settings-card", BrowserModSettingsCard);
+
const bmWindow = window;
loadDevTools().then(() => {
class BrowserModPanel extends s {
@@ -348,25 +490,9 @@ loadDevTools().then(() => {
-
-
-
- User sidebar
- Save sidebar as default for current user
- (${this.hass.user.name})
- Save
-
-
- Global sidebar
- Save sidebar as default for all users
- Save
-
-
-
+
`;
diff --git a/custom_components/browser_mod/connection.py b/custom_components/browser_mod/connection.py
index f447fd4..be71359 100644
--- a/custom_components/browser_mod/connection.py
+++ b/custom_components/browser_mod/connection.py
@@ -1,4 +1,5 @@
import logging
+from typing import Any
import voluptuous as vol
from datetime import datetime, timezone
@@ -130,8 +131,30 @@ async def async_setup_connection(hass):
dev = getBrowser(hass, browserID)
dev.update(hass, msg.get("data", {}))
+ @websocket_api.websocket_command(
+ {
+ vol.Required("type"): "browser_mod/settings",
+ vol.Required("key"): str,
+ vol.Optional("value"): vol.Any(int, str, bool, list, object, None),
+ vol.Optional("user"): str,
+ }
+ )
+ @websocket_api.async_response
+ async def handle_settings(hass, connection, msg):
+ store = hass.data[DOMAIN]["store"]
+ if "user" in msg:
+ # Set user setting
+ await store.set_user_settings(
+ msg["user"], **{msg["key"]: msg.get("value", None)}
+ )
+ else:
+ # Set global setting
+ await store.set_global_settings(**{msg["key"]: msg.get("value", None)})
+ pass
+
async_register_command(hass, handle_connect)
async_register_command(hass, handle_register)
async_register_command(hass, handle_unregister)
async_register_command(hass, handle_reregister)
async_register_command(hass, handle_update)
+ async_register_command(hass, handle_settings)
diff --git a/custom_components/browser_mod/store.py b/custom_components/browser_mod/store.py
index 20a5c45..4ceecd5 100644
--- a/custom_components/browser_mod/store.py
+++ b/custom_components/browser_mod/store.py
@@ -10,11 +10,11 @@ _LOGGER = logging.getLogger(__name__)
@attr.s
-class BrowserStoreData:
- last_seen = attr.ib(type=int, default=0)
- enabled = attr.ib(type=bool, default=False)
- camera = attr.ib(type=bool, default=False)
- meta = attr.ib(type=str, default="default")
+class Settings:
+ kiosk = attr.ib(type=bool, default=None)
+ defaultPanel = attr.ib(type=str, default=None)
+ sidebarPanelOrder = attr.ib(type=list, default=None)
+ sidebarHiddenPanels = attr.ib(type=list, default=None)
@classmethod
def from_dict(cls, data):
@@ -24,21 +24,54 @@ class BrowserStoreData:
return attr.asdict(self)
+@attr.s
+class BrowserStoreData:
+ last_seen = attr.ib(type=int, default=0)
+ enabled = attr.ib(type=bool, default=False)
+ camera = attr.ib(type=bool, default=False)
+ settings = attr.ib(type=Settings, factory=Settings)
+ meta = attr.ib(type=str, default="default")
+
+ @classmethod
+ def from_dict(cls, data):
+ settings = Settings.from_dict(data.get("settings", {}))
+ return cls(
+ **(
+ data
+ | {
+ "settings": settings,
+ }
+ )
+ )
+
+ def asdict(self):
+ return attr.asdict(self)
+
+
@attr.s
class ConfigStoreData:
browsers = attr.ib(type=dict[str:BrowserStoreData], factory=dict)
version = attr.ib(type=str, default="2.0")
+ settings = attr.ib(type=Settings, factory=Settings)
+ user_settings = attr.ib(type=dict[str:Settings], factory=dict)
@classmethod
def from_dict(cls, data={}):
browsers = {
- k: BrowserStoreData.from_dict(v) for k, v in data["browsers"].items()
+ k: BrowserStoreData.from_dict(v)
+ for k, v in data.get("browsers", {}).items()
}
+ user_settings = {
+ k: Settings.from_dict(v) for k, v in data.get("user_settings", {}).items()
+ }
+ settings = Settings.from_dict(data.get("settings", {}))
return cls(
**(
data
| {
"browsers": browsers,
+ "settings": settings,
+ "user_settings": user_settings,
}
)
)
@@ -97,3 +130,19 @@ class BrowserModStore:
async def delete_browser(self, browserID):
del self.data.browsers[browserID]
await self.updated()
+
+ def get_user_settings(self, name):
+ return self.data.user_settings.get(name, Settings())
+
+ async def set_user_settings(self, name, **data):
+ settings = self.data.user_settings.get(name, Settings())
+ settings.__dict__.update(data)
+ self.data.user_settings[name] = settings
+ await self.updated()
+
+ def get_global_settings(self):
+ return self.data.settings
+
+ async def set_global_settings(self, **data):
+ self.data.settings.__dict__.update(data)
+ await self.updated()
diff --git a/js/config_panel/helpers.ts b/js/config_panel/helpers.ts
deleted file mode 100644
index d780231..0000000
--- a/js/config_panel/helpers.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-// Loads in ha-config-dashboard which is used to copy styling
-// Also provides ha-settings-row
-export const loadDevTools = async () => {
- if (customElements.get("ha-config-dashboard")) return;
-
- await customElements.whenDefined("partial-panel-resolver");
- const ppResolver = document.createElement("partial-panel-resolver");
- const routes = (ppResolver as any).getRoutes([
- {
- component_name: "config",
- url_path: "a",
- },
- ]);
- await routes?.routes?.a?.load?.();
- await customElements.whenDefined("ha-panel-config");
- const configRouter = document.createElement("ha-panel-config");
- await (configRouter as any)?.routerOptions?.routes?.dashboard?.load?.(); // Load ha-config-dashboard
- await (configRouter as any)?.routerOptions?.routes?.cloud?.load?.(); // Load ha-settings-row
- await customElements.whenDefined("ha-config-dashboard");
-};
diff --git a/js/config_panel/main.ts b/js/config_panel/main.ts
index 7079421..1e64969 100644
--- a/js/config_panel/main.ts
+++ b/js/config_panel/main.ts
@@ -1,6 +1,9 @@
import { LitElement, html, css } from "lit";
import { property } from "lit/decorators.js";
-import { loadDevTools } from "./helpers";
+import { loadDevTools } from "../helpers";
+import { loadHaForm } from "../helpers";
+
+import "./settings-card";
const bmWindow = window as any;
@@ -292,25 +295,9 @@ loadDevTools().then(() => {
-
-
-
- User sidebar
- Save sidebar as default for current user
- (${this.hass.user.name})
- Save
-
-
- Global sidebar
- Save sidebar as default for all users
- Save
-
-
-
+
`;
diff --git a/js/config_panel/settings-card.ts b/js/config_panel/settings-card.ts
new file mode 100644
index 0000000..4efedfc
--- /dev/null
+++ b/js/config_panel/settings-card.ts
@@ -0,0 +1,160 @@
+import { LitElement, html, css } from "lit";
+import { property, state } from "lit/decorators.js";
+
+class BrowserModSettingsCard extends LitElement {
+ @property() hass;
+
+ @state() _selectedTab = 0;
+
+ firstUpdated() {
+ window.browser_mod.addEventListener("browser-mod-config-update", () =>
+ this.requestUpdate()
+ );
+ }
+
+ _handleSwitchTab(ev: CustomEvent) {
+ this._selectedTab = parseInt(ev.detail.index, 10);
+ }
+
+ render() {
+ const level = ["browser", "user", "global"][this._selectedTab];
+ return html`
+
+
+
+
+
+
+
+
+ ${this._render_settings(level)}
+
+
+ `;
+ }
+
+ _render_settings(level) {
+ const global = window.browser_mod.global_settings;
+ const user = window.browser_mod.user_settings;
+ const browser = window.browser_mod.browser_settings;
+ const current = { global, user, browser }[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`
Overridden by browser setting`;
+ if (level === "global" && user[key] !== undefined)
+ return html`
Overridden by user setting`;
+ };
+
+ return html`
+
+ Kiosk mode
+ Hide sidebar and header
+ Currenty: ${DESC_BOOLEAN(current.kiosk)} ${OVERRIDDEN("kiosk")}
+
+
+ window.browser_mod.set_setting("kiosk", true, level)}
+ >
+ Enable
+
+ window.browser_mod.set_setting("kiosk", false, level)}
+ >
+ Disable
+
+
+ window.browser_mod.set_setting("kiosk", undefined, level)}
+ >
+ Clear
+
+
+
+
+ Sidebar order
+ Order and visibility of sidebar buttons
+ Currenty: ${DESC_SET_UNSET(current.sidebarPanelOrder)}
+ ${OVERRIDDEN("sidebarPanelOrder")}
+
+
+ {
+ window.browser_mod.set_setting(
+ "sidebarPanelOrder",
+ localStorage.getItem("sidebarPanelOrder"),
+ level
+ );
+ window.browser_mod.set_setting(
+ "sidebarHiddenPanels",
+ localStorage.getItem("sidebarHiddenPanels"),
+ level
+ );
+ }}
+ >
+ Set
+
+ {
+ window.browser_mod.set_setting(
+ "sidebarPanelOrder",
+ undefined,
+ level
+ );
+ window.browser_mod.set_setting(
+ "sidebarHiddenPanels",
+ undefined,
+ level
+ );
+ }}
+ >
+ Clear
+
+
+ `;
+ }
+
+ _render_user() {
+ return html`
+ User
+
+ Kiosk mode
+ Hide sidebar and header
+ Currenty: Overridden
+
+
+ Set screensaver
+ Set screensaver card
+ Enable
+ Disable
+ Clear
+
+ `;
+ }
+
+ _render_browser() {
+ return html`
+ Browser
+
+ Kiosk mode
+ Hide sidebar and header
+ Currenty: Overridden
+
+
+ Set screensaver
+ Set screensaver card
+ Enable
+ Disable
+ Clear
+
+ `;
+ }
+}
+
+customElements.define("browser-mod-settings-card", BrowserModSettingsCard);
diff --git a/js/helpers.ts b/js/helpers.ts
index 4f0993e..912187d 100644
--- a/js/helpers.ts
+++ b/js/helpers.ts
@@ -85,3 +85,24 @@ export const loadHaForm = async () => {
if (!card) return;
await card.getConfigElement();
};
+
+// Loads in ha-config-dashboard which is used to copy styling
+// Also provides ha-settings-row
+export const loadDevTools = async () => {
+ if (customElements.get("ha-config-dashboard")) return;
+
+ await customElements.whenDefined("partial-panel-resolver");
+ const ppResolver = document.createElement("partial-panel-resolver");
+ const routes = (ppResolver as any).getRoutes([
+ {
+ component_name: "config",
+ url_path: "a",
+ },
+ ]);
+ await routes?.routes?.a?.load?.();
+ await customElements.whenDefined("ha-panel-config");
+ const configRouter = document.createElement("ha-panel-config");
+ await (configRouter as any)?.routerOptions?.routes?.dashboard?.load?.(); // Load ha-config-dashboard
+ await (configRouter as any)?.routerOptions?.routes?.cloud?.load?.(); // Load ha-settings-row
+ await customElements.whenDefined("ha-config-dashboard");
+};
diff --git a/js/plugin/auto-settings.ts b/js/plugin/auto-settings.ts
new file mode 100644
index 0000000..287f731
--- /dev/null
+++ b/js/plugin/auto-settings.ts
@@ -0,0 +1,25 @@
+export const AutoSettingsMixin = (SuperClass) => {
+ return class AutoSettingsMixinClass extends SuperClass {
+ constructor() {
+ super();
+
+ this._auto_settings_setup();
+ }
+
+ async _auto_settings_setup() {
+ await this.connectionPromise;
+
+ const settings = this.settings;
+
+ if (settings.sidebarPanelOrder) {
+ localStorage.setItem("sidebarPanelOrder", settings.sidebarPanelOrder);
+ }
+ if (settings.sidebarHiddenPanels) {
+ localStorage.setItem(
+ "sidebarHiddenPanels",
+ settings.sidebarHiddenPanels
+ );
+ }
+ }
+ };
+};
diff --git a/js/plugin/connection.ts b/js/plugin/connection.ts
index b219ba2..44f81ac 100644
--- a/js/plugin/connection.ts
+++ b/js/plugin/connection.ts
@@ -119,12 +119,66 @@ export const ConnectionMixin = (SuperClass) => {
});
}
- get meta() {
- if (!this.registered) return null;
- return this.browsers[this.browserID].meta;
+ get global_settings() {
+ const settings = {};
+ const global = this._data.settings ?? {};
+ for (const [k, v] of Object.entries(global)) {
+ if (v !== null) settings[k] = v;
+ }
+ return settings;
}
- set meta(value) {
- this._reregister({ meta: value });
+ get user_settings() {
+ const settings = {};
+ const user = this._data.user_settings[this.hass.user.id] ?? {};
+ for (const [k, v] of Object.entries(user)) {
+ if (v !== null) settings[k] = v;
+ }
+ return settings;
+ }
+ get browser_settings() {
+ const settings = {};
+ const browser = this.browsers[this.browserID]?.settings ?? {};
+ for (const [k, v] of Object.entries(browser)) {
+ if (v !== null) settings[k] = v;
+ }
+ return settings;
+ }
+
+ get settings() {
+ return {
+ ...this.global_settings,
+ ...this.user_settings,
+ ...this.browser_settings,
+ };
+ }
+
+ set_setting(key, value, level) {
+ switch (level) {
+ case "global": {
+ this.connection.sendMessage({
+ type: "browser_mod/settings",
+ key,
+ value,
+ });
+ break;
+ }
+ case "user": {
+ const user = this.hass.user.id;
+ this.connection.sendMessage({
+ type: "browser_mod/settings",
+ user,
+ key,
+ value,
+ });
+ break;
+ }
+ case "browser": {
+ const settings = this.browsers[this.browserID]?.settings;
+ settings[key] = value;
+ this._reregister({ settings });
+ break;
+ }
+ }
}
get cameraEnabled() {
diff --git a/js/plugin/main.ts b/js/plugin/main.ts
index 11879a4..1aef3b2 100644
--- a/js/plugin/main.ts
+++ b/js/plugin/main.ts
@@ -14,6 +14,7 @@ import "./popups";
import { PopupMixin } from "./popups";
import pjson from "../../package.json";
import "./popup-card";
+import { AutoSettingsMixin } from "./auto-settings";
/*
TODO:
@@ -48,11 +49,13 @@ import "./popup-card";
x Redesign services to target devices
- frontend editor for popup cards
- also screensavers
- - Tweaks
- - Save sidebar
- - Save sidebar per user
+ - Saved frontend settings
+ X Framework
+ x Save sidebar
- Kiosk mode
- - Kiosk mode per user
+ - Default panel?
+ - Screensaver?
+ - Tweaks
- Favicon templates
- Title templates
- Quickbar tweaks (ctrl+enter)?
@@ -67,7 +70,9 @@ export class BrowserMod extends ServicesMixin(
CameraMixin(
MediaPlayerMixin(
ScreenSaverMixin(
- FullyMixin(RequireInteractMixin(ConnectionMixin(EventTarget)))
+ AutoSettingsMixin(
+ FullyMixin(RequireInteractMixin(ConnectionMixin(EventTarget)))
+ )
)
)
)