mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +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";
|
declare global {
|
||||||
import NavigateMixin from "../../mixins/navigate-mixin";
|
interface Window {
|
||||||
import loadCustomPanel from "../../util/custom-panel/load-custom-panel";
|
customPanel: HaPanelCustom | undefined;
|
||||||
import createCustomPanelElement from "../../util/custom-panel/create-custom-panel-element";
|
}
|
||||||
import setCustomPanelProperties from "../../util/custom-panel/set-custom-panel-properties";
|
}
|
||||||
|
|
||||||
/*
|
export class HaPanelCustom extends UpdatingElement {
|
||||||
* Mixins are used by ifram to communicate with main frontend.
|
@property() public hass!: HomeAssistant;
|
||||||
* @appliesMixin EventsMixin
|
@property() public narrow!: boolean;
|
||||||
* @appliesMixin NavigateMixin
|
@property() public route!: Route;
|
||||||
*/
|
@property() public panel!: Panel;
|
||||||
class HaPanelCustom extends NavigateMixin(EventsMixin(PolymerElement)) {
|
private _setProperties?: (props: {}) => void | undefined;
|
||||||
static get properties() {
|
|
||||||
return {
|
public registerIframe(initialize, setProperties) {
|
||||||
hass: Object,
|
initialize(this.panel, {
|
||||||
narrow: Boolean,
|
hass: this.hass,
|
||||||
route: Object,
|
narrow: this.narrow,
|
||||||
panel: {
|
route: this.route,
|
||||||
type: Object,
|
});
|
||||||
observer: "_panelChanged",
|
this._setProperties = setProperties;
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get observers() {
|
public disconnectedCallback() {
|
||||||
return ["_dataChanged(hass, narrow, route)"];
|
super.disconnectedCallback();
|
||||||
|
this._cleanupPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
protected updated(changedProps: PropertyValues) {
|
||||||
super();
|
if (changedProps.has("panel")) {
|
||||||
this._setProperties = null;
|
// 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) {
|
private _cleanupPanel() {
|
||||||
// Clean up
|
|
||||||
delete window.customPanel;
|
delete window.customPanel;
|
||||||
this._setProperties = null;
|
this._setProperties = undefined;
|
||||||
while (this.lastChild) {
|
while (this.lastChild) {
|
||||||
this.removeChild(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");
|
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 (
|
if (
|
||||||
!config.trust_external &&
|
!config.trust_external &&
|
||||||
@ -96,32 +115,13 @@ It will have access to all data in Home Assistant.
|
|||||||
</style>
|
</style>
|
||||||
<iframe></iframe>
|
<iframe></iframe>
|
||||||
`.trim();
|
`.trim();
|
||||||
const iframeDoc = this.querySelector("iframe").contentWindow.document;
|
const iframeDoc = this.querySelector("iframe")!.contentWindow!.document;
|
||||||
iframeDoc.open();
|
iframeDoc.open();
|
||||||
iframeDoc.write(
|
iframeDoc.write(
|
||||||
`<!doctype html><script src='${window.customPanelJS}'></script>`
|
`<!doctype html><script src='${window.customPanelJS}'></script>`
|
||||||
);
|
);
|
||||||
iframeDoc.close();
|
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);
|
customElements.define("ha-panel-custom", HaPanelCustom);
|
@ -15,10 +15,13 @@ declare global {
|
|||||||
var __DEMO__: boolean;
|
var __DEMO__: boolean;
|
||||||
var __BUILD__: "latest" | "es5";
|
var __BUILD__: "latest" | "es5";
|
||||||
var __VERSION__: string;
|
var __VERSION__: string;
|
||||||
|
var __STATIC_PATH__: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
// Custom panel entry point url
|
||||||
|
customPanelJS: string;
|
||||||
ShadyCSS: {
|
ShadyCSS: {
|
||||||
nativeCss: boolean;
|
nativeCss: boolean;
|
||||||
nativeShadow: 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}
|
// Legacy support. Custom panels used to have to define element ha-panel-{name}
|
||||||
const tagName =
|
const tagName =
|
||||||
"html_url" in panelConfig
|
"html_url" in panelConfig
|
||||||
? `ha-panel-${panelConfig.name}`
|
? `ha-panel-${panelConfig.name}`
|
||||||
: panelConfig.name;
|
: panelConfig.name;
|
||||||
return document.createElement(tagName);
|
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)
|
// Make sure we only import every JS-based panel once (HTML import has this built-in)
|
||||||
const JS_CACHE = {};
|
const JS_CACHE = {};
|
||||||
|
|
||||||
export default function loadCustomPanel(panelConfig) {
|
export const loadCustomPanel = (panelConfig): Promise<unknown> => {
|
||||||
if (panelConfig.html_url) {
|
if (panelConfig.html_url) {
|
||||||
const toLoad = [
|
const toLoad = [
|
||||||
import(/* webpackChunkName: "import-href-polyfill" */ "../../resources/html-import/import-href"),
|
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 loadModule(panelConfig.module_url);
|
||||||
}
|
}
|
||||||
return Promise.reject("No valid url found in panel config.");
|
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) {
|
if ("setProperties" in root) {
|
||||||
root.setProperties(properties);
|
root.setProperties(properties);
|
||||||
} else {
|
} else {
|
||||||
@ -6,4 +6,4 @@ export default function setCustomPanelProperties(root, properties) {
|
|||||||
root[key] = properties[key];
|
root[key] = properties[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
@ -44,7 +44,7 @@ function createConfig(isProdBuild, latestBuild) {
|
|||||||
onboarding: "./src/entrypoints/onboarding.ts",
|
onboarding: "./src/entrypoints/onboarding.ts",
|
||||||
core: "./src/entrypoints/core.ts",
|
core: "./src/entrypoints/core.ts",
|
||||||
compatibility: "./src/entrypoints/compatibility.js",
|
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",
|
"hass-icons": "./src/entrypoints/hass-icons.js",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ function createConfig(isProdBuild, latestBuild) {
|
|||||||
include: [
|
include: [
|
||||||
/core.ts$/,
|
/core.ts$/,
|
||||||
/app.js$/,
|
/app.js$/,
|
||||||
/custom-panel.js$/,
|
/custom-panel.ts$/,
|
||||||
/hass-icons.js$/,
|
/hass-icons.js$/,
|
||||||
/\.chunk\.js$/,
|
/\.chunk\.js$/,
|
||||||
],
|
],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user