Convert <home-assistant> to Lit/TS (#2586)

* Convert home-assistant element to Lit/TS

* Fix disconnect toast

* Lint
This commit is contained in:
Paulus Schoutsen 2019-01-27 10:41:35 -08:00 committed by GitHub
parent 9299d548ba
commit d6887758a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 262 additions and 234 deletions

View File

@ -1,5 +1,8 @@
import { HomeAssistant } from "../../src/layouts/app/home-assistant";
import { provideHass } from "../../src/fake_data/provide_hass";
import { HomeAssistantAppEl } from "../../src/layouts/app/home-assistant";
import {
provideHass,
MockHomeAssistant,
} from "../../src/fake_data/provide_hass";
import { navigate } from "../../src/common/navigate";
import { mockLovelace } from "./stubs/lovelace";
import { mockAuth } from "./stubs/auth";
@ -11,14 +14,14 @@ import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockEvents } from "./stubs/events";
import { mockMediaPlayer } from "./stubs/media_player";
import { HomeAssistant } from "../../src/types";
class HaDemo extends HomeAssistant {
class HaDemo extends HomeAssistantAppEl {
protected async _handleConnProm() {
const initial: Partial<HomeAssistant> = {
const initial: Partial<MockHomeAssistant> = {
panelUrl: (this as any).panelUrl,
// Override updateHass so that the correct hass lifecycle methods are called
updateHass: (hassUpdate) =>
// @ts-ignore
updateHass: (hassUpdate: Partial<HomeAssistant>) =>
this._updateHass(hassUpdate),
};

View File

@ -55,7 +55,7 @@ export interface HASSDomEvent<T> extends Event {
* @return {Event} The new event that was fired.
*/
export const fireEvent = <HassEvent extends ValidHassDomEvent>(
node: HTMLElement,
node: HTMLElement | Window,
type: HassEvent,
detail?: HASSDomEvents[HassEvent],
options?: {

View File

@ -1,7 +1,7 @@
import { fireEvent } from "./dom/fire_event";
export const navigate = (
node: HTMLElement,
_node: any,
path: string,
replace: boolean = false
) => {
@ -18,5 +18,5 @@ export const navigate = (
history.pushState(null, "", path);
}
}
fireEvent(node, "location-changed");
fireEvent(window, "location-changed");
};

View File

@ -1,9 +1,10 @@
import "@polymer/paper-toast/paper-toast";
// tslint:disable-next-line
const PaperToast = customElements.get("paper-toast");
class HaToast extends PaperToast {
connectedCallback() {
export class HaToast extends PaperToast {
public connectedCallback() {
super.connectedCallback();
if (!this._resizeListener) {
@ -15,10 +16,16 @@ class HaToast extends PaperToast {
this._resizeListener(this._mediaq);
}
disconnectedCallback() {
public disconnectedCallback() {
super.disconnectedCallback();
this._mediaq.removeListener(this._resizeListener);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-toast": HaToast;
}
}
customElements.define("ha-toast", HaToast);

View File

@ -6,8 +6,8 @@ import { subscribeUser } from "../../data/ws-user";
export default (superClass) =>
class extends superClass {
ready() {
super.ready();
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.addEventListener("hass-logout", () => this._handleLogout());
// HACK :( We don't have a way yet to trigger an update of `subscribeUser`
this.addEventListener("hass-refresh-current-user", () =>

View File

@ -20,8 +20,8 @@ import { subscribePanels } from "../../data/ws-panels";
export default (superClass) =>
class extends EventsMixin(LocalizeMixin(superClass)) {
ready() {
super.ready();
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this._handleConnProm();
}
@ -48,7 +48,7 @@ export default (superClass) =>
panels: null,
services: null,
user: null,
panelUrl: this.panelUrl,
panelUrl: this._panelUrl,
language: getActiveTranslation(),
// If resources are already loaded, don't discard them

View File

@ -1,5 +1,4 @@
import { PolymerElement } from "@polymer/polymer";
import { Constructor } from "lit-element";
import { Constructor, LitElement } from "lit-element";
import { HASSDomEvent, ValidHassDomEvent } from "../../common/dom/fire_event";
interface RegisterDialogParams {
@ -23,10 +22,10 @@ declare global {
}
}
export const dialogManagerMixin = (superClass: Constructor<PolymerElement>) =>
export const dialogManagerMixin = (superClass: Constructor<LitElement>) =>
class extends superClass {
public ready() {
super.ready();
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.addEventListener("register-dialog", (e) =>
this.registerDialog(e.detail)
);

View File

@ -1,27 +0,0 @@
import LocalizeMixin from "../../mixins/localize-mixin";
export default (superClass) =>
class extends LocalizeMixin(superClass) {
hassConnected() {
super.hassConnected();
// Need to load in advance because when disconnected, can't dynamically load code.
import(/* webpackChunkName: "ha-toast" */ "../../components/ha-toast");
}
hassReconnected() {
super.hassReconnected();
this.__discToast.opened = false;
}
hassDisconnected() {
super.hassDisconnected();
if (!this.__discToast) {
const el = document.createElement("ha-toast");
el.duration = 0;
el.text = this.localize("ui.notification_toast.connection_lost");
this.__discToast = el;
this.shadowRoot.appendChild(el);
}
this.__discToast.opened = true;
}
};

View File

@ -0,0 +1,40 @@
import { Constructor, LitElement } from "lit-element";
import { HassBaseEl } from "./hass-base-mixin";
import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin";
import { HaToast } from "../../components/ha-toast";
export default (superClass: Constructor<LitElement & HassBaseEl>) =>
class extends hassLocalizeLitMixin(superClass) {
private _discToast?: HaToast;
protected hassConnected() {
super.hassConnected();
// Need to load in advance because when disconnected, can't dynamically load code.
import(/* webpackChunkName: "ha-toast" */ "../../components/ha-toast");
}
protected hassReconnected() {
super.hassReconnected();
if (this._discToast) {
this._discToast.opened = false;
}
}
protected hassDisconnected() {
super.hassDisconnected();
if (!this._discToast) {
const el = document.createElement("ha-toast");
el.duration = 0;
// Temp. Somehow the localize func is not getting recalculated for
// this class. Manually generating one. Will be fixed when we move
// the localize function to the hass object.
const { language, resources } = this.hass!;
el.text = (this as any).__computeLocalize(language, resources)(
"ui.notification_toast.connection_lost"
);
this._discToast = el;
this.shadowRoot!.appendChild(el as any);
}
this._discToast.opened = true;
}
};

View File

@ -1,42 +0,0 @@
/* eslint-disable no-unused-vars */
export default (superClass) =>
class extends superClass {
constructor() {
super();
this.__pendingHass = false;
this.__provideHass = [];
}
// Exists so all methods can safely call super method
hassConnected() {}
hassReconnected() {}
hassDisconnected() {}
panelUrlChanged(newPanelUrl) {}
hassChanged(hass, oldHass) {
this.__provideHass.forEach((el) => {
el.hass = hass;
});
}
provideHass(el) {
this.__provideHass.push(el);
el.hass = this.hass;
}
async _updateHass(obj) {
const oldHass = this.hass;
this.hass = Object.assign({}, this.hass, obj);
this.__pendingHass = true;
await 0;
if (!this.__pendingHass) return;
this.__pendingHass = false;
this.hassChanged(this.hass, oldHass);
}
};

View File

@ -0,0 +1,60 @@
import { Constructor } from "lit-element";
import { HomeAssistant } from "../../types";
/* tslint:disable */
export class HassBaseEl {
protected hass?: HomeAssistant;
protected hassConnected() {}
protected hassReconnected() {}
protected hassDisconnected() {}
protected hassChanged(_hass: HomeAssistant, _oldHass?: HomeAssistant) {}
protected panelUrlChanged(_newPanelUrl: string) {}
protected provideHass(_el: HTMLElement) {}
protected _updateHass(_obj: Partial<HomeAssistant>) {}
}
export default <T>(superClass: Constructor<T>): Constructor<T & HassBaseEl> =>
// @ts-ignore
class extends superClass {
private __provideHass: HTMLElement[];
// @ts-ignore
protected hass: HomeAssistant;
constructor() {
super();
this.__provideHass = [];
}
// Exists so all methods can safely call super method
protected hassConnected() {
// tslint:disable-next-line
}
protected hassReconnected() {
// tslint:disable-next-line
}
protected hassDisconnected() {
// tslint:disable-next-line
}
protected panelUrlChanged(_newPanelUrl) {
// tslint:disable-next-line
}
protected hassChanged(hass, _oldHass) {
this.__provideHass.forEach((el) => {
(el as any).hass = hass;
});
}
protected provideHass(el) {
this.__provideHass.push(el);
el.hass = this.hass;
}
protected async _updateHass(obj) {
this.hass = { ...this.hass, ...obj };
}
};

View File

@ -1,120 +0,0 @@
import "@polymer/app-route/app-location";
import "@polymer/app-route/app-route";
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { afterNextRender } from "@polymer/polymer/lib/utils/render-status";
import { html as litHtml, LitElement } from "lit-element";
import "../home-assistant-main";
import "../ha-init-page";
import "../../resources/ha-style";
import registerServiceWorker from "../../util/register-service-worker";
import { DEFAULT_PANEL } from "../../common/const";
import HassBaseMixin from "./hass-base-mixin";
import AuthMixin from "./auth-mixin";
import TranslationsMixin from "./translations-mixin";
import ThemesMixin from "./themes-mixin";
import MoreInfoMixin from "./more-info-mixin";
import SidebarMixin from "./sidebar-mixin";
import { dialogManagerMixin } from "./dialog-manager-mixin";
import ConnectionMixin from "./connection-mixin";
import NotificationMixin from "./notification-mixin";
import DisconnectToastMixin from "./disconnect-toast-mixin";
LitElement.prototype.html = litHtml;
const ext = (baseClass, mixins) =>
mixins.reduceRight((base, mixin) => mixin(base), baseClass);
export class HomeAssistant extends ext(PolymerElement, [
AuthMixin,
ThemesMixin,
TranslationsMixin,
MoreInfoMixin,
SidebarMixin,
DisconnectToastMixin,
ConnectionMixin,
NotificationMixin,
dialogManagerMixin,
HassBaseMixin,
]) {
static get template() {
return html`
<app-location
route="{{route}}"
use-hash-as-path="[[_useHashAsPath]]"
></app-location>
<app-route
route="[[route]]"
pattern="/:panel"
data="{{routeData}}"
tail="{{subroute}}"
></app-route>
<template is="dom-if" if="[[showMain]]" restamp>
<home-assistant-main
hass="[[hass]]"
route="[[route]]"
tail="[[subroute]]"
></home-assistant-main>
</template>
<template is="dom-if" if="[[!showMain]]" restamp>
<ha-init-page error="[[_error]]"></ha-init-page>
</template>
`;
}
static get properties() {
return {
hass: {
type: Object,
value: null,
},
showMain: {
type: Boolean,
computed: "computeShowMain(hass)",
},
route: Object,
routeData: Object,
panelUrl: {
type: String,
computed: "computePanelUrl(routeData)",
observer: "panelUrlChanged",
},
_error: {
type: Boolean,
value: false,
},
};
}
ready() {
super.ready();
afterNextRender(null, registerServiceWorker);
}
computeShowMain(hass) {
return hass && hass.states && hass.config && hass.panels && hass.services;
}
computePanelUrl(routeData) {
return (
(routeData && routeData.panel) ||
localStorage.defaultPage ||
DEFAULT_PANEL
);
}
get _useHashAsPath() {
return __DEMO__;
}
panelUrlChanged(newPanelUrl) {
super.panelUrlChanged(newPanelUrl);
this._updateHass({ panelUrl: newPanelUrl });
}
}
customElements.define("home-assistant", HomeAssistant);

View File

@ -0,0 +1,119 @@
import "@polymer/app-route/app-location";
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import {
html,
LitElement,
PropertyDeclarations,
PropertyValues,
} from "lit-element";
import "../home-assistant-main";
import "../ha-init-page";
import "../../resources/ha-style";
import { registerServiceWorker } from "../../util/register-service-worker";
import { DEFAULT_PANEL } from "../../common/const";
import HassBaseMixin from "./hass-base-mixin";
import AuthMixin from "./auth-mixin";
import TranslationsMixin from "./translations-mixin";
import ThemesMixin from "./themes-mixin";
import MoreInfoMixin from "./more-info-mixin";
import SidebarMixin from "./sidebar-mixin";
import { dialogManagerMixin } from "./dialog-manager-mixin";
import ConnectionMixin from "./connection-mixin";
import NotificationMixin from "./notification-mixin";
import DisconnectToastMixin from "./disconnect-toast-mixin";
import { Route, HomeAssistant } from "../../types";
import { navigate } from "../../common/navigate";
(LitElement.prototype as any).html = html;
const ext = <T>(baseClass: T, mixins): T =>
mixins.reduceRight((base, mixin) => mixin(base), baseClass);
export class HomeAssistantAppEl extends ext(HassBaseMixin(LitElement), [
AuthMixin,
ThemesMixin,
TranslationsMixin,
MoreInfoMixin,
SidebarMixin,
DisconnectToastMixin,
ConnectionMixin,
NotificationMixin,
dialogManagerMixin,
]) {
private _route?: Route;
private _error?: boolean;
private _panelUrl?: string;
static get properties(): PropertyDeclarations {
return {
hass: {},
_route: {},
_routeData: {},
_panelUrl: {},
_error: {},
};
}
protected render() {
const hass = this.hass;
return html`
<app-location
@route-changed=${this._routeChanged}
?use-hash-as-path=${__DEMO__}
></app-location>
${this._panelUrl === undefined || this._route === undefined
? ""
: hass && hass.states && hass.config && hass.panels && hass.services
? html`
<home-assistant-main
.hass=${this.hass}
.route=${this._route}
></home-assistant-main>
`
: html`
<ha-init-page .error=${this._error}></ha-init-page>
`}
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
setTimeout(registerServiceWorker, 1000);
}
protected updated(changedProps: PropertyValues): void {
if (changedProps.has("_panelUrl")) {
this.panelUrlChanged(this._panelUrl!);
this._updateHass({ panelUrl: this._panelUrl });
}
if (changedProps.has("hass")) {
this.hassChanged(this.hass!, changedProps.get("hass") as
| HomeAssistant
| undefined);
}
}
private _routeChanged(ev) {
const route = ev.detail.value as Route;
// If it's the first route that we process,
// check if we should navigate away from /
if (this._route === undefined && route.path === "/") {
navigate(window, `/${localStorage.defaultPage || DEFAULT_PANEL}`, true);
return;
}
this._route = route;
const dividerPos = route.path.indexOf("/", 1);
this._panelUrl =
dividerPos === -1
? route.path.substr(1)
: route.path.substr(1, dividerPos - 1);
}
}
customElements.define("home-assistant", HomeAssistantAppEl);

View File

@ -2,8 +2,8 @@ import { afterNextRender } from "@polymer/polymer/lib/utils/render-status";
export default (superClass) =>
class extends superClass {
ready() {
super.ready();
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.addEventListener("hass-more-info", (e) => this._handleMoreInfo(e));
// Load it once we are having the initial rendering done.

View File

@ -1,7 +1,7 @@
export default (superClass) =>
class extends superClass {
ready() {
super.ready();
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.registerDialog({
dialogShowEvent: "hass-notification",
dialogTag: "notification-manager",

View File

@ -2,8 +2,8 @@ import { storeState } from "../../util/ha-pref-storage";
export default (superClass) =>
class extends superClass {
ready() {
super.ready();
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.addEventListener("hass-dock-sidebar", (e) =>
this._handleDockSidebar(e)
);

View File

@ -4,9 +4,8 @@ import { subscribeThemes } from "../../data/ws-themes";
export default (superClass) =>
class extends superClass {
ready() {
super.ready();
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.addEventListener("settheme", (ev) => {
this._updateHass({ selectedTheme: ev.detail });
this._applyTheme();

View File

@ -8,8 +8,8 @@ import { storeState } from "../../util/ha-pref-storage";
export default (superClass) =>
class extends superClass {
ready() {
super.ready();
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.addEventListener("hass-language-select", (e) =>
this._selectLanguage(e)
);
@ -81,6 +81,6 @@ export default (superClass) =>
storeState(this.hass);
this._loadResources();
this._loadBackendTranslations();
this._loadTranslationFragment(this.panelUrl);
this._loadTranslationFragment(this.hass.panelUrl);
}
};

View File

@ -81,9 +81,6 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
return {
hass: Object,
narrow: Boolean,
tail: {
type: Object,
},
route: {
type: Object,
observer: "_routeChanged",
@ -136,13 +133,6 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
}
}
connectedCallback() {
super.connectedCallback();
if (this.tail.prefix === "") {
this.navigate(`/${localStorage.defaultPage || DEFAULT_PANEL}`, true);
}
}
computeForceNarrow(narrow, dockedSidebar) {
return narrow || !dockedSidebar;
}

View File

@ -1,7 +1,7 @@
const serviceWorkerUrl =
__BUILD__ === "latest" ? "/service_worker.js" : "/service_worker_es5.js";
export default () => {
export const registerServiceWorker = () => {
if (!("serviceWorker" in navigator)) return;
navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => {