Compare commits
	
		
			4 Commits
		
	
	
		
			5ecf24d501
			...
			1dddaa9bcc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1dddaa9bcc | |||
| b71197d003 | |||
| 947735292a | |||
| ae916a3900 | 
@ -260,6 +260,7 @@ data:
 | 
				
			|||||||
  [left_button_action: <service call>]
 | 
					  [left_button_action: <service call>]
 | 
				
			||||||
  [dismissable: <TRUE/false>]
 | 
					  [dismissable: <TRUE/false>]
 | 
				
			||||||
  [dismiss_action: <service call>]
 | 
					  [dismiss_action: <service call>]
 | 
				
			||||||
 | 
					  [autoclose: <true/FALSE>]
 | 
				
			||||||
  [timeout: <number>]
 | 
					  [timeout: <number>]
 | 
				
			||||||
  [timeout_action: <service call>]
 | 
					  [timeout_action: <service call>]
 | 
				
			||||||
  [style: <string>]
 | 
					  [style: <string>]
 | 
				
			||||||
@ -272,6 +273,7 @@ If `size` is `wide` or `fullscreen` the card will be displayed wider or covering
 | 
				
			|||||||
`right_button` and `left_button` specify the text of two action buttons. \
 | 
					`right_button` and `left_button` specify the text of two action buttons. \
 | 
				
			||||||
When either action button is clicked, the dialog is closed and the service specified as `right_button_action` or `left_button_action` is called. \
 | 
					When either action button is clicked, the dialog is closed and the service specified as `right_button_action` or `left_button_action` is called. \
 | 
				
			||||||
If `dismissable` is false, the dialog cannot be closed by the user without clicking either action button. If it is true and the dialog is dismissed, `dismiss_action` is called. \
 | 
					If `dismissable` is false, the dialog cannot be closed by the user without clicking either action button. If it is true and the dialog is dismissed, `dismiss_action` is called. \
 | 
				
			||||||
 | 
					If `autoclose` is true the dialog will close automatically when the mouse, screen or keyboard is touched, at which point `dismiss_action` will be called. \
 | 
				
			||||||
If `timeout` is specified the dialog will close automatically after `timeout` milliseconds, at which point `timeout_action` will be called. \
 | 
					If `timeout` is specified the dialog will close automatically after `timeout` milliseconds, at which point `timeout_action` will be called. \
 | 
				
			||||||
Finally, `style` lets you specify some CSS styles to apply to the dialog itself (to style a card in the dialog check out [card-mod](https://github.com/thomasloven/lovelace-card-mod))
 | 
					Finally, `style` lets you specify some CSS styles to apply to the dialog itself (to style a card in the dialog check out [card-mod](https://github.com/thomasloven/lovelace-card-mod))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -25,11 +25,8 @@ async def async_setup(hass, config):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
async def async_setup_entry(hass, config_entry):
 | 
					async def async_setup_entry(hass, config_entry):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
 | 
					    for domain in ["sensor", "binary_sensor", "light", "media_player", "camera"]:
 | 
				
			||||||
    await hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor")
 | 
					        await hass.config_entries.async_forward_entry_setup(config_entry, domain)
 | 
				
			||||||
    await hass.config_entries.async_forward_entry_setup(config_entry, "light")
 | 
					 | 
				
			||||||
    await hass.config_entries.async_forward_entry_setup(config_entry, "media_player")
 | 
					 | 
				
			||||||
    await hass.config_entries.async_forward_entry_setup(config_entry, "camera")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await async_setup_connection(hass)
 | 
					    await async_setup_connection(hass)
 | 
				
			||||||
    await async_setup_view(hass)
 | 
					    await async_setup_view(hass)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
from homeassistant.components.binary_sensor import BinarySensorEntity
 | 
					from homeassistant.components.binary_sensor import BinarySensorEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .const import DOMAIN, DATA_ADDERS
 | 
					from .const import DATA_BROWSERS, DOMAIN, DATA_ADDERS
 | 
				
			||||||
from .helpers import BrowserModEntity
 | 
					from .entities import BrowserModEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def async_setup_platform(
 | 
					async def async_setup_platform(
 | 
				
			||||||
@ -22,10 +22,7 @@ class BrowserBinarySensor(BrowserModEntity, BinarySensorEntity):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def is_on(self):
 | 
					    def is_on(self):
 | 
				
			||||||
        data = self._data
 | 
					        return self._data.get(DATA_BROWSERS, {}).get(self.parameter, None)
 | 
				
			||||||
        data = data.get("browser", {})
 | 
					 | 
				
			||||||
        data = data.get(self.parameter, None)
 | 
					 | 
				
			||||||
        return data
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ActivityBinarySensor(BrowserModEntity, BinarySensorEntity):
 | 
					class ActivityBinarySensor(BrowserModEntity, BinarySensorEntity):
 | 
				
			||||||
@ -47,6 +44,4 @@ class ActivityBinarySensor(BrowserModEntity, BinarySensorEntity):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def is_on(self):
 | 
					    def is_on(self):
 | 
				
			||||||
        data = self._data
 | 
					        return self._data.get("activity", False)
 | 
				
			||||||
        data = data.get("activity", False)
 | 
					 | 
				
			||||||
        return data
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -2,9 +2,9 @@ import logging
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from homeassistant.components.websocket_api import event_message
 | 
					from homeassistant.components.websocket_api import event_message
 | 
				
			||||||
from homeassistant.helpers import device_registry, entity_registry
 | 
					from homeassistant.helpers import device_registry, entity_registry
 | 
				
			||||||
 | 
					from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .const import DATA_BROWSERS, DOMAIN, DATA_ADDERS
 | 
					from .const import DATA_BROWSERS, DOMAIN, DATA_ADDERS
 | 
				
			||||||
from .coordinator import Coordinator
 | 
					 | 
				
			||||||
from .sensor import BrowserSensor
 | 
					from .sensor import BrowserSensor
 | 
				
			||||||
from .light import BrowserModLight
 | 
					from .light import BrowserModLight
 | 
				
			||||||
from .binary_sensor import BrowserBinarySensor, ActivityBinarySensor
 | 
					from .binary_sensor import BrowserBinarySensor, ActivityBinarySensor
 | 
				
			||||||
@ -14,11 +14,23 @@ from .camera import BrowserModCamera
 | 
				
			|||||||
_LOGGER = logging.getLogger(__name__)
 | 
					_LOGGER = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Coordinator(DataUpdateCoordinator):
 | 
				
			||||||
 | 
					    def __init__(self, hass, browserID):
 | 
				
			||||||
 | 
					        super().__init__(
 | 
				
			||||||
 | 
					            hass,
 | 
				
			||||||
 | 
					            _LOGGER,
 | 
				
			||||||
 | 
					            name="Browser Mod Coordinator",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.browserID = browserID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BrowserModBrowser:
 | 
					class BrowserModBrowser:
 | 
				
			||||||
    """A Browser_mod browser."""
 | 
					    """A Browser_mod browser.
 | 
				
			||||||
 | 
					    Handles the Home Assistant device corresponding to a registered Browser.
 | 
				
			||||||
 | 
					    Creates and updates entities based on available data.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, hass, browserID):
 | 
					    def __init__(self, hass, browserID):
 | 
				
			||||||
        """ """
 | 
					 | 
				
			||||||
        self.browserID = browserID
 | 
					        self.browserID = browserID
 | 
				
			||||||
        self.coordinator = Coordinator(hass, browserID)
 | 
					        self.coordinator = Coordinator(hass, browserID)
 | 
				
			||||||
        self.entities = {}
 | 
					        self.entities = {}
 | 
				
			||||||
@ -29,11 +41,13 @@ class BrowserModBrowser:
 | 
				
			|||||||
        self.update_entities(hass)
 | 
					        self.update_entities(hass)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update(self, hass, newData):
 | 
					    def update(self, hass, newData):
 | 
				
			||||||
 | 
					        """Update state of all related entities."""
 | 
				
			||||||
        self.data.update(newData)
 | 
					        self.data.update(newData)
 | 
				
			||||||
        self.update_entities(hass)
 | 
					        self.update_entities(hass)
 | 
				
			||||||
        self.coordinator.async_set_updated_data(self.data)
 | 
					        self.coordinator.async_set_updated_data(self.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_settings(self, hass, settings):
 | 
					    def update_settings(self, hass, settings):
 | 
				
			||||||
 | 
					        """Update Browser settings and entities if needed."""
 | 
				
			||||||
        self.settings = settings
 | 
					        self.settings = settings
 | 
				
			||||||
        self.update_entities(hass)
 | 
					        self.update_entities(hass)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,6 +58,7 @@ class BrowserModBrowser:
 | 
				
			|||||||
        browserID = self.browserID
 | 
					        browserID = self.browserID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def _assert_browser_sensor(type, name, *properties):
 | 
					        def _assert_browser_sensor(type, name, *properties):
 | 
				
			||||||
 | 
					            """Create a browser state sensor if it does not already exist"""
 | 
				
			||||||
            if name in self.entities:
 | 
					            if name in self.entities:
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            adder = hass.data[DOMAIN][DATA_ADDERS][type]
 | 
					            adder = hass.data[DOMAIN][DATA_ADDERS][type]
 | 
				
			||||||
@ -58,6 +73,7 @@ class BrowserModBrowser:
 | 
				
			|||||||
        _assert_browser_sensor("sensor", "currentUser", "Browser user")
 | 
					        _assert_browser_sensor("sensor", "currentUser", "Browser user")
 | 
				
			||||||
        _assert_browser_sensor("sensor", "width", "Browser width", "px")
 | 
					        _assert_browser_sensor("sensor", "width", "Browser width", "px")
 | 
				
			||||||
        _assert_browser_sensor("sensor", "height", "Browser height", "px")
 | 
					        _assert_browser_sensor("sensor", "height", "Browser height", "px")
 | 
				
			||||||
 | 
					        # Don't create battery sensor unless battery level is reported
 | 
				
			||||||
        if self.data.get("browser", {}).get("battery_level", None) is not None:
 | 
					        if self.data.get("browser", {}).get("battery_level", None) is not None:
 | 
				
			||||||
            _assert_browser_sensor(
 | 
					            _assert_browser_sensor(
 | 
				
			||||||
                "sensor", "battery_level", "Browser battery", "%", "battery"
 | 
					                "sensor", "battery_level", "Browser battery", "%", "battery"
 | 
				
			||||||
@ -65,6 +81,7 @@ class BrowserModBrowser:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        _assert_browser_sensor("binary_sensor", "darkMode", "Browser dark mode")
 | 
					        _assert_browser_sensor("binary_sensor", "darkMode", "Browser dark mode")
 | 
				
			||||||
        _assert_browser_sensor("binary_sensor", "fullyKiosk", "Browser FullyKiosk")
 | 
					        _assert_browser_sensor("binary_sensor", "fullyKiosk", "Browser FullyKiosk")
 | 
				
			||||||
 | 
					        # Don't create a charging sensor unless charging state is reported
 | 
				
			||||||
        if self.data.get("browser", {}).get("charging", None) is not None:
 | 
					        if self.data.get("browser", {}).get("charging", None) is not None:
 | 
				
			||||||
            _assert_browser_sensor("binary_sensor", "charging", "Browser charging")
 | 
					            _assert_browser_sensor("binary_sensor", "charging", "Browser charging")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -117,7 +134,7 @@ class BrowserModBrowser:
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete(self, hass):
 | 
					    def delete(self, hass):
 | 
				
			||||||
        """Delete browser and associated entities."""
 | 
					        """Delete the device and associated entities."""
 | 
				
			||||||
        dr = device_registry.async_get(hass)
 | 
					        dr = device_registry.async_get(hass)
 | 
				
			||||||
        er = entity_registry.async_get(hass)
 | 
					        er = entity_registry.async_get(hass)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -131,12 +148,15 @@ class BrowserModBrowser:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def connection(self):
 | 
					    def connection(self):
 | 
				
			||||||
 | 
					        """The current websocket connections for this Browser."""
 | 
				
			||||||
        return self._connections
 | 
					        return self._connections
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def open_connection(self, connection, cid):
 | 
					    def open_connection(self, connection, cid):
 | 
				
			||||||
 | 
					        """Add a websocket connection."""
 | 
				
			||||||
        self._connections.append((connection, cid))
 | 
					        self._connections.append((connection, cid))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def close_connection(self, connection):
 | 
					    def close_connection(self, connection):
 | 
				
			||||||
 | 
					        """Close a websocket connection."""
 | 
				
			||||||
        self._connections = list(
 | 
					        self._connections = list(
 | 
				
			||||||
            filter(lambda v: v[0] != connection, self._connections)
 | 
					            filter(lambda v: v[0] != connection, self._connections)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@ -156,7 +176,17 @@ def getBrowser(hass, browserID, *, create=True):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def deleteBrowser(hass, browserID):
 | 
					def deleteBrowser(hass, browserID):
 | 
				
			||||||
 | 
					    """Delete a browser by BrowserID."""
 | 
				
			||||||
    browsers = hass.data[DOMAIN][DATA_BROWSERS]
 | 
					    browsers = hass.data[DOMAIN][DATA_BROWSERS]
 | 
				
			||||||
    if browserID in browsers:
 | 
					    if browserID in browsers:
 | 
				
			||||||
        browsers[browserID].delete(hass)
 | 
					        browsers[browserID].delete(hass)
 | 
				
			||||||
        del browsers[browserID]
 | 
					        del browsers[browserID]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def getBrowserByConnection(hass, connection):
 | 
				
			||||||
 | 
					    """Get the browser that has a given connection open."""
 | 
				
			||||||
 | 
					    browsers = hass.data[DOMAIN][DATA_BROWSERS]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for k, v in browsers.items():
 | 
				
			||||||
 | 
					        if any([c[0] == connection for c in v.connection]):
 | 
				
			||||||
 | 
					            return v
 | 
				
			||||||
 | 
				
			|||||||
@ -427,7 +427,7 @@ const ConnectionMixin = (SuperClass) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        async _reregister(newData = {}) {
 | 
					        async _reregister(newData = {}) {
 | 
				
			||||||
            await this.connection.sendMessage({
 | 
					            await this.connection.sendMessage({
 | 
				
			||||||
                type: "browser_mod/reregister",
 | 
					                type: "browser_mod/register",
 | 
				
			||||||
                browserID: this.browserID,
 | 
					                browserID: this.browserID,
 | 
				
			||||||
                data: Object.assign(Object.assign({}, this.browsers[this.browserID]), newData),
 | 
					                data: Object.assign(Object.assign({}, this.browsers[this.browserID]), newData),
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -519,7 +519,7 @@ const ConnectionMixin = (SuperClass) => {
 | 
				
			|||||||
                ((_b = this.browsers) === null || _b === void 0 ? void 0 : _b[this.browserID]) === undefined) {
 | 
					                ((_b = this.browsers) === null || _b === void 0 ? void 0 : _b[this.browserID]) === undefined) {
 | 
				
			||||||
                (async () => {
 | 
					                (async () => {
 | 
				
			||||||
                    await this.connection.sendMessage({
 | 
					                    await this.connection.sendMessage({
 | 
				
			||||||
                        type: "browser_mod/reregister",
 | 
					                        type: "browser_mod/register",
 | 
				
			||||||
                        browserID: oldID,
 | 
					                        browserID: oldID,
 | 
				
			||||||
                        data: Object.assign(Object.assign({}, this.browsers[oldID]), { browserID: this.browserID }),
 | 
					                        data: Object.assign(Object.assign({}, this.browsers[oldID]), { browserID: this.browserID }),
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
@ -954,6 +954,7 @@ const ServicesMixin = (SuperClass) => {
 | 
				
			|||||||
              [left_button_action: <service call>]
 | 
					              [left_button_action: <service call>]
 | 
				
			||||||
              [dismissable: <TRUE/false>]
 | 
					              [dismissable: <TRUE/false>]
 | 
				
			||||||
              [dismiss_action: <service call>]
 | 
					              [dismiss_action: <service call>]
 | 
				
			||||||
 | 
					              [autoclose: <true/FALSE>]
 | 
				
			||||||
              [timeout: <number>]
 | 
					              [timeout: <number>]
 | 
				
			||||||
              [timeout_action: <service call>]
 | 
					              [timeout_action: <service call>]
 | 
				
			||||||
              [style: <string>]
 | 
					              [style: <string>]
 | 
				
			||||||
@ -1082,19 +1083,22 @@ const ActivityMixin = (SuperClass) => {
 | 
				
			|||||||
            this.activityTriggered = false;
 | 
					            this.activityTriggered = false;
 | 
				
			||||||
            this._activityCooldown = 15000;
 | 
					            this._activityCooldown = 15000;
 | 
				
			||||||
            for (const ev of ["pointerdown", "pointermove", "keydown"]) {
 | 
					            for (const ev of ["pointerdown", "pointermove", "keydown"]) {
 | 
				
			||||||
                window.addEventListener(ev, () => this.activityTrigger());
 | 
					                window.addEventListener(ev, () => this.activityTrigger(true));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            this.addEventListener("fully-update", () => {
 | 
					            this.addEventListener("fully-update", () => {
 | 
				
			||||||
                this.activityTrigger();
 | 
					                this.activityTrigger();
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        activityTrigger() {
 | 
					        activityTrigger(touched = false) {
 | 
				
			||||||
            if (!this.activityTriggered) {
 | 
					            if (!this.activityTriggered) {
 | 
				
			||||||
                this.sendUpdate({
 | 
					                this.sendUpdate({
 | 
				
			||||||
                    activity: true,
 | 
					                    activity: true,
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            this.activityTriggered = true;
 | 
					            this.activityTriggered = true;
 | 
				
			||||||
 | 
					            if (touched) {
 | 
				
			||||||
 | 
					                this.fireEvent("browser-mod-activity");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            clearTimeout(this._activityTimeout);
 | 
					            clearTimeout(this._activityTimeout);
 | 
				
			||||||
            this._activityTimeout = setTimeout(() => this.activityReset(), this._activityCooldown);
 | 
					            this._activityTimeout = setTimeout(() => this.activityReset(), this._activityCooldown);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -1124,18 +1128,18 @@ const t={ATTRIBUTE:1,CHILD:2,PROPERTY:3,BOOLEAN_ATTRIBUTE:4,EVENT:5,ELEMENT:6},e
 | 
				
			|||||||
 */class e extends i{constructor(i){if(super(i),this.it=w,i.type!==t.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===w||null==r)return this.ft=void 0,this.it=r;if(r===b)return r;if("string"!=typeof r)throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this.ft;this.it=r;const s=[r];return s.raw=s,this.ft={_$litType$:this.constructor.resultType,strings:s,values:[]}}}e.directiveName="unsafeHTML",e.resultType=1;const o=e$1(e);
 | 
					 */class e extends i{constructor(i){if(super(i),this.it=w,i.type!==t.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===w||null==r)return this.ft=void 0,this.it=r;if(r===b)return r;if("string"!=typeof r)throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this.ft;this.it=r;const s=[r];return s.raw=s,this.ft={_$litType$:this.constructor.resultType,strings:s,values:[]}}}e.directiveName="unsafeHTML",e.resultType=1;const o=e$1(e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BrowserModPopup extends s {
 | 
					class BrowserModPopup extends s {
 | 
				
			||||||
    closedCallback() {
 | 
					 | 
				
			||||||
        var _a;
 | 
					 | 
				
			||||||
        (_a = this._resolveClosed) === null || _a === void 0 ? void 0 : _a.call(this);
 | 
					 | 
				
			||||||
        this._resolveClosed = undefined;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    async closeDialog() {
 | 
					    async closeDialog() {
 | 
				
			||||||
        this.open = false;
 | 
					        this.open = false;
 | 
				
			||||||
        await new Promise((resolve) => (this._resolveClosed = resolve));
 | 
					 | 
				
			||||||
        clearInterval(this._timeoutTimer);
 | 
					        clearInterval(this._timeoutTimer);
 | 
				
			||||||
 | 
					        if (this._autocloseListener) {
 | 
				
			||||||
 | 
					            window.browser_mod.removeEventListener("browser-mod-activity", this._autocloseListener);
 | 
				
			||||||
 | 
					            this._autocloseListener = undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    openDialog() {
 | 
					    openDialog() {
 | 
				
			||||||
 | 
					        var _a;
 | 
				
			||||||
        this.open = true;
 | 
					        this.open = true;
 | 
				
			||||||
 | 
					        (_a = this.dialog) === null || _a === void 0 ? void 0 : _a.show();
 | 
				
			||||||
        if (this.timeout) {
 | 
					        if (this.timeout) {
 | 
				
			||||||
            this._timeoutStart = new Date().getTime();
 | 
					            this._timeoutStart = new Date().getTime();
 | 
				
			||||||
            this._timeoutTimer = setInterval(() => {
 | 
					            this._timeoutTimer = setInterval(() => {
 | 
				
			||||||
@ -1146,8 +1150,13 @@ class BrowserModPopup extends s {
 | 
				
			|||||||
                    this._timeout();
 | 
					                    this._timeout();
 | 
				
			||||||
            }, 10);
 | 
					            }, 10);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        this._autocloseListener = undefined;
 | 
				
			||||||
 | 
					        if (this._autoclose) {
 | 
				
			||||||
 | 
					            this._autocloseListener = this._dismiss.bind(this);
 | 
				
			||||||
 | 
					            window.browser_mod.addEventListener("browser-mod-activity", this._autocloseListener);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    async setupDialog(title, content, { right_button = undefined, right_button_action = undefined, left_button = undefined, left_button_action = undefined, dismissable = true, dismiss_action = undefined, timeout = undefined, timeout_action = undefined, size = undefined, style = undefined, } = {}) {
 | 
					    }
 | 
				
			||||||
 | 
					    async setupDialog(title, content, { right_button = undefined, right_button_action = undefined, left_button = undefined, left_button_action = undefined, dismissable = true, dismiss_action = undefined, timeout = undefined, timeout_action = undefined, size = undefined, style = undefined, autoclose = false, } = {}) {
 | 
				
			||||||
        this.title = title;
 | 
					        this.title = title;
 | 
				
			||||||
        if (content && typeof content === "object") {
 | 
					        if (content && typeof content === "object") {
 | 
				
			||||||
            // Create a card from config in content
 | 
					            // Create a card from config in content
 | 
				
			||||||
@ -1177,6 +1186,7 @@ class BrowserModPopup extends s {
 | 
				
			|||||||
        this.wide = size === "wide" ? "" : undefined;
 | 
					        this.wide = size === "wide" ? "" : undefined;
 | 
				
			||||||
        this.fullscreen = size === "fullscreen" ? "" : undefined;
 | 
					        this.fullscreen = size === "fullscreen" ? "" : undefined;
 | 
				
			||||||
        this._style = style;
 | 
					        this._style = style;
 | 
				
			||||||
 | 
					        this._autoclose = autoclose;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    async _primary() {
 | 
					    async _primary() {
 | 
				
			||||||
        var _a, _b, _c;
 | 
					        var _a, _b, _c;
 | 
				
			||||||
@ -1210,7 +1220,6 @@ class BrowserModPopup extends s {
 | 
				
			|||||||
        return $ `
 | 
					        return $ `
 | 
				
			||||||
      <ha-dialog
 | 
					      <ha-dialog
 | 
				
			||||||
        open
 | 
					        open
 | 
				
			||||||
        @closed=${this.closedCallback}
 | 
					 | 
				
			||||||
        .heading=${this.title !== undefined}
 | 
					        .heading=${this.title !== undefined}
 | 
				
			||||||
        ?hideActions=${this.actions === undefined}
 | 
					        ?hideActions=${this.actions === undefined}
 | 
				
			||||||
        .scrimClickAction=${this.dismissable ? this._dismiss : ""}
 | 
					        .scrimClickAction=${this.dismissable ? this._dismiss : ""}
 | 
				
			||||||
@ -1399,6 +1408,9 @@ __decorate([
 | 
				
			|||||||
__decorate([
 | 
					__decorate([
 | 
				
			||||||
    e$2()
 | 
					    e$2()
 | 
				
			||||||
], BrowserModPopup.prototype, "_style", void 0);
 | 
					], BrowserModPopup.prototype, "_style", void 0);
 | 
				
			||||||
 | 
					__decorate([
 | 
				
			||||||
 | 
					    i$1("ha-dialog")
 | 
				
			||||||
 | 
					], BrowserModPopup.prototype, "dialog", void 0);
 | 
				
			||||||
if (!customElements.get("browser-mod-popup"))
 | 
					if (!customElements.get("browser-mod-popup"))
 | 
				
			||||||
    customElements.define("browser-mod-popup", BrowserModPopup);
 | 
					    customElements.define("browser-mod-popup", BrowserModPopup);
 | 
				
			||||||
const PopupMixin = (SuperClass) => {
 | 
					const PopupMixin = (SuperClass) => {
 | 
				
			||||||
@ -2062,12 +2074,25 @@ const BrowserIDMixin = (SuperClass) => {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        async recall_id() {
 | 
				
			||||||
 | 
					            // If the connection is still open, but the BrowserID has disappeared - recall it from the backend
 | 
				
			||||||
 | 
					            // This happens e.g. when the frontend cache is reset in the Compainon app
 | 
				
			||||||
 | 
					            if (!this.connection)
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            const recalledID = await this.connection.sendMessagePromise({
 | 
				
			||||||
 | 
					                type: "browser_mod/recall_id",
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            if (recalledID) {
 | 
				
			||||||
 | 
					                localStorage[ID_STORAGE_KEY] = recalledID;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        get browserID() {
 | 
					        get browserID() {
 | 
				
			||||||
            if (document.querySelector("hc-main"))
 | 
					            if (document.querySelector("hc-main"))
 | 
				
			||||||
                return "CAST";
 | 
					                return "CAST";
 | 
				
			||||||
            if (localStorage[ID_STORAGE_KEY])
 | 
					            if (localStorage[ID_STORAGE_KEY])
 | 
				
			||||||
                return localStorage[ID_STORAGE_KEY];
 | 
					                return localStorage[ID_STORAGE_KEY];
 | 
				
			||||||
            this.browserID = "";
 | 
					            this.browserID = "";
 | 
				
			||||||
 | 
					            this.recall_id();
 | 
				
			||||||
            return this.browserID;
 | 
					            return this.browserID;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        set browserID(id) {
 | 
					        set browserID(id) {
 | 
				
			||||||
@ -2112,16 +2137,20 @@ const BrowserIDMixin = (SuperClass) => {
 | 
				
			|||||||
    x ll-custom handling
 | 
					    x ll-custom handling
 | 
				
			||||||
    - Commands
 | 
					    - Commands
 | 
				
			||||||
      x popup
 | 
					      x popup
 | 
				
			||||||
 | 
					        x Auto-close
 | 
				
			||||||
      x close_popup
 | 
					      x close_popup
 | 
				
			||||||
      x more-info
 | 
					      x more-info
 | 
				
			||||||
      x navigate
 | 
					      x navigate
 | 
				
			||||||
      - lovelace-reload?
 | 
					      - lovelace-reload?
 | 
				
			||||||
 | 
					        - Not needed
 | 
				
			||||||
      x window-reload
 | 
					      x window-reload
 | 
				
			||||||
      - screensaver ?
 | 
					      - screensaver ?
 | 
				
			||||||
 | 
					        - Refer to automations instead
 | 
				
			||||||
      x sequence
 | 
					      x sequence
 | 
				
			||||||
      x delay
 | 
					      x delay
 | 
				
			||||||
      x javascript eval
 | 
					      x javascript eval
 | 
				
			||||||
      - toast?
 | 
					      - toast?
 | 
				
			||||||
 | 
					        - Replaced with popups with timeout
 | 
				
			||||||
    x Redesign services to target devices
 | 
					    x Redesign services to target devices
 | 
				
			||||||
  x frontend editor for popup cards
 | 
					  x frontend editor for popup cards
 | 
				
			||||||
    - also screensavers
 | 
					    - also screensavers
 | 
				
			||||||
@ -2139,7 +2168,8 @@ const BrowserIDMixin = (SuperClass) => {
 | 
				
			|||||||
  - Video player?
 | 
					  - Video player?
 | 
				
			||||||
  - Media_seek
 | 
					  - Media_seek
 | 
				
			||||||
  - Screensavers
 | 
					  - Screensavers
 | 
				
			||||||
  - IMPORTANT: FIX DEFAULT HIDING OF ENTITIES
 | 
					  x IMPORTANT: FIX DEFAULT HIDING OF ENTITIES
 | 
				
			||||||
 | 
					    - NOFIX. Home Assistant bug
 | 
				
			||||||
  X Check functionality with CAST - may need to add frontend part as a lovelace resource
 | 
					  X Check functionality with CAST - may need to add frontend part as a lovelace resource
 | 
				
			||||||
  */
 | 
					  */
 | 
				
			||||||
class BrowserMod extends ServicesMixin(PopupMixin(ActivityMixin(BrowserStateMixin(CameraMixin(MediaPlayerMixin(ScreenSaverMixin(AutoSettingsMixin(FullyMixin(RequireInteractMixin(ConnectionMixin(BrowserIDMixin(EventTarget)))))))))))) {
 | 
					class BrowserMod extends ServicesMixin(PopupMixin(ActivityMixin(BrowserStateMixin(CameraMixin(MediaPlayerMixin(ScreenSaverMixin(AutoSettingsMixin(FullyMixin(RequireInteractMixin(ConnectionMixin(BrowserIDMixin(EventTarget)))))))))))) {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import base64
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from homeassistant.components.camera import Camera
 | 
					from homeassistant.components.camera import Camera
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helpers import BrowserModEntity
 | 
					from .entities import BrowserModEntity
 | 
				
			||||||
from .const import DOMAIN, DATA_ADDERS
 | 
					from .const import DOMAIN, DATA_ADDERS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
				
			|||||||
@ -10,15 +10,19 @@ from homeassistant.components.websocket_api import (
 | 
				
			|||||||
from homeassistant.components import websocket_api
 | 
					from homeassistant.components import websocket_api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .const import (
 | 
					from .const import (
 | 
				
			||||||
 | 
					    BROWSER_ID,
 | 
				
			||||||
 | 
					    DATA_STORE,
 | 
				
			||||||
    WS_CONNECT,
 | 
					    WS_CONNECT,
 | 
				
			||||||
 | 
					    WS_LOG,
 | 
				
			||||||
 | 
					    WS_RECALL_ID,
 | 
				
			||||||
    WS_REGISTER,
 | 
					    WS_REGISTER,
 | 
				
			||||||
 | 
					    WS_SETTINGS,
 | 
				
			||||||
    WS_UNREGISTER,
 | 
					    WS_UNREGISTER,
 | 
				
			||||||
    WS_REREGISTER,
 | 
					 | 
				
			||||||
    WS_UPDATE,
 | 
					    WS_UPDATE,
 | 
				
			||||||
    DOMAIN,
 | 
					    DOMAIN,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .browser import getBrowser, deleteBrowser
 | 
					from .browser import getBrowser, deleteBrowser, getBrowserByConnection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_LOGGER = logging.getLogger(__name__)
 | 
					_LOGGER = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -32,44 +36,74 @@ async def async_setup_connection(hass):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    @websocket_api.async_response
 | 
					    @websocket_api.async_response
 | 
				
			||||||
    async def handle_connect(hass, connection, msg):
 | 
					    async def handle_connect(hass, connection, msg):
 | 
				
			||||||
        browserID = msg["browserID"]
 | 
					        """Connect to Browser Mod and subscribe to settings updates."""
 | 
				
			||||||
        store = hass.data[DOMAIN]["store"]
 | 
					        browserID = msg[BROWSER_ID]
 | 
				
			||||||
 | 
					        store = hass.data[DOMAIN][DATA_STORE]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def listener(data):
 | 
					        def send_update(data):
 | 
				
			||||||
            connection.send_message(event_message(msg["id"], {"result": data}))
 | 
					            connection.send_message(event_message(msg["id"], {"result": data}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        store_listener = store.add_listener(listener)
 | 
					        store_listener = store.add_listener(send_update)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def unsubscriber():
 | 
					        def close_connection():
 | 
				
			||||||
            store_listener()
 | 
					            store_listener()
 | 
				
			||||||
            dev = getBrowser(hass, browserID, create=False)
 | 
					            dev = getBrowser(hass, browserID, create=False)
 | 
				
			||||||
            if dev:
 | 
					            if dev:
 | 
				
			||||||
                dev.close_connection(connection)
 | 
					                dev.close_connection(connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        connection.subscriptions[msg["id"]] = unsubscriber
 | 
					        connection.subscriptions[msg["id"]] = close_connection
 | 
				
			||||||
        connection.send_result(msg["id"])
 | 
					        connection.send_result(msg["id"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if store.get_browser(browserID).enabled:
 | 
					        if store.get_browser(browserID).registered:
 | 
				
			||||||
            dev = getBrowser(hass, browserID)
 | 
					            dev = getBrowser(hass, browserID)
 | 
				
			||||||
            dev.update_settings(hass, store.get_browser(browserID).asdict())
 | 
					            dev.update_settings(hass, store.get_browser(browserID).asdict())
 | 
				
			||||||
            dev.open_connection(connection, msg["id"])
 | 
					            dev.open_connection(connection, msg["id"])
 | 
				
			||||||
            await store.set_browser(
 | 
					            await store.set_browser(
 | 
				
			||||||
                browserID, last_seen=datetime.now(tz=timezone.utc).isoformat()
 | 
					                browserID, last_seen=datetime.now(tz=timezone.utc).isoformat()
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        listener(store.asdict())
 | 
					        send_update(store.asdict())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @websocket_api.websocket_command(
 | 
					    @websocket_api.websocket_command(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            vol.Required("type"): WS_REGISTER,
 | 
					            vol.Required("type"): WS_REGISTER,
 | 
				
			||||||
            vol.Required("browserID"): str,
 | 
					            vol.Required("browserID"): str,
 | 
				
			||||||
 | 
					            vol.Optional("data"): dict,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    @websocket_api.async_response
 | 
					    @websocket_api.async_response
 | 
				
			||||||
    async def handle_register(hass, connection, msg):
 | 
					    async def handle_register(hass, connection, msg):
 | 
				
			||||||
        browserID = msg["browserID"]
 | 
					        """Register a Browser."""
 | 
				
			||||||
        store = hass.data[DOMAIN]["store"]
 | 
					        browserID = msg[BROWSER_ID]
 | 
				
			||||||
        await store.set_browser(browserID, enabled=True)
 | 
					        store = hass.data[DOMAIN][DATA_STORE]
 | 
				
			||||||
        connection.send_result(msg["id"])
 | 
					
 | 
				
			||||||
 | 
					        browserSettings = {"registered": True}
 | 
				
			||||||
 | 
					        data = msg.get("data", {})
 | 
				
			||||||
 | 
					        if "last_seen" in data:
 | 
				
			||||||
 | 
					            del data["last_seen"]
 | 
				
			||||||
 | 
					        if BROWSER_ID in data:
 | 
				
			||||||
 | 
					            # Change ID of registered browser
 | 
				
			||||||
 | 
					            newBrowserID = data[BROWSER_ID]
 | 
				
			||||||
 | 
					            del data[BROWSER_ID]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Copy data from old browser and delete it from store
 | 
				
			||||||
 | 
					            if oldBrowserSettings := store.get_browser(browserID):
 | 
				
			||||||
 | 
					                browserSettings = oldBrowserSettings.asdict()
 | 
				
			||||||
 | 
					            await store.delete_browser(browserID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Delete the old Browser device
 | 
				
			||||||
 | 
					            deleteBrowser(hass, browserID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Use the new browserID from now on
 | 
				
			||||||
 | 
					            browserID = newBrowserID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Create and/or update Browser device
 | 
				
			||||||
 | 
					        dev = getBrowser(hass, browserID)
 | 
				
			||||||
 | 
					        dev.update_settings(hass, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Create or update store data
 | 
				
			||||||
 | 
					        if data is not None:
 | 
				
			||||||
 | 
					            browserSettings.update(data)
 | 
				
			||||||
 | 
					        await store.set_browser(browserID, **browserSettings)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @websocket_api.websocket_command(
 | 
					    @websocket_api.websocket_command(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -79,49 +113,15 @@ async def async_setup_connection(hass):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    @websocket_api.async_response
 | 
					    @websocket_api.async_response
 | 
				
			||||||
    async def handle_unregister(hass, connection, msg):
 | 
					    async def handle_unregister(hass, connection, msg):
 | 
				
			||||||
        browserID = msg["browserID"]
 | 
					        """Unregister a Browser."""
 | 
				
			||||||
        store = hass.data[DOMAIN]["store"]
 | 
					        browserID = msg[BROWSER_ID]
 | 
				
			||||||
 | 
					        store = hass.data[DOMAIN][DATA_STORE]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        deleteBrowser(hass, browserID)
 | 
					        deleteBrowser(hass, browserID)
 | 
				
			||||||
        await store.delete_browser(browserID)
 | 
					        await store.delete_browser(browserID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        connection.send_result(msg["id"])
 | 
					        connection.send_result(msg["id"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @websocket_api.websocket_command(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            vol.Required("type"): WS_REREGISTER,
 | 
					 | 
				
			||||||
            vol.Required("browserID"): str,
 | 
					 | 
				
			||||||
            vol.Required("data"): dict,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    @websocket_api.async_response
 | 
					 | 
				
			||||||
    async def handle_reregister(hass, connection, msg):
 | 
					 | 
				
			||||||
        browserID = msg["browserID"]
 | 
					 | 
				
			||||||
        store = hass.data[DOMAIN]["store"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data = msg["data"]
 | 
					 | 
				
			||||||
        del data["last_seen"]
 | 
					 | 
				
			||||||
        browserSettings = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if "browserID" in data:
 | 
					 | 
				
			||||||
            newBrowserID = data["browserID"]
 | 
					 | 
				
			||||||
            del data["browserID"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            oldBrowserSetting = store.get_browser(browserID)
 | 
					 | 
				
			||||||
            if oldBrowserSetting:
 | 
					 | 
				
			||||||
                browserSettings = oldBrowserSetting.asdict()
 | 
					 | 
				
			||||||
            await store.delete_browser(browserID)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            deleteBrowser(hass, browserID)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            browserID = newBrowserID
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (dev := getBrowser(hass, browserID, create=False)) is not None:
 | 
					 | 
				
			||||||
            dev.update_settings(hass, data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        browserSettings.update(data)
 | 
					 | 
				
			||||||
        await store.set_browser(browserID, **browserSettings)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @websocket_api.websocket_command(
 | 
					    @websocket_api.websocket_command(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            vol.Required("type"): WS_UPDATE,
 | 
					            vol.Required("type"): WS_UPDATE,
 | 
				
			||||||
@ -131,16 +131,17 @@ async def async_setup_connection(hass):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    @websocket_api.async_response
 | 
					    @websocket_api.async_response
 | 
				
			||||||
    async def handle_update(hass, connection, msg):
 | 
					    async def handle_update(hass, connection, msg):
 | 
				
			||||||
        browserID = msg["browserID"]
 | 
					        """Receive state updates from a Browser."""
 | 
				
			||||||
        store = hass.data[DOMAIN]["store"]
 | 
					        browserID = msg[BROWSER_ID]
 | 
				
			||||||
 | 
					        store = hass.data[DOMAIN][DATA_STORE]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if store.get_browser(browserID).enabled:
 | 
					        if store.get_browser(browserID).registered:
 | 
				
			||||||
            dev = getBrowser(hass, browserID)
 | 
					            dev = getBrowser(hass, browserID)
 | 
				
			||||||
            dev.update(hass, msg.get("data", {}))
 | 
					            dev.update(hass, msg.get("data", {}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @websocket_api.websocket_command(
 | 
					    @websocket_api.websocket_command(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            vol.Required("type"): "browser_mod/settings",
 | 
					            vol.Required("type"): WS_SETTINGS,
 | 
				
			||||||
            vol.Required("key"): str,
 | 
					            vol.Required("key"): str,
 | 
				
			||||||
            vol.Optional("value"): vol.Any(int, str, bool, list, object, None),
 | 
					            vol.Optional("value"): vol.Any(int, str, bool, list, object, None),
 | 
				
			||||||
            vol.Optional("user"): str,
 | 
					            vol.Optional("user"): str,
 | 
				
			||||||
@ -148,7 +149,8 @@ async def async_setup_connection(hass):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    @websocket_api.async_response
 | 
					    @websocket_api.async_response
 | 
				
			||||||
    async def handle_settings(hass, connection, msg):
 | 
					    async def handle_settings(hass, connection, msg):
 | 
				
			||||||
        store = hass.data[DOMAIN]["store"]
 | 
					        """Change user or global settings."""
 | 
				
			||||||
 | 
					        store = hass.data[DOMAIN][DATA_STORE]
 | 
				
			||||||
        if "user" in msg:
 | 
					        if "user" in msg:
 | 
				
			||||||
            # Set user setting
 | 
					            # Set user setting
 | 
				
			||||||
            await store.set_user_settings(
 | 
					            await store.set_user_settings(
 | 
				
			||||||
@ -159,9 +161,34 @@ async def async_setup_connection(hass):
 | 
				
			|||||||
            await store.set_global_settings(**{msg["key"]: msg.get("value", None)})
 | 
					            await store.set_global_settings(**{msg["key"]: msg.get("value", None)})
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @websocket_api.websocket_command(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            vol.Required("type"): WS_RECALL_ID,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    def handle_recall_id(hass, connection, msg):
 | 
				
			||||||
 | 
					        """Recall browserID of Browser with the current connection."""
 | 
				
			||||||
 | 
					        dev = getBrowserByConnection(hass, connection)
 | 
				
			||||||
 | 
					        if dev:
 | 
				
			||||||
 | 
					            connection.send_message(
 | 
				
			||||||
 | 
					                websocket_api.result_message(msg["id"], dev.browserID)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        connection.send_message(websocket_api.result_message(msg["id"], None))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @websocket_api.websocket_command(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            vol.Required("type"): WS_LOG,
 | 
				
			||||||
 | 
					            vol.Required("message"): str,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    def handle_log(hass, connection, msg):
 | 
				
			||||||
 | 
					        """Print a debug message."""
 | 
				
			||||||
 | 
					        _LOGGER.info(f"LOG MESSAGE: {msg['message']}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async_register_command(hass, handle_connect)
 | 
					    async_register_command(hass, handle_connect)
 | 
				
			||||||
    async_register_command(hass, handle_register)
 | 
					    async_register_command(hass, handle_register)
 | 
				
			||||||
    async_register_command(hass, handle_unregister)
 | 
					    async_register_command(hass, handle_unregister)
 | 
				
			||||||
    async_register_command(hass, handle_reregister)
 | 
					 | 
				
			||||||
    async_register_command(hass, handle_update)
 | 
					    async_register_command(hass, handle_update)
 | 
				
			||||||
    async_register_command(hass, handle_settings)
 | 
					    async_register_command(hass, handle_settings)
 | 
				
			||||||
 | 
					    async_register_command(hass, handle_recall_id)
 | 
				
			||||||
 | 
					    async_register_command(hass, handle_log)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,42 +1,31 @@
 | 
				
			|||||||
DOMAIN = "browser_mod"
 | 
					DOMAIN = "browser_mod"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BROWSER_ID = "browserID"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FRONTEND_SCRIPT_URL = "/browser_mod.js"
 | 
					FRONTEND_SCRIPT_URL = "/browser_mod.js"
 | 
				
			||||||
SETTINGS_PANEL_URL = "/browser_mod_panel.js"
 | 
					SETTINGS_PANEL_URL = "/browser_mod_panel.js"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DATA_BROWSERS = "browsers"
 | 
					DATA_BROWSERS = "browsers"
 | 
				
			||||||
DATA_ADDERS = "adders"
 | 
					DATA_ADDERS = "adders"
 | 
				
			||||||
DATA_STORE = "store"
 | 
					DATA_STORE = "store"
 | 
				
			||||||
DATA_ALIASES = "aliases"
 | 
					 | 
				
			||||||
DATA_CONFIG = "config"
 | 
					 | 
				
			||||||
DATA_SETUP_COMPLETE = "setup_complete"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CONFIG_DEVICES = "devices"
 | 
					 | 
				
			||||||
CONFIG_PREFIX = "prefix"
 | 
					 | 
				
			||||||
CONFIG_DISABLE = "disable"
 | 
					 | 
				
			||||||
CONFIG_DISABLE_ALL = "all"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
WS_ROOT = DOMAIN
 | 
					WS_ROOT = DOMAIN
 | 
				
			||||||
WS_CONNECT = "{}/connect".format(WS_ROOT)
 | 
					WS_CONNECT = f"{WS_ROOT}/connect"
 | 
				
			||||||
WS_UPDATE = "{}/update".format(WS_ROOT)
 | 
					 | 
				
			||||||
WS_CAMERA = "{}/camera".format(WS_ROOT)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WS_REGISTER = f"{WS_ROOT}/register"
 | 
					WS_REGISTER = f"{WS_ROOT}/register"
 | 
				
			||||||
WS_UNREGISTER = f"{WS_ROOT}/unregister"
 | 
					WS_UNREGISTER = f"{WS_ROOT}/unregister"
 | 
				
			||||||
WS_REREGISTER = f"{WS_ROOT}/reregister"
 | 
					WS_UPDATE = f"{WS_ROOT}/update"
 | 
				
			||||||
 | 
					WS_SETTINGS = f"{WS_ROOT}/settings"
 | 
				
			||||||
 | 
					WS_RECALL_ID = f"{WS_ROOT}/recall_id"
 | 
				
			||||||
 | 
					WS_LOG = f"{WS_ROOT}/log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
USER_COMMANDS = [
 | 
					BROWSER_MOD_SERVICES = [
 | 
				
			||||||
    "debug",
 | 
					    "sequence",
 | 
				
			||||||
    "popup",
 | 
					 | 
				
			||||||
    "close-popup",
 | 
					 | 
				
			||||||
    "navigate",
 | 
					 | 
				
			||||||
    "more-info",
 | 
					 | 
				
			||||||
    "set-theme",
 | 
					 | 
				
			||||||
    "lovelace-reload",
 | 
					 | 
				
			||||||
    "window-reload",
 | 
					 | 
				
			||||||
    "blackout",
 | 
					 | 
				
			||||||
    "no-blackout",
 | 
					 | 
				
			||||||
    "toast",
 | 
					 | 
				
			||||||
    "commands",
 | 
					 | 
				
			||||||
    "call_service",
 | 
					 | 
				
			||||||
    "delay",
 | 
					    "delay",
 | 
				
			||||||
 | 
					    "popup",
 | 
				
			||||||
 | 
					    "more_info",
 | 
				
			||||||
 | 
					    "close_popup",
 | 
				
			||||||
 | 
					    "navigate",
 | 
				
			||||||
 | 
					    "refresh",
 | 
				
			||||||
 | 
					    "console",
 | 
				
			||||||
 | 
					    "javascript",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +0,0 @@
 | 
				
			|||||||
import logging
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from homeassistant.helpers.update_coordinator import (
 | 
					 | 
				
			||||||
    DataUpdateCoordinator,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_LOGGER = logging.getLogger(__name__)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Coordinator(DataUpdateCoordinator):
 | 
					 | 
				
			||||||
    def __init__(self, hass, browserID):
 | 
					 | 
				
			||||||
        super().__init__(
 | 
					 | 
				
			||||||
            hass,
 | 
					 | 
				
			||||||
            _LOGGER,
 | 
					 | 
				
			||||||
            name="Browser Mod Coordinator",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.browserID = browserID
 | 
					 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
from homeassistant.components.light import LightEntity, ColorMode
 | 
					from homeassistant.components.light import LightEntity, ColorMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helpers import BrowserModEntity
 | 
					from .entities import BrowserModEntity
 | 
				
			||||||
from .const import DOMAIN, DATA_ADDERS
 | 
					from .const import DOMAIN, DATA_ADDERS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,7 @@ from homeassistant.const import (
 | 
				
			|||||||
    STATE_UNKNOWN,
 | 
					    STATE_UNKNOWN,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helpers import BrowserModEntity
 | 
					from .entities import BrowserModEntity
 | 
				
			||||||
from .const import DOMAIN, DATA_ADDERS
 | 
					from .const import DOMAIN, DATA_ADDERS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -9,12 +9,18 @@ _LOGGER = logging.getLogger(__name__)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
async def async_setup_view(hass):
 | 
					async def async_setup_view(hass):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Serve the Browser Mod controller and add it as extra_module_url
 | 
				
			||||||
    hass.http.register_static_path(
 | 
					    hass.http.register_static_path(
 | 
				
			||||||
        FRONTEND_SCRIPT_URL,
 | 
					        FRONTEND_SCRIPT_URL,
 | 
				
			||||||
        hass.config.path("custom_components/browser_mod/browser_mod.js"),
 | 
					        hass.config.path("custom_components/browser_mod/browser_mod.js"),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    add_extra_js_url(hass, FRONTEND_SCRIPT_URL)
 | 
					    add_extra_js_url(hass, FRONTEND_SCRIPT_URL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Serve the Browser Mod Settings panel and register it as a panel
 | 
				
			||||||
 | 
					    hass.http.register_static_path(
 | 
				
			||||||
 | 
					        SETTINGS_PANEL_URL,
 | 
				
			||||||
 | 
					        hass.config.path("custom_components/browser_mod/browser_mod_panel.js"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    hass.components.frontend.async_register_built_in_panel(
 | 
					    hass.components.frontend.async_register_built_in_panel(
 | 
				
			||||||
        component_name="custom",
 | 
					        component_name="custom",
 | 
				
			||||||
        sidebar_title="Browser Mod",
 | 
					        sidebar_title="Browser Mod",
 | 
				
			||||||
@ -28,10 +34,6 @@ async def async_setup_view(hass):
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    hass.http.register_static_path(
 | 
					 | 
				
			||||||
        SETTINGS_PANEL_URL,
 | 
					 | 
				
			||||||
        hass.config.path("custom_components/browser_mod/browser_mod_panel.js"),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Also load Browser Mod as a lovelace resource so it's accessible to Cast
 | 
					    # Also load Browser Mod as a lovelace resource so it's accessible to Cast
 | 
				
			||||||
    resources = hass.data["lovelace"]["resources"]
 | 
					    resources = hass.data["lovelace"]["resources"]
 | 
				
			||||||
@ -39,14 +41,17 @@ async def async_setup_view(hass):
 | 
				
			|||||||
        if not resources.loaded:
 | 
					        if not resources.loaded:
 | 
				
			||||||
            await resources.async_load()
 | 
					            await resources.async_load()
 | 
				
			||||||
            resources.loaded = True
 | 
					            resources.loaded = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        frontend_added = False
 | 
					        frontend_added = False
 | 
				
			||||||
        for r in resources.async_items():
 | 
					        for r in resources.async_items():
 | 
				
			||||||
            if r["url"].startswith(FRONTEND_SCRIPT_URL):
 | 
					            if r["url"].startswith(FRONTEND_SCRIPT_URL):
 | 
				
			||||||
                frontend_added = True
 | 
					                frontend_added = True
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # While going through the resources, also preload card-mod if it is found
 | 
					            # While going through the resources, also preload card-mod if it is found
 | 
				
			||||||
            if "card-mod.js" in r["url"]:
 | 
					            if "card-mod.js" in r["url"]:
 | 
				
			||||||
                add_extra_js_url(hass, r["url"])
 | 
					                add_extra_js_url(hass, r["url"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not frontend_added:
 | 
					        if not frontend_added:
 | 
				
			||||||
            await resources.async_create_item(
 | 
					            await resources.async_create_item(
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
from homeassistant.components.sensor import SensorEntity
 | 
					from homeassistant.components.sensor import SensorEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .const import DOMAIN, DATA_ADDERS
 | 
					from .const import DOMAIN, DATA_ADDERS
 | 
				
			||||||
from .helpers import BrowserModEntity
 | 
					from .entities import BrowserModEntity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def async_setup_platform(
 | 
					async def async_setup_platform(
 | 
				
			||||||
@ -48,12 +48,15 @@ class BrowserSensor(BrowserModEntity, SensorEntity):
 | 
				
			|||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def extra_state_attributes(self):
 | 
					    def extra_state_attributes(self):
 | 
				
			||||||
        retval = super().extra_state_attributes
 | 
					        retval = super().extra_state_attributes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.parameter == "currentUser":
 | 
					        if self.parameter == "currentUser":
 | 
				
			||||||
            retval["userData"] = self._data.get("browser", {}).get("userData")
 | 
					            retval["userData"] = self._data.get("browser", {}).get("userData")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.parameter == "path":
 | 
					        if self.parameter == "path":
 | 
				
			||||||
            retval["pathSegments"] = (
 | 
					            retval["pathSegments"] = (
 | 
				
			||||||
                self._data.get("browser", {}).get("path", "").split("/")
 | 
					                self._data.get("browser", {}).get("path", "").split("/")
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.parameter == "userAgent":
 | 
					        if self.parameter == "userAgent":
 | 
				
			||||||
            retval["userAgent"] = self._data.get("browser", {}).get("userAgent")
 | 
					            retval["userAgent"] = self._data.get("browser", {}).get("userAgent")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import logging
 | 
				
			|||||||
from homeassistant.helpers import device_registry
 | 
					from homeassistant.helpers import device_registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .const import (
 | 
					from .const import (
 | 
				
			||||||
 | 
					    BROWSER_MOD_SERVICES,
 | 
				
			||||||
    DOMAIN,
 | 
					    DOMAIN,
 | 
				
			||||||
    DATA_BROWSERS,
 | 
					    DATA_BROWSERS,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -59,12 +60,5 @@ async def async_setup_services(hass):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        call_service(service, browsers, data)
 | 
					        call_service(service, browsers, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hass.services.async_register(DOMAIN, "sequence", handle_service)
 | 
					    for service in BROWSER_MOD_SERVICES:
 | 
				
			||||||
    hass.services.async_register(DOMAIN, "delay", handle_service)
 | 
					        hass.services.async_register(DOMAIN, service, handle_service)
 | 
				
			||||||
    hass.services.async_register(DOMAIN, "popup", handle_service)
 | 
					 | 
				
			||||||
    hass.services.async_register(DOMAIN, "more_info", handle_service)
 | 
					 | 
				
			||||||
    hass.services.async_register(DOMAIN, "close_popup", handle_service)
 | 
					 | 
				
			||||||
    hass.services.async_register(DOMAIN, "navigate", handle_service)
 | 
					 | 
				
			||||||
    hass.services.async_register(DOMAIN, "refresh", handle_service)
 | 
					 | 
				
			||||||
    hass.services.async_register(DOMAIN, "console", handle_service)
 | 
					 | 
				
			||||||
    hass.services.async_register(DOMAIN, "javascript", handle_service)
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -101,6 +101,12 @@ popup:
 | 
				
			|||||||
      description: Action to perform when popup is dismissed
 | 
					      description: Action to perform when popup is dismissed
 | 
				
			||||||
      selector:
 | 
					      selector:
 | 
				
			||||||
        object:
 | 
					        object:
 | 
				
			||||||
 | 
					    autoclose:
 | 
				
			||||||
 | 
					      name: Auto close
 | 
				
			||||||
 | 
					      description: Close the popup automatically on mouse, pointer or keyboard activity
 | 
				
			||||||
 | 
					      default: false
 | 
				
			||||||
 | 
					      selector:
 | 
				
			||||||
 | 
					        boolean:
 | 
				
			||||||
    timeout:
 | 
					    timeout:
 | 
				
			||||||
      name: Auto close timeout
 | 
					      name: Auto close timeout
 | 
				
			||||||
      description: Time before closing (ms)
 | 
					      description: Time before closing (ms)
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,7 @@ class SettingsStoreData:
 | 
				
			|||||||
@attr.s
 | 
					@attr.s
 | 
				
			||||||
class BrowserStoreData:
 | 
					class BrowserStoreData:
 | 
				
			||||||
    last_seen = attr.ib(type=int, default=0)
 | 
					    last_seen = attr.ib(type=int, default=0)
 | 
				
			||||||
    enabled = attr.ib(type=bool, default=False)
 | 
					    registered = attr.ib(type=bool, default=False)
 | 
				
			||||||
    camera = attr.ib(type=bool, default=False)
 | 
					    camera = attr.ib(type=bool, default=False)
 | 
				
			||||||
    settings = attr.ib(type=SettingsStoreData, factory=SettingsStoreData)
 | 
					    settings = attr.ib(type=SettingsStoreData, factory=SettingsStoreData)
 | 
				
			||||||
    meta = attr.ib(type=str, default="default")
 | 
					    meta = attr.ib(type=str, default="default")
 | 
				
			||||||
 | 
				
			|||||||
@ -6,20 +6,23 @@ export const ActivityMixin = (SuperClass) => {
 | 
				
			|||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
      super();
 | 
					      super();
 | 
				
			||||||
      for (const ev of ["pointerdown", "pointermove", "keydown"]) {
 | 
					      for (const ev of ["pointerdown", "pointermove", "keydown"]) {
 | 
				
			||||||
        window.addEventListener(ev, () => this.activityTrigger());
 | 
					        window.addEventListener(ev, () => this.activityTrigger(true));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.addEventListener("fully-update", () => {
 | 
					      this.addEventListener("fully-update", () => {
 | 
				
			||||||
        this.activityTrigger();
 | 
					        this.activityTrigger();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    activityTrigger() {
 | 
					    activityTrigger(touched = false) {
 | 
				
			||||||
      if (!this.activityTriggered) {
 | 
					      if (!this.activityTriggered) {
 | 
				
			||||||
        this.sendUpdate({
 | 
					        this.sendUpdate({
 | 
				
			||||||
          activity: true,
 | 
					          activity: true,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.activityTriggered = true;
 | 
					      this.activityTriggered = true;
 | 
				
			||||||
 | 
					      if (touched) {
 | 
				
			||||||
 | 
					        this.fireEvent("browser-mod-activity");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      clearTimeout(this._activityTimeout);
 | 
					      clearTimeout(this._activityTimeout);
 | 
				
			||||||
      this._activityTimeout = setTimeout(
 | 
					      this._activityTimeout = setTimeout(
 | 
				
			||||||
        () => this.activityReset(),
 | 
					        () => this.activityReset(),
 | 
				
			||||||
 | 
				
			|||||||
@ -20,10 +20,23 @@ export const BrowserIDMixin = (SuperClass) => {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async recall_id() {
 | 
				
			||||||
 | 
					      // If the connection is still open, but the BrowserID has disappeared - recall it from the backend
 | 
				
			||||||
 | 
					      // This happens e.g. when the frontend cache is reset in the Compainon app
 | 
				
			||||||
 | 
					      if (!this.connection) return;
 | 
				
			||||||
 | 
					      const recalledID = await this.connection.sendMessagePromise({
 | 
				
			||||||
 | 
					        type: "browser_mod/recall_id",
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (recalledID) {
 | 
				
			||||||
 | 
					        localStorage[ID_STORAGE_KEY] = recalledID;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get browserID() {
 | 
					    get browserID() {
 | 
				
			||||||
      if (document.querySelector("hc-main")) return "CAST";
 | 
					      if (document.querySelector("hc-main")) return "CAST";
 | 
				
			||||||
      if (localStorage[ID_STORAGE_KEY]) return localStorage[ID_STORAGE_KEY];
 | 
					      if (localStorage[ID_STORAGE_KEY]) return localStorage[ID_STORAGE_KEY];
 | 
				
			||||||
      this.browserID = "";
 | 
					      this.browserID = "";
 | 
				
			||||||
 | 
					      this.recall_id();
 | 
				
			||||||
      return this.browserID;
 | 
					      return this.browserID;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    set browserID(id) {
 | 
					    set browserID(id) {
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,11 @@ export const ConnectionMixin = (SuperClass) => {
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
      const dt = new Date();
 | 
					      const dt = new Date();
 | 
				
			||||||
      console.log(`${dt.toLocaleTimeString()}`, ...args);
 | 
					      console.log(`${dt.toLocaleTimeString()}`, ...args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.connection.sendMessage({
 | 
				
			||||||
 | 
					        type: "browser_mod/log",
 | 
				
			||||||
 | 
					        message: args[0],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fireEvent(event, detail = undefined) {
 | 
					    private fireEvent(event, detail = undefined) {
 | 
				
			||||||
@ -109,7 +114,7 @@ export const ConnectionMixin = (SuperClass) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private async _reregister(newData = {}) {
 | 
					    private async _reregister(newData = {}) {
 | 
				
			||||||
      await this.connection.sendMessage({
 | 
					      await this.connection.sendMessage({
 | 
				
			||||||
        type: "browser_mod/reregister",
 | 
					        type: "browser_mod/register",
 | 
				
			||||||
        browserID: this.browserID,
 | 
					        browserID: this.browserID,
 | 
				
			||||||
        data: {
 | 
					        data: {
 | 
				
			||||||
          ...this.browsers[this.browserID],
 | 
					          ...this.browsers[this.browserID],
 | 
				
			||||||
@ -210,7 +215,7 @@ export const ConnectionMixin = (SuperClass) => {
 | 
				
			|||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
        (async () => {
 | 
					        (async () => {
 | 
				
			||||||
          await this.connection.sendMessage({
 | 
					          await this.connection.sendMessage({
 | 
				
			||||||
            type: "browser_mod/reregister",
 | 
					            type: "browser_mod/register",
 | 
				
			||||||
            browserID: oldID,
 | 
					            browserID: oldID,
 | 
				
			||||||
            data: {
 | 
					            data: {
 | 
				
			||||||
              ...this.browsers[oldID],
 | 
					              ...this.browsers[oldID],
 | 
				
			||||||
 | 
				
			|||||||
@ -39,16 +39,20 @@ import { BrowserIDMixin } from "./browserID";
 | 
				
			|||||||
    x ll-custom handling
 | 
					    x ll-custom handling
 | 
				
			||||||
    - Commands
 | 
					    - Commands
 | 
				
			||||||
      x popup
 | 
					      x popup
 | 
				
			||||||
 | 
					        x Auto-close
 | 
				
			||||||
      x close_popup
 | 
					      x close_popup
 | 
				
			||||||
      x more-info
 | 
					      x more-info
 | 
				
			||||||
      x navigate
 | 
					      x navigate
 | 
				
			||||||
      - lovelace-reload?
 | 
					      - lovelace-reload?
 | 
				
			||||||
 | 
					        - Not needed
 | 
				
			||||||
      x window-reload
 | 
					      x window-reload
 | 
				
			||||||
      - screensaver ?
 | 
					      - screensaver ?
 | 
				
			||||||
 | 
					        - Refer to automations instead
 | 
				
			||||||
      x sequence
 | 
					      x sequence
 | 
				
			||||||
      x delay
 | 
					      x delay
 | 
				
			||||||
      x javascript eval
 | 
					      x javascript eval
 | 
				
			||||||
      - toast?
 | 
					      - toast?
 | 
				
			||||||
 | 
					        - Replaced with popups with timeout
 | 
				
			||||||
    x Redesign services to target devices
 | 
					    x Redesign services to target devices
 | 
				
			||||||
  x frontend editor for popup cards
 | 
					  x frontend editor for popup cards
 | 
				
			||||||
    - also screensavers
 | 
					    - also screensavers
 | 
				
			||||||
@ -66,7 +70,8 @@ import { BrowserIDMixin } from "./browserID";
 | 
				
			|||||||
  - Video player?
 | 
					  - Video player?
 | 
				
			||||||
  - Media_seek
 | 
					  - Media_seek
 | 
				
			||||||
  - Screensavers
 | 
					  - Screensavers
 | 
				
			||||||
  - IMPORTANT: FIX DEFAULT HIDING OF ENTITIES
 | 
					  x IMPORTANT: FIX DEFAULT HIDING OF ENTITIES
 | 
				
			||||||
 | 
					    - NOFIX. Home Assistant bug
 | 
				
			||||||
  X Check functionality with CAST - may need to add frontend part as a lovelace resource
 | 
					  X Check functionality with CAST - may need to add frontend part as a lovelace resource
 | 
				
			||||||
  */
 | 
					  */
 | 
				
			||||||
export class BrowserMod extends ServicesMixin(
 | 
					export class BrowserMod extends ServicesMixin(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { LitElement, html, css } from "lit";
 | 
					import { LitElement, html, css } from "lit";
 | 
				
			||||||
import { property } from "lit/decorators.js";
 | 
					import { property, query } from "lit/decorators.js";
 | 
				
			||||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
 | 
					import { unsafeHTML } from "lit/directives/unsafe-html.js";
 | 
				
			||||||
import { provideHass, loadLoadCardHelpers, hass_base_el } from "../helpers";
 | 
					import { provideHass, loadLoadCardHelpers, hass_base_el } from "../helpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -16,27 +16,31 @@ class BrowserModPopup extends LitElement {
 | 
				
			|||||||
  @property() dismissable;
 | 
					  @property() dismissable;
 | 
				
			||||||
  @property({ reflect: true }) wide;
 | 
					  @property({ reflect: true }) wide;
 | 
				
			||||||
  @property({ reflect: true }) fullscreen;
 | 
					  @property({ reflect: true }) fullscreen;
 | 
				
			||||||
 | 
					  @property() _style;
 | 
				
			||||||
 | 
					  @query("ha-dialog") dialog: any;
 | 
				
			||||||
 | 
					  _autoclose;
 | 
				
			||||||
 | 
					  _autocloseListener;
 | 
				
			||||||
  _actions;
 | 
					  _actions;
 | 
				
			||||||
  timeout;
 | 
					  timeout;
 | 
				
			||||||
  _timeoutStart;
 | 
					  _timeoutStart;
 | 
				
			||||||
  _timeoutTimer;
 | 
					  _timeoutTimer;
 | 
				
			||||||
  @property() _style;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _resolveClosed;
 | 
					  _resolveClosed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  closedCallback() {
 | 
					 | 
				
			||||||
    this._resolveClosed?.();
 | 
					 | 
				
			||||||
    this._resolveClosed = undefined;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async closeDialog() {
 | 
					  async closeDialog() {
 | 
				
			||||||
    this.open = false;
 | 
					    this.open = false;
 | 
				
			||||||
    await new Promise((resolve) => (this._resolveClosed = resolve));
 | 
					 | 
				
			||||||
    clearInterval(this._timeoutTimer);
 | 
					    clearInterval(this._timeoutTimer);
 | 
				
			||||||
 | 
					    if (this._autocloseListener) {
 | 
				
			||||||
 | 
					      window.browser_mod.removeEventListener(
 | 
				
			||||||
 | 
					        "browser-mod-activity",
 | 
				
			||||||
 | 
					        this._autocloseListener
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      this._autocloseListener = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  openDialog() {
 | 
					  openDialog() {
 | 
				
			||||||
    this.open = true;
 | 
					    this.open = true;
 | 
				
			||||||
 | 
					    this.dialog?.show();
 | 
				
			||||||
    if (this.timeout) {
 | 
					    if (this.timeout) {
 | 
				
			||||||
      this._timeoutStart = new Date().getTime();
 | 
					      this._timeoutStart = new Date().getTime();
 | 
				
			||||||
      this._timeoutTimer = setInterval(() => {
 | 
					      this._timeoutTimer = setInterval(() => {
 | 
				
			||||||
@ -46,6 +50,14 @@ class BrowserModPopup extends LitElement {
 | 
				
			|||||||
        if (ellapsed >= this.timeout) this._timeout();
 | 
					        if (ellapsed >= this.timeout) this._timeout();
 | 
				
			||||||
      }, 10);
 | 
					      }, 10);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    this._autocloseListener = undefined;
 | 
				
			||||||
 | 
					    if (this._autoclose) {
 | 
				
			||||||
 | 
					      this._autocloseListener = this._dismiss.bind(this);
 | 
				
			||||||
 | 
					      window.browser_mod.addEventListener(
 | 
				
			||||||
 | 
					        "browser-mod-activity",
 | 
				
			||||||
 | 
					        this._autocloseListener
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async setupDialog(
 | 
					  async setupDialog(
 | 
				
			||||||
@ -62,6 +74,7 @@ class BrowserModPopup extends LitElement {
 | 
				
			|||||||
      timeout_action = undefined,
 | 
					      timeout_action = undefined,
 | 
				
			||||||
      size = undefined,
 | 
					      size = undefined,
 | 
				
			||||||
      style = undefined,
 | 
					      style = undefined,
 | 
				
			||||||
 | 
					      autoclose = false,
 | 
				
			||||||
    } = {}
 | 
					    } = {}
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.title = title;
 | 
					    this.title = title;
 | 
				
			||||||
@ -94,6 +107,7 @@ class BrowserModPopup extends LitElement {
 | 
				
			|||||||
    this.wide = size === "wide" ? "" : undefined;
 | 
					    this.wide = size === "wide" ? "" : undefined;
 | 
				
			||||||
    this.fullscreen = size === "fullscreen" ? "" : undefined;
 | 
					    this.fullscreen = size === "fullscreen" ? "" : undefined;
 | 
				
			||||||
    this._style = style;
 | 
					    this._style = style;
 | 
				
			||||||
 | 
					    this._autoclose = autoclose;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async _primary() {
 | 
					  async _primary() {
 | 
				
			||||||
@ -122,7 +136,6 @@ class BrowserModPopup extends LitElement {
 | 
				
			|||||||
    return html`
 | 
					    return html`
 | 
				
			||||||
      <ha-dialog
 | 
					      <ha-dialog
 | 
				
			||||||
        open
 | 
					        open
 | 
				
			||||||
        @closed=${this.closedCallback}
 | 
					 | 
				
			||||||
        .heading=${this.title !== undefined}
 | 
					        .heading=${this.title !== undefined}
 | 
				
			||||||
        ?hideActions=${this.actions === undefined}
 | 
					        ?hideActions=${this.actions === undefined}
 | 
				
			||||||
        .scrimClickAction=${this.dismissable ? this._dismiss : ""}
 | 
					        .scrimClickAction=${this.dismissable ? this._dismiss : ""}
 | 
				
			||||||
 | 
				
			|||||||
@ -30,6 +30,7 @@ export const ServicesMixin = (SuperClass) => {
 | 
				
			|||||||
          [left_button_action: <service call>]
 | 
					          [left_button_action: <service call>]
 | 
				
			||||||
          [dismissable: <TRUE/false>]
 | 
					          [dismissable: <TRUE/false>]
 | 
				
			||||||
          [dismiss_action: <service call>]
 | 
					          [dismiss_action: <service call>]
 | 
				
			||||||
 | 
					          [autoclose: <true/FALSE>]
 | 
				
			||||||
          [timeout: <number>]
 | 
					          [timeout: <number>]
 | 
				
			||||||
          [timeout_action: <service call>]
 | 
					          [timeout_action: <service call>]
 | 
				
			||||||
          [style: <string>]
 | 
					          [style: <string>]
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user