From 23ca1b972dd0a3bddb500f7f46f38c77c46e0cc3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 15 Mar 2019 23:15:16 -0700 Subject: [PATCH] Fix router (#2943) * Fix router * Fix demo * Extract update routes * Lint --- demo/public/index.html | 3 +- src/html/index.html.template | 2 +- src/layouts/app/home-assistant.ts | 2 +- src/layouts/hass-loading-screen.ts | 4 +- src/layouts/hass-router-page.ts | 115 ++++++++----- src/layouts/partial-panel-resolver.ts | 203 +++++++++++------------ src/panels/config/ha-panel-config.ts | 11 +- src/panels/lovelace/ha-panel-lovelace.ts | 2 +- 8 files changed, 183 insertions(+), 159 deletions(-) diff --git a/demo/public/index.html b/demo/public/index.html index a685e87125..5a9e3e49a7 100644 --- a/demo/public/index.html +++ b/demo/public/index.html @@ -96,7 +96,8 @@ -
+
+ +
-
<% if (!latestBuild) { %> diff --git a/src/layouts/app/home-assistant.ts b/src/layouts/app/home-assistant.ts index 62529ef38b..fe7307f8d9 100644 --- a/src/layouts/app/home-assistant.ts +++ b/src/layouts/app/home-assistant.ts @@ -54,7 +54,7 @@ export class HomeAssistantAppEl extends ext(HassBaseMixin(LitElement), [ > ${this._panelUrl === undefined || this._route === undefined ? "" - : hass && hass.states && hass.config && hass.panels && hass.services + : hass && hass.states && hass.config && hass.services ? html` - ${this.isRoot + ${this.rootnav ? html` ` diff --git a/src/layouts/hass-router-page.ts b/src/layouts/hass-router-page.ts index b46119c0e3..3d5cab0d09 100644 --- a/src/layouts/hass-router-page.ts +++ b/src/layouts/hass-router-page.ts @@ -13,14 +13,13 @@ const extractPage = (path: string, defaultPage: string) => { : path.substr(1, subpathStart - 1); }; -interface RouteOptions { +export interface RouteOptions { tag: string; load: () => Promise; cache?: boolean; } export interface RouterOptions { - isRoot?: boolean; defaultPage?: string; preloadAll?: boolean; cacheAll?: boolean; @@ -34,17 +33,19 @@ export interface RouterOptions { const LOADING_SCREEN_THRESHOLD = 400; // ms export class HassRouterPage extends UpdatingElement { - protected static routerOptions: RouterOptions = { routes: {} }; + @property() public route?: Route; - protected static finalize() { - super.finalize(); - this._routerOptions = this.routerOptions; - } + protected routerOptions!: RouterOptions; - private static _routerOptions: RouterOptions; - - @property() public route!: Route; + /** + * Optional variable to define extra routes dynamically. + * It is preferred to use static routes. + */ + protected extraRoutes?: { + [route: string]: RouteOptions; + }; private _currentPage = ""; + private _currentLoadProm?: Promise; private _cache = {}; protected update(changedProps: PropertyValues) { @@ -52,15 +53,13 @@ export class HassRouterPage extends UpdatingElement { if (!changedProps.has("route")) { if (this.lastChild) { - this._updatePageEl(this.lastChild, changedProps); + this.updatePageEl(this.lastChild, changedProps); } return; } const route = this.route; - - const routerOptions = (this.constructor as typeof HassRouterPage) - ._routerOptions; + const routerOptions = this.routerOptions || { routes: {} }; const defaultPage = routerOptions.defaultPage || ""; if (route && route.path === "") { @@ -71,22 +70,22 @@ export class HassRouterPage extends UpdatingElement { if (this._currentPage === newPage) { if (this.lastChild) { - this._updatePageEl(this.lastChild, changedProps); + this.updatePageEl(this.lastChild, changedProps); + } + return; + } + + const routeOptions = routerOptions.routes[newPage]; + + if (!routeOptions) { + this._currentPage = ""; + if (this.lastChild) { + this.removeChild(this.lastChild); } return; } this._currentPage = newPage; - - const routeOptions = routerOptions.routes[newPage]; - - if (!routeOptions) { - if (this.lastChild) { - this._updatePageEl(this.lastChild, changedProps); - } - return; - } - const loadProm = routeOptions.load(); // Check when loading the page source failed. @@ -125,35 +124,69 @@ export class HassRouterPage extends UpdatingElement { if (this.lastChild) { this.removeChild(this.lastChild); } - - const loadingEl = document.createElement("hass-loading-screen"); - loadingEl.isRoot = routerOptions.isRoot; - this.appendChild(loadingEl); + this.appendChild(this.createLoadingScreen()); }, LOADING_SCREEN_THRESHOLD); - loadProm.then(() => { - // Check if we're still trying to show the same page. - if (this._currentPage !== newPage) { - return; - } + this._currentLoadProm = loadProm.then( + () => { + this._currentLoadProm = undefined; + // Check if we're still trying to show the same page. + if (this._currentPage !== newPage) { + return; + } - created = true; - this._createPanel(routerOptions, newPage, routeOptions); - }); + created = true; + this._createPanel(routerOptions, newPage, routeOptions); + }, + () => { + this._currentLoadProm = undefined; + } + ); } protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); - const options = (this.constructor as typeof HassRouterPage)._routerOptions; + const options = this.routerOptions; - if (options.preloadAll) { + if (options && options.preloadAll) { Object.values(options.routes).forEach((route) => route.load()); return; } } - protected _updatePageEl(_pageEl, _changedProps?: PropertyValues) { + protected createLoadingScreen() { + return document.createElement("hass-loading-screen"); + } + + /** + * Rebuild the current panel. + * + * Promise will resolve when rebuilding is done and DOM updated. + */ + protected async rebuild(): Promise { + const oldRoute = this.route; + + if (oldRoute === undefined) { + return; + } + + this.route = undefined; + await this.updateComplete; + // Make sure that the parent didn't override this in the meanwhile. + if (this.route === undefined) { + this.route = oldRoute; + } + } + + /** + * Promise that resolves when the page has rendered. + */ + protected get pageRendered(): Promise { + return this.updateComplete.then(() => this._currentLoadProm); + } + + protected updatePageEl(_pageEl, _changedProps?: PropertyValues) { // default we do nothing } @@ -168,7 +201,7 @@ export class HassRouterPage extends UpdatingElement { const panelEl = this._cache[page] || document.createElement(routeOptions.tag); - this._updatePageEl(panelEl); + this.updatePageEl(panelEl); this.appendChild(panelEl); if (routerOptions.cacheAll || routeOptions.cache) { diff --git a/src/layouts/partial-panel-resolver.ts b/src/layouts/partial-panel-resolver.ts index 7d5628fe82..0693e4b1f7 100644 --- a/src/layouts/partial-panel-resolver.ts +++ b/src/layouts/partial-panel-resolver.ts @@ -1,118 +1,96 @@ -import { property, customElement } from "lit-element"; +import { property, customElement, PropertyValues } from "lit-element"; import { PolymerElement } from "@polymer/polymer"; -import { HomeAssistant } from "../types"; -import { HassRouterPage, RouterOptions } from "./hass-router-page"; +import { HomeAssistant, Panels } from "../types"; +import { + HassRouterPage, + RouterOptions, + RouteOptions, +} from "./hass-router-page"; + +const CACHE_COMPONENTS = ["lovelace", "states"]; +const COMPONENTS = { + calendar: () => + import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"), + config: () => + import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"), + custom: () => + import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"), + "dev-event": () => + import(/* webpackChunkName: "panel-dev-event" */ "../panels/dev-event/ha-panel-dev-event"), + "dev-info": () => + import(/* webpackChunkName: "panel-dev-info" */ "../panels/dev-info/ha-panel-dev-info"), + "dev-mqtt": () => + import(/* webpackChunkName: "panel-dev-mqtt" */ "../panels/dev-mqtt/ha-panel-dev-mqtt"), + "dev-service": () => + import(/* webpackChunkName: "panel-dev-service" */ "../panels/dev-service/ha-panel-dev-service"), + "dev-state": () => + import(/* webpackChunkName: "panel-dev-state" */ "../panels/dev-state/ha-panel-dev-state"), + "dev-template": () => + import(/* webpackChunkName: "panel-dev-template" */ "../panels/dev-template/ha-panel-dev-template"), + lovelace: () => + import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"), + states: () => + import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"), + history: () => + import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"), + iframe: () => + import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"), + kiosk: () => + import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"), + logbook: () => + import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"), + mailbox: () => + import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"), + map: () => + import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map"), + profile: () => + import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"), + "shopping-list": () => + import(/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list"), +}; + +const getRoutes = (panels: Panels): RouterOptions => { + const routes: { [route: string]: RouteOptions } = {}; + + Object.values(panels).forEach((panel) => { + routes[panel.url_path] = { + load: COMPONENTS[panel.component_name], + tag: `ha-panel-${panel.component_name}`, + cache: CACHE_COMPONENTS.includes(panel.component_name), + }; + }); + + return { + showLoading: true, + routes, + }; +}; @customElement("partial-panel-resolver") class PartialPanelResolver extends HassRouterPage { - protected static routerOptions: RouterOptions = { - isRoot: true, - showLoading: true, - routes: { - calendar: { - tag: "ha-panel-calendar", - load: () => - import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"), - }, - config: { - tag: "ha-panel-config", - load: () => - import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"), - }, - custom: { - tag: "ha-panel-custom", - load: () => - import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"), - }, - "dev-event": { - tag: "ha-panel-dev-event", - load: () => - import(/* webpackChunkName: "panel-dev-event" */ "../panels/dev-event/ha-panel-dev-event"), - }, - "dev-info": { - tag: "ha-panel-dev-info", - load: () => - import(/* webpackChunkName: "panel-dev-info" */ "../panels/dev-info/ha-panel-dev-info"), - }, - "dev-mqtt": { - tag: "ha-panel-dev-mqtt", - load: () => - import(/* webpackChunkName: "panel-dev-mqtt" */ "../panels/dev-mqtt/ha-panel-dev-mqtt"), - }, - "dev-service": { - tag: "ha-panel-dev-service", - load: () => - import(/* webpackChunkName: "panel-dev-service" */ "../panels/dev-service/ha-panel-dev-service"), - }, - "dev-state": { - tag: "ha-panel-dev-state", - load: () => - import(/* webpackChunkName: "panel-dev-state" */ "../panels/dev-state/ha-panel-dev-state"), - }, - "dev-template": { - tag: "ha-panel-dev-template", - load: () => - import(/* webpackChunkName: "panel-dev-template" */ "../panels/dev-template/ha-panel-dev-template"), - }, - lovelace: { - cache: true, - tag: "ha-panel-lovelace", - load: () => - import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"), - }, - states: { - cache: true, - tag: "ha-panel-states", - load: () => - import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"), - }, - history: { - tag: "ha-panel-history", - load: () => - import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"), - }, - iframe: { - tag: "ha-panel-iframe", - load: () => - import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"), - }, - kiosk: { - tag: "ha-panel-kiosk", - load: () => - import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"), - }, - logbook: { - tag: "ha-panel-logbook", - load: () => - import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"), - }, - mailbox: { - tag: "ha-panel-mailbox", - load: () => - import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"), - }, - map: { - tag: "ha-panel-map", - load: () => - import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map"), - }, - profile: { - tag: "ha-panel-profile", - load: () => - import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"), - }, - "shopping-list": { - tag: "ha-panel-shopping-list", - load: () => - import(/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list"), - }, - }, - }; @property() public hass?: HomeAssistant; @property() public narrow?: boolean; - protected _updatePageEl(el) { + protected updated(changedProps: PropertyValues) { + if (!changedProps.has("hass")) { + return; + } + + const oldHass = changedProps.get("hass") as this["hass"]; + + if (!oldHass || oldHass.panels !== this.hass!.panels) { + this._updateRoutes(); + } + } + + protected createLoadingScreen() { + const el = super.createLoadingScreen(); + el.rootnav = true; + return el; + } + + protected updatePageEl(el) { const hass = this.hass!; if ("setProperties" in el) { @@ -130,6 +108,17 @@ class PartialPanelResolver extends HassRouterPage { el.panel = hass.panels[hass.panelUrl]; } } + + private async _updateRoutes() { + this.routerOptions = getRoutes(this.hass!.panels); + await this.rebuild(); + await this.pageRendered; + + const initEl = document.getElementById("ha-init-skeleton"); + if (initEl) { + initEl.parentElement!.removeChild(initEl); + } + } } declare global { diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index aad87ca75a..8fbb0629b3 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -8,7 +8,11 @@ import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page"; @customElement("ha-panel-config") class HaPanelConfig extends HassRouterPage { - protected static routerOptions: RouterOptions = { + @property() public hass!: HomeAssistant; + @property() public _wideSidebar: boolean = false; + @property() public _wide: boolean = false; + + protected routerOptions: RouterOptions = { defaultPage: "dashboard", cacheAll: true, preloadAll: true, @@ -81,9 +85,6 @@ class HaPanelConfig extends HassRouterPage { }, }; - @property() public hass!: HomeAssistant; - @property() public _wideSidebar: boolean = false; - @property() public _wide: boolean = false; @property() private _cloudStatus?: CloudStatus; private _listeners: Array<() => void> = []; @@ -119,7 +120,7 @@ class HaPanelConfig extends HassRouterPage { ); } - protected _updatePageEl(el) { + protected updatePageEl(el) { el.route = this.route; el.hass = this.hass; el.isWide = this.hass.dockedSidebar ? this._wideSidebar : this._wide; diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index 8bbb0389b9..d59ffaea9f 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -85,7 +85,7 @@ class LovelacePanel extends LitElement { } return html` - + `; }