mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 01:06:35 +00:00
Convert custom panel to typescript (#2991)
* Convert custom panel to typescript * Address comments
This commit is contained in:
parent
e2a9cf0d3c
commit
702c17d658
8
src/data/panel_custom.ts
Normal file
8
src/data/panel_custom.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export interface CustomPanelConfig {
|
||||
name: string;
|
||||
embed_iframe: boolean;
|
||||
trust_external: boolean;
|
||||
js_url?: string;
|
||||
module_url?: string;
|
||||
html_url?: string;
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import { loadJS } from "../common/dom/load_resource";
|
||||
import loadCustomPanel from "../util/custom-panel/load-custom-panel";
|
||||
import createCustomPanelElement from "../util/custom-panel/create-custom-panel-element";
|
||||
import setCustomPanelProperties from "../util/custom-panel/set-custom-panel-properties";
|
||||
|
||||
const webComponentsSupported =
|
||||
"customElements" in window &&
|
||||
"import" in document.createElement("link") &&
|
||||
"content" in document.createElement("template");
|
||||
|
||||
let es5Loaded = null;
|
||||
|
||||
window.loadES5Adapter = () => {
|
||||
if (!es5Loaded) {
|
||||
es5Loaded = Promise.all([
|
||||
loadJS(`${__STATIC_PATH__}custom-elements-es5-adapter.js`).catch(),
|
||||
import(/* webpackChunkName: "compat" */ "./compatibility"),
|
||||
]);
|
||||
}
|
||||
return es5Loaded;
|
||||
};
|
||||
|
||||
let root = null;
|
||||
|
||||
function setProperties(properties) {
|
||||
if (root === null) return;
|
||||
setCustomPanelProperties(root, properties);
|
||||
}
|
||||
|
||||
function initialize(panel, properties) {
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = "body{margin:0}";
|
||||
document.head.appendChild(style);
|
||||
|
||||
const config = panel.config._panel_custom;
|
||||
let start = Promise.resolve();
|
||||
|
||||
if (!webComponentsSupported) {
|
||||
start = start.then(() => loadJS("/static/webcomponents-bundle.js"));
|
||||
}
|
||||
|
||||
if (__BUILD__ === "es5") {
|
||||
// Load ES5 adapter. Swallow errors as it raises errors on old browsers.
|
||||
start = start.then(() => window.loadES5Adapter());
|
||||
}
|
||||
|
||||
start
|
||||
.then(() => loadCustomPanel(config))
|
||||
// If our element is using es5, let it finish loading that and define element
|
||||
// This avoids elements getting upgraded after being added to the DOM
|
||||
.then(() => es5Loaded || Promise.resolve())
|
||||
.then(
|
||||
() => {
|
||||
root = createCustomPanelElement(config);
|
||||
|
||||
const forwardEvent = (ev) =>
|
||||
window.parent.customPanel.fire(ev.type, ev.detail);
|
||||
root.addEventListener("hass-toggle-menu", forwardEvent);
|
||||
window.addEventListener("location-changed", (ev) =>
|
||||
window.parent.customPanel.navigate(
|
||||
window.location.pathname,
|
||||
ev.detail ? ev.detail.replace : false
|
||||
)
|
||||
);
|
||||
setProperties(Object.assign({ panel }, properties));
|
||||
document.body.appendChild(root);
|
||||
},
|
||||
(err) => {
|
||||
// eslint-disable-next-line
|
||||
console.error(err, panel);
|
||||
alert(`Unable to load the panel source: ${err}.`);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
() => window.parent.customPanel.registerIframe(initialize, setProperties),
|
||||
{ once: true }
|
||||
);
|
97
src/entrypoints/custom-panel.ts
Normal file
97
src/entrypoints/custom-panel.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { loadJS } from "../common/dom/load_resource";
|
||||
import { loadCustomPanel } from "../util/custom-panel/load-custom-panel";
|
||||
import { createCustomPanelElement } from "../util/custom-panel/create-custom-panel-element";
|
||||
import { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { PolymerElement } from "@polymer/polymer";
|
||||
import { Panel } from "../types";
|
||||
import { CustomPanelConfig } from "../data/panel_custom";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
loadES5Adapter: () => Promise<unknown>;
|
||||
}
|
||||
}
|
||||
|
||||
const webComponentsSupported =
|
||||
"customElements" in window &&
|
||||
"import" in document.createElement("link") &&
|
||||
"content" in document.createElement("template");
|
||||
|
||||
let es5Loaded: Promise<unknown> | undefined;
|
||||
|
||||
window.loadES5Adapter = () => {
|
||||
if (!es5Loaded) {
|
||||
es5Loaded = Promise.all([
|
||||
loadJS(`${__STATIC_PATH__}custom-elements-es5-adapter.js`).catch(),
|
||||
import(/* webpackChunkName: "compat" */ "./compatibility"),
|
||||
]);
|
||||
}
|
||||
return es5Loaded;
|
||||
};
|
||||
|
||||
let panelEl: HTMLElement | PolymerElement | undefined;
|
||||
|
||||
function setProperties(properties) {
|
||||
if (!panelEl) {
|
||||
return;
|
||||
}
|
||||
setCustomPanelProperties(panelEl, properties);
|
||||
}
|
||||
|
||||
function initialize(panel: Panel, properties: {}) {
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = "body{margin:0}";
|
||||
document.head.appendChild(style);
|
||||
|
||||
const config = panel.config!._panel_custom as CustomPanelConfig;
|
||||
let start: Promise<unknown> = Promise.resolve();
|
||||
|
||||
if (!webComponentsSupported) {
|
||||
start = start.then(() => loadJS("/static/webcomponents-bundle.js"));
|
||||
}
|
||||
|
||||
if (__BUILD__ === "es5") {
|
||||
// Load ES5 adapter. Swallow errors as it raises errors on old browsers.
|
||||
start = start.then(() => window.loadES5Adapter());
|
||||
}
|
||||
|
||||
start
|
||||
.then(() => loadCustomPanel(config))
|
||||
// If our element is using es5, let it finish loading that and define element
|
||||
// This avoids elements getting upgraded after being added to the DOM
|
||||
.then(() => es5Loaded || Promise.resolve())
|
||||
.then(
|
||||
() => {
|
||||
panelEl = createCustomPanelElement(config);
|
||||
|
||||
const forwardEvent = (ev) => {
|
||||
if (window.parent.customPanel) {
|
||||
fireEvent(window.parent.customPanel, ev.type, ev.detail);
|
||||
}
|
||||
};
|
||||
panelEl!.addEventListener("hass-toggle-menu", forwardEvent);
|
||||
window.addEventListener("location-changed", (ev: any) =>
|
||||
navigate(
|
||||
window.parent.customPanel,
|
||||
window.location.pathname,
|
||||
ev.detail ? ev.detail.replace : false
|
||||
)
|
||||
);
|
||||
setProperties({ panel, ...properties });
|
||||
document.body.appendChild(panelEl!);
|
||||
},
|
||||
(err) => {
|
||||
// tslint:disable-next-line
|
||||
console.error(err, panel);
|
||||
alert(`Unable to load the panel source: ${err}.`);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
() => window.parent.customPanel!.registerIframe(initialize, setProperties),
|
||||
{ once: true }
|
||||
);
|
@ -1,50 +1,69 @@
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { property, PropertyValues, UpdatingElement } from "lit-element";
|
||||
import { loadCustomPanel } from "../../util/custom-panel/load-custom-panel";
|
||||
import { createCustomPanelElement } from "../../util/custom-panel/create-custom-panel-element";
|
||||
import { setCustomPanelProperties } from "../../util/custom-panel/set-custom-panel-properties";
|
||||
import { HomeAssistant, Route, Panel } from "../../types";
|
||||
import { CustomPanelConfig } from "../../data/panel_custom";
|
||||
|
||||
import EventsMixin from "../../mixins/events-mixin";
|
||||
import NavigateMixin from "../../mixins/navigate-mixin";
|
||||
import loadCustomPanel from "../../util/custom-panel/load-custom-panel";
|
||||
import createCustomPanelElement from "../../util/custom-panel/create-custom-panel-element";
|
||||
import setCustomPanelProperties from "../../util/custom-panel/set-custom-panel-properties";
|
||||
declare global {
|
||||
interface Window {
|
||||
customPanel: HaPanelCustom | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Mixins are used by ifram to communicate with main frontend.
|
||||
* @appliesMixin EventsMixin
|
||||
* @appliesMixin NavigateMixin
|
||||
*/
|
||||
class HaPanelCustom extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
narrow: Boolean,
|
||||
route: Object,
|
||||
panel: {
|
||||
type: Object,
|
||||
observer: "_panelChanged",
|
||||
},
|
||||
};
|
||||
export class HaPanelCustom extends UpdatingElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public narrow!: boolean;
|
||||
@property() public route!: Route;
|
||||
@property() public panel!: Panel;
|
||||
private _setProperties?: (props: {}) => void | undefined;
|
||||
|
||||
public registerIframe(initialize, setProperties) {
|
||||
initialize(this.panel, {
|
||||
hass: this.hass,
|
||||
narrow: this.narrow,
|
||||
route: this.route,
|
||||
});
|
||||
this._setProperties = setProperties;
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return ["_dataChanged(hass, narrow, route)"];
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._cleanupPanel();
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._setProperties = null;
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("panel")) {
|
||||
// Clean up old things if we had a panel
|
||||
if (changedProps.get("panel")) {
|
||||
this._cleanupPanel();
|
||||
}
|
||||
this._createPanel(this.panel);
|
||||
return;
|
||||
}
|
||||
if (!this._setProperties) {
|
||||
return;
|
||||
}
|
||||
const props = {};
|
||||
for (const key of changedProps.keys()) {
|
||||
props[key] = this[key];
|
||||
}
|
||||
this._setProperties(props);
|
||||
}
|
||||
|
||||
_panelChanged(panel) {
|
||||
// Clean up
|
||||
private _cleanupPanel() {
|
||||
delete window.customPanel;
|
||||
this._setProperties = null;
|
||||
this._setProperties = undefined;
|
||||
while (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
const config = panel.config._panel_custom;
|
||||
private _createPanel(panel: Panel) {
|
||||
const config = panel.config!._panel_custom as CustomPanelConfig;
|
||||
|
||||
const tempA = document.createElement("a");
|
||||
tempA.href = config.html_url || config.js_url || config.module_url;
|
||||
tempA.href = config.html_url || config.js_url || config.module_url || "";
|
||||
|
||||
if (
|
||||
!config.trust_external &&
|
||||
@ -96,32 +115,13 @@ It will have access to all data in Home Assistant.
|
||||
</style>
|
||||
<iframe></iframe>
|
||||
`.trim();
|
||||
const iframeDoc = this.querySelector("iframe").contentWindow.document;
|
||||
const iframeDoc = this.querySelector("iframe")!.contentWindow!.document;
|
||||
iframeDoc.open();
|
||||
iframeDoc.write(
|
||||
`<!doctype html><script src='${window.customPanelJS}'></script>`
|
||||
);
|
||||
iframeDoc.close();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
delete window.customPanel;
|
||||
}
|
||||
|
||||
_dataChanged(hass, narrow, route) {
|
||||
if (!this._setProperties) return;
|
||||
this._setProperties({ hass, narrow, route });
|
||||
}
|
||||
|
||||
registerIframe(initialize, setProperties) {
|
||||
initialize(this.panel, {
|
||||
hass: this.hass,
|
||||
narrow: this.narrow,
|
||||
route: this.route,
|
||||
});
|
||||
this._setProperties = setProperties;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-panel-custom", HaPanelCustom);
|
@ -15,10 +15,13 @@ declare global {
|
||||
var __DEMO__: boolean;
|
||||
var __BUILD__: "latest" | "es5";
|
||||
var __VERSION__: string;
|
||||
var __STATIC_PATH__: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
// Custom panel entry point url
|
||||
customPanelJS: string;
|
||||
ShadyCSS: {
|
||||
nativeCss: boolean;
|
||||
nativeShadow: boolean;
|
||||
|
@ -1,8 +1,8 @@
|
||||
export default function createCustomPanelElement(panelConfig) {
|
||||
export const createCustomPanelElement = (panelConfig) => {
|
||||
// Legacy support. Custom panels used to have to define element ha-panel-{name}
|
||||
const tagName =
|
||||
"html_url" in panelConfig
|
||||
? `ha-panel-${panelConfig.name}`
|
||||
: panelConfig.name;
|
||||
return document.createElement(tagName);
|
||||
}
|
||||
};
|
@ -3,7 +3,7 @@ import { loadJS, loadModule } from "../../common/dom/load_resource";
|
||||
// Make sure we only import every JS-based panel once (HTML import has this built-in)
|
||||
const JS_CACHE = {};
|
||||
|
||||
export default function loadCustomPanel(panelConfig) {
|
||||
export const loadCustomPanel = (panelConfig): Promise<unknown> => {
|
||||
if (panelConfig.html_url) {
|
||||
const toLoad = [
|
||||
import(/* webpackChunkName: "import-href-polyfill" */ "../../resources/html-import/import-href"),
|
||||
@ -29,4 +29,4 @@ export default function loadCustomPanel(panelConfig) {
|
||||
return loadModule(panelConfig.module_url);
|
||||
}
|
||||
return Promise.reject("No valid url found in panel config.");
|
||||
}
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
export default function setCustomPanelProperties(root, properties) {
|
||||
export const setCustomPanelProperties = (root, properties) => {
|
||||
if ("setProperties" in root) {
|
||||
root.setProperties(properties);
|
||||
} else {
|
||||
@ -6,4 +6,4 @@ export default function setCustomPanelProperties(root, properties) {
|
||||
root[key] = properties[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
@ -44,7 +44,7 @@ function createConfig(isProdBuild, latestBuild) {
|
||||
onboarding: "./src/entrypoints/onboarding.ts",
|
||||
core: "./src/entrypoints/core.ts",
|
||||
compatibility: "./src/entrypoints/compatibility.js",
|
||||
"custom-panel": "./src/entrypoints/custom-panel.js",
|
||||
"custom-panel": "./src/entrypoints/custom-panel.ts",
|
||||
"hass-icons": "./src/entrypoints/hass-icons.js",
|
||||
};
|
||||
|
||||
@ -156,7 +156,7 @@ function createConfig(isProdBuild, latestBuild) {
|
||||
include: [
|
||||
/core.ts$/,
|
||||
/app.js$/,
|
||||
/custom-panel.js$/,
|
||||
/custom-panel.ts$/,
|
||||
/hass-icons.js$/,
|
||||
/\.chunk\.js$/,
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user