Clean up hassio panel (#3067)

* Clean up hassio panel

* Extract dialog manager code

* Convert markdown dialog to show-dialog

* Extract snapshot dialog
This commit is contained in:
Paulus Schoutsen 2019-04-07 08:45:56 -07:00 committed by Pascal Vizeli
parent 31e351c75c
commit cda29fcd07
21 changed files with 466 additions and 432 deletions

View File

@ -12,6 +12,7 @@ import "../../../src/resources/ha-style";
import EventsMixin from "../../../src/mixins/events-mixin"; import EventsMixin from "../../../src/mixins/events-mixin";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
import "../components/hassio-card-content"; import "../components/hassio-card-content";
const PERMIS_DESC = { const PERMIS_DESC = {
@ -517,7 +518,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
showMoreInfo(e) { showMoreInfo(e) {
const id = e.target.getAttribute("id"); const id = e.target.getAttribute("id");
this.fire("hassio-markdown-dialog", { showHassioMarkdownDialog(this, {
title: PERMIS_DESC[id].title, title: PERMIS_DESC[id].title,
content: PERMIS_DESC[id].description, content: PERMIS_DESC[id].description,
}); });
@ -528,7 +529,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
.callApi("get", `hassio/addons/${this.addonSlug}/changelog`) .callApi("get", `hassio/addons/${this.addonSlug}/changelog`)
.then((resp) => resp, () => "Error getting changelog") .then((resp) => resp, () => "Error getting changelog")
.then((content) => { .then((content) => {
this.fire("hassio-markdown-dialog", { showHassioMarkdownDialog(this, {
title: "Changelog", title: "Changelog",
content: content, content: content,
}); });

View File

@ -1,14 +1,11 @@
import "@polymer/app-layout/app-header-layout/app-header-layout"; import "@polymer/app-layout/app-header-layout/app-header-layout";
import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/app-route/app-route";
import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-menu-button";
import "../../../src/resources/ha-style"; import "../../../src/resources/ha-style";
import "../hassio-markdown-dialog";
import "./hassio-addon-audio"; import "./hassio-addon-audio";
import "./hassio-addon-config"; import "./hassio-addon-config";
import "./hassio-addon-info"; import "./hassio-addon-info";
@ -51,16 +48,9 @@ class HassioAddonView extends PolymerElement {
} }
} }
</style> </style>
<app-route
route="[[route]]"
pattern="/addon/:slug"
data="{{routeData}}"
active="{{routeMatches}}"
></app-route>
<app-header-layout has-scrolling-region=""> <app-header-layout has-scrolling-region="">
<app-header fixed="" slot="header"> <app-header fixed="" slot="header">
<app-toolbar> <app-toolbar>
<ha-menu-button hassio></ha-menu-button>
<paper-icon-button <paper-icon-button
icon="hassio:arrow-left" icon="hassio:arrow-left"
on-click="backTapped" on-click="backTapped"
@ -72,14 +62,14 @@ class HassioAddonView extends PolymerElement {
<hassio-addon-info <hassio-addon-info
hass="[[hass]]" hass="[[hass]]"
addon="[[addon]]" addon="[[addon]]"
addon-slug="[[routeData.slug]]" addon-slug="[[addonSlug]]"
></hassio-addon-info> ></hassio-addon-info>
<template is="dom-if" if="[[addon.version]]"> <template is="dom-if" if="[[addon.version]]">
<hassio-addon-config <hassio-addon-config
hass="[[hass]]" hass="[[hass]]"
addon="[[addon]]" addon="[[addon]]"
addon-slug="[[routeData.slug]]" addon-slug="[[addonSlug]]"
></hassio-addon-config> ></hassio-addon-config>
<template is="dom-if" if="[[addon.audio]]"> <template is="dom-if" if="[[addon.audio]]">
@ -93,50 +83,38 @@ class HassioAddonView extends PolymerElement {
<hassio-addon-network <hassio-addon-network
hass="[[hass]]" hass="[[hass]]"
addon="[[addon]]" addon="[[addon]]"
addon-slug="[[routeData.slug]]" addon-slug="[[addonSlug]]"
></hassio-addon-network> ></hassio-addon-network>
</template> </template>
<hassio-addon-logs <hassio-addon-logs
hass="[[hass]]" hass="[[hass]]"
addon-slug="[[routeData.slug]]" addon-slug="[[addonSlug]]"
></hassio-addon-logs> ></hassio-addon-logs>
</template> </template>
</div> </div>
</app-header-layout> </app-header-layout>
<hassio-markdown-dialog
title="[[markdownTitle]]"
content="[[markdownContent]]"
></hassio-markdown-dialog>
`; `;
} }
static get properties() { static get properties() {
return { return {
hass: Object, hass: Object,
route: Object, route: {
routeData: {
type: Object, type: Object,
observer: "routeDataChanged", observer: "routeDataChanged",
}, },
routeMatches: Boolean, addonSlug: {
addon: Object,
markdownTitle: String,
markdownContent: {
type: String, type: String,
value: "", computed: "_computeSlug(route)",
}, },
addon: Object,
}; };
} }
ready() { ready() {
super.ready(); super.ready();
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev)); this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
this.addEventListener("hassio-markdown-dialog", (ev) =>
this.openMarkdown(ev)
);
} }
apiCalled(ev) { apiCalled(ev) {
@ -147,13 +125,13 @@ class HassioAddonView extends PolymerElement {
if (path.substr(path.lastIndexOf("/") + 1) === "uninstall") { if (path.substr(path.lastIndexOf("/") + 1) === "uninstall") {
this.backTapped(); this.backTapped();
} else { } else {
this.routeDataChanged(this.routeData); this.routeDataChanged(this.route);
} }
} }
routeDataChanged(routeData) { routeDataChanged(routeData) {
if (!this.routeMatches || !routeData || !routeData.slug) return; const addon = routeData.path.substr(1);
this.hass.callApi("get", `hassio/addons/${routeData.slug}/info`).then( this.hass.callApi("get", `hassio/addons/${addon}/info`).then(
(info) => { (info) => {
this.addon = info.data; this.addon = info.data;
}, },
@ -167,12 +145,8 @@ class HassioAddonView extends PolymerElement {
history.back(); history.back();
} }
openMarkdown(ev) { _computeSlug(route) {
this.setProperties({ return route.path.substr(1);
markdownTitle: ev.detail.title,
markdownContent: ev.detail.content,
});
this.shadowRoot.querySelector("hassio-markdown-dialog").openDialog();
} }
} }

View File

@ -4,10 +4,13 @@ import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import "../../src/resources/ha-style"; import "../../../../src/resources/ha-style";
import "../../src/components/dialog/ha-paper-dialog"; import "../../../../src/components/dialog/ha-paper-dialog";
import { customElement } from "lit-element";
import { PaperDialogElement } from "@polymer/paper-dialog";
@customElement("dialog-hassio-markdown")
class HassioMarkdownDialog extends PolymerElement { class HassioMarkdownDialog extends PolymerElement {
static get template() { static get template() {
return html` return html`
@ -72,8 +75,14 @@ class HassioMarkdownDialog extends PolymerElement {
}; };
} }
openDialog() { public showDialog(params) {
this.$.dialog.open(); this.setProperties(params);
(this.$.dialog as PaperDialogElement).open();
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-markdown": HassioMarkdownDialog;
} }
} }
customElements.define("hassio-markdown-dialog", HassioMarkdownDialog);

View File

@ -0,0 +1,18 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
export interface HassioMarkdownDialogParams {
title: string;
content: string;
}
export const showHassioMarkdownDialog = (
element: HTMLElement,
dialogParams: HassioMarkdownDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-markdown",
dialogImport: () =>
import(/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"),
dialogParams,
});
};

View File

@ -6,12 +6,24 @@ import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag"; import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getSignedPath } from "../../../src/auth/data"; import { getSignedPath } from "../../../../src/auth/data";
import "../../../src/resources/ha-style"; import "../../../../src/resources/ha-style";
import "../../../src/components/dialog/ha-paper-dialog"; import "../../../../src/components/dialog/ha-paper-dialog";
import { customElement } from "lit-element";
import { HomeAssistant } from "../../../../src/types";
import { PaperDialogElement } from "@polymer/paper-dialog";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
@customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog extends PolymerElement {
public hass!: HomeAssistant;
protected error?: string;
private snapshot?: any;
private dialogParams?: HassioSnapshotDialogParams;
private restoreHass!: boolean;
private snapshotPassword!: string;
class HassioSnapshot extends PolymerElement {
static get template() { static get template() {
return html` return html`
<style include="ha-style-dialog"> <style include="ha-style-dialog">
@ -139,15 +151,6 @@ class HassioSnapshot extends PolymerElement {
static get properties() { static get properties() {
return { return {
hass: Object, hass: Object,
snapshotSlug: {
type: String,
notify: true,
observer: "_snapshotSlugChanged",
},
snapshotDeleted: {
type: Boolean,
notify: true,
},
snapshot: Object, snapshot: Object,
restoreHass: { restoreHass: {
type: Boolean, type: Boolean,
@ -158,39 +161,43 @@ class HassioSnapshot extends PolymerElement {
}; };
} }
_snapshotSlugChanged(snapshotSlug) { public showDialog(params: HassioSnapshotDialogParams) {
if (!snapshotSlug || snapshotSlug === "update") return; this.dialogParams = params;
this.hass.callApi("get", `hassio/snapshots/${snapshotSlug}/info`).then( this.hass.callApi("GET", `hassio/snapshots/${params.slug}/info`).then(
(info) => { (info: any) => {
info.data.folders = this._computeFolders(info.data.folders); info.data.folders = this._computeFolders(info.data.folders);
info.data.addons = this._computeAddons(info.data.addons); info.data.addons = this._computeAddons(info.data.addons);
this.snapshot = info.data; this.setProperties({ snapshot: info.data });
this.$.dialog.open(); (this.$.dialog as PaperDialogElement).open();
}, },
() => { () => {
this.snapshot = null; this.dialogParams = undefined;
} }
); );
} }
_computeFolders(folders) { protected _computeFolders(folders) {
const list = []; const list: Array<{ slug: string; name: string; checked: boolean }> = [];
if (folders.includes("homeassistant")) if (folders.includes("homeassistant")) {
list.push({ list.push({
slug: "homeassistant", slug: "homeassistant",
name: "Home Assistant configuration", name: "Home Assistant configuration",
checked: true, checked: true,
}); });
if (folders.includes("ssl")) }
if (folders.includes("ssl")) {
list.push({ slug: "ssl", name: "SSL", checked: true }); list.push({ slug: "ssl", name: "SSL", checked: true });
if (folders.includes("share")) }
if (folders.includes("share")) {
list.push({ slug: "share", name: "Share", checked: true }); list.push({ slug: "share", name: "Share", checked: true });
if (folders.includes("addons/local")) }
if (folders.includes("addons/local")) {
list.push({ slug: "addons/local", name: "Local add-ons", checked: true }); list.push({ slug: "addons/local", name: "Local add-ons", checked: true });
}
return list; return list;
} }
_computeAddons(addons) { protected _computeAddons(addons) {
return addons.map((addon) => ({ return addons.map((addon) => ({
slug: addon.slug, slug: addon.slug,
name: addon.name, name: addon.name,
@ -199,11 +206,11 @@ class HassioSnapshot extends PolymerElement {
})); }));
} }
_isFullSnapshot(type) { protected _isFullSnapshot(type) {
return type === "full"; return type === "full";
} }
_partialRestoreClicked() { protected _partialRestoreClicked() {
if (!confirm("Are you sure you want to restore this snapshot?")) { if (!confirm("Are you sure you want to restore this snapshot?")) {
return; return;
} }
@ -216,21 +223,24 @@ class HassioSnapshot extends PolymerElement {
const data = { const data = {
homeassistant: this.restoreHass, homeassistant: this.restoreHass,
addons: addons, addons,
folders: folders, folders,
}; };
if (this.snapshot.protected) data.password = this.snapshotPassword; if (this.snapshot.protected) {
// @ts-ignore
data.password = this.snapshotPassword;
}
this.hass this.hass
.callApi( .callApi(
"post", "POST",
`hassio/snapshots/${this.snapshotSlug}/restore/partial`, `hassio/snapshots/${this.dialogParams!.slug}/restore/partial`,
data data
) )
.then( .then(
() => { () => {
alert("Snapshot restored!"); alert("Snapshot restored!");
this.$.dialog.close(); (this.$.dialog as PaperDialogElement).close();
}, },
(error) => { (error) => {
this.error = error.body.message; this.error = error.body.message;
@ -238,23 +248,23 @@ class HassioSnapshot extends PolymerElement {
); );
} }
_fullRestoreClicked() { protected _fullRestoreClicked() {
if (!confirm("Are you sure you want to restore this snapshot?")) { if (!confirm("Are you sure you want to restore this snapshot?")) {
return; return;
} }
const data = this.snapshot.protected const data = this.snapshot.protected
? { password: this.snapshotPassword } ? { password: this.snapshotPassword }
: null; : undefined;
this.hass this.hass
.callApi( .callApi(
"post", "POST",
`hassio/snapshots/${this.snapshotSlug}/restore/full`, `hassio/snapshots/${this.dialogParams!.slug}/restore/full`,
data data
) )
.then( .then(
() => { () => {
alert("Snapshot restored!"); alert("Snapshot restored!");
this.$.dialog.close(); (this.$.dialog as PaperDialogElement).close();
}, },
(error) => { (error) => {
this.error = error.body.message; this.error = error.body.message;
@ -262,16 +272,16 @@ class HassioSnapshot extends PolymerElement {
); );
} }
_deleteClicked() { protected _deleteClicked() {
if (!confirm("Are you sure you want to delete this snapshot?")) { if (!confirm("Are you sure you want to delete this snapshot?")) {
return; return;
} }
this.hass this.hass
.callApi("post", `hassio/snapshots/${this.snapshotSlug}/remove`) .callApi("POST", `hassio/snapshots/${this.dialogParams!.slug}/remove`)
.then( .then(
() => { () => {
this.$.dialog.close(); (this.$.dialog as PaperDialogElement).close();
this.snapshotDeleted = true; this.dialogParams!.onDelete();
}, },
(error) => { (error) => {
this.error = error.body.message; this.error = error.body.message;
@ -279,19 +289,19 @@ class HassioSnapshot extends PolymerElement {
); );
} }
async _downloadClicked() { protected async _downloadClicked() {
let signedPath; let signedPath;
try { try {
signedPath = await getSignedPath( signedPath = await getSignedPath(
this.hass, this.hass,
`/api/hassio/snapshots/${this.snapshotSlug}/download` `/api/hassio/snapshots/${this.dialogParams!.slug}/download`
); );
} catch (err) { } catch (err) {
alert(`Error: ${err.message}`); alert(`Error: ${err.message}`);
return; return;
} }
const name = this._computeName(this.snapshot).replace(/[^a-z0-9]+/gi, "_"); const name = this._computeName(this.snapshot).replace(/[^a-z0-9]+/gi, "_");
const a = document.createElement("A"); const a = document.createElement("a");
a.href = signedPath.path; a.href = signedPath.path;
a.download = `Hass_io_${name}.tar`; a.download = `Hass_io_${name}.tar`;
this.$.dialog.appendChild(a); this.$.dialog.appendChild(a);
@ -299,23 +309,23 @@ class HassioSnapshot extends PolymerElement {
this.$.dialog.removeChild(a); this.$.dialog.removeChild(a);
} }
_computeName(snapshot) { protected _computeName(snapshot) {
return snapshot.name || snapshot.slug; return snapshot.name || snapshot.slug;
} }
_computeType(type) { protected _computeType(type) {
return type === "full" ? "Full snapshot" : "Partial snapshot"; return type === "full" ? "Full snapshot" : "Partial snapshot";
} }
_computeSize(size) { protected _computeSize(size) {
return Math.ceil(size * 10) / 10 + " MB"; return Math.ceil(size * 10) / 10 + " MB";
} }
_sortAddons(a, b) { protected _sortAddons(a, b) {
return a.name < b.name ? -1 : 1; return a.name < b.name ? -1 : 1;
} }
_formatDatetime(datetime) { protected _formatDatetime(datetime) {
return new Date(datetime).toLocaleDateString(navigator.language, { return new Date(datetime).toLocaleDateString(navigator.language, {
weekday: "long", weekday: "long",
year: "numeric", year: "numeric",
@ -326,8 +336,14 @@ class HassioSnapshot extends PolymerElement {
}); });
} }
_dialogClosed() { protected _dialogClosed() {
this.snapshotSlug = null; this.dialogParams = undefined;
this.snapshot = undefined;
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-snapshot": HassioSnapshotDialog;
} }
} }
customElements.define("hassio-snapshot", HassioSnapshot);

View File

@ -0,0 +1,18 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
export interface HassioSnapshotDialogParams {
slug: string;
onDelete: () => void;
}
export const showHassioSnapshotDialog = (
element: HTMLElement,
dialogParams: HassioSnapshotDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot",
dialogImport: () =>
import(/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"),
dialogParams,
});
};

View File

@ -1,6 +1,6 @@
window.loadES5Adapter().then(() => { window.loadES5Adapter().then(() => {
import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons.js"); import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons");
import(/* webpackChunkName: "hassio-main" */ "./hassio-main.js"); import(/* webpackChunkName: "hassio-main" */ "./hassio-main");
}); });
const styleEl = document.createElement("style"); const styleEl = document.createElement("style");
styleEl.innerHTML = ` styleEl.innerHTML = `

View File

@ -1,59 +0,0 @@
import { PolymerElement } from "@polymer/polymer/polymer-element";
class HassioData extends PolymerElement {
static get properties() {
return {
hass: Object,
supervisor: {
type: Object,
notify: true,
},
host: {
type: Object,
notify: true,
},
homeassistant: {
type: Object,
notify: true,
},
};
}
connectedCallback() {
super.connectedCallback();
this.refresh();
}
refresh() {
return Promise.all([
this.fetchSupervisorInfo(),
this.fetchHostInfo(),
this.fetchHassInfo(),
]);
}
fetchSupervisorInfo() {
return this.hass.callApi("get", "hassio/supervisor/info").then((info) => {
this.supervisor = info.data;
});
}
fetchHostInfo() {
return this.hass.callApi("get", "hassio/host/info").then((info) => {
this.host = info.data;
});
}
fetchHassInfo() {
return this.hass
.callApi("get", "hassio/homeassistant/info")
.then((info) => {
this.homeassistant = info.data;
});
}
}
customElements.define("hassio-data", HassioData);

View File

@ -1,158 +0,0 @@
import "@polymer/app-route/app-route";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../src/layouts/hass-loading-screen";
import "./addon-view/hassio-addon-view";
import "./ingress-view/hassio-ingress-view";
import "./hassio-data";
import "./hassio-pages-with-tabs";
import "../../src/resources/ha-style";
import applyThemesOnElement from "../../src/common/dom/apply_themes_on_element";
import EventsMixin from "../../src/mixins/events-mixin";
import NavigateMixin from "../../src/mixins/navigate-mixin";
import { fireEvent } from "../../src/common/dom/fire_event";
class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
static get template() {
return html`
<app-route
route="[[route]]"
pattern="/:page"
data="{{routeData}}"
></app-route>
<hassio-data
id="data"
hass="[[hass]]"
supervisor="{{supervisorInfo}}"
homeassistant="{{hassInfo}}"
host="{{hostInfo}}"
></hassio-data>
<template is="dom-if" if="[[!loaded]]">
<hass-loading-screen></hass-loading-screen>
</template>
<template is="dom-if" if="[[loaded]]">
<template is="dom-if" if="[[equalsDashboard(routeData.page)]]">
<hassio-pages-with-tabs
hass="[[hass]]"
page="[[routeData.page]]"
supervisor-info="[[supervisorInfo]]"
hass-info="[[hassInfo]]"
host-info="[[hostInfo]]"
></hassio-pages-with-tabs>
</template>
<template is="dom-if" if="[[equalsAddon(routeData.page)]]">
<hassio-addon-view
hass="[[hass]]"
route="[[route]]"
></hassio-addon-view>
</template>
<template is="dom-if" if="[[equalsIngress(routeData.page)]]">
<hassio-ingress-view
hass="[[hass]]"
route="[[route]]"
></hassio-ingress-view>
</template>
</template>
`;
}
static get properties() {
return {
hass: Object,
route: {
type: Object,
// Fake route object
value: {
prefix: "/hassio",
path: "/dashboard",
__queryParams: {},
},
observer: "routeChanged",
},
routeData: Object,
supervisorInfo: Object,
hostInfo: Object,
hassInfo: Object,
loaded: {
type: Boolean,
computed: "computeIsLoaded(supervisorInfo, hostInfo, hassInfo)",
},
};
}
ready() {
super.ready();
applyThemesOnElement(this, this.hass.themes, this.hass.selectedTheme, true);
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
// Paulus - March 17, 2019
// We went to a single hass-toggle-menu event in HA 0.90. However, the
// supervisor UI can also run under older versions of Home Assistant.
// So here we are going to translate toggle events into the appropriate
// open and close events. These events are a no-op in newer versions of
// Home Assistant.
this.addEventListener("hass-toggle-menu", () => {
fireEvent(
window.parent.customPanel,
this.hass.dockedSidebar ? "hass-close-menu" : "hass-open-menu"
);
});
// Paulus - March 19, 2019
// We changed the navigate event to fire directly on the window, as that's
// where we are listening for it. However, the older panel_custom will
// listen on this element for navigation events, so we need to forward them.
window.addEventListener("location-changed", (ev) =>
fireEvent(this, ev.type, ev.detail, {
bubbles: false,
})
);
}
connectedCallback() {
super.connectedCallback();
this.routeChanged(this.route);
}
apiCalled(ev) {
if (ev.detail.success) {
let tries = 1;
const tryUpdate = () => {
this.$.data.refresh().catch(function() {
tries += 1;
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
});
};
tryUpdate();
}
}
computeIsLoaded(supervisorInfo, hostInfo, hassInfo) {
return supervisorInfo !== null && hostInfo !== null && hassInfo !== null;
}
routeChanged(route) {
if (route.path === "" && route.prefix === "/hassio") {
this.navigate("/hassio/dashboard", true);
}
fireEvent(this, "iron-resize");
}
equalsAddon(page) {
return page && page === "addon";
}
equalsDashboard(page) {
return !page || !["addon", "ingress"].includes(page);
}
equalsIngress(page) {
return page && page === "ingress";
}
}
customElements.define("hassio-main", HassioMain);

138
hassio/src/hassio-main.ts Normal file
View File

@ -0,0 +1,138 @@
import { customElement, PropertyValues, property } from "lit-element";
import { PolymerElement } from "@polymer/polymer";
import "../../src/resources/ha-style";
import applyThemesOnElement from "../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../src/common/dom/fire_event";
import {
HassRouterPage,
RouterOptions,
} from "../../src/layouts/hass-router-page";
import { HomeAssistant } from "../../src/types";
import {
fetchHassioSupervisorInfo,
fetchHassioHostInfo,
fetchHassioHomeAssistantInfo,
} from "../../src/data/hassio";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
@customElement("hassio-main")
class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
@property() public hass!: HomeAssistant;
protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard",
initialLoad: () => this._fetchData(),
routes: {
dashboard: {
tag: "hassio-pages-with-tabs",
load: () => import("./hassio-pages-with-tabs"),
cache: true,
},
snapshots: "dashboard",
store: "dashboard",
system: "dashboard",
addon: {
tag: "hassio-addon-view",
load: () => import("./addon-view/hassio-addon-view"),
},
ingress: {
tag: "hassio-ingress-view",
load: () => import("./ingress-view/hassio-ingress-view"),
},
},
};
@property() private _supervisorInfo: any;
@property() private _hostInfo: any;
@property() private _hassInfo: any;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
applyThemesOnElement(this, this.hass.themes, this.hass.selectedTheme, true);
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
// Paulus - March 17, 2019
// We went to a single hass-toggle-menu event in HA 0.90. However, the
// supervisor UI can also run under older versions of Home Assistant.
// So here we are going to translate toggle events into the appropriate
// open and close events. These events are a no-op in newer versions of
// Home Assistant.
this.addEventListener("hass-toggle-menu", () => {
fireEvent(
(window.parent as any).customPanel,
// @ts-ignore
this.hass.dockedSidebar ? "hass-close-menu" : "hass-open-menu"
);
});
// Paulus - March 19, 2019
// We changed the navigate event to fire directly on the window, as that's
// where we are listening for it. However, the older panel_custom will
// listen on this element for navigation events, so we need to forward them.
window.addEventListener("location-changed", (ev) =>
// @ts-ignore
fireEvent(this, ev.type, ev.detail, {
bubbles: false,
})
);
makeDialogManager(this, document.body);
}
protected updatePageEl(el) {
if ("setProperties" in el) {
// As long as we have Polymer pages
(el as PolymerElement).setProperties({
hass: this.hass,
supervisorInfo: this._supervisorInfo,
hostInfo: this._hostInfo,
hassInfo: this._hassInfo,
// @ts-ignore not fighting TS today
route: this.routeTail,
});
} else {
el.hass = this.hass;
el.supervisorInfo = this._supervisorInfo;
el.hostInfo = this._hostInfo;
el.hassInfo = this._hassInfo;
// @ts-ignore not fighting TS today
el.route = this.routeTail;
}
}
private async _fetchData() {
const [supervisorInfo, hostInfo, hassInfo] = await Promise.all([
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
]);
this._supervisorInfo = supervisorInfo;
this._hostInfo = hostInfo;
this._hassInfo = hassInfo;
}
private _apiCalled(ev) {
if (!ev.detail.success) {
return;
}
let tries = 1;
const tryUpdate = () => {
this._fetchData().catch(() => {
tries += 1;
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
});
};
tryUpdate();
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-main": HassioMain;
}
}

View File

@ -11,8 +11,6 @@ import "../../src/components/ha-menu-button";
import "../../src/resources/ha-style"; import "../../src/resources/ha-style";
import "./addon-store/hassio-addon-store"; import "./addon-store/hassio-addon-store";
import "./dashboard/hassio-dashboard"; import "./dashboard/hassio-dashboard";
import "./hassio-markdown-dialog";
import "./snapshots/hassio-snapshot";
import "./snapshots/hassio-snapshots"; import "./snapshots/hassio-snapshots";
import "./system/hassio-system"; import "./system/hassio-system";
@ -69,8 +67,6 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
<hassio-snapshots <hassio-snapshots
hass="[[hass]]" hass="[[hass]]"
installed-addons="[[supervisorInfo.addons]]" installed-addons="[[supervisorInfo.addons]]"
snapshot-slug="{{snapshotSlug}}"
snapshot-deleted="{{snapshotDeleted}}"
></hassio-snapshots> ></hassio-snapshots>
</template> </template>
<template is="dom-if" if='[[equals(page, "store")]]'> <template is="dom-if" if='[[equals(page, "store")]]'>
@ -84,47 +80,23 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
></hassio-system> ></hassio-system>
</template> </template>
</app-header-layout> </app-header-layout>
<hassio-markdown-dialog
title="[[markdownTitle]]"
content="[[markdownContent]]"
></hassio-markdown-dialog>
<template is="dom-if" if='[[equals(page, "snapshots")]]'>
<hassio-snapshot
hass="[[hass]]"
snapshot-slug="{{snapshotSlug}}"
snapshot-deleted="{{snapshotDeleted}}"
></hassio-snapshot>
</template>
`; `;
} }
static get properties() { static get properties() {
return { return {
hass: Object, hass: Object,
page: String, page: {
type: String,
computed: "_computePage(route)",
},
route: Object,
supervisorInfo: Object, supervisorInfo: Object,
hostInfo: Object, hostInfo: Object,
hassInfo: Object, hassInfo: Object,
snapshotSlug: String,
snapshotDeleted: Boolean,
markdownTitle: String,
markdownContent: {
type: String,
value: "",
},
}; };
} }
ready() {
super.ready();
this.addEventListener("hassio-markdown-dialog", (ev) =>
this.openMarkdown(ev)
);
}
handlePageSelected(ev) { handlePageSelected(ev) {
const newPage = ev.detail.item.getAttribute("page-name"); const newPage = ev.detail.item.getAttribute("page-name");
if (newPage !== this.page) { if (newPage !== this.page) {
@ -149,12 +121,8 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
} }
} }
openMarkdown(ev) { _computePage(route) {
this.setProperties({ return route.prefix.substr(route.prefix.indexOf("/", 1) + 1);
markdownTitle: ev.detail.title,
markdownContent: ev.detail.content,
});
this.shadowRoot.querySelector("hassio-markdown-dialog").openDialog();
} }
} }

View File

@ -17,14 +17,6 @@ import {
import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage"; import "../../../src/layouts/hass-subpage";
const extractAddon = (path: string) => {
path = path.substr("/ingress".length);
const subpathStart = path.indexOf("/", 1);
return subpathStart === -1
? path.substr(1)
: path.substr(1, subpathStart - 1);
};
@customElement("hassio-ingress-view") @customElement("hassio-ingress-view")
class HassioIngressView extends LitElement { class HassioIngressView extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@ -55,10 +47,10 @@ class HassioIngressView extends LitElement {
return; return;
} }
const addon = extractAddon(this.route.path); const addon = this.route.path.substr(1);
const oldRoute = changedProps.get("route") as this["route"] | undefined; const oldRoute = changedProps.get("route") as this["route"] | undefined;
const oldAddon = oldRoute ? extractAddon(oldRoute.path) : undefined; const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined;
if (addon && addon !== oldAddon) { if (addon && addon !== oldAddon) {
this._createSession(); this._createSession();

View File

@ -11,6 +11,8 @@ import "../components/hassio-card-content";
import "../resources/hassio-style"; import "../resources/hassio-style";
import EventsMixin from "../../../src/mixins/events-mixin"; import EventsMixin from "../../../src/mixins/events-mixin";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
class HassioSnapshots extends EventsMixin(PolymerElement) { class HassioSnapshots extends EventsMixin(PolymerElement) {
static get template() { static get template() {
return html` return html`
@ -287,6 +289,10 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
} }
_snapshotClicked(ev) { _snapshotClicked(ev) {
showHassioSnapshotDialog(this, {
slug: ev.model.snapshot.slug,
onDelete: () => this._updateSnapshots(),
});
this.snapshotSlug = ev.model.snapshot.slug; this.snapshotSlug = ev.model.snapshot.slug;
} }
@ -294,13 +300,6 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
return type === "full"; return type === "full";
} }
_snapshotDeletedChanged(snapshotDeleted) {
if (snapshotDeleted) {
this._updateSnapshots();
this.snapshotDeleted = false;
}
}
refreshData() { refreshData() {
this.hass.callApi("post", "hassio/snapshots/reload").then(() => { this.hass.callApi("post", "hassio/snapshots/reload").then(() => {
this._updateSnapshots(); this._updateSnapshots();

View File

@ -6,6 +6,8 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/buttons/ha-call-api-button"; import "../../../src/components/buttons/ha-call-api-button";
import EventsMixin from "../../../src/mixins/events-mixin"; import EventsMixin from "../../../src/mixins/events-mixin";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
class HassioHostInfo extends EventsMixin(PolymerElement) { class HassioHostInfo extends EventsMixin(PolymerElement) {
static get template() { static get template() {
return html` return html`
@ -173,7 +175,7 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
() => "Error getting hardware info" () => "Error getting hardware info"
) )
.then((content) => { .then((content) => {
this.fire("hassio-markdown-dialog", { showHassioMarkdownDialog(this, {
title: "Hardware", title: "Hardware",
content: content, content: content,
}); });

View File

@ -77,10 +77,22 @@ export const createHassioSession = async (hass: HomeAssistant) => {
};path=/api/hassio_ingress/`; };path=/api/hassio_ingress/`;
}; };
export const fetchHassioAddonInfo = async ( export const fetchHassioAddonInfo = (hass: HomeAssistant, addon: string) =>
hass: HomeAssistant,
addon: string
) =>
hass hass
.callApi<HassioResponse<HassioAddon>>("GET", `hassio/addons/${addon}/info`) .callApi<HassioResponse<HassioAddon>>("GET", `hassio/addons/${addon}/info`)
.then(hassioApiResultExtractor); .then(hassioApiResultExtractor);
export const fetchHassioSupervisorInfo = (hass: HomeAssistant) =>
hass
.callApi<HassioResponse<any>>("GET", "hassio/supervisor/info")
.then(hassioApiResultExtractor);
export const fetchHassioHostInfo = (hass: HomeAssistant) =>
hass
.callApi<HassioResponse<any>>("GET", "hassio/host/info")
.then(hassioApiResultExtractor);
export const fetchHassioHomeAssistantInfo = (hass: HomeAssistant) =>
hass
.callApi<HassioResponse<any>>("GET", "hassio/homeassistant/info")
.then(hassioApiResultExtractor);

View File

@ -0,0 +1,57 @@
import { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
import { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
declare global {
// for fire event
interface HASSDomEvents {
"show-dialog": ShowDialogParams<unknown>;
}
// for add event listener
interface HTMLElementEventMap {
"show-dialog": HASSDomEvent<ShowDialogParams<unknown>>;
}
}
interface HassDialog<T = HASSDomEvents[ValidHassDomEvent]> extends HTMLElement {
showDialog(params: T);
}
interface ShowDialogParams<T> {
dialogTag: keyof HTMLElementTagNameMap;
dialogImport: () => Promise<unknown>;
dialogParams: T;
}
const LOADED = {};
export const showDialog = async (
element: HTMLElement & ProvideHassElement,
root: ShadowRoot | HTMLElement,
dialogImport: () => Promise<unknown>,
dialogTag: string,
dialogParams: unknown
) => {
if (!(dialogTag in LOADED)) {
LOADED[dialogTag] = dialogImport().then(() => {
const dialogEl = document.createElement(dialogTag) as HassDialog;
element.provideHass(dialogEl);
root.appendChild(dialogEl);
return dialogEl;
});
}
const dialogElement = await LOADED[dialogTag];
dialogElement.showDialog(dialogParams);
};
export const makeDialogManager = (
element: HTMLElement & ProvideHassElement,
root: ShadowRoot | HTMLElement
) => {
element.addEventListener(
"show-dialog",
async (e: HASSDomEvent<ShowDialogParams<unknown>>) => {
const { dialogTag, dialogImport, dialogParams } = e.detail;
showDialog(element, root, dialogImport, dialogTag, dialogParams);
}
);
};

View File

@ -1,6 +1,10 @@
import { Constructor, LitElement } from "lit-element"; import { Constructor, LitElement } from "lit-element";
import { HASSDomEvent, ValidHassDomEvent } from "../../common/dom/fire_event"; import { HASSDomEvent } from "../../common/dom/fire_event";
import { HassBaseEl } from "./hass-base-mixin"; import { HassBaseEl } from "./hass-base-mixin";
import {
makeDialogManager,
showDialog,
} from "../../dialogs/make-dialog-manager";
interface RegisterDialogParams { interface RegisterDialogParams {
dialogShowEvent: keyof HASSDomEvents; dialogShowEvent: keyof HASSDomEvents;
@ -8,31 +12,17 @@ interface RegisterDialogParams {
dialogImport: () => Promise<unknown>; dialogImport: () => Promise<unknown>;
} }
interface ShowDialogParams<T> {
dialogTag: keyof HTMLElementTagNameMap;
dialogImport: () => Promise<unknown>;
dialogParams: T;
}
interface HassDialog<T = HASSDomEvents[ValidHassDomEvent]> extends HTMLElement {
showDialog(params: T);
}
declare global { declare global {
// for fire event // for fire event
interface HASSDomEvents { interface HASSDomEvents {
"register-dialog": RegisterDialogParams; "register-dialog": RegisterDialogParams;
"show-dialog": ShowDialogParams<unknown>;
} }
// for add event listener // for add event listener
interface HTMLElementEventMap { interface HTMLElementEventMap {
"register-dialog": HASSDomEvent<RegisterDialogParams>; "register-dialog": HASSDomEvent<RegisterDialogParams>;
"show-dialog": HASSDomEvent<ShowDialogParams<unknown>>;
} }
} }
const LOADED = {};
export const dialogManagerMixin = ( export const dialogManagerMixin = (
superClass: Constructor<LitElement & HassBaseEl> superClass: Constructor<LitElement & HassBaseEl>
) => ) =>
@ -43,13 +33,7 @@ export const dialogManagerMixin = (
this.addEventListener("register-dialog", (e) => this.addEventListener("register-dialog", (e) =>
this.registerDialog(e.detail) this.registerDialog(e.detail)
); );
this.addEventListener( makeDialogManager(this, this.shadowRoot!);
"show-dialog",
async (e: HASSDomEvent<ShowDialogParams<unknown>>) => {
const { dialogTag, dialogImport, dialogParams } = e.detail;
this._showDialog(dialogImport, dialogTag, dialogParams);
}
);
} }
private registerDialog({ private registerDialog({
@ -58,28 +42,13 @@ export const dialogManagerMixin = (
dialogImport, dialogImport,
}: RegisterDialogParams) { }: RegisterDialogParams) {
this.addEventListener(dialogShowEvent, (showEv) => { this.addEventListener(dialogShowEvent, (showEv) => {
this._showDialog( showDialog(
this,
this.shadowRoot!,
dialogImport, dialogImport,
dialogTag, dialogTag,
(showEv as HASSDomEvent<unknown>).detail (showEv as HASSDomEvent<unknown>).detail
); );
}); });
} }
private async _showDialog(
dialogImport: () => Promise<unknown>,
dialogTag: string,
dialogParams: unknown
) {
if (!(dialogTag in LOADED)) {
LOADED[dialogTag] = dialogImport().then(() => {
const dialogEl = document.createElement(dialogTag) as HassDialog;
this.provideHass(dialogEl);
this.shadowRoot!.appendChild(dialogEl);
return dialogEl;
});
}
const element = await LOADED[dialogTag];
element.showDialog(dialogParams);
}
}; };

View File

@ -14,7 +14,7 @@ export class HassBaseEl {
protected hassDisconnected() {} protected hassDisconnected() {}
protected hassChanged(_hass: HomeAssistant, _oldHass?: HomeAssistant) {} protected hassChanged(_hass: HomeAssistant, _oldHass?: HomeAssistant) {}
protected panelUrlChanged(_newPanelUrl: string) {} protected panelUrlChanged(_newPanelUrl: string) {}
protected provideHass(_el: HTMLElement) {} public provideHass(_el: HTMLElement) {}
protected _updateHass(_obj: Partial<HomeAssistant>) {} protected _updateHass(_obj: Partial<HomeAssistant>) {}
} }
@ -48,7 +48,7 @@ export default <T>(superClass: Constructor<T>): Constructor<T & HassBaseEl> =>
}); });
} }
protected provideHass(el) { public provideHass(el) {
this.__provideHass.push(el); this.__provideHass.push(el);
el.hass = this.hass; el.hass = this.hass;
} }

View File

@ -1,5 +1,6 @@
import { UpdatingElement, property, PropertyValues } from "lit-element"; import { UpdatingElement, property, PropertyValues } from "lit-element";
import "./hass-error-screen"; import "./hass-error-screen";
import "./hass-loading-screen";
import { Route } from "../types"; import { Route } from "../types";
import { navigate } from "../common/navigate"; import { navigate } from "../common/navigate";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@ -15,18 +16,27 @@ const extractPage = (path: string, defaultPage: string) => {
}; };
export interface RouteOptions { export interface RouteOptions {
// HTML tag of the route page.
tag: string; tag: string;
// Function to load the page.
load: () => Promise<unknown>; load: () => Promise<unknown>;
cache?: boolean; cache?: boolean;
} }
export interface RouterOptions { export interface RouterOptions {
// The default route to show if path does not define a page.
defaultPage?: string; defaultPage?: string;
// If all routes should be preloaded
preloadAll?: boolean; preloadAll?: boolean;
// If a route has been shown, should we keep the element in memory
cacheAll?: boolean; cacheAll?: boolean;
// Should we show a loading spinner while we load the element for the route
showLoading?: boolean; showLoading?: boolean;
// Promise that resolves when the initial data is loaded which is needed to show any route.
initialLoad?: () => Promise<unknown>;
routes: { routes: {
[route: string]: RouteOptions; // If it's a string, it is another route whose options should be adopted.
[route: string]: RouteOptions | string;
}; };
} }
@ -48,6 +58,7 @@ export class HassRouterPage extends UpdatingElement {
private _currentPage = ""; private _currentPage = "";
private _currentLoadProm?: Promise<void>; private _currentLoadProm?: Promise<void>;
private _cache = {}; private _cache = {};
private _initialLoadDone = false;
private _computeTail = memoizeOne((route: Route) => { private _computeTail = memoizeOne((route: Route) => {
const dividerPos = route.path.indexOf("/", 1); const dividerPos = route.path.indexOf("/", 1);
return dividerPos === -1 return dividerPos === -1
@ -64,6 +75,12 @@ export class HassRouterPage extends UpdatingElement {
protected update(changedProps: PropertyValues) { protected update(changedProps: PropertyValues) {
super.update(changedProps); super.update(changedProps);
const routerOptions = this.routerOptions || { routes: {} };
if (routerOptions && routerOptions.initialLoad && !this._initialLoadDone) {
return;
}
if (!changedProps.has("route")) { if (!changedProps.has("route")) {
// Do not update if we have a currentLoadProm, because that means // Do not update if we have a currentLoadProm, because that means
// that there is still an old panel shown and we're moving to a new one. // that there is still an old panel shown and we're moving to a new one.
@ -74,14 +91,20 @@ export class HassRouterPage extends UpdatingElement {
} }
const route = this.route; const route = this.route;
const routerOptions = this.routerOptions || { routes: {} };
const defaultPage = routerOptions.defaultPage || ""; const defaultPage = routerOptions.defaultPage || "";
if (route && route.path === "") { if (route && route.path === "") {
navigate(this, `${route.prefix}/${defaultPage}`, true); navigate(this, `${route.prefix}/${defaultPage}`, true);
} }
const newPage = route ? extractPage(route.path, defaultPage) : "not_found"; let newPage = route ? extractPage(route.path, defaultPage) : "not_found";
let routeOptions = routerOptions.routes[newPage];
// Handle redirects
while (typeof routeOptions === "string") {
newPage = routeOptions;
routeOptions = routerOptions.routes[newPage];
}
if (this._currentPage === newPage) { if (this._currentPage === newPage) {
if (this.lastChild) { if (this.lastChild) {
@ -90,8 +113,6 @@ export class HassRouterPage extends UpdatingElement {
return; return;
} }
const routeOptions = routerOptions.routes[newPage];
if (!routeOptions) { if (!routeOptions) {
this._currentPage = ""; this._currentPage = "";
if (this.lastChild) { if (this.lastChild) {
@ -151,7 +172,12 @@ export class HassRouterPage extends UpdatingElement {
} }
created = true; created = true;
this._createPanel(routerOptions, newPage, routeOptions); this._createPanel(
routerOptions,
newPage,
// @ts-ignore TS forgot this is not a string.
routeOptions
);
}, },
() => { () => {
this._currentLoadProm = undefined; this._currentLoadProm = undefined;
@ -164,10 +190,28 @@ export class HassRouterPage extends UpdatingElement {
const options = this.routerOptions; const options = this.routerOptions;
if (options && options.preloadAll) { if (!options) {
Object.values(options.routes).forEach((route) => route.load());
return; return;
} }
if (options.preloadAll) {
Object.values(options.routes).forEach(
(route) => typeof route === "object" && route.load()
);
}
if (options.initialLoad) {
setTimeout(() => {
if (!this._initialLoadDone) {
this.appendChild(this.createLoadingScreen());
}
}, LOADING_SCREEN_THRESHOLD);
options.initialLoad().then(() => {
this._initialLoadDone = true;
this.requestUpdate("route");
});
}
} }
protected createLoadingScreen() { protected createLoadingScreen() {

View File

@ -74,6 +74,8 @@ class PartialPanelResolver extends HassRouterPage {
@property() public narrow?: boolean; @property() public narrow?: boolean;
protected updated(changedProps: PropertyValues) { protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!changedProps.has("hass")) { if (!changedProps.has("hass")) {
return; return;
} }

View File

@ -0,0 +1,32 @@
import { UpdatingElement, Constructor, PropertyValues } from "lit-element";
import { HomeAssistant } from "../types";
export interface ProvideHassElement {
provideHass(element: HTMLElement);
}
/* tslint:disable */
export const ProvideHassLitMixin = <T extends UpdatingElement>(
superClass: Constructor<T>
): Constructor<T & ProvideHassElement> =>
// @ts-ignore
class extends superClass {
protected hass!: HomeAssistant;
private __provideHass: HTMLElement[] = [];
public provideHass(el) {
this.__provideHass.push(el);
el.hass = this.hass;
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("hass")) {
this.__provideHass.forEach((el) => {
(el as any).hass = this.hass;
});
}
}
};