diff --git a/src/layouts/partial-panel-resolver.js b/src/layouts/partial-panel-resolver.js deleted file mode 100644 index f427954372..0000000000 --- a/src/layouts/partial-panel-resolver.js +++ /dev/null @@ -1,246 +0,0 @@ -import "@polymer/app-route/app-route"; -import { dom } from "@polymer/polymer/lib/legacy/polymer.dom"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "./hass-loading-screen"; -import "./hass-error-screen"; -import { importHref } from "../resources/html-import/import-href"; - -import dynamicContentUpdater from "../common/dom/dynamic_content_updater"; -import NavigateMixin from "../mixins/navigate-mixin"; - -const loaded = {}; - -function ensureLoaded(panel) { - if (panel in loaded) return loaded[panel]; - - let imported; - // Name each panel we support here, that way Webpack knows about it. - switch (panel) { - case "config": - imported = import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"); - break; - - case "custom": - imported = import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"); - break; - - case "dev-event": - imported = import(/* webpackChunkName: "panel-dev-event" */ "../panels/dev-event/ha-panel-dev-event"); - break; - - case "dev-info": - imported = import(/* webpackChunkName: "panel-dev-info" */ "../panels/dev-info/ha-panel-dev-info"); - break; - - case "dev-mqtt": - imported = import(/* webpackChunkName: "panel-dev-mqtt" */ "../panels/dev-mqtt/ha-panel-dev-mqtt"); - break; - - case "dev-service": - imported = import(/* webpackChunkName: "panel-dev-service" */ "../panels/dev-service/ha-panel-dev-service"); - break; - - case "dev-state": - imported = import(/* webpackChunkName: "panel-dev-state" */ "../panels/dev-state/ha-panel-dev-state"); - break; - - case "dev-template": - imported = import(/* webpackChunkName: "panel-dev-template" */ "../panels/dev-template/ha-panel-dev-template"); - break; - - case "lovelace": - imported = import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"); - break; - - case "states": - imported = import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"); - break; - - case "history": - imported = import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"); - break; - - case "iframe": - imported = import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"); - break; - - case "kiosk": - imported = import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"); - break; - - case "logbook": - imported = import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"); - break; - - case "mailbox": - imported = import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"); - break; - - case "map": - imported = import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map"); - break; - - case "profile": - imported = import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"); - break; - - case "shopping-list": - imported = import(/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list"); - break; - - case "calendar": - imported = import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"); - break; - - default: - imported = null; - } - - if (imported != null) { - loaded[panel] = imported; - } - - return imported; -} - -/* - * @appliesMixin NavigateMixin - */ -class PartialPanelResolver extends NavigateMixin(PolymerElement) { - static get template() { - return html` - - - - - - - - `; - } - - static get properties() { - return { - hass: { - type: Object, - observer: "updateAttributes", - }, - - narrow: { - type: Boolean, - value: false, - observer: "updateAttributes", - }, - - showMenu: { - type: Boolean, - value: false, - observer: "updateAttributes", - }, - route: Object, - routeData: Object, - routeTail: { - type: Object, - observer: "updateAttributes", - }, - _state: { - type: String, - value: "loading", - }, - panel: { - type: Object, - computed: "computeCurrentPanel(hass)", - observer: "panelChanged", - }, - }; - } - - panelChanged(panel) { - if (!panel) { - if (this.$.panel.lastChild) { - this.$.panel.removeChild(this.$.panel.lastChild); - } - return; - } - - this._state = "loading"; - - let loadingProm; - if (panel.url) { - loadingProm = new Promise((resolve, reject) => - importHref(panel.url, resolve, reject) - ); - } else { - loadingProm = ensureLoaded(panel.component_name); - } - - if (loadingProm === null) { - this._state = "error"; - return; - } - - loadingProm.then( - () => { - dynamicContentUpdater( - this.$.panel, - "ha-panel-" + panel.component_name, - { - hass: this.hass, - narrow: this.narrow, - showMenu: this.showMenu, - route: this.routeTail, - panel: panel, - } - ); - this._state = "loaded"; - }, - (err) => { - // eslint-disable-next-line - console.error("Error loading panel", err); - this._state = "error"; - } - ); - } - - updateAttributes() { - var customEl = dom(this.$.panel).lastChild; - if (!customEl) return; - customEl.hass = this.hass; - customEl.narrow = this.narrow; - customEl.showMenu = this.showMenu; - customEl.route = this.routeTail; - } - - computeCurrentPanel(hass) { - return hass.panels[hass.panelUrl]; - } - - _equal(a, b) { - return a === b; - } -} - -customElements.define("partial-panel-resolver", PartialPanelResolver); diff --git a/src/layouts/partial-panel-resolver.ts b/src/layouts/partial-panel-resolver.ts new file mode 100644 index 0000000000..90b3ccb01f --- /dev/null +++ b/src/layouts/partial-panel-resolver.ts @@ -0,0 +1,268 @@ +import { + LitElement, + html, + PropertyDeclarations, + PropertyValues, +} from "lit-element"; + +import "./hass-loading-screen"; +import "./hass-error-screen"; +import { HomeAssistant, Panel, PanelElement, Route } from "../types"; + +// Cache of panel loading promises. +const LOADED: { [panel: string]: Promise } = {}; + +// Which panel elements we will cache. +// Maybe we can cache them all eventually, but not sure yet about +// unknown side effects (like history taking a lot of memory, reset needed) +const CACHED_EL = ["lovelace", "states"]; + +function ensureLoaded(panel): Promise | null { + if (panel in LOADED) { + return LOADED[panel]; + } + + let imported; + // Name each panel we support here, that way Webpack knows about it. + switch (panel) { + case "config": + imported = import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"); + break; + + case "custom": + imported = import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"); + break; + + case "dev-event": + imported = import(/* webpackChunkName: "panel-dev-event" */ "../panels/dev-event/ha-panel-dev-event"); + break; + + case "dev-info": + imported = import(/* webpackChunkName: "panel-dev-info" */ "../panels/dev-info/ha-panel-dev-info"); + break; + + case "dev-mqtt": + imported = import(/* webpackChunkName: "panel-dev-mqtt" */ "../panels/dev-mqtt/ha-panel-dev-mqtt"); + break; + + case "dev-service": + imported = import(/* webpackChunkName: "panel-dev-service" */ "../panels/dev-service/ha-panel-dev-service"); + break; + + case "dev-state": + imported = import(/* webpackChunkName: "panel-dev-state" */ "../panels/dev-state/ha-panel-dev-state"); + break; + + case "dev-template": + imported = import(/* webpackChunkName: "panel-dev-template" */ "../panels/dev-template/ha-panel-dev-template"); + break; + + case "lovelace": + imported = import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"); + break; + + case "states": + imported = import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"); + break; + + case "history": + imported = import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"); + break; + + case "iframe": + imported = import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"); + break; + + case "kiosk": + imported = import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"); + break; + + case "logbook": + imported = import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"); + break; + + case "mailbox": + imported = import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"); + break; + + case "map": + imported = import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map"); + break; + + case "profile": + imported = import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"); + break; + + case "shopping-list": + imported = import(/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list"); + break; + + case "calendar": + imported = import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"); + break; + + default: + imported = null; + } + + if (imported != null) { + LOADED[panel] = imported; + } + + return imported; +} + +class PartialPanelResolver extends LitElement { + public hass?: HomeAssistant; + public narrow?: boolean; + public showMenu?: boolean; + public route?: Route | null; + + private _routeTail?: Route | null; + private _panel?: Panel; + private _panelEl?: PanelElement; + private _error?: boolean; + private _cache: { [name: string]: PanelElement }; + + static get properties(): PropertyDeclarations { + return { + hass: {}, + narrow: {}, + showMenu: {}, + route: {}, + + _routeTail: {}, + _error: {}, + _panelEl: {}, + }; + } + + constructor() { + super(); + this._cache = {}; + } + + protected render() { + if (this._error) { + return html` + + `; + } + + if (!this._panelEl) { + return html` + + `; + } + + return html` + ${this._panelEl} + `; + } + + protected updated(changedProps: PropertyValues) { + if (!this.hass) { + return; + } + + if (changedProps.has("route")) { + // Manual splitting + const route = this.route!; + const dividerPos = route.path.indexOf("/", 1); + this._routeTail = { + prefix: route.path.substr(0, dividerPos), + path: route.path.substr(dividerPos), + }; + + // If just route changed, no need to process further. + if (changedProps.size === 1) { + return; + } + } + + if (changedProps.has("hass")) { + const panel = this.hass.panels[this.hass.panelUrl]; + + if (panel !== this._panel) { + this._panel = panel; + this._panelEl = undefined; + + // Found cached one, use that + if (panel.component_name in this._cache) { + this._panelEl = this._cache[panel.component_name]; + this._updatePanel(); + return; + } + + const loadingProm = ensureLoaded(panel.component_name); + + if (loadingProm === null) { + this._error = true; + return; + } + + loadingProm.then( + () => { + // If panel changed while loading. + if (this._panel !== panel) { + return; + } + + this._panelEl = (this._panelEl = document.createElement( + `ha-panel-${panel.component_name}` + )) as PanelElement; + + if (CACHED_EL.includes(panel.component_name)) { + this._cache[panel.component_name] = this._panelEl; + } + + this._updatePanel(); + }, + (err) => { + // tslint:disable-next-line + console.error("Error loading panel", err); + this._error = true; + } + ); + return; + } + } + + this._updatePanel(); + } + + private _updatePanel() { + const el = this._panelEl; + + if (!el) { + return; + } + + if ("setProperties" in el) { + // As long as we have Polymer panels + (el as any).setProperties({ + hass: this.hass, + narrow: this.narrow, + showMenu: this.showMenu, + route: this._routeTail, + panel: this._panel, + }); + } else { + el.hass = this.hass; + el.narrow = this.narrow; + el.showMenu = this.showMenu; + el.route = this._routeTail; + el.panel = this._panel; + } + } +} + +customElements.define("partial-panel-resolver", PartialPanelResolver); diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index 98ca2f1abd..4864fa20e5 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -4,7 +4,7 @@ import { fetchConfig, LovelaceConfig, saveConfig } from "../../data/lovelace"; import "../../layouts/hass-loading-screen"; import "../../layouts/hass-error-screen"; import "./hui-root"; -import { HomeAssistant, PanelInfo } from "../../types"; +import { HomeAssistant, PanelInfo, Route } from "../../types"; import { Lovelace } from "./types"; import { LitElement, html, PropertyValues, TemplateResult } from "lit-element"; import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; @@ -22,7 +22,7 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) { public hass?: HomeAssistant; public narrow?: boolean; public showMenu?: boolean; - public route?: object; + public route?: Route; private _columns?: number; private _state?: "loading" | "loaded" | "error" | "yaml-editor"; private _errorMsg?: string; diff --git a/src/types.ts b/src/types.ts index 483c439751..532f539825 100644 --- a/src/types.ts +++ b/src/types.ts @@ -169,3 +169,16 @@ export interface PanelInfo { url_path: string; config: T; } + +export interface Route { + prefix: string; + path: string; +} + +export interface PanelElement extends HTMLElement { + hass?: HomeAssistant; + narrow?: boolean; + showMenu?: boolean; + route?: Route | null; + panel?: Panel; +}