239 lines
6.9 KiB
TypeScript
239 lines
6.9 KiB
TypeScript
import { PolymerElement } from "@polymer/polymer";
|
|
import {
|
|
STATE_NOT_RUNNING,
|
|
STATE_RUNNING,
|
|
STATE_STARTING,
|
|
} from "home-assistant-js-websocket";
|
|
import { PropertyValues } from "lit";
|
|
import { customElement, property } from "lit/decorators";
|
|
import { deepActiveElement } from "../common/dom/deep-active-element";
|
|
import { deepEqual } from "../common/util/deep-equal";
|
|
import { getDefaultPanel } from "../data/panel";
|
|
import { CustomPanelInfo } from "../data/panel_custom";
|
|
import { HomeAssistant, Panels } from "../types";
|
|
import { removeLaunchScreen } from "../util/launch-screen";
|
|
import {
|
|
HassRouterPage,
|
|
RouteOptions,
|
|
RouterOptions,
|
|
} from "./hass-router-page";
|
|
|
|
const CACHE_URL_PATHS = ["lovelace", "developer-tools"];
|
|
const COMPONENTS = {
|
|
energy: () => import("../panels/energy/ha-panel-energy"),
|
|
calendar: () => import("../panels/calendar/ha-panel-calendar"),
|
|
config: () => import("../panels/config/ha-panel-config"),
|
|
custom: () => import("../panels/custom/ha-panel-custom"),
|
|
"developer-tools": () =>
|
|
import("../panels/developer-tools/ha-panel-developer-tools"),
|
|
lovelace: () => import("../panels/lovelace/ha-panel-lovelace"),
|
|
history: () => import("../panels/history/ha-panel-history"),
|
|
iframe: () => import("../panels/iframe/ha-panel-iframe"),
|
|
logbook: () => import("../panels/logbook/ha-panel-logbook"),
|
|
mailbox: () => import("../panels/mailbox/ha-panel-mailbox"),
|
|
map: () => import("../panels/map/ha-panel-map"),
|
|
my: () => import("../panels/my/ha-panel-my"),
|
|
profile: () => import("../panels/profile/ha-panel-profile"),
|
|
"shopping-list": () =>
|
|
import("../panels/shopping-list/ha-panel-shopping-list"),
|
|
"media-browser": () =>
|
|
import("../panels/media-browser/ha-panel-media-browser"),
|
|
};
|
|
|
|
@customElement("partial-panel-resolver")
|
|
class PartialPanelResolver extends HassRouterPage {
|
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
|
|
@property({ type: Boolean }) public narrow = false;
|
|
|
|
private _waitForStart = false;
|
|
|
|
private _disconnectedPanel?: HTMLElement;
|
|
|
|
private _disconnectedActiveElement?: HTMLElement;
|
|
|
|
private _hiddenTimeout?: number;
|
|
|
|
protected firstUpdated(changedProps: PropertyValues) {
|
|
super.firstUpdated(changedProps);
|
|
|
|
// Attach listeners for visibility
|
|
document.addEventListener(
|
|
"visibilitychange",
|
|
() => this._checkVisibility(),
|
|
false
|
|
);
|
|
document.addEventListener("resume", () => this._checkVisibility());
|
|
}
|
|
|
|
public willUpdate(changedProps: PropertyValues) {
|
|
super.willUpdate(changedProps);
|
|
|
|
if (!changedProps.has("hass")) {
|
|
return;
|
|
}
|
|
|
|
const oldHass = changedProps.get("hass") as this["hass"];
|
|
|
|
if (
|
|
this._waitForStart &&
|
|
(this.hass.config.state === STATE_STARTING ||
|
|
this.hass.config.state === STATE_RUNNING)
|
|
) {
|
|
this._waitForStart = false;
|
|
this.rebuild();
|
|
}
|
|
|
|
if (this.hass.panels && (!oldHass || oldHass.panels !== this.hass.panels)) {
|
|
this._updateRoutes(oldHass?.panels);
|
|
}
|
|
}
|
|
|
|
protected createLoadingScreen() {
|
|
const el = super.createLoadingScreen();
|
|
el.rootnav = true;
|
|
el.hass = this.hass;
|
|
el.narrow = this.narrow;
|
|
return el;
|
|
}
|
|
|
|
protected updatePageEl(el) {
|
|
const hass = this.hass;
|
|
|
|
if ("setProperties" in el) {
|
|
// As long as we have Polymer panels
|
|
(el as PolymerElement).setProperties({
|
|
hass: this.hass,
|
|
narrow: this.narrow,
|
|
route: this.routeTail,
|
|
panel: hass.panels[this._currentPage],
|
|
});
|
|
} else {
|
|
el.hass = hass;
|
|
el.narrow = this.narrow;
|
|
el.route = this.routeTail;
|
|
el.panel = hass.panels[this._currentPage];
|
|
}
|
|
}
|
|
|
|
private _checkVisibility() {
|
|
if (this.hass.suspendWhenHidden === false) {
|
|
return;
|
|
}
|
|
|
|
if (document.hidden) {
|
|
this._onHidden();
|
|
} else {
|
|
this._onVisible();
|
|
}
|
|
}
|
|
|
|
private getRoutes(panels: Panels): RouterOptions {
|
|
const routes: RouterOptions["routes"] = {};
|
|
Object.values(panels).forEach((panel) => {
|
|
const data: RouteOptions = {
|
|
tag: `ha-panel-${panel.component_name}`,
|
|
cache: CACHE_URL_PATHS.includes(panel.url_path),
|
|
};
|
|
if (panel.component_name in COMPONENTS) {
|
|
data.load = COMPONENTS[panel.component_name];
|
|
}
|
|
routes[panel.url_path] = data;
|
|
});
|
|
|
|
return {
|
|
beforeRender: (page) => {
|
|
if (!page || !routes[page]) {
|
|
return getDefaultPanel(this.hass).url_path;
|
|
}
|
|
return undefined;
|
|
},
|
|
showLoading: true,
|
|
routes,
|
|
};
|
|
}
|
|
|
|
private _onHidden() {
|
|
this._hiddenTimeout = window.setTimeout(() => {
|
|
this._hiddenTimeout = undefined;
|
|
// setTimeout can be delayed in the background and only fire
|
|
// when we switch to the tab or app again (Hey Android!)
|
|
if (!document.hidden) {
|
|
return;
|
|
}
|
|
const curPanel = this.hass.panels[this._currentPage];
|
|
if (
|
|
this.lastChild &&
|
|
// iFrames will lose their state when disconnected
|
|
// Do not disconnect any iframe panel
|
|
curPanel.component_name !== "iframe" &&
|
|
// Do not disconnect any custom panel that embeds into iframe (ie hassio)
|
|
(curPanel.component_name !== "custom" ||
|
|
!(curPanel as CustomPanelInfo).config._panel_custom.embed_iframe)
|
|
) {
|
|
this._disconnectedPanel = this.lastChild as HTMLElement;
|
|
const activeEl = deepActiveElement(
|
|
this._disconnectedPanel.shadowRoot || undefined
|
|
);
|
|
if (activeEl instanceof HTMLElement) {
|
|
this._disconnectedActiveElement = activeEl;
|
|
}
|
|
this.removeChild(this.lastChild);
|
|
}
|
|
}, 300000);
|
|
window.addEventListener("focus", () => this._onVisible(), { once: true });
|
|
}
|
|
|
|
private _onVisible() {
|
|
if (this._hiddenTimeout) {
|
|
clearTimeout(this._hiddenTimeout);
|
|
this._hiddenTimeout = undefined;
|
|
}
|
|
if (this._disconnectedPanel) {
|
|
this.appendChild(this._disconnectedPanel);
|
|
this._disconnectedPanel = undefined;
|
|
}
|
|
if (this._disconnectedActiveElement) {
|
|
this._disconnectedActiveElement.focus();
|
|
this._disconnectedActiveElement = undefined;
|
|
}
|
|
}
|
|
|
|
private async _updateRoutes(oldPanels?: HomeAssistant["panels"]) {
|
|
this.routerOptions = this.getRoutes(this.hass.panels);
|
|
|
|
if (
|
|
!this._waitForStart &&
|
|
this._currentPage &&
|
|
!this.hass.panels[this._currentPage]
|
|
) {
|
|
if (this.hass.config.state === STATE_NOT_RUNNING) {
|
|
this._waitForStart = true;
|
|
if (this.lastChild) {
|
|
this.removeChild(this.lastChild);
|
|
}
|
|
this.appendChild(this.createLoadingScreen());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (
|
|
!oldPanels ||
|
|
!deepEqual(
|
|
oldPanels[this._currentPage],
|
|
this.hass.panels[this._currentPage]
|
|
)
|
|
) {
|
|
await this.rebuild();
|
|
await this.pageRendered;
|
|
removeLaunchScreen();
|
|
}
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"partial-panel-resolver": PartialPanelResolver;
|
|
}
|
|
}
|